AI Agent上下文工程实战:从消息组装到多Agent协作完整套路

发布时间:2026/10/4 4:55:09
AI Agent上下文工程实战:从消息组装到多Agent协作完整套路 做 AI Agent 这段时间我最大的一个感受是模型本身的能力差距远没有上下文管理带来的效果差距大。同样一个模型基座有人做出来的 Agent 像是雇了个高级实习生交代一遍就能把事办得妥妥帖帖有人做出来的 Agent 就是个记性只有几秒的复读机用户刚说过的需求转头就忘多问两轮开始胡编乱造。差别基本不在模型而在上下文工程Context Engineering上。这篇文章想跟你聊聊我在这条路上摸出来的完整套路从 Agent 的上下文由什么组成到怎么组装一份能打的消息序列再到上下文窗口有限时怎么裁剪、怎么摘要以及多 Agent 协作时上下文怎么传、成本和并发怎么控、踩过的坑怎么止血。不管你是用 FastAPILangChainLangGraph 自己搭还是用扣子这类可视化平台拖拽还是好奇 Spring AI、Rust 生态里 Agent 怎么做底层逻辑都是同一套。把这套东西吃透你就拿到了 Agent 开发里最绕不开的那把钥匙。1. 先弄明白Agent 的上下文里到底装着什么1.1 一个让我印象深刻的翻车现场先说个我真实遇到的例子。当时做一个客服 Agent用户上来问我想买一张下周二去杭州的高铁票预算 500 以内。 Agent 回答得很正常查了车次、报了价格。接着用户又问那南京的呢 这本来是个很简单的追问——用户大概率想比较杭州和南京的交通成本。结果 Agent 直接回答南京的票也很充足还推荐了一趟完全不搭界的车次因为它已经完全忘了前面聊过去杭州这件事。这不是模型能力不行这就是最典型的上下文丢失。模型的每次调用都是独立的它不会天然记住上一轮你说了什么。你传给它的消息列表里如果没保留用户想去杭州这层信息它就只能在当前这句话里猜。另一个我印象很深的场景是做小红书自动发消息的 Agent如果上下文里没有记录哪个选题已经用过、哪篇文案已经发过、上次生成的 verbatim 风格是什么它就会连续生成几乎一字不差的重复文案用户被迫一遍遍删。这类问题的共同点是什么不是提示词写得不够好而是上下文这张小抄本身就残缺。做 Agent 的第一课是接受一个事实模型就是个没有瞬时记忆的考生你每次给它递的小抄长什么样直接决定了它能考多少分。1.2 一次对话里上下文其实由四类信息组成很多人对上下文的印象就是把历史聊天记录拼在一起发给模型。真做项目之后你会发现一段发给模型的上下文通常由下面几类东西拼成而且它们各自的作用和失效后果完全不同。上下文组成大致占比作用失效后果系统提示词2% ~ 8%定义角色、目标、约束、可用能力、输出格式回答风格漂移、工具乱用、输出格式不稳定对话历史40% ~ 60%记录多轮双方发言维持话题连续性丢需求、重复提问、逻辑断裂工具定义与返回值10% ~ 30%告诉模型有什么函数可调、参数怎么填、调用结果是什么不会调工具、拿错误数据继续硬答检索/知识片段5% ~ 25%注入外部知识库命中的片段、实时数据一本正经胡说八道、内容过时、编造来源这里有个容易忽略的点工具定义和返回值本质上也是上下文的一部分。你如果不把 function calling 的 schema 传进去模型再强也不可能凭空知道你要查天气。你不把工具返回的关键字段整理好再塞回去模型就会拿着又长又乱的原始 JSON 开始废物利用——经常有 Agent 从报错信息里总结出一堆子虚乌有的结论根源就在这。1.3 为什么上下文工程不等于调 Prompt现在说起 Prompt 工程的人很多但上下文工程的范围比 Prompt 工程大得多。你可以把每次模型调用想象成一个刚从昏迷中醒来的助手你递给他一张任务简报。Prompt 只是简报顶部那几行字后面真正的主体是对话记录、检索到的资料、工具返回的结果。Prompt 工程解决的是怎么说清楚要求上下文工程解决的是每一轮给他带什么东西、按什么顺序排、哪些内容必须是原文、哪些可以压缩成摘要、哪些必须果断丢弃。我见过不少团队Prompt 写了几百行效果还是不理想因为上下文组装顺序是乱的最新检索结果排到了最前面被后来居上的历史消息盖住或者把整个知识库一股脑塞进去上下文差点撑爆模型反而抓不住重点。这些问题靠优化 Prompt 解决不了得靠调整上下文本身。上下文工程本质上是给 Agent 设计一套信息投喂协议它决定的是 Agent 的感知边界。2. 从零组装一份能打的上下文2.1 系统提示词别写作文写接口规范很多人写系统提示词喜欢写成一篇文章你是一个友好亲切的客服助手要耐心回答用户问题语气热情但不能乱说话…… 这种写法不是不行但它太模糊了模型很容易在执行几轮之后漂移。我自己的写法是把它当成一份接口规范结构化地拆成几个固定的块角色、目标、约束、可用知识、输出格式、安全底线。给你一个可以参考的模板结构# Role 你是XX产品的官方客服助手代号小X。 # Goal 准确解答用户关于产品功能、订单、售后的咨询对于无法确认的信息明确表示需要人工介入。 # Constraints - 只基于系统提供的资料回答禁止自行推测 - 如果用户问到资料之外的信息回复这个我需要跟相关同事确认不得编造 - 每轮回答控制在 80 字以内 # Output Format - 回复用纯文本不使用 Markdown 标题 - 如果需要用户补充信息先列缺失项 # Guardrails - 不讨论与产品无关的话题 - 不承诺补偿方案为什么这么写因为模型对结构化文本的理解和遵守程度明显好过一大段自由散漫的描述而且固定位置的固定文本能在后面作为缓存前缀使用对成本和延迟都有好处。你还可以把当前系统的实际情况——比如客服坐席时间、商品在架状态——写进去让模型时刻知道自己处在什么环境里。2.2 工具定义让模型知道什么情况下该掏出什么工具工具定义是上下文里最容易被忽略却最影响体验的一块。模型能不能准确触发工具很大程度上取决于工具描述写得清不清楚。这里有个最常见的问题description 写得太短模型全靠猜。比如你写query_weather(city)描述是查天气模型就可能在用户说今天冷不冷时犹豫要不要调用或者把城市名参数填错。更稳的做法是把触发条件、参数格式、返回内容都写清楚{ name: query_weather, description: 当用户询问当前或未来几天的天气、气温、降水情况时调用。输入城市中文名。返回今日温度、天气状况、风力、降水概率。, parameters: { type: object, properties: { city: { type: string, description: 城市中文名如杭州 } }, required: [city] } }工具返回结果的处理同样重要。我的经验是写回上下文之前必须先做清洗。比如原始返回是一大段带嵌套结构的 JSON模型读起来既费 token 又容易被无关字段干扰。我会在代码里把返回结果抽成一句话杭州 今日 晴 5~15℃ 东北风3级 降水概率20%再塞回消息列表。这样既保留了事实又避免模型在复杂 JSON 里迷路。2.3 检索与知识RAG 不是把库搬进去很多人在 RAG 里犯的错是命中了几条片段不管三七二十一全塞进上下文总觉得多一点总比少一点好。实际上检索片段的质量和数量直接影响 Agent 的表现。标准其实很简单——你放进去的这段知识如果拿掉它模型的回答会变吗如果会变说明这是必要信息如果不会变就直接删掉。我通常会限制最终注入的检索片段数量。top_k 取多少要看知识库切片的粒度切片短就多取几条切片长两三条可能就够。还要注意顺序检索片段最好插在系统提示词后面、对话历史前面作为背景资料存在。另外每条检索内容最好带来源标记比如[知识库: 商品手册/第3章]这能显著降低模型把新旧信息混在一起编造的概率。2.4 记忆分类哪些必须原文保留哪些可以摘要对话历史不是越多越好也不是越新越好。我会把记忆分成硬记忆和软记忆两类分别处理。硬记忆用户明确给出的关键约束比如订单号、金额、时间、地址、价格上限。这类信息错一个字就全盘皆输必须原文保留。软记忆闲聊、模型自己推理的过程、已经完成的中间步骤、失败的尝试这些信息留着只会膨胀上下文能摘要就摘要能弃就弃。举个例子用户说帮我查一下订单 SB20240001 什么时候发货如果延迟就换顺丰。这里的订单号、诉求换顺丰就是硬记忆要原封不动带进每一轮至于中间查了几次、API 返回了什么中间状态等最终拿到结果后就可以压缩成一行订单 SB20240001 已查当前物流为圆通用户要求延迟则换顺丰。这样做的好处是即使后面对话框滚了 50 轮模型依然记得最核心的东西而不是只记得最近几句客套话。3. 上下文窗口是稀缺资源怎么分配才不浪费3.1 先做 Token 预算再写代码上下文窗口看着很大128K、200K 都有但真的用起来你会发现它远不够用尤其当对话轮数一长、工具结果一多的时候。我在项目里的习惯是开写代码之前先给不同部分划定 Token 预算。以 128K 窗口为例我一般这么分配上下文部分预算占比参考值系统提示词2% ~ 5%2K ~ 5K工具定义3% ~ 8%4K ~ 8K检索/知识片段10% ~ 20%10K ~ 25K对话历史50% ~ 70%50K ~ 80K输出预留5% ~ 10%5K ~ 10K这不是硬性公式但它逼你思考每个部分占多大比重有没有必要塞那么多历史我在代码里会直接用 tokenizer 统计长度一旦接近阈值就让处理函数提前做裁剪而不是等模型接口报 context_length_exceeded 错误再慌。这个习惯帮我少踩了很多坑。3.2 滑动窗口裁剪的副作用只留最近 N 轮关键信息丢了最粗暴的上下文管理方式就是滑动窗口只保留最近 5 轮、10 轮消息更早的一刀切。这个方法实现很简单但它有个致命副作用你刚刚把用户最开始说的关键需求也一起裁掉了。比如用户在第 1 轮说预算 500 以内到第 15 轮你只保留了最近 5 轮模型就完全不记得预算这回事了推荐了一堆 800 的选项。我现在的做法是裁剪之前先把硬记忆抽取出来存到 session 级变量里。就像写简历一样把最关键的事实提炼成几个字段用户诉求、约束条件、待办事项然后再做滑动窗口只裁那些无关紧要的过程对话。顺序大概是第一提取硬记忆并单独缓存第二把已经完成的任务压缩成一行摘要第三再做最近 N 轮窗口裁剪第四校验总长度是否在预算内。这样既保住关键信息又控制住了体积。3.3 摘要记忆把旧对话压成结构化笔记对话历史超过一定长度后直接裁剪不是唯一出路更好的做法是触发摘要让模型把旧对话重写成一页压缩笔记用笔记代替原文继续喂给后面的轮次。摘要不是简单概括而要提取结构化的关键信息。我常用的摘要触发条件有两个一是消息长度总和超过窗口预算的 60%二是关键事件完成后比如工具调用已经有最终结果了之前中间过程就可以压掉。摘要 prompt 我一般这么写请把下面的对话压缩成结构化笔记只保留 1. 用户的明确意图和约束条件 2. 尚未完成的待办事项 3. 已经发生的关键事实订单号、金额、地点、时间 4. 用户表达过的偏好 不要保留寒暄、重复、中间失败尝试。这段摘要生成之后替换掉旧消息的位置模型依然能知道用户要什么、做到哪一步而上下文体积却小了一个量级。在一些长会话 Agent 里我会配合 LangGraph 的节点机制做一个专门的 summarize 节点在每次对话轮次之间检查长度、决定是否触发摘要。这样上下文管理就变成了流程的一部分而不是事后的补救。3.4 分层记忆短期、长期、向量记忆再往上走一步就是分层记忆。单次会话内的消息可以理解为短期记忆跨会话仍然需要保留的用户画像、偏好、历史行为则属于长期记忆还有一种方式是向量记忆把历史消息切块向量化之后通过语义检索召回相关片段。这三层怎么配合我一般的读取顺序是先用向量检索找出当前 query 最相关的历史片段比如用户上次说过我喜欢靠窗座位这次订票时就应该把这条偏好放回上下文再把短期记忆里的最近几轮消息补上最后把长期记忆里跟当前任务相关的稳定信息等级、常用地址、折扣资格也一起注入。扣子这类可视化 Agent 平台里提供的记忆模块本质上就是帮你封装了这套逻辑自己搭 Agent 时就需要一个一个实现了。4. 多 Agent 协作里的上下文传递比单 Agent 难一个量级4.1 多 Agent 为什么容易断片单 Agent 的上下文就是一张纸就算丢信息至少整张纸在一个人手里。多 Agent 则完全不一样它更像多人协作每个角色只拿到其他人递给他的字条。问题就出在传话的过程中。举个例子一个内容创作 Agent 加一个校对 Agent。用户对创作 Agent 说语气要轻松别太正式。 创作 Agent 确实按轻松风格写了一篇。但等到校对 Agent 接手时主流程只把文稿传了过去没有把语气轻松这条约束传过去。校对 Agent 看到的文本没有风格标注于是按自己的默认偏好改得文绉绉。这种情况特别常见——不是某个 Agent 能力不行而是上下文在节点之间传递时缺了关键约束。4.2 LangGraph 的 State 是共享便签本但不是所有东西都要往上贴在用 LangGraph 这类框架时很多人直接把所有信息塞进一个全局 State让所有节点共享。这个思路在简单 demo 里没问题一旦 Agent 数量变多节点之间开始互相污染。LangGraph 的 State 本质上是一个共享便签本它应该只放那些真正需要跨节点流转的信息而不是把所有脏数据都堆上去。我会把 State 设计成字段分明的结构from typing import Annotated, TypedDict from langgraph.graph.message import add_messages class AgentState(TypedDict): session_id: str messages: Annotated[list, add_messages] # 主对话流 customer_info: dict # 用户相关硬记忆 task: str | None # 当前子任务描述 source_agent: str | None # 当前消息来自哪个 Agent注意这里我故意没有放整个知识库或全部检索结果只放必要字段。send给子 Agent 的时候我只从 State 里挑出task和customer_info而不是把messages整个传过去。子 Agent 不需要知道全局发生了什么它只需要知道自己当前的任务和完成这项任务必需的事实。4.3 主管-子 Agent 协作的一个落地示例下面是一个缩略版的主管-子 Agent 路由逻辑重点看它是怎么控制每个子 Agent 看到的内容范围def route_to_child(state: AgentState): last_user_message state[messages][-1][content] if 订单 in last_user_message or 退款 in last_user_message: child_prompt ( 你是订单处理专员。用户需要处理订单相关事务请基于以下信息提供服务。\n f客户关键信息{state[customer_info]}\n f用户本次诉求{last_user_message}\n 注意只处理订单/退款相关事宜不要回答其他问题。 ) return { task: child_prompt, source_agent: order_agent, } elif 商品 in last_user_message: # 另一个子 Agent只拿到商品相关背景 ...这段逻辑的精髓是每个子 Agent 收到的上下文是当前任务 必要硬记忆 最近一条用户消息而不是全部历史。子 Agent 的上下文越克制它就越不会发散。很多时候多 Agent 效果不好不是模型不行是子 Agent 收到的上下文太丰富了才开始发挥想象力。4.4 FastAPI LangGraph 部署上下文怎么和并发共存热搜里经常有人问ai agent 怎么扛并发。我在 FastAPI 里部署 LangGraph Agent 时最大的领悟是并发瓶颈往往不在模型 API 本身而在上下文的状态管理。单机单会话没问题一接多用户你就要回答这几个问题每个 session 的 messages 存在哪A 用户的消息会不会混进 B 用户进程重启后会话还能不能恢复我的方案是所有会话状态外部化存到 Redis 或者数据库按session_id建命名空间。每次请求从持久层把该 session 的上下文加载出来组装完传给模型返回后再写回去。LangGraph 里的 checkpointer 机制正好是为了这个场景设计的配合 FastAPI 作为请求入口一个请求进来先恢复 State跑完图再保存 State。这样 Agent 进程本身保持无状态水平扩展就容易了。如果做 Agent 中台我还会把上下文组装抽成独立服务输入 session_id 和当前用户消息输出组装好且已经做过裁剪/摘要的 messages 数组。这样不管下游是 LangGraph、Spring AI 还是 Rust 生态的 Agent都能共用这一层能力。语言会换但会话隔离、消息组装、记忆分级这套逻辑不会换。5. 每一分 Token 都要花在刀刃上成本、并发与可观测性5.1 算一笔账无效上下文到底烧掉多少钱上下文越长对成本的冲击远比想象中直接。假设你的 Agent 每次调用都会多塞 3000 个无效 token日调用量是 10 万次按输入侧 $0.003/千 token 算一天多烧 $9一个月就是 $270。这还是保守估的输入价格如果再算上更贵的主流模型、更长的上下文、更高的调用量一个月多烧几千块很轻松。更别说上下文越长每次推理的延迟也越高体验跟着一起下降。所以我会定期统计每次请求平均上下文长度和实际有效内容占比。怎么统计很简单在组装上下文的代码里打点记下每次 messages 的总 token 数以及其中检索片段、历史消息各自占多少。出现成本异常时先查上下文是不是在不知不觉间膨胀了。5.2 Prompt Caching把固定前缀变成低价缓存区现在不少模型服务支持 prompt caching原理是相同前缀的 prompt 会被缓存命中的前缀部分按更低价甚至免费计费也能显著降低首 token 延迟。想用好它上下文组装顺序就很讲究了。固定不变的文本要尽量放在最前面而且要保证前缀完全相同。所以系统提示词、工具定义、固定帮助文档应该排在最前面并且最好保持不变。动态内容——当前问题、最新消息、每次不同的检索结果——要排在后半段。千万别把动态内容插到系统提示词中间那样前缀会变缓存直接失效。另外一个细节就算你只是改了一个字只要在缓存前缀的覆盖范围内也会导致整段缓存失效。所以系统提示词一旦定稿就尽量别去动它任何调整都意味着缓存区需要重建。5.3 并发隔离上下文分区片是 Agent 稳定服务的前提前面提过并发基础做法这里再深入一点。并发场景里最容易出问题的不是模型 API 的 QPS而是状态串台。典型症状A 用户问订单Agent 回答里出现了 B 用户的名字或订单号。很多情况下就是全局变量 or 缓存 key 设计不当导致的。上下文在设计上必须严格遵守分区片原则每一个 session_id 就是一个独立命名空间messages、检索缓存、长期记忆全都按这个 key 隔离。子 Agent 之间传值只允许传拷贝不允许共享引用可变对象。我在代码里会用类似session_context(keysession_id)这样的上下文管理器保证无论函数调用多深拿到的都只是当前 session 的那一份数据。做并发压测时我一般会专门写一个大乱斗脚本几十个 session 同时对话每个 session 用一种独特标记跑完检查标记有没有串。5.4 可观测性看不到每次的上下文就无法排障如果只能给一条调试建议我会说把每次调用模型前组装好的 messages 完整记下来。很多 Agent 行为异常你光看模型输出根本猜不出原因但只要把当次上下文翻出来问题往往一眼就能定位——要么检索内容被排到了最后要么某条工具返回的报错信息原样混进了历史要么系统提示词被动态内容截断导致缓存失效。我会在项目里给每次调用打一条结构化日志session_id、调用时间、模型名、messages 数组、每个部分的 token 数、来源标记。调试时用 session_id 把整条链路拉出来基本能还原 Agent 当时“看到”了什么。有条件的话用 LangSmith 这类 trace 工具也行原理一样Agent 出了问题先看它这次到底看到了什么而不是猜模型。这条经验在我排过的故障里命中率超过八成。6. 我踩过的四个上下文坑以及对应的止血方案6.1 坑一工具报错信息被原样写回历史Agent 学坏了有段时间我的 Agent 经常出现诡异行为回答里偶尔夹着 JSON 片段、状态码、甚至堆栈信息。查了半天发现是工具调用失败后我把返回的异常内容直接塞回了消息列表。模型读到500 Internal Server Error这种信息会学着把它当重要事实反复引用甚至开始复述错误格式。止血方案很简单工具返回值写回上下文前先做一层转换。失败的时候不要贴原始报错转成一句面向模型的人话比如查询失败原因网络超时请提醒用户稍后重试。这既保留了必要信息又不会让模型被错误格式污染。6.2 坑二检索内容放的位置不对模型装着没看见另一个高频问题是RAG 命中的资料明明放进去了模型还是不用自顾自按历史消息回答。后来我把完整的上下文翻出来发现检索片段被排在了对话历史之后、最新用户消息之前。这时候模型已经被最新几条对话带偏了节奏检索内容夹在中间变成了背景噪音。我的固定排序现在是系统提示词 - 检索知识/工具说明 - 对话历史 - 最新用户消息。检索内容作为考试时的参考资料放在前面对话历史作为过程记录放在中间最新消息作为待办问题放在最末尾。这个顺序在我自己的项目里是最稳的你可以把它当默认起点再根据实际效果微调。6.3 坑三多个 Agent 共享同一个 State张冠李戴多 Agent 刚跑起来的时候我踩过一个很隐蔽的坑两个子 Agent 共用了同一个全局 memory 对象A Agent 在中间步骤里记下的临时结论被 B Agent 当成自己的入参读走结果 B 的回答里莫名出现了 A 的中间观点。后来我规定子 Agent 的函数签名只允许接收白名单字段也就是它完成任务真正需要的那几个值。跨节点传递全部通过显式字段完成禁止从全局 state 里顺路摸一把。如果你发现自己写代码时总想读全局变量停一下想想这个字段是不是真的应该给当前 Agent 看到。一旦答案犹豫就说明上下文边界划分得不够干净。6.4 坑四总在窗口爆掉之后才处理用户会话直接变废物最后一个坑也是我最开始常犯的不做提前量等模型报出上下文超限才想着处理。结果就是用户聊到一半Agent 突然失效前面所有对话记录因为超限被全部丢弃体验直接归零。现在我会在组装完成后主动算一次 token设置两级阈值超过预算的 60% 时触发摘要节点把旧对话压成笔记超过 80% 时强制裁剪并丢弃无关过程消息。这样窗口永远不会爆用户也不会感受到 Agent失忆。最后再分享一个小技巧每次踩完坑别急着改完就跑。把当时的坏案例存成一个固定的回归测试集比如用户第二次追问时不能忘记预算限制检索片段不能被历史消息淹没A 用户的订单不能串到 B 会话。下次改上下文策略先跑一遍回归集过了再上线。我在实际项目里就是这么做的它比对着线上案例反复瞎猜要高效得多。上下文工程说到底就是一件需要耐心打磨的事但回报也足够直接——你的 Agent 会从偶尔聪明变成稳定靠谱。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询