背景
opencode.ai 提供了多个国内大模型的统一接入端点,包括 DeepSeek V4、GLM-5、Kimi K2.6、MiMo、MiniMax、Qwen 等。平台在文档中为不同模型标注了不同的 SDK 兼容性:部分模型标注为 @ai-sdk/openai-compatible,部分标注为 @ai-sdk/anthropic。
我起初想直接用 curl 测试 DeepSeek V4 Pro 的流式输出,按照文档使用了 Anthropic 的 /v1/messages 端点,却得到了如下错误:
{"error":{"message":"Error from provider (DeepSeek): Empty input messages"}}
批量测试所有模型
为了搞清楚哪些模型能走哪个端点,我对平台提供的 14 个模型做了批量测试。测试脚本的核心逻辑是:对标注为 OpenAI 兼容的模型调用 /v1/chat/completions,对标注为 Anthropic 的模型调用 /v1/messages。
测试结果如下:
| 模型 | 标注端点 | 结果 |
|---|---|---|
| GLM-5.1 / GLM-5 | OpenAI | 正常 |
| Kimi K2.5 / K2.6 | OpenAI | 正常 |
| DeepSeek V4 Pro / Flash | Anthropic | 失败 |
| MiMo V2 / V2.5 全系 | OpenAI | 正常 |
| MiniMax M2.7 / M2.5 | Anthropic | 正常 |
| Qwen3.6 Plus / 3.5 Plus | OpenAI | 正常 |
结论很明确:DeepSeek V4 Pro 和 DeepSeek V4 Flash 实际需要使用 OpenAI 兼容端点 /v1/chat/completions 才能正常工作,但官方文档却将其标注为 Anthropic 端点。
设计协议转换器
既然平台本身存在端点标注与实际行为不一致的情况,写一个代理层来屏蔽这种差异是合理的做法。需求如下:
- 读取
config.toml配置(端口、API Key) - 同时暴露 OpenAI 格式的
/v1/chat/completions和 Anthropic 格式的/v1/messages - 暴露
/v1/models,根据请求头自动返回对应格式的模型列表 - 对 Anthropic 请求,自动判断模型应该直转 Anthropic 端点,还是转换为 OpenAI 请求后转发
- 将 OpenAI 的流式/非流式响应再转换回 Anthropic 格式
项目结构
opencode-go-2api/
├── config.toml
├── go.mod
├── main.go
└── internal/
├── config.go # TOML 配置读取
├── models.go # 14 个模型注册表
├── proxy.go # 上游 HTTP 代理
├── convert_request.go # Anthropic -> OpenAI 请求转换
├── convert_response.go # OpenAI -> Anthropic 响应转换(含 SSE)
├── handler_openai.go # OpenAI 端点 Handler
└── handler_anthropic.go # Anthropic 端点 Handler
核心转换逻辑
请求转换(Anthropic -> OpenAI)
当客户端以 Anthropic 格式发送请求时,代理执行以下转换:
messages中的结构化 content 数组被扁平化为字符串system字段被提取为一条role: system的消息stop_sequences映射为 OpenAI 的stoptools被重新包装为type: function格式thinking字段原样透传(DeepSeek 上游支持该字段)
响应转换(OpenAI -> Anthropic)
非流式响应的转换要点:
message.content映射为 Anthropic 的textcontent blockmessage.reasoning_content映射为thinkingcontent blockmessage.tool_calls被解析为tool_usecontent blocksfinish_reason: tool_calls被映射为stop_reason: tool_use
流式响应的转换更为复杂。OpenAI 的 SSE 格式是每个 chunk 携带 delta.content 或 delta.reasoning_content 的增量。代理需要将其重组为 Anthropic 的事件序列:
event: content_block_start (type: thinking / text / tool_use)
event: content_block_delta (type: thinking_delta / text_delta / input_json_delta)
event: content_block_stop
event: message_delta
event: message_stop
每个 content block 拥有独立的 index,代理需要动态分配和维护这些 index 的状态。
Tool Call 支持
请求侧:Anthropic 的 tools[].input_schema 映射为 OpenAI 的 tools[].function.parameters。
响应侧:OpenAI 的 tool_calls[].function.arguments(JSON 字符串)被反序列化为 Anthropic tool_use.input 对象。流式场景下,input_json_delta 的 partial_json 字段承载增量参数片段。
Thinking / Reasoning 支持
DeepSeek V4 的流式输出中会先返回一段 reasoning_content(思维链),再返回正式回复 content。代理已将 reasoning_content 完整映射为 Anthropic 的 thinking content block。
此外,opencode.ai 的 DeepSeek 端点也接受 thinking 和 reasoning_effort 字段:
thinking: {"type": "enabled"}会启用更长的推理过程reasoning_effort: "high"/"low"对推理长度有一定影响
测试表明,默认情况下 DeepSeek 已经会返回 reasoning 内容,显式设置 thinking 后 reasoning tokens 会明显增加。
启动方式
cd opencode-go-2api
# 编辑 config.toml 填入 api_key
go build -o opencode-go-2api .
./opencode-go-2api --config config.toml
代理监听配置的端口,对 OpenAI SDK 和 Anthropic SDK 均透明可用。
总结
这个项目的价值在于:用一个本地代理层屏蔽了上游平台在协议兼容性上的不一致性。用户不需要关心某个模型到底该走 OpenAI 端点还是 Anthropic 端点,代理会根据模型注册表中的 AnthropicOK 标记自动选择正确的转发路径。对于需要同时使用 DeepSeek 和 MiniMax 的 Anthropic SDK 用户来说,这个代理是必需的中间层。
评论区