← 返回资讯

发出第一个大模型 API 请求:最小可用示例

2026-06-19

发出第一个大模型 API 请求 只需三样东西:API key、端点 URL、一条 messages 数组。本文用最少代码帮你在 5 分钟内从零跑出成功响应,再介绍如何读懂返回值。

第一步:用 curl 验证连通性

拿到 key 之后,最快的验证方式是直接跑 curl,排除网络和 key 问题:

export OPENAI_API_KEY="sk-xxx"
export OPENAI_BASE_URL="https://api.lidayun.com/v1"   # 你的端点

curl "$OPENAI_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "hello"}],
    "max_tokens": 32
  }'

成功时会返回一段 JSON,找到 choices[0].message.content 就是模型的回复。

这一步不是走过场,是排查地基。SDK 封装了太多细节,一旦出错你很难分清是网络问题、key 问题还是代码写错了参数名。先用 curl 把「网络能不能通、key 对不对、模型名有没有拼错」这三件事确认清楚,后面写代码出错时至少能排除掉一半的可能性。

卡住没反应时,把命令改成带 -m 10 -v

curl -m 10 -v "$OPENAI_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hello"}]}'

-m 10 给请求设 10 秒超时,避免命令行卡死到你怀疑人生;-v 会把完整的请求头、TLS 握手过程、响应头都打印出来。我见过最典型的一个坑:本地开了系统代理,no_proxy 又没配置对,curl 悄悄把请求走了代理转发出去,结果连不上目标端点,报错信息却是很笼统的 Failed to connect to api.xxx.com。这条报错看着像 DNS 或者服务器的问题,实际上跟 key 对不对没有任何关系,你越检查 key 越查不出问题——这时候 -v 里的 Trying xxx.xxx.xxx.xxx 那一行会告诉你请求根本没走到正确的地址上,一眼就能定位。

不同平台的 base_url 写法也不完全一样,接入前先核对清楚,路径写错最常见的表现就是 404:

平台base_url 示例备注
OpenAI 官方https://api.openai.com/v1需要能访问境外网络
DeepSeekhttps://api.deepseek.com/v1国内可直连
阿里云百炼(Qwen)https://dashscope.aliyuncs.com/compatible-mode/v1兼容模式路径比想象中长,容易漏写 /compatible-mode
力达云聚合入口https://api.lidayun.com/v1统一多平台模型的接入地址

具体计费单价、并发限额以各平台官方文档和账单页为准,这里不写死数字,因为几乎每个月都在变。

读懂响应结构

{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I help you today?"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 9,
    "total_tokens": 18
  }
}
字段含义
choices[0].message.content模型回复文本
choices[0].finish_reasonstop=正常结束;length=达到 max_tokens 截断
usage.prompt_tokens输入消耗的 token 数
usage.completion_tokens输出消耗的 token 数
usage.total_tokens本次请求总 token(计费依据)

finish_reason 除了 stoplength,还有两个新手容易忽略的值:content_filter(内容被安全策略拦截,content 可能是空的或者被截断,这不是你代码的 bug,是平台侧的审核策略生效了)、tool_calls(模型没有直接回答,而是决定调用你注册的工具函数,这时候 message.content 通常为 null,真正的信息在 message.tool_calls 里)。如果你的代码只判断 content 是否为空就报错,遇到 tool_calls 场景会误判成请求失败——这是我踩过的一个真实的坑,排查了小半天才发现根本不是接口挂了,是没处理这个分支。

流式响应(stream=True)的结构跟这里展示的非阻塞响应不一样:不是一次性拿到完整的 message,而是收到一连串只包含增量文本的 delta 片段,usage 字段也可能只在最后一个 chunk 里才给出,具体位置各平台实现略有差异,写代码前翻一下你接入的那个平台的文档确认。

Python 最小示例

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    base_url=os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "你好"}],
    max_tokens=64,
)
print(resp.choices[0].message.content)

三行核心逻辑:初始化 client → 调用 create → 读取 content

注意这里 api_key 用的是 os.environ["OPENAI_API_KEY"](方括号取值),base_url 用的是 os.environ.get(...)(get 方法取值)。这两种写法不是随手换着写的,是故意的:key 是必须项,没配置直接让程序崩溃报 KeyError,比悄悄传个空值进去然后拿到一个语焉不详的 401 要好排查得多;base_url 有默认值兜底,用 .get() 配一个默认值更合理。如果你把两处写法搞反了,最容易出现的现象是:本地忘了 export 环境变量,代码没报错也没崩溃,但请求全部返回 401,你会怀疑是 key 本身失效了,其实是 os.environ.get("OPENAI_API_KEY") 悄悄拿到了 None,SDK 用 None 拼了一个 Authorization: Bearer None 的请求头发出去,服务端自然把它当成无效 key 拒绝。

进阶:流式输出

如果你在做类似聊天界面的场景,用户等待完整回复会觉得卡顿,这时候流式输出能明显改善体验:

stream = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "你好,介绍一下你自己"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

流式的好处是首字延迟(TTFT,Time To First Token)低,用户能立刻看到文字一点点蹦出来,感知上比等 3 秒钟一次性出结果要快得多,即便两者总耗时其实差不多。但流式不是任何场景都该用:如果你要模型输出的是一段结构化 JSON,后续还要解析字段做后续逻辑,流式反而麻烦——你得自己拼接完整字符串再解析,还得处理半截 JSON 解析失败的情况;这类场景老老实实用非流式的 stream=False(默认值),拿到完整 content 再一次性 json.loads 更省心。判断标准很简单:给人看的、要即时反馈的场景用流式;给程序下一步处理的场景用非流式。

进阶:请求失败后的重试退避

线上环境请求量一大,偶尔撞上 429(限流)或者短暂的网络抖动是常态,别指望永远不失败,要写好重试逻辑:

import time
import random

def call_with_retry(client, **kwargs):
    for attempt in range(5):
        try:
            return client.chat.completions.create(**kwargs)
        except Exception as e:
            msg = str(e)
            if "429" not in msg and "rate" not in msg.lower():
                raise
            wait = (2 ** attempt) + random.random()
            time.sleep(wait)
    raise RuntimeError("重试 5 次仍失败,请检查配额或稍后再试")

这里用的是指数退避(exponential backoff)加抖动(jitter):等待时间是 1、2、4、8、16 秒依次翻倍,再叠加一个 0-1 秒的随机数。为什么不直接固定 sleep(1) 重试?因为限流场景下如果你的服务同时有几十个请求都在 429,大家都固定等 1 秒后再一起重试,等于集体又撞了一次限流,越重试越拥堵;加上随机抖动能把重试请求错开时间点,退避的间隔也随着失败次数拉长,给服务端足够的恢复窗口。另外要注意判断条件——只对 429/限流类错误重试,像 401(key 错误)、400(参数错误)这种重试再多次结果也不会变,应该让它直接抛出来,不然会白白浪费几十秒时间还掩盖了真实问题。

Node.js 最小示例

// node 18+,先 npm install openai
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
});

const resp = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "你好" }],
  max_tokens: 64,
});
console.log(resp.choices[0].message.content);

Node 版同理,baseURL 用了 ?? 空值合并运算符兜底默认地址,跟 Python 里 .get() 的思路一致。如果你要做流式,把 create 的参数加上 stream: true,然后用 for await 遍历:

const stream = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "你好" }],
  stream: true,
});
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}

这里用了可选链 ?.,因为流式响应的某些 chunk(比如最后一个携带 usage 统计的 chunk)里 delta 字段可能不存在,不加可选链直接访问会抛 TypeError: Cannot read properties of undefined,这是 Node 里接流式接口最常见的一个报错,加上 ?. 就能安全跳过。

常见错误速查

HTTP 状态码原因解决
401key 无效或未传检查 Authorization: Bearer sk-xxx
403key 无权限访问该模型确认 key 所属平台支持该模型名
404端点 URL 错误检查 base_url 末尾是否带 /v1
429超出速率限制降低频率或申请更高配额
500平台内部错误稍后重试,或检查模型服务状态

除了这五个标准状态码,实际接入时还会碰到几个更隐蔽的报错,同样值得记下来:

报错现象根因排查方法
context_length_exceeded / 400输入内容加上历史对话超过了模型的上下文窗口上限裁剪历史消息,或换用上下文窗口更大的模型;不要把整段长文档硬塞进一次请求
ECONNRESET / 请求超时无响应网络链路中断,或者服务端正在处理长任务响应慢给客户端设置合理的超时时间(不要用默认的无限等待),超时后走重试逻辑而不是让程序卡死
SSL/TLS 证书校验失败本机系统时间不准,或者流量被代理软件中间劫持解密校准系统时间;关闭或正确配置代理的证书信任链
返回内容是乱码或部分中文变问号客户端按错误编码解析响应体,通常是没有显式按 UTF-8 处理确认 HTTP 客户端读取响应时使用 UTF-8 解码,尤其是自己手写 HTTP 请求而不是用官方 SDK 时容易漏这一步

上线前算一笔账:这次调用到底花多少钱

不要拍脑袋估成本,用 usage.total_tokens 这个字段自己测出来。做法很简单:在测试环境里,用你产品真实会出现的那种输入(不是打个招呼的 “hello”,而是真实业务场景下用户可能输入的长度),跑上 100 次左右的请求,把每次返回的 usage.total_tokens 记下来求平均值,再套下面这个公式估算月成本:

月成本 ≈ 日均调用次数 × 平均 total_tokens × 单价(元/千 token) × 30

单价直接查你接入平台当前的官方计费页,不同模型档位差好几倍,一定要对应到你实际调用的那个 model 名称去查,不要用别的档位的价格估算,否则误差可能翻倍都不止。这套方法比上线前凭感觉估的准得多——我见过不少团队上线前完全没测过真实场景下的 token 消耗,结果上线第一周账单就远超预期,回头查才发现是因为业务里习惯把很长的历史对话原样带上,prompt_tokens 比测试时用的短对话高出好几倍。

常见问题

model 字段填什么? 填平台支持的模型名称,如 gpt-4o-minideepseek-chatqwen-plus 等;不同平台的模型名不通用,请查阅对应文档。

max_tokens 设多少合适? 测试阶段设 64-256 节省费用;生产中根据业务需要设 1024-4096,不设或设太大会导致不必要的等待和费用。

curl 成功但 Python 代码 401,为什么? Python 从环境变量读取时注意 os.environ["KEY"](会抛异常)vs os.environ.get("KEY")(返回 None),后者传入 None 会导致鉴权失败;确认变量名拼写一致。

流式和非流式返回的 token 消耗会不一样吗? 不会,usage.total_tokens 反映的是模型实际生成的内容量,跟你选择流式还是非流式取回没有关系,流式只是把同样的结果拆成多个片段传输而已,计费口径以官方账单为准。

要不要每次请求都新建一个 client 实例? 不需要,也不建议。OpenAI(...) 初始化的 client 对象内部维护了连接池,正确做法是在应用启动时创建一次、全局复用,每次请求都重新 new 一个会导致 TCP 连接反复建立,在并发量上来之后会明显拖慢响应速度。


更多接入方案见大模型 API 接入完全指南接入教程专题。了解如何构造多轮对话见messages 角色与多轮对话构造。需要统一多模型入口?申请力达云聚合 API 内测