文章目录


你这个直觉很对:这段 agent loop 确实简洁、优雅,而且是值得学的核心模式。但要分清楚:

这不是“所有模型天然都这样返回”。

你现在能用 GLM4.7 跑通,主要原因大概率是:

from anthropic import Anthropic

client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))

也就是说,你还在用 Anthropic SDK,只是把 base_url 换成了 GLM 或某个兼容服务的地址。这个服务返回的数据被 Anthropic SDK 解析成了 Anthropic 风格的对象,所以你才能写:

response.stop_reason
response.content
block.type
block.input
block.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.type
block.input
block.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

真正要学的是这个核心结构:

while True:
    response = call_model(messages, tools)

    if model_wants_tool(response):
        tool_calls = parse_tool_calls(response)
        results = execute_tools(tool_calls)
        messages.append(tool_results(results))
    else:
        return final_answer(response)

你看到的这份代码牛逼的地方就在这里:它把 agent 的本质压缩到了一个循环里。

但如果以后你想写得更稳,最好抽一层 adapter:

def get_tool_calls(response):
    calls = []
    for block in response.content:
        if getattr(block, "type", None) == "tool_use":
            calls.append(block)
    return calls

这样至少不会因为某个 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,直接请求模型服务,大概是这样:

import requests

resp = requests.post(
    "https://api.anthropic.com/v1/messages",
    headers={...},
    json={...}
)
data = resp.json()

这时候你拿到的是普通 JSON / dict:

data["content"][0]["type"]

但用了 Anthropic SDK:

from anthropic import Anthropic

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.type
block.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 示例:

import anthropic

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 版本大概会变成:

def agent_loop(messages: list):
    while True:
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )

        message = response.choices[0].message
        messages.append(message)

        if not message.tool_calls:
            return

        for tool_call in message.tool_calls:
            name = tool_call.function.name
            args = json.loads(tool_call.function.arguments)

            if name == "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

现在最常见的几种格式

  1. OpenAI-compatible API

这是目前最通用、覆盖最广的格式。很多厂商都会说“兼容 OpenAI SDK,只需要改 api_key 和 base_url”。

典型形态:

from openai import OpenAI

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_urlGemini 也提供 OpenAI compatibility endpoint,DeepSeek 官方文档也说明可以通过改配置用 OpenAI/Anthropic SDK 访问。

所以如果问“常用的是哪一个”:OpenAI-compatible 是现在最通用的事实标准。

  1. Anthropic Messages API

这是你现在这 12 课用的格式。它在 coding agent 场景里特别重要,因为 Claude Code、Claude 的 tool use 设计、很多 agent 教程都围绕它。

典型形态:

from anthropic import Anthropic

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 生态特别关键
  1. 厂商原生 SDK/API

比如 Google Gemini 有自己的 google-genai SDK,Mistral 有自己的 SDK,Cohere、百度、火山、阿里云也都有自己的原生 API。
这些原生 API 往往能更完整支持自家特色能力,比如多模态、缓存、长上下文、thinking、文件、检索、批处理等。

但原生 API 的缺点也明显:换模型时适配成本更高。

  1. 统一封装层

这类不是模型厂原生协议,而是上层框架:

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。第一课别急着“学完”,你现在已经把地基下面的钢筋看见了。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐