
在真实智能体开发里最容易被忽略的往往不是模型能力而是执行上下文。无论 Agent 是在写代码、查资料还是调用多个工具完成任务模型真正能用来推理的并不是数据库或知识库里所有内容而是当前请求里被压缩进上下文的这批 token。腾讯发布的 ContextPilot用细粒度 RL 训练智能体主动管理工作上下文这个方向正好击中了很多 Agent 项目在长任务、多工具场景下的核心痛点。要理解 ContextPilot不能只看“压缩上下文”这四个字。它背后的关键变化在于把上下文管理从“规则截断”升级成“模型决策”。传统做法是上下文快满时按时间或长度丢弃旧消息ContextPilot 的方向则是让 Agent 自己判断哪些信息值得保留、哪些可以合并、哪些必须主动重新获取并把这个判断过程放到细粒度强化学习里训练。下面从工作上下文的本质开始拆解再看看如果要落地类似机制工程上应该怎么设计、验证和排错。1. 先理解工作上下文为什么会被塞满1.1 工作上下文不是 Prompt而是智能体的短时工作台很多人会把 Prompt 和上下文混在一起。实际上Prompt 只是开发者写给模型的那部分固定指令工作上下文则是 Agent 在某一次任务执行过程中真实看到的全部 token通常包含系统指令、用户需求、历史对话、检索文档、工具返回结果、中间思考和错误日志。可以把它理解成开发者的短时工作台。开发者会把自己正在处理的关键文件、编译信息、报错日志放在桌面上而不是把整个仓库都摊开。同样模型每一步推理能依赖的也只有上下文工作台里的东西。工作台太小任务做不完工作台摆放混乱模型会把重要信息和无关信息混在一起最终只能靠猜。LangGraph、Dify、Coze 等平台里的会话记忆、状态管理和工具返回内容都属于这个范畴。很多开发者在使用这些平台时发现会话一长模型回答开始变差或者请求直接报“上下文超限”就是因为工作台上的内容缺乏管理机制。1.2 上下文超限会引发的不只是报错上下文塞满之后最直接的表现是请求失败。因为模型 API 对输入 token 有上限超过后无法继续执行。但在超过上限之前更隐蔽的问题是性能劣化。当上下文里堆满中间过程时模型很难把注意力放在真正关键的信息上。检索到的文档可能有大量重复内容工具返回结果可能包含几十页原始数据历史对话里可能积累了十几轮无效澄清。这些内容不仅增加费用和延迟还会挤压关键信息的位置。很多模型在超长上下文里会出现“中间信息丢失”的现象也就是开头和结尾的内容被关注中段的关键结论被忽略。因此工作上下文管理的目标并不只是让请求不报错而是让有限的 token 空间始终保持高信噪比。下表是几种常见上下文状态的对比。上下文状态对模型效果的影响对成本和延迟的影响是否需要管理信息过载保留大量重复文档关键信息被淹没回答不准确成本高首字延迟高必须管理相关但过期保留旧指令Agent 做出错误决策中高成本浪费预算必须管理按长度硬截断丢失关键链路后续步骤无依据任务断层成本可控但返工率高需要更细策略主动压缩和保留摘要关键语义仍可复用成本低执行稳定理想状态1.3 Agent 需要主动管理而不是被动截断过去大部分方案是“被动管理”当 token 超过阈值时丢掉最早的消息或者对整个历史做一次 summary。这种方式能在短任务里工作但在复杂工具链中问题很明显。假设 Agent 先查看了一个 JSON 配置文件然后调用了接口接口返回又依赖配置里的某个字段。如果系统只是按时间截断了配置文件的原文后续模型就失去了判断依据。它可能在下一个动作里重新读取文件也可能直接凭印象猜一个值产生非常隐蔽的错误。ContextPilot 强调的“主动管理”本质上是要在正确的时间点决定哪些原始内容需要进入当前窗口哪些内容只保留摘要哪些内容可以从本地索引或外部存储延迟加载。这个决定不能在所有任务里都一刀切必须根据任务状态动态调整。2. ContextPilot 的方向把上下文管理变成 RL 决策问题2.1 为什么不能用固定规则完成主动管理固定规则可以解决一部分问题但解决不了所有问题。举个例子工具返回了一份 1000 行的日志Agent 正在排查接口超时。规则可以设定“只保留最后 50 行”但真正可能导致问题的是第一行里的超时配置或者中间某一行里的报错码。固定规则无法知道哪一行对当前任务最重要。如果让模型自己决定模型又可能为了减少风险而保留所有内容或者为了节省 token 而过度压缩。这里的本质是一个决策问题在每一个执行步骤是否压缩、压缩哪部分、压缩到什么程度、是否需要重新检索这些动作会影响后续所有步骤的成败。ContextPilot 的切入点是用细粒度 RL 来训练这个决策过程。RL 关心的是“状态-动作-奖励”。状态是当前执行上下文动作是某种上下文编辑操作奖励则来自任务成功率和上下文使用效率的综合评估。让模型在大量 Agent 任务中试错才能真正学会在“保留细节”和“节省上下文空间”之间做权衡。2.2 细粒度 RL 的“细”体现在哪里细粒度 RL 是相对任务级 RL 而言的。常规 RL 训练 Agent 时往往只看最终任务是否成功然后给一个整体奖励。这种信号太稀疏一个任务可能有 50 步工具调用如果中间 40 步都犯了错最后一步成功模型很难判断哪一步的上下文管理是对的。细粒度 RL 会把任务过程拆开尽可能对上下文编辑行为本身给出信号。压缩一个重要信息后导致任务失败当前这个压缩行为应该得负分压缩一段无关日志但没有影响后续任务当前行为应该得正分主动重新读取了被遗忘的关键配置也应该获得正向反馈。这种拆分后的信号更接近“教练逐回合指导”而不是“只看整场比赛输赢”。在上下文管理任务里细粒度信号尤其重要因为压缩动作本身不会立刻产生可观察结果它要到后续几步才会显现出价值。如果只用最终奖励很难把失败归因到某一次不当压缩。2.3 可训练的动作空间设计要让 RL 策略真正起作用必须先定义一套上下文编辑动作。ContextPilot 的具体动作集没有公开完整细节但从上下文管理任务的一般规律来看动作至少应该覆盖以下几类保留原始内容。对指定消息做摘要。折叠工具返回的详细结果。标记某段对话为可过期内容。从存档中重新加载关键上下文。主动删除无关的中间观察。调整系统指令和任务目标的展示顺序。每个动作最好能附带细粒度的对象信息。例如动作是summarize对象是某一次工具返回结果目标是“在保留关键字段的前提下压缩内容”。模型需要知道它操作的是哪一段数据否则策略即使学到也无法稳定应用。下面是一个动作空间示例仅用于说明数据结构不代表 ContextPilot 官方接口。{ action: summarize, target: tool_result_32, reason: 日志主体无异常只需要保留错误码和超时时间, params: { max_tokens: 400, keep_first: true, keep_last: true } }有了统一动作结构RL 策略、规则兜底和人工调试才能共用同一套日志和回放逻辑。3. 上下文管理器需要的工程组件和策略接口3.1 对上下文做分层建模如果只是修改当前 prompt很难做好上下文管理。工程上更稳妥的方法是把上下文分成几个层次。工作层当前真正进入模型的 token也叫 active context。存档层已经发生但不需要立即展示的完整记录。摘要层对历史记录、工具结果、长文档生成的语义压缩。检索层当任务需要更多细节时可以从哪取回原文。可以理解成一个渐进式的归档系统。当前需要的内容放在桌面上暂时不用的放进抽屉抽屉里放一份便利贴说明“这个抽屉是什么”。如果后续工作需要某个抽屉里的细节再打开抽屉读取并重新决定是不是要占用桌面空间。{ session_id: session_test_001, active_context: [ {role: system, content: 你是代码调试助手}, {role: user, content: 请帮我排查订单超时问题}, {role: tool_result, tool: read_file, content: 日志片段摘要保留错误码} ], archive: { tool_result_32_raw: 完整日志路径或分页存储标识 }, summary_index: { read_file_12: 用户要求查看 nginx 访问日志定位 504 错误, api_call_09: 订单服务返回超时上游库存服务耗时 3 秒 }, budget: { max_tokens: 16000, reserved_tokens: 1200, system_instruction_tokens: 800 } }这种分层结构的好处是任意时刻模型看到的只是 active_context但所有历史信息并没有被真正删除只是被挪到了更便宜、不占用当前注意力的存储层。一旦 RL 策略决定需要细节可以通过 retrieval 动作把存档内容重新拉回工作层。3.2 策略接口应当独立于具体模型实际编写 Agent 时最好不要把上下文管理逻辑散落在业务代码里。建议单独抽象出 ContextManager它负责更新状态、选择动作、执行压缩、记录日志。from dataclasses import dataclass from typing import Callable, List, Dict dataclass class ContextAction: action: str target: str params: Dict class ContextManager: def __init__(self, policy: Callable[[Dict], ContextAction], summarizer: Callable[[List[Dict]], str], estimate_tokens: Callable[[str], int], max_tokens: int 8000): self.policy policy self.summarizer summarizer self.estimate_tokens estimate_tokens self.max_tokens max_tokens self.active_context: List[Dict] [] self.archive: Dict[str, Dict] {} self.summary_index: List[Dict] [] self.action_log: List[Dict] [] def observe(self) - Dict: return { active_context: self.active_context, summary_index: self.summary_index, used_tokens: self._used_tokens(), max_tokens: self.max_tokens, last_actions: self.action_log[-5:] } def step(self, new_message: Dict) - ContextAction: self.active_context.append(new_message) observation self.observe() action self.policy(observation) self.action_log.append({obs: observation, action: action.__dict__}) return action def execute(self, action: ContextAction): if action.action summarize: target_message self._find_target(action.target) summary self.summarizer([target_message]) self.summary_index.append({ target: action.target, summary: summary, time: action.params.get(time) }) self._replace_with_summary(action.target, summary) elif action.action drop: self.archive[action.target] self._find_target(action.target) self.active_context [ m for m in self.active_context if m.get(id) ! action.target ] elif action.action reload: raw self.archive.get(action.target) if raw is not None: self.active_context.append(raw) self._compact_if_needed() def _used_tokens(self) - int: return sum( self.estimate_tokens(m.get(content, )) for m in self.active_context )这段代码没有真正训练模型但它把“策略决策”和“业务逻辑”分开了。policy 可以是任意函数前期可以先写规则例如“当 used_tokens 超过 max_tokens 的 80% 时对整个工具结果进行摘要”后期换成 RL 模型时只需要替换 policy 的实现不需要重写执行逻辑。3.3 预算管理是 RL 训练的必要约束上下文管理器不能无限制执行动作。每个任务都应该有明确的 token 预算。预算可以分给系统指令、压缩后的历史、当前工具结果、最终答案留白。建议保留一部分 token 给模型生成回复否则压缩后的上下文可用但最后输出超限仍然会失败。参数设计时需要衡量两个方向参数含义调大后的影响调小后的影响max_context_tokens当前工作层可用的最大 token 数能保留更多原始信息但成本和延迟上升节省成本但频繁压缩容易丢失细节reserved_output_tokens给最终回答保留的 token输出更稳定但压缩发生得更早可用空间变大但回答可能被截断compression_threshold触发压缩决策的上下文使用率触发少上下文压力大触发频繁决策成本高summarize_max_tokens摘要允许的最大长度摘要更完整但占用上下文摘要更短容易丢细节训练细粒度 RL 策略时可以把“是否遵守预算”作为硬约束再在预算内学习如何分配每个模块的空间。不能让模型牺牲任务成功率去追求上下文占用率低也不应该为了任务成功率无限制保留所有中间结果。4. 一个最小可运行的验证原型4.1 明确目标先验证上下文压缩是否影响任务ContextPilot 级别的完整训练需要大量算力和 Agent 环境。普通开发者可以先做一个简化版原型用来回答一个问题同一个任务在同样的模型能力下加入主动上下文管理后结果是否会变好。这个原型不直接复现腾讯内部模型而是用当前可用的模型 API 做 summarizer用规则或轻量策略做 policy。重点是把“上下文管理器”这套机制跑通并留下行为日志。4.2 准备环境需要准备一个能调用模型 API 的 Python 环境以及一个长文档阅读理解任务。比如让 Agent 读一份包含产品列表、价格、库存和用户评价的长文档回答“哪些商品适合补货”。原始文档可能超过模型窗口或者接近窗口限制导致 Agent 回答不稳定。建议使用下面的最小环境Python 3.10 及以上。一个模型 API 客户端。一个用于生成摘要的函数。一个简单的 token 估算函数。如果原始项目中已经接入了 LangGraph、LlamaIndex 或其他 Agent 框架不需要额外引入框架可以直接在调用大模型之前加一个 ContextManager 层。4.3 实现上下文压缩逻辑先实现一个简单的 summarize 触发def estimate_tokens(text: str) - int: # 中文可以按字符数估算中文约 1.5 到 2 token/字 return int(len(text) * 1.5) 8 def make_summarizer(llm_chat): def summarize(messages): content messages[-1][content] if len(content) 1000: return content prompt f请压缩以下内容保留所有关键数字和结论\n{content} reply llm_chat([{role: user, content: prompt}], max_tokens200) return reply return summarize这里的summarize并不完美只做一个 baseline。真实上下文管理器中摘要需要按信息来源分别压缩避免把工具结果和用户意图混在一起。然后组装验证流程def run_agent_with_context_manager(raw_document, user_question, llm_chat): summarizer make_summarizer(llm_chat) cm ContextManager( policylambda obs: ContextAction( actionsummarize, targetdoc_long, params{max_tokens: 500} ) if obs[used_tokens] 5000 else ContextAction( actionkeep, target, params{} ), summarizersummarizer, estimate_tokensestimate_tokens, max_tokens6000 ) cm.step({id: user_question, role: user, content: user_question}) cm.step({id: doc_long, role: user, content: raw_document}) action cm.execute(cm.policy(cm.observe())) final_messages cm.active_context return llm_chat(final_messages, max_tokens800), cm.action_log实际运行前要在日志里记录原始文档多少 token压缩后多少 token摘要是否保留了用户问题需要的字段。4.4 运行结果怎么看预期结果是在长文档任务中ContextManager 可以把输入从 8000 token 降到 2000 token 左右同时仍能回答出关键信息。但因为摘要函数比较粗糙可能出现以下问题摘要丢弃了某个价格字段导致 Agent 回答“库存充足”但无法给出准确数量。原始文档里数字较多压缩后模型开始编造缺失的数字。每次都重新对全文做 summary导致调用成本反而上升。因此最小原型验证的不只是“能不能跑通”而是“压缩之后任务效果是否保持一致”。如果 baseline 压缩导致回答质量明显下降说明简单摘要不够下一步才需要引入更细粒度的 RL 决策。5. 如何评估模型真的学会了主动管理上下文5.1 离线评测指标评估上下文管理策略时不能只看 token 节省量还要看任务成功率。我建议把指标分成三层。第一层是任务效果包括最终回答准确率、工具是否成功执行、用户需求是否被满足。第二层是上下文效率包括平均每次请求 token 数、主动压缩次数、上下文超限报错次数。第三层是压缩行为质量包括摘要是否需要反复重读、关键字段是否被保存、被压缩内容是否真的不再被后续步骤需要。在批量评估时可以分别记录使用原始上下文、使用硬截断、使用固定摘要、使用 RL 策略四组的差异。评测指标原始上下文硬截断固定摘要RL 策略任务成功率高但受窗口限制低明细丢失中等目标最高平均 token 消耗最高低中低中间重读次数少多中少上下文超限率高低低低可解释性无需解释较差中等可用日志解释如果一味的 token 节省导致任务成功率大幅下降说明策略过度压缩。反过来如果任务成功率很高但 token 用量和原始上下文几乎一样说明策略没有真正学会管理。5.2 测试集要覆盖关键决策点RL 策略训练结束后还需要一套专门用于验证上下文管理能力的数据集。测试数据不能只给普通问答应该设计包含上下文压力的任务。可以复用下面几类任务长文档单轮问答文档中有重复段落和关键结论。多工具连续调用任务必须保留中间步骤的输出结果才能继续下一步。长会话历史任务前 20 轮信息与最终问题相关但中间有一轮误导信息。超长错误日志任务需要从 full log 中定位根因。每类任务中都应标记“关键信息点”。评测时自动检查最终回答有没有包含这些关键点同时统计模型是否在调试日志中访问过对应的原始片段。5.3 奖励函数拆解到行为级细粒度 RL 的奖励设计需要细化到行为级。一个朴素奖励函数可以这样理解def compute_step_reward(task_success_so_far, action, before_tokens, after_tokens, key_info_preserved, reload_required): reward 0.0 if key_info_preserved and after_tokens before_tokens * 0.5: reward 1.0 if reload_required: reward - 1.0 if action summarize and task_success_so_far: reward 0.2 return reward这里没有使用最终任务一个分数而是对每个压缩动作单独打分。key_info_preserved表示摘要后还能不能通过检索或后续回答找到关键信息reload_required表示后续步骤是否必须重新加载原文。如果策略频繁压缩后又频繁重读原文单次压缩看似节省了 token整体却增加了调用次数和时间应该在奖励里体现负分。6. 实际调试中的常见坑和排查链路6.1 模型学会了压缩但任务结果变差现象token 用量确实下降了但 Agent 最终回答错误率上升尤其在需要精确数字时经常漏字段。原因奖励函数偏向“节省 token”没有足够强调关键信息保留。摘要函数又是一个独立模型压缩内容时的错误没有反馈给 RL 策略。检查方式打开 action_log看每次压缩动作前后的目标文本检查摘要里是否保留数字、名称、代码字段、错误码。如果十次压缩有八次丢掉关键字段问题通常出在摘要质量和奖励权重上。处理建议不要直接对整段原始数据做一句摘要先把文本按结构切块对每个块做“是否关键”的判断。奖励函数中对结构字段的保留单独加分。6.2 上下文管理器频繁触发导致延迟和成本上升现象任务并不长但系统每隔几步就调用一次摘要模型整体耗时比直接处理完整上下文还要高。原因压缩阈值设置过高或策略认为任何活跃消息都该被压缩。摘要动作本身也要消耗模型调用和 token如果不把决策成本纳入奖励策略会做出“过度管理”的行为。检查方式统计每个任务的平均压缩次数和压缩耗时。如果一个 5 步任务主动压缩了 8 次明显异常。处理建议调低压缩频率不是唯一办法更好的方式是给每个动作增加成本惩罚。同时在策略入口加冷却时间比如同一类摘要动作至少间隔 N 步。6.3 压缩后 Agent 找不到工具结果现象Agent 执行了一个工具调用随后要求“查看返回结果”但上下文中只保留了摘要没有原始数据导致后续指令无法执行。原因上下文管理器把工具返回结果压缩成摘要但没有保留工具结果 ID 和读取接口。Agent 只知道摘要不知道从哪里重新获取原文。检查方式在 action_log 中查找summarize动作确认目标工具结果是否归档到可检索存储。再模拟一次用户询问“根据刚才日志里的具体报错行继续排查”看 Agent 是否有能力 reload。处理建议压缩工具结果时摘要内容必须包含原始结果的来源标识。Agent 上下文里要有一个可用的 reload 动作不能只允许删不允许取回。6.4 RL 训练收敛慢策略几乎不采取压缩动作现象在训练早期模型发现压缩后偶尔会丢信息于是策略逐渐变成“不压缩任何东西”最终表现为任何上下文管理动作都不触发。原因这是典型的策略坍缩。RL 对动作的负反馈过于敏感模型最终选择保守策略来避免负分。如果默认动作keep的预期收益高于其他动作模型不会主动探索压缩。检查方式看训练日志中动作分布确认keep是否占比超过 95%。同时看奖励曲线如果奖励上升完全来自任务成功而不是来自上下文效率说明细粒度信号没有真正生效。处理建议在 RL 训练一开始给上下文效率目标加上一个较大的正向系数或者使用课程学习先只训练“删除明显无关内容”再逐步引入“摘要完整内容”。这也提醒我们不是所有压缩策略都适合一步到位。下表总结了这四类常见问题。问题现象可能原因检查方式处理建议token 下降但任务变差奖励偏好压缩摘要丢失关键信息查看摘要文本和关键字段保留率按结构化切块逐块压缩奖励中加入关键信息保留分压缩次数过多摘要调用成本未纳入决策统计每一步压缩耗时和次数给动作添加成本惩罚加入压缩冷却期后续无法获取工具详情压缩时丢失原始来源标识检查 action_log 和存档层摘要保留来源 ID提供 reload 机制策略从不压缩RL 对压缩负反馈过度敏感看动作分布和奖励分解调整动作奖励系数使用课程学习从无关内容删除开始7. 生产环境落地建议与后续扩展7.1 学习环境与生产环境的差异在实际项目里ContextPilot 这类思路最忌“在一个短任务里测不出差异就以为机制无效”。学习环境里可以用几百 token 的小任务快速验证上下文管理器是否安全但生产环境需要额外考虑以下内容上下文动作日志必须保存否则 RL 训练和问题归因都没有数据。摘要和压缩动作要可回滚必要时能手动在调试面板里恢复被折叠的原文。策略服务必须设置降级路径RL 策略不可用时自动回退到固定摘要规则。token 估算应使用真实 tokenizer而不是简单按字符数估算。需要监控上下文管理动作带来的额外延迟和成本。区分环境很重要。生产环境不要在每次请求都动态调用大模型做摘要建议增加缓存层。同一份长文档第一次摘要后保存 key后续直接复用。如果文档被修改再更新缓存。7.2 适合先试点上下文管理的 Agent 场景不是所有 Agent 都需要复杂的上下文管理器。如果用户只是做简单问答当前对话只有两三轮直接透传即可。下面几类场景更适合先试点销售智能体需要读大量客户资料和产品手册回答中还要不断引用不同来源。代码调试 Agent 需要保留代码文件、编译日志和上次修复动作任务跨越多轮。数据分析智能体需要调用 SQL 或图表工具每次返回都占大量 token。工作流 Agent 塞满会话后需要继续执行后续节点例如配置了多个工具的综合流程。这些场景的共同点是任务越长单轮成功率越低信息一旦被截断Agent 无法重新通过对话找回上下文里存在大量“相关但当前不需要”的内容。7.3 一条可复用的落地清单如果在自己的项目里引入 ContextPilot 类似思路可以从下面这份清单开始检查是否定义了 active context、archive、summary index 三层结构。是否所有消息都有唯一 ID。是否记录每一条压缩动作的 reason、target、before_tokens、after_tokens。是否能在摘要后重新加载原始内容。是否保留足够空间给模型最终输出。是否固定规则兜底避免 RL 策略完全不可用。是否用真实 tokenizer 统计长度。是否有离线数据集衡量压缩后的任务成功率。是否对摘要缓存避免重复调用模型。是否把上下文效率纳入最终评估指标而不只看任务成功。如果每一项都能明确回答“是”一套可观测、可回滚、可扩展的上下文管理机制基本就成型了。7.4 从摘要到细粒度 RL 的扩展路径对于刚接触这个方向的开发者建议不要一开始就写强化学习训练脚本。先实现固定的摘要策略再把它升级成“基于上下文占用率触发的规则策略”然后收集大量行为日志最后再用这些日志设计 RL 状态和奖励。这样做的好处是每一步都有可对比的 baseline也能在训练失败时快速定位是状态设计问题还是奖励函数问题。ContextPilot 把“上下文管理”从工程技巧提升到大模型决策问题这是 Agent 长任务落地的重要一步。它本质上在说不要让上下文窗口决定 Agent 能做什么而要让 Agent 学会在有限的窗口里精准组织自己的工作台。接下来最值得做的不是等待一个新模型发布而是先在当前项目里补上上下文日志、压缩动作和回读能力积累自己的最佳实践。