OpenModel

如何通过 API 调用一个 LLM:一套 OpenAI 兼容代码实现连通验证、流式输出与稳健调用

更新于 2026-10-23 · openmodel.cyou

简答:配置服务端点(Base URL)、密钥(API Key)和模型名(Model ID)3 个参数,先执行 1 条 cURL 命令请求 /models 接口,30 秒即可确认 Key 是否可用并核对模型 ID。验证通过后,使用官方 openai Python 库向 /chat/completions 发送包含 messages 数组的请求即可完成单次问答、流式输出与多轮对话。

定义:LLM API(大语言模型应用程序编程接口)是一种基于 HTTP/HTTPS 协议的标准化推理接口,允许开发者在不自建 GPU 硬件的情况下,通过发送 JSON 格式的提示词(Prompt)远程调用大模型并获取生成的文本或结构化数据。

先试这一步:3 个核心参数速查卡与 30 秒 cURL 连通性验证

刚接触大模型开发的开发者,最常卡在“不同厂商接口格式不一样”或“写了一堆 Python 代码却报 401/404 错误”。实际上,目前主流大模型与推理工具普遍支持 OpenAI API 规范。你只需准备好 3 个核心参数,先在终端执行 1 条命令,30 秒内就能确认密钥与网络是否可用。(注:本站为第三方指南站,具体接口参数与价格以各厂商官方文档为准。)

1. 如何获取 API 密钥与调用三要素(速查卡)

无论你选择哪家模型服务商,接入前的通用准备步骤均为四步:① 注册并登录对应服务商开发者平台 → ② 进入控制台的“API Keys”页面点击创建新密钥 → ③ 在账单设置(Billing / Limits)中充值或设置月度消费硬上限(Spending Limit)防刷爆账单 → ④ 查看文档获取服务端点(Base URL)与模型名称(Model ID)。

以下是主流服务商的 OpenAI 兼容接口三要素取值速查表(各项参数均以对应官方文档为准):

服务商 / 部署方式服务端点根地址(LLM_BASE_URL)API 密钥(LLM_API_KEY)获取位置模型 ID(LLM_MODEL)取值方式
OpenAI 官方https://api.openai.com/v1OpenAI Platform 控制台 API Keys 页面创建示例模型名可能随版本迭代变化(如 gpt-4o),请以调用 GET /models 返回的 id 为准
DeepSeek 官方https://api.deepseek.com(或 https://api.deepseek.com/v1)DeepSeek API 开放平台控制台创建以官方快速入门与更新日志当前列出的模型名(如 deepseek-v4-flash、deepseek-v4-pro,旧名 deepseek-chat 已逐步迁移)或 /models 返回结果为准
本地 LM Studio默认 http://localhost:1234/v1(以本地端显示为准)本地调试任意非空字符串即可(如 lm-studio)在本地 Developer 界面加载模型后,调用 GET /models 获取准确 id
第三方 API 聚合网关填入网关文档提供的含 /v1 根地址(以其文档为准)在对应网关控制台令牌/密钥管理页生成以网关模型列表页或调用 GET /models 返回的 id 为准

安全提示:API Key 仅在创建时完整显示一次,切勿直接硬编码在代码里。请将其写入本地环境变量或不提交到 Git 仓库的配置文件中,并在项目根目录的 .gitignore 文件中添加精确忽略规则:

# 忽略本地环境变量文件,防止密钥泄露到代码仓库
.env
.env.local

2. 步骤一:用列模型请求验证连通性与核对模型 ID

在编写对话代码前,先向 /models 端点发送 GET 请求。这一步不消耗任何对话 Token,既能测试网络与 LLM_API_KEY 是否有效,又能直接列出当前账号有权调用的准确模型 id。

macOS / Linux (Bash) 最小验证命令:

export LLM_BASE_URL="https://api.openai.com/v1"
export LLM_API_KEY="替换为你在控制台创建的真实API_KEY"

curl -sS "${LLM_BASE_URL}/models" \
  -H "Authorization: Bearer ${LLM_API_KEY}"

Windows (PowerShell 5.1 / PowerShell 7+) 最小验证命令:

$env:LLM_BASE_URL = "https://api.openai.com/v1"
$env:LLM_API_KEY = "替换为你在控制台创建的真实API_KEY"

Invoke-RestMethod -Uri "$env:LLM_BASE_URL/models" `
  -Method Get `
  -Headers @{ "Authorization" = "Bearer $env:LLM_API_KEY" } | ConvertTo-Json -Depth 4

预期成功响应(HTTP 200):返回一个包含模型列表的 JSON 对象。请从返回的 data 数组中挑出你要使用的模型 id(下文以 gpt-4o 为例,实际请以你的返回结果为准):

{
  "object": "list",
  "data": [
    {
      "id": "gpt-4o",
      "object": "model",
      "owned_by": "system"
    }
  ]
}

3. 步骤二:在终端发送首次对话请求

将核对好的模型 id 存入环境变量 LLM_MODEL,向 /chat/completions 发送 POST 请求完成首次问答。在 Bash 中我们使用双引号转义传递 JSON,确保环境变量 ${LLM_MODEL} 被正确展开,避免把字面量发给服务端:

macOS / Linux (Bash):

export LLM_MODEL="gpt-4o"

curl -sS "${LLM_BASE_URL}/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${LLM_API_KEY}" \
  -d "{
    \"model\": \"${LLM_MODEL}\",
    \"messages\": [
      {\"role\": \"user\", \"content\": \"请用一句话解释什么是 API\"}
    ]
  }"

Windows (PowerShell 5.1 / 7+ 兼容写法):

在 Windows PowerShell 5.1 中,直接用 curl.exe -d 传递含双引号和中文的字符串容易因引号剥离或编码问题报 JSON 解析错误。推荐使用 PowerShell 原生的 Invoke-RestMethod 并将请求体编码为 UTF-8 字节流发送:

$env:LLM_MODEL = "gpt-4o"

$bodyObj = @{
  model = $env:LLM_MODEL
  messages = @(
    @{ role = "user"; content = "请用一句话解释什么是 API" }
  )
}
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes(($bodyObj | ConvertTo-Json -Depth 4))

Invoke-RestMethod -Uri "$env:LLM_BASE_URL/chat/completions" `
  -Method Post `
  -Headers @{
    "Authorization" = "Bearer $env:LLM_API_KEY"
    "Content-Type"  = "application/json; charset=utf-8"
  } `
  -Body $bodyBytes | ConvertTo-Json -Depth 6

两种 Python 调用方式:官方 SDK 与第三方 HTTP 库

终端验证连通后,即可在 Python 项目中编写调用逻辑。常见的实现路径有两种:使用 OpenAI 官方 Python SDK(自带类型提示、连接池与异常分类),或使用轻量级的第三方 HTTP 客户端库 requests 直接发送 POST 请求。

方式一:使用 OpenAI 官方 SDK(推荐最小可用代码)

先在终端安装官方 SDK:

pip install --upgrade openai

通过 os.getenv 读取环境变量。未来若要切换到 DeepSeek 或本地模型,只需修改环境变量中的 LLM_BASE_URL、LLM_API_KEY 和 LLM_MODEL,无需改动任何业务代码:

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
)

response = client.chat.completions.create(
    model=os.getenv("LLM_MODEL", "gpt-4o"),
    messages=[
        {"role": "system", "content": "你是一个简洁的技术助手。"},
        {"role": "user", "content": "用 Python 写一个计算斐波那契数列的函数。"}
    ]
)

print(response.choices[0].message.content)

方式二:不安装大模型 SDK,使用 requests 库发送原生 HTTP 请求

如果你不想引入完整的 openai SDK 依赖,可以使用 Python 社区广泛使用的第三方 HTTP 库 requests(需先执行 pip install requests 安装)构造请求头与 JSON 载荷:

import os
import requests

base_url = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1").rstrip("/")
api_key = os.getenv("LLM_API_KEY")
model_id = os.getenv("LLM_MODEL", "gpt-4o")

headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {api_key}"
}

payload = {
    "model": model_id,
    "messages": [
        {"role": "system", "content": "你是一个简洁的技术助手。"},
        {"role": "user", "content": "用一句话说明 HTTP POST 的作用。"}
    ],
    "temperature": 0.7,
    "max_tokens": 200
}

resp = requests.post(f"{base_url}/chat/completions", headers=headers, json=payload, timeout=30)
resp.raise_for_status()
data = resp.json()
print(data["choices"][0]["message"]["content"])

请求核心参数与返回体 JSON 结构深度解析

跑通首次请求后,在实际业务中常会遇到回复被截断、特定模型报参数不支持或不知如何统计 Token 消耗的问题。这需要准确理解核心请求参数与返回体结构。

1. 核心请求参数调优与兼容性注意

2. 完整返回体 JSON 结构与状态判断

非流式请求成功后,服务端返回的典型 JSON 结构如下:

{
  "id": "chatcmpl-92UiIWQaf442wq7Eyp7kF8ge0e3fE",
  "object": "chat.completion",
  "created": 1710381746,
  "model": "gpt-4o",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "API 是软件系统之间进行通信和数据交换的约定接口。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 22,
    "total_tokens": 40
  }
}

在业务代码中,不能仅凭 HTTP 200 就认为回答正常完成,务必检查以下两个字段:

进阶实操:流式输出(打字机效果)与多轮对话窗口裁剪

1. 流式输出(Streaming)与安全读取 Token 用量

当模型生成长文本时,等待完整响应往往需要数秒甚至数十秒。开启 stream=True 后,首个 Token 通常远早于完整响应到达(具体首字延迟取决于模型规模与网络状况),客户端可以边接收边渲染出“打字机效果”。

避坑注意:在 OpenAI 官方接口中,若需在流式模式下获取 Token 消耗统计,可传入 stream_options={"include_usage": True}(注:并非所有第三方兼容服务都支持该参数,若服务端报错提示未知参数,请去掉 stream_options)。开启该选项后,服务端会在流末尾额外发送一个 choices 为空数组 [] 的统计分块。遍历时必须先判断 if chunk.choices:,否则直接访问 chunk.choices[0] 会触发 IndexError:

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
)

stream = client.chat.completions.create(
    model=os.getenv("LLM_MODEL", "gpt-4o"),
    messages=[
        {"role": "user", "content": "请列出 3 条编写稳健 API 客户端的建议。"}
    ],
    stream=True,
    # 若所选第三方兼容接口不支持 stream_options,请删除下一行
    stream_options={"include_usage": True}
)

full_reply = []
for chunk in stream:
    # 关键防御:先判断 choices 非空,避免在末尾 usage 分块触发 IndexError
    if chunk.choices and chunk.choices[0].delta.content is not None:
        text_piece = chunk.choices[0].delta.content
        print(text_piece, end="", flush=True)
        full_reply.append(text_piece)
    
    # 读取末尾分块携带的整次请求 Token 用量
    if getattr(chunk, "usage", None) is not None:
        print(f"\n\n[用量统计] 输入: {chunk.usage.prompt_tokens}, "
              f"输出: {chunk.usage.completion_tokens}, "
              f"总计: {chunk.usage.total_tokens} Tokens")

2. 多轮对话状态维护与历史轮数裁剪

Chat Completions 接口本身是无状态的(Stateless),服务端不会自动保存上一轮对话。实现多轮对话的核心,是在本地维护一个 messages 列表,每次循环将用户的提问(user)和模型的回复(assistant)追加进列表一并发送。

为避免历史消息无限累积导致费用飙升,可以在循环中按轮数裁剪旧消息。注意:按消息条数裁剪只是快速控制规模的近似做法(若单条消息极长,严格防止超出上下文窗口需结合分词器如 tiktoken 估算 Token 总数);同时,裁剪时必须成对移除最早的一问一答(user + assistant),确保紧跟在 system 消息之后的第一条对话始终是 user 角色,避免部分严格校验角色顺序的服务端报错:

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
)

# 限制最多保留最近 2 轮历史问答(即 4 条 user/assistant 消息)+ 1 条当前提问
MAX_HISTORY_PAIRS = 2
messages = [
    {"role": "system", "content": "你是一个精炼的编程顾问,每次回答控制在80字以内。"}
]

sample_questions = [
    "第一问:Python 发送 HTTP 请求常用什么第三方库?",
    "第二问:它和 httpx 相比有什么主要区别?",
    "第三问:如果我要发起异步并发请求,选哪个更好?",
    "第四问:请给出一行安装该异步库的命令。"
]

for q in sample_questions:
    print(f"\n用户: {q}")
    messages.append({"role": "user", "content": q})
    
    # 当除 system 外消息数超过 (MAX_HISTORY_PAIRS * 2 + 1) 时,成对裁剪最早的问答
    # 保证 messages[1] 始终以 user 角色开头
    max_allowed_len = 1 + (MAX_HISTORY_PAIRS * 2) + 1
    if len(messages) > max_allowed_len:
        messages = [messages[0]] + messages[-(MAX_HISTORY_PAIRS * 2 + 1):]
        print(f"[系统提示] 已触发历史裁剪,当前上下文消息数: {len(messages)}")
        
    resp = client.chat.completions.create(
        model=os.getenv("LLM_MODEL", "gpt-4o"),
        messages=messages,
        max_tokens=200
    )
    
    assistant_reply = resp.choices[0].message.content
    print(f"助手: {assistant_reply}")
    
    # 将模型回复追加回列表,供下一轮对话使用
    messages.append({"role": "assistant", "content": assistant_reply})

生产环境防坑配置:超时控制、重试设置与错误状态码排查

1. 显式设置 timeout 与 max_retries 防止请求挂起

根据 openai-python 官方文档,SDK 默认请求超时时间长达 10 分钟(600 秒),且遇到连接错误、408、409、429 或 >=500 错误时默认自动重试 2 次。在生产业务中,建议显式缩短超时时间,并捕获异常读取响应头中的 x-request-id 以便排查问题:

import os
from openai import OpenAI, APIStatusError, APITimeoutError

# 将单次请求超时收紧为 20 秒,最大自动重试设为 1 次
client = OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"),
    timeout=20.0,
    max_retries=1
)

try:
    res = client.chat.completions.create(
        model=os.getenv("LLM_MODEL", "gpt-4o"),
        messages=[{"role": "user", "content": "ping"}],
        max_tokens=10
    )
except APITimeoutError:
    print("请求超时:请检查本地网络连通性或适当调大 timeout 参数。")
except APIStatusError as exc:
    req_id = exc.response.headers.get("x-request-id", "未知")
    print(f"HTTP 状态码: {exc.status_code}, x-request-id: {req_id}, 错误详情: {exc.message}")

2. 如何区分本地、网关与上游模型错误(状态码排错表)

调用报错时,切勿盲目写死循环重试。对照 OpenAI 官方错误码指南 与 OpenAI 速率限制文档,可通过状态码、返回体与响应头快速定位故障归属:

状态码与原始提示文字错误归属层级触发原因与特征区分修复与安全验证方法
APIConnectionError / Request timed out(无 HTTP 状态码)本地网络或客户端配置请求未能建立连接,常见于 base_url 域名拼写错误、本地代理未启动或防火墙阻断。核对 LLM_BASE_URL 拼写,使用终端 curl -I 测试目标域名连通性。
401 - Incorrect API key provided 或 401 - Invalid Authentication上游服务商或网关认证层环境变量未生效、密钥首尾含多余空格、密钥已吊销,或将 A 厂商的 Key 发往了 B 厂商的 base_url。切勿直接 echo 打印完整密钥(防泄露到终端或 CI 日志);请在 Bash 中用 echo "长度:${#LLM_API_KEY} 前缀:${LLM_API_KEY:0:6}" 检查长度与前缀,或重新创建密钥。
404 Not Found(返回 HTML 或 JSON model_not_found)网关路径或请求参数若返回 HTML 页面,通常是 base_url 漏写了 /v1 路径;若返回 JSON 提示模型不存在,则是 model 名称拼写错误或无权限。检查 LLM_BASE_URL 是否包含 /v1,并调用 GET /models 复制返回的准确模型 id。
429 - Rate limit reached for requests上游速率限制(RPM/TPM)短时间内请求并发过高,触发了每分钟请求数(RPM)或每分钟 Token 数(TPM)限制。检查响应头中是否有 Retry-After 或 x-ratelimit-reset-requests / x-ratelimit-reset-tokens,若有则按指定时间等待,否则使用指数退避(Exponential Backoff)重试。
429 - You exceeded your current quota, please check your plan and billing details上游账户账单层JSON 错误码/类型为 insufficient_quota;并非请求太快,而是账户预付额度耗尽、欠费或已达到月度消费硬上限。停止代码重试,登录服务商控制台检查账单余额(Billing)或上调月度用量限额(Limits)。
500 - The server had an error while processing your request / 503 - The engine is currently overloaded上游模型推理集群服务端返回 JSON 错误体,且响应头中通常带有 x-request-id;代表上游内部故障或算力暂时过载。稍作等待后按指数退避重试;若持续报错,记录响应头中的 x-request-id 并查看服务商官方状态页(Status Page)。
502 Bad Gateway / 504 Gateway Timeout(常见为 HTML 响应)中间代理、CDN 或聚合网关OpenAI 错误码指南未单独列出 502/504;此类响应多来自客户端与上游之间的反向代理、CDN 边缘节点或聚合网关超时。检查中间代理或网关节点状态,排查是否因单次生成耗时过长触发了网关层面的超时断开(可尝试改用 stream=True 流式输出保持连接活跃)。

常见问题

调用不同厂商的 LLM(如 DeepSeek、OpenAI 或本地模型)需要安装多套不同的 Python SDK 吗?

不需要。目前主流大模型厂商与本地推理工具普遍兼容 OpenAI API 协议。你只需在项目中安装一套官方 openai 库,在初始化客户端时通过环境变量切换 base_url、api_key 和 model 三个参数,即可用同一套代码调用不同模型。具体功能差异与价格以各厂商官方文档为准。

为什么同样返回 HTTP 429 错误,按照教程加了指数退避重试还是一直失败?

因为 HTTP 429 包含两种性质完全不同的错误:一种是触发每分钟请求或 Token 速率限制(Rate limit reached),读取响应头中的 Retry-After 或 x-ratelimit-reset-* 等待后重试即可恢复;另一种是账户配额耗尽(insufficient_quota,提示 exceeded your current quota)。遇到后者时代码重试无效,必须登录服务商后台充值或调高月度消费限额。

在流式输出(stream=True)模式下,为什么有时候代码在打印完最后一个字后会报 IndexError?

当你在流式请求中开启了 stream_options={"include_usage": True} 或使用部分兼容网关时,服务端发送的最后一个数据分块(Chunk)仅用于传输 usage 用量统计,其 choices 数组为空列表 []。遍历分块时必须先加 if chunk.choices: 判断,确认非空后再读取 chunk.choices[0].delta.content。

相关阅读

参考资料

  1. API Overview | OpenAI API Reference
  2. The official Python library for the OpenAI API - GitHub
  3. Error codes | OpenAI API Guides
  4. Your First API Call | DeepSeek API Docs(api-docs.deepseek.com)