问题与目标
网页对话隐藏了认证、请求结构、超时、限流和用量统计。程序化调用必须把这些问题显式处理,否则一个可以偶尔返回结果的脚本,很难成为稳定模块。
本篇使用常见的 Chat Completions 兼容协议演示普通响应和 SSE 流式响应。不同服务商的字段、错误码和计费规则可能不同,运行前应以所选服务的文档为准。本篇不展开结构化输出和 Function Calling。

应用不应只保存最终文本,还应记录请求标识、模型、耗时、Token 用量和错误类型;密钥和完整敏感输入不能写入普通日志。
核心概念
请求的基本组成
- Endpoint:服务地址和接口路径。
- Authentication:通常通过请求头传递密钥。
- Model:服务端可识别的模型名称。
- Messages:按角色组织的对话内容。
- Generation parameters:温度、最大新增 Token 等。
- Timeout:连接、读取和整体等待上限。
普通响应与流式响应
普通响应等待生成结束后一次返回,代码简单,也容易获得完整用量。流式响应把新增内容按事件逐块传回,能降低用户感知等待时间,但需要处理半包、结束标记、中途中断和最终统计。
SSE 数据通常以多行事件传输,不能假设一次网络读取就是一个完整 Token。应使用客户端库提供的逐行或事件迭代接口。
哪些错误可以重试
网络瞬时失败、限流和部分服务端错误可以有限重试;认证失败、参数错误和内容超过上下文限制通常需要修正请求。重试应使用退避并设置上限,避免故障时制造更大流量。
可运行实现
安装依赖:
python -m pip install httpx
配置环境变量,名称保持通用:
export MODEL_API_BASE="https://provider.example/v1"
export MODEL_API_KEY="replace-with-your-key"
export MODEL_NAME="provider-model-name"
普通调用:
import os
import time
import httpx
BASE_URL = os.environ["MODEL_API_BASE"].rstrip("/")
API_KEY = os.environ["MODEL_API_KEY"]
MODEL = os.environ["MODEL_NAME"]
def chat(messages: list[dict], retries: int = 2) -> dict:
payload = {
"model": MODEL,
"messages": messages,
"temperature": 0.2,
"max_tokens": 200,
"stream": False,
}
for attempt in range(retries + 1):
try:
started = time.perf_counter()
response = httpx.post(
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
timeout=httpx.Timeout(30.0, connect=5.0),
)
response.raise_for_status()
data = response.json()
return {
"text": data["choices"][0]["message"]["content"],
"usage": data.get("usage", {}),
"request_id": response.headers.get("x-request-id"),
"elapsed_seconds": round(time.perf_counter() - started, 3),
}
except (httpx.TimeoutException, httpx.NetworkError):
if attempt == retries:
raise
time.sleep(2 ** attempt)
except httpx.HTTPStatusError as error:
if error.response.status_code not in {429, 500, 502, 503, 504} or attempt == retries:
raise
time.sleep(2 ** attempt)
result = chat([
{"role": "system", "content": "只整理输入中的事实,不补充处理建议。"},
{"role": "user", "content": "节点 NX-8 网络超时,切换线路后恢复。"},
])
print(result)
流式响应可以写成生成器:
import json
def stream_chat(messages: list[dict]):
payload = {
"model": MODEL,
"messages": messages,
"temperature": 0.2,
"max_tokens": 200,
"stream": True,
}
with httpx.stream(
"POST",
f"{BASE_URL}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
timeout=httpx.Timeout(60.0, connect=5.0),
) as response:
response.raise_for_status()
for line in response.iter_lines():
if not line.startswith("data:"):
continue
data = line.removeprefix("data:").strip()
if data == "[DONE]":
break
event = json.loads(data)
delta = event["choices"][0].get("delta", {})
content = delta.get("content")
if content:
yield content
for chunk in stream_chat([{"role": "user", "content": "用一句话总结网络状态。"}]):
print(chunk, end="", flush=True)
如果服务商的流式事件结构不同,应调整事件解析,而不是假设所有兼容接口完全一致。
常见问题与排查
把 API Key 写进代码或提交到仓库
使用环境变量或密钥管理服务,并在日志中脱敏。密钥泄漏后应立即吊销和轮换,删除 Git 中当前文件并不能清除历史提交。
所有错误都自动重试
参数错误和认证错误重试没有意义。按状态码和异常类型分类,并为请求设置幂等边界、最大次数和总体时间上限。
流式输出中途断开后直接拼接重试结果
重新请求通常会从头生成,直接追加可能重复内容。记录已展示状态,失败后明确提示重新生成或整体替换。
只记录字符数,不记录 Token
Token 用量与字符数不是固定比例。优先使用服务返回的 Usage;若服务不返回,再使用对应 Tokenizer 估算,并明确这是估算值。
小结
模型 API 的基础能力不只是拿到一段文字,还包括安全管理密钥、处理普通与流式响应、区分错误、有限重试并记录用量和耗时。完成这些之后,下一阶段才能可靠地组合输出解析、RAG 和工具调用。
License: CC BY-NC 4.0
Updated 2 hours ago
Was this article helpful? Give it a like.
0 comments


