Skip to content

fix(claude): convert web_search tools for Claude relay - #4771

Open
liaohch3 wants to merge 1 commit into
QuantumNous:mainfrom
liaohch3:fix/claude-web-search-tool
Open

fix(claude): convert web_search tools for Claude relay#4771
liaohch3 wants to merge 1 commit into
QuantumNous:mainfrom
liaohch3:fix/claude-web-search-tool

Conversation

@liaohch3

@liaohch3 liaohch3 commented May 11, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

修复 OpenAI 兼容 Chat Completions 请求里直接传入 Claude web search tool 时没有被转换的问题。

当前 RequestOpenAI2ClaudeMessage 只处理 function tool 和 web_search_options。如果客户端传 tools: [{"type":"web_search"}]web_search_previewweb_search_20250305,这些 tool 会被当作非 function tool 跳过,最终没有传给 Claude。

这个 PR 只做最小转换:

  • 识别 OpenAI 兼容请求里的 Claude web search tool type。
  • 转成 Anthropic web_search_20250305 server tool。
  • 已显式提供 web search tool 时,不再额外从 web_search_options 注入第二个搜索工具,避免重复。
  • 不扩展 ToolCallRequest DTO 结构。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过相关测试;全量测试的既有失败已在下方列出。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

本地 Go 版本:go version go1.25.1 linux/amd64

通过:

  • git diff --check
  • go test ./relay/channel/claude -run 'TestRequestOpenAI2ClaudeMessage_(ConvertsClaudeWebSearchTool|ConvertsWebSearchOptions|DoesNotDuplicateWebSearchTool)' -count=1 -v
  • go test ./relay/channel/vertex -run TestCopyRequestPreservesClaudeWebSearchTools -count=1 -v
  • go test ./dto ./service -count=1

真实端到端验证:

  • 启动本地 New API 服务,使用临时 SQLite DB 和真实 Vertex AI service account JSON key 配置临时 VertexAI channel;测试完成后已停止服务并删除临时数据。
  • claude-sonnet-4-6 普通 /v1/chat/completions:HTTP 200,返回 NEW_API_VERTEX_CLAUDE_OK
  • claude-sonnet-4-6 + tools: [{"type":"web_search"}]:HTTP 200;本地 New API 计费日志记录 web_search=trueweb_search_call_count=1
  • claude-sonnet-4-6 + web_search_options:HTTP 200;本地 New API 计费日志记录 web_search_call_count=1
  • claude-sonnet-4-6 同时带 tools: [{"type":"web_search_preview"}]web_search_options:HTTP 200;本地 New API 计费日志记录 web_search_call_count=1,确认不会重复注入搜索工具。
  • claude-opus-4-7 普通请求:HTTP 200,返回 NEW_API_VERTEX_OPUS_OK
  • claude-opus-4-7 + tools: [{"type":"web_search"}] + tool_choice: "required":HTTP 200;本地 New API 计费日志记录 web_search=trueweb_search_call_count=1
  • 补充可见结果验证:claude-sonnet-4-6 + tool_choice: "required" 返回正文包含 searched=yes; source=GitHub - QuantumNous/new-api...; url=https://github.com/QuantumNous/new-api,同时日志记录 web_search_call_count=1

补充说明:

  • claude-opus-4-7 如果传 temperature,Vertex Claude 返回 temperature is deprecated for this model;去掉 temperature 后正常。这是模型参数约束,不是本 PR 引入的问题。
  • go test ./... 已运行,当前失败项为既有测试状态:relay/channel/claude 下 3 条文件内容转换测试,以及 relay/helperTestStreamScannerHandler_StreamStatus_PreInitialized。本 PR 新增和相关测试均已单独通过。

Summary by CodeRabbit

  • Bug Fixes

    • Improved Claude web search tool handling to prevent duplicate tools when both explicit tool definitions and web search options are provided.
    • Enhanced web search configuration merging to ensure proper priority when multiple sources define web search settings.
  • Tests

    • Added comprehensive test coverage for web search tool conversion and deduplication scenarios.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR implements web-search tool detection and conversion in RequestOpenAI2ClaudeMessage, transforming OpenAI web-search tool calls into Claude format while preventing duplicate injection when both tool calls and WebSearchOptions are present. Comprehensive tests validate conversion behavior and tool preservation through request transformation.

Changes

Claude Web-Search Tool Conversion

Layer / File(s) Summary
Detection Helper
relay/channel/claude/relay-claude.go
Adds isClaudeWebSearchTool to identify Claude web-search tools by matching specific Type values and web_search_preview prefix.
Tool Conversion Logic
relay/channel/claude/relay-claude.go
RequestOpenAI2ClaudeMessage tracks hasWebSearchTool flag, detects incoming OpenAI web-search tools, converts them to dto.ClaudeWebSearchTool, skips generic tool-schema processing, and guards WebSearchOptions injection with !hasWebSearchTool to prevent duplicates.
Conversion Tests
relay/channel/claude/relay_claude_test.go
Tests validate OpenAI web_search_20250305 tool conversion to Claude format, WebSearchOptions conversion with MaxUses derived from SearchContextSize, UserLocation JSON parsing, and duplicate-prevention behavior when both tool calls and options are provided.
Request Preservation Test
relay/channel/vertex/dto_test.go
Test verifies copyRequest preserves ClaudeWebSearchTool properties (Type, Name, MaxUses) when transforming requests to Vertex API format.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • QuantumNous/new-api#1384: Adds Claude web-search tool support to RequestOpenAI2ClaudeMessage with overlapping detection and conversion logic.

Suggested reviewers

  • Calcium-Ion

Poem

🐰 A rabbit hops through web-search tools,
Converting OpenAI's forms with clever rules,
No doubles hop—detection keeps things clean,
Each web_search tool now Claude-lean! 🌐

🚥 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
Title check ✅ Passed The title clearly and specifically describes the main change: converting web_search tools for Claude relay, which matches the core functionality added in this PR.
Linked Issues check ✅ Passed The PR successfully addresses issue #3320 by implementing conversion of OpenAI web_search tools to Claude web_search_20250305 tools, handling web_search_options, and preventing duplication.
Out of Scope Changes check ✅ Passed All changes directly support web_search tool conversion for Claude relay; no unrelated modifications are present in the implementation, tests, or utility functions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dto/openai_request.go`:
- Around line 236-241: Change the optional string scalars Name and
SearchContextSize in the OpenAI request DTO to pointer types (*string) so absent
vs empty values are preserved; update any code that reads or writes these fields
(notably conversion/usage in relay-claude.go such as
webSearchMaxUsesFromContextSize and any places accessing Name) to check for nil
and dereference safely, and adjust marshaling/unmarshaling logic accordingly to
maintain omitempty behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc868899-9f97-491a-a86d-6f14d7beb427

📥 Commits

Reviewing files that changed from the base of the PR and between ba47439 and 8c5eafb.

📒 Files selected for processing (5)
  • dto/claude.go
  • dto/openai_request.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/claude/relay_claude_test.go
  • relay/channel/vertex/dto_test.go

Comment thread dto/openai_request.go Outdated
Comment on lines +236 to +241
Name string `json:"name,omitempty"`
MaxUses *int `json:"max_uses,omitempty"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
BlockedDomains []string `json:"blocked_domains,omitempty"`
SearchContextSize string `json:"search_context_size,omitempty"`
UserLocation json.RawMessage `json:"user_location,omitempty"`

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consider using pointer types for optional string fields.

The coding guideline states: "Optional scalar fields in request structs parsed from client JSON and re-marshaled to upstream providers MUST use pointer types with omitempty tags to preserve explicit zero values and distinguish between absent and zero-valued fields."

Name and SearchContextSize are string scalars with omitempty, but are not pointer types. If these fields are truly optional (which appears to be the case from the test coverage), they should be *string to comply with the guideline and allow distinguishing between absent fields and empty strings.

As per coding guidelines, optional scalar fields in relay DTO paths must use pointer types to preserve absent vs zero-value distinction.

♻️ Proposed fix
 	// Claude server-side web search tool 参数。OpenAI 兼容请求里可能直接传
 	// web_search / web_search_preview / web_search_20250305,转换 Claude 请求时需要保留。
-	Name              string          `json:"name,omitempty"`
+	Name              *string         `json:"name,omitempty"`
 	MaxUses           *int            `json:"max_uses,omitempty"`
 	AllowedDomains    []string        `json:"allowed_domains,omitempty"`
 	BlockedDomains    []string        `json:"blocked_domains,omitempty"`
-	SearchContextSize string          `json:"search_context_size,omitempty"`
+	SearchContextSize *string         `json:"search_context_size,omitempty"`
 	UserLocation      json.RawMessage `json:"user_location,omitempty"`

Note: This change would require updating the conversion logic in relay-claude.go to handle the pointer types, particularly in webSearchMaxUsesFromContextSize and any Name field access.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dto/openai_request.go` around lines 236 - 241, Change the optional string
scalars Name and SearchContextSize in the OpenAI request DTO to pointer types
(*string) so absent vs empty values are preserved; update any code that reads or
writes these fields (notably conversion/usage in relay-claude.go such as
webSearchMaxUsesFromContextSize and any places accessing Name) to check for nil
and dereference safely, and adjust marshaling/unmarshaling logic accordingly to
maintain omitempty behavior.

@liaohch3
liaohch3 force-pushed the fix/claude-web-search-tool branch from 8c5eafb to 86f2708 Compare May 11, 2026 10:14
Co-authored-by: Talon <talon@users.noreply.github.com>
@liaohch3
liaohch3 force-pushed the fix/claude-web-search-tool branch from 86f2708 to 5302a34 Compare May 11, 2026 10:57
@liaohch3

Copy link
Copy Markdown
Author

补充说明一下本 PR 的验证边界,方便 review:

  • 这个 PR 保持最小范围:只把 OpenAI-compatible tools: [{"type":"web_search"}] / web_search_preview / web_search_20250305 转成 Claude web_search_20250305 server tool。
  • 已用本地 New API + 临时 SQLite channel + 真实 Vertex Claude service account 做过端到端验证:Vertex Claude 返回 200,New API 计费日志记录 web_search=trueweb_search_call_count=1
  • 用户可见正文里的来源输出也做过一次验证:模型可以在答案文本中返回 source title 和 URL。
  • 本 PR 不处理结构化 citations / annotations 透传;如果要把 Claude 搜索结果转成 OpenAI-compatible message.annotations 或类似结构,建议后续单独 PR 处理,避免扩大本次改动范围。
  • 另有一个模型参数边界:claude-opus-4-7 在 Vertex 上会拒绝 temperature 参数;去掉 temperature 后普通请求和强制 web_search 请求均可成功。这和本 PR 的 tool 转换无关。

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.

无法按照anthropic官方文档使用web_search tool

2 participants