OpenCSG AI Gateway:auto 模型使用指南
💡 说明:本文示例中的
$AIGW代表 AI Gateway 的服务入口地址,实际调用时请替换为您的网关域名(例如 SaaS 平台对外地址或私有化部署服务 Endpoint)。
一、请求头的作用
| Header | 作用 | 格式要求 |
|---|---|---|
CSG-Router-Session-Id | Router 会话 ID,用来保证同一会话的上下文能连贯地做排序 | 必须是 ses_<32位hex> |
CSG-Router-Turn-Id | 同一轮内复用已保存的排序结果,让模型固定不变 | 必须是 turn_<32位hex>,而且必须同时带 Session |
X-Session-ID | ① 上游亲和:按一致性哈希固定到同一个上游,复用 KV/prefix cache;② 值是 ses_ 格式时,同时当作 Router Session 的别名 | 任意值都可用于①,只有 ses_ 格式才生效于② |
关键规则:
- 这些 ID 由 Router 生成,客户端不能自己编。网关会在响应头里返回
CSG-Router-Session-Id和CSG-Router-Turn-Id,客户端保存后在后续请求里带回去。 - 格式不对的值会被静默丢弃,不会报错,Router 会当作新会话重新生成 ID。只带 Turn、不带 Session 时,Turn 也会被丢弃。
- 网关转发给上游模型之前会删掉
CSG-Router-*头;X-Session-ID会保留,因为上游亲和要用。
二、正确的调用方式(以 Agent 多轮为例)
第 1 轮,用户提问:不带任何 Router 头。
curl -i $AIGW/v1/chat/completions \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"帮我写个快排"}],"tools":[...]}'
# 响应头:
# CSG-Router-Session-Id: ses_8f3a...(32hex)
# CSG-Router-Turn-Id: turn_1c9e...(32hex)
同一轮内的工具调用循环(tool_call → tool 结果 → 再次请求):Session 和 Turn 都带上。
curl $AIGW/v1/chat/completions \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "CSG-Router-Session-Id: ses_8f3a..." \
-H "CSG-Router-Turn-Id: turn_1c9e..." \
-d '{"model":"auto","messages":[ user, assistant(tool_calls), tool(result) ],"tools":[...]}'
Router 会返回这一轮保存好的排序结果,所以模型不会在工具循环中途切换。
第 2 轮,用户发了新消息:只带 Session,不带 Turn。
curl -i $AIGW/v1/chat/completions \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "CSG-Router-Session-Id: ses_8f3a..." \
-d '{"model":"auto","messages":[ ...历史..., {"role":"user","content":"再改成非递归"}]}'
# Router 结合会话上下文重新排序,并返回一个新的 Turn-Id
Python(OpenAI SDK)示例:
from openai import OpenAI
client = OpenAI(base_url=f"{AIGW}/v1", api_key=KEY)
sid = tid = None
def call(messages, new_turn):
global sid, tid
headers = {}
if sid:
headers["CSG-Router-Session-Id"] = sid
if not new_turn and tid:
headers["CSG-Router-Turn-Id"] = tid
raw = client.chat.completions.with_raw_response.create(
model="auto", messages=messages, extra_headers=headers)
sid = raw.headers.get("CSG-Router-Session-Id") or sid
tid = raw.headers.get("CSG-Router-Turn-Id") or tid
return raw.parse()
# 用户发新消息 -> call(msgs, new_turn=True)
# 工具结果回填后继续 -> call(msgs, new_turn=False)
三、带不带 Header 的区别
| 场景 | 行为 | 后果 |
|---|---|---|
| 完全不带 | 每个请求都是一个新会话,独立排序 | 单轮问答没问题。Agent 场景下,同一个工具循环里模型可能换来换去,tool_call 格式和推理风格都不连贯;每次还要多一次完整的排序,延迟更高 |
| 只带 Session(新一轮) | 在同一会话内重新排序,生成新 Turn | 每个新用户问题都应该这么发 |
| Session + Turn(同一轮内) | 直接复用这一轮的排序结果,模型不变 | 工具循环、重试时应该这么发 |
| 只带 Turn | 网关把 Turn 丢掉,等于不带 | 不报错,但会悄悄失去粘性 |
| ID 是自己编的、格式不对 | 静默丢弃,生成新会话 | 同上,排查起来很隐蔽 |
X-Session-ID: <任意值> | 不作为 Router Session,只用于上游亲和;同时会关闭容量感知的上游重选,只检查固定的那个上游 | 选模型时没有粘性,但选中模型后的上游固定,cache 命中率更高 |
X-Session-ID: ses_xxx | 同时作为 Router Session 和上游亲和的 key | 适合已经在发 X-Session-ID 的老客户端,不用再加一个头;有 CSG-Router-Session-Id 时以它为准 |
Responses API 带 previous_response_id | 会话已经绑定到某个上游,直接复用拥有该上游的模型,不调用 Router | 不排序、没有降级候选、不返回 Router 头;绑定的上游不可用时直接报错 |
一句话总结
model 填 auto。从响应头里拿到 Session 和 Turn 后,同一轮 内两个都带,新一轮只带 Session。不要自己编 ID。