登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

Moderation 怎样组合多类别判断

来源:17golang原创

时间:2026-10-25 12:07:00 101浏览 收藏

Moderation 返回的重点不只是 flagged。如果业务要同时关心仇恨、骚扰、自残或暴力等多个类别,比较稳妥的做法是把 categories、category_scores 和 category_applied_input_types 分开处理,再把它们汇总成自己的业务动作。这样既能知道“命中了什么”,也能为不同类别保留不同阈值。

官方文档:https://developers.openai.com/api/docs/guides/moderation

组合多类别判断的核心是:先收集类别信号,再按类别应用规则,最后统一映射为放行、人工复核或拦截。不要把所有场景都压缩成一个固定分数。
  • 用 categories 判断模型是否标记了某个类别。
  • 用 category_scores 做类别级别的置信度分层。
  • 用 category_applied_input_types 确认分数适用于文本、图片还是两者。

先分清三个结果字段

一次 Moderation 结果通常位于 response.results[0]。flagged 是整体提示,categories 是逐类别的布尔值,category_scores 是逐类别分数。三者用途不同:整体提示适合做快速分支,类别布尔值适合记录命中原因,分数适合做更细的策略分层。

另外要注意,分类项并不一定对每种输入都适用。官方返回的 category_applied_input_types 会说明某个类别的分数对应哪些输入类型。处理图片和文字混合请求时,不能只看分数高低,还要把这个适用范围带进日志或规则判断。

Moderation Response、categories、category_scores 和输入类型的静态字段关系说明图
图1:Moderation 返回字段的结构说明图,查看类别标记、分数和适用输入类型之间的关系。

把多个类别收集成稳定标签

不要在业务代码里连续写很多层 if result.categories.xxx。可以先把值为真的类别收集成列表,同时从分数表取出对应分数,后面的策略层只处理统一的数据结构。

from openai import OpenAI

client = OpenAI()

def classify_text(text: str) -> dict:
    # 使用官方的多模态审核模型,输入这里只放一段文本。
    response = client.moderations.create(
        model="omni-moderation-latest",
        input=text,
    )
    # 先取第一条结果,避免把 SDK 对象直接散落到策略层。
    result = response.results[0]
    categories = result.categories
    scores = result.category_scores

    # 只收集被标记的类别,保留原始类别名以便记录和追踪。
    hits = [name for name, marked in categories.model_dump().items() if marked]
    score_map = scores.model_dump()
    return {
        "flagged": result.flagged,
        "hits": hits,
        "scores": {name: score_map.get(name, 0.0) for name in hits},
    }

这里的 hits 是“模型已经标记的类别”,不是业务最终结论。比如一个输入同时命中两个类别,列表会保留两个原因;即使整体 flagged 为真,也不应该丢掉类别信息。

为不同类别配置不同阈值

多类别判断的难点在于:类别的风险含义和业务处置不一样。可以把类别阈值放在配置里,而不是写死在函数分支中。高风险类别可以直接进入拦截,普通类别在中间区间交给人工复核,低于阈值再放行。阈值只是应用策略,不能把某个示例数字当成所有产品的通用标准。

POLICY = {
    # 高风险类别的分数达到阈值时,直接交给拦截策略。
    "violence": {"review": 0.35, "block": 0.75},
    # 普通类别保留更宽的人工复核区间。
    "harassment": {"review": 0.55, "block": 0.90},
    "hate": {"review": 0.55, "block": 0.90},
}

def decide(result: dict) -> dict:
    # 没有命中类别时,不需要进入逐类别阈值判断。
    if not result["hits"]:
        return {"action": "allow", "reasons": []}

    reasons = []
    action = "allow"
    for category in result["hits"]:
        # 未配置的类别保留为人工复核,避免静默放行未知信号。
        rule = POLICY.get(category)
        if rule is None:
            return {"action": "review", "reasons": [category]}
        score = result["scores"].get(category, 0.0)
        reasons.append({"category": category, "score": score})
        if score >= rule["block"]:
            action = "block"
        elif score >= rule["review"] and action == "allow":
            action = "review"
    return {"action": action, "reasons": reasons}

示例采用“拦截优先于复核,复核优先于放行”的组合规则。实际产品还可以按用户年龄、内容类型、地区或人工队列容量做二次分层,但这些因素应作为单独的业务字段,不要混进模型分数。

命中类别、分数表、类别阈值和放行人工复核拦截之间的静态策略关系说明图
图2:多类别策略映射结构图,查看类别命中、阈值与三类处理动作的边界。

把组合结果接到小项目的验收点

从零接入时可以把流程拆成三个模块:请求模块只负责调用 Moderation,归一化模块负责生成 hits 和 scores,策略模块负责输出 allow、review 或 block。这样换模型或调整类别阈值时,不会影响调用代码。

验收时至少准备四种输入:没有命中的文本、只命中一个类别的文本、同时命中多个类别的文本,以及命中类别但分数处于复核区间的文本。每条记录应包含类别名、分数、输入类型和最终动作;不要只记录一个无法解释原因的 flagged=true。

如果请求返回错误,策略层不能把“没有结果”当成“安全”。应把错误交给重试或人工处理分支,并明确记录请求失败原因。官方文档也提示,生成式请求中的 moderation 结果可能是错误对象,读取分数前要先判断结果类型。

相关问题

只看 flagged 可以吗?可以作为最简单的总开关,但无法说明命中了哪个类别,也不适合做差异化处置。

category_scores 能直接当风险概率吗?不建议这样理解。它是模型对类别的置信度,具体阈值应结合业务样本和处置成本校准。

多个类别同时命中时先处理哪个?先定义优先级,通常让拦截优先于人工复核,再优先于放行,同时保留全部命中原因。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>