news 2026/10/10 13:12:16

Agent开发上下文管理实战:Token预算、压缩策略与工具返回值处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent开发上下文管理实战:Token预算、压缩策略与工具返回值处理

1. 上下文管理为什么成了Agent开发的分水岭

做Agent开发的人,迟早会撞上同一堵墙:模型本身够聪明,工具链也搭好了,但对话轮次一多,它就开始胡言乱语、忘记关键约束、重复调用同一个工具,甚至把早前明确否定的方案又捡回来执行。这不是模型退化了,而是上下文管理没做好。

上下文管理,说白了就是决定“每一轮请求里,到底该把哪些信息塞给模型,哪些该压缩、哪些该丢弃、哪些该外置存储”。它直接决定了Agent能不能在长任务里保持稳定。我见过太多项目,提示词写得漂亮,工具封装也干净,但一跑长流程就崩,最后定位下来全是上下文膨胀导致的注意力稀释。

这篇文章面向的是已经在用或准备用OpenCode、Codex、Claude Code这类Agent工具链的开发者,也适合正在自研Agent架构、想搞清楚上下文该怎么设计的人。我会从整体设计思路讲到具体实现细节,把参数计算、压缩策略、工具返回值的处理方式都拆开说,最后给一份常见问题速查表。全文基于我在多个Agent项目里踩过的坑整理,能直接抄作业。

2. 上下文管理的整体设计与核心思路拆解

2.1 上下文到底由哪几块拼成

很多人以为上下文就是“聊天记录”,这个理解太窄了。一个成熟Agent的上下文窗口里,通常同时塞着五类东西:

  • 系统提示词:角色定义、行为约束、输出格式要求,这部分基本固定,但往往最长。
  • 工具定义:每个可调用工具的schema描述,工具越多,这块占用越大。
  • 历史对话:用户输入和模型回复的累积,随轮次线性增长。
  • 工具调用结果:每次工具执行返回的内容,可能是几百字,也可能是几万字的文件内容。
  • 外部注入信息:检索到的文档、记忆库召回、当前环境状态等。

这五块加起来,很容易在十几轮之后就撑爆窗口。关键在于,这五块的“信息密度”差异极大。系统提示词和工具定义是高频复用的,历史对话里大量是寒暄和确认,工具结果里经常混着大段无关内容。上下文管理的核心任务,就是按信息密度重新分配这块有限的预算。

2.2 为什么不能简单粗暴地截断

最直觉的做法是“超了就删最早的”。我早期也这么干过,结果非常惨:Agent把最初设定的关键约束忘了,比如“不要修改生产环境配置”,然后照改不误。

截断的问题在于,它假设信息价值随时间线性衰减,但实际不是。系统提示词里的约束永远有效,用户第一轮说的核心目标可能贯穿全程,而中间某轮的工具返回可能只是一次性参考。所以正确的思路是分层管理,而不是一刀切。

我现在的做法是把上下文分成三层:

层级内容处理策略是否可压缩
固定层系统提示词、工具定义常驻,优化措辞谨慎压缩
活跃层最近N轮对话、当前任务状态完整保留不压缩
归档层早期对话、历史工具结果摘要或外置可压缩

固定层要尽量精简,工具描述能短则短;活跃层是模型当前推理的直接依据,必须完整;归档层才是压缩的主战场。

2.3 压缩策略的选型逻辑

压缩不是简单删字,常见的有四种手段,各有适用场景:

  • 摘要压缩:把多轮对话交给模型总结成一段。适合历史对话,但会丢失细节,且摘要本身也要花token。
  • 外置存储:把工具返回的大块内容存到文件或向量库,上下文里只留引用ID和简短描述。适合文件读取、网页抓取这类场景。
  • 滑动窗口:只保留最近K轮。实现简单,但会丢早期约束,必须配合固定层兜底。
  • 关键信息提取:从历史里抽取实体、决策、待办,结构化存储。适合任务型Agent。

实测下来,单一策略都不够用。我通常组合使用:固定层常驻,活跃层滑动窗口,归档层做摘要加外置。这样既控制了token,又保住了关键约束。

注意:摘要压缩有个隐蔽的坑——如果摘要模型和被压缩的对话是同一个模型,它可能把错误信息也“总结”进去,导致错误被固化。建议摘要时用更保守的提示词,明确要求“只保留事实和决策,不要推断”。

3. 核心细节解析与实操要点

3.1 Token预算怎么算才不翻车

先明确一个数:不同模型的上下文窗口不一样,但可用预算永远要留出余量。我的经验是,实际使用不超过窗口的70%,剩下30%留给模型输出和突发内容。

假设窗口是128K token,那输入侧控制在90K以内比较稳。这90K怎么分配?我给一个参考比例:

  • 系统提示词加工具定义:不超过15K
  • 活跃对话:30K到40K
  • 归档摘要:10K到15K
  • 工具结果引用:10K以内
  • 预留缓冲:10K

这个比例不是死的,工具特别多的项目,工具定义那块会涨,那就得从归档摘要里省。关键是每次请求前都要估算,而不是等报错了才处理。

估算token有个粗略办法:中文大约1个字1.5到2个token,英文大约1个词1.3个token,代码和JSON更密。更准的做法是用对应模型的分词器,但工程上没必要每轮都精确算,按字符数乘系数估个大概就够触发压缩逻辑了。

3.2 工具返回值是上下文膨胀的头号元凶

我统计过自己项目里的token消耗,工具返回值占了将近一半,而且大部分是浪费。比如读一个配置文件,返回几千行,模型真正需要的可能就其中十几行。

处理工具返回值,我总结了三道关:

  1. 工具侧裁剪:在工具实现里就限制返回量。比如读文件支持offset和limit参数,默认只返回前200行,需要更多让模型显式请求。
  2. 返回后过滤:拿到结果先做一次轻量处理,去掉空行、注释、重复内容,再决定是否入上下文。
  3. 外置加引用:超过阈值的内容直接落盘,上下文里只放“已保存到xxx,摘要如下”加一段简短摘要。

这里有个细节:外置存储的引用ID要稳定且可追溯,否则模型想回看时找不到。我一般用“文件路径加行号范围”作为引用,模型需要时可以用工具重新读取指定片段。

3.3 系统提示词的瘦身技巧

系统提示词是最容易被忽视的膨胀源。很多人写提示词像写文档,越写越长,最后光提示词就占了几万token。

瘦身有几个实用手法:

  • 合并同类约束:把“不要做A”“不要做B”“不要做C”合并成“禁止以下操作:A、B、C”。
  • 用示例代替描述:与其用三段话描述输出格式,不如给一个标准示例,模型模仿能力很强。
  • 工具描述精简:每个工具的description只写“什么时候用”和“关键参数”,详细用法放到工具报错信息里按需返回。
  • 动态加载:不是所有工具每轮都需要,可以按当前任务阶段动态挂载工具子集。

我做过对比,同样的功能,提示词从8000token压到3000token,Agent的表现反而更稳定,因为干扰信息少了。

3.4 多轮对话里的状态管理

长任务里,Agent需要记住“当前做到哪一步了”。这个状态如果全靠对话历史承载,很快就会乱。更好的做法是维护一个显式的任务状态对象,每轮更新,然后以结构化形式注入上下文。

比如一个代码修改任务,状态对象可以是:

{ "task": "重构用户模块", "current_step": "修改数据访问层", "completed": ["分析现有代码", "确定重构方案"], "pending": ["修改数据访问层", "更新单元测试", "回归验证"], "constraints": ["不改变对外接口", "保持向后兼容"] }

这个对象每轮都完整注入,比让模型从几十轮对话里自己回忆靠谱得多。而且它体积小,压缩时优先保留。

4. 实操过程与核心环节实现

4.1 搭一个最小可用的上下文管理器

下面用一个Python伪代码演示核心逻辑,思路可以直接迁移到任何Agent框架。

class ContextManager: def __init__(self, max_tokens=90000, reserve=10000): self.max_tokens = max_tokens self.reserve = reserve self.fixed_layer = [] # 系统提示词、工具定义 self.active_layer = [] # 最近对话 self.archive_layer = [] # 归档摘要 self.state = {} # 任务状态对象 def estimate_tokens(self, messages): # 粗略估算,中文按1.8系数 total = 0 for m in messages: total += len(str(m)) * 1.8 return int(total) def build(self, new_message): self.active_layer.append(new_message) # 先尝试直接组装 ctx = self.fixed_layer + self.archive_layer + self.active_layer if self.estimate_tokens(ctx) < self.max_tokens - self.reserve: return ctx # 超预算,触发压缩 self.compress() return self.fixed_layer + self.archive_layer + self.active_layer def compress(self): # 把活跃层最老的一半对话摘要进归档层 half = len(self.active_layer) // 2 old = self.active_layer[:half] summary = self.summarize(old) self.archive_layer.append(summary) self.active_layer = self.active_layer[half:] # 归档层也超了就再压 if self.estimate_tokens(self.archive_layer) > 15000: self.archive_layer = [self.summarize(self.archive_layer)]

这段代码的关键点在于:压缩是分级的,先压活跃层,再压归档层,固定层基本不动。summarize函数需要调用模型,提示词要明确要求保留决策、约束和待办。

4.2 摘要提示词怎么写才不丢信息

摘要质量直接决定压缩后Agent还能不能正常工作。我用的提示词模板大致是这样:

请将以下对话压缩为结构化摘要,严格保留: 1. 用户提出的所有明确要求和约束 2. 已经做出的决策及其理由 3. 当前任务进度和待办事项 4. 涉及的具体文件、函数、参数名 不要添加任何推断内容,不要省略任何约束条件。 对话内容: {content}

重点是“不要推断”和“不要省略约束”。我踩过的坑是,早期摘要提示词太宽松,模型把“用户可能想要X”这种猜测也写进去,结果后续Agent把猜测当成了确定需求。

4.3 工具结果外置的落地方式

以文件读取工具为例,我的实现逻辑是:

def read_file(path, offset=0, limit=200): with open(path) as f: lines = f.readlines() total = len(lines) chunk = lines[offset:offset+limit] content = "".join(chunk) if total > limit: # 内容较大,外置存储 ref_id = save_to_store(path, offset, limit, content) return { "ref": ref_id, "summary": f"文件{path}第{offset}到{offset+limit}行,共{total}行", "preview": content[:500] } return {"content": content}

这样模型拿到的是引用加预览,需要完整内容时再用另一个工具按ref读取。实测token消耗能降60%以上,而且模型对“有引用可查”这件事理解得很好,不会因为看不到全文就卡住。

4.4 参数选择与阈值设定

几个关键阈值我反复调过,给一组参考值:

参数建议值说明
窗口使用率上限70%超过就触发压缩
活跃层保留轮数8到12轮太少丢上下文,太多占预算
归档摘要上限15K token超了做二级摘要
工具结果外置阈值2000字符低于此值直接入上下文
单次工具返回上限5000字符工具侧硬限制

这些值不是绝对的,任务越复杂,活跃层可以适当多留;工具调用越频繁,外置阈值要调低。

提示:阈值一定要做成配置项,不同任务类型用不同配置。代码任务和客服任务对上下文的需求完全不同,一套参数打天下必然出问题。

5. 常见问题与排查技巧实录

5.1 典型问题速查表

现象可能原因排查方向解决手段
Agent忘记早期约束归档压缩丢了约束检查摘要是否保留约束摘要提示词强调保留约束,或约束放固定层
重复调用同一工具工具结果被压缩后模型以为没执行检查工具结果是否入上下文工具调用记录单独维护,不参与压缩
响应变慢、成本飙升上下文膨胀统计各层token占比定位膨胀源,外置或裁剪
模型输出格式错乱系统提示词被稀释检查提示词位置和长度提示词前置,精简工具定义
长任务中途跑偏任务状态丢失检查状态对象是否每轮注入显式维护状态对象,优先保留
摘要后信息矛盾摘要模型产生幻觉对比摘要与原文换更保守的摘要提示词,或人工校验关键摘要

5.2 几个容易忽视的坑

坑一:工具定义重复注入。有些框架每轮都把全部工具定义塞进去,工具一多就是灾难。正确做法是工具定义只在系统层出现一次,或者按需动态挂载。

坑二:错误信息无限累积。工具报错后,错误信息进入上下文,模型重试又报错,错误信息越堆越多。我的做法是同类错误只保留最近一条,历史错误折叠成“已尝试X次,均失败”。

坑三:摘要的摘要。归档层超限后做二级摘要,信息损失会叠加。我一般限制最多两级,再超就考虑把部分内容彻底外置,只留引用。

坑四:忽略输出预留。只算输入不算输出,结果模型刚要生成就被截断。预留至少10%给输出,长输出任务要留更多。

5.3 我的调试习惯

每次Agent行为异常,我第一件事是打印当前上下文的各层token占比,而不是急着改提示词。十次里有八次问题出在上下文结构上,而不是模型能力上。

具体做法是在每轮请求前打一条日志:

[CTX] fixed=8200 active=31000 archive=9000 tools_ref=4000 total=52200/90000

这条日志能快速看出是哪一层在膨胀。如果active涨得特别快,说明对话轮次太多或单轮内容太长;如果archive一直涨,说明摘要没压住。

另外我会定期做“上下文回放”:把某次失败任务的完整上下文导出,手动删减不同部分,看模型表现如何变化。这个方法很笨,但能精准定位到底哪块信息是关键的、哪块是冗余的。

6. 上下文管理的进阶思路

6.1 按任务阶段动态调整策略

一个长任务通常分几个阶段:理解需求、制定方案、执行、验证。每个阶段对上下文的需求不同。

理解阶段需要完整的需求描述和历史讨论;执行阶段需要当前步骤的详细信息和工具结果;验证阶段需要原始需求和执行结果的对比。与其用一套固定策略,不如按阶段切换配置。

我的做法是给每个阶段定义一套上下文配置,阶段切换时重新组装上下文。这样既省token,又让模型每轮看到的都是当前最相关的信息。

6.2 记忆库与上下文的配合

上下文是短期记忆,记忆库是长期记忆。两者配合的关键是召回时机和召回量。

召回太频繁,上下文被无关记忆污染;召回太少,Agent又显得“没记性”。我的经验是:只在任务开始和关键决策点召回,每次召回不超过3条,且必须带相关性评分,低于阈值的直接丢弃。

召回内容也要压缩,不能把整篇文档塞进去。通常召回的是“结论加引用”,需要细节时再让模型主动查询。

6.3 多Agent场景下的上下文隔离

多个Agent协作时,上下文不能共享,否则互相干扰。每个Agent维护自己的上下文,Agent之间通过结构化消息通信,消息里只传必要信息,不传完整上下文。

我见过一个反例:两个Agent共享同一个对话历史,结果A的工具调用结果被B当成了自己的,行为完全乱套。正确做法是每个Agent有独立的上下文管理器,通信走明确的消息协议。

7. 一些实操后的个人体会

上下文管理这件事,本质上是在“信息完整性”和“注意力集中度”之间找平衡。给得太多,模型抓不住重点;给得太少,模型缺关键信息。没有一劳永逸的配置,只有针对具体任务不断调优的过程。

我现在做新Agent项目,第一版一定先把上下文管理框架搭好,而不是先写业务逻辑。因为业务逻辑可以慢慢加,但上下文结构一旦定型,后面改起来伤筋动骨。工具返回值的外置、任务状态对象的维护、摘要策略的分级,这三件事在项目初期就定下来,能省掉后面大量的返工。

最后分享一个我常用的自检问题:如果把这轮上下文砍掉一半,模型还能不能完成任务?如果答案是能,说明上下文里有大量冗余,该压了;如果答案是绝对不能,那要检查是不是关键信息没有被结构化保留,而是散落在对话历史里靠模型自己找。把关键信息显式化,是上下文管理最核心的一条原则。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 13:06:31

分布式训练核心:大模型多卡训练中的显存与通信取舍

最早接触分布式训练的时候&#xff0c;我的理解特别朴素&#xff1a;把模型均匀拆到几张显卡上&#xff0c;算完梯度再同步一下&#xff0c;不就完事了。直到自己动手跑一个7B级别模型的训练&#xff0c;看着显存被瞬间吃光、日志里频繁出现卡死和OOM&#xff0c;才发现这个“朴…

作者头像 李华
网站建设 2026/10/10 13:06:12

shp转KML带名称标注:FME与GDAL实战及避坑指南

简介&#xff1a;这是一份基于FME的Shapefile转KML工具包&#xff0c;面向GIS数据处理人员与需要对竣工图、地块等空间数据做轻量可视化标注的开发者。该资源可解决shp格式数据无法直接在地图平台中展示名称标签的问题&#xff0c;通过内置模板一键完成格式转换与名称标注&…

作者头像 李华
网站建设 2026/10/10 13:06:00

本地AI助手Hermes部署全指南:免费、私密、离线可用

不夸张地说&#xff0c;把 AI 助手装进自己电脑这件事&#xff0c;我前前后后折腾了大半年。最先用的是各种云端服务&#xff0c;看着方便&#xff0c;但订阅费一笔一笔叠起来&#xff0c;心里总不踏实&#xff1b;后来试着换开源方案&#xff0c;又碰上环境配置、模型下载、显…

作者头像 李华
网站建设 2026/10/10 13:04:38

Mac轻量级系统监控仪表盘:Swift原生实现原理与实战

1. 项目概述&#xff1a;为什么一个轻量级系统监控仪表盘在Mac上如此稀缺又刚需“Mole mo status”这个名字乍一听有点陌生&#xff0c;但如果你在终端里敲过htop、开过 Activity Monitor、或者为某个后台进程 CPU 突增而手忙脚乱地切回桌面查资源占用——那你其实已经和它要解…

作者头像 李华
网站建设 2026/10/10 13:03:50

N100小主机EOS日志频繁报错?从心跳超时到散热降频的根因排查实录

1. 项目背景与排查目标拆解1.1 为什么一台 N100 小主机会被拉出来单独排查N100 这台机器在圈子里火起来不是没有道理的——低功耗、带核显、支持双网口甚至四网口&#xff0c;价格又压得很低&#xff0c;很多人拿它当软路由、轻量 NAS、家庭服务器或者边缘计算节点用。但正因为…

作者头像 李华