做AI智能体时间长了,你会发现一个规律:demo好做,生产难。单体Agent跑通一个复盘类、问答类的任务很简单,但一旦进入真实业务——成千上万条知识库条目、十几个业务源头、动辄上万行的仓库分析——上下文窗口、单点检索、串行执行这些瓶颈马上就暴露出来。这篇文章想分享的是我在多轮生产实践里反复验证过的一套设计思路:分片与分散检索。核心就两句话:别让Agent一次吞下整个世界,而是把数据分片、把检索分散,再用聚合层兜住整体的高可靠性。适合正在从原型走向生产、被上下文超限和检索不准反复折磨的开发者参考。
1. 先弄清楚单体Agent到底怎么崩的
1.1 三类最典型的故障模式
说到高可靠设计,得先明确我们到底在防什么问题。单体Agent最常见的故障模式,我归纳成三类。
第一类,上下文超限。这是接触面最大的问题。我见过有人把一个30万字的知识库直接往上下文里塞,结果还没开始干活,调用直接返回超过最大长度。即便没超,token成本也高得吓人,一次请求烧掉几美元都是常事。更隐蔽的是,上下文越长,注意力越稀释,模型在5000行代码里找一个函数的行为逻辑,效果远不如给它50行精准片段来得清楚。这个问题不是"加大窗口"能解决的,窗口变大只是把崩溃点后移,成本和精度会一起恶化。
第二类,单点依赖崩溃。Agent依赖一个检索服务、一套向量库或一个第三方工具时,这个环节一挂,整个任务就跟着失败。我之前某个项目里用单一向量库做全部召回,结果那套服务一抖动,一批任务全部重跑,损失不小。做单点依赖还有个麻烦:排查困难。不知道是模型问题、工具问题还是数据源问题,只能一层层看日志。
第三类,串行执行导致的低可靠。任务步骤一步接一步,任何一步失败,后面全部白做。任务是长链条时,成功概率是各步成功率的乘积。假设每步成功率为90%,十步下来成功率只剩35%,这可靠性根本扛不住生产。
1.2 高可靠不是"绝对不挂",而是三件事
在实际工程里,高可靠不是玄学,也不是"绝对不挂",而是可度量的三件事:可用性、一致性、可恢复性。
可用性,指任务在合理时间内能完成。一个检索任务如果两秒内能给结果,就算可用;拖到三十秒,用户早走了。
一致性,指同一查询在不同时间、不同负载下,输出偏差可控。不是要求每次一模一样,而是核心结论不能忽对忽错。
可恢复性,指失败之后能降级或重试,而不是直接报废。比如检索源挂了,能不能退回老索引?这一路超时了,能不能用另一路的结果顶上?
分片解决的主要是可用性和一致性问题——把大任务变小,减少单次执行的波动范围;分散检索解决的是可用性和可恢复性问题——多路召回,一路挂了还有其他路。这两个模式不是割裂的,它们共同服务于同一个目标:让Agent在高负载、多故障的真实环境中依然给出稳定可用的输出。
2. 分片策略:别让Agent一次吞下整个世界
2.1 分片到底分什么,不是切文件那么简单
分片的核心不是把文件切成几段,而是根据任务语义选择正确的分片维度。维度选错了,切出来的片既不自洽,也没法独立处理。我实践下来主要分四种。
数据分片,按内容结构切。知识库按条目切,数据库按记录或分区键切,日志按区间切。这种分片最直观,也最好落地。
任务分片,把一个复杂任务拆成多个子任务。比如代码评审拆成静态分析、依赖检查、测试覆盖评估三路并行;再比如写一份行业报告,拆成市场现状、竞品分析、趋势判断三路分别调研,最后汇总。任务分片的重点在"边界清晰",子任务之间不能有纠缠不清的依赖。
时间窗口分片,处理流式数据、历史数据时按时间切。比如分析某个业务过去一年的趋势,按月份切成十二个窗口分别处理,每个窗口内部有完整的时间上下文,不会被跨月数据搅乱。
语义分片,按主题、实体或意图聚类。同话题的对话记录归为一组,同一实体的材料放一个片里。这种分片对检索类Agent尤其有用,因为查询往往绑定某个实体或主题。
选择维度的标准很简单:分完之后的每个片要自包含、可独立处理,且片与片之间尽量减少依赖。做不到自包含的切法,只是把原来的问题切碎了重组成更多问题。
2.2 分片大小的经验值,以及背后的计算逻辑
分片大小没有统一公式,但我摸索出一个经验区间:单片内容控制在3000到6000 token之间,这是多数模型既有足够上下文理解局部语义、又不至于因过长而漂移的舒适区。
算笔账你就明白为什么要这么设计。一个知识库有20万字,按中文字符大约1.5到2 token每个算,总token量在30万到40万之间。如果单片按5000 token算,需要切60到80片。这么多片全由Agent挨个串行处理,响应时效没法看,成本也会翻好几倍。所以分片往往不是单独出现的,它后面必然跟着并行处理——这也是下一章分散检索的底层逻辑之一:把串行变并行,把单点变多点。
分片还有一个经常被忽略的细节:重叠。切片如果完全不重叠,跨片上下文就断了。比如一个概念在A片段开头定义、在B片段末尾解释,切成两片后各自都只有半截信息。我通常把重叠区域控制在10%到20%,让相邻片段保留一段公共上下文。这样检索时不会漏掉跨边界的语义,代价是稍微增加一点token开销,完全值得。
2.3 分片之后的合并逻辑,比分片本身更重要
分片只是手段,最终还得把各片的结果合回来。这里有个关键经验:合并时优先收集各片的结构化输出,再做统一决策。
举个例子。分析一个仓库的代码质量,让每个子任务返回"问题列表加置信度评分",最后汇总排序。这样每个片的结果是结构化的、可比较的、可去重的,还可以做一致性校验——比如两个片都报了同一个文件有性能问题,那这个问题就值得提级处理。
反过来,如果让Agent逐片写长文再拼接,你会得到一堆又长又啰嗦、互相重复甚至互相矛盾的段落,整理成本比重新跑一遍还高。结构化中间结果比自然语言中间结果可靠得多,这一点在分片模式下怎么强调都不过分。
合并阶段的另一个要点是冲突处理。不同片对同一个问题可能有不同结论,这时候不要简单投票。我习惯的做法是给每个片配置一个权威度权重,来源更可靠、证据链更完整的片话语权更大。权重可以来自数据源的可信等级,也可以来自模型输出时附带的置信度分数。
3. 分散检索:多路召回才是可靠性的护城河
3.1 单一检索源为什么撑不住
单一检索源的问题一句话总结:你的可靠性等于你最短的那块木板。向量库挂了,检索全挂;向量库里没收录相关数据,结果再准也没用;即便一切正常,单一路径的召回覆盖率天然有限——它只会给你它认为最像的,而真正有用的可能藏在它不擅长的匹配方式里。
分散检索的思路就是:把"查一个库"变成"查多路源,再合并排序"。它的价值不只是容灾,更在于召回覆盖率的提升。不同检索方式看到的"相关性"完全不同,多路组合能互相补齐盲区。
3.2 多路检索的组合设计,我常用的四路配置
我实际项目里常用的检索路有这么几种组合。
向量检索,负责语义召回。适合用户说不清准确名词、只描述意图的场景,比如"那个关于支付超时退款的流程文档"。它的短板是精确匹配能力弱,专有名词、编号、代码符号经常跑偏。
关键词检索(BM25),负责精确匹配。适合含型号、编号、人名、报错码的查询。比如"ERR-2043",语义检索大概率会模糊化处理,BM25直接命中。它的短板是不理解同义语义,换个说法就召回不到。
结构化查询,负责元数据过滤。按时间范围、文档类型、作者、业务线过滤,把候选集缩小再喂给后续排序。它不做相似度判断,只管精确条件筛选。
工具或外部API检索,负责实时数据。比如查库存、查价格、查实时状态。这些信息不在静态知识库里,必须走外部接口。
各路检索各有优势和死角,组合起来才能覆盖真实场景里五花八门的查询。
3.3 合并、去重与重排,决定最终质量的三板斧
多路结果合并前,做分数归一化是第一步。向量相似度0.8和BM25得分8.0,数值上看着差十倍,实际可能代表差不多的相关程度,直接把原始分数相加是拿苹果加橙子。我用过的最小可行方案是min-max归一化,把各路分数压到0到1区间再线性加权。更精细的做法是让每路检索返回时附带一个校准后的置信度,但那个调起来费劲,初期用min-max够了。
归一化之后必须做语义去重。同一条信息可能被向量库、BM25、结构化查询同时命中,重复内容进了重排会虚高相关度。轻量做法是算文本的embedding相似度,阈值设在0.85以上视为重复,保留分数最高的一条。数据量大时可以上MinHash做近似去重,速度更快。
然后是重排。多路召回的结果合并后,我一般用一个轻量级的cross-encoder模型做终排,因为它能同时看到查询和文档,能捕捉到向量检索和BM25单独看不出来的关联。没有cross-encoder资源时,用规则融合——各路归一化分数加权加一个新鲜度或来源权威度的惩罚项——也能凑合,效果差一些但稳定。
4. 一个可以直接抄作业的落地配置
4.1 整体架构与模块划分
我最近在做一个企业内部的跨业务文档问答Agent(就叫它某企业内部文档智能问答Agent),架构分四层,每一层职责单一,互相独立。
接入层负责接收查询,做意图解析和参数清洗,判断查询是"找文档"还是"问数据"。
分片层负责按文档结构把长文档切出候选片段,建好索引,标注每片的元数据,包括所属业务线、时间、类型。
检索层并行调用四路检索:向量库召回、BM25召回、结构化过滤、外部API查询。
聚合层负责分数归一化、去重、重排,最后把Top结果连同引用片段一起交给模型生成答案。
四层互相独立,任何一层挂了其他层还能降级工作。这个架构的核心价值在于:故障被限制在单层内部,不会像单体Agent那样一个接口超时就把整条链路拖垮。
4.2 关键参数配置,我踩过坑后的推荐值
分片大小,正文部分已经说过,3000到6000 token,重叠10%到20%。
并发度,并行检索的并发数设为3到5路。不是越多越快,检索服务本身有吞吐上限,并发太高反而引发雪崩。实测3路检索和5路检索的延迟差距不大,但3路的资源消耗少很多。
超时控制,单路检索超时设500到800毫秒,总检索超时2秒。单路时间太长会拖住整体,所以要给每一路单独的超时上限。最长的一路决定整体延迟,所以还要配快速路径优先返回。
重试次数,幂等操作可以重试两次,非幂等操作比如外部数据写入,不重试直接走降级。多数检索是幂等只读的,重试安全性高,但要加指数退避,第一次等100毫秒,第二次等200毫秒,避免服务恢复过程中被重试请求压垮。
TopK取值,每路检索取top 20,合并去重重排后取top 5进入最终生成。取太多徒增生成阶段的上下文负担,取太少又容易漏掉正确答案。20和5是我试过多个值之后比较稳的组合。
4.3 降级能力,可靠性设计的最后防线
降级顺序我定为:向量库挂了就只用BM25;BM25也挂了就退到元数据精确匹配;所有检索源全挂就返回静态FAQ兜底。
这里有个原则:宁肯返回不完整的准确结果,也不要返回完整的错误结果。降级不是"做得差一点",而是"保证底线可用"。用户问一个问题,给三条相关文档的摘要,远好过给一大篇编造出来的自信满满的回答。
降级的触发要自动化。我在聚合层放了一个健康检查模块,每路检索返回时带上状态标记,聚合层根据各路健康状态动态调整权重。健康的路权重上调,不健康的路自动摘除。这个机制上线后,我再也不用半夜爬起来手动切开关了。
5. 常见问题与排查技巧实录
5.1 分片不均导致的结果偏差
这是分片最容易踩的坑。如果切片器只按固定字符数切,不看实际内容,就会出现有些片大得超限、有些片小得没信息。我排查过一次:某个大文档里有一段特别长的表格,切片器把整个表格完整保留成了一个超大片段,模型处理时直接截断,表格后半部分全部丢失,后续检索怎么都查不到那几条记录。
解决办法是改用长度感知切片器。切之前先估算token数,超出上限的片段递归下钻,找到段落边界再切。表格类内容要单独处理,按行列维度二次拆分,不能整块硬塞。
5.2 多路检索结果重复与结论冲突
多路检索对同一信息命中多次很常见。症状是最终答案里同一句话反复出现,或者重排结果里前三名都是同一篇文章的不同片段。处理方案前面说过,embedding相似度去重,阈值0.85,保留分最高的。
更麻烦的是结论冲突。向量检索认为方案A好,BM25把支持方案B的文档权重排得很高。这种情况不能靠去重解决,要靠证据链。我给每个片段标注来源层级和时效性,冲突时优先采信权威度更高的来源,同时在生成提示词里明确要求模型把冲突点暴露出来,而不是强行融合成一个含糊结论。
5.3 延迟抖动与慢路径拖累
并行检索不是万能的,最慢的一路决定整体延迟。我遇到过向量库响应突然从200毫秒涨到1.8秒的情况,整个查询跟着变慢。
处理技巧是差异化超时加动态等待。主要检索源给足时间,次要检索源严格限制。快速路先出结果就先渲染给用户,慢速路结果到了再补充刷新。这种方式在交互场景里体验改善非常明显——用户第一眼看到的是近实时的高质量结果,而不是干等一个缓慢的完美答案。
另外给每路检索加一个滑动窗口的延迟统计,超过历史均值两倍就主动降级。这个机制帮我挡了好几次检索源抖动引起的雪崩。
5.4 排查这类系统,我有一个固定套路
日志要打全链路的trace ID,从接入层到聚合层,一个查询一个ID。所有中间结果落盘,合并前的各路召回结果、归一化分数、去重命中记录都要能追溯。
我踩过最深的坑是:线上答案错了,但只查最终输出日志,完全看不出是哪一路检索带偏的。后来加了中间结果上报,问题定位从小时级缩短到分钟级。排查分布式检索系统的核心原则就一句话——必须能回答"这个答案凭什么这么生成"。
我个人在实际操作中还有一个体会:这套架构不是一步到位的。最早做单体Agent时,我也以为把上下文塞满就能让模型"看得全",结果又贵又慢还容易偏。后来一步一步加上分片、分散检索和聚合层,才真正把它跑稳了生产任务。如果你正卡在长文档处理和检索不准这个坎上,建议从小规模试验开始。先把检索源从一路加到三路,再引入分片,最后慢慢调合并和重排策略——顺序别反了。分片和分散检索不是银弹,但它们是目前把Agent从"能跑"推向"可靠"的最实用路径之一。