logo

大模型API接入实战:从密钥到稳定上线

作者:沙与沫2026.11.13 03:37浏览量:0

简介:本文面向后端开发者、应用开发者和技术负责人,按步骤讲解大模型API的接入过程,包括密钥配置、首次调用、系统提示、多轮对话、函数调用和图像输入。文中附可运行的Python示例,整理了鉴权失败、限流、上下文超长等常见错误的排查思路,并给出超时重试与流式输出等上线改造要点。

一、这篇教程适合谁

本文面向三类读者。后端开发者负责把大模型能力接入业务系统。应用开发者和全栈开发者构建对话、问答、内容生成类应用。技术负责人关注稳定性、成本和安全。

读之前,你需要了解HTTP请求和JSON格式,会写至少一门后端语言,并知道什么是token(词元)和上下文窗口。

读完本文,你应当能完成以下事情:

  1. 配置密钥并跑通第一次调用;
  2. 使用系统提示、多轮上下文、函数调用、图像输入这几项核心能力;
  3. 用一套方法验证功能、排查错误,并把服务推向生产环境。

本文的示例按业界常见的Chat Completions风格编写,请求路径为 /chat/completions,鉴权方式为 Bearer Token。你使用的服务商如果有差异,以其官方文档为准,代码中的路径、参数名和取值需要对照修改。

二、开始前的准备

2.1 账号与密钥

在服务商官方门户注册开发者账号,并按要求完成实名认证。企业用户建议使用企业主体注册,便于后续的配额管理和开票。

在密钥管理页面创建API密钥。多数平台只在创建时完整展示一次密钥,因此创建后立即保存到安全的位置。

密钥管理的基本要求如下:

  • 通过环境变量或密钥管理服务读取密钥,不要写进代码或提交到版本库;
  • 开发、测试、生产环境各自使用独立密钥,方便隔离与轮换;
  • 为密钥设置最小可用权限,并定期轮换;
  • 一旦发现泄露,立即吊销旧密钥并重新创建。

2.2 环境变量

准备三个环境变量。密钥不要出现在代码里,地址和模型名也建议放在配置中,方便切换环境。

  1. LLM_API_KEY = <你的API密钥>
  2. LLM_BASE_URL = <服务商提供的基础地址,例如 https://api.example.com/v1>
  3. LLM_MODEL = <要使用的模型标识>

在Linux或macOS中,可以用 export LLM_API_KEY=... 设置。在容器或CI环境中,通过平台的密钥注入功能配置。

2.3 需要先懂的概念

  • Token:模型处理文本的最小计量单位,也是计费单位。中文通常一个汉字约占一到两个token,具体取决于分词方式。
  • 上下文窗口:单次请求能携带的最大token总量。输入和输出共享这个额度。
  • 消息角色:system 用于设定指令,user 是用户输入,assistant 是模型回复,tool 用于回传函数执行结果。
  • 函数调用:模型输出结构化的调用意图,真正的函数由开发者的代码执行,结果再回传给模型。

三、第一次调用

先发一条只含一条user消息的最小请求。这样可以先确认鉴权、网络和参数三个环节都正常,之后叠加功能时排错范围会小很多。

  1. import os
  2. import requests
  3. API_KEY = os.environ["LLM_API_KEY"]
  4. BASE_URL = os.environ["LLM_BASE_URL"].rstrip("/")
  5. MODEL = os.environ["LLM_MODEL"]
  6. def chat(messages, temperature=0.7, max_tokens=1024, timeout=60):
  7. url = f"{BASE_URL}/chat/completions"
  8. headers = {
  9. "Authorization": f"Bearer {API_KEY}",
  10. "Content-Type": "application/json",
  11. }
  12. payload = {
  13. "model": MODEL,
  14. "messages": messages,
  15. "temperature": temperature,
  16. "max_tokens": max_tokens,
  17. }
  18. resp = requests.post(url, headers=headers, json=payload, timeout=timeout)
  19. resp.raise_for_status()
  20. return resp.json()
  21. if __name__ == "__main__":
  22. data = chat([{"role": "user", "content": "什么是上下文窗口?请用两句话回答。"}])
  23. print(data["choices"][0]["message"]["content"])
  24. print("token用量:", data.get("usage"))

验证要点有三个:HTTP状态码为200;返回的助手回复不为空;记录这次请求的耗时,作为之后性能比较的基线。

四、用系统提示约束模型行为

系统提示放在messages数组的第一位,用来规定角色、语气、输出格式和拒答策略。它通常是影响模型输出最直接的配置之一,但效果还取决于模型本身和任务难度,需要用实际样本测试。

写系统提示时注意以下几点:

  1. 明确角色和任务边界,例如”你是技术客服,只回答产品使用问题”;
  2. 规定输出结构,例如”先给结论,再列步骤”;
  3. 给出兜底策略,例如”无法确定答案时,告知用户并建议联系人工”;
  4. 避免冗长且互相矛盾的规则,指令越少越容易被遵守。
  1. messages = [
  2. {"role": "system", "content": "你是严谨的技术助手,使用简体中文回答。不确定的内容要明确说明,不要编造。"},
  3. {"role": "user", "content": "HTTP 401 错误通常是什么原因?"},
  4. ]

验证方法很直接:同一个问题分别带系统提示和不带系统提示各调用一次,对比两份回复的格式与语气,确认指令确实生效。

五、多轮对话与上下文管理

模型本身没有记忆。每次请求都要把需要的历史消息放进messages数组。把历史按顺序追加进去,就能实现多轮对话。

  1. history = [{"role": "system", "content": "你是技术助手,回答简洁。"}]
  2. def ask(question):
  3. history.append({"role": "user", "content": question})
  4. data = chat(history)
  5. answer = data["choices"][0]["message"]["content"]
  6. history.append({"role": "assistant", "content": answer})
  7. return answer
  8. print(ask("我的服务报 429 错误,怎么办?"))
  9. print(ask("那退避重试的等待时间应该怎么设置?")) # 模型应能引用前文的429问题

128K上下文并不意味着可以无限累积历史。历史越长,延迟越高,费用也越高。生产环境需要做裁剪,常用策略是”系统提示 + 最近N轮 + 必要的历史摘要”:

  1. def trim_history(messages, keep_turns=6):
  2. system = [m for m in messages if m["role"] == "system"]
  3. rest = [m for m in messages if m["role"] != "system"]
  4. return system + rest[-keep_turns * 2:]

这段代码按轮数裁剪,需要注意两点。第一,裁剪后要确认保留的消息之间没有断开的函数调用链,否则 tool 消息会失去对应的调用请求。第二,超长文档不要一次性塞入全文,应先分段摘要,再把摘要聚合后交给模型。

六、函数调用

函数调用是”模型决策,客户端执行”的协作方式。整个过程分五步:

  1. 在请求中声明可用函数,包括名称、用途描述和参数的JSON Schema;
  2. 用户提问,模型判断是否需要调用函数;
  3. 响应返回调用意图,即函数名和参数;
  4. 客户端校验参数,执行本地函数,拿到结果;
  5. 把结果作为 tool 消息回传,模型生成自然语言的最终回复。

下面是一个只读查询的函数声明:

  1. tools = [{
  2. "type": "function",
  3. "function": {
  4. "name": "get_order_status",
  5. "description": "根据订单号查询订单状态。仅用于查询,不执行任何修改操作。",
  6. "parameters": {
  7. "type": "object",
  8. "properties": {
  9. "order_id": {"type": "string", "description": "12位数字订单号"}
  10. },
  11. "required": ["order_id"],
  12. "additionalProperties": False,
  13. },
  14. },
  15. }]

客户端收到调用意图后,要先校验参数,再执行函数:

  1. import json
  2. import re
  3. def handle_tool_call(data, messages):
  4. msg = data["choices"][0]["message"]
  5. if not msg.get("tool_calls"):
  6. return None
  7. call = msg["tool_calls"][0]
  8. args = json.loads(call["function"]["arguments"])
  9. if call["function"]["name"] != "get_order_status":
  10. raise ValueError("模型请求了未声明的函数")
  11. if not re.fullmatch(r"\d{12}", args.get("order_id", "")):
  12. raise ValueError("订单号格式不正确")
  13. result = {"order_id": args["order_id"], "status": "已发货"} # 此处替换为真实查询
  14. messages.append(msg)
  15. messages.append({
  16. "role": "tool",
  17. "tool_call_id": call["id"],
  18. "content": json.dumps(result, ensure_ascii=False),
  19. })
  20. return chat(messages)

有三条注意事项。函数描述要写清楚用途和边界,模糊的描述会让模型乱调用。参数必须在客户端做严格校验,不能直接信任模型输出。涉及创建订单、删除数据这类真实操作时,要先让用户二次确认,再执行。

七、图像输入

图像内容通过消息中的数组形式传入。可以用图片URL,也可以用base64编码。

  1. messages = [{
  2. "role": "user",
  3. "content": [
  4. {"type": "text", "text": "这张截图里的报错是什么意思?"},
  5. {"type": "image_url", "image_url": {"url": "https://example.com/screenshot.png"}},
  6. ],
  7. }]

使用图像时注意以下几点:

  • 图像同样占用上下文额度,大图的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.

发表评论

活动