Messages API
Anthropic 最底层的 HTTP 接口,所有官方 SDK 和 Agent SDK 都是它上面搭出来的。
Messages API 是 Anthropic 面向开发者最基础的一层。上一节讲的 Agent SDK 也好,官方的 @anthropic-ai/sdk 也好,Claude 桌面客户端也好,最终都会拆成一条条 POST /v1/messages 请求发到 Anthropic 的服务器。搞清楚这层协议本身,你能诊断任何一层上面出的问题。
绝大多数业务里你不会直接手写 HTTP 请求,而是用官方 SDK,但理解底层协议非常有价值:一是排错时能看懂 API 报错和 SDK 抛出的异常到底对应哪个字段;二是做自研 agent 框架、做批量数据处理时能精细控制每个字段的开销;三是切换到 Bedrock、Vertex 之类的托管入口时,接口签名和字段基本一致,学一次到处能用。
一个最简单的请求长什么样
端点固定:
POST https://api.anthropic.com/v1/messages三个必填的 header:
x-api-key: sk-ant-xxxxx
anthropic-version: 2023-06-01
content-type: application/jsonanthropic-version 是 API 的版本号,不是 SDK 版本,历史值一直是 2023-06-01,除非官方明确通知,都用这个即可。
请求体最小结构:
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "用一句话解释 CAP 定理"}
]
}用 curl 走一遍:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "用一句话解释 CAP 定理"}
]
}'返回大概是这样:
{
"id": "msg_01ABC...",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-5",
"content": [
{"type": "text", "text": "CAP 定理是说……"}
],
"stop_reason": "end_turn",
"usage": {"input_tokens": 15, "output_tokens": 42}
}注意 content 是数组而不是字符串。原因是响应里可能同时出现文本块和 tool_use 块,用数组统一表示。
请求体的核心字段
| 字段 | 说明 |
|---|---|
model | 模型 ID,见下节表格 |
messages | 对话数组,每项 {role, content},role 是 user 或 assistant |
max_tokens | 本次生成允许的最大 token 数,必填 |
system | 系统提示词,独立于 messages 传 |
temperature | 采样温度,0 到 1,默认 1 |
top_p / top_k | 采样截断参数,通常保持默认 |
stop_sequences | 遇到就停下的字符串列表 |
stream | 是否走流式,默认 false |
tools | 工具定义数组,用于 tool use |
tool_choice | 强制工具选择策略 |
当前模型 ID
写这一节时 Anthropic 官方在售的主要模型 ID:
claude-opus-4-8:最新一代 Opus,最强推理,最贵。适合复杂 agent 决策、深度代码理解、多步推理。claude-opus-4-7:上一代 Opus,仍在维护,价格略低。已有代码里如果钉死这个版本,可以继续用,但新项目直接上 4-8。claude-sonnet-5:Sonnet 系列旗舰,日常首选的性价比档。写代码、写文档、做一般 agent 循环,这一档最平衡。claude-haiku-4-5-20251001:Haiku 系列小模型,快而便宜,适合分类、抽取、批处理、大规模离线任务。claude-fable-5:Fable 系列,面向创意写作和角色扮演场景,比 Sonnet 在长文本连贯性上更强。
模型 ID 通常带日期后缀作为不变引用,也可以用不带日期的别名跟随最新版本。生产环境建议钉死带日期的 ID,避免升级破坏行为。原型阶段可以用别名图省事。
还有一个隐藏字段叫 metadata,可以传一个 user_id 字符串,Anthropic 后台用于滥用检测。做多租户服务时建议每次都带上,一是合规,二是出问题时排查更快。
多轮对话
messages 数组本身承载完整对话历史。要多轮,你自己把之前的 assistant 回复也塞回去:
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"system": "你是一个精通分布式系统的中文助教。",
"messages": [
{"role": "user", "content": "什么是 CAP?"},
{"role": "assistant", "content": "CAP 定理指的是……"},
{"role": "user", "content": "那 PACELC 呢?"}
]
}服务器不保存对话状态,每次请求你都要把上下文整个发过去。这也是为什么 prompt caching(下面会讲)对多轮场景很关键。
流式返回
把 stream 置为 true,服务器就以 SSE(Server-Sent Events)方式一段段推 token:
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "写一个 fizzbuzz"}]
}响应体是一串 event: 加 data: 的 SSE 事件流,常见事件有 message_start、content_block_start、content_block_delta、content_block_stop、message_delta、message_stop。SDK 会替你把这堆事件重新组装成一个 stream 对象,业务代码里几乎都用 SDK 而不是自己解析 SSE。
流式的意义有两层:一是用户体验层面,边生成边渲染,用户不用干等一分钟才看到第一个字;二是资源层面,如果你只需要开头一段就够了,可以中途中断连接,省下后续 token 费用。做聊天类产品几乎必须走流式,做批处理离线任务反倒可以不用。
Tool use 循环
要让 Claude 调你的函数,两步:请求里带 tools,响应里如果是 tool_use 就把结果作为 tool_result 发回去。
请求:
{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "查询指定城市的天气",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
],
"messages": [{"role": "user", "content": "北京今天天气怎么样?"}]
}响应里 stop_reason 是 tool_use,content 数组里出现一个 tool_use 块:
{
"content": [
{"type": "tool_use", "id": "toolu_01ABC", "name": "get_weather", "input": {"city": "北京"}}
],
"stop_reason": "tool_use"
}你的代码去执行 get_weather,把结果作为下一轮的 user 消息传回:
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_01ABC", "content": "晴 26 度"}
]
}循环直到 stop_reason 变成 end_turn。Agent SDK 帮你封装的正是这套循环。
如果一次响应里带了多个 tool_use 块(并行工具调用),你就要把每个都执行完,把结果拼成一个 user message 里的多个 tool_result 块一起发回去,而不是分成多轮。这里踩过坑的人不少:只回填一个 tool_use_id,另一个悬空,Claude 就会一直等那个不存在的结果。
Prompt caching
对于长 system prompt、大段 CLAUDE.md、多轮对话历史这种大量重复输入的场景,用 prompt caching 能省钱也能提速。原理是 Anthropic 服务端把打了缓存标记的那段内容前缀哈希后缓存下来,下次请求命中就跳过重新处理。
在需要缓存的内容块尾部打一个 cache_control 标记:
{
"system": [
{
"type": "text",
"text": "很长的系统提示词,加上一大段项目背景……",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [{"role": "user", "content": "开始吧"}]
}首次调用照常收费并写入缓存,后续 5 分钟内命中的部分按 10% 计费。ephemeral 是当前唯一的缓存类型。命中判断是基于前缀严格匹配,所以缓存块之前的所有内容改一个字都会失效——把稳定不变的内容放在 messages 数组最前面,动态部分放在后面。
Token 计数
估算 token 数不用扔进 messages 里试,专门的端点:
POST https://api.anthropic.com/v1/messages/count_tokens请求体和 /messages 一样但不会真的生成,返回一个 input_tokens 数字。做长文本预算控制时很好用。比如你要往 system prompt 塞一大堆项目文档,先 count 一下确认没超上下文窗口,再真正发生成请求,能省掉 400 错误的返工。
TypeScript 官方 SDK 示例
日常调用一般不直接写 fetch,用 @anthropic-ai/sdk 封装好的客户端:
npm install @anthropic-ai/sdkimport Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
system: "你是一个中文技术助手。",
messages: [{ role: "user", content: "解释一下什么是 CAP 定理" }],
});
console.log(response.content[0]);ANTHROPIC_API_KEY 环境变量会被 SDK 自动读到。流式改成 client.messages.stream({...}) 拿到一个可迭代的 stream 对象。
Python 端等价写法用 anthropic 包:
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="你是一个中文技术助手。",
messages=[{"role": "user", "content": "解释一下什么是 CAP 定理"}],
)
print(response.content[0].text)流式版本用 with client.messages.stream(...) as stream: 上下文管理器包起来,然后 for text in stream.text_stream: 拿到 delta。
缓存的另一个好处是首字节延迟。命中缓存的部分服务端处理时间几乎为零,Time-to-first-token 会明显低于全新请求。做交互式产品时用户能感知到这个差别。
错误码和限流
跟其它 REST 服务一样,Messages API 用标准 HTTP 状态码告诉你出了什么问题。几个高频的:
400:请求体有问题,通常是字段类型错、max_tokens缺失、模型 ID 拼错。401:x-api-key无效或者没传。403:key 没有对应模型的权限(比如免费额度不含 Opus)。429:触发速率限制。响应头里会带retry-after告诉你几秒后再试。500/529:服务端过载或临时故障,直接指数退避重试。
速率限制分两个维度:每分钟请求数(RPM)和每分钟 token 数(TPM)。生产系统一定要在客户端做退避和排队,否则一次流量峰值就能把整条链路打瘫。
直接用 REST vs 用官方 SDK
除非你在做嵌入式、需要自己实现 SSE 解析或者跑在非主流语言里,日常业务都建议走官方 SDK。SDK 帮你处理:
- 自动带上
anthropic-version头,跟着新版本升级 - SSE 流式解析、重连、超时
- Retry with exponential backoff
- 类型提示,编辑器自动补全
- 特殊参数(比如 tool_choice、metadata)的字段命名和类型
自己撸 REST 通常只在 debug 一个诡异行为或者写最小可复现代码时才会用。
API 和 Agent SDK 怎么选
一句话总结:只调一次拿文本,用 Messages API;要跑工具循环,用 Agent SDK。Agent SDK 里的 tool 循环本身就是若干次 Messages API 调用的编排,自己写不是不行,就是重复造轮子。
再具体一点,什么时候必须回到 Messages API 层:需要精细控制每一次调用的模型和 max_tokens、需要把响应喂给非 Claude 的下游模型、需要把 API 调用嵌进已有的自研 agent 框架里。除了这些,Agent SDK 更省事。