AI 内容审核
ai-comment-moderator 与 ai-username-moderator 的触发机制、审核逻辑与处理流程
本页面描述两个由数据库触发器自动调用的 AI 审核函数。它们不接受客户端直接请求,而是在数据写入时通过 supabase_functions.http_request / pg_net 异步触发。
函数对比
| ai-comment-moderator | ai-username-moderator | |
|---|---|---|
| 触发表 | comments | profiles |
| 触发时机 | AFTER INSERT | AFTER UPDATE |
| JWT 校验 | 是(触发器携带 Service Role JWT) | 否(verify_jwt: false) |
| AI 模型 | x-ai/grok-4.1-fast(via OpenRouter) | x-ai/grok-4.1-fast(via OpenRouter) |
| 审核对象 | 评论文本 content | 昵称 display_name |
| 违规处理 | 修改评论 status | 回滚昵称 + 发送通知 |
ai-comment-moderator
触发流程
用户发表评论
→ INSERT INTO comments
→ 触发器 review-new-comment (AFTER INSERT)
→ supabase_functions.http_request 调用 ai-comment-moderator
→ AI 分析评论内容
→ 更新 comments.status 和 moderation_meta触发器 review-new-comment 在 comments 表每次 INSERT 后触发,通过 supabase_functions.http_request 以 POST 方式调用函数,超时 5000ms。
评论在插入时 status 默认为 public,审核是后置异步的——用户提交后评论立即可见,审核完成后才可能被修改状态。
审核维度
AI 会对评论内容进行分析并返回以下结构:
Prop
Type
各等级的判定标准:
| 等级 | 含义 | 示例 |
|---|---|---|
low | 安全或无伤大雅 | 正常讨论、善意灌水、轻微擦边 |
medium | 轻度违规 | 阴阳怪气、轻微引战、疑似广告 |
high | 严重违规 | 明确色情、暴力、仇恨言论、诈骗广告 |
状态映射
审核结果会映射为 comment_status 枚举值并更新到 comments.status:
| risk_level | → status | 效果 |
|---|---|---|
low | public | 正常展示,所有人可见 |
medium | shadow_banned | 影子禁言——评论仅发布者自己可见,其他用户看不到 |
high | rejected | 直接拒绝(隐藏),所有人不可见 |
comment_status 枚举共有四个值:public、shadow_banned、rejected、pending。AI 审核只会产出前三种。
moderation_meta 示例
审核完成后,comments.moderation_meta(JSONB)会被更新为:
{
"ai_reviewed": true,
"provider": "OpenRouter:x-ai/grok-4.1-fast",
"reviewed_at": "2025-06-15T08:30:00.000Z",
"risk_level": "medium",
"reason": "疑似广告内容",
"tags": ["spam"]
}异常处理
当 AI 返回的 JSON 无法解析时(例如模型输出了 Markdown 包裹的 JSON),函数会回退为 medium 等级处理,reason 标记为 AI_PARSE_ERROR。这意味着解析失败时评论会被影子禁言而非放行,属于偏保守策略。
ai-username-moderator
触发流程
用户修改昵称
→ UPDATE profiles SET display_name = '新昵称'
→ 触发器 review-new-username (AFTER UPDATE)
→ supabase_functions.http_request 调用 ai-username-moderator
→ AI 分析新昵称
→ 通过:记录审核结果
→ 违规:回滚昵称 + 发送系统通知触发器 review-new-username 在 profiles 表每次 UPDATE 后触发。
防无限循环机制
由于违规处理本身也会 UPDATE profiles(回滚 display_name + 写入 moderation_meta),函数在入口处检查 moderation_meta 是否发生变化。如果变化了,说明本次 UPDATE 是由审核回滚引起的,直接跳过,避免无限递归。
收到触发 → moderation_meta 有变化?
→ 是:跳过(这是审核自身触发的更新)
→ 否:继续检查 display_name
→ display_name 无变化?跳过
→ display_name 有变化 → 调用 AI 审核审核维度
与评论审核不同,用户名审核仅关注两个维度,其他内容一律视为安全:
AI 返回结构:
Prop
Type
审核原则是宁可放过也不要误判——普通昵称、二次元名、表情符号等均视为安全。
处理方式
仅在 profiles.moderation_meta 记录审核通过信息,不改变 display_name:
{
"last_name_review": {
"ai_reviewed": true,
"provider": "OpenRouter: x-ai/grok-4.1-fast",
"reviewed_at": "2025-06-15T08:30:00.000Z",
"result": "pass"
}
}两步操作同时执行:
- 回滚昵称:将
display_name恢复为修改前的值,并在moderation_meta中记录被驳回的昵称和原因 - 发送系统通知:向
notifications表插入一条类型为system的通知,告知用户昵称被撤回及原因
通知内容示例:您的昵称「加我vx:xxx」因违反用户名使用规范被系统自动撤回(原因:推广引流)。请注意账号合规使用。如有疑问,请提交工单反馈。
moderation_meta 示例:
{
"last_name_review": {
"ai_reviewed": true,
"provider": "OpenRouter: x-ai/grok-4.1-fast",
"reviewed_at": "2025-06-15T08:30:00.000Z",
"result": "rejected",
"rejected_name": "加我vx:xxx",
"is_violation": true,
"reason": "推广引流",
"tags": ["promotion"]
}
}相关数据表
Prop
Type
Prop
Type
Prop
Type
Secrets 依赖
两个函数共用以下 Secrets:
| Secret | 用途 |
|---|---|
OPENAI_API_KEY | OpenRouter API Key,用于调用 AI 模型 |
OPENAI_BASE_URL | API 端点(当前指向 OpenRouter) |
SUPABASE_URL | 项目 URL(系统自带) |
SUPABASE_SERVICE_ROLE_KEY | Service Role Key,用于绕过 RLS 更新数据 |
这两个函数使用 SERVICE_ROLE_KEY 是必要的——触发器上下文没有用户 JWT,且需要修改其他用户的数据(回滚昵称、修改评论状态)。
注意事项
- 评论审核是后置的:评论发布后立刻可见,AI 审核通常在数秒内完成。极端情况下(AI 响应慢或失败),违规评论可能短暂暴露
- 昵称审核也是后置的:用户修改昵称后会短暂看到新昵称,违规时才被回滚
- AI 解析失败:评论审核回退为
medium(影子禁言);昵称审核回退为pass(放行)。两者策略不同——评论偏保守,昵称偏宽松 - 模型更换:如需更换 AI 模型,修改函数中的
model字段并确保OPENAI_BASE_URL指向正确的兼容端点 - 触发器超时:
supabase_functions.http_request设置的超时为 5000ms,超时后不会重试