把含图片的请求路由到视觉模型
文本模型(不支持视觉的模型)收到带图片的请求,上游会以 400 拒收。典型触发场景:coding agent 类客户端(Claude Code 等)会重放整段对话历史,一旦历史里某轮出现过图片——哪怕只是工具读过一张截图——后续每一轮都会带着这张图重发,文本模型从此轮轮报错。
解法:给该模型加一条 switch-route 规则(请求体规则的一种模式)。它在协议翻译前按客户端请求形状检测图片,命中即把本次请求改投到视觉模型;不含图片的请求照常走原模型。判定只做结构检查、不读图片二进制内容,含 base64 的大请求体不会被整段读入内存。
前置
- 已有一个文本模型路由(下称「原文本模型」),客户端反馈带图请求 400。
- 已配好一个支持视觉的目标模型(下称「视觉模型」)。
switch-route的目标支持跨协议:客户端说 Anthropic、视觉模型走 OpenAI Chat Completions,也可以。 - 客户端无需授权视觉模型:改投目标视为管理员指定的内部路由,网关不对它重新校验调用方分组的模型 ACL(模型 ACL 只在请求入口对客户端原始模型校验一次)。
配置步骤(控制台)
控制台 → 上游与模型 → 找到原文本模型 → 编辑 → 请求体规则 → + 添加规则:
- 模式下拉选
switch-route,该行展开两个控件。 - 切换到模型(use_model):下拉选视觉模型。
- 命中条件(when):逐条添加判定,每条填字段路径(按客户端协议的形状写);「等于」留空表示「字段存在即命中」,填值表示「值相等才命中」;多条之间是或的关系。
控制台实际界面(截图为英文 UI,中文 UI 对应「请求负载规则 / 切换到模型 (use_model) / 命中条件 when / + 添加条件」):

模式选 switch-route 后,规则行展开「Switch to model (use_model)」与「Match conditions when」两组控件;「等于」留空即存在性检查。

use_model 下拉只列网关里已配置的模型——目标视觉模型没建好时这里选不到,先建好模型。名字以列表里的完整名称为准,可能带上游前缀。
保存即生效(配置热重载,无需重启)。
⚠️ 规则要挂在客户端请求实际命中的那条模型配置上。如果客户端模型名是一个负载均衡器(LB),规则挂在 LB 背后的节点模型上——LB 解析出的节点会带着自己的请求体规则执行。
条件路径怎么写
图片在请求体里的位置取决于客户端协议,且对 Anthropic 客户端有一个容易漏的深层位置。以三类常见客户端协议为例:
Anthropic 客户端(/v1/messages)
Anthropic 的图片块是 {"type": "image", "source": {...}},可能出现在两个深度:
| 位置 | 形状 | 条件路径 |
|---|---|---|
| 消息顶层内容块 | messages[].content[] 直接是 image 块 | messages.*.content.*.type 等于 image |
| 工具结果嵌套 | messages[].content[] 是 tool_result 块,其 content[] 里才是 image 块 | messages.*.content.*.content.*.type 等于 image |
两个条件都要加。 只配顶层那条是常见错误:coding agent 用工具读过图片文件后,图片以 tool_result 内容块的形式进入历史,比顶层深一层——顶层条件匹配不到它,带图请求照样打到文本模型继续 400。
OpenAI Chat Completions 客户端(/v1/chat/completions)
图片块是 {"type": "image_url", ...},只出现在 user 消息顶层(tool 角色消息的 content 是字符串,没有嵌套图片):
messages.*.content.*.image_url,「等于」留空(存在性检查)
DashScope 客户端
input.messages.*.content.*.image,「等于」留空(存在性检查)
多种客户端协议并存时,把各协议的路径都并列进 when(OR 语义,任一命中即切换)。
JSON 形态
上面的控制台配置对应的 request_payload 规则(Anthropic 客户端,追加进原文本模型的 request_payload 数组,与已有规则共存):
{
"mode": "switch-route",
"when": [
{ "path": "messages.*.content.*.type", "eq": "image" },
{ "path": "messages.*.content.*.content.*.type", "eq": "image" }
],
"use_model": "qwen-vl-max"
}验证
配置保存后,重放一条带图片的请求(或用最小用例:user 消息含一个 image 块),到控制台日志查看器看这条请求:
- 模型列应显示视觉模型(而非原文本模型),上游协议是视觉模型的协议,状态码 200。
- 再发一条纯文本请求,模型列应仍是原文本模型——确认未误伤。
行为语义与注意事项
- 按请求粒度路由,不是按轮次。 图片一旦进入对话历史,后续每一轮请求都会带着它、每一轮都会命中条件改投视觉模型。这是预期行为:上下文里有图,就需要视觉模型来理解。
- 改投会带上全部历史。 命中后整段历史被翻译给视觉模型(包括其中的纯文本轮次),按视觉模型计价。
- 谓词只做结构检查。 路径里的
*是数组通配(任一元素命中即可);「等于」留空是存在性检查。谓词不读字符串值的内容、不读二进制,大 body 的磁盘暂存保持开启。
备选方案:不切模型,直接剥图
如果业务上「看不见图也行、只要别报错」,改用 filter-content-types 规则:网关把图片块从请求里剥掉、在原位注入一段提示文本(可自定义),请求合法地送给文本模型。两种方案互斥,取其一:
| 方案 | 效果 | 适用 |
|---|---|---|
switch-route(本页) | 带图请求改投视觉模型,图片信息保留 | 图片内容对回答有价值 |
filter-content-types | 剥掉图片,原模型照常服务 | 只想消除报错,不在乎图片内容 |
下一步:请求改写与路由:怎么选 看三种改写手段的选型边界;上游与模型字段 看 request_payload 全部模式一览。
