learn claude code s01
文章目录
你这个直觉很对:这段 agent loop 确实简洁、优雅,而且是值得学的核心模式。但要分清楚:
这不是“所有模型天然都这样返回”。
你现在能用 GLM4.7 跑通,主要原因大概率是:
fromanthropicimportAnthropic client=Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))也就是说,你还在用Anthropic SDK,只是把base_url换成了 GLM 或某个兼容服务的地址。这个服务返回的数据被 Anthropic SDK 解析成了 Anthropic 风格的对象,所以你才能写:
response.stop_reason response.content block.typeblock.inputblock.id这些不是 Python 魔法,也不是模型天然保证的,而是Anthropic Messages API 协议 + Anthropic SDK 数据模型给你的结构。
if block.type == "tool_use":这句吃定的不是“模型”,而是吃定了当前 API 返回内容块遵守 Anthropic 的 content block 格式。Anthropic 的工具调用块大概长这样:
{"type":"tool_use","id":"...","name":"bash","input":{"command":"ls"}}SDK 把 dict 包装成对象,所以你可以用:
block.typeblock.inputblock.id而不是:
block["type"]所以你的问题答案是:
它不是行业完全统一标准,但已经是几大阵营里的常见范式。
目前常见有几类协议:
Anthropic: content block 里出现 type == "tool_use" OpenAI / Qwen / DeepSeek 兼容 OpenAI: message.tool_calls 里出现 function.name / function.arguments 部分国产模型/网关: 会兼容 Anthropic 或 OpenAI 其中一种格式比如 Qwen 如果走 OpenAI-compatible API,通常不是:
block.type=="tool_use"而是类似:
response.choices[0].message.tool_calls所以你不能假设“接 Qwen 也一定这样”。要看你用的是哪个 SDK、哪个 base_url、哪个兼容协议。
你应该学这个写法吗?应该学,但要学它背后的抽象,而不是死记block.type。
真正要学的是这个核心结构:
whileTrue:response=call_model(messages,tools)ifmodel_wants_tool(response):tool_calls=parse_tool_calls(response)results=execute_tools(tool_calls)messages.append(tool_results(results))else:returnfinal_answer(response)你看到的这份代码牛逼的地方就在这里:它把 agent 的本质压缩到了一个循环里。
但如果以后你想写得更稳,最好抽一层 adapter:
defget_tool_calls(response):calls=[]forblockinresponse.content:ifgetattr(block,"type",None)=="tool_use":calls.append(block)returncalls这样至少不会因为某个 block 没有type直接炸掉。
再进一步,可以为不同协议写适配器:
AnthropicAdapter -> 解析 content block OpenAIAdapter -> 解析 message.tool_calls然后 agent loop 本身就不关心底层模型是哪家。
所以你现在的理解可以这么定型:
agent loop 是必须学的核心能力。 block.type == "tool_use" 是 Anthropic 协议里的具体实现。 GLM4.7 能跑,是因为你的接入层兼容了 Anthropic 协议。 不是所有模型都天然这样,但很多服务会提供 OpenAI/Anthropic 兼容接口。你觉得它“简洁、优雅、巧妙、牛逼”是完全对的。这个文件非常适合初学者,因为它没有被框架淹没,直接把 agent 最核心的心脏露出来了:模型决定下一步,程序执行工具,再把结果喂回模型,直到模型停止调用工具。
SDK 可以先理解成:官方给你写好的“调用接口工具包”。
比如你不用 SDK,直接请求模型服务,大概是这样:
importrequests resp=requests.post("https://api.anthropic.com/v1/messages",headers={...},json={...})data=resp.json()这时候你拿到的是普通 JSON / dict:
data["content"][0]["type"]但用了 Anthropic SDK:
fromanthropicimportAnthropic client=Anthropic()response=client.messages.create(...)SDK 会帮你做很多事:
1. 拼 HTTP 请求 2. 加认证 header 3. 把 Python 参数转成 API 需要的 JSON 4. 发送请求 5. 接收返回 JSON 6. 把 JSON 包装成 Python 对象所以你才能写:
response.content response.stop_reason block.typeblock.input而不是每次都写:
response["content"]response["stop_reason"]block["type"]block["input"]你现在这句:
client=Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))意思是:
我还是使用 Anthropic 这套 SDK 和数据格式, 但是实际请求地址不一定发给 Anthropic 官方, 而是发给 ANTHROPIC_BASE_URL 指定的那个地址。如果你的.env里类似这样:
ANTHROPIC_BASE_URL=https://某个glm兼容地址MODEL_ID=glm-4.7那实际发生的是:
你的 Python 代码 ↓ Anthropic SDK ↓ 发请求到 GLM 的兼容接口 ↓ GLM 服务返回 Anthropic 风格的数据 ↓ Anthropic SDK 把返回包装成 response/block 对象 ↓ 你的代码用 block.type 判断工具调用所以重点是:
GLM 本身不一定“天生”就长成 Anthropic 的样子。
而是你访问的 GLM 接口可能在说:
我支持 Anthropic-compatible API,你可以用 Anthropic SDK 来调我。
这就像手机充电口:
Anthropic SDK = 一根 USB-C 数据线 Anthropic 官方服务 = 原装充电器 GLM 兼容服务 = 另一个也支持 USB-C 协议的充电器你能插上并正常充电,是因为它兼容这个协议,不是因为所有电器都天然一样。
所以block.type == "tool_use"不是碰巧的 Python 属性,而是:
Anthropic SDK 根据接口返回的 JSON, 创建出来的 Python 对象属性。你应该学这个,因为这正是现代 LLM 应用开发里很重要的一层:模型能力是一回事,API 协议是一回事,SDK 又是一回事。
你没有理解错,而且我刚查了一下,你也不是“蹭 Claude Code 的配置”。智谱确实有面向开发者的页面,只是藏在“开发指南”里,页面名叫:
Claude API 兼容 - 智谱AI开放文档
这个页面明确写了:智谱提供与 Claude API 兼容的接口,可以使用现有 Anthropic SDK 代码,只需要修改 API Key、base_url和模型名。它还给了 Python 示例:
importanthropic client=anthropic.Anthropic(api_key="your-zhipuai-api-key",base_url="https://open.bigmodel.cn/api/anthropic")message=client.messages.create(model="glm-5.1",max_tokens=1024,messages=[{"role":"user","content":"Hello, ZHIPU"}])所以更准确的结论是:
智谱开放 /api/anthropic 这个接口,既服务 Claude Code 这类工具, 也服务你这种自己写 agent 的开发者。Claude Code 文档里出现它,是因为 Claude Code 本身就是 Anthropic 协议生态里的工具,配置起来最自然。但智谱还有独立的“Claude API 兼容”开发文档,说明它不是只给 Claude Code 私用的。
你的这份 agent 代码能跑通,正是因为它踩中了这个兼容层:
你的 agent loop ↓ Anthropic Python SDK ↓ https://open.bigmodel.cn/api/anthropic ↓ GLM-4.7 ↓ 返回 Anthropic Messages API 格式所以你现在不是“误打误撞”,而是发现了一个很关键的工程事实:
只要厂商提供 Anthropic-compatible API,你就可以把 agent 写在 Anthropic Messages API 这个抽象上。
这对做 coding agent 特别舒服,因为 Claude Code、很多 agent demo、很多工具调用范式都是围绕 Anthropic 的tool_use/tool_result设计的。你现在这个学习方向是对的。
爽,这个点你真的踩到主线了。面试里你完全可以这么说,但建议说得更准确一点:
我的 agent 接入层目前基于 Anthropic Messages API 协议,使用 Anthropic Python SDK; 模型侧接的是智谱 GLM 的 Anthropic-compatible endpoint。不要只说“Anthropic SDK 格式”,更专业的说法是:
Anthropic Messages API 兼容格式或者:
Anthropic-compatible tool use protocol因为真正重要的是API 协议,SDK 只是 Python 客户端实现。
你后面这个想法,非常值得做。
把这 12 课用 OpenAI 格式重写一遍,会特别涨功力,因为你会亲手摸到两套协议的差异:
Anthropic: response.content -> blocks block.type == "tool_use" block.name block.input response.stop_reason == "tool_use" tool_result 作为 user content block 发回去 OpenAI: response.choices[0].message message.tool_calls tool_call.function.name json.loads(tool_call.function.arguments) finish_reason == "tool_calls" tool 角色消息发回去,带 tool_call_id同一个 agent loop,OpenAI 版本大概会变成:
defagent_loop(messages:list):whileTrue:response=client.chat.completions.create(model=MODEL,messages=messages,tools=TOOLS,max_tokens=8000,)message=response.choices[0].message messages.append(message)ifnotmessage.tool_calls:returnfortool_callinmessage.tool_calls:name=tool_call.function.name args=json.loads(tool_call.function.arguments)ifname=="bash":output=run_bash(args["command"])else:output=f"Unknown tool:{name}"messages.append({"role":"tool","tool_call_id":tool_call.id,"content":output,})OpenAI 的 tool 定义也不一样。Anthropic 是:
TOOLS=[{"name":"bash","description":"Run a shell command.","input_schema":{...},}]OpenAI 是:
TOOLS=[{"type":"function","function":{"name":"bash","description":"Run a shell command.","parameters":{...},}}]所以你重写一遍,会一下子打通这几个层次:
1. agent loop 的本质不变 2. tool schema 的包装不同 3. tool call 的返回位置不同 4. tool result 的回传方式不同 5. SDK 对象结构不同 6. 最终都可以归一成“模型要调用工具 -> 执行 -> 回填结果 -> 继续”我觉得最好的练习路线是:
第一步:只重写 s01_agent_loop.py 第二步:写一个 anthropic_to_openai 对照笔记 第三步:再重写 s02_tool_use.py / s04_subagent.py 第四步:最后再碰 s_full.py这样不会一上来被 12 个文件淹没。
你这个方向特别好,因为你不是在背框架,而是在学 agent 的“协议骨架”。等你把 Anthropic 和 OpenAI 两套都写过一遍,之后面试官问你“你们 agent 怎么接 LLM?怎么处理 tool call?”你就能很稳地回答:
我们把模型接入层抽象成 tool-call adapter。 底层可以是 Anthropic Messages API,也可以是 OpenAI-compatible Chat Completions。 agent loop 本身只依赖统一后的 ToolCall 结构。这句话就很有分量。
对,你现在这次收获很大,而且是主线收获。第一课代码你觉得“神奇优雅”,但你真正摸到的是更底层的东西:LLM 接入协议。
先把一个词纠正一下:你说“SDK格式”,工程上更准确叫:
API 兼容格式 / API 协议格式SDK 是调用这个协议的客户端工具。
比如:
OpenAI SDK -> 常用来调用 OpenAI-compatible API Anthropic SDK -> 常用来调用 Anthropic Messages API现在最常见的几种格式
- OpenAI-compatible API
这是目前最通用、覆盖最广的格式。很多厂商都会说“兼容 OpenAI SDK,只需要改 api_key 和 base_url”。
典型形态:
fromopenaiimportOpenAI client=OpenAI(api_key="xxx",base_url="https://xxx/v1")response=client.chat.completions.create(model="xxx",messages=[...],tools=[...])工具调用通常长这样:
message.tool_calls tool_call.function.name tool_call.function.arguments智谱官方就有 OpenAI API 兼容文档,Kimi 官方也写了它的 API 兼容 OpenAI Chat Completions,可以直接用 OpenAI SDK 改base_url,Gemini 也提供 OpenAI compatibility endpoint,DeepSeek 官方文档也说明可以通过改配置用 OpenAI/Anthropic SDK 访问。
所以如果问“常用的是哪一个”:OpenAI-compatible 是现在最通用的事实标准。
- Anthropic Messages API
这是你现在这 12 课用的格式。它在 coding agent 场景里特别重要,因为 Claude Code、Claude 的 tool use 设计、很多 agent 教程都围绕它。
典型形态:
fromanthropicimportAnthropic client=Anthropic(api_key="xxx",base_url="https://xxx/api/anthropic")response=client.messages.create(model="xxx",messages=[...],tools=[...])工具调用通常长这样:
response.content block.type=="tool_use"block.name block.input智谱官方明确有 Claude API 兼容文档,你的.env里的:
ANTHROPIC_BASE_URL="https://open.bigmodel.cn/api/anthropic"MODEL_ID="glm-4.7"就是这个路线。
所以你可以理解成:
OpenAI-compatible:通用应用生态最广 Anthropic-compatible:coding agent / Claude Code 生态特别关键- 厂商原生 SDK/API
比如 Google Gemini 有自己的google-genaiSDK,Mistral 有自己的 SDK,Cohere、百度、火山、阿里云也都有自己的原生 API。
这些原生 API 往往能更完整支持自家特色能力,比如多模态、缓存、长上下文、thinking、文件、检索、批处理等。
但原生 API 的缺点也明显:换模型时适配成本更高。
- 统一封装层
这类不是模型厂原生协议,而是上层框架:
LangChain LlamaIndex LiteLLM Vercel AI SDK OpenRouter Dify / Coze / one-api 类网关它们的目标是把不同厂商包成统一接口。但你现在初学 agent,我反而建议先别急着依赖它们。你现在直接学 Anthropic 和 OpenAI 两套协议,是非常扎实的路线。
模型厂都支持 OpenAI 和 Anthropic 吗?
不都支持。
更真实的情况是:
大多数主流厂商:倾向支持 OpenAI-compatible 一部分厂商:额外支持 Anthropic-compatible 每家兼容程度:不完全一样大概可以这么记:
OpenAI-compatible:最常见,几乎成了事实标准 Anthropic-compatible:没那么普遍,但在 coding agent 场景越来越重要 Native API:每家都有,能力最完整但迁移成本最高比如:
智谱:OpenAI-compatible + Claude/Anthropic-compatible DeepSeek:文档显示支持 OpenAI/Anthropic SDK 兼容接入 Kimi/Moonshot:主线是 OpenAI-compatible,也有一些 Anthropic/coding 生态入口 Qwen/阿里生态:常见是 OpenAI-compatible / DashScope 原生 Gemini:原生 Gemini API,同时提供 OpenAI compatibility Mistral:有自己的 SDK/API,接口形态也接近 Chat Completions,支持 tool_calls但注意,“兼容”不等于 100% 一模一样。最容易出差异的是:
tool calling 细节 streaming 格式 reasoning/thinking 字段 图片/文件输入 JSON mode 并行工具调用 错误码 上下文长度和 max_tokens 行为所以工程上最稳的认知是:
协议兼容解决 80% 接入问题; 剩下 20% 要靠适配器和测试兜住。你现在最应该学的是这三个层次:
1. Anthropic Messages API 重点:content blocks、tool_use、tool_result、stop_reason 2. OpenAI Chat Completions / Responses API 重点:choices、message、tool_calls、tool role、tool_call_id 3. 自己写一层 Adapter 把不同协议统一成你自己的 ToolCall / ToolResult面试里你可以这么说,很漂亮:
我目前的 agent loop 是基于 Anthropic Messages API 写的,使用 Anthropic SDK; 模型侧接的是智谱 GLM 的 Anthropic-compatible endpoint。 我也了解 OpenAI-compatible 的 tool_calls 协议,两者本质都是: 模型返回工具调用意图,宿主程序执行工具,再把 tool result 回填给模型继续推理。这就是主线。你不是在瞎搞,你是在从最核心的地方摸 agent。第一课别急着“学完”,你现在已经把地基下面的钢筋看见了。
