要不要在通用模型之外再加一个 Xing4.0-29B-Intent,判断标准不是模型数量,而是意图判错的代价会不会以不可挽回的方式带进最终回复。只要对话里存在两条以上真正的分支动作——转人工、查订单、发起退款、追问补全——意图判定就值得单独成一个环节;如果只是单轮闲聊或固定栏目问答,所有输入最终都走同一段生成逻辑,拆开的收益通常低于多维护一套服务的成本。拆开之后,通用模型只对一段已经确定意图的话负责,判定环节用更小、更快的模型控制分支走向,两者职责不同,失败代价也不同,兜底必须分开设计。
适用场景:对话存在多条可执行分支,需要稳定的分流、转人工或业务动作判定。操作动作:把意图判定从通用模型里拆出来,只喂当前轮文本和少量必要字段,输出受限意图标签加置信度;通用模型只在既定意图下生成措辞。验证方式:按 trace id 在日志里对齐“意图标签、置信度、走的分支、最终回复”,看判错样本落在哪一类兜底。风险边界:多一套服务就多一次超时和一次版本变更,必须预先定义判定失败不阻断回复的降级路径。
把一次对话拆成意图判定和回复生成两个环节
先划边界,再决定拆不拆。判定环节回答的是“这句话要去哪条路”,生成环节回答的是“在这条路上怎么说”。两者对延迟的容忍度不一样,这也是它们适合分成两次调用的主要原因。
| 环节 | 输入 | 输出 | 可接受的延迟量级 |
|---|---|---|---|
| Xing4.0-29B-Intent 判定 | 当前轮用户文本、渠道或页面来源、登录态、已确认槽位、上一轮意图标签 | 单一意图标签、置信度、可选槽位补全结果 | 通常要求与生成首包同一量级,不宜让用户先等判定再等回复;具体数值需结合部署和网络确认 |
| 通用模型生成 | 意图标签、用户原文、必要会话摘要、业务口径提示 | 自然语言回复,或流式片段 | 几百毫秒到数秒可被接受,流式首包更敏感 |
这张表的作用是让你能对外解释:为什么判定环节的输出必须是结构化的、字段少的,而生成环节可以自由发挥。只要判定输出不是一个有限的标签集合,兜底分支就无从设计。
限定意图判定环节只吃必要上下文
判定被带偏,多数不是模型不行,而是输入里塞了不该塞的东西。整段会话拼进去,上一轮助手的长回复、推荐话术、营销文案会强烈影响分类结果,尤其是用户只回了一个“嗯”“好的”的时候。
- 建议送进判定:当前轮用户文本、渠道或页面来源、登录态或用户类型、会话中已确认的关键槽位、上一轮意图标签。
- 建议剔除:历史助手回复全文、营销与推荐话术、系统角色提示词、超长会话的全量拼接。
- 截断策略:用户文本按字符数截断,保留开头和结尾,不要只留开头。
剔除前后的对比要留下可查记录,最省事的做法是在判定日志里直接标出本次用了哪些字段,同名会话跑两版输入,比较标签是否变化。
{
trace_id: 'a1b2c3',
intent: 'order_query',
confidence: 0.71,
intent_input_fields: ['user_text','channel','login_state','last_intent'],
intent_input_version: 'slim_v2',
intent_status: 'ok'
}
intent_input_version 是这条记录的关键,没有它就无法说明“标签变了是因为输入变了还是模型变了”。切换输入版本时,建议先双跑一段时间再切主路径。
分别看两种失败的代价
判定环节值得单独做,是因为它的错误在时间和位置上都可控,而生成环节的错误往往要等用户反馈才暴露。
| 失败类型 | 下游影响 | 发现时间差异 |
|---|---|---|
| 判错意图 | 错误进入分支:该转人工没转、该查单走了闲聊;生成环节会把错误意图“合理化”,用户需要二次纠正才能回到正轨 | 较早。意图标签在调用后立即落地,按分支统计和低置信度样本就能看出异常 |
| 生成跑偏 | 意图正确但措辞、事实、口径出错,例如政策表述、金额说明、承诺范围;这类问题不会触发任何分支异常 | 较晚。通常要等用户投诉、抽检或人工复核才发现 |
结论很直接:判定环节的错误靠日志和阈值就能前置拦截,生成环节的错误只能靠抽检和口径约束收敛。把判定单独拆出来,本质是给自己加一个可观测的前置闸门;如果这个闸门在你的业务里没有分支可拦,那就不必拆。
给判定环节单独设计兜底分支
兜底要写在判定之后、生成之前,让判错不至于原样进入回复。三类分支各有触发条件,不要用同一个阈值糊在一起。
- 低置信度转人工:置信度低于设定阈值,或 top1 与 top2 的差值过小,或意图落在高风险集合(退款、投诉、账号申诉)。动作是进入人工队列,回复用固定话术模板,不交给通用模型自由组织。
- 追问补全:意图大类可判但槽位不足,或输入过短无法区分分支。动作是让通用模型生成一句追问,明确问缺失的那一项,不执行任何业务动作。
- 默认分流:判定超时、返回非法标签、服务报错。动作是落到安全默认意图(通用咨询),标记 degraded,回复照常生成,不阻塞会话。
# 可替换示例:阈值按自己的日志分布调,不要直接照抄
intent:
timeout_ms: 800 # 超过即走默认分流
confidence_threshold: 0.6
margin_threshold: 0.15 # top1 与 top2 差值下限
high_risk_intents: [refund, complaint]
fallback_intent: general_consult
这些阈值先用可观测的初值上线,再按 intent_status 和 confidence 的分布回调;不要在没有日志分布的情况下直接定死。
排出串行调用的顺序与超时处理
顺序是串行的:预处理 → 判定 → 分支 → 生成 → 记录 → 返回。判定在生成之前,但判定失败不能把整条链路卡住。
用户输入
→ 预处理(脱敏、截断、取必要字段)
→ 调用 Xing4.0-29B-Intent(timeout_ms 上限)
├─ 成功且置信度足 → 进入对应分支,调通用模型生成
├─ 低置信度 → 追问补全 / 转人工模板
└─ 超时或非法返回 → 默认意图 + 通用模型生成,intent_status=degraded
→ 写 trace(intent、confidence、status、input_version)
→ 返回回复
几个执行细节值得固定下来:
- 判定超时后不在同一请求内重试,重试会叠加延迟;把超时计数交给监控,靠整体指标判断是否需要扩容。
- 判定结果作为可空入参传给生成:为空或非法时,提示词里走通用口径,回复照常输出。
- 调用前先用白名单校验返回标签,避免模型输出自由文本直接当分支键使用。
- 降级请求单独打标,便于事后区分“本来就该这么答”和“因为判定失败才这么答”。
# 调用骨架,字段名按自己的接入方式替换
resp = intent_client.classify({
'text': user_text,
'channel': channel,
'login_state': login_state,
'last_intent': prev_intent
}, timeout_ms=800)
intent = resp.get('intent')
if intent not in ALLOWED_INTENTS:
intent, status = FALLBACK_INTENT, 'degraded'
reply = chat_model.generate(intent=intent, text=user_text, style=policy_style)
log(trace_id, intent, resp.get('confidence'), status)
验证方式很朴素:在预发或测试环境分别制造一次正常判定、一次低置信度、一次超时,确认三条路径都能返回回复,并且日志里的 intent_status 各不相同。如果超时那次会让用户看到空白或报错,说明降级还没有真正接上。