02.03概念更新于 2026年9月17日

调用模型 API

消息结构、角色、流式输出、常用参数与常见报错。

02.03 · 课时讲解

调用模型 API

带上模型名、指令和完整历史,拿回内容、停止原因和 Token 用量。

1 / 11

一句话理解:调用模型就是发一个 HTTP 请求:带上模型名、指令和完整的消息历史,拿回一段内容、一个停止原因和一份 Token 用量。

在聊天界面里,“多轮对话”“思考中”“流式打字效果”看起来都是模型自带的能力。一旦你自己调用 API,就会发现它们全是程序拼出来的:历史要自己保存并重发,打字效果要自己处理事件流,超时、限流、截断都要自己兜底。

这一课用 Claude API 和 OpenAI API 各演示一遍,目标不是让你背接口,而是让你能读懂工程师的代码、看懂一条调用日志,并在评审时问出“429 了怎么办”。

请求长什么样

所有主流模型 API 都遵循同一个心智模型:指令 + 消息列表 → 模型 → 内容 + 元数据。先看 Claude Messages API 最小的一次调用(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-opus-5",
    "max_tokens": 1024,
    "system": "你是客服工单分析助手,回答简洁,只基于提供的数据。",
    "messages": [
      {"role": "user", "content": "订单付款三天了还没有发货,一般是什么原因?"}
    ]
  }'

拆开看:

部分 作用
x-api-key 身份凭证。只能放在服务端,绝不能暴露在前端代码里
anthropic-version API 版本头,保证接口行为稳定
model 调用哪个模型,决定能力、速度与价格
max_tokens 本次输出的硬上限(包括思考内容),到了就截断
system 系统指令:角色、规则、格式,优先级高于用户消息
messages 按顺序排列的对话,role 为 user 或 assistant

返回的主体结构如下(字段节选,内容为示意):

{
  "id": "msg_01...",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5",
  "content": [
    {"type": "text", "text": "常见原因有三类:1. 支付状态未最终确认……"}
  ],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 58, "output_tokens": 212}
}

注意 content 是一个 内容块数组,不是一个字符串:开启思考时会有 thinking 块,调用工具时会有 tool_use 块。代码里要按 type 取值,而不是直接取第一个元素。

把请求和响应放在一起看,一次调用的全貌是这样的:

一次模型调用的请求与响应:请求带上模型、上限、系统指令和消息,响应返回内容块、停止原因与用量 请求(在服务端发出) model:选哪个模型 max_tokens:输出上限 system:角色与规则 messages:完整对话历史 API Key 只放在服务端 模型 逐 Token 生成 API 无状态 响应 content 内容块数组 text / thinking / tool_use stop_reason 停止原因 决定程序下一步做什么 usage Token 用量 输入、输出分别计费
图 1 · 请求与响应:指令 + 消息列表 → 模型 → 内容 + 元数据

同样的事情用 Python SDK 写:

import anthropic

client = anthropic.Anthropic()  # 默认从环境变量 ANTHROPIC_API_KEY 读取密钥

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    system="你是客服工单分析助手,回答简洁,只基于提供的数据。",
    messages=[{"role": "user", "content": "订单付款三天了还没有发货,一般是什么原因?"}],
)

for block in response.content:
    if block.type == "text":
        print(block.text)
print(response.stop_reason, response.usage)

OpenAI 目前主推的是 Responses API,结构略有不同:系统级指令可以放在 instructions 里,也可以作为 developer 角色的消息放进 input,官方文档说明 developer 消息的优先级高于 user 消息:

from openai import OpenAI

client = OpenAI()  # 默认从环境变量 OPENAI_API_KEY 读取密钥

response = client.responses.create(
    model="gpt-6-astra",  # 模型名以 OpenAI 官方模型页为准
    instructions="你是客服工单分析助手,回答简洁,只基于提供的数据。",
    input="订单付款三天了还没有发货,一般是什么原因?",
)
print(response.output_text)

多轮对话:历史要自己带上

API 是无状态的。以天气 Agent 为例,用户先问“上海明天天气”,再问“那后天呢”,第二次请求必须带上第一轮的完整内容,否则模型不知道“那”指的是上海:

messages = [
    {"role": "user", "content": "上海明天天气怎么样?"},
    {"role": "assistant", "content": "上海明天多云,18–24°C。"},
    {"role": "user", "content": "那后天呢?"},
]
response = client.messages.create(model="claude-opus-5", max_tokens=1024, messages=messages)

所谓“多轮对话”,其实是程序在每一轮把历史重新拼进请求:

多轮对话由程序拼出:第 1 次请求只有一条用户消息,程序保存回复后,第 2 次请求带上完整历史 第 1 次请求 messages user:上海明天天气? 模型返回 回复:多云,18–24°C 你的程序 保存历史,下一轮重发 第 2 次请求 messages user:上海明天天气? assistant:多云,18–24°C user:那后天呢? 模型不记得上一轮:它能看到的,只有这一次请求里带上的 messages
图 2 · 多轮对话:历史由你的程序保存,并在下一轮完整重发

在 Claude 的新模型上还有一个容易踩的坑:不再支持“预填充”,也就是不能让最后一条消息是 assistant 来“替模型起个头”,否则返回 400。想控制输出格式,应该用 结构化输出 或在指令里说明。

流式输出:让用户不必干等

非流式调用要等模型全部生成完才返回,长回答可能要几十秒甚至更久。流式输出(streaming)通过 Server-Sent Events 把内容边生成边推送,好处有三个:用户更快看到第一个字;长任务不容易触发网络超时;可以实时展示进度。

Claude Python SDK 的写法:

with client.messages.stream(
    model="claude-opus-5",
    max_tokens=4096,
    messages=[{"role": "user", "content": "写一份上周华东区销售下滑的分析提纲"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()  # 流结束后拿到完整消息与用量

底层的事件顺序大致是:

流式输出的事件顺序:message_start、内容块开始、增量、内容块结束、message_delta、message_stop message_start 消息开始,带初始元数据 content_block_start 一个内容块开始(text / thinking / tool_use) content_block_delta 增量内容,会出现很多次 content_block_stop 该内容块结束;之后可能还有下一个块或 ping message_delta 顶层变化:stop_reason 与最终用量 message_stop 消息结束 循环 流式请求可能在返回 200 之后中途出错:错误以 error 事件出现在流里
图 3 · 流式事件顺序:内容块可以有多个,错误也可能在流中途出现

OpenAI Responses API 设置 stream=True 后,常用事件是 response.created、response.output_text.delta、response.completed 和 error。

一个产品上很重要的细节:流式请求可能在返回 200 之后中途出错,错误会以事件形式出现在流里。前端要能展示“生成中断”,而不是把半截内容当成完整答案。

常用参数、停止原因与报错

常用参数(Claude / OpenAI 对照):

目的 Claude Messages API OpenAI Responses API
输出长度上限 max_tokens max_output_tokens
系统指令 system instructions 或 developer 消息
思考深度 output_config.effort reasoning.effort
结构化输出 output_config.format text.format
工具 tools、tool_choice tools、tool_choice
停止序列 stop_sequences 请查阅官方 API 参考

temperature 这类采样参数要特别小心:Claude Opus 4.7 起设置非默认值会返回 400,原因见 LLM 如何工作。

停止原因决定了程序下一步该做什么(Claude 的 stop_reason):

值 含义 程序应该
end_turn 自然结束 正常处理
max_tokens 到达输出上限被截断 提高上限、降低思考深度,或提示用户
stop_sequence 命中自定义停止序列 按业务处理
tool_use 模型要调用工具 执行工具并回传结果,见 Function Calling 原理
pause_turn 长任务暂停,可继续 把内容回传以继续
refusal 出于安全原因拒绝 读取 stop_details,展示合适的提示
model_context_window_exceeded 触到上下文窗口上限 压缩上下文或拆分任务

常见 HTTP 错误(Claude API 官方错误码):

状态码 类型 常见原因 能否重试
400 invalid_request_error 参数错误、格式不对、输入超出窗口 否,先修请求
401 authentication_error API Key 无效、过期 否
403 permission_error Key 无权使用该资源 否
413 request_too_large 请求体过大(Messages API 上限 32 MB) 否,压缩或改用文件接口
429 rate_limit_error 触发限流或额度上限 是,按 retry-after 退避
500 api_error 服务端内部错误 是,指数退避
504 timeout_error 处理超时 是,长请求改用流式
529 overloaded_error 服务整体过载 是,退避或切换备用模型

官方 SDK 默认会对连接错误、429 和 5xx 自动重试两次(指数退避,并遵守 retry-after)。每个响应都带有 request-id 头,务必写进日志,排查问题和联系支持时都需要它。

常见误区

  • “SDK 帮我重试了,就不用管错误了。” 自动重试只覆盖瞬时故障。持续 429、400、截断和中途断流,都需要产品层面的处理和提示。
  • “max_tokens 设小一点能省钱。” 设得过小会截断回答,开启思考时更容易发生,结果往往是重试更多、总成本更高。控制成本应该靠精简输入、调整思考深度和选择合适的模型。
  • “把 API Key 放在前端调用更简单。” Key 泄露意味着任何人都能用你的额度。调用必须经过你的服务端。

产品经理检查清单

  • 调用在服务端完成吗?密钥如何存放与轮换?
  • 用户可见的生成是否使用流式输出?中途失败时界面如何呈现?
  • 对 max_tokens、refusal、model_context_window_exceeded 等停止原因,产品各有什么处理?
  • 遇到 429 / 529 时是排队重试、降级到备用模型,还是提示用户稍后再试?
  • 日志里是否记录了模型名、request-id、输入输出 Token、耗时和停止原因?

练习

完成「天气 Agent」的第一步:只调模型,暂不接工具(约 60 分钟)。

  1. 用 Claude 或 OpenAI 的 Python SDK 写一个脚本:系统指令要求“只回答天气相关问题,其他问题礼貌拒绝”。
  2. 实现两轮对话:“杭州这周末适合爬山吗?” → “那苏州呢?”,确认第二轮能理解指代。
  3. 改成流式输出,打印最终的 usage 和 stop_reason。
  4. 故意把 max_tokens 设为 20,观察 stop_reason;故意写错 API Key,记录错误类型。
  5. 用一张表记录每次调用的输入 Token、输出 Token、耗时。

延伸阅读

下一步

下一课「推理模型与思考预算」会解释 effort 这个参数背后的事:让模型“多想一会儿”什么时候值得,代价是什么。

试试:上下文、MCP、沙箱、Prompt 注入、外链