资讯详情

资讯详情

Claude Code学习--从搭建Nano Claude Code学习CC机制的底层原理

1. 为什么要自己搭一个 Nano Claude CodeClaude Code 这类编码智能体表面看是“聊天框里让它改代码”底层其实就三件事一个不断循环的 agent loop、一组被 JSON Schema 约束的工具、以及一套在上下文快满时把历史压掉的压缩机制。你如果只是天天用它写业务很难感知到这三件事怎么咬合一旦想改行为、加工具、调压缩阈值就会卡在“不知道从哪下手”。Nano Claude Code 的价值就在这它把 Claude Code 的核心机制拆成从简到繁的最小示例每一章都能直接跑、直接改、直接打日志。我按它的目录一路调下来最大的收获不是“会写 agent”而是能亲眼看到一轮完整循环里 messages 数组是怎么被追加、tool_result 是怎么回填、压缩是在第几轮被触发的。这种可观测性比读十篇概念文章都管用。这篇面向的是想读懂 CC 底层原理的开发者你最好写过 Python、用过命令行对 LLM 的 messages 结构有基本概念。全文会给出可复制的目录结构、关键模块配置、运行验证步骤并说明怎么通过 TaoToken 统一 Key/API 通道把模型接进来在本地跑通并做对照实验。读完你应该能独立观察一轮 agent 循环的输入输出并定位到上下文压缩的触发点。先说清楚 Nano Claude Code 是什么它是一个教学性质的最小实现不是生产级框架。它复刻的是 Claude Code 的机制骨架——agent loop、工具调用、todo 计划、子 agent、skill 加载、上下文压缩。适合谁适合想从“会用”走到“会改”的人。不适合谁不适合想直接拿它上生产的人它的工具沙箱、错误处理都做了简化安全性要你自己补。我试过把它的每一章都跑一遍再对照日志读代码发现最容易劝退新手的不是 agent loop而是环境准备和模型接入这两步。所以下面先把接入通道讲清楚再进配置和验证。2. TaoToken 前置统一 Key 与 API 通道怎么准备Nano Claude Code 默认走 Anthropic 风格的 messages 接口所以你需要一个能提供该接口的通道。TaoToken 在这里的作用是统一 Key 和 API 通道你不用为每个实验单独配一套凭证一个 Key 就能覆盖模型对话、编码计划等场景切换模型时只改 Model ID 即可。先注册并拿到 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 完成账号注册后进入控制台。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在里面可以创建和管理 API Key。创建完记得立刻复制保存Key 一般只完整显示一次。拿到 Key 之后去 API Keys 页面确认它的状态和额度https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。这一步别跳过我踩过的坑就是 Key 建好了但没注意额度跑了几轮循环就报 401排查半天以为是代码问题。接口基地址用 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 Base URL 填进配置。模型对话的入口在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 你可以在那里先手动发一条消息确认 Key 和通道是通的再去跑代码。这个“先手动验证再写代码”的顺序能省掉大量排障时间。如果你后面要做长期编码或 Agent 实验可以了解下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数不确定时以文档为准。这里要强调一点TaoToken 是合规的 API 通道不是让你绕过任何限制的工具。你只需要把它当成一个标准的模型服务入口配置方式和接任何官方 SDK 一样。环境变量建议这样组织避免 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODELclaude-sonnet-4-20250514把这三件套Base URL、Key、Model ID固定下来后面所有章节的代码都从环境变量读切换模型时只改TAOTOKEN_MODEL。这也是我建议的对照实验基础同一份代码换 Model ID 就能对比不同模型在 agent loop 里的行为差异。3. 可复制配置目录结构与关键模块这一节给你可以直接抄的目录结构和配置片段。Nano Claude Code 的组织方式是“每章一个可运行脚本 共享的工具模块”我按这个思路整理成下面这棵树nano-claude-code/ ├── .env # 本地环境变量不要提交 ├── requirements.txt ├── config.py # 读取环境变量集中管理 ├── client.py # 封装 messages 调用 ├── tools/ │ ├── __init__.py │ ├── bash.py # bash 工具 沙箱约束 │ ├── file_ops.py # read_file / edit_file │ ├── todo.py # todo 工具 TodoManager │ └── task.py # 子 agent 委托 ├── skills/ │ └── SKILL.md # YAML frontmatter 正文 ├── chapters/ │ ├── 01_agent_loop.py │ ├── 02_tools.py │ ├── 03_todo.py │ ├── 04_subagent.py │ ├── 05_skills.py │ └── 06_compact.py └── transcripts/ # 压缩时落盘的完整历史config.py负责把三件套读进来这是所有章节的公共入口import os API_KEY os.environ[TAOTOKEN_API_KEY] BASE_URL os.environ[TAOTOKEN_BASE_URL] MODEL os.environ.get(TAOTOKEN_MODEL, claude-sonnet-4-20250514) # 压缩相关阈值 KEEP_RECENT 3 # micro_compact 保留最近几条 tool_result TOKEN_THRESHOLD 50000 # auto_compact 触发阈值 MAX_TODO_ITEMS 20client.py把调用封装起来注意 Base URL 的用法from anthropic import Anthropic from config import API_KEY, BASE_URL, MODEL client Anthropic(api_keyAPI_KEY, base_urlBASE_URL) def call_model(messages, toolsNone, systemNone, max_tokens2000): kwargs {model: MODEL, messages: messages, max_tokens: max_tokens} if tools: kwargs[tools] tools if system: kwargs[system] system return client.messages.create(**kwargs)工具定义的关键是input_schema它用 JSON Schema 子集约束模型输入。以 bash 和 edit_file 为例BASH_TOOL { name: bash, description: 执行一条 shell 命令并返回输出, input_schema: { type: object, properties: {command: {type: string}}, required: [command], }, } EDIT_FILE_TOOL { name: edit_file, description: 把文件中的 old_text 替换为 new_text, input_schema: { type: object, properties: { path: {type: string}, old_text: {type: string}, new_text: {type: string}, }, required: [path, old_text, new_text], }, }模型如果少传字段、类型不对或者传了 schema 以外的数据都会在执行前被拦截。这消除了一整类错误模型无法传递格式错误的输入因为 API 会在执行前校验 schema。这也让模型意图变得明确——当它用特定字符串调用edit_file时不存在“想改哪里”的解析歧义。如果你用 Claude Code 的 settings 文件来管理本地配置可以这样写路径和字段名保持一致{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }注意这里的三件套是 Base URL、Key、Model ID缺一不可。很多人只填了 Key 就以为能跑结果报local proxy failed其实是 Base URL 没配对。4. 验证请求跑通一轮完整 agent 循环配置就绪后先跑最原始的 agent loop确认通道和循环都正常。chapters/01_agent_loop.py的核心逻辑是一个循环 一个 bash 工具判断模型返回里有没有tool_use有就继续循环没有就退出。from client import call_model from tools.bash import run_bash, BASH_TOOL SYSTEM 你是一个编码助手可以用 bash 工具执行命令。 def agent_loop(messages): while True: resp call_model(messages, tools[BASH_TOOL], systemSYSTEM) messages.append({role: assistant, content: resp.content}) tool_uses [b for b in resp.content if b.type tool_use] if not tool_uses: print(循环结束最终回复, resp.content[-1].text) return messages for tu in tool_uses: print(f[tool_use] {tu.name} - {tu.input}) result run_bash(tu.input[command]) print(f[tool_result] {result[:200]}) messages.append({ role: user, content: [{ type: tool_result, tool_use_id: tu.id, content: result, }], }) if __name__ __main__: msgs [{role: user, content: 列出当前目录下的文件}] agent_loop(msgs)运行python chapters/01_agent_loop.py预期你会看到类似这样的输出第一轮模型返回tool_use第二轮返回tool_result后模型给出最终文本[tool_use] bash - {command: ls -la} [tool_result] total 24 drwxr-xr-x 5 user staff 160 ... 循环结束最终回复当前目录下有 config.py、client.py、tools 等文件...这里有个关键观察点第二次循环时messages 里会出现类型为tool_result的 content。这个结构在后面的压缩章节会反复出现因为 micro_compact 替换的就是它。你可以在循环里加一行print(len(messages))看着数组一轮轮变长就能直观理解“上下文是怎么被填满的”。接着跑 todo 章节验证计划驱动。TodoManager是 todo 工具背后的状态机做三层事校验最多 20 条、text 不能为空、status 必须在枚举里、同一时刻最多 1 条 in_progress、归一化清洗后覆盖写入self.items、可视化把状态映射成[ ]/[]/[x]并追加(done/total)统计。class TodoManager: def __init__(self): self.items [] def update(self, items): assert len(items) 20, 最多 20 条 in_progress 0 for it in items: assert it[text], text 不能为空 assert it[status] in (pending, in_progress, completed) if it[status] in_progress: in_progress 1 assert in_progress 1, 同一时刻最多 1 条 in_progress self.items items def render(self): mark {pending: [ ], in_progress: [], completed: [x]} done sum(1 for it in self.items if it[status] completed) lines [f{mark[it[status]]} {it[text]} for it in self.items] return \n.join(lines) f\n({done}/{len(self.items)})在 agent_loop 里加一个“催更机制”如果连续若干轮没有调用 todo就自动往下一轮结果里插入Update your todos.强制模型回到计划驱动。调试时我把rounds_since_todo改成1就触发这样能快速看到效果。rounds_since_todo 0 # 循环内 if used_todo: rounds_since_todo 0 else: rounds_since_todo 1 if rounds_since_todo 1: messages.append({role: user, content: Update your todos.})跑通后你会看到模型先提交一份 items 数组然后每轮更新状态。todo 清单有三个好处用户能在执行前看到 agent 打算做什么开发者能通过检查计划状态调试行为agent 自身能在后续轮次引用计划即使早期上下文已经滚出窗口。5. 本篇常见错排查这一节对照真实报错来。第一个高频错误是 401通常出现在 Key 没读到或额度不足anthropic.AuthenticationError: Error code: 401 - {error: {message: invalid api key}}排查顺序先确认echo $TAOTOKEN_API_KEY有值再确认 Key 没被复制时带空格最后去 API Keys 页面看额度。我遇到过一次是.env没被加载代码读的是空字符串。第二个是local proxy failed这个多半是 Base URL 配错。检查TAOTOKEN_BASE_URL是不是https://taotoken.net/api注意不要多加/v1或结尾斜杠。如果你用 settings 文件确认字段名是ANTHROPIC_BASE_URL写错成别的名字不会生效。第三个是reading choices类报错这通常说明你用了 OpenAI 风格的响应解析但通道返回的是 Anthropic 风格。Nano Claude Code 走的是 messages 接口响应里是content数组而不是choices。检查你的解析代码# 错误按 OpenAI 解析 # text resp.choices[0].message.content # 正确按 Anthropic messages 解析 text resp.content[-1].text第四个是 OAuth 相关报错如果你之前配过 Claude Code 的登录态可能会和 API Key 冲突。解决办法是清掉本地 OAuth 缓存只用 Key 认证。这类报错信息里一般带oauth字样看到就检查是不是混用了两种认证方式。第五个是压缩没触发。如果你跑长任务发现上下文一直涨检查TOKEN_THRESHOLD是不是设太高以及estimate_tokens有没有被正确调用。micro_compact 是每轮都跑的auto_compact 才看阈值。可以在agent_loop里打印estimate_tokens(messages)观察它什么时候越过 50000。第六个是子 agent 递归套娃。CHILD_TOOLS故意不包含task如果你不小心把task也传给了子 agent会出现无限递归。检查run_subagent里用的是CHILD_TOOLS而不是PARENT_TOOLS。排障时记住三件套的检查顺序Base URL、Key、Model ID。任何一个不对都会报错而且报错信息不一定直指根因。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 参数不确定时以它为准。6. 继续深入压缩机制与后续实验压缩是 Nano Claude Code 里最值得反复读的一章。问题很直接上下文窗口有限读一个 1000 行的文件就吃掉约 4000 token读 30 个文件、跑 20 条命令轻松突破 100k token。不压缩智能体根本没法在大项目里干活。三层压缩激进程度递增。第一层 micro_compact 每轮静默执行把超过 3 轮的旧 tool_result 替换成占位符def micro_compact(messages): tool_results [] for i, msg in enumerate(messages): if msg[role] user and isinstance(msg.get(content), list): for j, part in enumerate(msg[content]): if isinstance(part, dict) and part.get(type) tool_result: tool_results.append((i, j, part)) if len(tool_results) KEEP_RECENT: return messages for _, _, part in tool_results[:-KEEP_RECENT]: if len(part.get(content, )) 100: part[content] f[Previous: used {part.get(tool_name, tool)}] return messages第二层 auto_compact 在 token 超过阈值时触发先把完整对话落盘到transcripts/再让 LLM 做摘要用一条[Compressed]消息替换全部历史def auto_compact(messages): path TRANSCRIPT_DIR / ftranscript_{int(time.time())}.jsonl with open(path, w) as f: for msg in messages: f.write(json.dumps(msg, defaultstr) \n) resp call_model( [{role: user, content: Summarize this conversation for continuity... json.dumps(messages, defaultstr)[:80000]}], max_tokens2000, ) return [ {role: user, content: f[Compressed]\n\n{resp.content[0].text}}, {role: assistant, content: Understood. Continuing.}, ]第三层是 manual compact模型主动调用compact工具触发同样的摘要机制。循环里三层整合def agent_loop(messages): while True: micro_compact(messages) # Layer 1 if estimate_tokens(messages) TOKEN_THRESHOLD: messages[:] auto_compact(messages) # Layer 2 resp call_model(messages, toolsTOOLS) # ... 工具执行 ... if manual_compact: messages[:] auto_compact(messages) # Layer 3关键认知是完整历史通过 transcript 保存在磁盘上信息没有真正丢失只是移出了活跃上下文。你可以打开transcripts/里的 jsonl 文件对照压缩前后的 messages看哪些 tool_result 被替换、摘要保留了哪些信息。这就是“观察压缩触发点”的具体做法。后续实验建议把TOKEN_THRESHOLD调小到 5000跑一个多步任务观察 auto_compact 在第几轮触发再对比不同 Model ID 在同样任务下的压缩频率。长期做编码或 Agent 实验的话Coding Plan 能提供更稳定的额度https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。想先手动验证模型行为去模型对话页发几条消息最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。最后留一个我调试时的小技巧在agent_loop每轮开头打印len(messages)和estimate_tokens(messages)再在 micro_compact 和 auto_compact 里各加一行日志。这样一轮长任务跑下来你能拿到一张完整的“上下文增长与压缩”时间线比任何架构图都直观。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →