【大模型Wiki】09 · LLM API 与应用网关
LLM API 与应用网关
整理自博客园旧笔记《OPENAI API应用文档》《搭建LLM one-api & ChatGPT-Next-Web》(2024-04),2026-09 更新。
本文分两层:先讲清楚 LLM API 的协议细节(你能直接调用的那一层),再讲 应用网关(当你要接多家模型、做密钥/配额/限流时的那一层)。理解这两层,你就有了把「一个模型」变成「一套可控、可观测、可替换的模型服务」的完整认知。
一、OpenAI API(事实标准)
OpenAI API 是整个行业的接口规范:各家模型(DeepSeek、Qwen、Moonshot、Ollama 本地模型……)都提供"OpenAI 兼容"端点,学会它就等于学会了所有。
核心端点:
/v1/chat/completions:对话生成(最常用)/v1/embeddings:文本向量化/v1/images/generations:图像生成/v1/audio/*:TTS / 语音转写/v1/moderations:内容审核
1.1、Chat Completions 基本调用
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "用一句话解释递归"}
]
}'
from openai import OpenAI
client = OpenAI() # base_url 换成任意兼容服务即可接入其他模型
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "用一句话解释递归"}],
)
print(completion.choices[0].message.content)
三种角色:system(设定人设与规则)、user(用户输入)、assistant(模型回复);多轮对话就是把历史 messages 全部带上。
1.1.1、关键请求参数
chat/completions 的常用参数远不止 model/messages:
| 参数 | 含义 | 说明 |
|---|---|---|
model |
模型名 | 不同厂商命名不同,网关层可自定义映射 |
messages |
消息历史 | 含 system/user/assistant/tool 四种角色 |
temperature |
随机性 | 0=稳定可复现,越高越发散(0~2) |
top_p |
核采样 | 与 temperature 二选一控制多样性 |
max_tokens |
最大输出 | 注意是输出上限,不含输入 |
n |
返回几条 | 默认 1,多条会翻倍计费 |
stop |
停止符 | 命中即停 |
stream |
流式 | true 时 SSE 逐 token 返回 |
logit_bias |
词表偏置 | 高级用法,调节特定 token 概率 |
response_format |
输出格式 | {"type":"json_object"} 或 json_schema 强制结构化 |
tools / tool_choice |
工具调用 | 见 1.2 |
seed |
随机种子 | 配合 temperature=0 提高可复现性 |
user |
终端用户标识 | 用于滥用监控、配额分摊 |
返回结构要点:choices[0].message 是模型回复;usage.prompt_tokens / completion_tokens / total_tokens 是本次 token 用量;finish_reason 表示结束原因(stop 正常结束、length 触达 max_tokens、tool_calls 触发工具调用)。
1.2、工具调用(Function Calling → Tool Calling)
模型本身不执行函数,只输出"该调用哪个函数、参数是什么"的 JSON:
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
resp = client.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools)
# resp 中含 tool_calls: get_weather(city="北京")
# -> 本地执行真实 API -> 把结果作为 role="tool" 消息回传 -> 模型总结
完整的工具调用往返:
- 用户提问 → 带
tools调模型。 - 模型返回
message.tool_calls(含函数名与 JSON 参数),finish_reason="tool_calls"。 - 本地执行真实函数,拿到结果。
- 把
role="tool"、带tool_call_id的消息追加进 messages,再次调模型。 - 模型综合工具结果生成最终回答。
这是 Agent 的地基(见 08 篇)。2026 年的演进:函数调用统一为 tools 参数;结构化输出(response_format: json_schema)可强制严格 JSON;DeepSeek/Qwen 等国产模型均已兼容。
1.3、Embeddings
文本 → 向量(语义相似度 ≈ 向量距离),是 RAG/搜索/聚类的底座:
resp = client.embeddings.create(
model="text-embedding-3-small", input="今天的天气真好")
print(resp.data[0].embedding) # 1536 维向量
注意
input可以是字符串列表,一次批量向量化更省请求;维度随模型不同(3-small=1536,3-large=3072)。向量库里索引与查询必须用同一模型。
1.4、多模态
# 图像理解:content 里混排 text 与 image_url(URL 或 base64)
messages = [{
"role": "user",
"content": [
{"type": "text", "text": "图里有什么?"},
{"type": "image_url", "image_url": {"url": "https://.../cat.jpg", "detail": "high"}},
],
}]
图像生成(DALL·E 3 / gpt-image-1)、TTS 语音合成、Whisper 语音转写各有独立端点;各家开源生态(SD、F5-TTS、FunASR)对应补位。
1.5、流式输出
stream = client.chat.completions.create(
model="gpt-4o", messages=messages, stream=True)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta: print(delta, end="")
SSE 逐 token 推送——所有聊天应用的"打字机效果"都来自它。
1.6、Token 计费与上下文成本
理解计费是工程落地的硬门槛:
- token ≈ 中文字符的 1.5~2 倍 / 英文词的 1.3 倍左右,并非字符数。
- 输入、输出单价不同,通常输出更贵(如输入 $2.5/百万、输出 $10/百万 的量级,具体以各厂商为准,本文不写死版本价格)。
- 一次请求的成本 =
(prompt_tokens × 输入价) + (completion_tokens × 输出价)。 max_tokens只限制输出;但 prompt 越长(含历史),输入成本越高,所以控制上下文窗口直接省钱。
实用做法:
# 用 tiktoken 估算 token(OpenAI 模型)
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
n = len(enc.encode("用一句话解释递归"))
省 cost 三板斧:减小 system 提示冗余、对长历史做摘要/滑动窗口、对不需要长输出的任务显式设小
max_tokens。
1.7、错误码与重试策略
API 调用不是总成功的。常见错误:
| HTTP | 含义 | 处理 |
|---|---|---|
| 400 | 请求体非法(参数/格式错) | 不要重试,先改请求 |
| 401 | key 无效/过期 | 检查密钥,不重试 |
| 403 | 无权限/地域限制 | 检查账号/套餐 |
| 404 | 模型或端点不存在 | 检查 model 名 |
| 429 | 限流/配额耗尽 | 指数退避重试 |
| 500/502/503 | 服务端错误 | 退避重试 |
| 超时 | 网络/模型慢 | 重试,避免无限等待 |
重试要用指数退避 + 抖动(jitter),并对可重试错误与不可重试错误区分对待:
import time, random
from openai import OpenAI, APIError, RateLimitError
client = OpenAI()
def chat_with_retry(messages, max_retries=3):
for i in range(max_retries):
try:
return client.chat.completions.create(
model="gpt-4o", messages=messages, timeout=30)
except RateLimitError:
wait = (2 ** i) + random.uniform(0, 1) # 1s, 2s, 4s + 抖动
print(f"限流,{wait:.1f}s 后重试 {i+1}/{max_retries}")
time.sleep(wait)
except APIError as e: # 5xx 也退避
if i == max_retries - 1: raise
time.sleep((2 ** i) + random.uniform(0, 1))
raise RuntimeError("重试耗尽")
SDK 也内置了重试(如
max_retries参数),生产建议显式设置;同时给每次请求加timeout防止挂死。
二、one-api:统一网关
项目:https://github.com/songquanpeng/one-api (新分支 new-api 功能更全)
解决的问题:应用方要接 N 家模型(OpenAI/DeepSeek/Qwen/本地 Ollama…),密钥、价格、限流各不相同。one-api 把它们全部转换成统一的 OpenAI 格式,并做渠道管理、负载均衡、令牌分发与额度控制。
docker run --name one-api -d --restart always \
-p 3000:3000 -e TZ=Asia/Shanghai \
-v /home/ubuntu/data/one-api:/data \
justsong/one-api
使用流程:
- 管理后台(:3000)→ 渠道:填入各家上游的 key/base_url,选支持模型
- 令牌:生成一个自己的 key 分发给应用
- 应用端
base_url=http://your-host:3000/v1,模型名随意路由到不同渠道
典型架构:
ChatGPT-Next-Web / Dify / 你的代码
│ 统一 OpenAI 格式
▼
one-api 网关 ──► OpenAI
├────────► DeepSeek
├────────► Qwen API
└────────► Ollama(本地)
2.1、网关核心原理
应用网关的本质是「协议转换 + 流量治理」。拆开看,它要解决四件事:
2.1.1、密钥管理(Key Management)
- 上游密钥集中在网关配置(渠道里),应用侧永远看不到真实厂商 key,降低泄漏风险。
- 网关向应用发放自有令牌(token),应用用它调网关;网关再拿对应渠道的上游 key 去请求厂商。
- 支持令牌级别的额度(quota):按元/按次/无限;用尽即拒,避免厂商账单失控。
2.1.2、配额与限流(Quota & Rate Limit)
- 配额:令牌总可用额度,请求扣减、余额不足返回 4xx。适合「给团队/客户发子钥匙」的场景。
- 限流:基于令牌/IP/渠道的 QPS 或并发限制,防止单用户打爆上游;常见算法是令牌桶(token bucket)。
- 计费换算:不同厂商计价单位不同,网关用统一的「内部额度单位」做换算后再扣减。
2.1.3、负载均衡(Load Balancing)
- 同一模型可配置多个渠道(如两个 DeepSeek key),网关按策略分发:轮询(round-robin)、随机、按权重、按健康度(自动剔除连续失败的渠道)。
- 某渠道 429/超时,自动故障转移(failover)到下一个可用渠道,对上层透明。
2.1.4、渠道抽象(Channel Abstraction)
网关把「OpenAI / Azure / DeepSeek / Claude / Gemini / Ollama …」抽象成统一接口:
请求 ──► 网关统一解析(model、messages、stream…)
──► 匹配渠道(按模型名 + 优先级)
──► 适配成该厂商的请求格式(有的要 Azure 特殊 header,有的要不同字段)
──► 转发上游
──► 把上游响应归一化成 OpenAI 格式回传
这正是「学会一套协议,接遍所有模型」的工程实现。
2.2、one-api 的数据库表与转发流程
one-api 用关系型数据库(SQLite 默认,生产可换 MySQL/PostgreSQL)持久化配置与账单。核心表(逻辑概念,字段随版本演进,下面为通用结构):
| 表 | 作用 | 关键字段 |
|---|---|---|
channels |
上游渠道 | id, type, key, base_url, models, priority, weight, status, balance |
tokens |
分发的令牌 | id, key(哈希), status, quota(剩余额度), used_quota, group, rate_limit |
users |
后台用户 | id, username, role, quota |
logs |
调用日志 | id, channel_id, token_id, model, prompt_tokens, completion_tokens, cost, status, created_at |
abilities |
模型能力映射 | model, channel_id, priority, weight, enabled |
一次请求在网关内的转发流程:
1. 应用 POST /v1/chat/completions,带自己的网关令牌
2. 网关校验令牌(存在?状态正常?是否超额?是否超限流?)
3. 根据请求 model 在 abilities 中匹配可用渠道(按 priority/weight)
4. 取出渠道的上游 key + base_url,把请求体适配为该厂商格式
5. 转发到上游;若失败且可重试,按 failover 切下一渠道
6. 把上游响应归一化为 OpenAI 格式(流式则逐块透传 SSE)
7. 统计用量(prompt/completion tokens),换算成额度,更新 tokens 表与 logs 表
8. 返回给应用
要点:日志与用量统计在第 7 步完成,所以网关天然就是「计量 + 审计」中心——谁、用哪个模型、花了多少 token,一目了然。
三、ChatGPT-Next-Web:开箱即用前端
项目:https://github.com/ChatGPTNextWeb/NextChat
一个精致的 ChatGPT 风格 Web UI,支持多模型、角色预设、对话导出、压缩历史、PWA。任何 OpenAI 兼容后端都能接:
docker run -d -p 3001:3000 \
-e OPENAI_API_KEY=sk-xxxx \
-e BASE_URL=http://your-one-api:3000 \
yidadaa/chatgpt-next-web
2026 年同类的选择还有 LobeChat(插件生态更丰富)、Open WebUI(主打接本地 Ollama)等;自建前端 + one-api 网关 + 多渠道模型,仍是个人/小团队自托管 LLM 服务的标准三件套。
四、从 API 调用到平台化
旧笔记写于 2024 年,当时的"应用开发"= 直接调 chat/completions。2026 年的分层已明显:
| 层 | 代表 | 职责 |
|---|---|---|
| 模型层 | GPT-5、DeepSeek-V3.2、Qwen3 | 推理与生成 |
| 网关层 | one-api/new-api、LiteLLM | 路由、鉴权、计费、限流 |
| 编排层 | LangChain/LangGraph、Dify、Coze | RAG、工作流、Agent 编排 |
| 协议层 | MCP(07 篇) | 工具/资源的标准化接入 |
| Agent 运行时 | DeepSeek Harness、Claude Code 等(09-10 篇) | 长任务自主执行 |
个人项目用"Next-Web + one-api"足够;做产品则建议编排层直接选 Dify/LangGraph,工具接入走 MCP。
五、实践细节与踩坑
- 把真实厂商 key 写进前端:绝对禁止。key 必须只存在于网关/后端,前端只持网关令牌。
- 忘了设 timeout:上游慢时请求挂死,线程池耗尽。→ 每次调用都设
timeout。 - 429 后暴力重试:把上游打得更死。→ 指数退避 + 抖动,区分可重试/不可重试错误。
- 上下文无限增长:历史越滚越长,成本与延迟飙升。→ 滑动窗口/摘要压缩。
- 网关单点:只跑一个 one-api 实例,挂了全公司不可用。→ 数据库放外部(MySQL/PG),one-api 起多副本 + 反代。
- 渠道 key 过期无感知:上游 key 失效,所有命中该渠道的请求全失败。→ 利用网关的渠道「余额/健康检查」自动停用,并告警。
- 模型名不一致:应用写
gpt-4o,但网关没配这个模型映射。→ 在网关把上游模型统一别名后再暴露给应用。 - 没开 CORS/没配 BASE_URL:前端直连报跨域或连到官方。→ 前端
BASE_URL指向网关,网关统一出口。 - 流式中断不处理:SSE 中途断连,前端卡住。→ 前端对 stream 做超时与重连,后端对上游断流做兜底。
- 额度单位混乱:不同厂商计费口径不同,自己手算容易错。→ 用网关内置计费换算,以网关 logs 为准。
六、docker-compose 部署示例(网关 + 前端)
把网关和前端一起编排,方便自托管:
# docker-compose.yml
version: "3.8"
services:
one-api:
image: justsong/one-api:latest
container_name: one-api
restart: always
ports:
- "3000:3000"
environment:
- TZ=Asia/Shanghai
- SQL_DSN=root:password@tcp(mysql:3306)/oneapi # 用外部 MySQL
volumes:
- ./one-api-data:/data
depends_on:
- mysql
mysql:
image: mysql:8.0
container_name: one-api-mysql
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=oneapi
volumes:
- ./mysql-data:/var/lib/mysql
next-web:
image: yidadaa/chatgpt-next-web:latest
container_name: next-web
restart: always
ports:
- "3001:3000"
environment:
- BASE_URL=http://one-api:3000
- OPENAI_API_KEY=sk-网关令牌 # 在 one-api 后台生成的令牌
启动:docker compose up -d,访问 :3000 配置渠道与令牌,next-web 用令牌连 :3000。
七、小结
- OpenAI 协议是行业事实标准:
chat/completions的请求参数(temperature、max_tokens、tools、response_format、stream)、返回结构(choices/usage/finish_reason)和错误码(429 限流、5xx 服务端),是和所有模型对话的通用语言。 - 流式(SSE)与 token 计费是工程必修:前者决定交互体验,后者决定成本;用退避重试 + timeout 守住可靠性。
- 网关解决的是「多厂商治理」:密钥集中、令牌配额、限流、负载均衡、渠道抽象。one-api 通过
channels/tokens/logs等表把配置与用量持久化,并以内置计费把各家价格归一。 - 转发流程就是「校验令牌 → 选渠道 → 适配格式 → 转发 → 归一响应 → 记账」,网关天然兼具计量与审计。
- 自托管标准三件套:Next-Web + one-api + 多渠道模型;做产品则上 Dify/LangGraph 编排层、MCP 接工具。
八、参考
- OpenAI API 文档:https://platform.openai.com/docs/api-reference
- one-api:https://github.com/songquanpeng/one-api
- new-api(新分支):https://github.com/Calcium-Ion/new-api
- LiteLLM 网关:https://github.com/BerriAI/litellm
- NextChat:https://github.com/ChatGPTNextWeb/NextChat

浙公网安备 33010602011771号