跳到正文

客户端接入与网关差异

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

客户端接入

通用规则:把客户端的 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 同理,完整路径见 端点清单

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 这类客户端因流异常而中断整段对话。

错误码与重试

经网关时你会遇到这些状态码(完整速查见 错误码):

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

所有 429 响应均为 JSON{"error":{...}});仅每 IP 限流不带 Retry-After 头。

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

如果你的模型配了负载均衡(见 负载均衡字段),上游某节点故障时,网关会自动把请求重试到其他节点。你会观察到:

  • 响应可能来自不同于上一次的节点(正常现象)。
  • 如果所有节点都不可用,返回 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_errortool_use ids must be unique),与 Anthropic 官方 API 一致。
  • 流式工具调用期间,网关持续转发增量片段,保证长工具调用时始终有事件流动。

思考签名(signature / encrypted_content)经网关封装

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

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

store 参数与响应取回

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

  • store 缺省是 true。不传就按 true 处理,网关照常落库。
  • store: false 只是不落库:历史合并照常进行,响应也照常返回。之后如果有请求拿这个没落库的 id 当 previous_response_id,网关查不到历史,会静默降级为只用当前轮上下文,不报错。
  • GET /v1/responses/{id} 取回:id 带不带 resp_ 前缀都行。网关存储的轮次,响应包含 idobject:"response"statusmodeloutput(逐字输出项)、previous_response_id(若有)、created_at,不含 input。刚结束流式响应后立刻 GET,输出可能还在补写,此时 statusin_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 字段,见 配置参考 → 仅配置文件可配的字段),如需调高联系管理员。

下一步:管理员从 控制台登录与角色 开始;查错误码看 错误码;查协议对支持看 协议互通矩阵