openai-agents-python 沙箱 Token 截断工具深度解析:TruncationPolicy 与截断算法实战指南
2026/9/10 20:30:49 网站建设 项目流程

openai-agents-python 沙箱 Token 截断工具深度解析:TruncationPolicy 与截断算法实战指南

【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python

agents.sandbox.util.token_truncation是 openai-agents-python 沙箱子系统中的核心文本截断模块,负责在 Agent 工作流中把超长输出(Shell 命令结果、PTY 会话回显、记忆摘要、rollout 日志)安全地裁剪到模型上下文窗口内。本文以 API 参考文档 为骨架,结合源码实现与单元测试,系统讲解TruncationPolicy配置模型、字节/Token 双模式截断算法、UTF-8 安全切割原理及其在仓库各模块中的真实调用场景,读完即可掌握该工具的全部公开 API 并理解其内部机制。

模块定位:沙箱输出安全的最后一道防线

在沙箱执行流程中,Agent 运行 Shell 命令、读写记忆文件、回放 PTY 终端输出时,可能产生远超模型上下文窗口的超长文本。如果直接把原始文本塞进提示词,轻则浪费 Token 配额,重则触发上下文溢出导致请求失败。token_truncation模块就是为了解决这个问题而设计的轻量级工具集,它不依赖任何分词器,仅凭"约 1 Token ≈ 4 字节"的经验换算即可完成近似预算控制。

从源码结构看,该模块被沙箱子系统多处复用:

  • src/agents/sandbox/capabilities/tools/shell_tool.py:Shell 工具输出截断
  • src/agents/sandbox/session/pty_types.py:PTY 输出截断包装
  • src/agents/sandbox/session/pty_output.py:PTY 输出流截断落地
  • src/agents/sandbox/capabilities/memory.py:记忆摘要读取截断
  • src/agents/sandbox/memory/phase_one.py:Phase 1 记忆生成的 rollout 截断

模块的公开导出列表(__all__)在 token_truncation.py 中定义,并在 src/agents/sandbox/util/init.py 中向上层模块统一再导出,方便沙箱各子包以from ..util import TruncationPolicy, truncate_text的方式引用。

TruncationPolicy:两种模式统一预算配置

所有截断入口都接受一个TruncationPolicy实例作为策略描述。它定义在 token_truncation.py,是一个不可变(frozen=True)的 dataclass,包含两个字段:

字段类型说明
modeTruncationMode"bytes"/"tokens"预算计量单位
limitint预算上限数值

推荐通过两个类方法构造策略,它们会把负数限制钳制为 0,避免非法配置:

from agents.sandbox.util.token_truncation import TruncationPolicy # 以字节为单位的截断策略 byte_policy = TruncationPolicy.bytes(4096) # 以 Token 为单位的截断策略 token_policy = TruncationPolicy.tokens(15_000)

TruncationPolicy还提供两个预算换算方法,统一了"字节预算"与"Token 预算"的互转口径:

  • token_budget():若模式为bytes,按 4 字节/Token 把字节上限换算为 Token 上限;若模式为tokens,直接返回limit
  • byte_budget():反向换算,tokens模式按 4 字节/Token 放大为字节预算,bytes模式直接返回limit

这样无论上层以哪种单位声明限制,截断算法内部都可以统一按字节执行精确切割(见下文"字节级切割")。

近似换算模型:APPROX_BYTES_PER_TOKEN = 4

整个模块的 Token 估算都建立在一个常数上:

APPROX_BYTES_PER_TOKEN = 4

围绕它提供了三个公开换算函数(定义于 token_truncation.py):

  • approx_token_count(text):估算一段文本的 Token 数,实现为(字节数 + 3) // 4,即向上取整的字节数除以 4;
  • approx_bytes_for_tokens(tokens):Token 数乘以 4 得到字节预算,负数钳制为 0;
  • approx_tokens_from_byte_count(byte_count):从字节数反推 Token 数,同样向上取整,<= 0时返回 0。

测试 tests/sandbox/test_token_truncation.py 验证了这些换算行为,例如approx_token_count("abcde") == 2(5 字节向上取整为 2 个 Token)。需要强调的是,这是近似估算而非真实分词器结果,对于中文等多字节字符场景会存在偏差,但足以满足"防止上下文溢出"的工程目标。

截断函数族:从简单裁剪到带元数据输出

模块提供了四个面向不同场景的公开截断入口,按功能递进:

1.truncate_text(content, policy):基础截断

最简单的入口,按策略模式分派:

def truncate_text(content: str, policy: TruncationPolicy) -> str: if policy.mode == "bytes": return truncate_with_byte_estimate(content, policy) truncated, _ = truncate_with_token_budget(content, policy) return truncated

bytes模式走字节估算路径,tokens模式走 Token 预算路径并丢弃返回的原始计数。若内容本身未超预算,则原样返回(truncate_with_token_budget的提前返回逻辑见 token_truncation.py)。

2.formatted_truncate_text(content, policy):带行数前缀的格式化截断

在基础截断之上,当发生截断时会在结果前附加原始总行数元数据:

def formatted_truncate_text(content: str, policy: TruncationPolicy) -> str: if _byte_len(content) <= policy.byte_budget(): return content total_lines = len(content.splitlines()) if policy.mode == "tokens": prefix = f"Total output lines: {total_lines}\n\n" return _truncate_token_output(content, policy, prefix=prefix) result = truncate_text(content, policy) return f"Total output lines: {total_lines}\n\n{result}"

行为要点:内容未超预算时原样返回(不附加前缀);一旦触发截断,输出以Total output lines: N开头,让模型知道原始输出共有多少行,避免把截断结果误认为完整内容。测试 tests/sandbox/test_token_truncation.py 验证了行数前缀与chars truncated标记的共存。

3.formatted_truncate_text_with_token_count(text, max_output_tokens):带原始 Token 数回传的版本

这是 Shell 工具与 PTY 输出实际使用的入口,返回(截断后文本, 原始Token数)二元组:

def formatted_truncate_text_with_token_count( content: str, max_output_tokens: int | None ) -> tuple[str, int | None]: if max_output_tokens is None: return content, None policy = TruncationPolicy.tokens(max_output_tokens) if _byte_len(content) <= policy.byte_budget(): return content, None total_lines = len(content.splitlines()) prefix = f"Total output lines: {total_lines}\n\n" truncated = _truncate_token_output(content, policy, prefix=prefix) return truncated, approx_token_count(content)

设计上三个细节值得注意:

  • max_output_tokens=None表示不限制,原样返回且计数为None
  • 未超预算时返回(content, None),即只有真正发生截断时才报告原始 Token 数
  • 预算为 0 时返回空字符串(测试 tests/sandbox/test_token_truncation.py 验证),并仍回传原始估算 Token 数。

4.truncate_with_token_budget(s, policy):可获取原始计数的低层接口

返回(截断后文本, 原始Token数),与第 3 个函数的差异在于:它直接接受策略对象而非max_output_tokens,且不添加行数前缀,适合需要自行控制前缀格式的调用方。空字符串提前返回("", None)

截断算法原理:保留头尾 + 标记计入预算

截断不是简单地从尾部切掉,而是保留开头和结尾、只移除中间,这样模型既能读到命令输出的开头,又能看到结尾的错误码或收尾信息。核心流程在_truncate_token_output(token_truncation.py):

  1. 先按byte_budget()得到总字节预算;
  2. 预算为 0 时直接返回空串;
  3. split_budget把内容预算对半分给头部和尾部(split_budget(5) == (2, 3),即不均等时余数给尾部);
  4. 调用split_string做 UTF-8 安全的字节级切割,返回(被移除字符数, 头部文本, 尾部文本)
  5. format_truncation_marker生成截断标记,并统计实际被移除的字节/字符数;
  6. 关键点:标记文本本身也计入字节预算。如果前缀加标记超过预算,会先丢弃前缀;若仍放不下,则退化为只保留标记本身(_truncate_utf8(marker, max_bytes))。这保证了截断结果严格不超过预算,测试 tests/sandbox/test_token_truncation.py 断言approx_token_count(result) <= 32验证了这一点。

UTF-8 安全的split_string

split_string(token_truncation.py)是整个模块在字节模式下保持文本合法性的关键实现。它逐字符遍历字符串,按字符的 UTF-8 编码字节长度累积偏移量,只在字符边界处切分,避免把多字节字符(如中文"あ"、emoji)拦腰截断成无法解码的字节序列。测试 tests/sandbox/test_token_truncation.py 用split_string("aあbいc", 2, 4)验证了结果为头部"a"、尾部"いc",且正确报告移除了 2 个字符。

截断标记格式

format_truncation_marker(token_truncation.py)按模式生成不同标记:

  • tokens模式:…N tokens truncated…
  • bytes模式:…N chars truncated…

标记中的 N 是实际被移除的量:字节模式统计被移除字符数,Token 模式按approx_tokens_from_byte_count把被移除字节换算为 Token 数(removed_units_for_source,token_truncation.py)。最终由assemble_truncated_output拼装为头部 + 标记 + 尾部的形式。

仓库实战场景:五处真实调用

Shell 工具输出截断

src/agents/sandbox/capabilities/tools/shell_tool.py 中_truncate_output直接委托给formatted_truncate_text_with_token_count,并在_format_response(同文件 L33-L52)中将回传的original_token_count拼进响应头,例如Original token count: 12345,让模型对"原始输出有多大"有明确感知。

PTY 输出截断

PTY 会话侧,pty_types.py 的truncate_text_by_tokensformatted_truncate_text_with_token_count的薄包装,随后由 pty_output.py 在真实输出路径中调用并重新编码为 UTF-8 字节流返回。

记忆摘要读取截断

src/agents/sandbox/capabilities/memory.py 定义_MEMORY_SUMMARY_MAX_TOKENS = 15_000,在读取memory_summary.md时用TruncationPolicy.tokens(15_000)截断后再渲染进记忆读取提示词(同文件 L69-L72),防止巨型记忆摘要挤占上下文。

Phase 1 记忆生成 rollout 截断

src/agents/sandbox/memory/phase_one.py 定义_PHASE_ONE_ROLLOUT_TOKEN_LIMIT = 150_000,将累计的 JSONL rollout 内容截断后送入提取提示词;且一旦发生截断,会在结果前插入显式的省略声明(同文件 L20-L26),警告模型"当前渲染的 rollout 是不完整的视图"。这与 docs/sandbox/memory.md 描述的行为一致:"如果对话过长,将截断以适配上下文窗口,保留开头和结尾"。

测试验证:行为契约一览

tests/sandbox/test_token_truncation.py 是模块行为的权威契约,覆盖了以下关键不变量:

  • 负数 limit 被钳制为 0,且两个模式的预算换算结果都为 0(L20-L30);
  • 未超预算的内容原样返回,不附加任何元数据(L32-L34);
  • 截断后总 Token 数严格不超过预算,包括标记与行数前缀在内(L43-L50);
  • 空内容与预算为 0 的边界行为(L83-L101);
  • UTF-8 多字节字符切割边界保持合法(L110-L115);
  • 换算辅助函数的数值正确性(L122-L133)。

这些测试在仓库中以tests/sandbox/目录形式组织,可直接用pytest tests/sandbox/test_token_truncation.py在本地复现。

小结

agents.sandbox.util.token_truncation以"4 字节 ≈ 1 Token"的近似模型为根基,通过TruncationPolicy统一了字节与 Token 两种预算口径,用"保留头尾 + 标记计入预算 + UTF-8 安全切割"的算法保证了截断结果既合法又严格受限,同时通过Total output lines前缀与Original token count回传让模型对截断失真保持感知。无论是为 Shell 工具、PTY 输出设置max_output_tokens,还是在记忆生成管线中控制 rollout 与摘要体积,该模块都是 openai-agents-python 沙箱中控制上下文成本与稳定性的基础设施。

【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询