Skip to content

fix(openai): tolerate object response arguments - #4445

Closed
gaoren002 wants to merge 1 commit into
QuantumNous:mainfrom
gaoren002:fix/response-arguments-object
Closed

fix(openai): tolerate object response arguments#4445
gaoren002 wants to merge 1 commit into
QuantumNous:mainfrom
gaoren002:fix/response-arguments-object

Conversation

@gaoren002

@gaoren002 gaoren002 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

变更描述 / Description

本 PR 修复 OpenAI Responses 流式/转换链路中 arguments 字段只能按字符串解析的问题。

目前代码里 FunctionResponse.ArgumentsResponsesOutput.Arguments 都按 string 建模。但在 Responses API 的部分工具调用事件里,上游可能返回对象格式的 arguments,例如:

{
  "type": "function_call",
  "arguments": {
    "query": "hello",
    "limit": 3
  }
}

这种 payload 在 Go 里反序列化到 string 会失败,进而导致响应转换中断。实际表现可能是流式响应提前断开、上游响应无法正常转换,或者下游客户端看到解码/连接中断类错误。

本 PR 新增 dto.ResponseArguments 类型,使 arguments 同时兼容两种输入:

  • 标准字符串格式:"{\"query\":\"hello\"}"
  • 对象格式:{"query":"hello","limit":3}

内部仍统一保存为 JSON 字符串,并且 MarshalJSON 继续输出字符串,避免影响现有 OpenAI Chat Completions / Claude / Gemini / Ollama 转换链路对工具参数字符串的预期。

同时,本 PR 把相关转换代码改为显式使用 ResponseArguments(...).String(),避免类型变化后在 Claude、Gemini、Ollama、OpenAI Responses 转换路径中出现不一致。

为什么之前一直没发现?

这个问题比较隐蔽,主要原因是历史测试和常见请求都覆盖在 arguments 为字符串的路径上:

  • Chat Completions 传统工具调用里,tool_calls[].function.arguments 通常就是字符串。
  • 大多数已有 provider 转换逻辑也会主动把工具参数 marshal 成字符串后再传递。
  • 只有 Responses API 的部分事件或中间转换场景可能直接给出对象格式 arguments
  • 原有测试没有覆盖 ResponsesOutput.arguments 为 JSON object 的反序列化场景。

因此正常聊天、普通工具调用、以及大部分 provider 转换都不会触发该错误。只有当 Responses 流式事件中出现对象型 arguments 时,才会暴露出结构体字段类型过窄的问题,需要足够多的请求来发现此问题。

本 PR 增加了对象型 arguments 的回归测试,避免之后再把该字段收窄回纯 string

变更类型 / Type of change

  • Bug 修复 (Bug fix)
  • 新功能 (New feature)
  • 性能优化 / 重构 (Refactor)
  • 文档更新 (Documentation)

关联任务 / Related Issue

  • Closes # (无)

提交前检查项 / Checklist

  • 人工确认:我已整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交:已检查当前分支只包含本修复,不包含自定义邀请注册等无关改动。
  • Bug fix 说明:该问题是实际 payload 形态与 DTO 类型定义不兼容导致的解析失败。
  • 变更理解:本 PR 只放宽 arguments 入站解析,出站仍保持字符串格式,降低兼容风险。
  • 范围聚焦:本 PR 未包含与当前任务无关的代码改动。
  • 本地验证:已运行相关测试。
  • 安全合规:代码中无敏感凭据。

运行证明 / Proof of Work

本地验证命令:

go test ./dto ./relay/channel/openai ./service

结果:

ok   github.com/QuantumNous/new-api/dto
?    github.com/QuantumNous/new-api/relay/channel/openai [no test files]
ok   github.com/QuantumNous/new-api/service

Summary by CodeRabbit

  • Refactoring

    • Unified and improved handling of tool-call/function arguments across integrations (OpenAI, Claude, Gemini, Ollama) to ensure consistent behavior for streaming and non‑streaming responses.
  • Tests

    • Added tests validating robust JSON parsing/serialization of response arguments (objects, arrays, null, and escaped JSON strings).

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 606de7f6-34ca-452a-a28e-553342da7fb1

📥 Commits

Reviewing files that changed from the base of the PR and between e90a0b2 and e9f245e.

📒 Files selected for processing (8)
  • dto/openai_response.go
  • dto/openai_response_test.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/gemini/relay-gemini.go
  • relay/channel/ollama/stream.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/openai/helper.go
  • service/convert.go
✅ Files skipped from review due to trivial changes (4)
  • relay/channel/openai/helper.go
  • relay/channel/gemini/relay-gemini.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/openai/chat_via_responses.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • relay/channel/ollama/stream.go
  • dto/openai_response_test.go
  • dto/openai_response.go

Walkthrough

Introduces a new dto.ResponseArguments type with custom JSON (un)marshalling and replaces plain string usage for function/tool-call arguments across DTOs, relay channels (OpenAI, Claude, Gemini, Ollama), and service utilities to ensure consistent argument handling.

Changes

Cohort / File(s) Summary
DTO Type Definition
dto/openai_response.go
Adds ResponseArguments type (alias of string) with UnmarshalJSON, MarshalJSON, and String to handle null, string, and raw JSON inputs/outputs.
DTO Tests
dto/openai_response_test.go
Adds tests validating unmarshalling/stringification for objects, arrays, null, and escaped-string arguments.
Relay Channel Integrations
relay/channel/claude/relay-claude.go, relay/channel/gemini/relay-gemini.go, relay/channel/ollama/stream.go, relay/channel/openai/chat_via_responses.go
Wraps tool-call argument assignments in dto.ResponseArguments(...) and uses the new type at DTO boundaries for streamed and non-streamed flows.
Service / Helpers
relay/channel/openai/helper.go, service/convert.go
Use .String() when reading ResponseArguments for building response text and streaming partial JSON payloads instead of accessing raw string fields.

Sequence Diagram(s)

(Skipped — changes are DTO/type replacements and small control-flow adaptations; no new multi-actor sequential feature introduced.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • 重构ollama渠道 #1811: Overlaps with Ollama channel tool-call/DTO handling changes affecting relay/channel/ollama/stream.go.
  • Fix/aws non empty text #3080: Related edits to Claude streaming/tool-call construction and DTO boundaries in relay/channel/claude/relay-claude.go.

Suggested reviewers

  • creamlike1024

Poem

🐰 A chewy byte of JSON tucked neat,
Arguments wrapped so parsing's complete.
From Claude to Gemini, Ollama in tune,
I hop through the code and hum a small tune. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: handling OpenAI response arguments that come as objects rather than strings, which is the core issue and fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
dto/openai_response_test.go (1)

8-28: Consider extending coverage to null/empty and array forms.

The two existing cases cover the main regression, but since UnmarshalJSON also handles null/empty input (should become "") and valid non-object JSON (e.g. arrays), a couple of additional sub-tests would lock in that contract and prevent accidental regressions to these branches.

🧪 Suggested additional cases
func TestResponseArgumentsNullAndEmpty(t *testing.T) {
	var fn FunctionResponse
	if err := json.Unmarshal([]byte(`{"name":"x","arguments":null}`), &fn); err != nil {
		t.Fatalf("null arguments should unmarshal: %v", err)
	}
	if got := fn.Arguments.String(); got != "" {
		t.Fatalf("expected empty string for null, got %q", got)
	}
}

func TestResponsesOutputArgumentsAcceptsArray(t *testing.T) {
	var output ResponsesOutput
	if err := json.Unmarshal([]byte(`{"type":"function_call","arguments":[1,2,3]}`), &output); err != nil {
		t.Fatalf("Unmarshal ResponsesOutput failed: %v", err)
	}
	if got := output.Arguments.String(); got != `[1,2,3]` {
		t.Fatalf("unexpected arguments: %s", got)
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dto/openai_response_test.go` around lines 8 - 28, Add two unit tests to cover
the null/empty and array forms: create TestResponseArgumentsNullAndEmpty which
unmarshals {"name":"x","arguments":null} into FunctionResponse and asserts
fn.Arguments.String() == "" and that unmarshaling returns no error, and create
TestResponsesOutputArgumentsAcceptsArray which unmarshals
{"type":"function_call","arguments":[1,2,3]} into ResponsesOutput and asserts
output.Arguments.String() == `[1,2,3]`; reference the FunctionResponse and
ResponsesOutput types and their Arguments.String()/UnmarshalJSON behavior to
ensure these branches remain covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@dto/openai_response_test.go`:
- Around line 8-28: Add two unit tests to cover the null/empty and array forms:
create TestResponseArgumentsNullAndEmpty which unmarshals
{"name":"x","arguments":null} into FunctionResponse and asserts
fn.Arguments.String() == "" and that unmarshaling returns no error, and create
TestResponsesOutputArgumentsAcceptsArray which unmarshals
{"type":"function_call","arguments":[1,2,3]} into ResponsesOutput and asserts
output.Arguments.String() == `[1,2,3]`; reference the FunctionResponse and
ResponsesOutput types and their Arguments.String()/UnmarshalJSON behavior to
ensure these branches remain covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f57efd22-930f-4ee3-8299-aa47d5cedff2

📥 Commits

Reviewing files that changed from the base of the PR and between a7c38ec and e90a0b2.

📒 Files selected for processing (8)
  • dto/openai_response.go
  • dto/openai_response_test.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/gemini/relay-gemini.go
  • relay/channel/ollama/stream.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/openai/helper.go
  • service/convert.go

@gaoren002
gaoren002 force-pushed the fix/response-arguments-object branch from e90a0b2 to e9f245e Compare April 24, 2026 16:36
@gaoren002

Copy link
Copy Markdown
Contributor Author

我正在确认该PR是否必要,可能是不正常使用导致的

@gaoren002

Copy link
Copy Markdown
Contributor Author

此PR并无必要

@gaoren002 gaoren002 closed this Apr 25, 2026
@gaoren002
gaoren002 deleted the fix/response-arguments-object branch June 8, 2026 13:10
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.

1 participant