基础预备知识

1.1 什么是 API 返回值

客户端向大模型服务商服务器发送 HTTP/HTTPS 请求,服务器运算完成后传回的数据,统称为 API 返回值。

我们在 Python、Java、JavaScript 中使用的官方 SDK,本质只是封装了网络请求、JSON 序列化、对象映射。SDK 提供的response对象,内部承载的就是服务器下发的原始 JSON。开发者可以选择直接解析原始 JSON,或者使用 SDK 封装好的属性取值。

无论使用哪一种开发方式,底层原始 JSON 结构永远是标准。看懂原生 JSON,就不会被不同语言 SDK 的封装差异迷惑。

1.2 两种响应模式底层通信原理

1.2.1 非流式响应(stream=false)

通信模型:标准 HTTP 短连接。 完整流程:

  1. 客户端发起 POST 请求;
  2. 服务端接收请求,将 prompt 送入大模型;
  3. 模型完整生成全部输出文本;
  4. 服务端组装完整 JSON 数据包,一次性返回;
  5. HTTP 连接关闭。

优势:数据一次性到达,处理逻辑简单; 劣势:需要等待模型生成全部内容,首包延迟高,不适合聊天交互场景。

1.2.2 流式响应(stream=true)

通信模型:基于 SSE(Server-Sent Events)服务器推送长连接。 完整流程:

  1. 客户端发起 POST 请求,声明开启流式;
  2. HTTP 长连接持续保持;
  3. 模型每生成一小段文本,立刻封装为分片数据包实时推送;
  4. 客户端持续循环接收分片;
  5. 模型生成结束,推送结束标记分片,连接关闭。

优势:首字输出速度快,实现打字机动态效果,聊天产品标准方案; 劣势:需要循环迭代分片,手动拼接完整文本,代码容错逻辑更多。

1.3 关键名词定义

  1. Token:大模型处理文本的最小单位,中文大致 1 个 Token≈1.5 个汉字,输入、输出 Token 均为计费依据;
  2. Chunk:流式模式下,服务器推送的单条分片数据包;
  3. delta:增量差分数据,流式分片专属字段,代表本次新增生成内容;
  4. finish_reason:生成终止原因,用来判断 AI 回答停止输出的真实诱因;
  5. choices:候选回答数组,接口支持同时生成多条回答,绝大多数业务仅使用第 0 条结果。

模式一:非流式响应(stream=False)完整结构详解

当请求参数stream设置为 false 或者不传递该参数时,接口启用非流式模式。服务器等待文本全部生成完毕,返回单一完整 JSON 对象。

2.1 标准原始 JSON 样板

{
  "id": "chatcmpl-8k72jd9sk123abc",
  "object": "chat.completion",
  "created": 1756889231,
  "model": "glm-4-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "大模型API通过网络请求远程调用云端模型,不需要本地部署大模型。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 24,
    "completion_tokens": 26,
    "total_tokens": 50
  }
}

2.2 顶层一级字段逐字段深度解析

2.2.1 id

字段含义:本次对话请求全局唯一标识符。 数据类型:字符串。 业务作用:

  1. 日志追踪:程序记录日志时保存 id,在平台后台查询对应调用记录;
  2. 故障排查:出现回答异常、内容拦截、计费异常时,提供给厂商客服定位具体请求;
  3. 分布式系统中用于请求去重、链路追踪。

注意:每一次 API 调用都会生成全新 id,连续多轮对话的 id 互不相同。

2.2.2 object

字段含义:响应类型标记。 固定取值:chat.completion。 作用:区分接口类别,用于框架内部路由、数据解析判断。在兼容 OpenAI 协议的所有接口中,非流式对话响应 object 永远为此值。

2.2.3 created

字段含义:响应生成时间戳。 数据类型:整数,Unix 时间戳(秒级)。 可以通过代码转换为标准年月日时间,用于统计调用耗时、记录对话发生时间。

2.2.4 model

字段含义:实际执行本次推理的模型名称。 很多开发者会忽略这个字段。 场景价值:当配置存在路由分发、模型自动降级策略时,可以校验实际运行模型是否为预期模型,防止配置错误导致调用廉价 / 高价模型。

2.3 核心数组:choices

choices 是一个数组。协议设计初衷支持一次性生成多条不同回答(n 参数控制候选数量)。绝大多数业务场景 n=1,数组内仅存在一条元素,固定读取choices[0]

2.3.1 index

候选回答序号,n=1 时恒等于 0。开启多候选生成时,序号依次递增。日常开发几乎不需要使用。

2.3.2 message 对象(非流式专属核心对象)

message 内部包含两个子字段:role、content。

  1. role 固定取值:assistant。 语义代表:本条消息由大模型 AI 产生。 回顾请求参数 messages 数组三大角色:
  • system:系统提示词,设定 AI 规则;
  • user:人类用户提问;
  • assistant:AI 模型回复。 多轮对话开发时,我们拿到这条 message 后,需要组装同样结构放入下一轮 messages,维持对话上下文。
  1. content 业务最核心字段,保存 AI 完整回答文本。这是非流式模式下获取 AI 输出的唯一文本来源。

2.3.3 finish_reason(工程关键字段)

代表模型停止生成文本的原因,是线上业务必不可少的判断依据,支持以下标准枚举值:

  1. stop 正常终止。模型自主完成全部回答,没有受到外部限制,输出文本完整。
  2. length 达到请求参数max_tokens设置的最大输出上限,模型被强制截断文本。 业务处理建议:检测到此值,需要提示用户内容过长,回答被截断,或者自动精简上文上下文。
  3. content_filter 输出内容触发服务商内容安全审核策略,输出被拦截,文本大概率不完整或者为空。 业务处理建议:友好提示用户当前提问存在敏感内容,无法生成回答。
  4. null 非流式模式下不会出现 null,仅存在于流式中间分片。

2.2 usage 计费统计模块

usage 模块是成本管控核心,所有 Token 统计数据存放于此。

  1. prompt_tokens 提示词消耗 Token 总数。包含 system 提示词、全部历史对话、当前用户输入文本。
  2. completion_tokens AI 输出内容消耗 Token。
  3. total_tokens 本次请求输入 + 输出 Token 总和。

落地应用场景:

  1. 自建计费系统,面向内部用户或者外部客户按量收费;
  2. 设置额度预警,当累计 token 到达阈值主动停止调用;
  3. 优化 prompt,持续精简文本,降低调用成本。

2.3 非流式标准取值范式

原始 JSON 取值伪代码: data["choices"][0]["message"]["content"] 各类 SDK 封装后对象取值形式: response.choices[0].message.content

2.4 非流式模式典型边界场景

场景 1:content_filter 触发,content 为空字符串; 场景 2:触发 length 截断,文本结尾语句不完整; 场景 3:网络异常返回 4xx/5xx HTTP 错误,不存在完整响应 JSON。

模式二:流式响应(stream=True)完整结构详解

开启 stream=True 后,服务器不再返回单一完整 JSON,持续推送多条 SSE 分片数据包(chunk)。这里出现新手最大认知误区:很多开发者直接套用非流式取值方式,读取message.content,程序直接抛出异常。

铁律:流式分片数据包内不存在 message 对象,只有 delta 增量对象

3.1 普通分片标准 JSON 样板(生成过程中推送)

{
  "id": "chatcmpl-8k72jd9sk123abc",
  "object": "chat.completion.chunk",
  "created": 1756889232,
  "model": "glm-4-flash",
  "choices": [
    {
      "index": 0,
      "delta": {
        "content": "大模型API"
      },
      "finish_reason": null
    }
  ]
}

3.2 结束分片样板(模型生成完成最后一条数据包)

{
  "id": "chatcmpl-8k72jd9sk123abc",
  "object": "chat.completion.chunk",
  "created": 1756889235,
  "model": "glm-4-flash",
  "choices": [
    {
      "index": 0,
      "delta": {},
      "finish_reason": "stop"
    }
  ]
}

3.3 顶层一级字段解析

id、created、model 字段含义和非流式完全一致。 唯一区别字段:object 流式分片固定取值:chat.completion.chunk。 通过 object 字段可以在底层框架中区分当前数据包是完整响应,还是流式分片。

3.4 choices 数组内部结构详解

3.4.1 index

和非流式定义一致,多候选场景使用,常规业务恒为 0。

3.4.2 delta(流式专属增量对象)

delta 全称增量差分。含义:当前这一轮分片,模型新生成的文本,不包含历史所有内容。 重点特征:

  1. 一条分片内 delta.content 通常只有几个汉字、词语;
  2. 需要开发者在代码中定义字符串变量,循环累加所有分片内容,才能拼接出完整回答;
  3. delta 可能为空对象{},结束分片经常出现 delta 无 content;
  4. delta.content 有可能为 null。

很多新手报错根源:没有做空值判断,直接读取 content 进行字符串拼接。当 content 为 null,字符串与 null 合并直接触发程序异常。

3.4.3 finish_reason

中间持续生成的分片:finish_reason = null; 最后结束分片:finish_reason 填充 stop /length/content_filter。 业务开发规范:不要依靠分片关闭连接判断生成结束,优先识别 finish_reason 非 null 作为终止标志

3.5 流式取值逻辑标准流程

  1. 定义空字符串变量 reply,用于保存完整回答;
  2. 持续循环接收每一条 chunk 分片;
  3. 安全校验:判断 choices 数组是否存在、长度是否大于 0;
  4. 获取 delta 对象;
  5. 判断 delta.content 不为空且不为 null;
  6. 将增量文本追加至 reply,同时实时输出实现打字机效果;
  7. 循环结束后,reply 变量存储 AI 完整回答文本;
  8. 将完整回答组装为{"role":"assistant","content":reply}存入对话上下文。

3.6 新手高频致命错误汇总

错误 1:在循环 chunk 中使用chunk.choices[0].message.content 原因:流式数据包不存在 message 对象,直接属性访问报错。 错误 2:不判空,直接读取 delta.content,遇到 null 程序崩溃。 错误 3:把单条分片的 delta 内容直接当做完整回答,丢失绝大多数文本。

流式与非流式返回结构系统性对比

对比维度非流式 stream=False流式 stream=True
底层传输方式单次 HTTP 响应,短连接SSE 长连接,持续推送分片
顶层 object 值chat.completionchat.completion.chunk
文本载体对象messagedelta
文本字段message.contentdelta.content
数据获取方式一次取值即可循环迭代分片,手动拼接文本
finish_reason 状态响应内直接存在有效值中间分片为 null,仅最后分片填充结果
适用场景后台批量处理、数据分析、离线任务在线聊天、人机交互、前端实时展示
首字延迟较高较低
代码复杂度较高,需要增加容错与拼接逻辑

返回值常见枚举状态深度业务解读

5.1 finish_reason 全场景业务处理方案

  1. stop 正常结束。可以直接使用完整回答,存入对话上下文,继续下一轮提问。
  2. length 达到输出 token 上限,文本截断。 推荐策略: 方案 A:提示用户「回答过长,内容已截断,可以精简问题继续提问」; 方案 B:程序自动裁剪历史对话上下文,减少 prompt 长度,扩大可用输出空间。
  3. content_filter 内容安全拦截。两种细分情况:用户输入违规、AI 输出违规。 推荐策略:不展示残缺内容,返回标准化友好提示,同时记录日志用于审计。

5.2 HTTP 层面错误响应(不属于正常返回 JSON)

很多学习者只关注成功场景的返回结构,忽略异常 HTTP 状态码。当接口鉴权失败、余额不足、限流、模型不存在时,服务器不会返回上文标准 JSON,而是返回错误结构体。 常见状态码: 401:API 密钥错误、密钥过期、权限不足; 429:调用频率超限,触发限流; 400:请求参数错误(模型名错误、消息格式非法); 500:服务商服务内部异常。

错误响应示例(不同厂商格式略有差异,但都包含错误信息)

{
  "error": {
    "code": "invalid_api_key",
    "message": "提供的API Key不正确"
  }
}

工程规范:代码必须捕获 HTTP 异常,区分业务正常响应与错误响应,不能假定接口一定返回标准对话 JSON。

返回值工程开发规范(生产环境强制要求)

6.1 空值安全校验规范

  1. 非流式场景 读取choices[0]前,建议判断 choices 数组长度大于 0,防止极端空返回;
  2. 流式场景双重校验 ① 判断 chunk.choices 是否存在且非空; ② 判断 delta.content 不为 None、非空字符串。

伪代码安全模板:

reply = ""
for chunk in response:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta
    if delta.content:
        reply += delta.content

6.2 多轮对话上下文组装规范

无论流式还是非流式,想要实现 AI 记住历史对话,必须手动维护 messages 列表。 流程:

  1. 获取完整 AI 回答文本;
  2. 构造消息 {"role":"assistant","content":"完整文本"}
  3. append 存入全局 message 数组;
  4. 下一轮 API 调用携带完整 messages。

重要提醒:不能使用分片内零散 delta 片段直接存入上下文,必须使用拼接完成的完整文本

6.3 Token 用量监控规范

非流式可以直接读取 usage; ⚠️ 巨大缺陷:流式标准分片数据包不会携带 usage 字段! 痛点说明:绝大多数厂商流式响应结束分片不会返回 token 统计数据。 解决方案: 方案 1:使用第三方 Token 计算器,本地统计文本 token 数量; 方案 2:调用结束后主动查询平台账单接口获取消耗; 方案 3:业务上流式模式不做细粒度实时统计,采用定时汇总方式。

这是非常容易踩坑的知识点:很多开发者以为流式同样能拿到 usage,调试时发现所有 chunk 均无 usage 对象。

6.4 日志记录规范

建议每次调用存储以下返回信息: 请求 id、模型名称、finish_reason、输入文本摘要、输出文本摘要、token 消耗、调用时间。出现问题可以快速复盘。

更多推荐