ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零手写 ClaudeCode:learn-claude-code 项目实战笔记(6)Context Compact 上下文压缩与 TaoToken 配置实战

从零手写 ClaudeCode:learn-claude-code 项目实战笔记(6)Context Compact 上下文压缩与 TaoToken 配置实战 1. 为什么你的 Agent 跑着跑着就“失忆”了如果你正在跟着 learn-claude-code 这个项目从零手写 ClaudeCode大概率会在 s05 之后遇到一个很现实的问题智能体刚开始还挺聪明读几个文件、跑几条命令之后回答开始变慢、变糊甚至把前面确认过的结论忘得一干二净。这不是模型变笨了而是上下文窗口被塞满了。Context Compact上下文压缩就是 learn-claude-code 第六章要解决的核心问题。简单说它是一套让 AI Agent 在长会话里“腾地方”的机制把旧的工具调用结果替换成占位符、把超长的对话历史摘要成一段话、再让模型自己决定什么时候主动压缩。适合谁适合所有想让自己的 Agent 连续工作几十分钟甚至几小时、而不是聊三轮就崩的开发者。这一篇我会把 s06 的三层压缩策略拆开讲清楚同时把 ClaudeCode 的 settings.json 配置骨架搭起来用 TaoToken 作为统一的 Key/API 通道让你在本地真正跑通压缩流程并且能观察到 token 消耗的变化。整套流程我实测下来是可以直接复制的配置片段和验证命令都会给全。2. 先搞懂 Context Compact 到底在压什么2.1 上下文膨胀的真实来源很多人以为 token 是被“对话”吃掉的其实真正的大头是工具调用结果。你让 Agent 读一个 1000 行的 Python 文件差不多就是 4000 token读 30 个文件、跑 20 条 bash 命令轻松突破 100k token。而 Claude 这类模型的上下文窗口是有限的一旦接近上限性能会急剧下降关键信息开始丢失最后直接报错或者胡言乱语。在 s05 及之前的版本里Agent 用的是最简单的消息累积模式每次工具调用的结果都完整塞进 messages 列表历史只增不减。这种模式在短任务里没问题但一旦进入大项目基本没法干活。2.2 三层压缩策略的分工learn-claude-code 的 s06 给出了三层压缩激进程度递增层级名称触发时机压缩动作特点Layer 1micro_compact每轮 LLM 调用前旧 tool_result 替换为[Previous: used {tool_name}]轻量、无感知Layer 2auto_compacttoken 估算 50000存 transcript 到磁盘LLM 摘要替换全部消息自动、保命Layer 3compact tool模型主动调用同 auto_compact 的摘要机制模型自主控制第一层是“静默清理”保留最近 3 次工具调用的完整结果更早的替换成占位符。第二层是“紧急刹车”当估算 token 超过阈值时把完整对话持久化到.transcripts/目录然后让 LLM 生成结构化摘要用两条消息替换掉整个历史。第三层是“主动瘦身”模型自己意识到需要重置上下文时调用 compact 工具触发同样的摘要流程。关键点在于完整历史并没有真正丢失它被保存在磁盘上的 transcript 文件里只是移出了活跃上下文。这样既保证了任务连续性又极大降低了 token 消耗。3. 用 TaoToken 搭好 ClaudeCode 的配置骨架3.1 为什么需要统一 Key/API 通道learn-claude-code 的代码里用的是 Anthropic SDK通过ANTHROPIC_BASE_URL和MODEL_ID来指定模型。如果你直接对接官方需要处理 Key 管理、额度、多模型切换这些琐事。用 TaoToken 的好处是一个 Key 走通所有模型调用base_url 统一配置一次就能在 s06 到 s12 之间无缝切换。TaoToken 的 API 地址是https://taotoken.net/api官网是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。你需要在控制台创建一个 API Key然后把它写进环境变量或 settings.json。3.2 settings.json 配置骨架ClaudeCode 的配置通常放在项目根目录或用户目录下的settings.json。下面是我实测可用的骨架把 base_url、model、token 阈值、transcript 目录都集中管理{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-your-taotoken-key, MODEL_ID: claude-sonnet-4-20250514 }, context_compact: { threshold: 50000, keep_recent: 3, transcript_dir: .transcripts, summary_max_tokens: 2000 }, tools: { compact: { enabled: true, description: Trigger manual conversation compression. } } }这里有几个参数需要解释。threshold是 auto_compact 的触发阈值默认 50000你可以根据模型窗口大小调整。keep_recent是 micro_compact 保留的最近工具结果数量默认 3。transcript_dir是完整历史落盘的位置建议放在项目根目录下并加入.gitignore。summary_max_tokens控制摘要的长度2000 足够保留关键信息。如果你不想用 settings.json也可以直接在.env里写ANTHROPIC_BASE_URLhttps://taotoken.net/api ANTHROPIC_AUTH_TOKENsk-your-taotoken-key MODEL_IDclaude-sonnet-4-20250514代码里用load_dotenv(overrideTrue)加载然后client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL))就能走通。注意 s06 的代码里有一行os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)这是为了避免 SDK 自动读取环境变量导致冲突实际使用时保留即可。3.3 把配置接进 s06 的 agent_loops06 的agent_loop已经把三层压缩串起来了你只需要确保配置能读到import json from pathlib import Path CONFIG json.loads(Path(settings.json).read_text()) THRESHOLD CONFIG[context_compact][threshold] KEEP_RECENT CONFIG[context_compact][keep_recent] TRANSCRIPT_DIR Path(CONFIG[context_compact][transcript_dir])这样阈值和保留数量就不用硬编码在代码里改配置就能调行为。对于长期编码和 Agent 场景如果你打算把 s06 到 s12 都跑一遍建议直接用 TaoToken 的 Coding Plan额度和模型切换会更省心入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。4. 可复制的压缩模块实现与触发验证4.1 micro_compact 的四阶段实现micro_compact 的核心逻辑是扫描所有 tool_result保留最近 KEEP_RECENT 个更早的替换成占位符。代码分四个阶段def micro_compact(messages: list) - list: # 第一阶段收集所有工具结果 tool_results [] for msg_idx, msg in enumerate(messages): if msg[role] user and isinstance(msg.get(content), list): for part_idx, part in enumerate(msg[content]): if isinstance(part, dict) and part.get(type) tool_result: tool_results.append((msg_idx, part_idx, part)) # 第二阶段早期退出 if len(tool_results) KEEP_RECENT: return messages # 第三阶段构建工具名称映射 tool_name_map {} for msg in messages: if msg[role] assistant: content msg.get(content, []) if isinstance(content, list): for block in content: if hasattr(block, type) and block.type tool_use: tool_name_map[block.id] block.name # 第四阶段执行替换 to_clear tool_results[:-KEEP_RECENT] for _, _, result in to_clear: if isinstance(result.get(content), str) and len(result[content]) 100: tool_id result.get(tool_use_id, ) tool_name tool_name_map.get(tool_id, unknown) result[content] f[Previous: used {tool_name}] return messages这里有个细节只有内容长度超过 100 字符的结果才会被替换短结果保留原样避免把有用的短输出也清掉。工具名称映射是为了让占位符里能显示具体用了哪个工具保持语义连贯。4.2 auto_compact 的落盘与摘要auto_compact 做三件事存 transcript、调 LLM 摘要、替换消息列表。def auto_compact(messages: list) - list: TRANSCRIPT_DIR.mkdir(exist_okTrue) transcript_path TRANSCRIPT_DIR / ftranscript_{int(time.time())}.jsonl with open(transcript_path, w) as f: for msg in messages: f.write(json.dumps(msg, defaultstr) \n) print(f[transcript saved: {transcript_path}]) conversation_text json.dumps(messages, defaultstr)[:80000] response client.messages.create( modelMODEL, messages[{role: user, content: Summarize this conversation for continuity. Include: 1) What was accomplished, 2) Current state, 3) Key decisions made. Be concise but preserve critical details.\n\n conversation_text}], max_tokens2000, ) summary response.content[0].text return [ {role: user, content: f[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}}, {role: assistant, content: Understood. I have the context from the summary. Continuing.}, ]摘要 prompt 明确要求包含三个维度已完成工作、当前状态、关键决策。这样压缩后的上下文虽然短但任务连续性不会断。transcript 文件路径也保留在摘要消息里方便追溯。4.3 触发验证观察 token 消耗变化跑起来之后你可以用下面这组 prompt 验证三层压缩是否生效cd learn-claude-code python agents/s06_context_compact.py然后在交互界面里输入Read every Python file in the agents/ directory one by one你会看到 micro_compact 开始工作旧的 tool_result 被替换成[Previous: used read_file]。继续输入Keep reading files until compression triggers automatically当估算 token 超过 50000 时控制台会打印[auto_compact triggered]和[transcript saved: .transcripts/transcript_xxx.jsonl]。这时候你去.transcripts/目录下能看到完整的 JSONL 历史文件。最后输入Use the compact tool to manually compress the conversation模型会主动调用 compact 工具控制台打印[manual compact]然后走一遍 auto_compact 的摘要流程。如果你想更直观地看 token 变化可以在estimate_tokens里加一行打印def estimate_tokens(messages: list) - int: tokens len(str(messages)) // 4 print(f[token estimate: {tokens}]) return tokens这样每轮调用前都能看到当前估算值压缩前后对比非常明显。我试过连续读 20 个文件压缩前估算值冲到 6 万多auto_compact 触发后直接降到 2000 以内。5. 本篇常见错排查5.1 transcript 目录写入失败如果你在容器或只读文件系统里跑TRANSCRIPT_DIR.mkdir(exist_okTrue)可能报权限错误。解决办法是把 transcript_dir 改到有写权限的路径比如/tmp/.transcripts或者提前手动创建目录并赋权。5.2 auto_compact 不触发最常见的原因是estimate_tokens的估算方式太粗糙。s06 用的是len(str(messages)) // 4对于中文和代码混合的内容这个比例可能偏小。如果你发现 token 已经很多但没触发可以把阈值调低到 30000或者改用更精确的 tokenizer 估算。另一个原因是 messages 结构不对。micro_compact 只处理role user且content是 list 的消息如果你的工具结果是以字符串形式塞进 user 消息的就扫不到。确保工具结果按 Anthropic 的 tool_result 格式组织。5.3 摘要后模型“失忆”如果摘要 prompt 太简略模型可能丢掉关键决策。建议在 prompt 里明确要求保留文件路径、函数名、变量名这些具体信息。另外summary_max_tokens不要设太小2000 是底线复杂任务可以调到 4000。5.4 API 调用报 401 或 base_url 错误检查ANTHROPIC_BASE_URL是否写成了https://taotoken.net/api注意不要多加斜杠或路径。Key 要放在ANTHROPIC_AUTH_TOKEN里不是ANTHROPIC_API_KEY。如果你在 s06 代码里看到os.environ.pop(ANTHROPIC_AUTH_TOKEN, None)那是为了清理环境变量冲突实际运行时确保.env里的值能被load_dotenv正确加载。5.5 compact 工具调用后没有压缩检查TOOL_HANDLERS里是否注册了 compact以及agent_loop里manual_compact标志是否在工具执行后被正确检查。s06 的逻辑是先遍历 response.content 找到 compact 工具调用标记manual_compact True然后在工具结果追加到 messages 之后再执行messages[:] auto_compact(messages)。顺序错了就不会触发。6. 把压缩流程接进你的日常开发跑通 s06 之后你手里就有了一套可复用的上下文压缩骨架。接下来可以做的事把 settings.json 里的阈值和保留数量做成可调参数针对不同任务类型切换把 transcript 文件按日期归档方便回溯在 compact 工具的 description 里加上 focus 参数让模型摘要时能指定保留重点。如果你要长期跑编码 Agent建议把 API Key 和模型配置统一走 TaoToken接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI Keys 管理在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。想先验证模型对话效果可以直接用模型对话入口https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content试几条压缩相关的 prompt。最后一句话经验压缩不是目的让 Agent 在长会话里保持“记得住关键、忘得掉冗余”才是。三层策略里micro_compact 负责日常清理auto_compact 负责保命compact tool 负责自主控制三者配合起来你的 ClaudeCode 才算真正能在大项目里干活。
返回列表