原厂 SDK 03:Claude Anthropic SDK——Messages API 结构与显式 Tool Use 状态循环

与 OpenAI 推出 Agents SDK 封装自动循环的路线不同,Anthropic 在其官方 Python SDK(anthropic)中长期坚持一种更为朴素、透明的工程哲学:不做黑盒化的运行时封装,将协议层与状态驱动的主动权完全交还给开发者。

在许多生产级复杂 Agent 架构(如 Claude Code 或开源的 Harness 体系)中,工程师往往更青睐 Anthropic 的这种设计。因为所有网络交互、消息追加与工具执行都是完全可见且可拦截的,不存在框架暗中发起的隐式请求。


安装与客户端基础

通过官方包安装:

1
pip install anthropic

初始化客户端并读取环境变量:

1
2
3
4
import os
import anthropic

client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

对于高吞吐异步服务,使用 anthropic.AsyncAnthropic(...) 即可。


Messages API 核心契约

Anthropic 的 Messages API 在数据结构上有几处非常明确的规范设计:

  1. system 提示词独立于消息列表:不将 System 提示混在 messages 数组里作为一种特殊角色,而是作为顶层命名参数传入;
  2. 严格的角色交替规则:messages 列表必须严格维持 user 与 assistant 的交替流转;
  3. content 并非纯文本,而是内容块列表(List[ContentBlock]):模型的响应中,文本、工具调用请求、思维推理过程都是平级的块对象。

基础单轮与系统提示

1
2
3
4
5
6
7
8
9
10
11
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
system="你是一名资深的分布式高可用审计员,专注于网络分区与脑裂隐患。",
messages=[
{"role": "user", "content": "etcd 在遭遇网络分区时,少数派节点集群如何保证强一致性?"}
],
)

# content 是列表,提取首个文本块
print(response.content[0].text)

显式 Tool Use 状态机驱动循环

Claude 实现工具调用的核心是 stop_reason 字段。当模型判断需要调用外部工具辅助推演时,它不会直接生成最终答复,而是返回 stop_reason == "tool_use"。

完整的驱动循环拓扑如下:

代码实现:生产级白盒工具循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import json
import anthropic

client = anthropic.Anthropic()

# 1. 声明工具的 JSON Schema
tools_schema = [
{
"name": "query_slow_queries",
"description": "查询指定数据库实例中超过阈值的慢查询日志列表",
"input_schema": {
"type": "object",
"properties": {
"instance_id": {"type": "string", "description": "数据库实例标识"},
"duration_threshold_ms": {"type": "integer", "description": "耗时阈值,毫秒"},
},
"required": ["instance_id", "duration_threshold_ms"],
},
}
]


# 2. 本地真实函数
def query_slow_queries(instance_id: str, duration_threshold_ms: int) -> str:
# 模拟日志系统返回
return json.dumps([
{
"query": "SELECT * FROM orders WHERE user_id = ? AND status = ?",
"cost_ms": 2450,
"rows_scanned": 1500000,
"index_used": None,
}
])


tools_registry = {"query_slow_queries": query_slow_queries}


# 3. 显式驱动循环
def run_database_diagnosis(user_prompt: str) -> str:
messages = [{"role": "user", "content": user_prompt}]

while True:
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=2048,
tools=tools_schema,
messages=messages,
)

# 判断模型的停止原因
if response.stop_reason == "tool_use":
# 1. 提取所有工具调用块(支持单轮内模型并行调用多个工具)
tool_use_blocks = [b for b in response.content if b.type == "tool_use"]
tool_results = []

for tub in tool_use_blocks:
print(f"[审计拦截] 模型请求执行工具: {tub.name}, 参数: {tub.input}")

# 执行本地逻辑
output_str = tools_registry[tub.name](**tub.input)

# 组装针对该 tool_use_id 的执行产物
tool_results.append({
"type": "tool_result",
"tool_use_id": tub.id, # 必须与调用块中的 ID 精确对应
"content": output_str,
})

# 2. 维持消息上下文的严格对称性
# 首先追加模型给出的 assistant 响应(包含 tool_use 声明)
messages.append({"role": "assistant", "content": response.content})
# 紧接着追加包含所有执行结果的 user 消息
messages.append({"role": "user", "content": tool_results})

elif response.stop_reason == "end_turn":
# 模型已收敛并给出最终结论
for block in response.content:
if hasattr(block, "text"):
return block.text
return "无文本产出"
else:
raise RuntimeError(f"异常停止状态: {response.stop_reason}")


# 执行调用
conclusion = run_database_diagnosis(
"请诊断 db-orders-01 实例,找出耗时超过 2000ms 的慢查询并给出优化方案。"
)
print(f"\n================ 诊断建议 ================\n{conclusion}")

Extended Thinking:扩展思考与推理预算

从 Claude 3.7 开始,Anthropic 引入了原生的 Extended Thinking(扩展思考) 机制。模型在生成最终内容或决定调用工具之前,会先在独立的 thinking 内容块中展开深度推演。

开发者可以通过 budget_tokens 显式控制思考预算,权衡推理深度与 Token 成本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
response = client.messages.create(
model="claude-3-7-sonnet-20250219",
max_tokens=16000,
thinking={
"type": "enabled",
"budget_tokens": 4000, # 允许模型思考最多消耗 4000 tokens
},
messages=[{
"role": "user",
"content": "我们需要为千万级日活的交易网关设计限流降级方案,对比令牌桶、滑动窗口与分布式 Redis 方案的利弊并给出具体架构。",
}],
)

# 响应解析:思考内容与最终回答天然隔离
for block in response.content:
if block.type == "thinking":
print(f"--- 内部推演过程(前 300 字)---\n{block.thinking[:300]}...\n")
elif block.type == "text":
print(f"--- 最终架构报告 ---\n{block.text}")

这种将推演过程独立建模的做法,使得在生产排错时可以清晰复盘模型“为什么做这个决策”,而无需在 Prompt 里手写“Let’s think step by step”。


为什么企业生产架构青睐显式工具循环?

相比于全自动框架,手动编写 Tool Use 循环为高要求工程系统带来了三项核心保障:

  1. 确定的权限拦截与准入审计:在 tool_registry[tub.name](**tub.input) 执行之前,可以无缝插入本地校验、风控过滤或向前端推送“用户确认授权”弹窗,没有框架层的隐式旁路;
  2. 故障隔离与异常回填:如果本地工具执行崩溃(如数据库连接超时),开发者可以捕获异常并构造 {"type": "tool_result", "is_error": True, "content": "连接超时"} 回传给模型,引导模型自主重试或降级,而不是导致整个应用抛出未捕获异常;
  3. 断点续跑与持久化友好:由于消息结构完全由普通的 Python dict 组成,每一轮往返后都可以直接序列化存入 Redis 或 PostgreSQL,天然契合长生命周期工作流。

下一篇我们将把 OpenAI、Google 与 Anthropic 三大 SDK 放在同一维度进行横向横评,梳理从研发验证到企业生产的最佳选型路线。


系列导航与参考