AI网关:Responses直通codex兼容与流式升级回退
CI / test (push) Successful in 31s
Release / release (push) Successful in 39s

This commit is contained in:
2026-07-15 10:39:56 +08:00
parent a8bde89b56
commit e1f8a0539c
8 changed files with 1339 additions and 221 deletions
+416 -198
View File
@@ -1,294 +1,512 @@
# AI 网关
> 本文是 OCI Portal AI 网关的完整使用与兼容性文档:端点定位、协议兼容边界、已知上游限制与字段兼容矩阵。
> 路由与鉴权的机器可读定义以 [Swagger YAML](swagger.yaml) 或运行时 Swagger UI 为准;无法由 OpenAPI 表达的兼容边界以本文为准。
> [!NOTE]
> OCI Portal AI 网关将多个 OCI GenAI 渠道统一为 OpenAI、Anthropic 与 xAI
> 兼容接口,并集中处理鉴权、模型访问控制、渠道调度和协议适配。
>
> 路径、参数与响应结构以 [Swagger YAML](swagger.yaml) 或运行时 Swagger UI
> 为准;协议差异、兼容改写和实测边界以本文为准。
AI 网关使用面板创建的独立密钥鉴权,支持 `Authorization: Bearer sk-...``x-api-key: sk-...`。密钥可绑定渠道分组和模型白名单;全局模型黑名单会从模型列表、路由和探测候选中同时排除目标模型。
**兼容快照:2026-07-15**
| 端点 | 定位 | 流式 |
## 快速导航
- [快速接入](#quick-start)
- [端点一览](#endpoints)
- [路由与全局行为](#routing)
- [Codex 接入](#codex)
- [已知限制](#limitations)
- [字段兼容矩阵](#compatibility)
- [实现索引](#implementation)
<a id="quick-start"></a>
## 快速接入
### 基础地址与鉴权
```text
Base URL: https://<网关地址>/ai/v1
```
网关密钥在管理面板中创建,支持以下任一请求头:
```http
Authorization: Bearer sk-...
```
```http
x-api-key: sk-...
```
密钥可绑定渠道分组和模型白名单。全局模型黑名单会同时作用于模型列表、
请求路由和探测候选;开启「过滤弃用模型」后,OCI 已宣布弃用的模型也会从
列表与路由中移除。
可先用模型列表验证地址与密钥:
```bash
curl "https://<网关地址>/ai/v1/models" \
-H "Authorization: Bearer $OCI_PORTAL_KEY"
```
<a id="endpoints"></a>
## 端点一览
| 协议域 | 端点 | 角色 | 流式 |
| --- | --- | --- | :---: |
| 对话 | `POST /ai/v1/responses` | OpenAI Responses,无状态主接口 | SSE |
| 对话 | `POST /ai/v1/chat/completions` | OpenAI Chat Completions 兼容层 | SSE |
| 对话 | `POST /ai/v1/messages` | Anthropic Messages 转换层 | SSE |
| 向量 | `POST /ai/v1/embeddings` | OpenAI Embeddings | — |
| 语音 | `POST /ai/v1/audio/speech` | OpenAI Audio Speech 外壳 | — |
| 语音 | `POST /ai/v1/tts` | xAI TTS 格式转换层 | — |
| 检索 | `POST /ai/v1/rerank` | Cohere / Jina 风格文档重排 | — |
| 安全 | `POST /ai/v1/moderations` | OpenAI 外壳映射 OCI Guardrails | — |
| 发现 | `GET /ai/v1/models` | 当前密钥可见模型列表 | — |
选择建议:
- 新客户端优先使用 **Responses**
- Anthropic SDK 或 Claude 生态客户端使用 **Messages**
- 仅支持旧 OpenAI 对话协议的客户端使用 **Chat Completions**
- Embeddings、TTS、Rerank 与 Moderations 使用各自专用端点
<a id="routing"></a>
## 路由与全局行为
### 请求链路
```mermaid
flowchart LR
A[客户端] --> B[网关密钥鉴权]
B --> C[分组、白名单与全局过滤]
C --> D[模型与能力匹配]
D --> E[选择最小 Priority]
E --> F[同优先级按 Weight 加权]
F --> G[OCI GenAI]
G -. 可重试且流未建立 .-> D
```
单次请求最多尝试三个渠道。模型不可用、限流、上游服务错误或网络错误可触发
换渠道;流式连接建立后不会切换渠道重试。
### 全局兼容边界
| 主题 | 当前行为 |
| --- | --- |
| 会话状态 | 网关不保存会话历史,客户端必须在每次请求中携带完整上下文 |
| 上游存储 | Responses 请求始终强制 `store:false` |
| 有状态字段 | Responses 拒绝非空 `previous_response_id`、非 `null``conversation``background:true` |
| 对话上游 | 对话请求统一进入 OCI OpenAI 兼容面,当前供给以 `xai.``meta.``openai.` 前缀模型为主 |
| 推理强度 | Responses `reasoning.effort`、Messages `output_config.effort`、Chat `reasoning_effort` 会传给上游;可用档位由模型决定 |
| 服务端工具 | Responses 支持 `web_search``x_search``code_interpreter` 和远程 `mcp`;命名容器管理与 File Search 不提供 |
| 文件输入 | `input_file` 会被 OCI ZDR 形态拒绝,详见[已知限制](#limitations) |
| Chat 定位 | Chat Completions 只承担协议转换与兼容修复;新能力优先落在 Responses 与 Messages |
<a id="codex"></a>
## Codex 接入
### 主配置
自定义模型提供方必须写在用户级 `~/.codex/config.toml`。Codex 的项目级
`.codex/config.toml` 不允许改写 `model_provider``model_providers`
```toml
model = "xai.grok-4.3"
model_provider = "oci"
[model_providers.oci]
name = "oci-portal"
base_url = "https://<网关地址>/ai/v1"
env_key = "OCI_PORTAL_KEY"
wire_api = "responses"
```
### 自定义子代理
新版 Codex 可在 `~/.codex/agents/` 或项目级 `.codex/agents/` 放置独立 TOML
文件,并为子代理覆盖模型。每个文件都需要 `name``description`
`developer_instructions`
```toml
# .codex/agents/oci-worker.toml
name = "oci-worker"
description = "通过 OCI Portal 网关执行通用开发任务。"
developer_instructions = """
完成被分配的开发任务,保持改动聚焦,并返回验证结果。
"""
model = "xai.grok-4.3"
```
> [!IMPORTANT]
> `codex-cli 0.144.1` 已实测主会话和 multi-agent 子代理全链路可用。
> 内置 worker 可能先尝试 `gpt-5.6-luna` 或 `gpt-5.4`;网关没有对应渠道时
> 会出现少量 404,随后由 Codex 回落到可用模型。自定义 agent 的模型覆盖是否
> 直接生效取决于 Codex 版本;0.144.1 的实测主要依赖自动回落。
Codex 工具兼容现状:
| 工具形态 | 网关处理 | 验证状态 |
| --- | --- | --- |
| `POST /ai/v1/responses` | OpenAI Responses,无状态主接口 | SSE |
| `POST /ai/v1/chat/completions` | OpenAI Chat Completions,存量客户端兼容层 | SSE |
| `POST /ai/v1/messages` | Anthropic Messages 转换层 | SSE |
| `POST /ai/v1/embeddings` | OpenAI Embeddings | 否 |
| `POST /ai/v1/audio/speech` | OpenAI Audio Speech,文本转语音(xAI Voice | 否 |
| `POST /ai/v1/tts` | xAI 官方格式文本转语音(同一上游,网关转换) | 否 |
| `POST /ai/v1/rerank` | 文档重排(Cohere RerankJina 风格协议) | 否 |
| `POST /ai/v1/moderations` | 内容审核(OCI Guardrails:内容审核 / PII / 提示注入) | 否 |
| `GET /ai/v1/models` | 当前密钥可见的模型列表 | 否 |
| `function` | 原样透传 | 可用 |
| `namespace` | 子工具拍平为限定名 `function`,响应时还原 | multi-agent 已端到端实测;MCP 同路径有单测 |
| `custom` | 顶层普通工具转换为 `function`,调用与历史记录双向回转 | 可用,但存在降级边界 |
| `custom:apply_patch` | 直接剥离,让模型回落到其他编辑方式 | 有意限制 |
| `tool_search` | namespace 已完整展开,目录搜索语义冗余,直接剥离 | 有意降级 |
兼容边界:
配置语法参考 [Codex Subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
与 [Codex Advanced Configuration](https://learn.chatgpt.com/docs/config-file/config-advanced)。
- 对话请求统一转发 OCI OpenAI 兼容面,当前供给以 `xai.``meta.``openai.` 前缀模型为主;Cohere Embeddings 不受该对话模型范围影响
- 网关不保存会话历史,客户端需要携带完整上下文;Responses 拒绝非空 `previous_response_id`、非 `null` `conversation``background:true`
- Responses 支持 Oracle 文档化的 xAI 服务端工具 `web_search` / `x_search` / `code_interpreter` 与远程 `mcp` 工具(非流式与流式均可),工具参数与限制遵循 [xAI 规格](https://docs.oracle.com/en-us/iaas/Content/generative-ai/get-started-agents.htm#xai-compatible-tools)`code_interpreter` 的命名容器管理(containers API)与 File Search 不提供
- Responses 的 `reasoning.effort`、Messages 的 `output_config.effort` 和 Chat Completions 的 `reasoning_effort` 会传给上游,实际档位和效果由模型决定
- Chat Completions 只承担协议转换与兼容修复;新能力优先在 Responses 和 Messages 提供
- 单次请求最多尝试三个渠道;可重试错误会切换渠道,流式响应建立后不会换渠道重试
- Audio Speech 直通 OCI 兼容面(模型 `xai.grok-tts`voice 取 xAI Grok Voice 列表:`ara`/`eve`/`leo`/`rex`/`sal`);上游把 `language` 当必填,缺省时网关自动注入 `"auto"`xAI 专属参数(`output_format` 等)可平铺在请求体透传;仅单请求返回音频,不提供 WebSocket 流式
- `/ai/v1/tts` 为 xAI 官方 TTS 格式(`text`/`language` 必填、`voice_id``output_format` 对象)的转换端点:网关转换为 OpenAI 兼容形态后走同一上游与渠道调度,`model` 为网关扩展字段(缺省 `xai.grok-tts`);实测 `output_format`(codec/sample_rate/bit_rate)与 `speed` 透传生效
- Rerank 走 OCI typed 面(`cohere.rerank-v4.0-pro` / `-fast`),请求 `{model, query, documents[], top_n?, return_documents?}`,响应 `results[].index` 指向入参下标;无 token 用量口径,调用日志只记时延
- Moderations 是 OpenAI moderations 外壳映射 OCI Guardrails`input` 为字符串或字符串数组(至多 8 条),categories 用 OCI 原生维度 `overall` / `blocklist` / `prompt_injection`(阈值 0.5 判定 `flagged`),PII 命中放扩展字段 `results[].pii`(不参与 flagged);`model` 字段接受但忽略,无模型白名单维度;实测中文人名/手机号识别较弱,英文 PII 识别正常
- Responses 的 `input_file` 内容块(file_url / file_data)实测被上游拒绝:`File content is currently unsupported for ZDR customers`——网关强制 `store:false` 属 ZDR 形态,该能力在上游侧不可用
<a id="limitations"></a>
### 已知上游限制:大 system 区流式断流
## 已知限制
实测(2026-07-13OCI 兼容面对 `instructions``tools` 合计超约 64.5KB 的**流式**请求会在发出少量事件后静默断开连接(无任何错误事件;同请求非流式正常),与模型、字符集、消息正文大小均无关——消息正文(`input`)不计入该限制。Chat Completions 与 Messages 的 system/developer 提示会转换为 `instructions`,因此 Claude Code 等自带大体量系统提示与工具定义的客户端极易触发。
### 超大流式请求
网关侧应对:
> [!WARNING]
> 2026-07 实测:OCI xAI 兼容面对**完整请求体超过约 82 KB** 的流式请求,
> 可能在推理阶段提前断流;该现象由请求体积触发,与工具构成或字符集无关,
> 同一请求改为非流式可正常完成。
- Messages 与 Chat Completions 的流式请求在客户端尚未收到任何输出时遭遇上游断流,会自动降级为非流式重做,并按标准事件/chunk 序列一次推送;调用日志记 `retries=1` 与降级标记
- Responses 直通因初始事件已转发、协议上无法透明降级,调用日志记「上游流提前终止」,客户端需自行回退非流式
- 应急规避:将超长 system 内容移入首条 user 消息正文可绕过该限制(正文不计入),但语义有别,根治有待上游修复
网关按协议采取不同保护:
这里提供的是兼容接口而非 OpenAI / Anthropic 协议的完整实现。OCI OpenAI 兼容面的部分行为来自实测,未见 Oracle 文档承诺,可能随上游调整。路由与鉴权定义以 [Swagger YAML](swagger.yaml) 或运行时 Swagger UI 为准;无法由 OpenAPI 完整表达的兼容边界列于上方。
- **Responses**:兼容改写后的请求体超过 **76 KB** 时,预防性改为非流式上游
请求,再合成最小 SSE 序列(`response.created`
`response.output_item.done``response.completed`);结果语义保留,但没有增量输出
- **Chat Completions / Messages**:若上游在客户端收到任何内容前断流,自动用
非流式重做,并合成对应 chunk / event 序列
- **已开始输出的流**:不能透明重试;调用日志会记录提前终止,客户端可能只拿到
部分事件
### 字段兼容矩阵
### ZDR 与文件输入
以下矩阵以 2026-07-13 的 [OpenAI Responses](https://developers.openai.com/api/reference/resources/responses/methods/create)、[Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)、[Embeddings](https://developers.openai.com/api/reference/resources/embeddings/methods/create)、[Audio Speech](https://developers.openai.com/api/reference/resources/audio/methods/speech)、[Moderations](https://developers.openai.com/api/reference/resources/moderations/methods/create) 和 [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create) 为标准基线,并与当前实现逐项核对;Rerank 无 OpenAI 对应端点,基线取 [Cohere Rerank](https://docs.cohere.com/reference/rerank) 协议。
Responses 的 `input_file` 内容块(`file_url` / `file_data`)实测会被上游拒绝:
这是一份兼容性快照,不替代 Swagger。标准接口和 OCI 上游都可能变化,最终行为以当前版本代码与实测为准。
```text
File content is currently unsupported for ZDR customers
```
矩阵只列网关支持的字段;不支持(本地拒绝)与被忽略的字段不入表,在各端点段落末尾以文字简述
网关强制 `store:false`,属于 ZDR 请求形态,因此当前不能通过该端点上传或引用文件
### 规格与实测边界
本项目提供的是兼容接口,而不是 OpenAI、Anthropic 或 xAI 协议的完整实现。
部分 OCI OpenAI 兼容行为来自实测,未见 Oracle 文档合同,可能随上游调整。
<a id="compatibility"></a>
## 字段兼容矩阵
矩阵基线:
- [OpenAI Responses](https://developers.openai.com/api/reference/resources/responses/methods/create)
- [OpenAI Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
- [OpenAI Embeddings](https://developers.openai.com/api/reference/resources/embeddings/methods/create)
- [OpenAI Audio Speech](https://developers.openai.com/api/reference/resources/audio/methods/speech)
- [OpenAI Moderations](https://developers.openai.com/api/reference/resources/moderations/methods/create)
- [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create)
- [Cohere Rerank](https://docs.cohere.com/reference/rerank)
| 标记 | 含义 |
| :---: | --- |
| ✅ | 网关直接支持 |
| ➡️ | 网关原样透传;是否生效由 OCI 上游决定 |
| 🔄 | 网关行字段或协议转换后支持 |
| ➡️ | 原样保留或透传;是否生效由 OCI 上游决定 |
| 🔄 | 网关行字段或协议转换后支持 |
| ◐ | 部分支持、存在前置条件或语义降级 |
<details>
<summary><code>POST /ai/v1/responses</code> 对比 OpenAI Responses</summary>
### OpenAI Responses
Responses 是“原始 JSON 直通 + 本地门禁”。除 `store` 外,网关不会重建请求体;未知顶层字段也会保留并送往 OCI。
**`POST /ai/v1/responses` · 无状态主接口**
网关以原始 JSON 为基底保留未知字段,但会强制关闭上游存储,并执行下表列出的
Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转发。
<details open>
<summary><strong>核心字段</strong></summary>
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 必须非空,还要通过密钥模型白名单并匹配可用渠道;随后原样透传 |
| `input` | ➡️ | 支持标准的字符串或 item 数组,原始内容透传;网关不逐项保证 OCI 能处理所有 item 类型 |
| `instructions``max_output_tokens` | ➡️ | 类型可解析后原样透传,不做范围或模型能力校验 |
| `temperature``top_p``parallel_tool_calls` | ➡️ | 原样透传,不做取值范围校验 |
| `text` / `text.format` / `text.verbosity` | ➡️ | 整个原始对象透传;结构化输出是否可用由 OCI 模型决定 |
| `reasoning` | ➡️ | 整个原始对象透传;`effort` 不校验档位,`summary` 等字段不会被网关删除 |
| `tool_choice` | ➡️ | 任意 JSON 原样透传,本地不校验枚举或结构 |
| `model` | ◐ | 必填;通过密钥白名单并匹配可用渠道后送往上游 |
| `input` | ➡️ | 支持字符串或 item 数组;普通内容保留,Codex 工具历史项可能改写 |
| `instructions``max_output_tokens` | ➡️ | 类型可解析后保留,不做范围或模型能力校验 |
| `temperature``top_p` | ➡️ | 保留,不做取值范围校验 |
| `parallel_tool_calls` | | 通常保留;若全部工具被剥离,则与 `tool_choice` 一并删除 |
| `text` / `text.format` / `text.verbosity` | ➡️ | 整个对象保留;是否生效由 OCI 模型决定 |
| `reasoning` | ➡️ | 整个对象保留;`effort` 不校验档位 |
| `tool_choice` | ◐ | 普通形态保留;namespace 对象会重限定,全部工具被剥离时删除 |
| `store` | 🔄 | 无论客户端传什么,上游请求都强制改写为 `false` |
| `stream` | | 普通请求、`function` 与服务端工具均支持 SSE |
| `tools[].type=function` | ➡️ | 非流式和流式均可,工具对象原样透传 |
| `tools[].type=web_search` / `x_search` / `code_interpreter` | ◐ | Oracle 文档化的 xAI 服务端工具,参数与限制遵循 xAI 规格(如 `allowed_domains` 上限 10、`container` 支持 `{"type":"auto"}`),非流式与流式均实测可用;`x_search` 不是 OpenAI 标准工具 |
| `tools[].type=mcp` | ➡️ | 远程 MCP 服务由上游直连调用(`server_url` / `require_approval` / `authorization` 等原样透传),非流式与流式均实测可用 |
| 其余标准与未知顶层字段 | ➡️ | `context_management``include``metadata``prompt``prompt_cache_key``service_tier``truncation``user` 等未建模字段一律保留在原始请求中,由 OCI 决定是否接受 |
不支持(请求到达上游前返回 400):非空 `previous_response_id`、非 `null``conversation``background:true`——网关无状态,不保存历史响应;以及 `function` / xAI 服务端工具 / `mcp` 之外的工具类型(`web_search_preview``file_search``computer``image_generation``shell``custom` 等)。
响应边界:
- 非流式成功响应不做转换,OCI JSON 原样返回;usage 解析只用于面板调用日志
- SSE 事件逐行原样转发,不补 `data: [DONE]`,推理增量也不会被网关过滤
- 流建立前最多切换三个渠道;流建立后中断不重试,客户端可能只收到部分事件
- 未知模型返回 404、无渠道返回 503;上游错误会套入 OpenAI 风格错误体,不保证与标准 OpenAI 错误字段完全相同
实现依据:[`airesponses.go`](../internal/service/airesponses.go) · [`responses.go`](../internal/aiwire/responses.go) · [`aigateway.go`](../internal/api/aigateway.go)
| `stream` | | 支持 SSE;请求体超过 76 KB 时改为非流式上游并合成 SSE |
| 其余标准与未知顶层字段 | ➡️ | `context_management``include``metadata``prompt``prompt_cache_key``service_tier``truncation``user` 等均保留,由 OCI 决定是否接受 |
</details>
<details>
<summary><code>POST /ai/v1/chat/completions</code> 对比 OpenAI Chat Completions</summary>
<details open>
<summary><strong>工具兼容</strong></summary>
Chat Completions 会先转换为 Responses 请求,再把 OCI Responses 响应桥接回 Chat Completions 形态。转换器只搬运下表字段,其余字段不进入上游请求。
| 工具或参数 | 状态 | 网关行为 |
| --- | :---: | --- |
| `tools[].type=function` | ➡️ | 非流式与流式均可,工具对象保留 |
| `web_search` / `x_search` / `code_interpreter` | ◐ | Oracle 文档化的 xAI 服务端工具;参数与限制遵循 [xAI 规格](https://docs.oracle.com/en-us/iaas/Content/generative-ai/get-started-agents.htm#xai-compatible-tools) |
| `tools[].type=mcp` | ➡️ | 远程 MCP 由上游直连,`server_url``require_approval``authorization` 等保留 |
| `tools[].type=namespace` | 🔄 | 子 `function` 上提并限定命名;响应、历史调用和 `tool_choice` 会反向还原;组内非 `function` 子工具被剥离 |
| `tools[].type=custom` | ◐ | 顶层非 `apply_patch` 工具转为带 `input` schema 的 `function`;响应与多轮历史双向回转;`format` 会删除 |
| `custom:apply_patch` | ◐ | 整体剥离;grok 系未针对 Codex 补丁格式训练,模型应回落其他编辑方式 |
| `tools[].type=tool_search` | ◐ | 请求可被接受,但工具本身直接剥离 |
| `web_search.external_web_access=true` | 🔄 | 删除上游不识别的字段,保留 `web_search` |
| `web_search.external_web_access=false` | ◐ | 上游没有“仅缓存检索”对应能力,按不越权原则剥离整个工具 |
</details>
本地拒绝(返回 400):
- 非空 `previous_response_id`
-`null``conversation`
- `background:true`
- 未列入白名单的工具类型,例如 `web_search_preview``file_search`
`computer``image_generation``shell`
响应边界:
- 无工具还原且未触发流式升级时,普通响应保持直通语义
- namespace / custom 调用会在非流式响应与命中的 SSE `data` 事件中定向还原
- 普通 SSE 不补 `data: [DONE]`reasoning 增量不会被过滤
- 未知模型返回 404,无可用渠道返回 503
- 上游错误使用 OpenAI 风格错误外壳,但不保证字段与标准 OpenAI 完全一致
### OpenAI Chat Completions
**`POST /ai/v1/chat/completions` · 存量客户端兼容层**
请求先转换为 Responses,再将 OCI Responses 响应桥接回 Chat Completions。
只有下表字段会进入上游请求。
<details>
<summary><strong>展开字段矩阵</strong></summary>
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 必填,受密钥白名单和可用渠道限制;模型名保留到上游请求 |
| `messages` | 🔄 | 必填转换为 Responses `input` / `instructions` |
| `system` / `developer` 消息 | ◐ | 文本按出现顺序合并为 `instructions`;块数组中的非文本内容忽略 |
| `user` / `assistant` 文本内容 | 🔄 | 字符串 `text` 块分别转为 `input_text` / `output_text` |
| `image_url` 内容块 | ◐ | URL 或 data URI 转为 `input_image``image_url.detail` 忽略 |
| assistant `tool_calls` / `role=tool` | 🔄 | 转为 `function_call` / `function_call_output`,保留调用 ID、函数名和参数 |
| `model` | ◐ | 必填,受密钥白名单和可用渠道限制 |
| `messages` | 🔄 | 必填转换为 Responses `input` / `instructions` |
| `system` / `developer` 消息 | ◐ | 文本按顺序合并为 `instructions`;块数组中的非文本内容忽略 |
| `user` / `assistant` 文本 | 🔄 | 字符串 `text` 块分别转为 `input_text` / `output_text` |
| `image_url` | ◐ | URL 或 data URI 转为 `input_image``detail` 忽略 |
| assistant `tool_calls` / `role=tool` | 🔄 | 转为 `function_call` / `function_call_output` |
| `max_completion_tokens` | 🔄 | 转为 `max_output_tokens`,优先于 `max_tokens` |
| `max_tokens` | 🔄 | 未提供 `max_completion_tokens` 时转为 `max_output_tokens` |
| `temperature``top_p``parallel_tool_calls` | ◐ | 原值写入 Responses 请求,但不校验范围或模型能力 |
| `stream` | 🔄 | OCI Responses SSE 桥接`chat.completion.chunk`,末尾补 `data: [DONE]`;上游断流且尚无输出时自动降级非流式重做,结果按 chunk 序列一次推送 |
| `stream_options.include_usage` | 🔄 | 控制网关在终块后追加 `choices: []` usage 块 |
| `tools[].type=function` | ◐ | `name``description``parameters` 支持;`function.strict` 忽略 |
| `tool_choice` | ◐ | 支持 `auto` / `none` / `required` 和具名 function;非法或未知值被静默忽略 |
| `response_format` | ◐ | `json_object``json_schema` 转为 Responses `text.format`;未知类型交给 OCI 处理 |
| `reasoning_effort` | ◐ | 转小写后映射为 `reasoning.effort`,不校验模型或档位 |
| `store` | 🔄 | 客户端取值忽略,转换后的上游请求始终使用 `store:false` |
| `temperature``top_p``parallel_tool_calls` | ◐ | 写入 Responses不校验范围或模型能力 |
| `stream` | 🔄 | Responses SSE `chat.completion.chunk`,末尾补 `data: [DONE]` |
| `stream_options.include_usage` | 🔄 | 控制终块后的独立 usage 块 |
| `tools[].type=function` | ◐ | 支持 `name``description``parameters``strict` 忽略 |
| `tool_choice` | ◐ | 支持 `auto` / `none` / `required` 和具名 function |
| `response_format` | ◐ | `json_object``json_schema` 转为 Responses `text.format` |
| `reasoning_effort` | ◐ | 转小写后映射为 `reasoning.effort` |
| `store` | 🔄 | 客户端取值忽略,上游始终使用 `store:false` |
不支持(返回 400):`input_audio``file``refusal` 等消息内容块;`function` 以外的工具类型(含 `custom`)。
</details>
静默忽略(转换后的上游请求不包含):消息级 `name` / `refusal` / `audio` / 旧式 `function_call`;采样与输出控制类 `stop``seed``n`(恒返回单个 choice)、`frequency_penalty``presence_penalty``logprobs``top_logprobs``logit_bias`;平台类 `user``audio``modalities``prediction``metadata``moderation``prompt_cache_key``safety_identifier``service_tier``verbosity``web_search_options``stream_options.include_obfuscation` 及其他未知字段。
不支持(返回 400):`input_audio``file``refusal` 等消息内容块,以及
`function` 之外的工具类型。
<details>
<summary><strong>静默忽略字段</strong></summary>
消息级 `name` / `refusal` / `audio` / 旧式 `function_call``stop``seed`
`n``frequency_penalty``presence_penalty``logprobs``top_logprobs`
`logit_bias``user``audio``modalities``prediction``metadata`
`moderation``prompt_cache_key``safety_identifier``service_tier`
`verbosity``web_search_options``stream_options.include_obfuscation`
及其他未知字段。
</details>
响应边界:
- 非流式固定生成一个 `choices[0]`;文本合并,函数调用转为 `tool_calls`
- `finish_reason` 只生成 `stop``tool_calls``length`;其他上游终止原因不保留
- reasoning 输出、logprobs、refusal、annotations、audio、service tier 和 system fingerprint 不返回
- usage 保留 `prompt_tokens``completion_tokens``total_tokens` `prompt_tokens_details.cached_tokens`
- 已建立的流中断或上游 `response.failed` 不会转换成标准 SSE 错误事件
- 非流式固定生成一个 `choices[0]`;文本合并,函数调用转为 `tool_calls`
- `finish_reason` 只生成 `stop``tool_calls``length`
- reasoning、logprobs、refusal、annotations、audio、service tier 和 system fingerprint 不返回
- usage 保留 `prompt_tokens``completion_tokens``total_tokens`
`prompt_tokens_details.cached_tokens`
- 已开始输出的流中断不会转换成标准 SSE 错误事件
实现依据:[`chatresponses.go`](../internal/service/chatresponses.go) · [`openai.go`](../internal/aiwire/openai.go) · [`aigateway.go`](../internal/api/aigateway.go)
### Anthropic Messages
</details>
**`POST /ai/v1/messages` · Anthropic 协议转换层**
支持 `Authorization: Bearer``x-api-key`,但不校验或使用
`anthropic-version``anthropic-beta` 请求头。
<details>
<summary><code>POST /ai/v1/embeddings</code> 对比 OpenAI Embeddings</summary>
<summary><strong>展开顶层字段矩阵</strong></summary>
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 必填,受密钥白名单限制,并且必须存在具有 `EMBEDDING` 能力的渠道 |
| `input` 为字符串 | | 包装为单个输入后调用 OCI |
| `input` 为字符串数组 | ✅ | 按原顺序调用 OCI;空字符串不在本地拒绝,由 OCI 决定 |
| `dimensions` | ◐ | 映射为 OCI 输出维度,不做范围或模型能力校验 |
| `encoding_format=float` | ✅ | 返回 float 数组;省略时行为相同 |
不支持(返回 400):token ID 数组(一维或二维)形态的 `input`、空数组、`null`,以及 `encoding_format=base64`。静默忽略:`user` 及其他未知字段。
响应使用标准的 `object:"list"``data[].object:"embedding"``index``model` 和可选 `usage` 外壳;向量为 `float32` 数组,不支持流式。
实现依据:[`embeddings.go`](../internal/aiwire/embeddings.go) · [`aigateway_chat.go`](../internal/service/aigateway_chat.go) · [`aigateway.go`](../internal/api/aigateway.go)
</details>
<details>
<summary><code>POST /ai/v1/messages</code> 对比 Anthropic Messages</summary>
网关接受 `Authorization: Bearer``x-api-key`,但不会校验或使用标准 Anthropic `anthropic-version``anthropic-beta` 请求头。
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 用于模型与渠道选择,但空字符串不会在 handler 中按参数错误拒绝,通常最终返回模型不存在 |
| `max_tokens` | 🔄 | 可缺省(缺省或 ≤0 时按默认值 8192),转换为 `max_output_tokens` |
| `model` | ◐ | 用于模型与渠道选择;空字符串通常最终返回模型不存在 |
| `max_tokens` | 🔄 | 缺省或 ≤0 时使用 8192,再转为 `max_output_tokens` |
| `messages` | ◐ | 必须非空;角色、交替顺序和空内容不做完整校验 |
| `system` | ◐ | 支持字符串或 text 块数组;多个文本块直接拼接,`cache_control`附加字段忽略 |
| `temperature``top_p` | ◐ | 写入 Responses 请求,不做取值范围或模型能力校验 |
| `stream` | 🔄 | Responses SSE 桥接为 Anthropic 事件序列;上游断流且尚无输出时自动降级非流式重做,结果按事件序列一次推送 |
| `tools` | ◐ | 每个工具都转换成 Responses `function`自定义客户端工具可用,Anthropic 服务端工具类型不保留原语义 |
| `tool_choice` | ◐ | 支持 `auto``any``none`、具名 `tool``disable_parallel_tool_use` 等附加字段被忽略 |
| `output_config.effort` | 🔄 | 转小写后映射为 Responses `reasoning.effort` |
| `system` | ◐ | 支持字符串或 text 块数组;文本块拼接,附加字段忽略 |
| `temperature``top_p` | ◐ | 写入 Responses,不校验范围或模型能力 |
| `stream` | 🔄 | Responses SSE 桥接为 Anthropic 标准事件序列 |
| `tools` | ◐ | 每个工具转为 Responses `function`;服务端工具类型不保留原语义 |
| `tool_choice` | ◐ | 支持 `auto``any``none`、具名 `tool` |
| `output_config.effort` | 🔄 | 转小写后映射为 `reasoning.effort` |
顶层静默忽略(能解析但不传上游):`top_k``stop_sequences`(响应 `stop_sequence` 恒为 `null`)、`metadata``thinking`(不控制上游思考预算)、`output_config` 其余子字段、`cache_control``container``inference_geo``service_tier` 及其他未知顶层字段。
</details>
`messages[].content`
内容块兼容
| 标准内容块 | 状态 | 网关行为 |
| `messages[].content` | 状态 | 网关行为 |
| --- | :---: | --- |
| 字符串 / `text` | 🔄 | user 转 `input_text`assistant 历史转 `output_text` |
| `image` | ◐ | 仅支持 `base64` `url` source;缺字段或其他 source 类型返回 400 |
| `tool_use` | 🔄 | 转为 `function_call`,保留 ID、名称输入 |
| `tool_result` | ◐ | 转为 `function_call_output`;块数组只拼接 text`is_error` 非文本结果丢失 |
| `null` / 空块数组 | ◐ | 消息可能从上游 input 中消失,不返回参数错误 |
| `image` | ◐ | 仅支持 `base64` `url` source |
| `tool_use` | 🔄 | 转为 `function_call`,保留 ID、名称输入 |
| `tool_result` | ◐ | 转为 `function_call_output`;块数组只拼接 text`is_error` 非文本结果丢失 |
| `null` / 空块数组 | ◐ | 消息可能从上游 input 中消失,不返回参数错误 |
不支持的内容块(返回 400):`document`、服务端工具结果及其他未知块。历史 `thinking` / `redacted_thinking` 块会被删除,不进入上游上下文。
不支持(返回 400):`document`、服务端工具结果及未知内容块。历史
`thinking` / `redacted_thinking` 会删除。
<details>
<summary><strong>静默忽略字段</strong></summary>
`top_k``stop_sequences``metadata``thinking``output_config` 其余子字段、
`cache_control``container``inference_geo``service_tier` 及其他未知顶层字段。
</details>
响应边界:
- 非流式只把 Responses `output_text` `text``function_call` `tool_use`
- `stop_reason` 只生成 `end_turn``tool_use``max_tokens``stop_sequence` 恒为 `null`
- reasoning 不会生成 Anthropic `thinking` / `redacted_thinking` 块,也没有 signature
- usage 只保留 `input_tokens``output_tokens` `cache_read_input_tokens`,不提供 `cache_creation_input_tokens`
- 流式输出标准事件骨架,但不生成 `thinking_delta``signature_delta`上游错误事件与无终态断流会转成 Anthropic `error` 事件
- 仅将 Responses `output_text` `text``function_call` `tool_use`
- `stop_reason` 只生成 `end_turn``tool_use``max_tokens`
- 生成 Anthropic thinking / signature 内容块
- usage 只保留 `input_tokens``output_tokens` `cache_read_input_tokens`
- 流式上游错误与无终态断流会转成 Anthropic `error` 事件
实现依据:[`anthresponses.go`](../internal/service/anthresponses.go) · [`anthropic.go`](../internal/aiwire/anthropic.go) · [`aigateway.go`](../internal/api/aigateway.go)
### OpenAI Embeddings
</details>
<details>
<summary><code>POST /ai/v1/audio/speech</code> 对比 OpenAI Audio Speech</summary>
Audio Speech 与 Responses 同为“原始 JSON 直通 + 本地门禁”:除缺省注入 `language` 外不重建请求体,未知字段原样透传。
**`POST /ai/v1/embeddings` · 向量化专用端点**
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 必填,受密钥白名单限制,并且必须存在具有 `TTS` 能力渠道(当前上游仅 `xai.grok-tts` |
| `input` | | 必填非空,随后原样透传 |
| `voice` | ➡️ | 原样透传;取 xAI Grok Voice 音色(`ara` / `eve` / `leo` / `rex` / `sal`),OpenAI 标准音色名不可用 |
| `response_format` | ➡️ | 原样透传;实测 `mp3` 可用,其余格式由上游决定 |
| `language`(扩展字段) | 🔄 | 上游必填;客户端缺省时网关自动注入 `"auto"` |
| `speed``instructions` 及其他未知字段 | ➡️ | 原样透传(含 xAI 专属参数如 `output_format`),是否生效由上游决定 |
| `model` | ◐ | 必填,受白名单限制,且需要 `EMBEDDING` 能力渠道 |
| 字符串 `input` | | 包装为单个输入后调用 OCI |
| 字符串数组 `input` | | 按原顺序调用 OCI |
| `dimensions` | | 映射为 OCI 输出维度,不校验范围或模型能力 |
| `encoding_format=float` | | 返回 float 数组;省略时相同 |
不支持:流式音频(`stream_format` 等流式选项无效,响应恒为一次性完整音频)与 WebSocket 语音会话。
不支持(返回 400):token ID 数组、空数组、`null`
`encoding_format=base64``user` 与其他未知字段静默忽略。
响应边界:
响应使用标准 `object:"list"` 外壳,向量为 `float32` 数组,不支持流式。
- 成功响应为音频字节,`Content-Type` 透传上游(缺省 `audio/mpeg`
- 无 token 用量口径,调用日志只记时延与渠道
### 语音生成
实现依据:[`genai_speech.go`](../internal/oci/genai_speech.go) · [`service/aigateway_extras.go`](../internal/service/aigateway_extras.go) · [`api/aigateway_extras.go`](../internal/api/aigateway_extras.go)
两个端点最终使用同一 OCI xAI TTS 上游与渠道调度:
</details>
#### OpenAI Audio Speech
<details>
<summary><code>POST /ai/v1/tts</code> 对比 xAI TTS</summary>
`/ai/v1/tts` 接受 [xAI 官方 TTS 格式](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech)(OCI 无 HTTP 版 xAI 面,网关转换为 OpenAI 兼容形态后与 `/ai/v1/audio/speech` 走同一上游与调度)。
**`POST /ai/v1/audio/speech`**
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `text` | 🔄 | 必填非空,转换为上游 `input` |
| `language` | ◐ | 必填(对齐 xAI 官方;接受 BCP-47 或 `auto`),原样透传 |
| `voice_id` | 🔄 | 转换为上游 `voice`;缺省交上游默认(`eve`) |
| `output_format` | ➡️ | `{codec, sample_rate, bit_rate}` 对象原样透传,实测生效(44.1kHz/192kbps 验证) |
| `speed` | ➡️ | 原样透传,实测接受 |
| `model`(网关扩展) | ◐ | xAI 官方无此字段;缺省注入 `xai.grok-tts`,可显式覆盖,受密钥白名单限制 |
| `optimize_streaming_latency``text_normalization``with_timestamps` 及其他未知字段 | ➡️ | 原样透传,是否生效由上游决定 |
| `model` | | 必填,受白名单限制,需要 `TTS` 能力渠道;当前为 `xai.grok-tts` |
| `input` | ◐ | 必填非空,随后保留 |
| `voice` | ➡️ | 使用 xAI 音色 `ara` / `eve` / `leo` / `rex` / `sal` |
| `response_format` | ➡️ | 实测 `mp3` 可用,其余格式由上游决定 |
| `language`(扩展) | 🔄 | 上游必填;缺省时注入 `"auto"` |
| `speed``instructions` 与未知字段 | ➡️ | 保留,是否生效由上游决定 |
不支持:流式音频输出(xAI 官方 `optimize_streaming_latency` 面向流式场景,本端点响应恒为一次性完整音频);WebSocket 流式(OCI 另有 `wss://…/xai/v1/tts` 私有协议面,网关未代理)。
#### xAI TTS
响应边界:
**`POST /ai/v1/tts`**
- 成功响应为音频字节,`Content-Type` 透传上游(缺省 `audio/mpeg`)
- 无 token 用量口径,调用日志只记时延与渠道(端点名 `tts`)
实现依据:[`service/aigateway_extras.go`](../internal/service/aigateway_extras.go) · [`api/aigateway_extras.go`](../internal/api/aigateway_extras.go)
</details>
<details>
<summary><code>POST /ai/v1/rerank</code> 对比 Cohere Rerank</summary>
Rerank 无 OpenAI 对应端点,协议取 Jina / Cohere 通行风格,上游走 OCI typed 面(`cohere.rerank-v4.0-pro` / `-fast`)。
接受 [xAI 官方 TTS 格式](https://docs.x.ai/developers/model-capabilities/audio/text-to-speech)
转换为 OpenAI 兼容形态后进入同一上游。
| 字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | | 必填,受密钥白名单限制,并且必须存在具有 `RERANK` 能力的渠道 |
| `query` | | 必填非空 |
| `documents` | | 必填,仅接受字符串数组;Cohere 旧版 `{"text": ...}` 对象数组形态返回 400 |
| `top_n` | | 可选,传给上游限制返回条数;缺省返回全部文档的重排结果 |
| `return_documents` | | 可选,`true``results[].document.text` 回带原文 |
| `text` | 🔄 | 必填非空,转换为 `input` |
| `language` | | 必填,接受 BCP-47 或 `auto` |
| `voice_id` | 🔄 | 转为 `voice`;缺省交给上游默认值 `eve` |
| `output_format` | ➡️ | `{codec, sample_rate, bit_rate}` 对象保留,实测生效 |
| `speed` | ➡️ | 保留,实测可用 |
| `model`(网关扩展) | ◐ | 缺省注入 `xai.grok-tts`,可覆盖,受白名单限制 |
| 其余未知字段 | ➡️ | 保留,是否生效由上游决定 |
静默忽略:`max_tokens_per_doc` 等 Cohere 专属参数及其他未知字段。
共同响应边界:
响应边界:
- `results[]` 按相关度降序,`index` 指向入参 `documents` 下标,`relevance_score` 为 01 浮点;响应 `model` 回显请求值
- 成功响应为一次性完整音频,`Content-Type` 透传上游,缺省 `audio/mpeg`
- 不提供 HTTP 流式音频或 WebSocket 代理
- 无 token 用量口径,调用日志只记时延与渠道
实现依据:[`genai_guard.go`](../internal/oci/genai_guard.go) · [`rerank.go`](../internal/aiwire/rerank.go) · [`service/aigateway_extras.go`](../internal/service/aigateway_extras.go)
### Rerank
</details>
**`POST /ai/v1/rerank` · Cohere / Jina 风格协议**
<details>
<summary><code>POST /ai/v1/moderations</code> 对比 OpenAI Moderations</summary>
上游走 OCI typed 面,模型为 `cohere.rerank-v4.0-pro``-fast`
Moderations 是 OpenAI moderations 外壳映射 OCI Guardrails(内容审核 / PII / 提示注入)。上游是服务级 API,没有模型与白名单维度;渠道按分组直接挑选。
| 字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `model` | ◐ | 必填,受白名单限制,需要 `RERANK` 能力渠道 |
| `query` | ✅ | 必填非空 |
| `documents` | ◐ | 仅接受字符串数组;旧版 `{"text": ...}` 对象数组返回 400 |
| `top_n` | ✅ | 可选,限制返回条数 |
| `return_documents` | ✅ | 为 `true` 时回带原文 |
`max_tokens_per_doc` 与未知字段静默忽略。响应按相关度降序,
`results[].index` 指向输入下标,`relevance_score` 为 0~1 浮点;无 token 用量口径。
### Moderations
**`POST /ai/v1/moderations` · OpenAI 外壳映射 OCI Guardrails**
| 标准字段 | 状态 | 网关行为 |
| --- | :---: | --- |
| `input` 为字符串 | ✅ | 单条审核 |
| `input` 为字符串数组 | ✅ | 逐条审核,单次 1~8 条 |
| 字符串 `input` | ✅ | 单条审核 |
| 字符串数组 `input` | ✅ | 逐条审核,单次 18 条 |
不支持(返回 400):多模态 input(图片等对象数组形态)、空字符串条目、空数组或超过 8 条的数组。静默忽略:`model`(接受任意值,不校验白名单)及其他未知字段
不支持(返回 400):多模态 input、空字符串条目、空数组或超过 8 条。
`model` 接受但忽略,不参与白名单判断。
响应边界:
- `categories` / `category_scores` 用 OCI 原生维度 `overall` / `blocklist` / `prompt_injection`,而非 OpenAI 标准类目(`hate` / `violence` 等);任一维度得分 ≥ 0.5 判定 `flagged`
- PII 命中放扩展字段 `results[].pii``text` / `label` / `score` / `offset` / `length`),不参与 `flagged` 判定;实测中文人名 / 手机号识别较弱,英文 PII 识别正常
- categories 使用 OCI 原生维度 `overall` / `blocklist` / `prompt_injection`
- 任一维度得分 ≥ 0.5 时 `flagged=true`
- PII 命中放在扩展字段 `results[].pii`,包含 `text``label``score`
`offset``length`,不参与 `flagged`
- 实测中文人名与手机号识别较弱,英文 PII 识别正常
- 响应 `model` 恒为 `oci-guardrails`
实现依据:[`genai_guard.go`](../internal/oci/genai_guard.go) · [`moderations.go`](../internal/aiwire/moderations.go) · [`service/aigateway_extras.go`](../internal/service/aigateway_extras.go)
### Models
</details>
**`GET /ai/v1/models` · 当前密钥可见模型列表**
`GET /ai/v1/models` 使用 OpenAI Models 列表外壳(`object``data[].id/object/created/owned_by`),但只返回当前渠道目录中通过分组、全局黑名单和密钥白名单筛选后的模型;网关不提供标准的单模型检索端点。面板「过滤弃用模型」开关开启时,OCI 已宣布弃用(即使未到退役日)的模型同时从模型列表与路由中排除,关闭后恢复。
响应使用 OpenAI Models 列表外壳,只返回同时通过以下条件的模型:
1. 存在于当前渠道目录
2. 匹配密钥绑定的渠道分组
3. 不在全局模型黑名单
4. 命中密钥模型白名单(配置时)
5. 未被「过滤弃用模型」开关排除
网关不提供标准的单模型检索端点。
<a id="implementation"></a>
## 实现索引
| 端点 / 能力 | 主要实现 |
| --- | --- |
| Responses 直通与 Codex 工具兼容 | [`airesponses.go`](../internal/service/airesponses.go) · [`responses.go`](../internal/aiwire/responses.go) |
| Chat Completions 转换 | [`chatresponses.go`](../internal/service/chatresponses.go) · [`openai.go`](../internal/aiwire/openai.go) |
| Anthropic Messages 转换 | [`anthresponses.go`](../internal/service/anthresponses.go) · [`anthropic.go`](../internal/aiwire/anthropic.go) |
| 渠道路由与 Embeddings | [`aigateway_chat.go`](../internal/service/aigateway_chat.go) |
| TTS 上游 | [`genai_speech.go`](../internal/oci/genai_speech.go) · [`aigateway_extras.go`](../internal/service/aigateway_extras.go) |
| Rerank / Moderations 上游 | [`genai_guard.go`](../internal/oci/genai_guard.go) · [`rerank.go`](../internal/aiwire/rerank.go) · [`moderations.go`](../internal/aiwire/moderations.go) |
| HTTP Handler 与流式桥接 | [`aigateway.go`](../internal/api/aigateway.go) · [`aigateway_extras.go`](../internal/api/aigateway_extras.go) |
本文是兼容性快照,不替代 Swagger。标准接口、Codex 客户端与 OCI 上游均可能
变化,最终行为以当前版本代码、运行时 Swagger 和实测结果为准。