如何通过 API 调用一个 LLM:一套 OpenAI 兼容代码实现连通验证、流式输出与稳健调用
简答:配置服务端点(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/v1 | OpenAI 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.local2. 步骤一:用列模型请求验证连通性与核对模型 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. 核心请求参数调优与兼容性注意
messages(对话消息数组):定义对话上下文。其中role常用角色包括:system(或新推理模型中的developer)用于设定助手的角色约束与输出格式;user代表用户的提问;assistant存储模型此前的回复。temperature(采样温度):控制输出的随机性。在 OpenAI 接口中取值范围通常为0到2,数值越低(如0.1)结果越确定,适合代码生成与数据提取。兼容性提示:部分推理类模型(Reasoning Models)不支持自定义temperature,其他厂商的取值上限也可能为1.0,具体以各厂商文档为准。max_tokens/max_completion_tokens(最大生成长度):限制单次回复生成的最大 Token 数,防止模型输出失控导致单次账单过高。兼容性提示:OpenAI 较新的模型(尤其是推理模型)已将该参数更新为max_completion_tokens,而多数第三方 OpenAI 兼容接口仍使用max_tokens,调用时请以目标模型文档为准。stream(流式传输):布尔值,默认为false。设为true时服务端通过 Server-Sent Events (SSE) 逐块推送生成内容。
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 就认为回答正常完成,务必检查以下两个字段:
- 检查结束原因(
finish_reason):位于choices[0].finish_reason。当值为"stop"时,表示模型自然结束输出;当值为"length"时,说明回复因达到最大生成长度限制而被强制截断,需要调大限制参数或分段请求;若为"content_filter",代表触发了安全过滤;若为"tool_calls",表示模型请求调用外部工具函数。 - 拆解读取 Token 用量(
usage):位于顶层usage对象。prompt_tokens为输入提示词及历史上下文消耗量,completion_tokens为模型输出消耗量,total_tokens为总和。将该字段记录到业务日志中,即可对照官方定价页核算每次调用的真实成本。
进阶实操:流式输出(打字机效果)与多轮对话窗口裁剪
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。
相关阅读
参考资料
- API Overview | OpenAI API Reference
- The official Python library for the OpenAI API - GitHub
- Error codes | OpenAI API Guides
- Your First API Call | DeepSeek API Docs(api-docs.deepseek.com)