跳到正文

把含图片的请求路由到视觉模型

文本模型(不支持视觉的模型)收到带图片的请求,上游会以 400 拒收。典型触发场景:coding agent 类客户端(Claude Code 等)会重放整段对话历史,一旦历史里某轮出现过图片——哪怕只是工具读过一张截图——后续每一轮都会带着这张图重发,文本模型从此轮轮报错。

解法:给该模型加一条 switch-route 规则(请求体规则的一种模式)。它在协议翻译客户端请求形状检测图片,命中即把本次请求改投到视觉模型;不含图片的请求照常走原模型。判定只做结构检查、不读图片二进制内容,含 base64 的大请求体不会被整段读入内存。

前置

  • 已有一个文本模型路由(下称「原文本模型」),客户端反馈带图请求 400。
  • 已配好一个支持视觉的目标模型(下称「视觉模型」)。switch-route 的目标支持跨协议:客户端说 Anthropic、视觉模型走 OpenAI Chat Completions,也可以。
  • 客户端无需授权视觉模型:改投目标视为管理员指定的内部路由,网关不对它重新校验调用方分组的模型 ACL(模型 ACL 只在请求入口对客户端原始模型校验一次)。

配置步骤(控制台)

控制台 → 上游与模型 → 找到原文本模型 → 编辑 → 请求体规则+ 添加规则

  1. 模式下拉选 switch-route,该行展开两个控件。
  2. 切换到模型(use_model):下拉选视觉模型。
  3. 命中条件(when):逐条添加判定,每条填字段路径(按客户端协议的形状写);「等于」留空表示「字段存在即命中」,填值表示「值相等才命中」;多条之间是的关系。

控制台实际界面(截图为英文 UI,中文 UI 对应「请求负载规则 / 切换到模型 (use_model) / 命中条件 when / + 添加条件」):

Request Payload Rules:模式下拉展开,选中 switch-route(列表末项高亮)

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

Switch to model (use_model):下拉列出网关已配置的全部模型,选中目标视觉模型;下方逐条添加命中条件

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 数组,与已有规则共存):

json
{
  "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 全部模式一览。