ClaudeMap

·SDK 与工具

Claude API 新手友好教程——获取 API key、用 Python 和 TypeScript 发出第一次 messages.create 调用、流式响应、接上工具使用,以及生产级错误处理、429 限流指数退避、prompt caching + Batch API 叠加降本 80%。

Claude API 入门:第一次调用、流式响应与工具使用

Claude API 是你在自己产品里用上 Claude 的途径——一个客服机器人、一个代码生成器、一个文档摘要器,任何由你掌控循环的场景。好消息是它的接口很小:一个端点、每种语言一个 SDK、几个能相互组合的概念。本指南带你走一遍获取 API key、用 Python 和 TypeScript 发出第一次 messages.create 调用、流式响应,以及接上工具使用(tool use)让 Claude 能调用你定义的函数。

TL;DR

  • 一个端点:POST /v1/messages,所有语言 SDK 都是它的薄封装
  • 必填三件套:model / max_tokens / messages——其他都是可选优化
  • 2026 年现状:Sonnet 4.5 是性价比主力,Haiku 4.5 适合低延迟高 QPS
  • 工具使用 = tools + tool_use 块 + tool_result 块来回对话的循环
  • 流式响应两种姿势:messages.stream() 高层辅助 vs messages.create(..., stream=True) 原始事件

获取 API key

一切都从 Anthropic 控制台开始。注册账号,然后在 API Keys 区创建一个 API key。key 以 sk-ant- 开头,且只显示一次——立刻复制到安全的地方。把它当成密码:任何拿到 key 的人都能花你的额度。

把它设成环境变量,这样 SDK 能自动找到它,而不需要你写死在源码里:

export ANTHROPIC_API_KEY="sk-ant-..."

把这行加到你的 shell 配置里,让它跨终端持久化;在生产里用 .env 文件和加载器。SDK 在你不显式传 key 时会自动读取 ANTHROPIC_API_KEY,这样密钥就不会出现在代码里。

在控制台时,顺便设一个消费上限。API 按 token 计费,一个失控的循环很容易烧光额度。预算上限能把一个 bug 变成一个错误,而不是一张意外账单。

安装 SDK

Anthropic 为 Python 和 TypeScript 维护官方 SDK(其他语言有社区 SDK)。选跟你技术栈匹配的那个。

Python:

pip install anthropic

TypeScript / Node.js:

npm install @anthropic-ai/sdk

两个 SDK 都是同一个 HTTP API 的薄封装,所以概念在它们之间完全互通。下面的例子用 Python,TypeScript 有差异的地方会注明。

你的第一条消息

API 围绕一个端点组织:创建一条消息。你传一个模型、一组消息(每条带角色和内容)、一个最大输出长度,拿回 Claude 的响应。

from anthropic import Anthropic

client = Anthropic()  # 自动从环境变量读取 ANTHROPIC_API_KEY

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "用两句话解释什么是 MCP 服务器。"}
    ],
)

print(response.content[0].text)

关于这次调用,几点值得理解:

  • model —— 模型 ID。当前一代包括 Claude Opus、Sonnet、Haiku 等家族,各有带版本号的版本。模型 ID 遵循类似 claude-sonnet-4-5 的模式;务必查官方模型文档获取确切的、当前的 ID 字符串,因为它们会随时间演进。
  • max_tokens —— 响应最多包含多少 token。这是一个硬上限,也是必填字段。开放式生成设宽松些,结构化抽取设紧一些。
  • messages —— 对话,是一组 {role, content} 轮次。角色是 userassistant。这里不单独传 system 消息;它走可选的顶层 system 参数。
  • response.content —— 响应是一个内容块列表,不是纯字符串。最常见的块类型是 text,但正如你在工具使用里看到的,它也可以是 tool_use。取典型响应的文本用 response.content[0].text

TypeScript 调用形态相同:client.messages.create({ model, max_tokens, messages }),返回一个带 .content 数组的对象。

加上系统提示词

系统提示词为整段对话设定 Claude 的角色、语气和基本规则。作为顶层 system 参数传入:

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    system="你是一个严谨的文字编辑。只改语法和清晰度,绝不改变作者原意或添加信息。",
    messages=[
        {"role": "user", "content": "检查这句话的清晰度:……"}
    ],
)

系统提示词是放稳定指令的地方——身份、策略、输出格式。用户消息是放变化输入的地方。保持这种分离,提示词才好维护。

流式响应

对交互式应用来说,等完整响应显得慢。流式让你在 token 一产生时就发出。Python SDK 提供了一个流式辅助:

with client.messages.stream(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "写一首关于 TCP 的短诗。"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

stream.text_stream 在文本块生成时逐个 yield,所以你可以实时打印、发到 websocket、或追加到 UI。底层用的是服务器推送事件(server-sent events);SDK 替你解析。

你也可以调 messages.create(..., stream=True) 自己迭代原始事件流,这给你对单个事件(消息开始、内容块增量、消息结束)更细的控制。对大多数场景,stream() 辅助是更简单的路;原始流用于你需要显式处理每种事件类型时。

工具使用:让 Claude 调用你的函数

工具使用(常被叫做「function calling」)是把 Claude 从文本生成器变成能采取行动的 Agent 的关键。你用名字、描述和入参的 JSON Schema 定义工具。当 Claude 判定某个工具有助于回答用户时,它会发出一个 tool_use 块(而不是、或除了文本之外)。你执行工具、回传 tool_result、Claude 继续。

下面是一个完整最小的例子。我们定义一个 get_weather 工具,让 Claude 调用它:

import json
from anthropic import Anthropic

client = Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "The city name, e.g. 'San Francisco'."}
            },
            "required": ["city"],
        },
    }
]

# 真实应用里这里会调一个真正的天气 API。
def get_weather(city: str) -> str:
    return json.dumps({"city": city, "condition": "foggy", "temperature_c": 14})

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in San Francisco?"}],
)

# 当 Claude 决定用工具时,stop_reason 是 "tool_use",且响应内容里出现 tool_use 块。
if response.stop_reason == "tool_use":
    # 找到 tool_use 块(一次响应里可能有多个)。
    for block in response.content:
        if block.type == "tool_use":
            result = get_weather(**block.input)
            # 把助手的 tool_use 轮次原样回传,再跟一个带 tool_result 的 user 轮次。
            # 然后再调一次 create,让 Claude 用结果来回答。
            follow_up = client.messages.create(
                model="claude-sonnet-4-5",
                max_tokens=1024,
                tools=tools,
                messages=[
                    {"role": "user", "content": "What's the weather in San Francisco?"},
                    {"role": "assistant", "content": response.content},
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "tool_result",
                                "tool_use_id": block.id,
                                "content": result,
                            }
                        ],
                    },
                ],
            )
            print(follow_up.content[0].text)
else:
    print(response.content[0].text)

值得注意的机制:

  • input_schema 是描述工具参数的 JSON Schema。Claude 按 schema 填充 input,所以精确的 schema 产生精确的调用。
  • tool_use_id 把结果和调用绑起来。如果 Claude 在一次响应里做了两次工具调用,每个结果都需要匹配的 id。
  • 要把助手那一轮原样回传。 注意后续消息里有 {"role": "assistant", "content": response.content}——完整的原始内容块,而不只是抽出来的文本。这保留了工具使用的上下文,让 Claude 知道它问过什么。
  • stop_reason == "tool_use" 是你判断 Claude 想调工具而非直接回答的方式。

在生产 Agent 循环里,你一直调 create 直到 stop_reason 不再是 tool_use,每轮执行工具并回填结果。这个循环是任何 Agent 的心脏——如果你不想自己写,Claude Agent SDK 已经替你实现了。

实用建议

几条很快见效的习惯:

  • 发布前核对模型 ID。 ID 会演进;教程里写死的 ID 可能已过时。官方模型文档是事实来源。
  • 慎重设 max_tokens 太低 Claude 的答案会被半句截断;太高你就放弃了成本控制。对结构化抽取,紧的上限还能把模型往简洁输出上推。
  • 开发期间记录完整请求和响应。 出问题时,请求体和原始响应通常足够诊断。生产前剥掉日志,避免记录用户数据。
  • 处理错误和限流。 SDK 会为错误、限流和服务器过载抛出类型化异常。对可重试的做指数退避重试。
  • 先不用工具,需要时再加。 令人惊讶的大量工作,靠一个好的系统提示词和结构良好的输入就能完成。当模型确实需要采取行动或查它不可能知道的东西时,再加工具使用。

错误处理、限流与指数退避

生产里你一定会遇到三类错误:4xx 输入错误(参数错、token 超限、auth 失败——不要重试,修请求)、5xx 服务器错误(过载、内部错误——短暂重试)、429 rate_limit_error(限流——等待后重试)。SDK 把这些都映射成类型化异常,规则如下:

from anthropic import APIStatusError, APITimeoutError, RateLimitError
import time, random

for attempt in range(5):
    try:
        response = client.messages.create(...)
        break
    except RateLimitError:
        # 429:等 Retry-After 头或指数退避
        time.sleep(2 ** attempt + random.random())
    except APIStatusError as e:
        if e.status_code >= 500:
            time.sleep(2 ** attempt)  # 5xx:短暂重试
        else:
            raise  # 4xx:别重试
    except APITimeoutError:
        time.sleep(2 ** attempt)

关键规则只在 429 / 5xx 重试,4xx 立刻抛出。指数退避 base 用 2 秒,加 random.random() jitter 防雪崩。长跑请求一定要给 timeout=(SDK 默认 10 分钟,但 UI / agent loop 里设 30~60 秒更合理——卡死的请求比失败更糟)。

Prompt caching 与 batch 叠加:降本 80%

claude-sonnet-4-5 / claude-opus-4-1 支持 prompt caching——把长 system prompt / 文档前缀标记为缓存,后续命中按 1/10 价格计 input token。叠加 Batch API(异步批量,5 小时内返回,5 折)能再砍一半:

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": LONG_RAG_CONTEXT,  # 几千 token 的检索结果
        "cache_control": {"type": "ephemeral"}  # 5 分钟缓存
    }],
    messages=[{"role": "user", "content": "总结上面文档的关键点"}]
)
print(response.usage)  # cache_creation_input_tokens / cache_read_input_tokens

实战经验:(1) cache 断点放 system 段尾——前面任一字节改了,缓存整体失效;(2) TTL 默认 5 分钟,间隔 > 5 分钟的复用价值大,否则纯烧钱;(3) batch API 适合离线(夜间摘要、批量分类),不适合实时对话。调试关键:看响应里 usage.cache_creation_input_tokens > 0 = 刚写入,cache_read_input_tokens > 0 = 命中;如果 cache_creation 反复 > 0 而 cache_read 一直是 0,你的缓存配置错了

流式与工具使用的常见坑

把上面两块拼进真实应用时,五个坑最高频。按「现象 → 原因 → 修法」拆解:

坑 1:无脑取 content[0].text,工具调用一响应就崩。 现象:加了 tools 之后代码开始抛 AttributeError 或打印出乱码对象。原因:工具调用响应里 content[0] 往往是 tool_use 块而不是 text 块,tool_use 块没有 .text。修法:永远按 block.type 分派处理text 走展示、tool_use 走执行,不要假设第一个块是文本。

坑 2:stop_reason 被忽略,截断的 JSON 溜进下游。 现象:让 Claude 输出 JSON 时偶发解析失败,且失败的答案末尾总是半句话。原因:max_tokens 触顶时 stop_reason"max_tokens",输出被硬截断——下游拿到的 JSON 缺右括号。修法:每次响应检查 stop_reason;结构化输出给足预算,截断时重试或分批生成。

坑 3:多轮循环里 messages 数组无界增长。 现象:对话越长越慢、越聊越贵,第 50 轮的延迟是第 1 轮的十倍。原因:每轮 create 都全量重发整个历史,token 成本按平方级累积。修法:滑动窗口(保留最近 N 轮)+ 把更早的历史压缩成一条摘要消息;固定的 system 前缀配上 cache_control,让被缓存的部分不重复计费。

坑 4:多个 tool_use 只回了一个 tool_result 现象:一次响应里 Claude 发起两个工具调用,回传后 API 报 400。原因:每个 tool_use 块都必须有一条 tool_use_id 匹配的 tool_result——多个结果要作为多个块放进同一条 user 消息。修法:回传前数一遍 tool_use 块的数量,逐个配对;id 不匹配是 invalid_request_error 的头号来源。

坑 5:流式中途断线,半截答案进了 UI。 现象:网络抖动后用户看到没写完的回复,程序还以为说完了。原因:流是 SSE 长连接,中途可能断;text_stream 循环正常退出就当代码走到头了。修法:流式调用包在 with 上下文管理器里确保连接释放;循环结束后检查消息是否完整stop_reason 事件),不完整走重试逻辑,把已收到的文本作为草稿丢弃或续写。

实战案例:一个多轮客服机器人的主循环

把「消息数组管理 + 工具循环 + 退出条件」三件事合到一起,就是一个客服 bot 的骨架。它带一个查订单工具,支持连续追问:

import json
from anthropic import Anthropic

client = Anthropic()
SYSTEM = "你是客服。查订单用 get_order 工具;回答保持两句话以内。"
messages = []

def get_order(order_id: str) -> str:
    return json.dumps({"order_id": order_id, "status": "shipped", "eta_days": 2})

TOOLS = [{
    "name": "get_order",
    "description": "Query order status by id.",
    "input_schema": {
        "type": "object",
        "properties": {"order_id": {"type": "string"}},
        "required": ["order_id"],
    },
}]

while True:
    user_input = input("> ")
    if user_input in ("exit", "quit"):
        break
    messages.append({"role": "user", "content": user_input})

    # 内层工具循环:直到 Claude 给出最终回答(stop_reason != "tool_use")
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=1024,
            system=SYSTEM,
            tools=TOOLS,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason != "tool_use":
            print(response.content[0].text)
            break

        # 逐个执行 tool_use,把所有 tool_result 放进同一条 user 消息
        results = []
        for block in response.content:
            if block.type == "tool_use" and block.name == "get_order":
                output = get_order(**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})

    # 历史窗口控制:只保留最近 20 条(工具配对轮不拆散)
    if len(messages) > 20:
        messages = messages[-20:]

这段代码的三个关键点:(1) 内层 while 循环以 stop_reason 为退出条件——工具可能连环调用,一次 create 不够;(2) 所有 tool_result 合并进同一条 user 消息回传,天然满足「每个 tool_use 都有配对结果」;(3) 每轮结束后裁剪历史窗口,且按「整轮」裁(assistant 的 tool_use 轮和 user 的 tool_result 轮必须同进同退),否则配对被拆散会 400。生产里再叠加:get_order 的异常兜底(工具抛错时回传错误文本让 Claude 向用户解释)、RateLimitError 重试、以及 system 段的 cache_control

常见问题

怎么获取 Claude API 的 API key?

在 Anthropic 控制台注册账号,从 API Keys 区生成一个 key。key 以 sk-ant- 开头且只显示一次,所以立刻复制。把它设成 ANTHROPIC_API_KEY 环境变量,SDK 就会自动读取;并在控制台设一个消费上限以封顶你的敞口。

messages.create 该传哪个模型名?

模型 ID 遵循「家族-版本」模式,例如 Sonnet 家族的 claude-sonnet-4-5。确切的、当前的 ID 字符串会随新版本发布而演进,所以务必查官方模型文档获取最新 ID,而不是依赖教程里写死的值。

Claude API 的工具使用是怎么工作的?

你向 messages.create 传一个 tools 数组,每个工具有 name、description 和入参的 JSON Schema(input_schema)。当 Claude 判定某个工具有用时,它返回一个 tool_use 内容块,并把 stop_reason 设为 'tool_use'。你执行工具,然后发一条带 tool_result 块(带匹配的 tool_use_id)的后续消息,再调一次 create,让 Claude 用结果来回答。

messages.create 带 stream=True 和 messages.stream 有什么区别?

两者都是流式响应。messages.stream 是一个高层辅助,通过 stream.text_stream yield 已解析的文本块,并替你处理事件解析——对大多数场景最简单。messages.create(..., stream=True) 返回原始事件流,让你对内容块增量等单个事件有更细的控制;当你需要显式处理每种事件类型时用它。

429 限流了怎么处理?

SDK 会抛 RateLimitError 异常。不要立即重试——读响应里的 Retry-After 头(秒数)或者用指数退避:time.sleep(2 ** attempt + random.random()),base 用 2 秒、加 jitter 防雪崩。最多重试 5 次,然后把异常抛给上层(让用户看到错误而不是无限重试)。生产里给 token 用量加监控并设消费上限——失控的循环可以一个晚上烧光账号。

怎么降 Claude API 调用成本?

三个组合拳:(1) cache_control 标记长前缀——命中按 1/10 价;(2) messages.batches.create 异步批量,5 折(适合离线任务);(3) 短任务用 Haiku 4.5(成本 1/3、速度 2x,质量对短 prompt 已够用)。关键:缓存断点放 system 段尾——前面任一字节改了缓存整体失效;间隔低于 5 分钟的复用价值低,纯烧钱。调试时核对响应里的 usage.cache_creation_input_tokens / cache_read_input_tokens——如果前者反复 > 0 而后者一直 0,缓存配错了。

工具调用必须每个 tool_use 都回 tool_result 吗?

必须。每个 tool_use 块都需要一条 tool_use_id 精确匹配的 tool_result,缺失或不匹配会返回 400 invalid_request_error。Claude 一次响应里发起多个工具调用时,把所有结果作为多个 tool_result 块放进同一条后续 user 消息即可。裁剪历史时也要整轮整轮地裁——assistant 的 tool_use 轮和 user 的 tool_result 轮必须同进同退,拆散配对同样报错。

多轮对话的 messages 数组越来越长怎么办?

三招组合:滑动窗口(只保留最近 N 轮)、把更早的历史压缩成一条摘要消息、把固定的 system 前缀用 cache_control 缓存以降低重复计费。注意两点:裁剪按「整轮」进行,不能把 tool_use/tool_result 配对拆散;摘要压缩放在用户轮的边界上做,避免把工具结果中间态当成对话内容误摘要。

官方参考资料

本文基于截至 2026 年 7 月的公开信息,相关 API 可能演进。