这是《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")
重试要设置:
- 最大次数;
- 总超时;
- 指数退避;
- 抖动;
- 可重试错误白名单;
- 熔断阈值;
- 备用模型。
3.7 超时与取消
一次用户请求通常包含多个阶段:
网关超时 30s
-> 模型调用 20s
-> 工具调用 5s
-> 输出处理 1s
原则:
- 外层超时必须大于内层总耗时;
- 每个工具单独设置超时;
- 用户取消时释放资源;
- 流式请求定期发送心跳或空事件;
- 异步任务使用任务 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_reason 和 max_tokens |
| 延迟抖动 | 输入长度、输出长度、服务商状态 |
| JSON 解析失败 | 格式约束和解析重试 |
| 结果不可复现 | 温度、采样、缓存、模型版本 |
| 工具不调用 | schema、模型能力、参数格式 |
本章小结
模型 API 调用不是一行 SDK 代码,而是一个小型基础设施层。要统一封装模型、Prompt、参数、超时、重试、限流、成本和追踪,并对错误分类处理。调用层稳定后,上层的 RAG、Agent 和评测才有可靠基础。
思考题
- 哪些 HTTP 状态码不应该自动重试?
finish_reason=length时应如何处理?- 为什么业务代码不应直接依赖具体厂商 SDK?
- 调用日志应该记录哪些字段,哪些字段需要脱敏?
- 如何避免重试导致成本放大?