1. 为什么只卷Prompt已经不够用了过去一年多我接触过不少做Agent项目的团队也自己从零搭过几个能跑通业务流程的智能体。一个很明显的感受是大家一开始都把精力砸在Prompt上反复调措辞、加few-shot示例、试各种角色设定但调到最后往往发现单靠Prompt能带来的提升是有天花板的。模型还是那个模型工具还是那些工具可为什么有的Agent跑起来像老手有的却像个刚入职的实习生动不动就答非所问、工具调错、上下文丢失差距不在Prompt本身而在Context Engineering也就是上下文工程。这个词最近被提得越来越多但它并不是什么新概念。简单说Prompt Engineering关注的是“怎么问”而Context Engineering关注的是“模型在回答这一刻眼前到底看到了什么”。这包括系统指令、历史对话、检索到的文档、工具返回结果、当前任务状态、甚至时间戳和用户画像。Prompt只是上下文里的一个切片而上下文才是模型做决策的全部依据。我见过一个很典型的对比两个团队做同一个客服AgentA团队把System Prompt打磨到极致B团队Prompt写得中规中矩但花了大量精力设计上下文组装逻辑——什么时候注入订单信息、什么时候压缩历史、工具返回结果怎么结构化、失败重试时保留哪些字段。结果B团队的Agent在真实工单里的解决率高出A团队近三成。这不是Prompt的胜利这是上下文工程的胜利。这篇文章适合谁看如果你正在做Agent开发或者准备从LLM应用转向更复杂的智能体系统又或者你已经被“Prompt调不动了”这个问题卡住那接下来的内容应该能帮你把视角从“怎么写提示词”拉到“怎么设计模型眼前的整个世界”。我会从整体设计思路、核心细节、实操落地、常见坑四个层面展开尽量把每个选择背后的逻辑讲清楚让你能直接抄作业也能自己判断什么时候该改。2. 上下文工程的整体设计与思路拆解2.1 从Prompt到Context视角的转变Prompt Engineering的默认假设是模型能力固定输入质量决定输出质量。所以大家拼命优化那一小段文本。但Agent场景下模型不是一次性回答问题而是要多轮决策、调用工具、维护状态。这时候模型每一轮看到的输入都是动态变化的Prompt只是其中一部分。Context Engineering的核心思路是把模型当成一个需要持续供料的决策引擎你要设计的是“供料系统”而不是“单次提问”。这个系统要决定哪些信息该进、哪些该出、以什么格式进、什么时候进、进多少。听起来像数据管道确实就是。我习惯把上下文分成四层指令层System Prompt、角色设定、输出格式要求、安全边界。任务层当前目标、子任务拆解、进度状态、待办列表。知识层检索到的文档、数据库查询结果、工具返回内容、历史对话摘要。环境层时间、用户ID、会话ID、外部系统状态、错误码。Prompt Engineering只覆盖了指令层的一部分而Context Engineering要管四层。这就是为什么只卷Prompt会遇到瓶颈——你只调了四分之一。2.2 为什么ReAct模式放大了上下文的重要性ReActReasoning Acting是现在Agent最常用的范式之一模型先推理下一步该做什么然后选择工具执行观察结果再继续推理。这个循环里每一轮的“观察结果”都会进入下一轮的上下文。问题来了如果工具返回一大段JSON你直接塞进上下文模型可能被无关字段干扰如果历史对话太长模型可能忘记最初的目标如果检索文档太多模型可能抓不住重点。ReAct本身不解决这些问题它只是把问题暴露得更明显。我实测下来ReAct Agent的失败案例里超过一半不是模型推理能力不够而是上下文里塞了错误的信息、过期的信息、或者格式混乱的信息。比如工具返回了一个嵌套五层的JSON模型在下一轮推理时把某个无关字段当成了关键参数直接跑偏。这时候你回去改Prompt写“请忽略无关字段”效果有限因为模型根本分不清哪个是无关字段。正确的做法是在上下文组装阶段就把工具结果扁平化、只保留必要字段、加上明确的字段说明。2.3 上下文工程的目标让模型每一轮都“看得清、记得住、找得到”我总结下来Context Engineering要达成三个目标看得清当前轮次的关键信息必须突出不能被噪声淹没。比如当前任务目标、上一步工具返回的核心结果、待决策的选项。记得住跨轮次的重要状态不能丢。比如用户之前说过的偏好、已经确认过的参数、失败过的尝试。找得到需要外部知识时能快速检索并注入且注入的内容要相关、简洁、可信。这三个目标对应三种技术手段上下文压缩、状态管理、检索增强。后面会逐一展开。2.4 方案选型什么时候该上上下文工程不是所有项目都需要复杂的上下文工程。如果你的Agent只是单轮问答或者任务流程非常固定那Prompt调好就够了。但如果你遇到以下信号就该考虑上下文工程了Prompt越写越长但效果提升越来越小。Agent在多轮对话后开始“失忆”或“跑偏”。工具调用错误率高且错误原因和上下文噪声相关。同一个Prompt在不同用户、不同会话下表现差异大。你需要Agent处理长文档、多步骤任务、或动态环境。我个人的判断标准是当Prompt的边际收益低于上下文组装的边际收益时就该转方向了。这个拐点通常在Agent需要处理超过3轮工具调用、或者上下文长度超过模型窗口的30%时出现。3. 核心细节解析与实操要点3.1 上下文窗口不是越大越好注意力稀释问题很多人觉得模型上下文窗口越大越好128K、200K甚至1M塞进去就完事了。但实际用下来长上下文里的信息利用率并不高。模型在长文本里找关键信息的能力远不如在短文本里。这就是所谓的“注意力稀释”。我做过一个测试同一个任务把关键信息放在上下文开头、中间、结尾模型的表现差异明显。放在开头和结尾时准确率最高放在中间时明显下降。所以上下文工程的第一条实操原则是关键信息放两头次要信息放中间或者干脆不放。具体怎么做System Prompt和当前任务目标放最前面最近一轮工具返回结果和待决策选项放最后面中间的历史对话和检索文档做压缩。如果模型支持还可以用特殊的标记把关键段落框起来比如用XML标签或分隔符。注意不同模型对位置的敏感度不同建议在你的目标模型上做一次位置敏感性测试再决定信息排布策略。3.2 历史对话压缩摘要、滑窗、还是向量检索多轮对话最大的问题是历史越来越长。三种常见处理方式滑窗只保留最近N轮。简单但会丢失早期关键信息。摘要定期把历史对话压缩成摘要。保留语义但可能丢细节。向量检索把历史对话存起来每轮根据当前问题检索相关片段。灵活但增加延迟和复杂度。我一般用混合策略最近3-5轮保留原文更早的对话做摘要同时把关键实体用户ID、订单号、已确认参数单独抽出来放在状态区。这样既保证近期细节不丢又保证早期关键信息可追溯。摘要的生成也有讲究。不要让模型自由发挥而是给它一个固定模板用户目标、已确认信息、已尝试操作、待解决问题。这样摘要出来结构一致模型下一轮读取时更容易定位。3.3 工具返回结果的结构化与裁剪工具返回结果是上下文噪声的主要来源。一个数据库查询可能返回几十个字段但Agent真正需要的可能只有三四个。直接塞进去模型容易被带偏。我的做法是在工具层和上下文层之间加一个适配器。适配器负责解析工具返回的原始数据。根据当前任务目标筛选出必要字段。把筛选后的结果转成简洁的文本或表格。加上字段说明和单位。如果结果为空或出错生成明确的错误描述而不是原始堆栈。举个例子查询订单接口返回了20个字段适配器只保留订单号、状态、金额、预计送达时间转成一行文本“订单A123状态已发货金额299元预计明天送达。”模型下一轮推理时一眼就能抓住重点。提示适配器的筛选逻辑最好配置化不同任务用不同字段集避免硬编码。3.4 状态管理让Agent记住该记的Agent的状态包括当前任务目标、子任务进度、已确认参数、失败记录、用户偏好。这些状态不能全靠模型从历史里回忆而要显式维护。我通常用一个轻量级的状态对象每轮更新然后序列化后注入上下文。状态对象的结构要稳定字段名要语义清晰。比如{ task: 退换货申请, stage: 确认退货地址, confirmed: { order_id: A123, reason: 尺寸不符 }, failed_attempts: [查询物流失败单号无效], user_preference: 希望上门取件 }这个状态对象放在上下文靠前的位置模型每轮都能看到当前进度和已确认信息避免重复询问或逻辑跳跃。3.5 检索增强的注入策略相关性、时效性、可信度RAG是上下文工程的重要部分但检索到文档不等于用好文档。注入时要注意相关性只注入和当前子任务相关的片段不要整篇文档。时效性优先注入最新版本过期文档要标注或过滤。可信度不同来源的文档可信度不同注入时带上来源和置信度让模型自己权衡。我习惯在检索结果前加一行元信息“来源内部知识库更新时间2024-05置信度高”。模型看到这些推理时会更有依据。3.6 上下文格式结构化优于自然语言同样的信息用结构化格式JSON、YAML、表格比用自然语言描述更省token也更不容易被模型误解。比如自然语言“用户之前说过他想要红色尺码是L预算在300以内。”结构化用户偏好 - 颜色红色 - 尺码L - 预算≤300元后者模型解析起来更准。但也不是所有信息都适合结构化指令类和推理类内容还是自然语言更合适。我的原则是事实性信息结构化指令性信息自然语言化。4. 实操过程与核心环节实现4.1 搭建上下文组装管道从输入到模型调用的完整流程一个完整的上下文组装管道通常包含以下步骤接收用户输入原始query。加载会话状态从存储中读取当前会话的状态对象。检索相关知识根据query和状态从向量库或数据库检索。调用工具如需要执行工具获取原始结果。适配工具结果筛选、结构化、裁剪。压缩历史对话滑窗摘要。组装上下文按优先级排列各层信息。调用模型传入组装好的上下文。解析模型输出提取动作或回复。更新状态根据模型输出更新状态对象写回存储。这个管道可以用任何语言实现我用Python比较多因为生态全。核心是每一步都要有明确的输入输出格式方便调试和替换。4.2 关键参数计算上下文预算分配模型上下文窗口是有限资源要像管理内存一样管理它。我通常按以下比例分配预算以8K窗口为例层级预算占比说明指令层15%System Prompt、输出格式任务层10%当前目标、状态对象知识层50%检索文档、工具结果、历史摘要环境层5%时间、用户ID等缓冲20%留给模型输出和意外情况这个比例不是固定的任务越复杂知识层占比越高指令越复杂指令层占比越高。关键是要有预算意识不能任由某一层无限膨胀。4.3 实操现场一个退换货Agent的上下文组装示例假设用户说“我上周买的那个红色衣服想退掉尺码不对。”第一步加载状态{ task: 退换货, stage: 识别订单, confirmed: {}, failed_attempts: [] }第二步检索知识从订单库检索该用户最近订单找到红色衣服订单A123。第三步适配工具结果订单A123红色连衣裙尺码M购买日期2024-06-01状态已签收可退换。第四步压缩历史无历史跳过。第五步组装上下文[指令] 你是退换货助手请根据用户请求和订单信息确认退货原因并引导下一步。 [状态] 任务退换货阶段识别订单已确认无。 [知识] 订单A123红色连衣裙尺码M购买日期2024-06-01状态已签收可退换。 [用户] 我上周买的那个红色衣服想退掉尺码不对。第六步模型输出“找到您的订单A123红色连衣裙尺码M。请问您是想退货还是换货如果是尺码问题换货可能更快。”第七步更新状态stage改为“确认退换类型”confirmed加入order_id。这个流程跑下来模型不需要猜订单号不需要回忆颜色所有关键信息都在上下文里且格式清晰。4.4 上下文压缩的代码实现要点压缩历史对话时我一般用以下策略def compress_history(history, max_turns5): if len(history) max_turns: return history recent history[-max_turns:] older history[:-max_turns] summary summarize(older) # 调用模型生成结构化摘要 return [{role: system, content: f历史摘要{summary}}] recent摘要的Prompt要固定模板比如“请提取以下对话中的用户目标、已确认信息、已尝试操作、待解决问题。用JSON输出。”这样摘要结果稳定下一轮注入时模型容易解析。4.5 工具结果适配器的实现适配器我通常写成配置驱动FIELD_MAP { order_query: [order_id, status, amount, eta], logistics_query: [tracking_no, latest_event, updated_at] } def adapt_tool_result(tool_name, raw_result): fields FIELD_MAP.get(tool_name, []) filtered {k: raw_result.get(k) for k in fields if k in raw_result} return format_as_text(filtered)这样新增工具时只需加配置不用改代码。5. 常见问题与排查技巧实录5.1 模型忽略上下文中的关键信息现象明明把订单号放在上下文里了模型还是问“请问您的订单号是多少”排查检查关键信息的位置是否被淹没在长文本中间。检查格式是否和周围文本混在一起没有区分。检查是否有冲突信息比如历史摘要里写了旧订单号。解决把关键信息用标记框起来比如order_idA123/order_id或者放在上下文最后一行。实测下来放在最后一行效果最好。5.2 工具调用参数错误现象模型调用查询接口时传了错误的参数名或格式。排查检查工具描述是否清晰参数说明是否完整。检查上下文里是否有干扰字段比如工具返回结果里有个同名字段但含义不同。检查历史对话里是否有过成功的调用示例模型可能模仿了旧格式。解决在工具描述里加参数示例在上下文里把工具返回结果的字段名和工具入参字段名做区分比如返回结果字段加result_前缀。5.3 多轮后Agent“失忆”现象聊了十几轮后Agent忘记了最初的目标。排查检查历史压缩是否把关键信息压掉了。检查状态对象是否每轮都注入。检查摘要模板是否覆盖了目标信息。解决状态对象每轮必须注入且放在靠前位置。摘要模板里强制包含“用户原始目标”字段。5.4 上下文过长导致响应变慢现象上下文越长模型响应越慢成本越高。排查统计每层信息的token占比找出膨胀层。检查是否有重复信息比如历史摘要和状态对象都包含了订单号。解决去重同一信息只出现一次。知识层做更激进的裁剪只保留最相关的3-5个片段。5.5 常见问题速查表问题可能原因快速解决模型忽略关键信息位置不佳、格式不突出放最后一行加标记工具参数错误描述不清、字段混淆加示例字段加前缀多轮失忆状态未注入、摘要丢目标状态每轮注入摘要含目标响应慢上下文膨胀、重复信息去重裁剪知识层输出格式不稳定指令层不明确加输出格式示例用JSON schema5.6 独家避坑技巧不要相信模型的“记忆”任何需要跨轮次保留的信息都要显式写入状态对象不要指望模型从历史里自己找。工具结果先适配再注入原始工具结果永远不要直接塞进上下文哪怕它看起来很短。上下文组装逻辑要可测试每个步骤都写成纯函数输入输出明确方便单元测试。做A/B测试同一任务用不同上下文策略跑一批case看解决率和token消耗用数据决定策略。监控上下文长度分布线上跑的时候记录每轮上下文的token数发现异常膨胀及时告警。6. 上下文工程的进阶方向6.1 动态上下文根据任务阶段调整策略不同任务阶段需要不同的上下文。比如任务初期需要更多检索知识任务后期需要更多状态信息。可以设计阶段感知的组装策略每个阶段用不同的预算分配和注入规则。6.2 上下文评估怎么衡量上下文质量上下文质量可以从几个维度衡量信息覆盖率关键信息是否都在、噪声比无关信息占比、位置合理性关键信息是否在高效位置、格式一致性。可以人工标注一批case定期评估。6.3 多Agent协作中的上下文隔离与共享多个Agent协作时上下文既要隔离又要共享。隔离的是各自的任务状态和工具结果共享的是全局目标和已确认信息。可以用一个共享状态层加各自私有上下文的方式实现。6.4 上下文安全防止注入与泄露上下文里可能包含用户输入、工具返回、检索文档这些都可能被恶意注入。要在组装前做清洗过滤掉可疑指令。同时敏感信息如密钥、个人数据不要注入上下文或者在注入前脱敏。我在实际项目里踩过最深的坑就是早期太信任工具返回结果直接把原始JSON塞进上下文结果模型把某个调试字段当成了业务参数导致连续几轮调用失败。后来加了适配器层问题才解决。这个适配器看起来增加了工作量但它把“工具层”和“上下文层”解耦了后面换工具、改字段、加裁剪规则都只动适配器不动主流程。如果你现在正在搭Agent建议一开始就把这层留出来哪怕先写个最简单的字段筛选后面会省很多事。