LLMNotes

第 03 章:API 调用

zjc 于 2026-01-03 发布

这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 模型 API 是 LLM 应用的基础依赖。调用层要处理协议、参数、认证、超时、重试、限流、流式响应、错误分类和调用追踪。本章以 OpenAI 风格的 Chat Completions 接口为例讲解,工程原则同样适用于其他兼容或非兼容接口。

3.1 请求结构

{
  "model": "gpt-4.1-mini",
  "messages": [
    {"role": "system", "content": "你是严谨的企业知识库助手。"},
    {"role": "user", "content": "总结这份文档的核心风险。"}
  ],
  "temperature": 0.1,
  "max_tokens": 800,
  "stream": false
}
字段 说明
model 模型或部署名
messages 按顺序排列的对话上下文
temperature 采样随机性
max_tokens 输出上限
stream 是否流式返回
tools 可调用工具定义
response_format 输出格式约束

不同厂商的字段名和协议可能不同,调用层应做适配,不要让业务代码散落各种 SDK 细节。

3.2 Python 调用示例

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def ask(question: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4.1-mini",
        messages=[
            {"role": "system", "content": "只根据用户提供的资料回答。"},
            {"role": "user", "content": question},
        ],
        temperature=0.1,
        max_tokens=600,
        timeout=30,
    )
    return response.choices[0].message.content

密钥必须来自环境变量或密钥管理系统,不能写进代码和日志。

3.3 TypeScript 调用示例

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  timeout: 30_000,
});

export async function ask(question: string) {
  const res = await client.chat.completions.create({
    model: "gpt-4.1-mini",
    messages: [
      { role: "system", content: "只根据用户提供资料回答。" },
      { role: "user", content: question },
    ],
    temperature: 0.1,
    max_tokens: 600,
  });

  return res.choices[0]?.message?.content ?? "";
}

3.4 响应处理

典型响应包含:

字段 用途
choices 候选输出
finish_reason 停止原因
usage.prompt_tokens 输入 token
usage.completion_tokens 输出 token
id 请求追踪
created 创建时间

必须处理停止原因:

stop        正常结束
length      达到输出上限,内容可能被截断
tool_calls  需要执行工具
content_filter 被安全策略拦截

3.5 错误分类

错误 含义 处理
400 请求格式或参数错误 不重试,记录并修复
401 / 403 认证或权限失败 告警,检查密钥
404 模型或路径不存在 不盲目重试
408 / 504 超时 可有限重试
429 限流 指数退避或降级
5xx 服务端异常 换模型或降级
网络错误 连接失败 重试备用节点

只重试幂等请求。工具调用和写操作要先确认副作用。

3.6 重试与退避

import time
import random

def call_with_retry(fn, attempts=4):
    for i in range(attempts):
        try:
            return fn()
        except Exception as exc:
            if i == attempts - 1:
                raise
            sleep = min(2 ** i, 8)
            sleep += random.uniform(0, 0.3)
            time.sleep(sleep)
    raise RuntimeError("unreachable")

重试要设置:

  1. 最大次数;
  2. 总超时;
  3. 指数退避;
  4. 抖动;
  5. 可重试错误白名单;
  6. 熔断阈值;
  7. 备用模型。

3.7 超时与取消

一次用户请求通常包含多个阶段:

网关超时 30s
  -> 模型调用 20s
  -> 工具调用 5s
  -> 输出处理 1s

原则:

  1. 外层超时必须大于内层总耗时;
  2. 每个工具单独设置超时;
  3. 用户取消时释放资源;
  4. 流式请求定期发送心跳或空事件;
  5. 异步任务使用任务 ID 查询结果。

3.8 适配层设计

Business Code
  -> LLMGateway
     |-- provider adapter
     |-- prompt renderer
     |-- token estimator
     |-- retry / circuit breaker
     |-- rate limiter
     |-- cost recorder
     +-- tracer

接口示例:

from dataclasses import dataclass

@dataclass
class LLMRequest:
    prompt_name: str
    variables: dict
    model: str
    max_tokens: int = 800
    timeout: int = 30

@dataclass
class LLMResult:
    text: str
    model: str
    prompt_version: str
    input_tokens: int
    output_tokens: int
    latency_ms: int
    request_id: str

业务代码只关心语义,不直接绑定某一家 SDK。

3.9 观测记录

每次调用至少记录:

{
  "trace_id": "t-001",
  "user_id": "u-1001",
  "prompt_name": "order_answer",
  "prompt_version": "v3",
  "model": "gpt-4.1-mini",
  "input_tokens": 1830,
  "output_tokens": 210,
  "latency_ms": 2380,
  "status": "success",
  "cache_hit": false
}

不要记录完整密钥。正文可能包含个人数据,需要采样、脱敏和设置保留周期。

3.10 常见故障排查

现象 排查
一直 401 密钥、环境、部署区域
频繁 429 并发、速率、重试风暴
输出被截断 finish_reasonmax_tokens
延迟抖动 输入长度、输出长度、服务商状态
JSON 解析失败 格式约束和解析重试
结果不可复现 温度、采样、缓存、模型版本
工具不调用 schema、模型能力、参数格式

本章小结

模型 API 调用不是一行 SDK 代码,而是一个小型基础设施层。要统一封装模型、Prompt、参数、超时、重试、限流、成本和追踪,并对错误分类处理。调用层稳定后,上层的 RAG、Agent 和评测才有可靠基础。

思考题

  1. 哪些 HTTP 状态码不应该自动重试?
  2. finish_reason=length 时应如何处理?
  3. 为什么业务代码不应直接依赖具体厂商 SDK?
  4. 调用日志应该记录哪些字段,哪些字段需要脱敏?
  5. 如何避免重试导致成本放大?