实习笔记 06:Tau Context 管理机制解析
最近开始阅读 Tau 的源码,希望通过一个真实的 Coding Agent 项目,理解 Agent 内部的工作机制。
相比直接学习各种 Agent Framework,我更希望从一个完整实现中弄清楚:
- Agent 如何维护上下文?
- Token 如何计算?
- 为什么需要 Context Compression?
- 压缩后的历史如何继续支持后续任务?
这次主要阅读的是 Tau 中负责 Context 管理的部分。
为什么需要关注 Context?
LLM 本身并没有无限记忆。一次请求发送给模型的内容通常包括:
System Prompt +Conversation Messages +Tool Definitions +Tool Results随着 Agent 不断执行任务,对话会越来越长,工具调用越来越多,文件内容也会不断加入上下文,最终可能超过模型的 Context Window。
因此,Agent 至少需要具备两个能力:
- 估算当前 Context 的大小;
- 在接近限制时压缩历史信息。
Tau 的 Context 管理主要围绕这两个方向展开。
Tau 如何估算 Context?
Tau 对 Context 的计算主要分为两种情况。
Provider 返回可靠的 usage 信息时
如果模型 Provider 返回了可靠的 usage 信息,例如:
- Input Tokens
- Output Tokens
- Total Tokens
- Cache Tokens
Tau 会优先使用 Provider 提供的数据,因为它来自模型的真实计算结果,比基于字符数的估算更准确。
此时,Tau 的计算策略可以概括为:
Provider 已知 Token +后续新增消息估算 +后续新增工具估算例如,Provider 返回已经使用了 5000 tokens,之后新增的用户消息约为 200 tokens,新增工具定义约为 100 tokens,那么最终估算结果就是:
5000 + 200 + 100 = 5300 tokens这样可以避免重复计算已经有可靠数据的历史上下文。
Provider 没有返回可靠的 usage 信息时
如果没有可用的 Provider Token 信息,Tau 会使用自己的估算方式:
System Prompt Token +Message Token +Tool Token =Estimated Context TokensText Token 估算
Tau 使用下面的方式进行粗略估算:
字符数量 / CHARS_PER_TOKEN默认情况下:
4 个字符 ≈ 1 Token这并不是真实的模型 tokenizer,而是一种低成本的近似计算,用于快速判断当前 Context 是否接近限制。
Message Token 估算
一条消息不仅可能包含正文,还可能包含 Thinking 内容、Tool Call 和 Tool 参数。因此,消息的 Token 估算大致包括:
Message Token =消息固定开销 +正文 Token +Thinking Token +Tool Call TokenTool Token 估算
工具定义本身也会占用 Context。模型需要知道工具名称、工具作用以及输入参数格式,因此工具 Token 的估算主要包括:
Tool Token =工具名称 +工具描述 +Input SchemaTau 如何压缩上下文?
当 Context 接近限制时,Tau 会压缩部分历史。整个过程主要分为两个阶段。
第一步:判断是否存在旧摘要
Tau 首先判断当前 Context 中是否已经存在之前生成的摘要,以此决定本次是新建摘要还是更新摘要。
第一次压缩时:
历史消息 ↓生成新 Summary后续再次压缩时:
旧 Summary +新增消息 ↓更新 Summary旧摘要的存在意味着上下文此前已经被压缩过。此时不能忽略旧摘要,否则更早的任务信息就会丢失。
第二步:构造摘要 Prompt
Tau 不会直接把原始消息对象交给模型,而是先将消息序列化,再构造专门用于摘要的 Prompt:
Agent Messages ↓serialize_messages_for_compaction() ↓转换成模型可读的结构化文本 ↓build_compaction_summary_prompt() ↓生成完整的 Summary Prompt最终的 Prompt 大致包含以下内容:
<conversation>
用户任务……助手操作……工具结果……
</conversation>
<previous-summary>
旧摘要内容……
</previous-summary>
Summary Rules其中 <previous-summary> 只会在旧摘要存在时加入。
阅读这部分源码时,最容易混淆的是几个名字相近的函数。它们分别处在不同层次:
summarize_messages_for_compaction()
作用:将消息按照固定格式整理成简化文本。
Message ↓提取角色 ↓提取正文 ↓添加 Tool 信息 ↓限制长度 ↓拼接字符串它属于程序级摘要,不会调用模型。
serialize_messages_for_compaction()
作用:将消息对象转换成结构化文本。
例如:
UserMessage ↓<message role="user"> 内容</message>它主要负责:
- 保留消息角色;
- 保留工具调用信息;
- 保留工具错误状态。
build_compaction_summary_prompt()
作用:构建最终发送给模型的摘要 Prompt。
新消息 +旧摘要(如果存在) +摘要规则 +用户额外要求 ↓完整 Prompt需要特别注意:这个函数只负责构造 Prompt,并不负责:
- 调用模型;
- 保存摘要;
- 替换历史消息。
把这些步骤串联起来,Tau Context 管理的完整流程如下:
Agent 运行 ↓产生 Messages ↓估算 Context Token ↓判断是否接近限制 ↓触发 Compression ↓判断是否存在旧 Summary ↓构造 Summary Prompt ↓调用 LLM 生成 Summary ↓保存 Summary ↓后续 Context 使用 Summary 代替部分历史我对 Context 管理的理解
1. Context 不只是聊天记录
在 Agent 中:
Context =System Prompt +Messages +Tools +Tool Results +历史摘要Context 是模型在当前请求中能够看到的全部信息。
2. Compression 不等于删除历史
过去我对上下文压缩的理解更接近“删除旧消息”,但实际过程是:
旧消息 ↓总结成 Summary ↓Summary 代替旧消息进入 Context完整历史仍然可以保存,只是当前模型看到的是压缩后的版本。
3. Agent 的核心不只是调用模型
一个真正的 Agent 系统还需要解决:
- 如何管理 Context;
- 如何调用工具;
- 如何保存状态;
- 如何恢复任务。
LLM 只是其中负责推理的一部分。
下一步:进入 Agent Loop
完成 Context 模块后,下一步准备进入 Agent Loop。计划中的学习顺序是:
Context Management ↓Agent Loop ↓Message System ↓Tool System ↓Session Management下一阶段需要重点回答这些问题:
- 用户输入后,模型在哪里被调用?
- 模型如何决定调用工具?
- Tool Call 如何执行?
- Tool Result 如何返回模型?
- 什么条件下结束循环?
也就是理解下面这条完整链路:
User ↓LLM ↓Tool Call ↓Tool Execution ↓Tool Result ↓LLM ↓Final Answer这是理解 Agent 工作机制最核心的一步。
通过阅读 Tau 的 Context 源码,我第一次从实现层面理解了:一个 Coding Agent 并不是简单地“调用大模型,再加几个工具”,而是需要围绕模型构建一套 Context 管理、状态保存、工具执行和任务恢复机制。
Context 管理只是 Agent 系统中的一个模块。它决定了模型能够看到什么、历史信息如何被保留,以及长任务如何在有限的 Context Window 中继续执行。
下一篇计划继续阅读 Tau 的 Agent Loop,理解 Agent 如何完成一次完整的任务执行:
《Tau 源码学习笔记 02:从 Agent Loop 理解 Coding Agent 如何调用工具》
现在已经知道了“模型看到了什么”,下一步就是研究:模型为什么会行动,以及谁驱动它行动。
Comments
Quiet notes for this article.