版本号
v1.3.1
请求接口
Anthropic
模型名称
gemini-3.1-pro-preview
客户端/工具
Claude
问题描述
Issue Body
环境信息
- 项目: iBUHub/AIStudioToAPI
- 版本: v1.3.1
- 接口:
POST /v1/messages(Anthropic Claude 兼容格式)
- 目标模型:
gemini-3.1-pro-preview
- 客户端: Claude Code CLI
- 复现日期: 2026-06-05
问题描述
通过本项目的 Claude 兼容接口发起带 多轮 tool 调用 的请求时,Google API 返回:
400 INVALID_ARGUMENT
{"error":{"code":400,"message":"Request contains an invalid argument.","status":"INVALID_ARGUMENT"}}
服务端日志示例:
[INFO] [Adapter] Starting translation of Claude request format to Google format...
[INFO] [Adapter] Converted 27 Claude tool(s) to Gemini format
[DEBUG] [Registry] Found WebSocket connection for authIndex=28
[INFO] [Context#28] Received request: POST /v1beta/models/gemini-3.1-pro-preview:streamGenerateContent
[INFO] [Context#28] Request processing failed: Google API returned error: 400 INVALID_ARGUMENT
[ERROR] ❌ [Request] Claude real stream failed. Status code: 400
请求在 Adapter 转换完成、转发到 Gemini 之前 结构已不合法,属于 FormatConverter.translateClaudeToGoogle() 的转换逻辑问题。
复现步骤
- 部署 AIStudioToAPI,配置可用 auth 与
gemini-3.1-pro-preview 模型。
- 使用 Claude Code CLI(或任何会发送 Anthropic Messages API 格式的客户端)连接本项目的
/v1/messages。
- 发起包含以下特征的对话:
messages 数组中存在 role: "system" 的消息(Claude Code 会把 skills 列表放在这里);
- 多轮
tool_use / tool_result(如 Bash 工具调用);
tool_result 之后紧跟普通 user 文本消息(如多次「继续」)。
- 观察服务端日志,请求在转发至
streamGenerateContent 后返回 400 INVALID_ARGUMENT。
期望行为
Claude 格式请求应被正确转换为符合 Gemini API 要求的 contents 结构(user / model 角色交替),并成功获得模型响应。
实际行为
转换后的 Gemini 请求包含 连续的 user 角色,触发 Google API 400 INVALID_ARGUMENT。
根因分析
问题位于 src/core/FormatConverter.js 的 translateClaudeToGoogle(),主要有两点:
1. messages 中的 role: "system" 未被过滤(P0)
OpenAI 适配路径会过滤 system 消息并合并到 systemInstruction:
// translateOpenAIToGoogle — 正确处理
const conversationMessages = openaiBody.messages.filter(msg => msg.role !== "system");
但 Claude 适配路径 只处理顶层 claudeBody.system,不处理 messages 里的 role: "system"。这些消息在写入 googleContents 时被映射为 user:
// translateClaudeToGoogle — 第 2298-2302 行
role: message.role === "assistant" ? "model" : "user", // system 也变成 user
Claude Code 会在 messages 中插入 skills 的 system 消息,导致对话开头出现:
user(用户消息)→ user(system/skills)→ model(tool_use)→ ...
2. tool_result 与普通 user 文本未合并(P0)
flushToolParts() 将工具结果写成一条 user 消息;若下一条仍是 user 文本(如「继续」),会再推入一条 user,形成:
user(functionResponse)→ user(继续/文本)
Gemini API 要求 contents 中 user 与 model 严格交替,连续 user 会导致 INVALID_ARGUMENT。
3. Claude tool_result 数组内容处理不完整(P1,次要)
OpenAI 路径对数组型 tool 响应有完整规范化(保证 functionResponse.response 为 object):
// translateOpenAIToGoogle — 第 574-621 行
if (Array.isArray(responseContent)) { ... responseContent = { result: JSON.stringify(...) }; }
Claude 路径对 Array.isArray(toolResult.content) 仅提取 type === "text" 的块。若内容为原始 JSON 数组(如 docker inspect 返回 [{ "Id": "...", ... }]),会被转成 { "result": "" },工具结果内容丢失(不一定直接 400,但行为异常)。
本地验证
在 v1.3.1 代码上构造最小复现 payload,转换后 contents 角色序列为:
user → user → model → user → user
出现 两处连续 user,与上述分析一致。
建议修复方向
- 对齐 OpenAI 路径:在
translateClaudeToGoogle() 中过滤 messages 里 role === "system" 的消息,合并进 systemInstruction(或追加到已有 systemInstruction)。
- 合并相邻同角色
contents:转换结束后,将连续 user(或连续 model)合并为单条消息;至少处理「tool_result flush 后紧跟 user 文本」的场景。
- 复用 OpenAI 的数组 tool 响应规范化逻辑到 Claude 的
tool_result 分支。
- 补充单元测试:覆盖 system-in-messages、tool_result + 后续文本、非 text 数组 tool_result 三类场景。
相关代码位置
| 文件 |
函数/行号 |
说明 |
src/core/FormatConverter.js |
translateClaudeToGoogle() ~2066 |
Claude 转换主逻辑 |
src/core/FormatConverter.js |
~2120-2129 |
仅处理 claudeBody.system |
src/core/FormatConverter.js |
~2298-2302 |
system 被映射为 user |
src/core/FormatConverter.js |
~2134-2142, 2216-2223 |
tool_result flush 逻辑 |
src/core/FormatConverter.js |
translateOpenAIToGoogle() ~534-544 |
可参考的 system 过滤实现 |
src/core/FormatConverter.js |
translateOpenAIToGoogle() ~574-621 |
可参考的数组 tool 响应处理 |
补充说明
scripts/client/build.js 中有针对 thinkingLevel / thoughtSignature 的 INVALID_ARGUMENT 兜底注释,但无法修复 contents 角色交替 这一结构性问题。
test/ 目录目前缺少 translateClaudeToGoogle 相关测试,建议一并补充以防回归。
日志片段(节选)
[INFO] [Adapter] Converted 27 Claude tool(s) to Gemini format
[INFO] [Context#28] Received request: POST /v1beta/models/gemini-3.1-pro-preview:streamGenerateContent
[INFO] [Context#28] Request processing failed: Google API returned error: 400 INVALID_ARGUMENT
[ERROR] ❌ [Request] Claude real stream failed. Status code: 400, message: Proxy browser error: Google API returned error: 400 INVALID_ARGUMENT
感谢维护!如需更多日志或完整 Final Gemini Request DEBUG 输出,我可以继续提供。
日志信息 (关键)
1. 错误摘要
[INFO] [Entrypoint] Received a request: POST /v1/messages
[INFO] [Auth] API Key verification passed (from: x.x.x.x)
[INFO] [Request] Claude generation request - account rotation count: 1/10 (Current account: 28), request ID: req_1780673184457_v27089l7j
[INFO] [Adapter] Starting translation of Claude request format to Google format...
[INFO] [Adapter] Converted 27 Claude tool(s) to Gemini format
[INFO] [Adapter] Claude to Google translation complete.
[INFO] [Context#28] Received request: POST /v1beta/models/gemini-3.1-pro-preview:streamGenerateContent
[INFO] [Context#28] Request processing failed: Google API returned error: 400 INVALID_ARGUMENT
[ERROR] [Request] Claude real stream failed. Status code: 400, message: Proxy browser error: Google API returned error: 400 INVALID_ARGUMENT
2. 入站 Claude 请求特征(节选)
{
"model": "gemini-3.1-pro-preview",
"max_tokens": 32000,
"stream": true,
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "<system-reminder>...[truncated]...</system-reminder>" },
{ "type": "text", "text": "用户任务内容(已省略)" }
]
},
{
"role": "system",
"content": "The following skills are available for use with the Skill tool: ... [skills 列表,已省略]"
},
{ "role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_xxx", "name": "Bash", "input": { "command": "..." } }] },
{ "role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_xxx", "content": "...", "is_error": false }] },
"... 多轮 tool_use / tool_result ...",
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_yyy", "content": "[{...docker inspect JSON 数组...}]" },
{ "type": "text", "text": "继续\n" },
"... 多次「继续」..."
]
},
{
"role": "system",
"content": "The task tools haven't been used recently. ... [harness 提醒,已省略]"
}
],
"system": [
{ "type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude." },
{ "type": "text", "text": "... [system prompt,已省略] ..." }
],
"tools": "[27 个 Claude Code 工具定义,含 Bash/Agent/Edit 等,已省略]"
}
关键点:
messages 数组内存在 role: "system"(skills 列表、harness 提醒),不在顶层 system 字段。
- 对话含 多轮
tool_use / tool_result。
- 最后一个
user 消息同时包含 tool_result 与多条 text(「继续」)。
3. 转换后的 Gemini 请求(问题证据)
以下为 LOG_LEVEL=DEBUG 时 [Adapter] Debug: Final Gemini Request 的结构摘要(长文本已截断):
{
"contents": [
{
"role": "user",
"parts": [
{ "text": "<system-reminder>...[truncated]...</system-reminder>" },
{ "text": "用户任务内容" }
]
},
{
"role": "user",
"parts": [
{ "text": "The following skills are available for use with the Skill tool: ..." }
]
},
{
"role": "model",
"parts": [
{
"functionCall": { "name": "Bash", "args": { "command": "..." } },
"thoughtSignature": "context_engineering_is_the_way_to_go"
}
]
},
{
"role": "user",
"parts": [
{ "functionResponse": { "name": "Bash", "response": { "result": "..." } } }
]
},
"... 多组 model(user functionCall) + user(functionResponse) 交替 ...",
{
"role": "user",
"parts": [
{ "functionResponse": { "name": "Bash", "response": { "result": "" } } },
{ "text": "继续\n" },
{ "text": "继续\n" },
"... 更多「继续」..."
]
},
{
"role": "user",
"parts": [
{ "text": "The task tools haven't been used recently. ..." }
]
}
],
"systemInstruction": {
"role": "user",
"parts": [{ "text": "You are Claude Code... [truncated]" }]
},
"generationConfig": {
"maxOutputTokens": 32000
},
"tools": [
{
"functionDeclarations": [
{ "name": "Agent", "parameters": { "type": "OBJECT", "...": "..." } },
{ "name": "Bash", "parameters": { "type": "OBJECT", "...": "..." } },
"... 共 27 个 ..."
]
}
],
"safetySettings": "[...]"
}
转换后 contents 角色序列(按条数):
user → user → model → user → model → user → ... → user → user
^^^^ ^^^^^^^^^^^^
skills 被写成 user 连续 user(含 harness 提醒)
即存在 至少两处连续 user 角色,不符合 Gemini contents 要求的 user / model 交替。
4. 根因对照(供维护者参考)
| Claude 入站 |
当前转换结果 |
OpenAI 路径行为 |
messages[].role === "system" |
写入 contents 且 role: "user" |
过滤并合并到 systemInstruction |
tool_result 后紧跟 user 文本 |
分成两条 user content |
OpenAI tool 消息会合并到同一 user |
tool_result.content 为 JSON 数组 |
非 text 块时可能变成 { "result": "" } |
OpenAI 路径有完整数组规范化 |
5. 账号切换副作用(非根因,但会放大失败体验)
[WARN] [Auth] Failure threshold reached (1/1)! Preparing to switch account...
[INFO] [Auth] Successfully switched to account #29, counters reset.
单次 400 INVALID_ARGUMENT 即触发账号切换(FAILURE_THRESHOLD=1),导致上下文池 rebalance、WebSocket 重连等额外开销。
6. 复现条件小结
- AIStudioToAPI v1.3.1
- Claude Code CLI →
POST /v1/messages
- 模型:
gemini-3.1-pro-preview
- 对话含:
messages 内 role:system + 多轮 tools + 末尾 user 文本
- 观察:
Final Gemini Request 出现连续 user,随后 Google API 400 INVALID_ARGUMENT
版本号
v1.3.1
请求接口
Anthropic
模型名称
gemini-3.1-pro-preview
客户端/工具
Claude
问题描述
Issue Body
环境信息
POST /v1/messages(Anthropic Claude 兼容格式)gemini-3.1-pro-preview问题描述
通过本项目的 Claude 兼容接口发起带 多轮 tool 调用 的请求时,Google API 返回:
服务端日志示例:
请求在 Adapter 转换完成、转发到 Gemini 之前 结构已不合法,属于
FormatConverter.translateClaudeToGoogle()的转换逻辑问题。复现步骤
gemini-3.1-pro-preview模型。/v1/messages。messages数组中存在role: "system"的消息(Claude Code 会把 skills 列表放在这里);tool_use/tool_result(如 Bash 工具调用);tool_result之后紧跟普通user文本消息(如多次「继续」)。streamGenerateContent后返回400 INVALID_ARGUMENT。期望行为
Claude 格式请求应被正确转换为符合 Gemini API 要求的
contents结构(user/model角色交替),并成功获得模型响应。实际行为
转换后的 Gemini 请求包含 连续的
user角色,触发 Google API400 INVALID_ARGUMENT。根因分析
问题位于
src/core/FormatConverter.js的translateClaudeToGoogle(),主要有两点:1.
messages中的role: "system"未被过滤(P0)OpenAI 适配路径会过滤
system消息并合并到systemInstruction:但 Claude 适配路径 只处理顶层
claudeBody.system,不处理messages里的role: "system"。这些消息在写入googleContents时被映射为user:Claude Code 会在
messages中插入 skills 的 system 消息,导致对话开头出现:2.
tool_result与普通 user 文本未合并(P0)flushToolParts()将工具结果写成一条user消息;若下一条仍是user文本(如「继续」),会再推入一条user,形成:Gemini API 要求
contents中user与model严格交替,连续user会导致INVALID_ARGUMENT。3. Claude
tool_result数组内容处理不完整(P1,次要)OpenAI 路径对数组型 tool 响应有完整规范化(保证
functionResponse.response为 object):Claude 路径对
Array.isArray(toolResult.content)仅提取type === "text"的块。若内容为原始 JSON 数组(如docker inspect返回[{ "Id": "...", ... }]),会被转成{ "result": "" },工具结果内容丢失(不一定直接 400,但行为异常)。本地验证
在 v1.3.1 代码上构造最小复现 payload,转换后
contents角色序列为:出现 两处连续
user,与上述分析一致。建议修复方向
translateClaudeToGoogle()中过滤messages里role === "system"的消息,合并进systemInstruction(或追加到已有 systemInstruction)。contents:转换结束后,将连续user(或连续model)合并为单条消息;至少处理「tool_result flush 后紧跟 user 文本」的场景。tool_result分支。相关代码位置
src/core/FormatConverter.jstranslateClaudeToGoogle()~2066src/core/FormatConverter.jsclaudeBody.systemsrc/core/FormatConverter.jssrc/core/FormatConverter.jssrc/core/FormatConverter.jstranslateOpenAIToGoogle()~534-544src/core/FormatConverter.jstranslateOpenAIToGoogle()~574-621补充说明
scripts/client/build.js中有针对thinkingLevel/thoughtSignature的 INVALID_ARGUMENT 兜底注释,但无法修复 contents 角色交替 这一结构性问题。test/目录目前缺少translateClaudeToGoogle相关测试,建议一并补充以防回归。日志片段(节选)
感谢维护!如需更多日志或完整
Final Gemini RequestDEBUG 输出,我可以继续提供。日志信息 (关键)
1. 错误摘要
2. 入站 Claude 请求特征(节选)
{ "model": "gemini-3.1-pro-preview", "max_tokens": 32000, "stream": true, "messages": [ { "role": "user", "content": [ { "type": "text", "text": "<system-reminder>...[truncated]...</system-reminder>" }, { "type": "text", "text": "用户任务内容(已省略)" } ] }, { "role": "system", "content": "The following skills are available for use with the Skill tool: ... [skills 列表,已省略]" }, { "role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_xxx", "name": "Bash", "input": { "command": "..." } }] }, { "role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_xxx", "content": "...", "is_error": false }] }, "... 多轮 tool_use / tool_result ...", { "role": "user", "content": [ { "type": "tool_result", "tool_use_id": "toolu_yyy", "content": "[{...docker inspect JSON 数组...}]" }, { "type": "text", "text": "继续\n" }, "... 多次「继续」..." ] }, { "role": "system", "content": "The task tools haven't been used recently. ... [harness 提醒,已省略]" } ], "system": [ { "type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude." }, { "type": "text", "text": "... [system prompt,已省略] ..." } ], "tools": "[27 个 Claude Code 工具定义,含 Bash/Agent/Edit 等,已省略]" }关键点:
messages数组内存在role: "system"(skills 列表、harness 提醒),不在顶层system字段。tool_use/tool_result。user消息同时包含tool_result与多条text(「继续」)。3. 转换后的 Gemini 请求(问题证据)
以下为
LOG_LEVEL=DEBUG时[Adapter] Debug: Final Gemini Request的结构摘要(长文本已截断):{ "contents": [ { "role": "user", "parts": [ { "text": "<system-reminder>...[truncated]...</system-reminder>" }, { "text": "用户任务内容" } ] }, { "role": "user", "parts": [ { "text": "The following skills are available for use with the Skill tool: ..." } ] }, { "role": "model", "parts": [ { "functionCall": { "name": "Bash", "args": { "command": "..." } }, "thoughtSignature": "context_engineering_is_the_way_to_go" } ] }, { "role": "user", "parts": [ { "functionResponse": { "name": "Bash", "response": { "result": "..." } } } ] }, "... 多组 model(user functionCall) + user(functionResponse) 交替 ...", { "role": "user", "parts": [ { "functionResponse": { "name": "Bash", "response": { "result": "" } } }, { "text": "继续\n" }, { "text": "继续\n" }, "... 更多「继续」..." ] }, { "role": "user", "parts": [ { "text": "The task tools haven't been used recently. ..." } ] } ], "systemInstruction": { "role": "user", "parts": [{ "text": "You are Claude Code... [truncated]" }] }, "generationConfig": { "maxOutputTokens": 32000 }, "tools": [ { "functionDeclarations": [ { "name": "Agent", "parameters": { "type": "OBJECT", "...": "..." } }, { "name": "Bash", "parameters": { "type": "OBJECT", "...": "..." } }, "... 共 27 个 ..." ] } ], "safetySettings": "[...]" }转换后
contents角色序列(按条数):即存在 至少两处连续
user角色,不符合 Geminicontents要求的user/model交替。4. 根因对照(供维护者参考)
messages[].role === "system"contents且role: "user"systemInstructiontool_result后紧跟user文本usercontenttool消息会合并到同一usertool_result.content为 JSON 数组{ "result": "" }5. 账号切换副作用(非根因,但会放大失败体验)
单次
400 INVALID_ARGUMENT即触发账号切换(FAILURE_THRESHOLD=1),导致上下文池 rebalance、WebSocket 重连等额外开销。6. 复现条件小结
POST /v1/messagesgemini-3.1-pro-previewmessages内role:system+ 多轮 tools + 末尾 user 文本Final Gemini Request出现连续user,随后 Google API400 INVALID_ARGUMENT