ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent上下文工程实战:从设计到落地的完整指南

AI Agent上下文工程实战:从设计到落地的完整指南 1. 为什么上下文工程成了 AI Agent 的分水岭做 AI Agent 这一年多我最大的感受是模型能力已经不是瓶颈了真正拉开差距的是上下文工程。同一个 DeepSeek API、同一个智谱 API两个人写出来的 Agent 效果能差出三倍问题几乎都出在上下文怎么组织上。先把概念说清楚。上下文工程Context Engineering指的是在调用大语言模型时如何系统性地设计、筛选、压缩、排序和组织送进模型窗口的全部信息。它包含系统提示词、历史对话、工具定义、检索结果、记忆片段、当前任务状态等等。跟早年说的提示词工程相比上下文工程管的不是一个 prompt 怎么写而是整个信息流怎么进模型。为什么它现在这么重要因为 Agent 和普通聊天机器人有本质区别。聊天机器人一轮对话就结束了上下文很短。而 Agent 要循环执行任务调工具、看结果、再决策、再调工具。每一轮都要把之前的全部轨迹塞回模型。跑十几轮之后上下文轻松突破几万 token。这时候你会发现几个致命问题模型开始忘事、开始重复调用同一个工具、开始忽略早期的关键指令、API 费用飙升、甚至直接报maximum context length is 1048576 tokens这种错。我踩过最典型的一个坑做一个自动处理表格的 Agent前 5 轮跑得好好的到第 8 轮突然开始胡言乱语把已经处理过的数据又处理了一遍。排查半天才发现是工具返回的原始 JSON 太长把早期的任务指令挤出了有效注意力范围。后来我把工具返回值做了摘要压缩问题立刻消失。这就是上下文工程的威力——不改模型、不改 prompt 措辞只改信息组织方式效果天差地别。这篇文章适合谁看如果你正在用 API 搭建 AI Agent不管是接 DeepSeek、智谱还是本地部署的大语言模型只要你遇到过Agent 跑着跑着就傻了、token 烧得太快、多轮之后指令失效这类问题那这篇就是写给你的。我会从设计思路、核心细节、实操落地到问题排查把上下文工程这套东西完整拆一遍尽量给到能直接抄作业的方案。2. 上下文工程的整体设计思路拆解2.1 上下文窗口不是仓库是工作台很多人对上下文窗口有个误解觉得它是个越大越好的仓库恨不得把所有信息都塞进去。我早期也这么想直到被现实教育。上下文窗口更像一张工作台——桌面再大你同时盯着的东西也就那么几样堆太多反而找不到要用的工具。大语言模型的注意力机制决定了中间位置的信息最容易被忽略。业界管这叫lost in the middle现象。你把最关键的任务指令放在一长串工具返回结果的中间模型大概率会漏掉。所以上下文工程的第一原则不是塞多少而是放哪里、留什么。基于这个认知我给自己定了一条铁律任何进入上下文的内容都必须回答它对当前这一步决策有用吗。没用的一律不进。这条规则听起来简单执行起来能砍掉一半以上的 token。2.2 分层组织把上下文切成四个区实操中我习惯把上下文分成四个逻辑区从稳定到易变依次排列静态区系统提示词、角色设定、全局规则。这部分几乎不变放在最前面也最容易被缓存。工具区当前可用的工具定义和参数 schema。只在工具集变化时更新。记忆区长期记忆摘要、用户偏好、历史任务结论。定期压缩更新。动态区当前对话、最近几轮的工具调用轨迹、实时检索结果。变化最频繁放在最后。这么分层的好处是静态区可以命中 API 的 prompt caching省钱又提速。DeepSeek、智谱这些平台的 API 都支持前缀缓存你把不变的内容放前面重复调用时这部分几乎不花钱。我实测过一个 Agent分层之后 token 成本降了大概 40%响应也快了一截。2.3 为什么不用全量历史这种偷懒方案新手最容易犯的错就是把所有历史消息原封不动地 append 进去。代码写起来最省事但代价巨大。假设每轮工具调用产生 2000 token跑 20 轮就是 4 万 token还没算系统提示词。这时候模型不仅慢、贵而且决策质量断崖式下跌。正确的做法是滚动窗口 摘要压缩。保留最近 N 轮完整轨迹N 一般取 3 到 5更早的内容压缩成一段结构化摘要。摘要不是随便写要保留任务目标、已完成的关键步骤、当前状态、待办事项。我通常让模型自己生成这个摘要格式固定成 JSON方便程序解析和回填。注意摘要压缩本身也要花一次模型调用别每轮都做。我的经验是每积累 5 到 8 轮压缩一次或者当 token 数超过阈值比如窗口的 60%时触发。3. 核心细节解析与实操要点3.1 系统提示词把规则写成检查清单而不是散文系统提示词是上下文的定海神针但很多人把它写成一大段抒情散文模型读完抓不住重点。我的做法是把它结构化用清晰的标题和列表## 角色 你是一个数据处理 Agent负责清洗和转换表格数据。 ## 硬性规则 1. 每次只调用一个工具等结果返回再决策 2. 禁止编造数据缺失字段标记为 null 3. 处理超过 100 行时先分批 ## 输出格式 每轮回复必须是 JSON{thought: ..., action: ..., args: {...}}这样写的好处是模型对规则的遵循度明显提升。我做过对比测试同样的规则散文式写法遵循率大概 70%清单式能到 90% 以上。原因很简单清单式结构和大语言模型训练时见过的指令数据分布更接近。3.2 工具返回值的瘦身处理这是上下文工程里最容易被忽视、但收益最大的环节。工具返回的原始数据往往又臭又长——一个 API 返回的 JSON 可能几百行但 Agent 真正需要的就几个字段。我的处理流程是这样的字段白名单只保留 Agent 决策必需的字段其余丢弃截断长文本超过 500 字符的文本字段截断加省略标记错误归一化把各种 API 错误统一成简短格式比如{error: timeout, retryable: true}举个真实例子。之前接一个股票历史明细 API返回的 JSON 每条记录有 20 多个字段一次返回 100 条。全塞进去一轮就 8000 token。我做了字段白名单只留日期、开盘、收盘、成交量四个字段直接降到 800 token。Agent 的决策质量反而更好了因为噪音少了。3.3 记忆的写入与召回策略Agent 要有记忆但不能什么都记。我一般分两类事实记忆用户偏好、固定配置、领域知识。这类写入向量库按需召回。任务记忆当前任务的进度、中间结论。这类直接放上下文任务结束就丢弃。召回的时候有个坑别一次性召回太多。向量检索 top-k 设成 3 到 5 就够了召回 20 条只会稀释注意力。而且召回的内容要加时间戳和来源标记让模型知道这条信息的可信度和新鲜度。3.4 参数计算怎么估算你的上下文预算给个实用的估算方法。假设你的模型窗口是 128K token别想着用满留出安全边际区域建议占比128K 对应 token静态区系统提示词5%6400工具区10%12800记忆区15%19200动态区对话轨迹50%64000预留缓冲20%25600预留 20% 缓冲是为了应对突发的长工具返回和模型输出。我见过太多人把窗口算得死死的结果某次工具返回超长直接报maximum context length错误整个 Agent 崩掉。提示不同平台的 token 计算方式略有差异中文一般按 1 字约 1.5 到 2 token 估算英文按 1 词约 1.3 token。上线前一定要用真实数据压测一遍。4. 实操过程与核心环节实现4.1 搭建一个带上下文管理的 Agent 骨架下面这套骨架我用在好几个项目里基于 Python接任意兼容 OpenAI 格式的 APIDeepSeek、智谱、本地部署的都能用。核心是一个ContextManager类负责上下文的组装和压缩。class ContextManager: def __init__(self, system_prompt, max_tokens100000): self.system_prompt system_prompt self.max_tokens max_tokens self.tools [] self.memory_summary self.recent_turns [] # 最近几轮完整轨迹 def build(self): 组装最终送给模型的 messages messages [{role: system, content: self.system_prompt}] if self.memory_summary: messages.append({ role: system, content: f## 历史任务摘要\n{self.memory_summary} }) messages.extend(self.recent_turns) return messages def add_turn(self, turn): self.recent_turns.append(turn) if self._estimate_tokens() self.max_tokens * 0.6: self._compress() def _compress(self): 把较早的轮次压缩成摘要 old_turns self.recent_turns[:-3] # 保留最近3轮 self.memory_summary summarize(old_turns, self.memory_summary) self.recent_turns self.recent_turns[-3:]关键点在_compress方法保留最近 3 轮完整轨迹更早的压缩进memory_summary。这样既保证了近期上下文的完整性又控制了总量。4.2 摘要压缩的提示词怎么写压缩质量直接决定 Agent 会不会失忆。我用的摘要提示词是这样的SUMMARIZE_PROMPT 你是一个上下文压缩器。请把以下 Agent 执行轨迹压缩成结构化摘要。 要求 1. 保留任务目标一句话 2. 列出已完成的关键步骤最多5条 3. 记录当前状态和待办事项 4. 保留所有出现过的关键数据ID、文件名、数值 5. 丢弃冗余的工具返回细节 输出格式严格 JSON { goal: ..., done: [..., ...], state: ..., todo: [...], key_data: {...: ...} } 已有摘要 {old_summary} 新增轨迹 {new_turns} 注意最后要求输出严格 JSON。这样程序可以直接解析回填到上下文时格式统一模型读起来也清晰。我试过让模型输出自然语言摘要结果每次格式都不一样回填后反而增加了解析负担。4.3 工具调用的上下文回填Agent 每调用一次工具结果都要回填到上下文。这里有个细节工具结果要用toolrole 还是userrole取决于你用的 API。OpenAI 格式支持toolroleDeepSeek 也支持。但有些本地部署的模型对toolrole 支持不好这时候退化成userrole 加明确标记# 兼容性写法 {role: user, content: f[工具返回] {tool_name}\n{result}}回填时一定要带上工具名否则模型分不清这是哪个工具的结果。我见过有人只回填结果不带工具名模型直接懵了开始瞎猜。4.4 一个完整的执行循环示例把上面这些拼起来一个完整的 Agent 循环大概长这样def run_agent(task, max_steps20): ctx ContextManager(SYSTEM_PROMPT) ctx.recent_turns.append({role: user, content: task}) for step in range(max_steps): messages ctx.build() response call_llm(messages, toolsctx.tools) action parse_action(response) if action[type] finish: return action[result] # 执行工具 result execute_tool(action[name], action[args]) # 瘦身处理 result slim_result(result) ctx.add_turn({role: assistant, content: response}) ctx.add_turn({role: tool, content: result}) return 达到最大步数限制这个循环里slim_result就是前面说的工具返回值瘦身ctx.add_turn里内置了压缩触发。整套跑下来一个 20 步的任务上下文能稳定控制在 3 万 token 以内成本可控。5. 常见问题与排查技巧实录5.1 典型问题速查表现象可能原因排查方向解决手段Agent 重复调用同一工具工具结果未正确回填或格式混乱打印完整 messages 检查统一回填格式加工具名标记多轮后忽略早期指令指令被挤出注意力范围检查指令在上下文中的位置把关键指令放系统提示词或每轮重申报 maximum context length 错误上下文超窗口统计各区域 token 占比触发压缩瘦身工具返回决策质量突然下降上下文被噪音污染检查是否有超长工具返回字段白名单 截断token 成本异常高静态区未命中缓存检查内容顺序是否稳定不变内容前置启用 prompt caching摘要后 Agent 失忆摘要丢失关键数据检查摘要 JSON 的 key_data强化摘要提示词强制保留 ID 类数据5.2 排查上下文问题的三板斧遇到 Agent 行为异常我一般按这三步排查第一步打印完整 messages。别只看最后一轮把送给模型的完整上下文 dump 出来从头到尾读一遍。十有八九问题就藏在你没注意的某个工具返回里。第二步统计 token 分布。写个小脚本按区域统计 token 数。如果动态区占了 80% 以上那肯定是压缩没做好。第三步做消融实验。把可疑的那段上下文删掉重跑一次。如果问题消失就锁定元凶了。这招特别管用比盯着代码看半天强。5.3 几个我踩过的坑坑一以为 token 数等于字符数。中文一个字符可能占 2 个 token英文一个词可能占 1.3 个。我早期按字符数估算结果实际 token 是估算的两倍直接爆窗口。后来老老实实用 API 返回的 usage 字段统计。坑二压缩太频繁。一开始我每轮都压缩结果摘要调用本身花了不少钱而且频繁压缩导致信息丢失严重。后来改成阈值触发成本和质量都平衡了。坑三忽略工具定义的 token 开销。工具 schema 本身也占 token工具多了很可观。我有个项目定义了 15 个工具光工具定义就 6000 token。后来做了工具动态加载只把当前任务相关的工具塞进去省了一大截。坑四摘要里丢了关键 ID。有次 Agent 处理文件摘要时把文件名丢了后面几轮它开始瞎编文件名。教训是所有标识符类数据ID、文件名、URL、数值必须在摘要里原样保留不能概括。提示上下文工程没有一劳永逸的配置。任务类型变了、工具集变了、模型换了上下文策略都得跟着调。我建议把上下文相关的参数都做成可配置项方便快速迭代。6. 上下文工程的进阶玩法与个人体会6.1 动态工具加载按需给工具别一次全给前面提过工具定义占 token进阶做法是动态加载。根据当前任务阶段只把相关工具塞进上下文。比如任务分检索和处理两个阶段检索阶段只给搜索类工具处理阶段只给计算和写入类工具。实现上可以给每个工具打标签Agent 进入新阶段时切换工具集。这样不仅省 token还能减少模型选错工具的概率。我实测过一个 12 工具的项目动态加载后工具区 token 从 5000 降到 1500选错工具的次数也少了一半。6.2 上下文缓存把省钱做到极致DeepSeek、智谱这些平台的 API 都支持前缀缓存。原理是如果两次请求的前缀完全一致这部分就命中缓存价格大幅降低。所以上下文工程要配合缓存策略静态区内容一字不改放最前面工具定义顺序固定别每次随机排记忆摘要放在静态区之后、动态区之前我有个高频调用的 Agent优化缓存后单次成本从 0.03 元降到 0.008 元降了七成多。对于调用量大的场景这个优化比什么都值。6.3 多 Agent 场景下的上下文隔离如果你在做多 Agent 协作比如一个规划 Agent 加一个执行 Agent上下文一定要隔离。别让执行 Agent 看到规划 Agent 的全部思考过程只传结论。否则上下文会爆炸式增长而且执行 Agent 容易被规划 Agent 的中间犹豫带偏。我的做法是Agent 之间只传结构化消息格式固定成{task: ..., context: ..., constraints: [...]}。每个 Agent 维护自己的上下文互不干扰。6.4 我个人的几条经验做上下文工程这段时间最深的体会是它更像是一门信息取舍的手艺而不是纯技术活。你得不断问自己这条信息对模型当前决策到底有没有用。大部分时候答案都是没用。还有一点别迷信大窗口。我见过有人专门挑 1M 窗口的模型觉得窗口大就不用管上下文了。结果呢token 成本高得离谱而且模型在超长上下文里的表现反而不如精心组织的短上下文。窗口大是给你缓冲的不是让你偷懒的。最后分享一个我常用的小技巧给上下文加锚点。在系统提示词里放一句固定的、每轮都出现的关键指令比如记住任何写入操作前必须先读取。这句话因为反复出现模型对它的注意力权重会更高能有效防止多轮后指令失效。这个技巧成本几乎为零但效果立竿见影。上下文工程这块内容后续还能往深了挖比如结合向量检索做长期记忆、用图结构组织任务状态、针对视觉大语言模型做多模态上下文管理等等。但万变不离其宗——搞清楚什么信息该进模型、以什么形式进、放在什么位置这三件事做好了你的 Agent 就已经超过市面上大多数了。
返回列表