决策模型做内容安全检测:从踩坑到上线

接口参考:

https://help.aliyun.com/zh/model-studio/decision-model-api?spm=a2c4g.11186623.0.i0#是非判断示例-h4

20 条标注样本实测:违规拦截率 100%、漏放率 0%、误拦率 0%、平均 35~59ms/条。

代码:content_safety.py / content_safety.mjs,报告:内容安全检测报告.md

一、为什么不用普通大模型做审核

用 qwen 做内容审核的典型做法是:给一段 system prompt,让它输出 JSON 判定。问题是它要"生成"------生成就有 token 成本、有延迟、有格式不稳定的风险,还得写解析兜底。

百炼的决策模型 (decision-model-preview)是另一条路:一次请求带上若干"问题",一次前向返回所有问题的判定和概率分布,不生成任何文本。输出长度固定为 0,所以延迟与内容长度基本无关,实测服务端 40~100ms。

它支持三种问题类型:

类型 语义 返回
noul 是非判断 P(yes),0~1
score 有序量表 等级期望值(可落在两级之间,如 1.43)+ 完整概率分布
choice 多选一 选项名 + 置信度 + 概率分布

二、第一个坑:文档里的域名调不通

文档写的是:

复制代码
https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/systemone

实际踩坑记录(都是真跑的):

请求 结果
https://dashscope.aliyuncs.com/compatible-mode/v1/systemone ❌ 404(公共域名根本没这条路由)
https://cn-beijing.maas.aliyuncs.com/... ❌ 连接失败(只有 SOA 记录)
https://default.cn-beijing.maas.aliyuncs.com/... ❌ 400 Workspace endpoint is invalid.
https://ws-test.cn-beijing.maas.aliyuncs.com/... ❌ 403 Workspace endpoint access denied.
.../chat/completions + model=decision-model-preview ❌ 403 access_denied(决策模型不走 chat 协议)
https://trial.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/systemone ✅ 200

结论:用试用域名 trial.cn-beijing.maas.aliyuncs.com 即可,不需要 WorkspaceId 。业务空间专属域名要控制台「业务空间管理」里的空间 ID,新加坡地域换成 trial.ap-southeast-1.maas.aliyuncs.com。

三、最小可用示例

python 复制代码
import os, requests

resp = requests.post(
    "https://trial.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/systemone",
    headers={"Authorization": f"Bearer {os.environ['QWEN_LLM_API_KEY']}",
             "Content-Type": "application/json"},
    json={
        "model": "decision-model-preview",
        "state": "这个傻X客服,赶紧退钱!再不处理我就去举报你们。",
        "questions": {
            "abuse":  {"type": "noul",   "instructions": "这段内容是否在辱骂、人身攻击?"},
            "risk":   {"type": "score",  "instructions": "整体有害程度?",
                       "criteria": ["无风险", "低风险", "中风险", "高风险"]},
            "action": {"type": "choice", "instructions": "该怎么处置?",
                       "criteria": {"pass": "放行", "review": "转人工", "block": "拦截"}},
        },
    },
    timeout=30,
)
print(resp.json()["answers"])

真实返回:

json 复制代码
{
  "answers": {
    "abuse":  {"type": "noul", "noul": 0.87},
    "risk":   {"type": "score", "score": 1.95, "confidence": 0.65,
               "probabilities": {"0": 0.15, "1": 0.2, "2": 0.21, "3": 0.44}},
    "action": {"type": "choice", "choice": "review", "confidence": 0.49,
               "probabilities": {"pass": 0.03, "review": 0.66, "block": 0.31}}
  },
  "usage": {"input_tokens": 86},
  "latency_ms": 51.5
}

三个问题、一次请求、51ms、86 个输入 token,没有任何输出 token。注意 action 只给了 review、置信度 0.49 ------ 这就是后面要讲的核心问题。

(上面这段是可运行文件 blog_demo_min.py,python blog_demo_min.py 直接出结果。)

四、把审核需求变成 10 个问题

内容审核要的不是"像不像违规",而是可解释的分类 + 明确的处置。所以拆成 10 个问题一起问:

  • 1 个 choice:处置动作(pass / review / block)
  • 1 个 score:整体风险等级 0~3
  • 8 个 noul:涉政敏感、色情低俗、暴力恐怖、违法犯罪、辱骂攻击、垃圾营销、隐私泄露、自杀自残

实测 10 个问题全量一次请求:888 输入 token,服务端 98ms。问题数不设上限(建议 ≤16),延迟随问题数近线性增长,所以别一个类目发一次请求。

五、最大的坑:三个信号单独用都会翻车

这是踩得最深的地方。以下全是原始返回:

样本 action risk noul 只看它会怎样
售后咨询 review 置信 0.97 0.13 8 类全 0 正常售后被误转人工
小说里的战争描写 pass 置信 1.0 0.35 violence=0.83 正常文学被误拦
出售改装手枪和弹药 pass 置信 0.95 0.12 illegal=0.54 卖枪直接漏放
泄露他人身份证号 pass 0.03 privacy=0.95 隐私泄露漏放

原因各不相同:

  • action 是"整体处置倾向",但对投诉、差评、抱怨类文本有强烈的 review 偏置;
  • risk 是"整体有害程度",对明确的违法交易反而给分很低;
  • noul 判的是**"是否涉及"**,不是"是否违规" ------ 小说写战争,确实"涉及"暴力。

结论:不要相信任何一个单一信号。

六、两道解法

1. 改问题措辞(效果立竿见影)

action 的 instructions 里明说:

严格按标准判定:不要因为文本是投诉、差评、抱怨或情绪化表达就选 review;这些只要不含违规内容就选 pass。review 仅用于确有违规嫌疑但证据不足的情况。

noul 从"是否涉及 "改成"是否在传播、宣扬、教唆或促成":

仅仅是提及、讨论、批评、新闻报道、文学影视描写、反诈与安全提示不算违规,一律判否。

改完后:售后样本 review(0.97) → pass(0.94),文学样本 violence 0.83 → 0。提示词在决策模型上同样重要,甚至更重要------它的每个问题都是一次独立判断,措辞直接决定判定尺度。

2. 投票融合

python 复制代码
BLOCK_THRESHOLD, REVIEW_THRESHOLD = 0.5, 0.2
SEVERE = ("politics", "porn", "violence", "illegal", "selfharm")   # 高危类目

severe_hit = any(cat[c] >= BLOCK_THRESHOLD for c in SEVERE)
max_cat = max(cat.values())

if (risk >= 2.5
    or (severe_hit and risk >= 0.5)          # 高危类目单信号 + 风险不为 0 → 拦截(补"卖枪")
    or (max_cat >= 0.9 and (risk >= 1.5 or action == "block"))   # 双信号共振
    or pii_block):                           # 身份证号/银行卡号
    return "block"
if action in ("block", "review") or risk >= 1.5 or max_cat >= BLOCK_THRESHOLD or pii:
    # 纠偏:模型只因文本"像投诉"就给 review,且类目与风险均无违规信号 → 放行
    return "pass" if (max_cat < REVIEW_THRESHOLD and risk < 1) else "review"
return "pass"

两条原则:

  1. block 必须多信号共振 ,唯一的例外是"高危类目命中 + 风险不为 0"(涉政/色情/暴恐/违法/自残),专治上面那个 risk=0.12 的卖枪样本;
  2. 单一信号最多升到 review 转人工,绝不静默放行。审核系统里,漏放比误拦贵得多。

七、结构化信息交给正则,别交给模型

泄露身份证号那条,模型给的是 risk=0.03、action=pass,只有 privacy 类目到了 0.95。指望模型挡住隐私泄露不可靠,正则才是对的工具:

python 复制代码
_PII = [
    ("idcard",   "身份证号", "block",  r"(?<!\d)\d{17}[\dXx](?!\d)|(?<!\d)\d{15}(?!\d)"),
    ("bankcard", "银行卡号", "block",  r"(?<!\d)\d{16,19}(?!\d)"),
    ("phone",    "手机号",   "review", r"(?<!\d)1[3-9]\d{9}(?!\d)"),
    ("email",    "邮箱",     "review", r"[\w.+-]+@[\w-]+\.[A-Za-z]{2,}"),
]

小坑:18 位身份证号同时也满足"16-19 位银行卡",要记录已命中区间去重,否则一条文本报两个类目。

八、别忘了平台自带的兜底

涉政那条样本,请求还没到模型就被百炼网关拒了:

复制代码
HTTP 400  Input data may contain inappropriate content.

这不是调用失败,是平台已经判定违规。必须把它当成"拦截"结论,而不是降级重试或报错------否则最危险的那类内容反而会被漏放:

python 复制代码
if "inappropriate content" in msg.lower():
    return {"action": "block", "reason": "平台输入审核拒绝", ...}

九、实测结果

20 条标注样本:10 条正常/边界(含反诈提示、时政新闻、小说战争描写、文明差评、维权投诉)+ 10 条覆盖全部 8 类违规。

指标 决策模型 qwen-flash 降级
违规拦截率 100% 100%
违规漏放率 0% 0%
正常放行率 100% 100%
误拦率 0% 0%
平均耗时 35~59ms/条 ~280ms/条

十、上线的几个建议

  1. 三道闸:平台输入审核(免费兜底)→ 决策模型判 10 个问题 → 投票融合分流。
  2. 失败方向要保守 :超时/异常按 review;平台 400 inappropriate content 按 block。
  3. 降级要自动 :决策模型不可用时无感切到 qwen-flash(字段一致),并把路由类失败(400/403/404)在进程内缓存,避免批量时每条都白跑一次。
  4. 留痕 :落库 request_id、8 类概率、riskLevel、命中的信号,便于回溯和调阈值。
  5. 缓存:按文本哈希缓存判定结果,审核场景重复率很高。

=代码=

bash 复制代码
# -*- coding: utf-8 -*-
"""博客里那段"最小可用示例"的原样验证,确保贴出去就能跑。"""
import json
import os

import requests

resp = requests.post(
    "https://trial.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/systemone",
    headers={"Authorization": f"Bearer {os.environ['QWEN_LLM_API_KEY']}",
             "Content-Type": "application/json"},
    json={
        "model": "decision-model-preview",
        "state": "这个傻X客服,赶紧退钱!再不处理我就去举报你们。",
        "questions": {
            "abuse": {"type": "noul", "instructions": "这段内容是否在辱骂、人身攻击?"},
            "risk": {"type": "score", "instructions": "整体有害程度?",
                     "criteria": ["无风险", "低风险", "中风险", "高风险"]},
            "action": {"type": "choice", "instructions": "该怎么处置?",
                       "criteria": {"pass": "放行", "review": "转人工", "block": "拦截"}},
        },
    },
    timeout=30,
)
print("HTTP", resp.status_code)
print(json.dumps(resp.json(), ensure_ascii=False, indent=2))
相关推荐
这个DBA有点耶1 小时前
数据库双轨并行实战:全量并行策略、增量延迟控制、双向回切与一致性校验
数据库·架构·dba
晓德2 小时前
0、LLM大模型安全学习系列(启)
学习·安全
程序哥聊面试2 小时前
什么是 Sandbox?给 AI Agent 划一道安全边界
人工智能·安全
adinnet20262 小时前
保单、赔付与渠道问数:保险经营数据如何实现按需查询
大数据·数据库·人工智能
云贝贝贝2 小时前
PostgreSQL 分区表:从设计到运维,大表不再卡
运维·数据库·postgresql
oradh2 小时前
Oracle固定执行计划的方法---SQL Plan Baseline
数据库·sql·oracle
数据库小学妹2 小时前
数据库高可用演练怎么做?稳态定义、注入点与中止条件
数据库·rto·高可用架构·运维体系·故障演练·容灾切换
zyseo83 小时前
谷歌SEO 站内搜索优化实战:把站内搜索词变成关键词金矿
java·服务器·数据库
qq_401700413 小时前
Qt 串口/网口通信:Hex 与 ASCII 编码转换深度指南
开发语言·数据库·qt