做了这么多年后端,第一次接 AI 功能的时候,我下意识把它当成一个普通的外部服务来设计:调接口、拿结果、处理异常。这个直觉是对的,但只对了一半。
对的一半是:它确实是个外部依赖,超时、重试、降级、幂等、限流、审计,一样都不能少。错的一半是:它的失败模式和你熟悉的所有外部服务都不一样。普通接口挂了会报错,AI 挂了会给你一个非常流畅、语气笃定、但是错的答案。
这篇讲落地顺序、每个层次真正的难点,以及一份必须补的工程件清单。
一、先想清楚你在哪一层
「给系统加 AI」这句话下面藏着三件完全不同的事,难度和风险差着一个数量级:
| 层次 | 做什么 | 难度 | 什么时候够用 |
|---|---|---|---|
| 直接调用 | 把用户的输入交给模型,拿回结果 | 低 | 摘要、分类、改写、提取结构化信息、生成文案 |
| 检索增强(RAG) | 先从你的数据里找相关内容,再让模型基于它回答 | 中到高 | 需要用到私有知识:产品文档、历史工单、内部规范 |
| 自主 Agent | 让模型自己决定调什么工具、按什么顺序做 | 高 | 流程不固定、需要多步决策,且你能接受它偶尔跑偏 |
我的建议一直是:从最上面一层开始,确实不够用了再往下走。
原因很实在。很多人一上来就要做 RAG,其实是把「模型不会答」和「我的数据该怎么组织」两个难题捆在一起解——任何一头出问题,你都分不清是哪头的锅。而相当多的需求,靠一个写清楚的提示词加直接调用就能解决,根本用不上检索。
二、RAG 真正的难点不在向量库
如果有人告诉你「接个向量数据库,把文档塞进去就能问答」,那他多半没实际做过。向量库是这套系统里最简单的部分,真正决定效果的是这三件事:
难点一:怎么切
按固定长度切是最省事也最容易出问题的做法——一句话被切成两半,两半单独检索都没有意义。更好的方式是按语义结构切:按标题层级、按段落、按表格、按代码函数。一份技术文档和一份会议纪要,切法应该完全不同。
难点二:召回得准不准
用户问的和文档里写的,用词经常不一样。「报销要多久」对不上「费用报销周期为 5 个工作日」。纯向量检索在这种词汇不匹配上会漏,所以成熟的做法是向量检索加关键词检索混合,再加上一次重排序。
难点三:怎么评测
这是最容易被跳过、也最致命的一环。没有评测集,你就只能靠「问几个问题看看感觉不错」来判断——而这是最容易自欺欺人的方式。
最小可用的评测集长这样(几十条就够起步):
{
"question": "差旅住宿的报销标准是多少?",
"expected_source": "财务制度_v3.pdf#p12", ← 应该命中哪一段
"expected_answer_contains": ["500", "一线城市"],
"negative_case": false
}
关键字段是 expected_source——先测「召回对不对」,
再测「答得对不对」。顺序反了会白忙。还要专门准备反面用例:知识库里没有的问题,系统应该老实说「不知道」,而不是硬编一个答案。这一条在生产环境里非常重要——编一个答案的成本,比说不知道高得多。
三、Agent 的边界:什么适合,什么不适合
Agent 的能力是「自己决定下一步做什么」,代价是你放弃了一部分控制权。所以判断标准就一条:这个流程你能承受它偶尔跑偏吗?
| 适合交给 Agent | 不适合 |
|---|---|
| 步骤不固定、要按情况判断 | 步骤固定、有明确的流程图 |
| 出错可以回滚、代价低 | 涉及资金、权限、不可逆操作 |
| 有明确的停止条件和验收标准 | 没法定义「做完了」是什么 |
| 人会在最后看一眼 | 全自动、无人值守 |
一句实话:如果你的流程本来就能画成流程图,那就直接写代码实现它,别让模型去猜。确定性高的地方用代码,不确定性高的地方才用模型。很多人反过来做,然后把简单的事情搞复杂了。
四、MCP:把内部系统接给模型
MCP 说白了就是一套让模型调用你的工具的标准化接口。以前每接一个内部系统,你都要写一套适配;有了它,你把「查订单」「查库存」「创建工单」这类能力按统一格式暴露出去,支持这套标准的客户端都能直接调用。
用的时候有三条经验:
- 工具要细粒度,别做成万能接口。一个「执行任意 SQL」的工具,等于把数据库交出去了。要做成「按订单号查订单」这种具体、可校验、可鉴权的操作。
- 工具本身要做权限校验。不要指望模型会自觉地只看自己该看的东西,权限要在你的服务端强制。
- 写清楚工具的描述。模型是根据描述来决定用不用、怎么用的。描述写得含糊,它就用错——这和给同事写接口文档是同一件事。
五、必须补的工程件
这部分是我们后端的主场,也是 AI 功能最容易缺的一块。按顺序过一遍:
| 工程件 | 为什么必须有 | 具体做法 |
|---|---|---|
| 超时 | 模型响应比普通接口慢得多,且长尾很严重 | 设超时并区分「首字节超时」和「整体超时」;流式输出时更要设上限 |
| 重试 | 限流和偶发失败是常态 | 只对可重试错误重试;带退避;重试前换 request id,避免把重复请求凑成缓存命中而拿到同一个错答案 |
| 降级 | 模型不可用时业务不能整个挂掉 | 准备兜底:规则答案、缓存答案、或者干脆转人工,并明确告知用户 |
| 幂等 | 重试和重复提交都会重复扣费 | 业务侧生成幂等键;涉及写操作的工具尤其要注意 |
| 限流 | 一个死循环能让账单失控 | 按用户、按会话、按天分别设上限;超出要给出明确提示 |
| 成本闸门 | 成本不设上限等于裸奔 | 设置每日/每月预算,触达阈值告警,超出熔断 |
| 审计留痕 | 出事了要能回溯 | 记录输入、输出、用的模型、token 数、耗时、以及最终有没有被采纳 |
| 可观测 | 没有指标就没法优化 | 盯四个数:延迟分布、失败率、token 消耗、人工介入率 |
其中成本闸门是最多人漏的。它不是财务报表问题,是系统安全问题——一个在循环里反复调用模型的 bug,可以在你发现之前烧掉一个月的预算。
六、失败模式清单
上线前拿这份清单过一遍,能省掉大部分售后:
- 自信地胡说。知识库里没有的内容,它也会编。用评测集里的反面用例压住,并在产品上允许它说「不确定」。
- 提示词注入。用户输入里藏着「忽略上面的指令」这类内容。凡是用户输入要拼进提示词的,都要做隔离和转义,并且不要让模型拿到不该拿的工具。
- 上下文撑爆。多轮对话越攒越长,直到超限报错或者成本飙升。要有截断和摘要策略。
- 模型悄悄换了。你用的版本名可能指向更新的模型,行为变了但没人通知你。生产环境要锁定具体的版本号,不要依赖别名。
- 效果随时间退化。业务数据变了、知识库更新了,但没人重新跑评测。评测集要进 CI,至少在知识库更新后跑一次。
- 慢。用户能忍受的等待远比你以为的短。流式输出、先给部分结果、以及明确的进度提示,都是必要的。
备忘
- 三个层次:直接调用 → 检索增强 → 自主 Agent,难度与风险各差一个数量级,从最上面开始。
- 别一上来做 RAG:那是把「模型不会答」和「数据怎么组织」两个难题捆一起解。
- RAG 难点不在向量库,在怎么切(按语义结构,不是固定长度)、召回准不准(向量+关键词混合+重排序)、有没有评测集。
- 评测集关键字段是
expected_source:先测召回对不对,再测答得对不对;必须包含反面用例(不会就老实说不知道)。 - Agent 判断标准:能承受它偶尔跑偏吗?流程能画成流程图的,直接写代码,别让模型猜。
- MCP 三条:工具要细粒度可鉴权(别做「执行任意 SQL」)、权限在服务端强制、描述写清楚。
- 工程件八件套:超时(区分首字节/整体)、重试(带退避,重试换 request id)、降级、幂等、限流(按用户/会话/天)、成本闸门、审计留痕、可观测(延迟/失败率/token/人工介入率)。
- 失败模式:自信地胡说、提示词注入、上下文撑爆、模型悄悄换版(生产锁具体版本号)、效果随时间退化(评测进 CI)、慢(流式+进度提示)。
一起聊聊