我们经常看到这样的 URL 后缀:

javascript
/v1/chat/completions
/v1/responses
/v1/messages
/v1/embeddings
/v1/models
/v1beta/models/{model}:generateContent
/openai/v1/chat/completions
/api/v1/chat/completions

这些是模型厂商给模型能力定的接口协议。后缀规定了某个模型该用哪种协议跟它说话。

javascript
https://api.openai.com/v1/chat/completions
        └────────────┬────────────┘
                 API Base URL
                         └─ /v1              API 协议版本
                            └─ /chat         聊天
                                  └─ /completions  生成聊天补全

/v1 约束的是请求字段、响应字段、错误格式、流式事件格式、工具调用格式这些接口规范。同一个模型可能同时支持 /v1/chat/completions 和 /v1/responses 两种协议。反过来,同一个 /v1/chat/completions 协议也被 OpenAI、Mistral、xAI、DeepSeek、Groq、OpenRouter 等不同平台支持,但兼容程度并不一致。

这篇是"AI从0到1"系列的第一篇,想把这件事从头讲清楚:协议是怎么一步步演出来的,每一步为什么这么改,实际接入时该注意什么。

/v1/completions

早期的大语言模型是纯续写模型,模型不知道自己在聊天。你给一段 prompt,它接着往下写:

javascript
POST /v1/completions
{
  "model": "gpt-3",
  "prompt": "Translate this sentence into Chinese: Hello, how are you?"
}

模型不知道什么是系统消息,什么是用户输入。想做对话,得自己拼字符串:

javascript
System: You are a helpful assistant.
User: Hello.
Assistant: Hi, how can I help?
User: Explain API suffixes.
Assistant:

这样有两个麻烦。模型只能靠猜哪部分是系统说的、哪部分是用户说的,破限的空子更大。对话历史也没法结构化取出来,一旦要接工具调用,或者图片、音频这些多模态输入,就很难优雅地表达。

/v1/chat/completions

2023 年 3 月 1 日,OpenAI 随 gpt-3.5-turbo 一起放出了 /v1/chat/completions。这套接口后来成了行业事实标准。OpenAI 自己在发布 Responses API 的博客里说,它是一个周末里搭出来的(Why we built the Responses API)。

javascript
POST /v1/chat/completions
{
  "model": "gpt-5.5",
  "messages": [
    {"role": "system", "content": "You are a concise technical explainer."},
    {"role": "user", "content": "Explain what /v1/chat/completions means."}
  ]
}

messages 数组给了对话结构:system 定边界,user 是输入,assistant 是模型之前的回复,tool 是工具调用结果。上下文从一整段字符串,变成了一组带角色、带顺序的消息。

这套接口跑得很快。到 2023 年 7 月,Chat Completions 已经占了 API 里 GPT 调用量的 97%(OpenAI Developer Community)。2023 年 6 月 13 日,OpenAI 给它加上了 function calling。模型第一次能可靠地吐出结构化 JSON,去调用外部函数(Grokipedia)。

往后几年,这套协议原样被搬到了别的平台上:

javascript
OpenAI:       /v1/chat/completions
Mistral:      /v1/chat/completions
xAI:          /v1/chat/completions
DeepSeek:     /chat/completions 或 OpenAI 兼容路径
Groq:         /openai/v1/chat/completions
OpenRouter:   /api/v1/chat/completions
Together AI:  /v1/chat/completions

整个基础设施都已经绕着这套协议长起来了:SDK、AI IDE、聊天前端、RAG 框架。直接复用,只需要改三样东西:

javascript
base_url
api_key
model

原来调用 OpenAI:

python
from openai import OpenAI

client = OpenAI(
    api_key="OPENAI_API_KEY",
    base_url="https://api.openai.com/v1"
)

response = client.chat.completions.create(
    model="gpt-5.5",
    messages=[{"role": "user", "content": "Hello"}]
)

切到别的兼容服务,通常只改 base_url 和 api_key:

python
client = OpenAI(
    api_key="OTHER_PROVIDER_API_KEY",
    base_url="https://api.other-provider.com/v1"
)

但"兼容"不代表"完全兼容"。至少可以分成六层:

javascript
Level 1:普通文本聊天兼容
Level 2:支持流式输出
Level 3:支持函数调用 / 工具调用
Level 4:支持结构化输出
Level 5:支持多模态输入
Level 6:支持复杂 agent 事件流和状态管理

大部分平台停在 Level 1-3。tools、parallel_tool_calls、response_format、JSON schema strict mode、vision/audio 输入、reasoning tokens、cached tokens 这些不一定齐全。

/v1/messages

Anthropic 的 Messages API 在 2023 年上线,接口版本头是 anthropic-version: 2023-06-01。它取代的是更早的 Text Completions 接口,按 \n\nHuman: / \n\nAssistant: 拼字符串那种(Migrating from Text Completions)。

javascript
POST /v1/messages
{
  "model": "claude-sonnet-4-8",
  "max_tokens": 1024,
  "system": "You are a careful technical explainer.",
  "messages": [
    {"role": "user", "content": "Explain /v1/messages."}
  ]
}

和 OpenAI 长得像,但不是一回事。OpenAI 把 system 也塞进 messages 数组里,当一条消息。Anthropic 把它拎成了顶层参数 system,不跟对话轮次混在一起。这是刻意的设计取舍。

响应结构也不同。Chat Completions 常见解析路径是:

python
text = response.choices[0].message.content

Claude Messages 是:

python
text = response.content[0].text

content 是 block 数组,文本、图片、工具调用结果都是不同类型的 block。响应里还带 stop_reason(end_turn、tool_use、pause_turn、refusal 等)和 usage.input_tokens / usage.output_tokens。OpenAI 和 Claude 的响应不能直接互换解析,这是两套协议,不是一套协议的两种方言。

generateContent

Gemini 的 REST 路径长这样:

javascript
POST /v1beta/models/{model}:generateContent

或者:

javascript
POST /v1/models/{model}:generateContent

这跟 OpenAI、Anthropic 的命名风格明显不是一路。拆开看:/v1 或 /v1beta 是 API 版本,v1 稳定,v1beta 是可能变化的预览能力。/models/{model} 是模型这个资源。:generateContent 是对这个资源执行的一个方法。

这是 Google API 一贯的"资源 + 方法"命名哲学,继承自更早的 PaLM API。PaLM 同样是无状态设计,每次请求都要把完整对话历史通过 contents(role + parts)传一遍。

现在这套设计正在被 Google 自己标成 legacy。较新的 Interactions API 引入了 previous_interaction_id,状态存到服务端,不用每次都重发全部历史。这跟 OpenAI 从 Chat Completions 走向 Responses API 是同一个剧本:无状态到有状态。

/v1/responses

2023 年 11 月,OpenAI 出过一个 Assistants API。model、instructions、tools 打包成一个持久化的 Assistant 对象,Thread 存对话,Run 异步执行,得轮询或者等 webhook 才能知道跑完没有。

它最大的痛点是计费。每发一条消息,服务端都要把整个 thread 重新处理一遍。上传文件的全文也在里面(eesel AI)。

2025 年 3 月 11 日,OpenAI 发布 Responses API。同时放出来的还有 Agents SDK,和一批内置工具:网页搜索、文件搜索、computer use(OpenAI Developers)。这是新一代 agent 基础设施。

javascript
POST /v1/responses
{
  "model": "gpt-5.5",
  "input": [
    {
      "role": "user",
      "content": [{"type": "input_text", "text": "Search the web and summarize the result."}]
    }
  ],
  "tools": [{"type": "web_search"}]
}

Chat Completions 这个名字带着两个历史词:chat 和 completions。骨子里还是"给一段聊天记录,补全下一条"。但现在的模型要做的事情多了:读图片、处理音频、查文件、搜网页、调函数、用代码解释器、接 MCP 工具、维护服务端状态、输出结构化 JSON、吐推理摘要、产生一串中间事件。全塞进 chat.completion 这一个对象里,只会越来越别扭。Responses API 把输入变成 input/items,输出变成一个可以装多个 output item 的 response 对象。这套结构是专门为这些场景设计的。

Assistants API 拿到功能对等后被判了死刑。2025 年 8 月 26 日,OpenAI 通知开发者。2026 年 8 月 26 日,彻底下线(OpenAI Developer Community)。Assistant 变成 Prompt,Thread 变成 Conversation,Run 变成 Response,Run Step 变成 Item。这不是换个端点名字,是整套对象模型重来一遍。

图片生成协议的演进

早期的图片生成模型跟文本模型没有共同语言。Stable Diffusion 2022 年开源的时候,官方脚本是 txt2img.py,参数是 --scale 7.5(guidance scale)、50 步 PLMS 采样、512x512(Hugging Face Diffusers 文档)。社区把这套参数包成了 REST 接口,AUTOMATIC1111 的 WebUI 定下了 /sdapi/v1/txt2img 这个事实标准:

json
{"prompt": "a puppy dog", "steps": 20, "cfg_scale": 7.5, "sampler_name": "Euler a"}

steps、cfg_scale、sampler,这套词汇跟 messages、role、content 完全是两个世界。图片生成模型不需要对话历史,只需要一个 prompt 和一堆采样参数。

OpenAI 走的是另一条路。2022 年 11 月,DALL-E 2 上线 API,端点是 /v1/images/generations。跟聊天接口分开,单独一条(GPT Image - Wikipedia)。DALL-E 3(2023 年)沿用同一个端点。2025 年 3 月,OpenAI 把 DALL-E 换成了 GPT Image,架构上从扩散模型换成了自回归模型,但端点没变,还是 /v1/images/generations。

变化发生在 2026 年 4 月。GPT Image 2 上线。生成一张图之前,它会先"推理":规划构图,检查指令。OpenAI 管它叫第一个 agentic 的图片生成模型(mindwiredai.com)。这次它不只挂在 /v1/images/generations 上,也能直接通过 /v1/responses 调用。图片生成变成了 agent 工作流里的一个工具,不再是一个孤立的功能。DALL-E 2 和 3 的 API,2026 年 5 月 12 日正式下线。

Google 那边更彻底,压根没给图片生成开一个新端点。2025 年 8 月,Gemini 2.5 Flash Image 上线,社区给它起了个外号"nano banana"(Google Developers Blog)。调用方式还是那个 generateContent:传入文字和图片,模型可能吐文字,也可能直接吐一张图,取决于你让它做什么:

python
response = client.models.generate_content(
    model="gemini-2.5-flash-image-preview",
    contents=["把这张猫的照片改成吉卜力风格", image]
)

生成图片和生成文字,走的是同一个方法。nano banana 后来长成了一整个系列——Nano Banana 2、Nano Banana Pro,但入口始终是 generateContent 这一个。

这跟前面 Chat Completions 演化到 Responses API 是同一件事。功能一开始各占一个专用端点,后来都被塞进了同一个通用协议里。图片、文字、工具调用,现在越来越像是同一个 response 对象里的不同 item 类型,不是三个互不相干的 API。

/v1/embeddings 和 /v1/models

/v1/embeddings 把文本变成一组向量:

javascript
POST /v1/embeddings
{
  "model": "text-embedding-3-large",
  "input": "AI API suffixes explained"
}

用在语义搜索、RAG 检索、相似度匹配、聚类、推荐、去重、分类。典型 RAG 流程:

javascript
1. 用 /v1/embeddings 把文档切片转成向量
2. 存入向量数据库
3. 用户提问时,把问题也转成向量
4. 检索最相似的文档片段
5. 把片段塞进 /v1/chat/completions 或 /v1/responses 或 /v1/messages 生成答案

/v1/models 就是列出可用模型、查模型详情,调试 model_not_found 时最先看它。

javascript
GET /v1/models
GET /v1/models/{model}

2024 年那次断供

前面说"OpenAI 协议是事实标准"。这句话在国内是被一次具体事件砸实的。2024 年 6 月 9 日,OpenAI 通知:7 月 9 日起,终止对中国大陆的 API 服务。腾讯混元、阿里通义、百度文心、智谱 GLM、月之暗面 Kimi、深度求索 DeepSeek,几乎连夜同步发了迁移公告。降价,送 token,核心动作就一个:把自己的接口对齐到 /v1/chat/completions(腾讯新闻)。

在断供之前,国内一大批创业公司、SaaS 工具、Agent 应用、教育类 App,后端逻辑就直接依赖这一个接口。断供消息一出,谁能最快把自己的协议兼容上去,谁就能接住这批开发者。不用他们改一行业务代码,只换 base_url、api_key、model 三个参数。

但兼容不等于兼容到位。同一批报道里也有另一种声音:国内好几家大模型的 OpenAI 兼容,细看 function calling、streaming、finish_reason 这些语义,没有哪一家做对过。协议名字抄对了,行为对不上,接进来才发现坑在细节里。

开源生态里也有一模一样的故事。Open WebUI 是最流行的开源 LLM 聊天前端之一,GitHub 上"No support for Gemini API?"这个 issue 2024 年年中就开了(open-webui/open-webui #3482)。到现在,它的原生 Gemini 连接还是断断续续出 bug:system prompt 传不对,原生 function calling 报错(open-webui/open-webui #13400)。根子在于没有非做不可的理由。2024 年 11 月,Google 自己给 Gemini API 加了一层 OpenAI 兼容入口:base_url 指到 https://generativelanguage.googleapis.com/v1beta/openai/,普通聊天靠这层就够用(Gemini is now accessible from the OpenAI Library)。真正的原生功能没人逼着做,就一直放着没人修。

Base URL 和 Endpoint

这是实际接入时最容易踩的坑,值得单独说清楚:什么时候要带 v1,什么时候不能带。判断标准不看协议设计,看具体 SDK 内部怎么拼路径。

OpenAI 的 Python SDK 是这么用的:

python
client = OpenAI(base_url="http://my.test.server.example.com:8083/v1")
client.chat.completions.create(...)

SDK 内部只拼接相对路径,比如 chat/completions、embeddings,不会替你补版本号。配置 base_url 时,必须自己把 /v1 带上。不带,请求就会变成 .../chat/completions,少了 v1,直接 404。Groq、OpenRouter、阿里 DashScope 的 compatible-mode,这些 OpenAI 兼容服务给出的 base_url 无一例外都以 /v1 结尾。它们接住的正是这个 SDK 的拼接逻辑。

Anthropic 的 Python SDK 反过来。默认 base_url 是 https://api.anthropic.com,不带 v1。调用 client.messages.create() 时,SDK 内部请求的相对路径本身就是 v1/messages。版本号写死在 SDK 调用代码里,不是写在 base_url 里。如果在 base_url 后面自己又加了一次 /v1,拼出来就是 .../v1/v1/messages,一样 404。

经验规则很简单:先看你在用哪家的 SDK,再决定 base_url 要不要带 v1。

javascript
OpenAI 及其兼容生态(Groq / OpenRouter / DashScope compatible-mode 等):
base_url 要以 /v1 结尾,SDK 只拼相对路径,不会替你补版本号。

Anthropic:
base_url 不带 v1(默认 https://api.anthropic.com),
SDK 内部请求路径自带 v1/messages,你加了反而重复。

如果软件让你填的是 "Endpoint" / "Full URL" / "Request URL" 这类字段,那就不走 SDK 的相对路径拼接逻辑,直接填完整地址,比如 https://api.openai.com/v1/chat/completions。

流式格式和工具调用

流式输出不是一套格式,三种协议也各有各的规范。三种协议吐出来的东西,形状完全不一样,点开下面每个 tab 直接对比:

只有 data: 行,没有具名事件。增量文本藏在 choices[0].delta.content 里,一个 chunk 一个字:

javascript
data: {"choices":[{"delta":{"content":"Hel"}}]}

data: {"choices":[{"delta":{"content":"lo"}}]}

data: [DONE]

三种格式看下来是一条清楚的线:OpenAI 老协议只有一种消息(data: 携带增量),Anthropic 把生命周期拆成几个具名事件,Responses 把生命周期拆得更细——同一个"模型正在吐字"的过程,协议版本越新,描述得越精确,但要处理的事件类型也越多。

工具调用也一样。Chat Completions 里靠 tools / tool_calls / tool role 表达。Claude 里,工具使用是 content block 的一种。Responses API 里,工具调用和结果是 response item 的一部分,更适合表达复杂的 agent 事件流。三套解析代码不能混用,这也是为什么 LiteLLM 这类网关有市场。

LiteLLM

三套解析代码不能混用,除非你不打算自己写这三套代码。LiteLLM 干的就是把三套协议的差异吞掉这件事。

起源

LiteLLM 出自 YC W23 那批公司 BerriAI,两个创始人 Krrish Dholakia 和 Ishaan Jaffer(Y Combinator)。BerriAI 一开始做的是帮 SaaS 公司接入类 ChatGPT 功能的产品。自己接 Azure、OpenAI、Cohere 的时候,代码里堆出了一串上百行的 if/else,调试一次比一次痛苦。2023 年 8 月 9 日,他们把这坨 if/else 抽成了一个独立的包。LiteLLM 就是这么来的(Grokipedia)。

现在这个包已经从内部工具变成了基础设施:GitHub 5.2 万+ star(GitHub),官方说法是每天帮 NASA、Adobe、Netflix、Stripe、Nvidia 这些公司转发几亿次 LLM 调用。

它的思路很直接:你只学 OpenAI 一种调用方式,它在背后把请求翻译成 100 多家厂商各自要的格式,再把响应翻译回统一的 OpenAI 格式还给你(LiteLLM 文档)。

LiteLLM 有两种用法。一种是 Python SDK,直接替换 openai 库里的调用。另一种是 Proxy Server,起一个本地网关,任何认 OpenAI 协议的客户端都能直接指过去:

python
import openai
client = openai.OpenAI(api_key="anything", base_url="http://0.0.0.0:4000")
response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "Write a short poem"}]
)

业务代码里看到的永远是 choices[0].message.content。背后调的是 Claude 的 content[0].text,还是 Gemini 的 candidates[...].content.parts[...].text,LiteLLM 在网关这一层就转好了,不用自己写三套解析。

代价也在这一层。多了一跳网关,多了一层要跟着更新的翻译逻辑。某个厂商加了新字段,比如 reasoning tokens、cached tokens,LiteLLM 没跟上,业务代码里就看不到这个字段。协议差异没有消失,只是从业务代码搬到了 LiteLLM 的适配层里。

而这一层适配层,现在自己也成了一个真实的攻击面。2026 年上半年,LiteLLM 连着出了三次安全事故,一次比一次重。

事故一:PyPI 投毒

2026 年 3 月,PyPI 上的 v1.82.7、v1.82.8 两个版本被人植入了恶意代码。proxy_server.py 里塞了后门,能偷凭证。攻击者拿到了维护者账号,绕过正常发布流程,直接把包推上了 PyPI。恶意包只活了大概 40 分钟就被 PyPI 下架。发现的契机挺戏剧性:有使用者提了个 issue,标题是"CRITICAL: Malicious LiteLLM_init.pth in LiteLLM 1.82.8 PyPI package, credential stealer"。按 Andrej Karpathy 的说法,是被植入的代码本身引发了一个崩溃 bug,意外把这事捅了出来(Trend Micro)。

事故二:SQL 注入

一个月后,2026 年 4 月,CVE-2026-42208 曝出。LiteLLM 代理直接把请求头 Authorization: Bearer 的值拼进 SQL 查询,没做参数化。任何人不用登录,就能对着后端 PostgreSQL 跑任意 SELECT。CVSS 评分 9.3。4 月 19 日,官方发布补丁。但漏洞公告发出后大约 26 小时,第一次实际攻击就被记录到了,攻击者直接冲着存 API key 的 litellm_credentials 表去的(Sysdig)。

事故三:命令注入串成远程代码执行

5 月到 6 月,第三刀来了。CVE-2026-42271 是个命令注入漏洞,单独看 CVSS 8.7。但它跟 Starlette 的一个 Host 头校验漏洞串起来后,变成了不需要任何登录凭证的远程代码执行,CVSS 直接拉到 10.0。CISA 把它列进已知被利用漏洞名单时,专门标注这条漏洞被 Qilin 勒索软件团伙用上了(The Hacker News)。

三次事件之后,BerriAI 上线了 bug bounty 计划。用 LiteLLM 做网关,方便是真方便。但它现在也是一个真实的攻击面:所有厂商的 API key 都汇聚在这一层,出了事就是一锅端。

一张对照表

厂商 / 平台常见接口路径协议风格
OpenAI/v1/responses新一代统一响应协议
OpenAI/v1/chat/completions传统聊天,生态兼容最好
OpenAI/v1/images/generations图片生成
OpenAI/v1/embeddings文本向量化
OpenAI/v1/models查询模型列表
Anthropic/v1/messages原生对话、工具、多模态
Google Gemini/v1/models/{model}:generateContent稳定接口,也是 nano banana 的入口
Google Gemini/v1beta/models/{model}:generateContent预览能力
Mistral/v1/chat/completionsOpenAI-like
xAI/v1/chat/completionsOpenAI-like
DeepSeek/chat/completions 或兼容路径OpenAI-like
Groq/openai/v1/chat/completionsOpenAI-compatible
OpenRouter/api/v1/chat/completionsOpenAI-compatible aggregator

/v1 不是模型版本

容易搞混的一点:/v1 是 API 版本,不是模型版本。

javascript
API 版本:/v1
模型版本:gpt-5.5、claude-sonnet-4-8、gemini-3.5-pro
SDK 版本:openai Python SDK 1.x、2.x
协议风格:OpenAI-compatible、Anthropic-compatible、Gemini-compatible

API 版本约束的是字段名字、错误格式、流式事件、鉴权方式。模型版本约束的是能力、上下文长度、价格、速度。厂商不会轻易改动一个已经稳定的 API 版本的核心结构。要么开一个新版本(/v2),要么像 OpenAI 那样干脆新开一套接口(/v1/responses)。

还没人把兼容做对

2023 年 3 月那个周末搭出来的 chat/completions,2025 年 3 月的 responses,2026 年 8 月彻底下线的 Assistants API。协议一直在换,理由都差不多:工具调用、多模态、agent 状态,上一代设计撑不住。

国内那次断供,把这套协议从"OpenAI 自己的接口"变成了事实标准。但标准这层皮之下,tools、streaming、finish_reason 这些细节,谁都没抄齐过。

腾讯新闻那篇报道的标题很直接:全军覆没,国产大模型都没做好 OpenAI 兼容。协议名字抄得再快,细节要一条条测过去,才知道兼容到哪一步。

参考资料