> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/reference/clients-and-gateway-diffs · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 客户端接入与网关差异

本章讲两件事：各客户端怎么连上网关；连上之后，网关的行为和直连上游有什么不同。后者只讲**你会观察到什么、怎么应对**，不讲背后实现。

## 客户端接入

通用规则：把客户端的 `base_url` 指向网关，API key 填你在网关签发的访问密钥，模型名填你在网关配的名字。

### OpenAI SDK（Python / Node）

```text
base_url = http://<host>:7890/v1
api_key  = <你的访问密钥>
```

curl：

```bash
curl http://localhost:7890/v1/chat/completions \
  -H "Authorization: Bearer <你的访问密钥>" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'
```

Embeddings / Images / Audio / Rerank 同理，完整路径见 [端点清单](/zh-CN/reference/endpoints.md#endpoint-list)。

### Anthropic SDK

```text
base_url = http://<host>:7890
api_key  = <你的访问密钥>   # 通过 x-api-key 头发送
```

curl：

```bash
curl http://localhost:7890/v1/messages \
  -H "x-api-key: <你的访问密钥>" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet","max_tokens":1024,"messages":[{"role":"user","content":"hi"}]}'
```

> Anthropic SDK 默认把路径拼成 `/v1/messages`，所以 base_url 填到根（不带 `/v1`）。

### Google Gemini SDK

```text
api_key  = <你的访问密钥>   # 通过 x-goog-api-key 头发送
base_url = http://<host>:7890
```

curl：

```bash
curl "http://localhost:7890/v1beta/models/gemini-2.0-flash:generateContent" \
  -H "x-goog-api-key: <你的访问密钥>" \
  -H "Content-Type: application/json" \
  -d '{"contents":[{"parts":[{"text":"hi"}]}]}'
```

### DashScope

两种接法：

- **透传**：`POST /v1/services/{*rest}`，请求体原样发给 DashScope 上游。
- **翻译**：`POST /v1/chat/completions`，网关把 OpenAI 格式翻译成 DashScope 格式。

### 任意协议透传

当客户端协议与目标协议相同、你想完全绕过翻译时：

```bash
curl http://localhost:7890/v3/<模型名>/<任意后续路径> \
  -H "Authorization: Bearer <你的访问密钥>" \
  -d '{ ...原样请求体... }'
```

`/v3/{model}/{*rest}` 把请求体原样转发到该模型配置的目标上游。

### Cherry Studio 等第三方客户端

填法：

- API 地址：`http://<host>:7890/v1`（OpenAI 兼容）
- API Key：网关访问密钥
- 模型：填网关里配的模型名

> 部分客户端按 model id 推断是否支持 function calling。若工具调用不生效，检查模型名是否落在客户端的能力识别规则内，或在客户端侧手动开启。

## 网关与原生 API 的差异

直连上游 vs 经网关，调用方会观察到这些不同。

### 长时间推理时收到 `:keep-alive` 注释行

流式请求中，如果上游推理时间较长，网关会周期性发送 SSE 注释行 `:keep-alive` 保持连接活跃，防止中间代理因空闲超时断开。

- 这是 SSE 注释（以 `:` 开头），**不是数据事件**，SSE 客户端标准实现会自动忽略，无需特殊处理。
- 如果你手写 SSE 解析，跳过以 `:` 开头的行即可。

### 上游流异常时收到 `_gateway_warning` 字段

当上游流在传输中途异常中断时，网关不会直接报错打断你的对话，而是：

- 关闭尚未结束的内容块；
- 在 SSE 流里插入一个 `_gateway_warning` 扩展字段，告诉你中断原因（含 `reason` / `detail` / `last_finish_reason` / `timestamp`）；
- 发送正常的终止序列，让客户端能正常收尾。

- `_gateway_warning` 是网关的协议扩展字段（带下划线前缀）。解析流时如果看到它，说明这次响应中途出了问题，可据此记录或告警。
- 设计目的是避免 Claude Code 这类客户端因流异常而中断整段对话。

### 错误码与重试

经网关时你会遇到这些状态码（完整速查见 [错误码](/zh-CN/reference/error-codes.md)）：

| 状态码 | 含义 | 你的处理 |
|--------|------|---------|
| 429 | 被限流（每密钥/每 IP/上游） | 看 `Retry-After` 头等待后重试；无该头则按响应体提示 |
| 503 | 服务过载/不可用/并发达上限 | 退避后重试 |
| 504 | 超时 | 退避后重试 |
| 502 | 上游故障 | 网关通常已自动故障转移到其他节点；持续 502 检查上游配置 |

> **所有 429 响应均为 JSON**（`{"error":{...}}`）；仅每 IP 限流不带 `Retry-After` 头。

### 上游故障时自动转移到其他节点

如果你的模型配了负载均衡（见 [负载均衡字段](/zh-CN/reference/load-balancing-fields.md)），上游某节点故障时，网关会自动把请求重试到其他节点。你会观察到：

- 响应可能来自不同于上一次的节点（正常现象）。
- 如果所有节点都不可用，返回 503。

单上游（非负载均衡）不会跨节点转移，上游故障直接返回 502。

### 跨协议工具调用

当客户端协议和上游协议不同、且请求带工具调用时：

- 网关自动翻译 `tools` / `tool_choice` / `tool_calls` 等字段。
- **`tool_call_id` 在跨协议往返中必须保留原值**——你在多轮工具对话里回传的 `tool_call_id` 要和网关给你的完全一致，否则上游无法匹配。
- **Anthropic 面向客户端：响应里的 `tool_use` id 由网关合成为全局唯一的 `toolu_` id**（不透传上游原始 id），客户端原样回显即可。
- **Anthropic 面向客户端：请求历史里的 `tool_use` id 必须全局唯一**，重复时网关回 `400 invalid_request_error`（`tool_use ids must be unique`），与 Anthropic 官方 API 一致。
- 流式工具调用期间，网关持续转发增量片段，保证长工具调用时始终有事件流动。

### 思考签名（signature / encrypted_content）经网关封装

部分厂商（Anthropic、OpenAI、Google 等）在多轮对话里下发加密的思考签名/密文，下轮要原样带回，且**只有颁发它的那个账号能解开**。经网关时你观察到的差异：

- **同协议直通也不再裸透传**：即使客户端协议和上游协议相同（如 Anthropic ⇄ Anthropic、OpenAI Responses ⇄ OpenAI 官方），签名也会被网关封装成不透明的网关信封——你拿到的仍是一个不透明字符串，照常原样回传即可，无需解析。
- **中途切换模型/上游/账号**：旧签名对新的处理方是无法解开的外来密文，网关会自动剥离（thinking 保留可读文本、工具调用历史保留），而不是把它送去解不开它的账号导致上游 400。剥离只影响思考的加密接续，不影响对话内容。
- **升级过渡**：升级前下发的旧格式签名，只要模型没变仍照常工作；模型变了则同样按剥离处理。

### store 参数与响应取回 {#store-param}

Responses 协议的 `store` 请求参数控制这一轮是否被保存。只要网关配有数据库存储后端（`STORAGE_MODE` 为 `sqlite` / `postgresql`，即有持久化后端），**无论上游是什么协议，Responses 存储都由网关集中承载**：历史接龙（`previous_response_id`）由网关消费，轮次落网关存储，响应 id 重写为网关自己的 `resp_{id}`。你在多轮对话中途切换模型不会断链——接龙、取回、删除都在网关侧闭环。

- **`store` 缺省是 true**。不传就按 true 处理，网关照常落库。
- **`store: false` 只是不落库**：历史合并照常进行，响应也照常返回。之后如果有请求拿这个没落库的 id 当 `previous_response_id`，网关查不到历史，会静默降级为只用当前轮上下文，不报错。
- **`GET /v1/responses/{id}` 取回**：id 带不带 `resp_` 前缀都行。网关存储的轮次，响应包含 `id`、`object:"response"`、`status`、`model`、`output`（逐字输出项）、`previous_response_id`（若有）、`created_at`，不含 `input`。刚结束流式响应后立刻 GET，输出可能还在补写，此时 `status` 为 `in_progress`，稍后重试即为 `completed`。
- **`DELETE /v1/responses/{id}` 删除**：成功返回 `{id, object:"response.deleted", deleted:true}`。只删这一轮，不级联；删掉链条中间的一轮之后，它的后代轮仍能取回，但后续请求的历史重建会静默丢掉被删祖先的历史。
- **GET/DELETE 也能触达上游侧存储**：轮次存在上游（网关未集中承载、原生 Responses 透传到上游，或 background 轮）时，网关按属主登记把请求代理到当初存它的那个上游，响应是上游的完整原生形状。
- **只能操作自己的数据**：无论轮次在网关存储还是上游侧，都只认当初存它的那把 access key——查不到、归属不符，一律返回 404（不暴露别人的数据是否存在）。
- 归属信息是随轮次一起存的；没有归属信息的旧轮次无法取回或删除（404）。
- **与 background 共存**：`background: true` 的轮次豁免集中化——响应 id 是上游的真实 id（它是事后取结果的凭证），轮次不落网关存储。拿这个 id GET 取结果时，网关经属主代理从上游取回真实完成态；`previous_response_id` 接龙到这类轮次时，该轮及其后继在上游侧延续，引用回更早的网关轮次时自动切回网关侧接龙。

## 常见问题

**Q：客户端报「stream 超时」或连接断开？**
检查客户端和网关之间是否有反向代理，其空闲超时是否短于网关 keep-alive 间隔。调大反代超时，或调小 `STREAMING_KEEPALIVE_SECONDS`（默认 15 秒）。

**Q：响应里混进了 `_gateway_warning`，是上游返回的吗？**
不是，是网关加的。说明上游流中途断了。看里面的 `reason`/`detail` 判断是否需重试。

**Q：流式请求被全局并发限制挡了，返回 503？**
网关有全局并发上限（`server.max_global_concurrency`，默认 1000；流式另有 `streaming.max_concurrent_streams`，默认 200）。高峰期短暂 503 属正常，退避重试即可；上限可调（TOML 字段，见 [配置参考 → 仅配置文件可配的字段](/zh-CN/reference/configuration.md#config-file-only-fields)），如需调高联系管理员。

**下一步**：管理员从 [控制台登录与角色](/zh-CN/console/login-and-roles.md) 开始；查错误码看 [错误码](/zh-CN/reference/error-codes.md)；查协议对支持看 [协议互通矩阵](/zh-CN/reference/protocol-matrix.md)。
