一张六步工单,跑完要42.6秒,成本0.184美元。复盘时团队发现,问题不在模型不够强,而在每一步都用了同一个前沿推理模型——连改写搜索词、格式化JSON这种活,也在为“深思熟虑”付费。

这是TICKET-8841事件的下半场。上一阶段,计费代理把一笔48000美元的信用额度直接结掉,本该转人工。出口处的确定性闸门拦住了资金流动,但闸门只止血,不解释代理为什么一开始就得出错误结论。真正的原因在上游:边界2和边界3。

先被否掉的方案:把手册全塞进提示词

复盘会上有人提了个直接的问题:现在前沿模型有百万级上下文窗口,为什么还要维护检索管线?把350页的财务与合同手册整个灌进提示词不就行了。

团队测了。成本上,350页手册约175000个token,按每百万输入token 3美元的示意价格,单次调用0.525美元;每月45000张工单,未缓存约23600美元,算上约90%的提示缓存折扣约2400美元。现有检索管线每月约600美元。差距真实存在,但不算悬殊,所以成本不是最硬的理由。

召回上,长上下文表现不错但并非完美。Anthropic公布Opus 4.6在100万token变体的8针MRCR v2上约为76%,这是合成基准,长度远超这份手册,说明不了自家文档的精确表现。它只说明一件事:“模型会找到那一条款”是需要实测的主张,而在金融合同上漏掉一次代价很高。

可追溯性才是关键。走检索时,进入提示词的具体片段是可记录、可重放的小工件;灌进17.5万token,就说不清模型到底倚重了哪一条款,而且手册每改一次,整个提示词都变。结论是保留检索,问题在于2026年还在跑2023年那套“检索v1”。

故障一:固定长度切块,把条件和公式切散了

入库脚本每512个token切一次PDF。凌晨2点14分,向量库里躺着这样两块内容:chunk_041结尾是“企业年付套餐的席位缩减须在续约日前至少30个日历日书面提交,中期或逾期缩减不予退款,仅适用于下一续约周期”;chunk_042开头是表4.2的席位降级按比例抵扣公式。

客户消息里提到“降级40个席位”和“已计费”,与公式表匹配度很高。chunk_042余弦相似度0.86,而让公式不适用的那个前置条件chunk_041只有0.71,掉出前三。规划器看到的是一张没有前提的公式。

团队改了三处:

  • 按章节做父子切块,子块用于搜索,返回时永远带上完整父章节,4.2节和表4.2一起给;每个文档版本打上生效时间和被取代时间,过期政策无法被检索到。
  • 混合检索加重排,先用稠密向量和BM25关键词做倒数排名融合取前20个候选,再经交叉编码器重排取前5。在200张工单的回归集上,政策例外召回率从64%升到97%
  • 把事故钉成回归测试,冻结凌晨2点14分那次丢掉“年付”字样的错误查询改写,断言新管线仍能检索到通知条款。

故障二:直接对指标库跑原始SQL

边界3处,代理通过MCP对数据仓库写了SELECT active_seats FROM subscriptions。查询本身合法,问题在语义:仓库里active_seats是实时运营遥测值,contracted_billable_seats才是受治理的财务指标。模型挑了一个看起来合理的列,又赶上每晚的dbt任务正在刷新,读到的是80而不是120。

文本转SQL从来不是难点,转成“正确的那条SQL”才是语义问题。团队删掉了run_sql()工具,MCP服务器改为只暴露一个受治理的工具,用一次原子读取返回定义明确的指标:合同计费席位120、遥测活跃席位80、通知窗口剩余天数-13。

-13就是全部答案:降级请求发生在续约前17天,而要求是30天。代理不再需要在两个席位字段之间做选择,由合同来选,数据来自一致快照而非刷新中的表。

延迟与成本解剖

重放测试全绿之后,团队才回头看42.6秒和0.184美元。答案有点尴尬:每个边界都路由给了前沿推理模型。规划器决定整次运行成败,而一旦规格明确,执行层几乎可以互换。

于是只在边界4保留前沿模型——那是唯一需要多条款合同判断的一步,其余按任务分级路由。端到端延迟下降86%,从42.6秒到5.86秒;单票成本下降82.7%。按每月45000张工单算,推理费用约8280美元对1430美元,不含任何自托管模型的托管成本。

两处代价值得记下:正确性略贵了一点,检索从0.9秒变成1.15秒,因为混合搜索和重排做了更多工作,这是划算的交换;闸门让路由变安全,“200条重放轨迹零误批”不是靠便宜模型守规矩,而是结构性的——超过5000美元的资金动作,无论哪个模型说什么都会被拦下。

贯穿两次复盘的形状是一样的:概率模型极擅长把混乱的人类意图变成结构化计划,却不擅长充当自己的数据库目录、访问控制层和回归测试集。管用的是把它包进确定性工程——入口是语义契约,中段是外科手术式的算力分配,出口是代码里的硬不变量,CI里是冻结的切点重放。

如果正在调试一个代理,可以先问一句:上一次真实事故的修复,有没有在当初导致它的那个上游状态上跑过?如果答案是“我重跑了几次”,就从那里开始。