Token 用量分析

OpenClaw 为什么消耗那么多 token?优化空间在哪里?

每次 LLM 调用的 token 预算

每次 LLM 调用发送的内容:

输入 tokens = System prompt(系统提示词)
            + Tool 定义(每个启用工具的 JSON schema)
            + 会话历史(所有之前的轮次,或压缩摘要 + 最近轮次)
            + Memory/RAG 上下文(注入的搜索结果)
            + 当前用户消息

默认上下文窗口:200,000 tokenssrc/agents/defaults.ts:6DEFAULT_CONTEXT_TOKENS = 200_000)。

Token 消耗来源排名

1. 会话历史(最大且持续增长)

源码~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl

每一轮(用户消息 + 助手回复 + 工具调用 + 工具结果)都追加到 JSONL 转录文件。下一轮时,整个历史被加载并发送给 LLM。

典型对话中:

  • 每次工具调用来回约 ~500-2000 tokens(工具输入 + 输出)
  • Agent 每条用户消息可能做 5-10 次工具调用
  • 10 条用户消息带工具使用后,历史可轻松达到 50,000-100,000 tokens

压缩触发条件src/agents/compaction.ts):

  • 当历史超过 contextWindow - reserveTokensFloor 时,触发压缩
  • Reserve 值可通过 agents.defaults.compaction.reserveTokensFloor 配置
  • 压缩将历史分成多个块(默认 2 部分),逐个总结,再合并摘要
  • 总结开销:预留 4,096 tokensSUMMARIZATION_OVERHEAD_TOKENS
  • 使用 1.2x 安全余量,因为 estimateTokens() 用 chars/4 启发式算法会低估

关键洞察:在压缩触发之前,每轮都要支付完整历史的代价。一个 20 轮带工具使用的对话,轻松每次调用烧掉 100k+ 输入 tokens

2. System prompt(约 2,000-5,000 tokens,每次调用固定)

源码src/agents/system-prompt.tsbuildAgentSystemPrompt()

System prompt 包含以下章节(按顺序):

章节条件估算 tokens
身份行总是~15
工具列表(工具摘要)总是~500-800(24 个核心工具 × 约 30 tokens)
工具调用风格总是~80
安全总是~100
CLI 快速参考总是~80
Skills如果加载了 skills~1,000-5,000(见下文)
记忆回忆如果有记忆工具~60
自更新如果有 gateway 工具~80
模型别名如果配置了别名~100-300
工作空间总是~40
文档如果设置了 docs 路径~60
沙盒如果在沙盒中~200
授权发送者如果配置了 owner~30
时间如果设置了时区~20
工作空间文件(注入)总是(标题)~20
回复标签仅 full 模式~80
消息仅 full 模式~200
语音 (TTS)如果配置了 TTS~30
上下文文件(SOUL.md 等)如果存在上下文文件~500-5,000(取决于文件大小)
静默回复仅 full 模式~100
心跳仅 full 模式~60
运行时总是~40

System prompt 总量:约 2,000 tokens(最小)到 5,000+ tokens(完整模式含 skills + 上下文文件)。

子 Agent 优化PromptMode):

  • "full":所有章节(约 5,000 tokens)
  • "minimal":精简章节 — 跳过记忆、消息、语音、回复标签、静默回复、心跳(约 1,500 tokens)
  • "none":仅身份行(约 15 tokens)

3. Skills 目录(约 1,000-5,000 tokens,每次调用固定)

源码src/agents/skills/workspace.ts

Skills 从三个来源加载:

  1. 内置 skills(skills/)— 52 个
  2. 工作空间 skills(~/.openclaw/agents/<id>/workspace/skills/
  3. 插件 skills(来自 extensions)

硬限制workspace.ts:95-98):

DEFAULT_MAX_CANDIDATES_PER_ROOT = 300
DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE = 200
DEFAULT_MAX_SKILLS_IN_PROMPT = 150
DEFAULT_MAX_SKILLS_PROMPT_CHARS = 30,000  ← 约 7,500 tokens
DEFAULT_MAX_SKILL_FILE_BYTES = 256,000

Skills prompt 不包含完整的 SKILL.md 内容。它包含的是一个目录:每个 skill 的名称 + 描述 + 文件位置。Agent 只有在选中某个 skill 后才会读取完整的 SKILL.md。

已有优化compactSkillPaths() 将 home 目录路径替换为 ~,共节省约 400-600 tokens(源码注释:"Saves ~5-6 tokens per skill path × N skills")。

但是:52+ 个 skill 每个约 20-50 tokens(名称 + 描述 + 路径),仅目录就有约 1,500-3,000 tokens

4. Tool 定义(约 2,000-4,000 tokens,每次调用固定)

源码:伴随每次 LLM 调用发送的工具 JSON schema

Pi Agent SDK 将工具定义作为 JSON schema 对象发送。每个工具包含:

  • 名称
  • 描述
  • 参数 schema(properties、types、descriptions、required 字段)

24 个核心工具 + 插件工具 + skill 工具,典型总量约 2,000-4,000 tokens 的 JSON schema。

待确认:system prompt 中的工具摘要和 API 调用中的工具定义之间是否有重复。这可能是一个冗余点。

5. Memory/RAG 上下文(每轮约 500-2,000 tokens)

源码src/memory/manager.ts

记忆搜索结果被注入对话中。混合搜索(BM25 + 向量 + MMR 重排序)从 SQLite 数据库返回最佳匹配。

待确认:注入的 chunk 数量上限和每次注入的最大 token 数。需要更深入阅读 src/memory/manager-search.ts

6. 上下文文件(0-5,000+ tokens,每次调用固定)

源码buildAgentSystemPrompt() 第 588-608 行

用户可编辑文件(如 SOUL.mdRULES.md、工作空间文档)被原样加载注入 system prompt 的"Project Context"下。没有截断 — 完整文件内容都包含在内。

一个较大的 SOUL.md 或工作空间文档可以轻松增加 2,000-5,000 tokens 每次调用。

7. 心跳开销(每次心跳约完整上下文)

源码src/cron/service/timer.ts

默认:当 cron.heartbeat.enabled: true 时每 600 秒(10 分钟)一次。

每次心跳发送完整 system prompt + 会话历史来做一次 LLM 调用,只是为了得到"HEARTBEAT_OK"的回复。如果会话历史很长,这很昂贵。

成本估算:200k 上下文 × $3/M 输入 tokens(Claude)× 6 次/小时 × 24h = 约 $86/天(上下文满载时)。实际会话通常更短,但心跳成本随会话大小线性增长。

汇总:token 去了哪里

典型单轮(对话进行到第 15 轮时):

System prompt:         ~3,000 tokens  (固定)
Tool 定义:             ~3,000 tokens  (固定)
Skills 目录:           ~2,000 tokens  (固定)
上下文文件:            ~1,000 tokens  (固定,取决于 SOUL.md)
会话历史:             ~40,000 tokens  (持续增长)
Memory/RAG 上下文:     ~1,000 tokens  (每轮)
当前消息:                ~100 tokens
                      ─────────────
总输入:               ~50,000 tokens  每次 LLM 调用

30 轮重度工具使用后:轻松达到 100,000-150,000 tokens 每次调用

优化机会

高影响

优化方向预估节省相关文件
更早/更激进的压缩历史 tokens 的 30-50%src/agents/compaction.ts — 降低阈值或更频繁压缩
滑动窗口替代完整历史历史 tokens 的 40-60%只发送最近 N 轮 + 摘要,而非上次压缩后的全部内容
心跳上下文精简心跳成本的 90%心跳时发送最小上下文而非完整会话
工具结果截断历史 tokens 的 10-30%工具结果(特别是文件读取、网页获取)可能很大。截断历史中的旧工具结果

中等影响

优化方向预估节省相关文件
按需加载 skill 目录每次调用约 1,500-2,500 tokens只包含与当前上下文相关的 skills 而非完整目录
上下文文件摘要化每次调用约 1,000-3,000 tokens总结大型 SOUL.md / 文档而非原样包含
Tool schema 裁剪每次调用约 500-1,000 tokens只发送本轮可能需要的工具的 schema
去重工具摘要与 schema每次调用约 500 tokensSystem prompt 已有工具摘要;JSON schema 描述可能是冗余的

低影响(已优化)

已有优化源码
路径压缩(~ 替换)workspace.ts 中的 compactSkillPaths()
子 Agent prompt 精简PromptMode = "minimal" 跳过多个章节
压缩时剥离工具结果详情compaction 中的 stripToolResultDetails()

贡献者关键文件

文件控制什么
src/agents/system-prompt.tsSystem prompt 构建
src/agents/skills/workspace.tsSkills 目录注入 + 限制
src/agents/compaction.ts会话压缩逻辑 + 阈值
src/agents/defaults.tsDEFAULT_CONTEXT_TOKENS = 200_000
src/agents/pi-settings.tsreserveTokensFloor 解析
src/agents/tool-policy-pipeline.tsLLM 调用前的工具过滤
src/memory/manager.ts记忆上下文注入
src/cron/service/timer.ts心跳频率和 payload

我的认知盲区

  • Tool JSON schema 的精确 token 数 — 需要序列化后测量
  • Memory 注入限制(maxChunks、token 上限)— 需要更深入阅读 manager-search.ts
  • 是否在使用 prompt caching(Anthropic)— 这会大幅降低固定 system prompt 的实际成本
  • Pi Agent SDK 中 estimateTokens() 的实现方式 — 提到了 chars/4 启发式但精度未知
  • 跨轮次是否有重复工具调用的批处理或缓存