news 2026/10/2 23:29:26

30行代码,就是一个完整的AI Agent——Claude Code源码精读(一):从Agent Loop到TaoToken统一Key

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
30行代码,就是一个完整的AI Agent——Claude Code源码精读(一):从Agent Loop到TaoToken统一Key

1. 从一次“模型只会说不会做”的对话说起

Claude Code 这类工具最容易被误解的地方,是大家把它当成“更聪明的代码补全”。但如果你真的去翻它的源码结构,会发现它最核心的机制朴素得有点反直觉:一个while True循环,加上一张工具调度表,不到 30 行 Python 就能把骨架搭出来。这个骨架就是 Agent Loop(智能体循环),也是所有后续能力——多 Agent 协作、上下文压缩、任务规划——真正的地基。

我先说清楚这篇要解决什么问题。你直接调 Claude 或 GPT 的 API,默认是“一问一答”:你让它“帮我列出当前目录下所有 Python 文件”,它会回你一段ls *.py或者find . -name "*.py",然后就没有然后了。它能推理出命令,但它碰不到真实世界——不能读文件、不能跑测试、看不到报错。语言模型本身是个纯文本函数,输入文本、输出文本,中间没有任何执行能力。

解法也很直接:让外部程序替它跑命令,把结果塞回去,再问它下一步。这个“外部程序”就是 Agent Loop。它的数据流是这样的:用户 prompt 进入累积的messages[],模型决定是否调用工具,工具执行结果追加回messages[],循环直到stop_reason不等于tool_use为止。关键只有两点:messages是累积的,每次对话的完整历史都在里面,模型看得到自己之前的决策;退出条件只有一个,模型主动“交号”,说自己不需要再调工具了。

这篇是 Claude Code 源码精读系列的第一篇,我会用可运行的 Python 代码还原这个最小循环,并且把模型调用的 endpoint 改到 TaoToken 统一 Key/API 通道,让你不用折腾多家厂商的 Key 就能跑通。适合谁看:写过一点 Python、调过至少一次大模型 API、想搞清楚 Agent 到底怎么“动起来”的人。读完你能得到一个能真实读写文件、执行命令的 30 行 Agent,以及一套可复制的环境变量配置。

2. TaoToken 统一 Key 与 API 通道的前置准备

在写循环之前,得先把模型调用这条链路打通。Claude Code 源码里默认走的是 Anthropic 的 Messages API,请求体长这样:model、system、messages、tools、max_tokens。我们要做的是把这个请求的 base URL 换掉,指向 TaoToken 的统一通道,这样同一个 Key 就能覆盖多家模型,切换模型时只改一个model字段。

TaoToken 在这里扮演的角色是统一入口:你拿到一个 Key,配好 Base URL,就能用 Anthropic 兼容的接口格式发请求。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加任何 UTM 参数,直接用它作为 base。

具体操作分三步。第一步,去控制台创建 API Key,入口是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,创建完把 Key 复制出来,形如sk-开头的一串。第二步,确认你要用的模型 ID,这个在模型列表或文档里能查到,比如 Claude 系列、GPT 系列的对应标识。第三步,把这两样东西写进环境变量,别硬编码在代码里。

我试过把 Key 直接写在脚本里,结果一次误提交到公开仓库,虽然及时删了但心里还是发毛。所以下面统一用环境变量。你可以在终端里临时导出,也可以写进~/.bashrc或~/.zshrc:

export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="claude-sonnet-4-20250514"

这里TAOTOKEN_MODEL填你实际要用的模型 ID,不同模型能力不同,跑 Agent Loop 建议选工具调用(tool use)支持好的。配好之后,用一行 curl 验证通道是否通:

curl -s "$TAOTOKEN_BASE_URL/v1/messages" \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "'"$TAOTOKEN_MODEL"'", "max_tokens": 64, "messages": [{"role": "user", "content": "只回复两个字:通了"}] }'

如果返回的 JSON 里有content字段且文本是“通了”,说明 Key、Base URL、模型 ID 三件套都对。这一步别跳过,后面 Agent Loop 报错时,你能快速判断是通道问题还是代码问题。顺便说一句,如果你打算长期跑编码类 Agent,可以了解下 Coding Plan,入口是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合高频调用场景;只是验证模型的话,用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 手动试几条也行。

3. 可复制的 30 行 Agent Loop 与工具配置

现在进入正题。Claude Code 的工具系统有个很值得学的设计:加工具不需要改循环。它用一张 dispatch map(调度字典)替代所有 if/else,循环里分发只有三行。我们照这个思路来。

先看完整的 Agent Loop,我把它压到 30 行左右,去掉注释后更短:

import os, json, subprocess from pathlib import Path from anthropic import Anthropic WORKDIR = Path.cwd().resolve() client = Anthropic( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) MODEL = os.environ["TAOTOKEN_MODEL"] SYSTEM = "你是一个编码助手。需要操作文件或执行命令时调用工具,完成后直接回答。" def safe_path(p: str) -> Path: path = (WORKDIR / p).resolve() if not path.is_relative_to(WORKDIR): raise ValueError(f"Path escapes workspace: {p}") return path def run_bash(command: str) -> str: r = subprocess.run(command, shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=30) return (r.stdout + r.stderr)[:4000] or "(no output)" def run_read(path: str, limit: int = 200) -> str: lines = safe_path(path).read_text(errors="replace").splitlines() return "\n".join(lines[:limit]) def run_write(path: str, content: str) -> str: safe_path(path).write_text(content) return f"wrote {len(content)} chars to {path}" TOOLS = [ {"name": "bash", "description": "执行 shell 命令", "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}}, {"name": "read_file", "description": "读取文件内容", "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}, {"name": "write_file", "description": "写入文件", "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}, ] TOOL_HANDLERS = { "bash": lambda **kw: run_bash(kw["command"]), "read_file": lambda **kw: run_read(kw["path"], kw.get("limit", 200)), "write_file": lambda **kw: run_write(kw["path"], kw["content"]), } def agent_loop(query: str): messages = [{"role": "user", "content": query}] while True: resp = client.messages.create(model=MODEL, system=SYSTEM, messages=messages, tools=TOOLS, max_tokens=4096) messages.append({"role": "assistant", "content": resp.content}) if resp.stop_reason != "tool_use": return resp.content results = [] for block in resp.content: if block.type == "tool_use": handler = TOOL_HANDLERS.get(block.name) output = handler(**block.input) if handler else f"Unknown tool: {block.name}" results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)}) messages.append({"role": "user", "content": results})

这段代码里有两个关键点值得单独拎出来。第一,messages是累积的,每次工具结果都追加进去,模型能看到自己之前读了什么、改了什么,这是它能做出一致决策的前提。第二,退出条件只有stop_reason != "tool_use",把控制权完全交给模型——它说不需要再调工具了,循环才结束,不设轮次限制。

工具层还有一个安全边界设计:safe_path()。所有文件操作都先经过它,把路径 resolve 后检查是否还在工作目录内,越界就抛错。Claude Code 的工程哲学在这里体现得很清楚:每一层只负责自己的边界,安全逻辑封装在工具层,而不是依赖 bash 命令的行为去兜底。你如果只给 bash 工具,cat截断不可预测、sed遇到特殊字符就崩,更重要的是 bash 是无边界的,没有路径限制。

如果你用的是 Cline MCP 或 Codex 这类工具,配置思路一样,都是三件套:Base URL、Key、Model ID。以 Cline 的 MCP 配置为例,JSON 片段长这样:

{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_MODEL": "claude-sonnet-4-20250514" } } } }

Codex 的auth.json则是把 base URL 和 Key 写进对应字段,模型 ID 在配置里单独指定。不管哪种,记住三件套缺一不可,少一个就会在请求阶段报错。

4. 验证一次完整对话:从提问到工具执行

代码写完了,得跑一次真实对话来验证。我准备了一个小场景:让 Agent 在当前目录创建一个hello.py,写入一段打印语句,然后运行它。这个任务会触发write_file和bash两个工具,能完整走一遍循环。

先建一个干净的工作目录,把上面的代码存成agent.py,然后执行:

mkdir -p ~/agent-demo && cd ~/agent-demo python agent.py

在脚本末尾加上调用:

if __name__ == "__main__": result = agent_loop("创建 hello.py,内容打印 Hello Agent,然后运行它并告诉我输出") for block in result: if hasattr(block, "text"): print(block.text)

预期你会看到类似这样的过程。第一轮,模型返回stop_reason="tool_use",content里有一个write_file的 tool_use block,input是{"path": "hello.py", "content": "print('Hello Agent')"}。循环分发到run_write,返回wrote 20 chars to hello.py,追加进 messages。第二轮,模型看到写入成功,返回bash的 tool_use,命令是python hello.py。run_bash执行后返回Hello Agent。第三轮,模型拿到输出,stop_reason变成end_turn,返回最终文本,循环退出。

终端最后打印出“Hello Agent”,说明整条链路通了:TaoToken 通道正常、工具调用正常、循环退出正常。你可以再试一个更复杂的任务,比如“读取当前目录所有 .py 文件,统计总行数”,它会连续调多次read_file或一次bash的wc -l,观察messages数组怎么一轮轮变长。

这里有个细节值得注意:每次工具调用的输出都会追加到messages里。10 个文件读取加 10 次命令,就是 20 条 tool_result,轻松吃掉几万 token。System prompt 的影响力会被稀释——模型还在处理任务,只是忘了任务的全貌。这就是为什么 Claude Code 后面要引入 TodoWrite 和上下文压缩,但那是下一篇的事。当前这个最小循环,已经能完成多步代码任务了。

5. 本篇常见报错与排查对照

跑不通是常态,我把几个高频报错和对应原因列出来,你对照着查。

401 Unauthorized:最常见。要么 Key 没导出到当前 shell(echo $TAOTOKEN_API_KEY看看是不是空的),要么 Key 复制时带了空格或换行。还有一种情况是 base URL 写成了带 UTM 的完整链接,导致路径拼接出错。记住 API 地址就是https://taotoken.net/api,后面不加参数。

local proxy failed / connection refused:这类报错通常出现在你本地配了某些网络工具,请求被拦到本地端口但那个端口没服务。检查环境变量里有没有HTTP_PROXY、HTTPS_PROXY之类的残留,临时unset掉再试。另外确认base_url拼出来的完整路径是https://taotoken.net/api/v1/messages,少一段或错一段都会连不上。

reading 'choices' of undefined:这个报错说明你用的 SDK 或代码在按 OpenAI 的响应格式解析(找choices字段),但实际返回的是 Anthropic 格式(content字段)。检查你用的客户端库和接口格式是否匹配。Anthropic 兼容接口返回的是content数组,不是choices。

OAuth / authentication_error:如果你之前配过 Claude Code 的 OAuth 登录,环境里可能残留了旧的认证配置,和现在的 API Key 冲突。清掉相关环境变量,确保只走x-api-key这一条认证路径。

模型返回 tool_use 但 handler 找不到:报Unknown tool: xxx。说明模型调用的工具名不在TOOL_HANDLERS字典里。检查TOOLS定义里的name和字典的 key 是否完全一致,大小写、下划线都要对上。

循环不退出:模型一直调工具,stop_reason始终是tool_use。先看工具返回内容是不是空字符串或异常信息,模型可能因为拿不到有效结果而反复重试。给run_bash加超时和输出截断(我上面设了 30 秒和 4000 字符),避免单次工具调用卡死整个循环。

排查顺序建议:先 curl 验证通道,再单独测一个工具函数,最后跑完整循环。这样能把问题定位到具体层,而不是对着一个报错瞎猜。

6. 把统一 Key 接进你的日常编码流程

到这里你已经有了一个能跑的 30 行 Agent,也知道了怎么把模型调用指向 TaoToken 统一通道。接下来最实际的一步,是把这个通道接进你日常用的工具里,而不是每次手写脚本。

如果你主要用命令行做编码,Claude Code 的接入方式是把 Base URL 和 Key 配到它的环境变量或配置文件里,模型 ID 按需切换。具体路径和字段名参考接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各客户端的配置示例。配好之后,你切换模型只需要改一个字段,不用重新申请 Key。

如果你更习惯在编辑器里用 Cline 或类似插件,前面给的 MCP JSON 片段直接改 Key 和模型 ID 就能用。长期跑 Agent 类任务、调用频率高的,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,比按量计费更适合持续开发场景。只是想快速验证某个模型效果的,用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 手动发几条消息最省事。

Key 的管理入口统一在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,建议给不同项目建不同的 Key,方便追踪用量和随时吊销。我自己的习惯是本地开发一个 Key、CI 环境一个 Key,互不影响。

最后留个动手练习:把上面的agent_loop加一个edit_file工具,实现“找到文件里的某段文本并替换”。加工具只需要在TOOLS里加一个定义、在TOOL_HANDLERS里加一行、写一个 handler 函数,循环本身一行都不用改。这就是 Claude Code 工具系统的设计精髓——循环永远不变,能力靠工具扩展。下一篇我会讲它怎么用 Subagent 和上下文压缩突破单 Agent 的瓶颈,那是从“能跑”到“能跑大任务”的关键一跃。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 23:29:11

EMC设计实战:从共模电流路径到辐射整改与RJ45防护

做硬件这些年,我越来越觉得EMC是个"平时没人管,测试时教你做人"的科目。前阵子帮朋友救火,一块工控板做辐射发射测试,120MHz附近超标6个dB,换了好几种滤波方案都没用,最后发现是机箱出线孔和屏蔽…

作者头像 李华
网站建设 2026/10/2 23:27:05

HMC575LP有源倍频器工程应用:本振扩展、杂散抑制与设计要点

1. 从一颗小芯片说起:为什么倍频器在射频链路里这么重要做射频收发系统的人,几乎都绕不开频率合成这个话题。很多场景下,我们需要把本振信号从低频端搬到高频端,但又不想用一个额外的高频振荡器——成本高、相位噪声难控、电路面积…

作者头像 李华
网站建设 2026/10/2 23:26:47

金额存储选型:Long还是BigDecimal?精度、单位与工程实践全解析

这个题目我太有发言权了。老读者都知道,我过去几年一直在做交易结算类的系统,几乎每个迭代都要跟金额打交道。组里新来的同事几乎都问过同一个问题:金额到底用Long还是BigDecimal?面试的时候我也常拿这个当考点,十个人…

作者头像 李华