意图路由
很多"AI 应用"的架构是:所有用户请求一股脑儿发给一个大语言模型,让它自己决定做什么。这样做能跑,但又慢又贵——而且大量请求其实根本不需要大模型:查个余额、改个密码、查个快递,一段普通代码几十毫秒就能完成。
意图路由把"理解用户要干什么"和"把事情办成"拆开:
text
┌─ 查询类(余额、账单、进度) ──▶ 确定性代码:调接口,直接返回
用户请求 ──▶ 象信 intent ──▶│─ 开放式咨询(理财、政策解读)──▶ 专用大模型 + 知识库
+ 置信度 │─ 投诉、敏感操作 ──────────────▶ 人工坐席
└─ other 或低置信度 ─────────────▶ 澄清 / 通用大模型 / 人工一个例子:城市政务服务热线
某市 12345 政务服务热线上线了网上留言入口,每天几千条留言。过去全部由人工坐席阅读后分派。我们希望:
- 办事进度查询:直接查工单系统并回复,不占用坐席;
- 政策咨询(社保、公积金、落户):交给接入了政策知识库的大模型生成答复草稿;
- 投诉举报:必须由人工处理,并按紧急程度排序;
- 其他或模型拿不准的:交人工分派。
第一步:意图分类(加上几个推测性问题)
python
from xiangxin import XiangxinClient, Choice, Noul, Score
client = XiangxinClient()
message = {
"channel": "网上留言",
"text": (
"我上个月 12 号在区政务中心交了公积金提取材料,说 15 个工作日办完,"
"到现在还没到账,受理号是 GJJ-2026-0831-0457,麻烦帮我查一下。"
),
}
INTENT = Choice(
instructions="市民这条留言的主要诉求是什么?",
criteria={
"progress_query": "查询已提交的业务办理进度,通常会给出受理号或办理时间",
"policy_question": "咨询政策规定、办事条件、所需材料或办理流程",
"complaint": "投诉或举报:对服务、执法、环境、噪音、违建等问题的不满或检举",
"suggestion": "对政府工作提出意见或建议",
"other": "以上都不是,或无法判断",
},
)
QUESTIONS = {
"intent": INTENT,
# 推测性问题:只有部分分支用得上,但一次问完几乎没有额外耗时
"has_ticket_no": Noul(instructions="留言中是否给出了业务受理号或工单号?"),
"urgency": Score(
instructions="留言所反映问题的紧急程度",
criteria=[
"一般咨询或建议,不紧急",
"影响个人正常办事,需要近期处理",
"涉及人身安全、公共安全或正在发生的严重问题",
],
),
"policy_topic": Choice(
instructions="如果是政策咨询,涉及哪个领域?",
criteria={
"social_security": "社保、医保、养老",
"housing_fund": "住房公积金",
"hukou": "户籍、落户、居住证",
"other": "其他领域",
},
),
}
resp = client.system_one(state=message, questions=QUESTIONS)
a = resp.answers第二步:按意图和置信度分发
python
import re
INTENT_FLOOR = 0.6 # 意图置信度低于它,交人工分派
def dispatch(a, text: str) -> str:
intent = a["intent"]
# 紧急问题无论什么类型,都直接进人工加急队列
if a["urgency"].score >= 1.5:
return "human:urgent"
if intent.confidence < INTENT_FLOOR or intent.choice == "other":
return "human:triage"
if intent.choice == "progress_query":
# 受理号的提取交给正则,模型只负责判断"有没有"
m = re.search(r"[A-Z]{2,5}-\d{4}-\d{4}-\d{4}", text)
if a["has_ticket_no"].noul > 0.5 and m:
return f"code:lookup_ticket({m.group(0)})"
return "code:ask_for_ticket_no"
if intent.choice == "policy_question":
topic = a["policy_topic"].choice
return f"llm:policy_assistant(topic={topic})"
if intent.choice in ("complaint", "suggestion"):
return "human:complaint_desk"
return "human:triage"
print(dispatch(a, message["text"]))
# 示意输出:code:lookup_ticket(GJJ-2026-0831-0457)这条留言没有经过任何大模型:象信判断出它是进度查询,正则取出受理号,工单系统返回结果。整个过程通常在几百毫秒内完成,成本是一次象信调用的零头。
省下了什么
假设热线每天 5,000 条留言,意图分布如下(示意数据):
| 意图 | 占比 | 处理者 | 需要大模型? |
|---|---|---|---|
| 进度查询 | 35% | 确定性代码 | 否 |
| 政策咨询 | 30% | 专用大模型 | 是 |
| 投诉举报 | 25% | 人工 | 否 |
| 建议 / 其他 / 低置信度 | 10% | 人工分派 | 否 |
如果所有留言都先交给大模型"理解",每天是 5,000 次大模型调用;采用意图路由后,只有约 1,500 条政策咨询需要大模型,而且它拿到的是已经确定主题的请求,可以配上更窄的知识库和提示词,效果往往更好。其余的请求只花了一次象信调用——按 ¥0.042/百万输入 token,一条 500 token 的留言约 ¥0.00002。
设计选项的要点
- 一定要有
other。 真实流量永远会出现你没想到的请求。没有兜底选项,模型只能在已有选项里硬选一个,而且可能选得很"自信"。 - 选项描述写具体特征,而不是抽象名称。 "通常会给出受理号或办理时间"比"进度查询"更容易判断。
- 选项的键名也会被模型读到。 用有意义的键名(
progress_query)而不是opt1,也不要让两个选项键名或描述几乎相同,见已知短板。 - 选项之间尽量互斥。 如果一条留言常常"既是投诉又是咨询",考虑把"是否包含投诉"单独做成一个 Noul。
- 选项多时考虑分层。 几十个意图可以先分大类、再分小类;单个 Choice 最多支持 255 个选项。
何时使用 / 何时不用
适合使用:
- 请求类型多样,但大部分类型有明确、便宜的处理方式;
- 大模型调用是主要成本或延迟来源;
- 某些类型必须由人工处理(合规、担责)。
不适合:
- 几乎所有请求都需要开放式生成回答,分类不会减少大模型调用;
- 意图边界本身模糊到连人工都难以一致判断——先梳理业务分类,再上模型。
常见陷阱
- 没有置信度门控。 意图分错会把请求送进完全错误的流程。一定要结合置信度门控路由。
- 让模型抽取值。 象信负责判断"有没有受理号",抽取具体字符串交给正则或解析器;如果候选值有多个,可以让象信在候选中做 Choice,见函数调用。
- 分类后再发第二次请求问细节。 细节问题能对原始留言提出的,就放进同一次请求。
- 从不复盘
other。 定期看落入other和低置信度的样本,它们会告诉你缺了哪些意图。

