fuzhengwei / fuzhengwei/WaLiAPI

原生透传请求带 image_url 块到纯文本上游,导致 400

Open
#15 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
128
Forks
39
Avg merge
15h 25m
Merged PRs (30d)
44

Description

## Bug: 原生透传请求带 `image_url` 块到纯文本上游,导致 400

### 现象

一个会话请求失败,上游返回:

```text
400 Error from provider (Console Go): Upstream request failed:
[invalid_request_error] Failed to deserialize the JSON body into the target type:
messages[64]: unknown variant `image_url`, expected `text` at line 1 column 371639
```

逐段拆解:

- 上游是 **Rust + serde** 写的网关(报错是 serde 反序列化特征),其消息 content block 枚举**只有 `text` 一种变体**。
- `messages[64]` 指请求第 65 条消息里带了一个 `{"type": "image_url", "image_url": {...}}` 块。
- `column 371639` 说明请求体约 37 万字节,该图是 **base64 内嵌**(很可能是历史消息 / 工具调用结果里的图)。

### 根因

1. 下游发的是 OpenAI Chat Completions 请求,某条历史消息带 `image_url` 块(base64)。
2. WaLiAPI 将该请求**原生透传**给该渠道:`src-tauri/src/core/attempt.rs` 的 `GroupTier::Native` 分支直接 `forward_json.clone()` 原样转发,**只替换 model,图片块不过任何校验**。
3. 上游 "Console Go" 是纯文本网关(schema 只有 `text` 块),不认识 `image_url` → 400。

### 关键点:这是能力盲区,不是回归

- 安全闸门其实**收集了**图片特征:`src-tauri/src/security/features.rs` 会抓 `image_urls` 和 `base64_attachments`,但 `RequestFeatures` 只用于**审计记录**,**不参与路由,也不拦截**。
- `route_plan.rs` 选渠道时只按协议/端点匹配,**没有任何渠道是否有视觉能力(vision)的判断**。
- 现有 codec "image gate"(`src-tauri/src/protocol/codec/chat.rs` 的 `image_url` 分支)只校验图片**格式**(media type / 大小 / http(s) 或 data URL),且只发生在 **Chat→Anthropic 转换路径**;**原生透传路径完全不过它**。

### 对照参考(已调研)

- **CLIProxyAPI (CPA)**:图片只做格式转换(OpenAI `image_url` ↔ Claude `image` base64),**默认不剥离、不重路由**。唯一例外是用户**手配** `input-modalities: [text]` 的 OpenAI-compat 渠道,会把 **`role=tool` 消息**里的图展平为 `[image omitted: unsupported by upstream]`(`internal/runtime/executor/helps/openai_compat_tool_results.go`)。`SupportedInputModalities`(models.json)只喂给 Codex 客户端做目录展示,不参与请求转换。
- **9Router**:在翻译前查内联能力表(`providers/capabilities.js`),模型 `vision:false` 时**剥掉图片块**并换成文字占位符(`translator/concerns/modality.js`)。只处理翻译路径,原生透传不动,**不重路由**。
- 两家都不做"换渠道",因为都是单渠道代理;WaLiAPI 是多渠道路由,理论上可以**避开**无视觉渠道。

### 期望行为(候选方向,未定)

1. 路由层:请求带图时跳过被声明/推断为无视觉能力的渠道(需给渠道加能力标记,改动较大)。
2. 或仿 9Router:路由到无视觉渠道前剥掉图片块并替换占位文本(丢内容但请求成功)。
3. 或仿 CPA 最低成本:不处理图片,仅在把上游 400 抛回前,检测请求带图时附加"该渠道疑似不支持图片"诊断信息(fail-open,与项目现有哲学一致)。

### 环境

- 分支:`codex/channel-protocol-refactor-plan`
- 上游渠道 "Console Go":OpenAI Chat Completions,纯文本网关
- 请求:OpenAI Chat Completions,`messages[64]` 带 base64 `image_url`

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the native forwarding branch in src-tauri/src/core/attempt.rs, then trace feature collection in src-tauri/src/security/features.rs and channel selection in route_plan.rs. Compare this with the image_url handling in src-tauri/src/protocol/codec/chat.rs, reproduce the reported native request if possible, and first settle whether the intended result is rerouting, image omission, or diagnostic-only failure handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.