Skip to content

feat: Anthropic Messages -> Chat Completions#3336

Open
FjlI5 wants to merge 3 commits into
Wei-Shaw:mainfrom
FjlI5:feat/anthropic-chat-direct-bridge
Open

feat: Anthropic Messages -> Chat Completions#3336
FjlI5 wants to merge 3 commits into
Wei-Shaw:mainfrom
FjlI5:feat/anthropic-chat-direct-bridge

Conversation

@FjlI5

@FjlI5 FjlI5 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

本 PR 含两个提交。

1. fix(apicompat): 修复 chat→responses 桥接中工具调用参数翻倍

流式转换 Chat Completions → Responses 事件时,某 tool_call index 的首个带有 Function.Arguments 的 delta 被整体拷贝(copyCall := toolCall),随后 += 又把同一片 Function.Arguments 加了一遍。对于在首个 delta 内联完整参数的上游(如 DeepSeek/GLM/Kimi,区别于 OpenAI 首片为空),会产出 {...}{...} 这种翻倍参数,破坏 JSON 解析与非流式路径。修复:拷贝后清空种子参数,让 += 对每片只累加一次。

2. feat(openai): Anthropic Messages -> Chat Completions 直连桥接

OpenAI 平台分组开启 messages 调度后,客户端使用 /v1/messages 请求,若调度到只支持 /v1/chat/completions 的API-key 账号上游时,仍然走 /v1/responses 而不会回退成 /v1/chat/completions

此提交为「客户端走 /v1/messages、而 API-key 账号上游只支持 /v1/chat/completions」的场景提供一条直连转换路径。

  • apicompat/anthropic_chatcompletions.goAnthropicToChatCompletions(请求)+ 流式 chunk→Anthropic-SSE 状态机 + 非流式 stream→response 折叠;复用 sanitizeAnthropicToolUseInputResponsesAnthropicEventToSSE
  • service/openai_messages_chat_fallback.goforwardMessagesViaRawChatCompletions(上游恒流式 + include_usage;按客户端偏好选择转发或折叠),与 responses 的 raw-chat fallback 发送逻辑对齐。
  • ForwardAsAnthropic 顶部按 APIKey && !ShouldUseResponsesAPI 分流到该直连路径。
  • 配套单测:请求转换、流式、并行工具无孤儿、Read 缓冲、reasoning-only 兜底、非流式折叠。

效果:
image
image

测试

go build 通过;go test ./internal/pkg/apicompat/go test ./internal/service/ 通过;golangci-lint 通过。

…s bridge

The first delta for a tool-call index was copied wholesale (copyCall := toolCall),
seeding Function.Arguments, and then the same fragment was appended again by the
accumulating +=. Upstreams that inline a tool call's full arguments in the first
delta (DeepSeek/GLM/Kimi, unlike OpenAI's empty first delta) thus produced doubled
arguments like {...}{...}, breaking JSON parsing and the non-streaming path.
Reset the seeded arguments so += accumulates each fragment exactly once.
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@FjlI5 FjlI5 force-pushed the feat/anthropic-chat-direct-bridge branch from e8dcf1b to ebb090a Compare June 17, 2026 17:51
@FjlI5

FjlI5 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

recheck

Serve /v1/messages clients on API-key accounts whose upstream only speaks
/v1/chat/completions through a direct Anthropic<->Chat converter, bypassing the
Responses hub. This avoids the orphan/doubling artifacts that surface when routing
chat->responses->anthropic for parallel tool calls and reasoning.

- apicompat/anthropic_chatcompletions.go: AnthropicToChatCompletions (request) plus
  a streaming chunk->Anthropic-SSE state machine and a sync stream->response
  collapse, reusing sanitizeAnthropicToolUseInput and ResponsesAnthropicEventToSSE.
- service/openai_messages_chat_fallback.go: forwardMessagesViaRawChatCompletions
  (always-stream upstream with include_usage; client preference selects relay vs
  collapse), mirroring the responses raw-chat fallback send path.
- ForwardAsAnthropic: top branch routes chat-only API-key accounts here.
- tests: request conversion, streaming, parallel-tools no-orphan, Read buffering,
  reasoning-only fallback, sync collapse.
@FjlI5 FjlI5 force-pushed the feat/anthropic-chat-direct-bridge branch from ebb090a to 2718093 Compare June 17, 2026 23:31
@VoidIsVoid

Copy link
Copy Markdown

Looking forward to having this merged. I really need this!

@VoidIsVoid

Copy link
Copy Markdown

首先非常感谢这个 PR,非常实用!🎉

在接入 Claude 时遇到一个问题,想反馈一下。

问题描述

使用本功能提供的接口接入 Claude 时,在 auto 模式下,自动分类器会持续报错:

${model} is temporarily unavailable, so auto mode cannot determine the safety of Edit right now.
Wait briefly and then try this action again. If it keeps failing, continue with other tasks that don't
require this action and come back to it later

抓包分析

我通过抓包定位到了原因:

Claude 在 auto 模式下做安全分类时,会发起一个非流式(non-stream)的 messages 请求。然而本功能在处理非流式请求时,响应头的 Content-Type 仍然被设置成了 text/event-stream

这导致 Claude 客户端无法把非流式请求返回的 JSON 正常解析(它期望的是 application/json),从而分类失败,最终触发上面那段 "model is temporarily unavailable" 的报错。

期望

希望能够在判断请求为非流式(stream: false)时,把响应头的 Content-Type 正确设置为 application/json,以保证 JSON 能被正常解析。

期待修复!🙏

The Anthropic<->Chat bridge always streams the upstream, so its
text/event-stream Content-Type is forwarded by WriteFilteredHeaders and
gin's c.JSON cannot override an already-set header. Non-stream /v1/messages
clients (e.g. Claude auto-mode safety classification) then received a JSON
body under a text/event-stream Content-Type and failed to parse it. Reset
the header explicitly before c.JSON, matching the buffered Responses/Chat
paths, and add a regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@FjlI5

FjlI5 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@VoidIsVoid 感谢反馈,已修复:
image
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants