Skip to content

[Feature] Add Anthropic Messages API support for vision backends (e.g. OpenCode Go Qwen3.7 Plus) #16

Description

@ArietidsZ

Use case or problem

我希望在 Pi 中使用以下模型组合:

  • 主模型:opencode-go/deepseek-v4-flash
  • 辅助视觉模型:opencode-go/qwen3.7-plus
  • 同时保留 Pi 中已登录的 Codex、Grok 等原生多模态模型

当前已经会根据:

ctx?.model?.input?.includes?.("image")

旁路原生多模态模型,因此本请求不涉及更改 Codex、Grok 等模型的原生图片输入,也不涉及模型路由冲突。

当前限制在于辅助视觉模型的 API 格式

目前Pi extension 和 OpenCode plugin 中的视觉请求实现均固定使用 OpenAI Chat Completions 格式:

POST {VISION_BASE_URL}/chat/completions

图片使用:

{
  "type": "image_url",
  "image_url": {
    "url": "data:image/png;base64,..."
  }
}

响应则固定从以下路径读取:

choices[0].message.content

但是 OpenCode Go及部分其它运营商当前为 Qwen3.7 Plus 提供的是 Anthropic Messages-compatible endpoint:

POST https://opencode.ai/zen/go/v1/messages

模型 ID:

qwen3.7-plus

OpenCode Go 的 endpoint 表:

https://github.com/anomalyco/opencode/blob/dev/packages/web/src/content/docs/go.mdx#endpoints

例如,以下配置目前无法通过 OpenCode Go 文档中为该模型提供的 API 格式工作:

VISION_API_KEY=<redacted>
VISION_BASE_URL=https://opencode.ai/zen/go/v1
VISION_MODEL=qwen3.7-plus

当 Pi 的文本模型收到图片时,extension 会把请求发送到:

https://opencode.ai/zen/go/v1/chat/completions

而不是该模型对应的:

https://opencode.ai/zen/go/v1/messages

同时,请求图片块和响应结构也分别属于 OpenAI 与 Anthropic 两种不同格式。

这不仅影响 Pi extension,也影响所有复用 vision_client.py 的联网视觉工具,例如 glance,以及包含相同 describe core 的 OpenCode plugin。

Expected behavior

允许用户显式选择视觉后端使用的 API 格式,使以下组合可以工作:

Pi primary model:
opencode-go/deepseek-v4-flash

Vision backend:
opencode-go/qwen3.7-plus

Vision API format:
anthropic-messages

当切换到支持图片输入的 Codex、Grok 或其他原生多模态模型时,Pi extension 仍应保持当前行为:不改写图片,也不调用辅助视觉 API。

Actual behavior

视觉客户端固定构造 OpenAI /chat/completions 请求,因此只能使用支持 OpenAI Chat Completions 与 image_url 格式的视觉 endpoint,无法使用仅暴露 Anthropic /messages 格式的视觉模型。

Proposed change

建议增加一个显式、向后兼容的视觉 API 格式配置,例如:

# Existing behavior and default
VISION_API_FORMAT=openai-chat-completions

或:

VISION_API_FORMAT=anthropic-messages

变量名称可以按项目现有命名习惯调整;重点是由用户明确选择格式,而不是根据模型名称进行隐式推断。

openai-chat-completions

保持当前行为不变:

POST {VISION_BASE_URL}/chat/completions

请求使用:

{
  "model": "...",
  "max_tokens": 4096,
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "..."
        },
        {
          "type": "image_url",
          "image_url": {
            "url": "data:image/png;base64,..."
          }
        }
      ]
    }
  ]
}

解析:

choices[0].message.content

anthropic-messages

使用:

POST {VISION_BASE_URL}/messages

将 data URL 转换为 Anthropic image source block,例如:

{
  "model": "qwen3.7-plus",
  "max_tokens": 4096,
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "..."
        },
        {
          "type": "image",
          "source": {
            "type": "base64",
            "media_type": "image/png",
            "data": "..."
          }
        }
      ]
    }
  ]
}

解析响应中的文本块:

{
  "content": [
    {
      "type": "text",
      "text": "..."
    }
  ]
}

即合并所有:

content[].text

鉴权 header 可以遵循 Anthropic-compatible endpoint 的要求,同时继续保证:

  • API key 不会进入日志
  • API key 在错误正文中被替换为 <redacted>
  • 请求失败时不会静默返回伪造的图片描述
  • 当前重试和超时策略保持不变

Suggested implementation scope

建议把协议差异限制在较小的 request/response adapter 中,例如:

build request URL
build request headers
build request payload
parse response text

然后在以下实现中复用相同语义:

  • vision_client.py
  • extensions/pi/vision.ts
  • extensions/opencode/vision.ts

Pi 和 OpenCode extension 仍可保持单文件、自包含,不需要引入 SDK 或额外运行时依赖。

本功能只需要支持视觉描述所需的最小 Anthropic Messages 子集:

  • 非流式请求
  • 一个 user message
  • text blocks
  • image blocks
  • 一个或多个图片
  • text response blocks

不需要同时实现:

  • tool use
  • thinking blocks
  • prompt caching
  • streaming
  • 完整 Anthropic SDK 行为

Backward compatibility

VISION_API_FORMAT 未设置时,应继续默认使用当前的 OpenAI Chat Completions 格式。因此已有用户的配置和行为不需要变化。

该变更也不应修改:

  • Pi 对原生多模态模型的旁路逻辑
  • 当前 focus-hint 行为
  • 图片描述缓存
  • stored session 中的原始图片
  • CLI 与 proxy 的边界
  • 用户的 Pi/OpenCode 登录凭证
  • 模型名称或 provider 配置

本请求也不要求 toolkit 自动读取 Pi 的 auth.json。继续由用户通过 VISION_API_KEY 显式提供凭证即可。

Suggested acceptance criteria

  • 未设置 VISION_API_FORMAT 时,现有 OpenAI-compatible 配置和测试全部保持原行为。
  • VISION_API_FORMAT=anthropic-messages 时,请求发送到 {VISION_BASE_URL}/messages
  • data URL 图片被正确转换为 Anthropic base64 image blocks。
  • 多图请求仍在一次视觉调用中发送。
  • 正确提取并合并 Anthropic 响应中的 content[].text
  • Pi 的原生多模态模型仍保持图片原样,不调用辅助视觉后端。
  • Pi 文本模型可以通过 Qwen3.7 Plus 的 Anthropic Messages endpoint 获得图片描述。
  • API 错误仍明确展示,且响应正文中的 API key 被脱敏。
  • tests/test_vision_client.py 增加两个 API 格式的 stubbed request/response 测试。
  • tests/test_extensions.mjs 增加 Pi 和 OpenCode extension 的 Anthropic Messages 测试。
  • 测试不需要真实 API key,也不需要发起网络请求。
  • .env.exampleREADME.mdREADME_CN.md 和 extension README 记录该配置。

Primary area

Other

Alternatives considered

1. 使用另一个 OpenAI-compatible 视觉模型

这可以绕过问题,但无法使用已经通过 OpenCode Go 或部分其他订阅获得的 Qwen3.7 Plus,也限制了 toolkit 可使用的视觉提供商。

2. 在本地额外运行 OpenAI-to-Anthropic 转换代理

技术上可行,但会增加:

  • 一个额外的后台服务
  • 端口和进程管理
  • 额外的错误和日志层
  • 安装与配置复杂度

而且无法自然覆盖 Pi extension、OpenCode plugin 和独立 CLI 的所有使用方式。

3. 让 Pi extension 直接通过 Pi 的 provider abstraction 调用 Qwen

这可以解决 Pi 单一宿主中的问题,但会使 extension 强依赖 Pi 的内部模型执行 API,也不能解决 glance、proxy 和 OpenCode plugin 使用 Anthropic-compatible 视觉 endpoint 的需求。

4. 根据模型名自动推断 API 格式

例如看到 qwen3.7-plus 就自动选择 Anthropic Messages。该方案比较脆弱,因为同一个模型可能由不同 provider 通过不同协议提供。

显式的 VISION_API_FORMAT 配置更可预测,也更容易测试。

Scope checks

  • I searched existing issues and pull requests.
  • This proposal does not require logging request bodies, images, prompts, conversations, or API keys.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions