Back ay4's blog · blog

实习笔记06:Tau Context 管理机制解析

1,839 words 5 min read #Agent开发

实习笔记 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 至少需要具备两个能力:

  1. 估算当前 Context 的大小;
  2. 在接近限制时压缩历史信息。

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 Tokens

Text Token 估算

Tau 使用下面的方式进行粗略估算:

字符数量 / CHARS_PER_TOKEN

默认情况下:

4 个字符 ≈ 1 Token

这并不是真实的模型 tokenizer,而是一种低成本的近似计算,用于快速判断当前 Context 是否接近限制。

Message Token 估算

一条消息不仅可能包含正文,还可能包含 Thinking 内容、Tool Call 和 Tool 参数。因此,消息的 Token 估算大致包括:

Message Token
=
消息固定开销
+
正文 Token
+
Thinking Token
+
Tool Call Token

Tool Token 估算

工具定义本身也会占用 Context。模型需要知道工具名称、工具作用以及输入参数格式,因此工具 Token 的估算主要包括:

Tool Token
=
工具名称
+
工具描述
+
Input Schema

Tau 如何压缩上下文?

当 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.