年初接了一个偏门需求:把几家分支机构的知识库、工单系统、产品手册全塞给大模型,做一套既能回答问题、又能主动查数据、还能触发后续流程的「分析型助理」。一开始我以为就是个普通的RAG项目,真正做起来才发现,单靠提示词堆料完全不够,上下文怎么喂、喂多长、模型什么时候该调用哪个工具、多轮对话里哪些历史要保留,这些问题搅在一起,逼着我从提示词工程、上下文工程和agent框架三个层面同步下手。
这篇文章不聊理论,就讲这套联合应用的落地过程,包括三个层面各自解决什么问题、我在关键环节的取舍依据、以及实测踩过的坑。想搭一套复杂智能体、或者正在把RAG往生产级推进的朋友,可以对照着少走点弯路。
1. 三层能力边界:提示词、上下文与agent框架的定位划分
动手之前,我先逼着自己把三个概念理干净。很多人把提示词工程、上下文工程、agent框架混为一谈,真到协作时职责不清楚,系统一复杂就乱套。
1.1 核心定位的差异化拆解
我的理解是:
提示词工程解决的是「模型的行为规范」问题。包括角色设定、输出格式约束、任务分解策略、答案风格引导。它的作用范围是从用户输入到模型输出这一层映射关系。一个人问「帮我看看上周销量」,提示词决定的不是上周销量数据从哪来,而是模型用什么语气、什么结构、什么维度来呈现答案。
上下文工程解决的是「模型的信息边界」问题。模型本身是离线训练的,它的知识截止在训练那一刻。上下文工程负责把当前任务真正需要的私有数据、实时数据、历史对话摘要,以合理的方式拼装到模型的输入窗口里。它的关键词是筛选、压缩、编排、更新。没有它,提示词写得再好,模型也只能凭想象回答。
agent框架解决的是「模型的执行链路」问题。一个任务往往不是一次问答就能结束的,需要模型判断需不需要查数据库、查完之后要不要再追问、是不是要调一个外部API、失败之后怎么重试。agent框架负责把这些步骤组织成循环:规划、执行、观察、再规划。
这三个层面是层层依赖的关系:agent框架一个循环里包含了很多次模型调用,每一次调用之前都要由上下文工程组装信息,而组装完信息之后的模型行为,由提示词来做规范和约束。
1.2 三个层面如果没有配合会出什么问题
我在项目早期做了个对比测试。同一套业务问题,分别用三种方案实现:
方案A:只用提示词工程。把角色、格式限制写得很详细,但知识库里新增的产品资料根本没进入上下文。实测10个问题里,有4个模型给出了貌似合理、实际完全是编造的操作建议。原因很直接:模型根本没看过这些资料,再强的格式约束也改变不了它的知识底子。
方案B:提示词加上下文工程,但不引入agent框架。对于「XX型号打印机WIFI连不上怎么办」这类直接检索型问题,效果确实不错。但问题是用户不会一直问简单东西,当问题变成「对比一下办公区两台设备最近两个月的故障率,分析一下要不要统一换新」时,光靠一次检索+一次生成完全搞不定,需要拆成多轮检索、甚至要动态调用统计工具才能算出来。没有agent的调度能力,这类复合任务基本歇菜。
方案C:三个层面都上。问题进来之后先由上下文工程做意图识别和关键词预处理,agent根据意图决定执行路径,涉及数据分析时调用代码解释器,涉及文档查询时切换检索策略,每一轮工具返回的结果都重新拼接进上下文,最终由统一的提示词模板约束输出标准。
结论很清楚:提示词管嘴巴,上下文管食材,agent管动作。三者缺一,复杂任务就露出明显的短板。
2. 提示词工程的关键实施细节
提示词工程看起来门槛不高,反正就是写一段话让模型照着做。但做到生产级,需要花精力打磨的地方远比想象多。下面是我觉得最值得抠的三个方向。
2.1 角色与任务的显式分层
我见过太多提示词把角色设定和能力描述混成一团,模型容易抓不住重点。我的做法是拆成三层写。
第一层是角色声明,一句话定义模型是什么,比如「你是设备运维助理工程师,负责解读设备数据与操作手册」。这一层决定模型的回答基调和专业程度。
第二层是职责边界,明确说明模型在什么情况下做什么、不做什么。比如「只依据提供的资料回答问题,资料中找不到相关内容时明确说明,不得编造操作步骤」。这一层是防幻觉的第一道闸门。
第三层才是任务指令,针对当前具体问题给出的操作要求。
这个分层写法的价值在于,模型对「你是谁」和「你能做什么」的权重是不同的。把职责边界放在角色声明后面,可以让模型的自我认知约束前置,比只在结尾加一句「如果不知道就说不知道」管用得多。
2.2 few-shot示例的选样策略
有一次,我为了做产品对比功能,在提示词里放了两组示例,一组是价格对比,一组是参数对比。实测下来,模型回答产品参数类问题时,结构明显偏向示例的格式,但数据引用经常错位,金额和数量混为一谈。
后来把示例换成一组真实的「带数据引用」的对比分析后,问题基本消失。这说明示例选择的关联度极其重要。示例不是随便找两个差不多的就行的,它必须覆盖当前任务的真实数据形态。如果示例里的数据格式都带着单位、引文编号、表格结构,模型就会模仿那个形态去组织输出。
选择示例时,我通常坚持三条原则:
- 示例和真实问题的领域强相关,最好是同一类子任务
- 示例里必须包含标准的结构化要素,比如「依据资料[x]的说明,该参数正常范围是...」
- 示例的覆盖顺序要跟常见问题类型匹配,用户最常问的类型放最前面
2.3 结构化输出与运行时校验
生产级提示词输出不能停留在「看起来不错」的程度,必须让下游程序能稳定解析。我建了一套输出契约,在提示词里直接约束返回JSON格式,包含固定的字段,并且说明每个字段的值域。
实测最影响解析稳定性的不是格式措辞,而是模型偶尔会在JSON里塞多余的解释性文字,或者把字段名微调变形。我在提示词里加了「只输出JSON对象,不要输出任何解释性内容」,同时在下游加了一层解析容错:先正则提取JSON区块,再尝试解析,解析失败才进入重试逻辑,而不是直接返回错误。
这一套下来,几百次测试的输出可解析率大概从88%提到了接近98%。剩下的2%靠agent循环里的重试兜底,具体见后面agent部分。
3. 上下文工程:把信息精准投喂到模型的「工作台」
上下文工程是我这次项目里花时间最多、也是收获最大的一块。它不像提示词那样可以通过肉眼快速迭代,因为它牵涉到数据流、窗口占用和动态更新,出了问题排查也比较绕。
3.1 检索增强不是简单拼接:先重构信息再进上下文
最初我图省事,知识库文档直接按固定长度切块,用户提问时拿问题原文去做相似度检索,然后把命中的几个块原样拼进上下文。结果模型经常答非所问:检索到的一个块是关于安装步骤的中间段,标题信息不在块里,模型就不知道这段话在说哪个产品。
后来我对信息重构做了以下几件事:
第一,块切分从固定长度改成按语义边界切。一级标题、二级标题这样的章节结构作为切分主轴,块内保留上下文连通性,块与块之间允许部分重叠。
第二,构建块摘要。每个块除了正文内容,还生成一个50字以内的摘要,包含产品型号、章节主题、核心结论。检索时把摘要和正文一起做相似度计算,只拿正文进模型上下文。这样既保证检索精度,又减少无关噪声占用窗口。
第三,答案拼接时带上来源标记。在每块正文前面加「【资料1】产品手册-型号X-章节3.2」,模型回答问题时就会带着编号引用。这招是后续做溯源验证的关键,也是用户信任感的来源。
3.2 上下文预算管理:窗口不是用来装的,是用来算的
这是上下文工程里最核心的意识转变。模型上下文窗口是有限的,但真实业务资料是无限的,不可能全塞进去。上下文预算就是给每一次模型调用计算出「最佳投喂量」。
我的固定套路是:
- 系统提示词固定占用约1200 token(含角色、职责边界、few-shot示例)
- 历史对话摘要压缩到600 token以内
- 查询任务相关的检索块,最多取4块,每块控制在300 token上下,总量约1200 token
- 预留约400 token给工具调用返回结果
整体目标是把单次调用的输入稳定控制在3500到4000 token之间,即使模型上下文窗口有128K,也只启用它的零头。很多人以为窗口大就能多塞,实际上输入过长会显著增加延迟、成本和出现注意力分散的风险,模型反而更容易被中段噪声带跑。把信息裁剪到刚好够用,是我做过的最有效的质量提升手段。
3.3 多轮对话中的记忆分层
多轮对话最容易翻车的点在于:把每一轮的历史原文全塞进去,窗口快速膨胀,然后模型被早期错误信息带偏。或者反过来,一句历史都不带,用户说「它那个问题解决了吗」,模型完全不知道「它」指谁。
我这个项目里用了两层记忆:
短期记忆:最近3轮对话的原文摘要,按角色交替保存,每轮不超过100 token。用于处理直接指代和基本承接。
长期记忆:每一轮结束之后,由模型从当前对话中抽取结构化事实,存入一个独立列表。比如设备型号、故障现象、最后结论、遗留事项。下一次对话把这份事实表放进上下文,模型就有了「真正记住的东西」。
实际测试下来,长期记忆抽取带来的体验提升非常明显。用户问「上次说的那个噪音问题,后来排查到哪一步了」,模型能准确接住,而不是只能复述聊天历史。
注意:长期记忆必须做「过期清理」。我在结构化的同时给每项记忆打上生成时间戳,超过7天的自动丢弃,避免旧信息污染新决策。尤其是设备状态类信息,时效性极强。
4. agent框架:让模型从「回答问题」进入「完成任务」
如果说上下文工程解决的是「喂什么」,agent框架解决的就是「干什么」。一个复杂任务要拆成几步、每一步用什么工具、中间结果如何判断合法性,这是agent框架的职责。
4.1 任务分解与工具调用的循环设计
我先用最简单的方式跑通闭环:问题进来后丢给模型,模型输出一份「行动计划」,包含若干步骤,每个步骤对应一个工具调用或一次检索操作。然后由程序逐条执行计划,把执行结果反馈给模型,模型根据结果决定是继续下一步还是直接收敛答案。
具体到代码层面,核心循环大概长这样:
用户输入 → 上下文组装 → 模型推理 → 工具调用指令解析 ↑ ↓ └──── 结果反馈并入上下文 ← 工具执行并返回结果这个循环最关键的地方在于:模型每次收到的输入必须包含两个部分:当前目标、以及上一步工具返回的真实结果。回流的信息不经过筛选直接进下一个推理轮次,保证模型能基于真实数据决策,而不是靠记忆补完。
遇到工具执行失败时,我让模型看到报错信息并自行判断重试还是换路径。比如一次数据查询因为参数格式问题失败,模型会在下一步计划里修正参数重新调用,而不是简单把错误抛给用户。
4.2 防止「无限空转」:最大轮次与终止条件
agent框架最大的工程隐患是循环失控。模型会陷入一种情况:反复调同一个工具,调完说不行,再调,再不行,最后在兜圈子里耗尽时间和预算。
我设置了三个终止条件:
- 最大轮次限制:单次任务最多执行6个工具调用,超出直接降级为「基于现有信息给出最终回答」
- 收敛判定:连续两轮工具结果没有产生新的有效信息,强制结束
- 答案确认:模型认为自己已经完成目标时,输出终态标记
这三个条件里,收敛判定最省成本。我记录了每一轮工具结果的摘要哈希,如果本轮结果摘要和上一轮相同,就判定没有新信息,直接终止循环。实测至少砍掉了三成无意义轮次。
4.3 单一agent还是多agent分工
一开始我的设计是「一个大代理包打天下」,什么任务都往里塞。结果问题多了之后,提示词里的任务说明越来越长,面对不同类型请求时,模型行为开始「人格分裂」:有时候像运维专家,有时候像数据分析师,切换不够稳定。
我把架构改成「主agent + 子agent」的分层模式。主agent负责意图识别和任务拆分,判断请求属于「文档问答」「数据分析」「工单操作」三种类型中的哪一种,然后把子任务分发给对应的子agent。每个子agent保留自己独立的系统提示词、工具集和上下文模板。
改完之后整体稳定性提升很明显。原因也不复杂:每个子agent的提示词足够聚焦,上下文工程也更有针对性——文档问答走RAG检索,数据分析走工具调用。相比之下,单一agent的上下文窗口鱼龙混杂,很容易互相干扰。
个人经验:除非任务类型极其单一,否则不要把所有能力捆绑进一个agent里。面对复合场景,分层设计带来的维护成本,远小于单agent混沌状态下的调试成本。
5. 联合实战:一个跨文档分析场景的完整拆解
讲完三个层面的方法论,我用一个实际场景把它们串起来,看看一次完整的任务是怎么在联合体系里流转的。这个场景是我们内部系统里的高频请求,我用它做了大量回归测试。
场景:用户提了一个问题——
「帮我分析一下办公区A区和B区最近三个月的设备报修趋势,主要看哪些型号的问题在增加。如果B区某个型号的报修量占比超过30%,帮我拉一份该型号的维护建议摘要,并按月生成一张趋势说明表。」
这个问题如果交给普通RAG,基本只能部分回答,而且很可能答错。因为这里既有数据查询,又有条件判断,还要查询文档生成摘要建议,是一个典型的复合任务。
流程大致如下:
第一步:主agent收到问题后,解析出关键要素:时间范围(最近3个月)、对象(A区/B区)、指标(报修趋势、型号占比)、条件阈值(30%)、输出动作(建议摘要和趋势表)。
第二步:主agent将任务拆成三个阶段:数据查询阶段、条件判断阶段、文档查询与汇总阶段,并在上下文中写入任务状态标记。
第三步:数据分析子agent执行第一步,调用数据查询工具。工具返回近三个月报修明细,按型号聚合。模型读取所有返回内容,识别出B区确有某型号的报修占比达到34.7%,超过阈值,于是触发第三步。
第四步:文档问答子agent以该型号为关键词做检索,把资料切块过一遍增强流程,找到操作手册中的维护建议部分,提取并压缩成摘要。
第五步:结果回流到主agent,由它统一按预设的表格模板生成最终答案。表格列包含月份、报修总数、某型号报修量、占比趋势,回答段落里带出摘要建议,并附检索到的资料编号。
整个任务一共消耗了5轮模型调用、2次工具调用,总耗时约11秒。而同一问题如果由人来手动查三个系统再整理,正常需要20分钟以上。
这套流程能跑通的关键,就是三个层面的职责配合:agent决定先查数再查文档,上下文工程保证数据查询结果和文档块精准入槽,提示词则统一了每个阶段的输出格式,让程序能解析「触发条件」和「汇总结构」。
6. 踩过的坑与排查实录
联合应用的系统调试难度,不是三个模块难度的简单相加。我整理几个实际工程里最高频的问题,基本覆盖了大部分优化需求。
6.1 模型的「幻觉来源」常被误判
一开始我认为幻觉是提示词没写够导致的。后来追踪了一批幻觉样本,发现接近六成的问题根因在上下文工程:检索阶段没找到正确内容,或者找到的内容块本身就不含答案信息。模型面对「没有可用信息」的输入,本能地开始编造,哪怕是提示词里写了「不知道就说不知道」。
排查方法很简单:给每次模型调用加日志,记录输入上下文包含哪些资料块、模型输出引用了哪些编号。校验引用编号是不是真的存在于本次上下文里。这组日志跑了两周,基本能定位出幻觉出现在检索层还是生成层,再针对性修复。
6.2 上下文压缩导致的信息丢失
压缩历史对话时我踩过一次大坑。当时用了一个比较激进的摘要策略,把一轮长对话压成一句话。结果到下一轮,模型完全丢掉了关键参数细节,比如设备型号被摘要成了「那台设备」,后续工具调用传参时直接报错。
后来我调整了压缩策略:摘要只压缩「过程性对话」,关键结构化要素单独建字段保存,比如设备型号、时间范围、数值阈值这些,不参与摘要压缩,以原样进入下一轮上下文。这个改动之后,跨轮次任务的成功率大幅提升。
6.3 工具调用时的格式抖动
模型在决定调用工具时,偶尔会输出不符合预期的参数结构。尤其是添加字段之后,旧分支里的模型仍然按旧字段名传参,造成解析失败。
我的对策是一端双保险:一方面在提示词的工具说明里强调字段名精确匹配,另一方面在代码里给每个工具加参数校正层。校正逻辑是:如果发现字段名不匹配,尝试做一次别名映射,比如「machine_type」映射到「device_type」。映射失败才真的报错。
6.4 多agent之间的上下文串扰
分层agent上线后出现了一个隐蔽问题:数据子agent的推理风格会「传染」给文档问答子agent。排查后发现是共享了底层基础模板,系统提示词里的风格描述互相影响。
处理办法是把每个子agent的提示词完全独立,包括风格词、约束词、示例,全部各自维护一份。同时增加每次调用的「身份前缀」,比如在系统消息里强调「你是文档问答助理」和「你是数据分析助理」,从输入层就把身份区分开。
7. 工具选型与工程落地经验
最后聊几句工具层面的实战心得。这一套架构的落地,我强烈建议先确定编排层再选模型,而不是反过来。
编排层的选型,我试过用代码直接写循环逻辑,也试过更成熟的agent框架。直接用代码写的优势是可控性最强,每个循环节点都可以加日志、断点、人为干预,但缺点是工具多了之后,维护成本飙升。成熟框架的缺点是封装较黑盒,出问题时排查链路比较长。
我的最终选择是轻量框架 + 核心循环自研的组合:框架提供工具调用与模型接口的基础能力,核心的终止判定、上下文预算、任务状态管理等关键逻辑全部收在自己代码里。这样既保留了灵活性,又不会重复造轮子。
模型选型方面,效果差异最大的不是推理能力上限,而是指令跟随稳定性。同样的提示词模板,有些模型在工具调用格式上频繁抖动,有些就非常稳定。有条件的话,先用一周的线上问题集做A/B测试再定,别只看单条prompt的对齐效果。
检索方面,不要把「向量检索」当成唯一答案。很多业务场景里,关键词检索 + 过滤条件的效果不比向量差,而且响应更快。我的项目里最终用的是向量+关键词混合检索,按得分加权合并,实测在设备型号、工单编号这类精确匹配场景下,准确率明显优于纯向量方案。
最后再分享一个后来养成但非常受益的习惯:给每个版本的系统建立「回归问题集」。这个集合覆盖了五种常见任务类型、二十条经典问题,外加十条容易触发幻觉的刁钻问题。每次修改提示词、调整上下文策略、升级模型,都先跑一遍问题集,再对比输出差异。这套体系看起来笨重,却是保证联合应用系统长期稳定迭代最扎实的办法。很多偶发问题在新增功能后悄然出现,没有回归集的兜底,上线后真的会措手不及。