把通义千问接进工单系统:三个版本够用,但有两个坑没人提前告诉我

我们公司的工单系统跑了六年,去年年底加了一个 AI 助手:客服把用户的话粘进去,自动判断该派给哪个组、紧急程度几级、要不要拉人。

一开始很朴素------所有请求都丢给同一个模型。跑了两周,账单下来了,我盯着那个数字看了很久,然后决定重做。

不是模型不好。是我们把一个需要"想得很深"的活和一个只需要"归个类"的活,交给了同一个脑子。

一、先看一个很土但很有用的分法

工单系统的 AI 请求,拆开看其实只有三类:

类型 占请求量 特点
分类打标 71% 输出只有几个字段,不需要推理
摘要改写 22% 中等长度输入,输出 100 字左右
疑难判断 7% 长上下文,需要跨多轮记录找线索

这三类的成本不该一样。但当时我们是一个价。

改完之后,71% 的请求走通义千问里最小的那个版本,22% 走中间档,只有 7% 走大版本。月度 token 花费从 4,180 元降到 1,590 元,降了 62%。

而工单分类的准确率------从 91.2% 涨到 93.7%。是的,涨了。因为小模型在"输入很短、任务很明确"的分类上,反而更少自作聪明。

这一点当时挺意外,后来想通了:大模型的问题不是不够聪明,是有时候太聪明,会给你一个"更合理但不是你要的"答案。

二、接法:一份代码,三个模型名

通义千问的接口(Qwen API)是 OpenAI 兼容的,这一点省了非常多事。我们不需要为每个版本写一套客户端,只改 model 这个字符串。

python 复制代码
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["API_KEY"],
    base_url="https://api.tokenfreeai.com/v1",   # OpenAI 兼容,换服务只改这一行
)

ROUTE = {
    "classify": "qwen-turbo",     # 分类打标,71%
    "summarize": "qwen-plus",     # 摘要改写,22%
    "reason":    "qwen-max",      # 疑难判断,7%
}

def ask(task: str, prompt: str, **kw):
    return client.chat.completions.create(
        model=ROUTE[task],
        messages=[{"role": "user", "content": prompt}],
        **kw,
    )

调用方传的不是模型名,是任务名。这个抽象很重要------它意味着以后换模型,改的是这张表,不是散落在四十几个文件里的字符串。

上线三个月,这张表改过五次。如果没有这层,那五次就会变成四十个文件的 diff。

三、坑一:长文本不是"上下文更长",是"计价方式不一样"

这个是真正让我吃了一次亏的地方。

工单里有不少"疑难判断"要读历史记录,一条对话可能几万字。我一开始想得很简单------找个支持长上下文的版本,把整段丢进去不就行了。

于是我把 7% 的疑难工单全切到长文本版本,跑了三天,成本没降反升,而且升得很多。

原因在计价结构上:长文本模型的输入单价和你按普通模型算出来的完全不是一回事。 我原来按"普通模型贵一点"估的,实际结算是另一套折算,超出的部分比我想的狠。

后来改成两步:先用普通版本对历史记录做一次压缩,把 3 万字的对话压成 800 字的摘要,再交给长文本版本判断。成本回到预期,判断质量几乎没掉------因为对"该派给谁"这个问题,原始对话里 95% 的内容是噪音。

一个通用结论:上下文长度是能力,不是免费的容量。 能用摘要解决的事,别用长上下文硬扛。

这也是我用 token 计费这件事上最实在的一课:单价表看起来简单,但输入长度、输出长度、缓存命中,每一项都在改最终数字。RFC 里没写的那部分,得自己跑一个月账单才看得见。

四、坑二:结构化输出的 strict 模式,不是所有版本都一致

工单分类的输出必须是机器能吃的。我们用的是结构化输出,JSON Schema 写死字段:group、urgency、need_escalate。

问题出在小版本上。同一份 Schema,大版本老老实实按字段返回,小版本偶尔会:

  • 把 urgency 从整数变成 "3" 字符串
  • 在 JSON 外面包一层 ```json 代码块
  • 字段顺序乱掉(这个其实不影响,但我们的老解析器按位置取的)

第三个是我们的问题,前两个是真实的兼容性差异。

解决方式很土:在代码里做一次归一化,不指望模型。 字符串数字转整数,剥掉代码块包裹,按 key 取值不用位置。二十行代码,比换模型便宜得多。

教训:不要假设"同一个接口"等于"同一个行为"。 版本间的差异往往不在能力上,在边界情况的处理上。

五、限流和并发:这件事比模型选择更影响体验

我们做过一次线上观测:真正让客服抱怨"这个 AI 好慢"的请求里,只有三成是真的模型慢,另外七成是在队列里等。

原因是并发开太大。我们把并发从 32 调到 12,同时加了两个东西:

一是超时降档------超过 2.5 秒没拿到首字,换一个小版本先回一个"正在分析",后台继续跑。用户看到的是有反应,不是转圈。

二是按任务分级排队------分类打标这类短请求走快队列,疑难判断走慢队列,互不占坑。以前一个疑难工单会把后面二十个分类请求全堵住。

改完之后,端到端 P95 从 6.8 秒降到 2.1 秒。这个 2.1 秒里,首字延迟占 1.3 秒左右,剩下的是网络和渲染。

代码里的地址和密钥是一个入口一套,账单按子密钥分了"工单分类"和"疑难判断"两个账本,月底对账不用再拿 Excel 拆。我这边用的是词元云(tokenfreeai.com),几个版本在同一个密钥下面,做这种按任务的成本拆分省了不少事;用各家官方后台也能做,就是得开几个账号对着看。

六、三个版本各自适合干什么(跑了一年之后的结论)

小版本:分类、打标、简短改写、意图识别。输入短、输出结构固定、要求快。这类任务上它和大版本的差距小到可以忽略,价格却差一个数量级。

中间版本:摘要、多轮改写、带一点推理的判断。这是最"万金油"的一档,我个人用得最多。

大版本:跨多份材料的比对、需要按步骤推理的疑难、写出来要直接给人看的长文。真用它的时候,你会发现它值那个价------但如果你发现自己 30% 的请求都在用大版本,先别调价,先看看是不是任务没分好。

技术选型里最贵的一课,从来不是选错了模型,是根本没分。 一个模型走天下,最后一定是大材小用和小材大用同时发生------这也是我们在选大模型 API 时最该先想清楚的一件事。

相关推荐
wei_shuo1 小时前
MongoDB迁移实战:基于SQL的文档数据开发与多模态融合实践
后端
小兔子2 小时前
os.path.join 遇到绝对路径会丢掉前面所有段,这条规则写在文档第二句
后端
小兔子2 小时前
Go 的接口是类型加值,所以赋了 nil 的 err 判断起来不等于 nil
后端
不一样的少年_2 小时前
不花一分额外 API 钱,用现成的 Claude Code / Codex / Cursor 搭建 AI 个人知识库
前端·后端·ai编程
用户6919026813392 小时前
Redis 1:简述Redis概念与Agent的联系
redis·后端·设计
艾思控驱动器2 小时前
手写一个电流环采样时序校核工具:为什么你的 FOC 电流总差几个百分点
后端
智驭未来掌门人2 小时前
桌面程序里的“1K 以内”:到底该限制哪一层像素(C++ Qt 版)
后端
reesun2 小时前
分层干不干净,要等你想换掉一层才见分晓:DuckDB 2.0 换掉 parser 靠的那道缝
后端
逸风尊者2 小时前
智能调度系统从0到1:系统架构设计方法
后端