01-Python调用大模型API-从第一次请求到可维护客户端
Python 调用大模型 API:从第一次请求到可维护客户端
系列:Python + 大模型应用开发(第 1 篇)
目标:使用 Python 完成一次大模型调用,并解决密钥、超时、异常和响应校验问题。
1. 为什么不能只满足于“调用成功”
很多入门示例只做三件事:把 API Key 写进代码、发送请求、打印结果。这能验证接口,却很难继续扩展成 RAG、Agent 或企业业务系统。
一个可以继续迭代的最小模型客户端,至少需要处理:
- API Key(接口密钥)不能硬编码;
- 网络请求必须有超时;
- 401、429、500 等状态需要区分;
- 外部返回的 JSON 不能盲目信任;
- 服务地址和模型名称应当可配置;
- 用户输入需要进行基本校验。
本文使用常见的兼容式 /chat/completions 接口演示。不同模型服务商的地址、模型名称和字段可能不同,实际参数必须以对应服务商的官方文档为准。
2. 从第一性原理理解模型 API
大模型 API 本质上是一次 HTTP 网络通信:
用户问题
↓
Python 组装 HTTP 请求
↓
模型服务接收并处理请求
↓
模型服务返回 HTTP 响应
↓
Python 校验响应并提取答案
一次常见请求由三部分组成:
- URL(请求地址);
- Headers(请求头),用于鉴权和声明数据格式;
- Body(请求体),包含模型名称、对话消息和生成参数。
示例请求体:
{
"model": "your-model-id",
"messages": [
{
"role": "system",
"content": "你是一名严谨的 Python 助手。"
},
{
"role": "user",
"content": "请解释 Python 列表和元组的区别。"
}
],
"temperature": 0.2
}
常见角色说明:
system:描述模型的任务、规则和边界;user:用户输入;assistant:模型在历史对话中的回答。
3. 创建项目
项目结构:
llm_api_demo/
├── config.py
├── llm_client.py
├── main.py
└── requirements.txt
创建虚拟环境并安装依赖:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install "requests>=2.31,<3"
requirements.txt:
requests>=2.31,<3
4. 使用环境变量保护密钥
不要这样写:
# 错误示例:代码一旦被上传或分享,真实密钥就可能泄露
API_KEY = "sk-真实密钥"
在 Windows PowerShell 当前窗口中设置环境变量:
$env:LLM_API_KEY = "替换为真实密钥"
$env:LLM_BASE_URL = "https://替换为模型服务地址/v1"
$env:LLM_MODEL = "替换为真实模型标识"
注意:示例地址不能直接使用。不同厂商的服务地址、模型标识和鉴权方法可能不同。
5. 集中读取配置
新建 config.py:
import os
from dataclasses import dataclass
@dataclass(frozen=True)
class Settings:
"""保存模型服务配置。
frozen=True 表示对象创建后字段不能被意外修改。
"""
api_key: str
base_url: str
model: str
def load_settings() -> Settings:
"""从环境变量读取配置,并在程序启动阶段完成校验。"""
# strip() 清除变量两侧可能存在的空格
api_key = os.getenv("LLM_API_KEY", "").strip()
base_url = os.getenv("LLM_BASE_URL", "").strip().rstrip("/")
model = os.getenv("LLM_MODEL", "").strip()
# 收集所有缺少的变量,一次性告诉使用者
missing_variables = []
if not api_key:
missing_variables.append("LLM_API_KEY")
if not base_url:
missing_variables.append("LLM_BASE_URL")
if not model:
missing_variables.append("LLM_MODEL")
if missing_variables:
names = ", ".join(missing_variables)
raise RuntimeError(f"缺少环境变量:{names}")
# 模型密钥会通过网络传输,因此示例要求使用 HTTPS
if not base_url.startswith("https://"):
raise RuntimeError("LLM_BASE_URL 必须使用 https:// 地址")
return Settings(
api_key=api_key,
base_url=base_url,
model=model,
)
把配置集中管理的价值在于:程序可以在真正请求模型前发现配置问题,而不是运行到一半才产生难以定位的错误。
6. 封装模型客户端
新建 llm_client.py:
from typing import Any
import requests
from config import Settings
class LLMError(RuntimeError):
"""表示模型调用过程中可以预期的错误。"""
class LLMClient:
"""一个最小但相对完整的大模型 HTTP 客户端。"""
def __init__(self, settings: Settings) -> None:
# 保存经过校验的配置
self.settings = settings
# Session 可以在多次请求之间复用底层 HTTP 连接
self.session = requests.Session()
def chat(
self,
user_message: str,
system_message: str = "你是一名严谨、准确的 AI 助手。",
temperature: float = 0.2,
) -> str:
"""向模型发送一轮对话,并返回模型文本。
Args:
user_message: 用户问题。
system_message: 模型需要遵守的系统要求。
temperature: 常见的随机性参数,本文示例限制在 0 到 2。
Returns:
模型返回的非空字符串。
Raises:
ValueError: 输入参数不合法。
LLMError: 网络、鉴权、限流或响应结构异常。
"""
# 用户输入来自程序外部,使用前必须校验
user_message = user_message.strip()
if not user_message:
raise ValueError("用户问题不能为空")
# 本文为了演示设置 0 到 2 的范围;真实范围以模型文档为准
if not 0 <= temperature <= 2:
raise ValueError("temperature 必须位于 0 到 2 之间")
# rstrip('/') 已在配置层处理,避免地址中出现双斜杠
request_url = f"{self.settings.base_url}/chat/completions"
# Bearer 后面必须有一个空格
request_headers = {
"Authorization": f"Bearer {self.settings.api_key}",
"Content-Type": "application/json",
}
# requests 会通过 json 参数把 Python 字典序列化成 JSON
request_body = {
"model": self.settings.model,
"messages": [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message},
],
"temperature": temperature,
}
try:
response = self.session.post(
request_url,
headers=request_headers,
json=request_body,
# 连接最多等待 5 秒,读取响应最多等待 60 秒
timeout=(5, 60),
)
except requests.exceptions.Timeout as exc:
# 使用 from exc 保留原始异常链,方便开发阶段定位问题
raise LLMError("模型请求超时,请稍后重试") from exc
except requests.exceptions.ConnectionError as exc:
raise LLMError("无法连接模型服务,请检查网络和接口地址") from exc
except requests.exceptions.RequestException as exc:
raise LLMError("模型请求发生网络异常") from exc
# 不同状态码代表不同类型的问题,不能统一当成“调用失败”
if response.status_code == 401:
raise LLMError("鉴权失败,请检查 API Key")
if response.status_code == 429:
raise LLMError("请求过于频繁或额度不足,请稍后重试")
if 400 <= response.status_code < 500:
raise LLMError(
f"模型请求参数错误,状态码:{response.status_code}"
)
if response.status_code >= 500:
raise LLMError(
f"模型服务暂时异常,状态码:{response.status_code}"
)
try:
# 把 JSON 响应转换成 Python 字典
data: dict[str, Any] = response.json()
except requests.exceptions.JSONDecodeError as exc:
raise LLMError("模型服务返回的内容不是有效 JSON") from exc
try:
# 常见兼容式响应中的模型文本位于以下路径
content = data["choices"][0]["message"]["content"]
except (KeyError, IndexError, TypeError) as exc:
# 请求成功不代表数据结构一定符合预期
raise LLMError("模型响应缺少预期字段") from exc
# 再次校验最终业务数据,防止返回 None 或空字符串
if not isinstance(content, str) or not content.strip():
raise LLMError("模型返回了空内容")
return content.strip()
def close(self) -> None:
"""释放 Session 持有的网络资源。"""
self.session.close()
7. 编写程序入口
新建 main.py:
from config import load_settings
from llm_client import LLMClient, LLMError
def main() -> None:
"""程序入口:加载配置、读取问题、调用模型、展示结果。"""
try:
# 先加载配置;配置错误时没有必要继续创建客户端
settings = load_settings()
client = LLMClient(settings)
except RuntimeError as exc:
print(f"配置错误:{exc}")
return
try:
# input() 返回字符串;具体输入校验由 chat() 统一负责
question = input("请输入你的问题:")
# 调用模型并取得最终文本
answer = client.chat(
user_message=question,
system_message="你是一名 Python 教师,请用初学者能理解的方式回答。",
temperature=0.2,
)
print("\n模型回答:")
print(answer)
except ValueError as exc:
print(f"输入错误:{exc}")
except LLMError as exc:
# 只向终端展示可理解的信息,不打印密钥和完整请求头
print(f"调用失败:{exc}")
finally:
# 无论调用成功还是失败,都释放网络资源
client.close()
if __name__ == "__main__":
main()
运行:
.\.venv\Scripts\python.exe main.py
8. 为什么要区分 HTTP 状态码
| 状态码 | 常见含义 | 建议处理 |
|---|---|---|
| 200 | 请求成功 | 解析并校验 JSON |
| 400 | 请求参数错误 | 检查请求体和模型名称 |
| 401 | 鉴权失败 | 检查 API Key,不应盲目重试 |
| 429 | 请求过多或额度受限 | 限流、延迟重试、检查额度 |
| 500—599 | 服务端异常 | 有限次数重试或执行降级 |
自动重试并非越多越好。401 通常是配置问题,重复请求不会自动恢复;无限重试还会增加系统压力和调用成本。
9. 对抗性审查:这还不是生产系统
当前代码已经适合作为后续学习的基础,但上线前仍然需要解决:
- 接口差异:并非所有厂商都支持同一种请求结构;
- 有限重试:只对网络抖动、429 和部分 5xx 使用指数退避;
- 日志脱敏:不能记录 API Key,也不能默认记录客户隐私;
- 成本控制:限制输入长度,统计 Token 和用户额度;
- 事实校验:请求成功不代表模型回答正确;
- 输入攻击:恶意输入可能诱导模型忽略原有规则;
- 权限控制:模型不应该因为用户的一句话就获得高风险操作权限。
10. 常见问题排查
返回 401
检查密钥是否正确、是否失效、是否有模型权限,以及鉴权格式是否符合服务商文档。
返回 404
检查服务地址和路径。网页首页地址通常不等于 API 地址,不要靠猜测拼接接口。
返回 429
可能是请求频率受限,也可能与账户额度有关,具体含义需要结合服务商响应和官方文档判断。
可以把密钥放到浏览器或小程序吗
不应该。前端代码和网络请求可能被检查。通常应由后端保存密钥,前端只调用自己的后端服务。
11. 总结
本文完成了一个可维护的大模型 API 客户端,并建立了以下工程意识:
- 敏感配置与代码分离;
- 所有外部输入都需要校验;
- 网络请求必须设置超时;
- 不同错误应该采用不同处理策略;
- 接口成功与业务答案正确是两件事;
- 模型调用代码应该被封装,避免散落在业务代码中。
下一篇将使用 FastAPI 把模型客户端封装成 HTTP 服务,让网页、小程序、企业微信侧边栏或其他后端系统都可以通过统一接口调用模型。
12. 练习题
- 输入空字符串,观察程序如何拦截;
- 设置错误的 API Key,观察 401 处理;
- 设置错误的服务地址,观察连接异常;
- 修改
temperature为 3,观察参数校验; - 思考哪些错误适合自动重试,哪些错误不适合;
- 尝试为模型响应增加 Token 用量提取,但必须先判断相应字段是否存在。
更多推荐

所有评论(0)