Feat: 添加 LLM 反垃圾检测#1017
Conversation
Reviewer's Guide添加一个可选的、基于 LLM 的垃圾评论检测层,使用兼容 OpenAI 的 SDK,在现有的垃圾检测流程中接入到腾讯云与 Akismet 之后;在管理后台 UI / i18n 中暴露相关配置,并在常见问题(FAQs)中编写使用文档。 使用 LLM 的更新版垃圾检测流程时序图sequenceDiagram
actor Commenter
participant TwikooServer
participant TencentCloud
participant Akismet
participant OpenAI_LLM
Commenter->>TwikooServer: postCheckSpam(comment, config)
alt config.TENCENT_SECRET_ID && config.TENCENT_SECRET_KEY
TwikooServer->>TencentCloud: getTencentCloud()
TwikooServer->>TencentCloud: moderate(comment)
else config.AKISMET_KEY
TwikooServer->>Akismet: getAkismetClient()
TwikooServer->>Akismet: checkSpam(comment)
else config.LLM_API_KEY
TwikooServer->>TwikooServer: checkByLLM(comment, config)
TwikooServer->>TwikooServer: getOpenAI(config)
TwikooServer->>OpenAI_LLM: openai.chat.completions.create(model, messages)
OpenAI_LLM-->>TwikooServer: choices[0].message.content
TwikooServer->>TwikooServer: extractJson(rawText)
TwikooServer->>TwikooServer: repairJson(jsonStr)
TwikooServer->>TwikooServer: validateJson(jsonStr)
note over TwikooServer: Retry loop up to config.LLM_MAX_RETRIES
else no spam provider configured
TwikooServer->>TwikooServer: isSpam = false
end
TwikooServer-->>Commenter: isSpam
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 来:
Getting HelpOriginal review guide in EnglishReviewer's GuideAdd an optional LLM-based spam detection layer using an OpenAI-compatible SDK, wire it into the existing spam-check flow after Tencent Cloud and Akismet, expose configuration in the admin UI/i18n, and document usage in FAQs. Sequence diagram for updated spam detection flow with LLMsequenceDiagram
actor Commenter
participant TwikooServer
participant TencentCloud
participant Akismet
participant OpenAI_LLM
Commenter->>TwikooServer: postCheckSpam(comment, config)
alt config.TENCENT_SECRET_ID && config.TENCENT_SECRET_KEY
TwikooServer->>TencentCloud: getTencentCloud()
TwikooServer->>TencentCloud: moderate(comment)
else config.AKISMET_KEY
TwikooServer->>Akismet: getAkismetClient()
TwikooServer->>Akismet: checkSpam(comment)
else config.LLM_API_KEY
TwikooServer->>TwikooServer: checkByLLM(comment, config)
TwikooServer->>TwikooServer: getOpenAI(config)
TwikooServer->>OpenAI_LLM: openai.chat.completions.create(model, messages)
OpenAI_LLM-->>TwikooServer: choices[0].message.content
TwikooServer->>TwikooServer: extractJson(rawText)
TwikooServer->>TwikooServer: repairJson(jsonStr)
TwikooServer->>TwikooServer: validateJson(jsonStr)
note over TwikooServer: Retry loop up to config.LLM_MAX_RETRIES
else no spam provider configured
TwikooServer->>TwikooServer: isSpam = false
end
TwikooServer-->>Commenter: isSpam
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 3 个问题,并给出了一些整体性反馈:
postCheckSpam中的垃圾信息检测优先级(腾讯 → Akismet → LLM)与 PR 描述以及管理后台 UI 中提示的「LLM 优先」不一致;请让实际分支执行顺序与文档/界面提示保持一致。- 在
checkByLLM中使用answer.includes('SPAM'),会导致像"NOT SPAM"这样的回复被错误地判定为垃圾信息;建议改为使用严格相等(例如answer === 'SPAM')以符合提示语中的要求。 - 管理配置中对
LLM_API_ENDPOINT的描述称其默认使用 OpenAI,但实现中使用的是https://api.deepseek.com作为后备 base URL;请更新代码或说明文字,使默认行为保持一致且不具误导性。
给 AI Agents 的提示
Please address the comments from this code review:
## Overall Comments
- `postCheckSpam` 中的垃圾信息检测优先级(腾讯 → Akismet → LLM)与 PR 描述以及管理后台 UI 中提示的「LLM 优先」不一致;请让实际分支执行顺序与文档/界面提示保持一致。
- 在 `checkByLLM` 中使用 `answer.includes('SPAM')`,会导致像 `"NOT SPAM"` 这样的回复被错误地判定为垃圾信息;建议改为使用严格相等(例如 `answer === 'SPAM'`)以符合提示语中的要求。
- 管理配置中对 `LLM_API_ENDPOINT` 的描述称其默认使用 OpenAI,但实现中使用的是 `https://api.deepseek.com` 作为后备 base URL;请更新代码或说明文字,使默认行为保持一致且不具误导性。
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="68-69" />
<code_context>
+ model: config.LLM_MODEL || 'deepseek-v4-pro',
+ messages: [{ role: 'user', content }]
+ })
+ const answer = (chatCompletion.choices[0].message.content || '').trim().toUpperCase()
+ if (answer.includes('SPAM')) {
+ logger.info(`LLM 判定为 SPAM: id="${comment.id}" nick="${comment.nick}"`)
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** 使用 `includes('SPAM')` 会把像 `NOT SPAM` 这样的响应也判定为垃圾信息。
由于模型有可能合理地返回类似 `NOT SPAM` 的短语,使用 `answer.includes('SPAM')` 会错误地将这些结果标记为垃圾信息。为了避免这类误报,建议采用更严格的检查方式,例如 `answer === 'SPAM'`,或使用更受限的匹配模式(例如 `answer.startsWith('SPAM')`,同时显式排除 `NOT SPAM`/`HAM`)。
</issue_to_address>
### Comment 2
<location path="src/client/view/components/TkAdminConfig.vue" line_range="138" />
<code_context>
- ]
+ { key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' },
+ { key: 'LLM_API_KEY', desc: 'LLM API Key, used for AI spam comment detection. Takes precedence over Akismet and Tencent Cloud when configured.', ph: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', value: '', secret: true },
+ { key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to OpenAI', ph: 'https://api.deepseek.com', value: '' },
+ { key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-flash', value: '' },
+ { key: 'LLM_SPAM_PROMPT', desc: 'LLM spam detection prompt (optional, uses default if left empty)', ph: '', value: '' } ]
</code_context>
<issue_to_address>
**suggestion:** 端点描述和默认值与后端默认配置不一致。
说明中写的是端点“默认为 OpenAI”,但占位符和 `checkByLLM` 中默认的 `baseURL` 都是 `https://api.deepseek.com`。请将说明与实际默认值对齐(例如注明 DeepSeek),或者如果默认应为 OpenAI,则更新后端默认值,以确保配置行为清晰明确、不产生歧义。
```suggestion
{ key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to DeepSeek', ph: 'https://api.deepseek.com', value: '' },
```
</issue_to_address>
### Comment 3
<location path="src/client/view/components/TkAdminConfig.vue" line_range="139" />
<code_context>
+ { key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' },
+ { key: 'LLM_API_KEY', desc: 'LLM API Key, used for AI spam comment detection. Takes precedence over Akismet and Tencent Cloud when configured.', ph: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', value: '', secret: true },
+ { key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to OpenAI', ph: 'https://api.deepseek.com', value: '' },
+ { key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-flash', value: '' },
+ { key: 'LLM_SPAM_PROMPT', desc: 'LLM spam detection prompt (optional, uses default if left empty)', ph: '', value: '' } ]
},
</code_context>
<issue_to_address>
**suggestion:** UI 中的 LLM 模型占位符与后端默认模型不一致。
在 `checkByLLM` 中,当 `config.LLM_MODEL` 未设置时,默认模型为 `deepseek-v4-pro`,但管理后台 UI 中的占位符却是 `deepseek-v4-flash`。这种不一致可能会误导管理员对实际使用模型的判断。请更新后端默认值或 UI 占位符,使二者保持一致。
```suggestion
{ key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-pro', value: '' },
```
</issue_to_address>帮我变得更有用!请在每条评论上点选 👍 或 👎,我会根据你的反馈改进后续的评审质量。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The spam-check priority in
postCheckSpam(Tencent → Akismet → LLM) conflicts with both the PR description and the admin UI hint that LLM takes precedence; align the actual branching order with the documented/UX expectation. - In
checkByLLM, usinganswer.includes('SPAM')means a reply like"NOT SPAM"will be treated as spam; consider enforcing strict equality (e.g.,answer === 'SPAM') to match the prompt instructions. - The admin config description for
LLM_API_ENDPOINTsays it defaults to OpenAI, but the implementation useshttps://api.deepseek.comas the fallback base URL; update either the code or the label so that the default behavior is consistent and not misleading.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The spam-check priority in `postCheckSpam` (Tencent → Akismet → LLM) conflicts with both the PR description and the admin UI hint that LLM takes precedence; align the actual branching order with the documented/UX expectation.
- In `checkByLLM`, using `answer.includes('SPAM')` means a reply like `"NOT SPAM"` will be treated as spam; consider enforcing strict equality (e.g., `answer === 'SPAM'`) to match the prompt instructions.
- The admin config description for `LLM_API_ENDPOINT` says it defaults to OpenAI, but the implementation uses `https://api.deepseek.com` as the fallback base URL; update either the code or the label so that the default behavior is consistent and not misleading.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="68-69" />
<code_context>
+ model: config.LLM_MODEL || 'deepseek-v4-pro',
+ messages: [{ role: 'user', content }]
+ })
+ const answer = (chatCompletion.choices[0].message.content || '').trim().toUpperCase()
+ if (answer.includes('SPAM')) {
+ logger.info(`LLM 判定为 SPAM: id="${comment.id}" nick="${comment.nick}"`)
+ }
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `includes('SPAM')` will classify responses like `NOT SPAM` as spam.
Because the model may legitimately return phrases like `NOT SPAM`, using `answer.includes('SPAM')` will incorrectly flag these as spam. To avoid these false positives, use a stricter check such as `answer === 'SPAM'` or a more constrained pattern (e.g. `answer.startsWith('SPAM')` while explicitly excluding `NOT SPAM`/`HAM`).
</issue_to_address>
### Comment 2
<location path="src/client/view/components/TkAdminConfig.vue" line_range="138" />
<code_context>
- ]
+ { key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' },
+ { key: 'LLM_API_KEY', desc: 'LLM API Key, used for AI spam comment detection. Takes precedence over Akismet and Tencent Cloud when configured.', ph: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', value: '', secret: true },
+ { key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to OpenAI', ph: 'https://api.deepseek.com', value: '' },
+ { key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-flash', value: '' },
+ { key: 'LLM_SPAM_PROMPT', desc: 'LLM spam detection prompt (optional, uses default if left empty)', ph: '', value: '' } ]
</code_context>
<issue_to_address>
**suggestion:** The endpoint description and default value are inconsistent with the backend default.
The description says the endpoint "defaults to OpenAI" but the placeholder and `checkByLLM` default `baseURL` are `https://api.deepseek.com`. Please align the description with the actual default (e.g., mention DeepSeek) or update the backend default if OpenAI is intended, so configuration behavior is unambiguous.
```suggestion
{ key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to DeepSeek', ph: 'https://api.deepseek.com', value: '' },
```
</issue_to_address>
### Comment 3
<location path="src/client/view/components/TkAdminConfig.vue" line_range="139" />
<code_context>
+ { key: 'NOTIFY_SPAM', desc: t('ADMIN_CONFIG_ITEM_NOTIFY_SPAM'), ph: `${t('ADMIN_CONFIG_EXAMPLE')}false`, value: '' },
+ { key: 'LLM_API_KEY', desc: 'LLM API Key, used for AI spam comment detection. Takes precedence over Akismet and Tencent Cloud when configured.', ph: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', value: '', secret: true },
+ { key: 'LLM_API_ENDPOINT', desc: 'LLM API endpoint URL, defaults to OpenAI', ph: 'https://api.deepseek.com', value: '' },
+ { key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-flash', value: '' },
+ { key: 'LLM_SPAM_PROMPT', desc: 'LLM spam detection prompt (optional, uses default if left empty)', ph: '', value: '' } ]
},
</code_context>
<issue_to_address>
**suggestion:** LLM model placeholder in the UI does not match the backend default model.
In `checkByLLM`, the default model is `deepseek-v4-pro` when `config.LLM_MODEL` is unset, but the admin UI placeholder shows `deepseek-v4-flash`. This inconsistency can mislead admins about which model is actually used. Please update either the backend default or the placeholder so they match.
```suggestion
{ key: 'LLM_MODEL', desc: 'LLM model name', ph: 'deepseek-v4-pro', value: '' },
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
|
挺好的想法 |
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体反馈:
- 建议将
buildMessages中较长的 system prompt 抽取为可配置或支持 i18n 的常量,这样在不改动代码的前提下更易于维护和本地化。 checkByLLM中的重试行为(固定maxRetries = 3且延迟 1 秒)可以通过防垃圾设置进行配置,让运营方可以根据实际环境在延迟和健壮性之间进行调优。- 目前在每次调用
checkByLLM时都会实例化一个新的 OpenAI client;建议考虑复用单例 client 实例,以避免在评论量较高时重复初始化带来的开销。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider extracting the long system prompt in `buildMessages` into a configurable or i18n-able constant so that it’s easier to maintain and localize without touching code.
- The retry behavior in `checkByLLM` (fixed `maxRetries = 3` and 1s delay) could be made configurable through anti-spam settings so operators can tune latency vs robustness for their environment.
- You instantiate a new OpenAI client on every `checkByLLM` call; consider reusing a singleton client instance to avoid repeated setup overhead under high comment traffic.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="64-73" />
<code_context>
+function buildMessages (commentData, errorMsg = '', customPrompt = '') {
</code_context>
<issue_to_address>
**suggestion:** 自定义 prompt 的占位符替换目前只会替换每个占位符第一次出现的位置,对于更复杂的模板来说有一定限制。
使用 `.replace('{{comment}}', ...)` 只会更新 `LLM_SPAM_PROMPT` 中每个占位符的第一次出现。对于多次引用 `{{comment}}` 的 prompt,后续位置会保持未替换状态。建议改为使用全局替换(例如 `new RegExp('{{comment}}', 'g')` 或 `replaceAll`),以确保所有出现的位置都能被一致替换。
建议实现:
```javascript
function buildMessages (commentData, errorMsg = '', customPrompt = '') {
// 1. system指令(固定不变)
const systemContent = `You are a blog comment moderation assistant. Analyze ALL fields below and determine if this submission is spam or ham.
Spam includes ANY of the following in ANY field:
- Commercial ads, promotions, or buying/selling offers (e.g., "代开发票", "加微信", "兼职", "办证", "AI中转站").
- Special case: If the comment text is harmless, but the nickname is suspicious (e.g., contains ads or promotions) AND a website link is provided, treat it as SPAM.
- Meaningless gibberish or spammy repetition (e.g., "顶顶顶", "111111", "asdfgh", "好" repeated).
- Abusive language, insults, or offensive Chinese slang.
- Suspicious links or SEO spam in the website field.
- Bot-like automated greetings.
```
```javascript
const userContent = (customPrompt || LLM_SPAM_PROMPT)
.replaceAll('{{comment}}', commentData.comment || '')
.replaceAll('{{nick}}', commentData.nick || '')
.replaceAll('{{mail}}', commentData.mail || '')
.replaceAll('{{link}}', commentData.link || '')
```
我目前只看到了 `buildMessages` 函数的部分实现。要完整应用上述建议:
1. 确保在 `buildMessages` 内部(以及它调用的任何辅助函数中)所有占位符替换都使用全局替换(`replaceAll` 或 `string.replace(new RegExp('...', 'g'))`),而不是只替换一次的 `replace`。
2. 如果运行环境可能不支持 `String.prototype.replaceAll`,可以改用:
```js
.replace(/{{comment}}/g, commentData.comment || '')
```
针对每个占位符分别处理。
3. 如果 `LLM_SPAM_PROMPT` 中还定义了除 `comment`、`nick`、`mail` 和 `link` 之外的其他占位符,也请将这些占位符的替换方式一并更新为全局替换。
</issue_to_address>Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- Consider extracting the long system prompt in
buildMessagesinto a configurable or i18n-able constant so that it’s easier to maintain and localize without touching code. - The retry behavior in
checkByLLM(fixedmaxRetries = 3and 1s delay) could be made configurable through anti-spam settings so operators can tune latency vs robustness for their environment. - You instantiate a new OpenAI client on every
checkByLLMcall; consider reusing a singleton client instance to avoid repeated setup overhead under high comment traffic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider extracting the long system prompt in `buildMessages` into a configurable or i18n-able constant so that it’s easier to maintain and localize without touching code.
- The retry behavior in `checkByLLM` (fixed `maxRetries = 3` and 1s delay) could be made configurable through anti-spam settings so operators can tune latency vs robustness for their environment.
- You instantiate a new OpenAI client on every `checkByLLM` call; consider reusing a singleton client instance to avoid repeated setup overhead under high comment traffic.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="64-73" />
<code_context>
+function buildMessages (commentData, errorMsg = '', customPrompt = '') {
</code_context>
<issue_to_address>
**suggestion:** Custom prompt substitution only replaces the first occurrence of each placeholder, which is limiting for richer templates.
Using `.replace('{{comment}}', ...)` only updates the first occurrence of each placeholder in `LLM_SPAM_PROMPT`. Prompts that reference `{{comment}}` multiple times will have later instances left untouched. Please switch to a global replacement (e.g. `new RegExp('{{comment}}', 'g')` or `replaceAll`) so all occurrences are substituted consistently.
Suggested implementation:
```javascript
function buildMessages (commentData, errorMsg = '', customPrompt = '') {
// 1. system指令(固定不变)
const systemContent = `You are a blog comment moderation assistant. Analyze ALL fields below and determine if this submission is spam or ham.
Spam includes ANY of the following in ANY field:
- Commercial ads, promotions, or buying/selling offers (e.g., "代开发票", "加微信", "兼职", "办证", "AI中转站").
- Special case: If the comment text is harmless, but the nickname is suspicious (e.g., contains ads or promotions) AND a website link is provided, treat it as SPAM.
- Meaningless gibberish or spammy repetition (e.g., "顶顶顶", "111111", "asdfgh", "好" repeated).
- Abusive language, insults, or offensive Chinese slang.
- Suspicious links or SEO spam in the website field.
- Bot-like automated greetings.
```
```javascript
const userContent = (customPrompt || LLM_SPAM_PROMPT)
.replaceAll('{{comment}}', commentData.comment || '')
.replaceAll('{{nick}}', commentData.nick || '')
.replaceAll('{{mail}}', commentData.mail || '')
.replaceAll('{{link}}', commentData.link || '')
```
I only see part of the `buildMessages` function. To fully apply the suggestion:
1. Ensure that all placeholder substitutions inside `buildMessages` (or any helper it calls) use global replacement (`replaceAll` or `string.replace(new RegExp('...', 'g'))`) rather than single-occurrence `replace`.
2. If the runtime environment might not support `String.prototype.replaceAll`, you can instead use:
```js
.replace(/{{comment}}/g, commentData.comment || '')
```
for each placeholder.
3. If `LLM_SPAM_PROMPT` defines additional placeholders beyond `comment`, `nick`, `mail`, and `link`, update those substitutions to use global replacement as well.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并留下了一些整体反馈:
- 建议将
checkByLLM中的maxRetries和重试退避策略通过设置配置化,这样管理员可以针对不同的提供商在鲁棒性和延迟之间进行调优。 - 在
getOpenAI中,缓存失效目前只检查 API key 和 endpoint;如果管理员更改了模型或用于垃圾检测的提示词,客户端不会被重新创建。因此,最好将客户端缓存与提示词 / 模型相关的配置分离开来,以避免混淆。 buildMessages中的默认 system prompt 比较长,而且把业务规则紧密耦合在代码里;将其提取到单独的常量或配置值会让后续调整更容易,也能减少函数内部的噪音。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider making `maxRetries` and retry backoff in `checkByLLM` configurable via settings so admins can tune robustness vs. latency for different providers.
- In `getOpenAI`, the cache invalidation only checks API key and endpoint; if admins change the model or spam prompt, the client won't be recreated, so it may be clearer to separate client caching from prompt/model-related config to avoid confusion.
- The default system prompt in `buildMessages` is quite long and tightly couples business rules to code; extracting it into a separate constant or config value would make future adjustments easier and reduce noise in the function.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="121" />
<code_context>
+ { role: 'system', content: systemContent },
+ { role: 'user', content: userContent }
+ ]
+ logger.log('提示词是:', finalPrompt)
+ return finalPrompt
+}
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Logging the full LLM prompt may be noisy and potentially expose user content in logs.
`finalPrompt` contains full user and system content. Logging this at normal level can both leak user data into logs and substantially increase log volume on a busy site. Please either reduce the log level (e.g., debug), redact/summarize the prompt (e.g., only IDs/metadata), or guard this behind a configuration flag for troubleshooting only.
Suggested implementation:
```javascript
const finalPrompt = [
{ role: 'system', content: systemContent },
{ role: 'user', content: userContent }
]
// 使用 debug 级别并只输出部分内容,避免在日志中泄露完整提示词
logger.debug('提示词已生成', {
systemContentPreview: systemContent.slice(0, 200),
userContentPreview: userContent.slice(0, 200)
})
return finalPrompt
}
```
如果你们有统一的配置管理或日志级别控制,可以进一步:
1. 将 `200` 这一截断长度抽成常量或配置项。
2. 如果需要完全关闭这类日志,可以用环境变量或配置开关包裹这一段 `logger.debug` 调用,例如只在开发或故障排查模式下启用。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- Consider making
maxRetriesand retry backoff incheckByLLMconfigurable via settings so admins can tune robustness vs. latency for different providers. - In
getOpenAI, the cache invalidation only checks API key and endpoint; if admins change the model or spam prompt, the client won't be recreated, so it may be clearer to separate client caching from prompt/model-related config to avoid confusion. - The default system prompt in
buildMessagesis quite long and tightly couples business rules to code; extracting it into a separate constant or config value would make future adjustments easier and reduce noise in the function.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider making `maxRetries` and retry backoff in `checkByLLM` configurable via settings so admins can tune robustness vs. latency for different providers.
- In `getOpenAI`, the cache invalidation only checks API key and endpoint; if admins change the model or spam prompt, the client won't be recreated, so it may be clearer to separate client caching from prompt/model-related config to avoid confusion.
- The default system prompt in `buildMessages` is quite long and tightly couples business rules to code; extracting it into a separate constant or config value would make future adjustments easier and reduce noise in the function.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="121" />
<code_context>
+ { role: 'system', content: systemContent },
+ { role: 'user', content: userContent }
+ ]
+ logger.log('提示词是:', finalPrompt)
+ return finalPrompt
+}
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Logging the full LLM prompt may be noisy and potentially expose user content in logs.
`finalPrompt` contains full user and system content. Logging this at normal level can both leak user data into logs and substantially increase log volume on a busy site. Please either reduce the log level (e.g., debug), redact/summarize the prompt (e.g., only IDs/metadata), or guard this behind a configuration flag for troubleshooting only.
Suggested implementation:
```javascript
const finalPrompt = [
{ role: 'system', content: systemContent },
{ role: 'user', content: userContent }
]
// 使用 debug 级别并只输出部分内容,避免在日志中泄露完整提示词
logger.debug('提示词已生成', {
systemContentPreview: systemContent.slice(0, 200),
userContentPreview: userContent.slice(0, 200)
})
return finalPrompt
}
```
如果你们有统一的配置管理或日志级别控制,可以进一步:
1. 将 `200` 这一截断长度抽成常量或配置项。
2. 如果需要完全关闭这类日志,可以用环境变量或配置开关包裹这一段 `logger.debug` 调用,例如只在开发或故障排查模式下启用。
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:
- 建议为
LLM_MAX_RETRIES增加一个硬性的最大上限(例如,钳制到一个合理的最大值),以避免由于配置错误导致在故障情况下出现过多的重试循环和过高的延迟。 - 当前的 JSON 提取/修复流水线(
extractJson+repairJson)比较宽松,可能会错误地捕获或修改非 JSON 文本中的大括号;你可能需要收紧这部分逻辑,或者只依赖response_format: { type: 'json_object' },当模型返回无效内容时快速失败,而不是尝试自动修复。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider adding a hard upper bound on `LLM_MAX_RETRIES` (e.g., clamping to a reasonable max) to avoid misconfiguration leading to excessive retry loops and latency under failure conditions.
- The JSON extraction/repair pipeline (`extractJson` + `repairJson`) is quite permissive and may incorrectly pick up or modify braces in non-JSON text; you might want to tighten this logic or rely solely on `response_format: { type: 'json_object' }` and fail fast when the model returns invalid content instead of trying to auto-repair.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="126" />
<code_context>
+}
+
+async function checkByLLM (comment, config) {
+ const maxRetries = config.LLM_MAX_RETRIES || 3
+ let lastError = ''
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 将 `LLM_MAX_RETRIES` 强制转换为数字,以避免隐蔽的类型问题。
`config.LLM_MAX_RETRIES` 可能是字符串(例如来自环境变量/env 或管理后台/admin UI)。在使用 `const maxRetries = config.LLM_MAX_RETRIES || 3` 时,`maxRetries` 可能是 `'5'`,并且循环依赖于 `attempt <= maxRetries` 中的隐式类型转换。通过显式转换,例如 `const maxRetries = Number(config.LLM_MAX_RETRIES) || 3`,可以避免类型上的意外行为,并确保后续的数值运算更安全。
建议实现:
```javascript
const maxRetries = Number(config.LLM_MAX_RETRIES) || 3
```
此更改会显式地将 `config.LLM_MAX_RETRIES` 转换为数字,确保即使配置值以字符串形式提供(例如通过环境变量或管理后台),`maxRetries` 也始终为数值类型。只要 `maxRetries` 只在数值场景中使用(如比较或算术运算),通常不需要进一步调整。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进后续的代码审查。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- Consider adding a hard upper bound on
LLM_MAX_RETRIES(e.g., clamping to a reasonable max) to avoid misconfiguration leading to excessive retry loops and latency under failure conditions. - The JSON extraction/repair pipeline (
extractJson+repairJson) is quite permissive and may incorrectly pick up or modify braces in non-JSON text; you might want to tighten this logic or rely solely onresponse_format: { type: 'json_object' }and fail fast when the model returns invalid content instead of trying to auto-repair.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider adding a hard upper bound on `LLM_MAX_RETRIES` (e.g., clamping to a reasonable max) to avoid misconfiguration leading to excessive retry loops and latency under failure conditions.
- The JSON extraction/repair pipeline (`extractJson` + `repairJson`) is quite permissive and may incorrectly pick up or modify braces in non-JSON text; you might want to tighten this logic or rely solely on `response_format: { type: 'json_object' }` and fail fast when the model returns invalid content instead of trying to auto-repair.
## Individual Comments
### Comment 1
<location path="src/server/function/twikoo/utils/spam.js" line_range="126" />
<code_context>
+}
+
+async function checkByLLM (comment, config) {
+ const maxRetries = config.LLM_MAX_RETRIES || 3
+ let lastError = ''
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Coerce `LLM_MAX_RETRIES` to a number to avoid subtle type issues.
`config.LLM_MAX_RETRIES` may be a string (e.g. from env/admin UI). With `const maxRetries = config.LLM_MAX_RETRIES || 3`, `maxRetries` can be `'5'`, and the loop relies on implicit coercion in `attempt <= maxRetries`. Converting explicitly, e.g. `const maxRetries = Number(config.LLM_MAX_RETRIES) || 3`, avoids type surprises and keeps future numeric operations safe.
Suggested implementation:
```javascript
const maxRetries = Number(config.LLM_MAX_RETRIES) || 3
```
This change explicitly coerces `config.LLM_MAX_RETRIES` to a number, ensuring `maxRetries` is always numeric even if the configuration value is provided as a string (e.g. via environment variables or an admin UI). No further adjustments should be necessary as long as `maxRetries` is only used in numeric contexts (such as comparisons or arithmetic).
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
改动详情
1. 新增依赖
openaiSDK2. 检测逻辑
checkByLLM()函数LLM_SPAM_PROMPT配置自定义提示词postCheckSpam的最后新增 LLM 分支:3. 管理后台配置 UI
LLM_API_KEY— API 密钥LLM_API_ENDPOINT— API 端点地址LLM_MODEL— 模型名称LLM_SPAM_PROMPT— 自定义检测提示词TODO
已完成TkAdminConfig.vue中的desc还是硬编码英文,i18n 未支持相关文档还没写已完成需要实现校验和重试逻辑,大模型输出常含格式瑕疵或多余内容已完成Summary by Sourcery
在评论审核中新增可选的基于 LLM 的垃圾信息检测功能,并在管理后台中开放其配置。
新功能:
改进:
构建:
Original summary in English
Summary by Sourcery
Add optional LLM-based spam detection to comment moderation and expose its configuration in the admin UI.
New Features:
Enhancements:
Build:
Summary by Sourcery
在评论系统中添加可选的基于 LLM 的垃圾内容检测,并在管理后台中提供相关配置选项。
New Features:
textarea输入支持。Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
Add optional LLM-based spam detection for comments and expose its configuration in the admin panel.
New Features:
Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
在评论系统中添加可选的基于 LLM 的垃圾内容检测,并在管理后台中提供相关配置选项。
New Features:
textarea输入支持。Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
Add optional LLM-based spam detection for comments and expose its configuration in the admin panel.
New Features:
Enhancements:
Build:
Documentation:
新功能:
增强:
构建:
文档:
Original summary in English
Summary by Sourcery
在评论系统中添加可选的基于 LLM 的垃圾内容检测,并在管理后台中提供相关配置选项。
New Features:
textarea输入支持。Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
Add optional LLM-based spam detection for comments and expose its configuration in the admin panel.
New Features:
Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
在评论系统中添加可选的基于 LLM 的垃圾内容检测,并在管理后台中提供相关配置选项。
New Features:
textarea输入支持。Enhancements:
Build:
Documentation:
Original summary in English
Summary by Sourcery
Add optional LLM-based spam detection for comments and expose its configuration in the admin panel.
New Features:
Enhancements:
Build:
Documentation: