cv.cm

AI · 开发者AI

大模型API怎么调用?

阅读约 11 分钟

简短回答

发送一个 HTTPS POST 请求:API 密钥放在请求头里,JSON 请求体写明模型名和消息列表。返回的是包含生成文本和 token 数的 JSON。许多服务商兼容 OpenAI 风格的 chat completions 格式,Anthropic 和 Gemini 也有各自的原生格式。密钥要放在服务端,按 token 付费。

打开JSON 格式化

一次API调用发生了什么

大模型API就是一个网络服务。你的程序发送文本,服务商的服务器运行模型,结果以 JSON 返回。调用之间没有记忆:每个请求都必须包含你希望模型看到的整段对话。

你需要三样东西:带有API密钥的账号、接口的URL,以及服务商模型列表里的模型 ID。下面示例中的 MODEL_ID 只是占位符。模型名称更新很频繁,请从服务商的文档里复制当前的 ID,而不是从博客文章里抄。

用 curl、Python 和 JavaScript 发起最简请求

最早由 OpenAI 发布的 chat completions 格式,是被仿用得最广泛的请求形态。许多其他服务商和本地服务器都兼容它,有时只需换一个基础URL。下面是最小而有用的请求:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "MODEL_ID",
    "messages": [
      {"role": "system", "content": "You answer in one short paragraph."},
      {"role": "user", "content": "What is a context window?"}
    ]
  }'

messages 列表保存对话。每条消息有 role 和 content。system 消息为回复设定规则(OpenAI 较新的模型也接受用 developer 这个名字来表示这个角色)。user 消息是请求,assistant 消息是之前的回复,需要继续对话时要把它们一起发回去。精简后的响应如下:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "A context window is ..."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 27, "completion_tokens": 64, "total_tokens": 91}
}

文本在 choices[0].message.content 里。usage 对象统计了你被计费的 token 数。finish_reason 说明模型为什么停下来:stop 表示正常结束,length 表示碰到了你设置的 token 上限,回答被截断。

在 Python 中,带超时和重试的同一个调用如下。它使用常见的 requests 库。各服务商的官方 SDK 做的是同样的事,而且会替你加好重试。

import os, random, time
import requests

URL = "https://api.openai.com/v1/chat/completions"
API_KEY = os.environ["OPENAI_API_KEY"]   # read from the server environment

def chat(messages, model="MODEL_ID", max_attempts=5):
    body = {"model": model, "messages": messages,
            "max_completion_tokens": 500}   # many compatible APIs: "max_tokens"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    for attempt in range(max_attempts):
        last = attempt == max_attempts - 1
        try:
            r = requests.post(URL, headers=headers, json=body, timeout=60)
        except (requests.Timeout, requests.ConnectionError):
            if last:
                raise
            time.sleep(2 ** attempt + random.random())   # network error: retry
            continue
        if r.status_code == 429 or r.status_code >= 500:
            if last:
                break
            backoff = 2 ** attempt + random.random()      # exponential + jitter
            try:
                wait = max(float(r.headers.get("Retry-After", 0)), backoff)
            except ValueError:
                wait = backoff
            time.sleep(wait)
            continue
        r.raise_for_status()                # 400, 401, 403: fix the request, do not retry
        data = r.json()
        return data["choices"][0]["message"]["content"], data["usage"]
    raise RuntimeError("API still failing after retries")

text, usage = chat([{"role": "user", "content": "What is a context window?"}])
print(text)
print(usage["prompt_tokens"], "in /", usage["completion_tokens"], "out")

各家服务商的差异:OpenAI 兼容、Anthropic 与 Gemini

chat completions 的形态很常见,但并不通用。Anthropic 的 Messages API 和 Google 的 Gemini API 都有自己的原生格式。两家也提供了兼容选项,但新功能总是先在原生 API 上开放。主要差异如下:

项目OpenAI 风格 chat completionsAnthropic Messages APIGemini API(原生)
接口POST /v1/chat/completionsPOST /v1/messagesPOST /v1beta/models/{model}:generateContent
API 密钥请求头Authorization: Bearer <key>x-api-key,另需必填的日期型头 anthropic-versionx-goog-api-key
系统指令system 或 developer 消息顶层的 system 字段顶层的 systemInstruction 字段
必填字段model、messagesmodel、max_tokens、messagescontents(模型写在 URL 里)
回复文本位置choices[0].message.contentcontent 数组中的文本块candidates[0].content.parts[].text
流式输出"stream": true,服务器发送事件(SSE),以 [DONE] 结束"stream": true,带事件名的 SSE使用 streamGenerateContent 方法并加 alt=sse

Gemini 提供了兼容 OpenAI 的基础URL,其他许多服务商,包括 DeepSeek 以及一些国内和开源模型托管方,也接受 chat completions 格式。兼容通常是部分的:结构化输出、工具调用或图像输入等高级功能可能不同。只改 URL 就切换服务商之前,请先读该服务商的兼容性说明页面。

API密钥要放在服务端

谁拿到了你的API密钥,谁就能花你的钱。绝不要把它放进浏览器 JavaScript、手机 App、公开仓库,或者会被打包进页面的前端环境变量文件,这些地方用户都读得到。

  1. 把密钥存在服务器的环境变量或密钥管理服务里。
  2. 让你的网页或手机应用调用你自己的后端接口。
  3. 在后端先检查用户(登录、按用户限速、输入长度),再带着密钥调用大模型API。
  4. 只把结果返回给客户端。
  5. 在服务商后台设置每月消费上限,每个项目用单独的密钥,一旦泄露立即轮换。

流式输出:边写边显示答案

长回答可能要花好几秒。加上 "stream": true,服务器会以服务器发送事件的形式分小段推送,界面就能在文字出现时随即显示。每个事件行以 data: 开头,带有一个含 delta 的数据块。OpenAI 格式以 data: [DONE] 结束。下面的例子在 Node.js 服务器上运行:

// Node.js 18+ (server side). Prints the reply as it arrives.
const res = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: "MODEL_ID",
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about rain." }],
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);

const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of res.body) {
  buffer += decoder.decode(chunk, { stream: true });
  const lines = buffer.split("\n");
  buffer = lines.pop();                       // keep a partial line for later
  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6);
    if (data === "[DONE]") continue;          // end of stream
    const delta = JSON.parse(data).choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
  }
}

要在浏览器里显示流,请把数据块从你的服务器转发给页面,不要带着你的密钥直接在浏览器里调用服务商。在 OpenAI 格式中,除非在 stream_options 里设置 include_usage,否则流式数据块不包含用量,所以如果需要 token 数用于计费,请打开它。

要JSON,不要散文

当代码要读取答案时,就要求返回 JSON。有两个层次。JSON 模式("response_format": {"type": "json_object"})保证返回合法的 JSON,但不保证有哪些字段。使用 JSON Schema 的结构化输出,则能让模型严格遵循你规定的字段和类型,前提是模型支持:

{
  "model": "MODEL_ID",
  "messages": [
    {"role": "user", "content": "Extract the total and currency: 'Amount due: 1.250,00 EUR'"}
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "invoice_total",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "total": {"type": "number"},
          "currency": {"type": "string"}
        },
        "required": ["total", "currency"],
        "additionalProperties": false
      }
    }
  }
}

即使有 schema,也要在代码里校验解析后的结果。结构对了,值仍可能出错,比如读错一个数字。Anthropic 和 Gemini 在各自的格式中支持 schema 和工具定义。调试很长的 JSON 回复时,用 cv.cm 的 json 工具这样的格式化器,能让嵌套的输出更容易读。

限流、重试与错误处理

服务商既限制每分钟请求数(RPM),也限制每分钟 token 数(TPM),先触到哪个就按哪个。超限时会收到 HTTP 状态码 429。按类型处理错误:

状态码含义怎么做
400请求有误:字段错误、token 太多修正请求,不要重试。
401 / 403密钥缺失、错误或无权限检查密钥和权限,不要重试。
429触发限流,有时是额度用完等待并按退避策略重试。如果提示是额度或账单问题,就停下来处理账户。
500 / 503服务器错误或过载按退避策略重试。
超时未在时限内返回重试一两次。长回答请使用流式输出。

用指数退避来重试:先等约 1 秒,再 2、4、8 秒,外加一点随机延迟(抖动),避免许多客户端在同一时刻重试。如果响应带有 Retry-After 头,至少等那么久。要限制重试次数。失败的请求也可能计入限额,所以在紧凑的循环里立刻重试只会更糟。如果你使用官方 SDK,别忘了它已经会重试某些错误。

成本按 token 计算

你为输入 token(你发送的一切,包括系统提示词和整个对话历史)和输出 token(模型写出的内容)付费。输出 token 通常比输入 token 贵。公式很简单:

cost = input_tokens  / 1,000,000 * input_price_per_million
     + output_tokens / 1,000,000 * output_price_per_million

Example with made-up prices (2.00 in, 8.00 out per million tokens):
  27 input + 64 output tokens
  = 0.000054 + 0.000512 = 0.000566 per call
  x 100,000 calls a month = about 56.60
  • 从每次响应的 usage 读取真实的计数并记录下来,不要用字数去估算。
  • 限制每次回复的长度:OpenAI 用 max_completion_tokens(较老的 max_tokens 在那里已弃用),Anthropic 和许多兼容 API 用 max_tokens。
  • 对话历史每轮都在增长,所以第 20 条消息比第一条更贵。要裁剪或总结旧的轮次。
  • 分类等简单任务用较小的模型,只在质量确实需要的地方用较大的模型。
  • 有些服务商对重复的提示词前缀给折扣(提示词缓存),或者为可以等待的任务提供更便宜的批处理。

常见问题

可以直接从浏览器调用大模型API吗?

用你自己的密钥不安全,因为任何访客都能从页面里读到。应该调用你自己的服务器,由服务器加上密钥。例外情况是用户输入自己账号的密钥,或者模型完全在浏览器里运行。

为什么API会忘记我之前说过的话?

API 是无状态的。每个请求都必须带上你希望模型看到的之前的消息。聊天应用会保存历史记录,并在每次请求时重新发送。

temperature 有什么用,需要设置吗?

temperature 控制用词的随机程度:越低越可预测,越高越多样。有些服务商和较新的模型会限制或移除采样参数,设置之前请先查看 API 参考文档。

为什么回答说到一半就停了?

回复达到了你设置的 token 上限,或者模型的最大输出长度。请检查 finish_reason(Anthropic 是 stop_reason),然后调高上限,或者要求更简短的回答。

有免费的大模型API吗?

有几家服务商提供免费额度或试用金,但限速很低;开源模型也可以在你自己的机器上运行,通过兼容 OpenAI 的本地服务器来调用。免费额度及其条款经常变化,请查看服务商当前的页面。

全部AI教程