Loading

【大模型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" 消息回传 -> 模型总结

完整的工具调用往返:

  1. 用户提问 → 带 tools 调模型。
  2. 模型返回 message.tool_calls(含函数名与 JSON 参数),finish_reason="tool_calls"。
  3. 本地执行真实函数,拿到结果。
  4. 把 role="tool"、带 tool_call_id 的消息追加进 messages,再次调模型。
  5. 模型综合工具结果生成最终回答。

这是 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

使用流程:

  1. 管理后台(:3000)→ 渠道:填入各家上游的 key/base_url,选支持模型
  2. 令牌:生成一个自己的 key 分发给应用
  3. 应用端 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。

五、实践细节与踩坑

  1. 把真实厂商 key 写进前端:绝对禁止。key 必须只存在于网关/后端,前端只持网关令牌。
  2. 忘了设 timeout:上游慢时请求挂死,线程池耗尽。→ 每次调用都设 timeout。
  3. 429 后暴力重试:把上游打得更死。→ 指数退避 + 抖动,区分可重试/不可重试错误。
  4. 上下文无限增长:历史越滚越长,成本与延迟飙升。→ 滑动窗口/摘要压缩。
  5. 网关单点:只跑一个 one-api 实例,挂了全公司不可用。→ 数据库放外部(MySQL/PG),one-api 起多副本 + 反代。
  6. 渠道 key 过期无感知:上游 key 失效,所有命中该渠道的请求全失败。→ 利用网关的渠道「余额/健康检查」自动停用,并告警。
  7. 模型名不一致:应用写 gpt-4o,但网关没配这个模型映射。→ 在网关把上游模型统一别名后再暴露给应用。
  8. 没开 CORS/没配 BASE_URL:前端直连报跨域或连到官方。→ 前端 BASE_URL 指向网关,网关统一出口。
  9. 流式中断不处理:SSE 中途断连,前端卡住。→ 前端对 stream 做超时与重连,后端对上游断流做兜底。
  10. 额度单位混乱:不同厂商计费口径不同,自己手算容易错。→ 用网关内置计费换算,以网关 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 接工具。

八、参考

posted @ 2026-09-19 05:42  Stellan7  阅读(30)  评论(0)    收藏  举报