大模型API接入实战:从密钥到稳定上线
作者:沙与沫2026.11.13 03:37浏览量:0简介:本文面向后端开发者、应用开发者和技术负责人,按步骤讲解大模型API的接入过程,包括密钥配置、首次调用、系统提示、多轮对话、函数调用和图像输入。文中附可运行的Python示例,整理了鉴权失败、限流、上下文超长等常见错误的排查思路,并给出超时重试与流式输出等上线改造要点。
一、这篇教程适合谁
本文面向三类读者。后端开发者负责把大模型能力接入业务系统。应用开发者和全栈开发者构建对话、问答、内容生成类应用。技术负责人关注稳定性、成本和安全。
读之前,你需要了解HTTP请求和JSON格式,会写至少一门后端语言,并知道什么是token(词元)和上下文窗口。
读完本文,你应当能完成以下事情:
- 配置密钥并跑通第一次调用;
- 使用系统提示、多轮上下文、函数调用、图像输入这几项核心能力;
- 用一套方法验证功能、排查错误,并把服务推向生产环境。
本文的示例按业界常见的Chat Completions风格编写,请求路径为 /chat/completions,鉴权方式为 Bearer Token。你使用的服务商如果有差异,以其官方文档为准,代码中的路径、参数名和取值需要对照修改。
二、开始前的准备
2.1 账号与密钥
在服务商官方门户注册开发者账号,并按要求完成实名认证。企业用户建议使用企业主体注册,便于后续的配额管理和开票。
在密钥管理页面创建API密钥。多数平台只在创建时完整展示一次密钥,因此创建后立即保存到安全的位置。
密钥管理的基本要求如下:
- 通过环境变量或密钥管理服务读取密钥,不要写进代码或提交到版本库;
- 开发、测试、生产环境各自使用独立密钥,方便隔离与轮换;
- 为密钥设置最小可用权限,并定期轮换;
- 一旦发现泄露,立即吊销旧密钥并重新创建。
2.2 环境变量
准备三个环境变量。密钥不要出现在代码里,地址和模型名也建议放在配置中,方便切换环境。
LLM_API_KEY = <你的API密钥>LLM_BASE_URL = <服务商提供的基础地址,例如 https://api.example.com/v1>LLM_MODEL = <要使用的模型标识>
在Linux或macOS中,可以用 export LLM_API_KEY=... 设置。在容器或CI环境中,通过平台的密钥注入功能配置。
2.3 需要先懂的概念
- Token:模型处理文本的最小计量单位,也是计费单位。中文通常一个汉字约占一到两个token,具体取决于分词方式。
- 上下文窗口:单次请求能携带的最大token总量。输入和输出共享这个额度。
- 消息角色:
system用于设定指令,user是用户输入,assistant是模型回复,tool用于回传函数执行结果。 - 函数调用:模型输出结构化的调用意图,真正的函数由开发者的代码执行,结果再回传给模型。
三、第一次调用
先发一条只含一条user消息的最小请求。这样可以先确认鉴权、网络和参数三个环节都正常,之后叠加功能时排错范围会小很多。
import osimport requestsAPI_KEY = os.environ["LLM_API_KEY"]BASE_URL = os.environ["LLM_BASE_URL"].rstrip("/")MODEL = os.environ["LLM_MODEL"]def chat(messages, temperature=0.7, max_tokens=1024, timeout=60):url = f"{BASE_URL}/chat/completions"headers = {"Authorization": f"Bearer {API_KEY}","Content-Type": "application/json",}payload = {"model": MODEL,"messages": messages,"temperature": temperature,"max_tokens": max_tokens,}resp = requests.post(url, headers=headers, json=payload, timeout=timeout)resp.raise_for_status()return resp.json()if __name__ == "__main__":data = chat([{"role": "user", "content": "什么是上下文窗口?请用两句话回答。"}])print(data["choices"][0]["message"]["content"])print("token用量:", data.get("usage"))
验证要点有三个:HTTP状态码为200;返回的助手回复不为空;记录这次请求的耗时,作为之后性能比较的基线。
四、用系统提示约束模型行为
系统提示放在messages数组的第一位,用来规定角色、语气、输出格式和拒答策略。它通常是影响模型输出最直接的配置之一,但效果还取决于模型本身和任务难度,需要用实际样本测试。
写系统提示时注意以下几点:
- 明确角色和任务边界,例如”你是技术客服,只回答产品使用问题”;
- 规定输出结构,例如”先给结论,再列步骤”;
- 给出兜底策略,例如”无法确定答案时,告知用户并建议联系人工”;
- 避免冗长且互相矛盾的规则,指令越少越容易被遵守。
messages = [{"role": "system", "content": "你是严谨的技术助手,使用简体中文回答。不确定的内容要明确说明,不要编造。"},{"role": "user", "content": "HTTP 401 错误通常是什么原因?"},]
验证方法很直接:同一个问题分别带系统提示和不带系统提示各调用一次,对比两份回复的格式与语气,确认指令确实生效。
五、多轮对话与上下文管理
模型本身没有记忆。每次请求都要把需要的历史消息放进messages数组。把历史按顺序追加进去,就能实现多轮对话。
history = [{"role": "system", "content": "你是技术助手,回答简洁。"}]def ask(question):history.append({"role": "user", "content": question})data = chat(history)answer = data["choices"][0]["message"]["content"]history.append({"role": "assistant", "content": answer})return answerprint(ask("我的服务报 429 错误,怎么办?"))print(ask("那退避重试的等待时间应该怎么设置?")) # 模型应能引用前文的429问题
128K上下文并不意味着可以无限累积历史。历史越长,延迟越高,费用也越高。生产环境需要做裁剪,常用策略是”系统提示 + 最近N轮 + 必要的历史摘要”:
def trim_history(messages, keep_turns=6):system = [m for m in messages if m["role"] == "system"]rest = [m for m in messages if m["role"] != "system"]return system + rest[-keep_turns * 2:]
这段代码按轮数裁剪,需要注意两点。第一,裁剪后要确认保留的消息之间没有断开的函数调用链,否则 tool 消息会失去对应的调用请求。第二,超长文档不要一次性塞入全文,应先分段摘要,再把摘要聚合后交给模型。
六、函数调用
函数调用是”模型决策,客户端执行”的协作方式。整个过程分五步:
- 在请求中声明可用函数,包括名称、用途描述和参数的JSON Schema;
- 用户提问,模型判断是否需要调用函数;
- 响应返回调用意图,即函数名和参数;
- 客户端校验参数,执行本地函数,拿到结果;
- 把结果作为
tool消息回传,模型生成自然语言的最终回复。
下面是一个只读查询的函数声明:
tools = [{"type": "function","function": {"name": "get_order_status","description": "根据订单号查询订单状态。仅用于查询,不执行任何修改操作。","parameters": {"type": "object","properties": {"order_id": {"type": "string", "description": "12位数字订单号"}},"required": ["order_id"],"additionalProperties": False,},},}]
客户端收到调用意图后,要先校验参数,再执行函数:
import jsonimport redef handle_tool_call(data, messages):msg = data["choices"][0]["message"]if not msg.get("tool_calls"):return Nonecall = msg["tool_calls"][0]args = json.loads(call["function"]["arguments"])if call["function"]["name"] != "get_order_status":raise ValueError("模型请求了未声明的函数")if not re.fullmatch(r"\d{12}", args.get("order_id", "")):raise ValueError("订单号格式不正确")result = {"order_id": args["order_id"], "status": "已发货"} # 此处替换为真实查询messages.append(msg)messages.append({"role": "tool","tool_call_id": call["id"],"content": json.dumps(result, ensure_ascii=False),})return chat(messages)
有三条注意事项。函数描述要写清楚用途和边界,模糊的描述会让模型乱调用。参数必须在客户端做严格校验,不能直接信任模型输出。涉及创建订单、删除数据这类真实操作时,要先让用户二次确认,再执行。
七、图像输入
图像内容通过消息中的数组形式传入。可以用图片URL,也可以用base64编码。
messages = [{"role": "user","content": [{"type": "text", "text": "这张截图里的报错是什么意思?"},{"type": "image_url", "image_url": {"url": "https://example.com/screenshot.png"}},],}]
使用图像时注意以下几点:
- 图像同样占用上下文额度,大图的token成本更高,上传前先压缩;
- 支持的格式和体积上限以官方文档为准,上线前逐项核对;
- 使用URL时,确认地址可以被服务端访问,并设置过期控制;
- 含有身份证、银行卡等敏感信息的图片,先脱敏再上传。
八、上线前的工程化改造
8.1 超时与重试
网络错误和服务端5xx错误适合用指数退避重试。429限流响应应优先遵守 Retry-After 响应头。鉴权错误和参数错误不要重试,重试只会浪费配额。
```python
import time
RETRYABLE = {429, 500, 502, 503, 504}
def post_with_retry(url, headers, payload, max_retries=3, timeout=60):
for attempt in range(max_retries + 1):
try:
resp = requests.post(url, headers=headers, json=payload, timeout=timeout)
except requests.RequestException:
if attempt == max_retries:
raise
time.sleep(2 ** attempt)
continue
if resp.status_code in RETRYABLE and attempt < max_retries:
wait = resp.

登录后可评论,请前往 登录 或 注册