上下文工程实战:四根支柱让AI Agent告别失控

发布时间:2026/10/21 7:13:46
上下文工程实战:四根支柱让AI Agent告别失控 做AI Agent最难受的时刻不是模型偶尔给你一句胡说八道而是你明明已经把指令写得清清楚楚它却在一个意想不到的角落偷偷发挥。我早期做某客服Agent的时候系统提示词写了将近三屏每一条都觉得自己在跟模型“交心”结果一上线用户问一个稍微绕弯的问题它就开始自己编流程。后来我意识到问题不在提示词写得不够多而在于整个上下文没有工程化。上下文工程这个词听起来像概念实际上是一件非常具体的事决定哪些信息进入模型、以什么顺序进入、以什么结构呈现、过期之后怎么淘汰、输出之后怎么兜底。这篇文章把上下文工程拆成四个支柱——输入结构化、检索增强、记忆管理、输出校验——逐条讲清楚怎么落地适合正在搭Agent、或者已经在跑但效果不稳定的开发者参考。内容偏实操看完可以直接对你的上下文管线做一次体检。1. 上下文工程是什么为什么它决定Agent的确定性1.1 Agent不稳定的根子其实在上下文很多人把Agent的“不稳定”归咎于模型本身的随机性话只对了一半。模型确实有采样随机性但更常在线上出问题的是模型拿到了一堆没说清楚的输入。我给你一个例子你就明白了同样一个模型你给它一句“帮我处理用户退款”它能自由发挥出十种路径你明确告诉它“用户符合A类条件时走流程B否则走流程C每一步都要输出当前状态”它的行为就收敛到一种可审计的路径。这种偏差的根源是模型只能依据你给的材料推理。材料里如果混进了互相矛盾的指令、过期的历史记录、无关的知识片段它就只能在这些碎片里做“看起来合理”的猜测。我见过最典型的翻车现场是对话历史里用户早先误说过一句话“我已经提交申请”Agent在第三轮把这个误述当成事实直接推进了后续流程结果整条链路被一句话带偏。这不是模型笨是上下文里埋了一个脏数据。所以上下文工程的第一课是输入端不可控输出端就不可能可控。想让Agent稳定必须先在上下文层面把混乱源头掐掉。模型负责的是“基于材料的推理”负责整理、筛选、组织材料的是你自己。你现在做的Prompt优化本质上都是在给这一句话做铺垫。1.2 高确定性具体指什么我判断一个Agent是不是高确定性只看两个指标。第一个叫一致性指的是同样的输入在多次运行里输出结果不会出现本质上的偏差。一致性差通常表现为温度参数明明调低到0模型还会在措辞、流程选择甚至JSON字段结构上来回横跳。第二个叫可预期性指的是给定输入后你能预先判断它会做什么、会在哪一步失败、失败时以什么形式暴露。别小看这两个词很多Agent不是不出错是出错出得毫无章法让你没法修。要做到这两个指标光靠压低temperature是远远不够的。temperature管的是采样的“脑洞程度”管不了语义层面的结构性摇摆。模型今天心情好给你按步骤输出明天因为上下文长度变了、检索知识插进来的位置变了它可能就换了表达顺序。真正管用的是你在上下文层面建立的强约束指令结构是否稳定、输入材料是否有序、输出格式是否有强制校验、错误发生后是否有重试兜底。这四个位置做到位Agent的行为才谈得上可预期。1.3 上下文工程和Prompt工程到底差在哪不少人有疑问一直在优化的提示词跟上下文工程有什么不同我的理解是Prompt工程聚焦于“把一句指令写得更清楚”解决的是措辞、逻辑、范例层面的问题。上下文工程则是一整套管线思维——你要操心模型能看到什么、先看到什么、看不到什么这些信息的生命周期又是怎么被管理和验证的。换句话说Prompt工程是上下文工程里的一小块。完整上下文工程至少包括五类要素任务指令、输入数据、外部检索知识、历史对话或状态、工具调用结果。这五类要素如何清洗、如何拼接、如何分配权重、如何轮转淘汰才是上下文工程真正要处理的命题。你在做Agent架构设计的时候脑子里应该是“上下文管线”而不是“提示词”。2. 支柱一输入上下文的结构化设计2.1 系统指令不要写成散文不少开发者在写系统提示词时习惯用大段自然语言描述“你是一个友好的客服助手你的任务是为用户提供帮助请注意语气不要泄露内部信息……”这段话模型能读懂但它把权重放在了一个非常模糊的描述上真正关键的操作约束反而不够突出。我推荐的做法是把系统指令当成一份“API文档”来写而不是散文。固定分成几个区块角色定义、任务目标、边界约束、输入数据说明、输出格式要求。区块之间用清晰的分隔符标记每一块内部用短句和列表不要长篇抒情。这样做有两个直接好处第一模型对“边界约束”这类重点区域的注意力更集中第二你可以对每个区块单独做版本迭代而不是每次改个措辞就要整体重写。举个简化例子我用XML标签做分区system role你是某客服系统的流程执行器。/role task严格按TASK中给出的流程处理用户请求。/task rules - 不得编造未在context中出现的订单信息 - 用户要求退款时必须先校验订单状态字段 - 每一步处理结果必须写入output。/rules input_spec context来自订单数据库的结构化信息/context task来自上层编排器的指令/task /input_spec /system这样写完之后你会发现模型的“发挥空间”明显变小因为它清楚自己是在执行带规格说明的任务而不是在聊天。2.2 用固定信息块封装上下文系统指令只是上下文工程的第一步。在实际请求里真正让模型迷惑的往往是动态变化的部分用户刚说了什么、系统查到了什么、历史对话里哪些是有效信息。我的习惯是给动态信息也做固定封装而不是简单地把对话记录一股脑堆进去。比如我会在上下文里规定几个信息块按固定顺序排列第一条是“当前任务指令”第二条是“实时数据快照”第三条是“相关历史决策”第四条是“工具结果备注”。顺序一旦定下来就不要频繁调换。模型对首尾信息的注意力通常强于中间部分所以我会把最高优先级的指令放在最前面把最希望它遵守的输出契约放在最后面。中间部分塞检索知识和历史状态但要控制数量和长度。这种固定块封装还有一个附加价值便于日志排查。每次请求里拼了什么数据、哪一段是旧的、哪一段是新注入的在日志里一目了然。没有这套封装的时候出了问题你只能对着几千字上下文发呆有了封装你能一眼看出是哪一块数据把模型带偏了。2.3 输出格式用“契约”而不是“建议”如果你告诉模型“请输出JSON格式”它大概率某天会给你在JSON外面包一层Markdown代码块或者多塞一个你根本没定义的字段。想让输出真正可控不能靠“建议”要靠“契约”在输出格式里明确声明必需的字段、字段类型、允许的枚举值并在输出之后用代码做强制校验。我通常采用的做法是在系统指令末尾给出一个JSON Schema并且在提示里写清楚“输出必须符合以下schema不得输出多余字段所有枚举值只能从给定列表中选择。”同时给一个极简的少样本示例。示例不要给太长的模型不需要看一个完整对话来学会输出格式它只需要看见一个“正确形态”。需要提醒的是少样本示例里选什么内容很关键。示例本身会作为上下文的一部分参与推理如果你给了一个与当前任务关系不大的示例模型可能照着那个场景的语境跑偏。我会让示例与线上真实请求保持同一种“风格”并且每次只给一到两个避免示例喧宾夺主。3. 支柱二检索增强——给Agent装一个靠谱的外部记忆3.1 没有检索模型只能靠“背”回答问题不接任何检索的Agent本质上是在凭训练时学到的“印象”作答。训练数据里的知识有截止日期、有覆盖盲区更重要的是没有来源可用。用户问一个稍微偏门的问题模型为了不让对话断掉就会开始编。检索增强解决的不只是“接上知识库”的问题它在为Agent提供“推理依据”。当你把检索到的材料明确放进上下文并告诉模型“只能依据下列材料做出判断”模型的幻觉空间就大大收缩。你可以理解为模型从“自由联想”切换成了“基于材料作答”。这也是确定性提升的关键一步——自由联想是不可控的基于材料作答是可预期、可审计的。在Agent语境里检索增强可以服务四类信息产品知识或FAQ、用户的历史订单数据、企业内部流程文档、前序会话中的决策记录。每一类信息的检索策略可能不同但原则一致只把“当前这一步需要的高价值材料”提出来不要一股脑倾泻。3.2 检索质量三件套切分、召回、过滤很多团队以为检索增强就是“把文档灌到一个向量库里然后问一句就召回top-k”。这么做出来的Agent经常答非所问根子在检索质量。我做检索增强的经验重点看三块。第一块是索引准备。入库前要做清洗去掉页眉页脚、处理乱码、统一编码。切分时不要迷信固定长度我更倾向于按语义单元切比如一个章节、一个完整段落再配合200到300个字符的长度限制切片之间保留一到两句重叠避免把关键信息拦腰截断。第二块是召回策略。只用向量召回会漏掉精确定位的关键词只用关键词召回又容易舍本逐末。我建议做“向量召回关键词召回”的混合然后在候选集上用交叉编码模型重排把最相关的几条排在前面。如果资源有限至少也要把两路召回的结果合并去重再按相关性排序。第三块是过滤规矩。元数据过滤非常关键比如知识库里的文档有部门权限、有发布时间召回时就要把这些条件带进去。还要注意时效性过期的政策文档相关性再高也不能进上下文。这一条容易忽略但也是上下文污染的常见来源。3.3 检索结果进入上下文的正确姿势检索结果不是直接拼接进上下文就行。同样的内容换一种呈现方式模型的使用效果差别很大。我会规定一套统一的插入格式knowledge source_iddoc_0032/source_id content此处是原始材料原文/content source_iddoc_0107/source_id content此处是另一段材料原文/content /knowledge instruction 请优先使用上述材料作答。回答中需要引用材料编号。 如果材料中没有相关内容请明确回复“无法确认”不要自行推断。 /instruction把这段插在任务指令之后、对话历史之前模型会把它当成最直接的“解题材料”。上面这条指令很重要它等于给模型划了一条硬边界能用材料的就用材料不能用的就承认不知道。这一句话防住了绝大多数编造行为。另外检索结果不是越多越好。我一般控制在3到5条最多不超过8条。条数一旦变多模型会把注意力分散到大量无关信息上甚至从低相关度的材料里“硬找”依据反而降低确定性。宁可少而准不要多而杂。3.4 检索失败也要有兜底话术检索增强了不代表每次都能命中。总有用户问的是知识库里没有的东西。真正稳的Agent在这种时候有一套固定的拒绝逻辑而不是让模型现场发挥。我的做法是如果召回结果的相关性分数低于阈值或者命中了“无结果”占位符就把一段固定话术放进上下文让模型照着输出。这段话术可以设计成“根据目前系统记录暂时没有找到该问题的准确信息请稍后重试或联系人工处理。”这听起来很简单但在工程上意义重大它把“模型不知道”的状态转成了“产品标准反馈”用户体验是稳定的日志也是可以筛选分析的。你宁可让用户觉得“系统没查到”也不要让用户觉得“系统在胡扯”。4. 支柱三记忆管理——让Agent记住该记住的4.1 上下文窗口有限记忆必须做预算模型上下文窗口再大也不等于你可以无限堆内容。上下文越长模型对关键信息的注意力就越容易被稀释成本也随之上涨。我给Agent做记忆管理第一步就是给上下文划分预算。一个常见预算分配比例大致是这样任务指令和输出契约占20%实时检索知识占30%结构化状态和历史摘要占30%剩下20%留给模型推理空间和工具描述。当然比例可以调关键是你要有意识地在拼装上下文的时候控制每个模块的长度而不是等上下文爆掉再处理。有了预算你就得学会“舍弃”。某段历史对话虽然完整但它对当前任务没有直接帮助硬塞进去只会添乱。我通常会设置一个长度上限超过上限就触发压缩流程而不是无脑追加。4.2 用结构化状态替代对话流水账这是我在记忆管理里收获最大的一条不要整段保留原始对话历史而是把对话“消化”成一个结构化状态卡。一个支持多轮交互的Agent真正需要记住的往往不是一个字一个字的历史而是几个关键变量用户当前意图、已经确认的信息、当前所处流程步骤、待补充字段、可用操作范围。比如用户说“我要退货订单号不知道但我手机号是138****”系统真正需要沉淀的状态是意图退货申请待校验字段订单号已提供字段手机号下一步操作根据手机号查订单。原始对话里那些“嗯”“对”“你看下”之类的语气词对后续决策没有价值反而会干扰模型。我把这类状态做成一个JSON块在每轮对话结束后更新然后放到下一轮上下文的固定位置。模型每次看到的是“当前事实”不是“一堆人说过的废话”。这个改动带来的提升比我预想的大得多模型决策一致性明显增强因为它的推理基础不再依赖模糊的上下文回忆。4.3 长期记忆要分层不要一股脑压扁如果一个Agent要跨会话工作比如一个周期性陪伴型助手或者企业内部的长期助手那就需要长期记忆。我的经验是长期记忆分三层管理。第一层是事实记忆记录用户或系统的稳定偏好比如“用户偏好简洁回复”“用户所在区域是华东”“客户等级为VIP”。这类信息结构化存储每次会话直接注入上下文。第二层是会话摘要每一次会话结束后用模型生成一个浓缩摘要包括主题、结论、未完成事项。第三层是向量记忆把重要的历史片段向量化只在需要时按语义召回。写记忆的时机很重要。不是每轮对话都值得写入记忆否则很快塞满噪声。我会制定一套写入规则用确定性逻辑触发比如“当用户明确表示偏好时”“当流程走到关键节点时”“当出现订单号这类强标识信息时”。让规则控制写入而不是让模型自行判断可以避免记忆库被无关内容污染。4.4 记忆需要淘汰机制不然就变成脏数据长期记忆还有一个很容易忽略的问题会过期、会冲突。如果用户上个月还是高级会员这个月降级了老记忆还留在上下文里模型就会根据过期的身份做出错误判断。所以记忆必须配套淘汰和修订机制。我常用的策略有三个。第一时间衰减给每条记忆打上时间戳超过一定期限未复核就自动降权或移出主记忆区。第二冲突替代当新消息与旧记忆矛盾时更新旧记忆并保留一条修订记录而不是同时把两条信息都塞给模型。第三主动确认对关键变量在对话中设计确认节点比如“您仍然是VIP会员吗”用户确认后再更新记忆。这一条很容易被忽略但记忆污染是长期Agent最隐蔽的坑。上下文工程不只是管“当下放什么”还要管“不该放什么”。淘汰机制做得好上下文才不会有慢性中毒。5. 支柱四输出校验与兜底恢复——把最后一道门守住5.1 校验不只是查格式要分三层来做很多开发者的校验只做了一步解析JSON成功了就往下走。但Agent生产环境的校验至少应该分三层。第一层是结构校验负责检查输出是否是合法JSON、字段齐全与否、类型是否正确、枚举值是否在允许范围内。第二层是语义校验负责用规则或轻量模型判断输出内容本身是否合理。比如退款金额不能是负数、日期范围不能颠倒、当用户要求退款时输出步骤里必须包含“校验订单状态”这一环。第三层是业务校验负责核对这次操作在流程中是否被允许。比如用户尚未登录却触发了查看订单操作这就要拦下来。三层校验的代码可以写得很简单但思维上必须分开。我在项目里会为每一层分别定义错误码这样日志里就能定位是结构坏了、语义歪了还是业务逻辑被绕过。三层校验做完Agent的失败就从“莫名其妙”变成了“可归因”。5.2 校验失败时把错误信息回填给模型再重试遇到输出不合法常见的错误做法是原封不动地把同一个prompt再发给同一模型。这基本上是在赌概率赌第二次采样能蒙对。稍微好一点的做法是在第二次请求里带上“上一次的失败输出”和“失败原因”明确告诉模型哪里错了、应该如何修正。我试过的一种有效提示结构是这样的correction 你的上一次输出未能通过校验。 错误原因缺少“order_id”字段且“refund_status”不在允许枚举值内。 请根据上述错误修正输出严格遵循原始output_spec要求。 /correction把这段拼接到原有上下文之后再做一次推理。模型能看到具体错在哪修正成功率会明显提升。重试次数我一般限制在两到三次超过次数就不再重试避免陷入死循环直接进入兜底逻辑。兜底逻辑也很重要我通常预先编写一组标准回退回复比如“系统暂时无法处理该请求请稍后重试”。用户看到的是一条明确的产品态信息而不是一次崩溃或空白响应。这个兜底看着不起眼却决定了Agent在线上是否“体面地失败”。5.3 可观测性日志即底气高确定性Agent的基础不是模型多聪明而是你能知道每一轮到底发生了什么。我从一开始就给每次请求打详细日志至少包含拼装后的完整上下文、模型原始输出、每一层校验的结果、重试次数、最终回退原因。这套日志最大的价值在于回放和消融。出问题之后我把当时的上下文原样回放给同一个模型往往能轻松复现然后我再逐块摘除记忆、减少检索条数、调整指令位置观察输出变化。这个“上下文消融”的过程就是定位问题根因的利器。没有日志你连这一步都做不了。另外一个容易被忽略的好处是版本管理。每改一次上下文结构都要把旧版本存下来配上一组固定的评估样例。改完之后跑一遍评估集看通过率是涨还是跌。没有这套机制你改上下文全靠感觉今天觉得好了、明天又坏了像在蒙着眼睛调音。6. 一个完整Agent请求的落地骨架6.1 请求生命周期分几步走前面四根支柱讲了原理这一段把它串成一个可执行流程。我做的模拟项目X是一个多轮流程类Agent它的单次请求大致走七步。第一步读取状态从存储中取出当前会话的结构化状态卡。第二步检索增强根据当前意图召回相关知识。第三步拼装上下文按固定顺序拼接任务指令、状态卡、检索材料、历史摘要、输出契约。第四步调用模型让模型基于这份上下文生成回复或操作指令。第五步三层校验结构、语义、业务一次过。第六步失败重试把校验错误回填给模型做修正超过次数就进入兜底。第七步状态更新把本轮产出的关键变量写回状态卡并决定是否需要写入长期记忆。你会发现这几步里有的属于支柱一的输入结构有的属于支柱二的检索有的属于支柱三的记忆有的属于支柱四的校验。真正稳定的Agent不是靠哪一个单独环节特别强而是整条链路环环相扣。6.2 简化实现思路下面是这个流程的简化Python伪代码目的是展示上下文管线该怎么组织别照抄框架重点是理解顺序和封装def handle_request(user_input, session): # 1. 读取结构化状态 state session.state_card # 2. 检索增强根据意图召回知识 docs retrieve(user_input, top_k4) # 3. 拼装上下文固定顺序 context build_context( taskstate.current_task, statestate.to_json(), knowledgeformat_docs(docs), history_summarysession.summary ) # 4. 调用模型 raw_output call_model(context) # 5. 三层校验 error validate(raw_output) if error: raw_output retry_with_error(context, error, max_retry2) # 6. 回退兜底 if raw_output is None: return FALLBACK_REPLY, state # 7. 更新状态与记忆 state.update(raw_output) write_memory_if_needed(state) return raw_output, state这个骨架里真正难的不是调用模型那一步而是build_context、validate、retry_with_error这几个函数。把上下文拼装和校验反馈做好比花时间调模型参数管用得多。6.3 从零开始四根支柱的落地顺序如果你现在有一个Agent正在线上跑但表现不稳定先别急着四根支柱一起上。我建议按成本从低到高推进。第一优先做输出校验和日志——成本最低却能让你立刻拥有“失败可见”的能力。第二优先做输入结构化把系统指令和上下文的拼接顺序规范化。第三优先做记忆管理用结构化状态卡替代原始对话历史。最后再做检索增强因为它的建设和评估成本最高需要在前面三项稳了之后才有意义。这个顺序背后的逻辑很简单先保证失败是可观测、可控制的再逐步压缩模型的自由发挥空间。很多人一上来就做RAG结果检索知识还没用对Agent输出本身又是一团乱麻问题叠加之后根本无从排查。7. 常见问题速查与避坑心得7.1 高频问题对照表我整理了在Agent上线过程中最常见的五类问题基本都是上下文环节引起的。典型现象根本原因推荐解法模型把历史里的错误当成事实原始对话历史未经验证就进入上下文用结构化状态卡替代原始历史回答与知识库明显矛盾指令没限定材料优先级明确“只能依据检索材料作答并标注来源编号”重试了两次还是错第二次请求没有带上错误信息把“上次输出错误原因”回填给模型上下文越来越长、费用失控没有token预算和压缩机制设置长度上限做滚动摘要和状态卡temperature已经为0输出还是不稳输出约束不够“硬”强制JSON Schema、枚举校验再加重试机制这个表基本覆盖了大多数团队的初始痛点。你会发现除了最后一个问题涉及模型配置其余全部是上下文工程层面的事情。你不需要换一个更强的模型才能解决大部分问题你只需要把上下文管线修好。7.2 排查问题的三个实用方法第一个方法是日志回放。出现异常时把当时的完整上下文原样重构出来发给同一个模型复现。能复现就说明问题出在上下文本身不能复现再去检查状态被哪一步改坏了。第二个方法是单因子消融。不要一次性改三四个变量。比如怀疑记忆污染就先把记忆区块摘掉用同一批测试问题跑一遍怀疑检索噪声就把检索结果从5条减到2条再跑。每改一个变量记录一次输出质量变化几次之后你就能定位到元凶。第三个方法是建一个固定评估集。不要用肉眼一两个案例判断上下文改得好不好至少准备30到50条典型请求覆盖正常流程、异常输入、边界情况。每次改动后跑一遍记录通过率和失败原因。长期积累这套评估集就是你调优Agent的底气。我个人的体会是上下文工程做得好不好看一个指标就行Agent在你没有预料到的场景里是否仍然能“体面地处理”。输出校验、日志回放、记忆淘汰这些机制看起来都不是核心模型能力的提升但它们恰恰是把Agent从“演示品”推向“生产可用”的关键一步。最后分享一个小技巧每次对上下文做改动都存一份旧版本配上同一条测试用例的运行结果。你很快会发现很多所谓的“优化”其实是在倒退而你真正需要的改动往往只是把某一块信息的优先级调高一点或者把某些历史从上下文里拿走。所谓高确定性不过是把模型眼前的世界整理得足够清楚而已。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询