-
Notifications
You must be signed in to change notification settings - Fork 0
WebSearch @ Anthropic
本项目(gcli2api)将 Google Gemini CLI (GCLI) 和 Antigravity 内部 API 桥接为 Anthropic-compatible API。WebSearch 工具在两个体系中实现方式不同,需要桥接。
- GCLI 模式:通过模型名后缀
-search(如gemini-2.5-flash-search)在normalize_gemini_request()中自动注入{"googleSearch": {}}工具 - Antigravity 模式:无搜索支持
- Anthropic-compatible 端点(
/v1/messages、/antigravity/v1/messages):不支持 Anthropic 原生web_search_*工具类型——客户端传入此类工具时会被convert_tools()误当作 function 工具处理
{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5,
"allowed_domains": ["example.com"],
"blocked_domains": ["untrustedsource.com"],
"user_location": {
"type": "approximate",
"city": "San Francisco",
"region": "California",
"country": "US",
"timezone": "America/Los_Angeles"
}
}有两个版本:web_search_20250305(基础)和 web_search_20260209(动态过滤,需 code_execution 工具)。
关键字段:
-
type:版本化工具类型字符串,以web_search_开头 -
name:固定为"web_search" -
max_uses:最大搜索次数限制 -
allowed_domains/blocked_domains:互斥的域名过滤 -
user_location:搜索本地化
1. server_tool_use → 模型决定搜索,含搜索 query
2. web_search_tool_result → 搜索结果列表
3. text (with citations) → 带 source 引用标注的文本回复
server_tool_use 块:
{
"type": "server_tool_use",
"id": "srvtoolu_01ABC",
"name": "web_search",
"input": {"query": "claude shannon birth date"}
}web_search_tool_result 块:
{
"type": "web_search_tool_result",
"tool_use_id": "srvtoolu_01ABC",
"content": [
{
"type": "web_search_result",
"url": "https://en.wikipedia.org/wiki/Claude_Shannon",
"title": "Claude Shannon - Wikipedia",
"page_age": "April 30, 2025",
"encrypted_content": "..."
}
]
}text 块 with citations:
{
"type": "text",
"text": "Claude Shannon was born on April 30, 1916.",
"citations": [
{
"type": "web_search_result_location",
"url": "https://en.wikipedia.org/wiki/Claude_Shannon",
"title": "Claude Shannon - Wikipedia",
"cited_text": "Claude Elwood Shannon (April 30, 1916 ...)"
}
]
}错误通过 200 响应内的特殊 content block 返回:
{
"type": "web_search_tool_result",
"tool_use_id": "...",
"content": {
"type": "web_search_tool_result_error",
"error_code": "max_uses_exceeded"
}
}错误码:too_many_requests、invalid_input、max_uses_exceeded、query_too_long、unavailable
event: content_block_start → server_tool_use
event: content_block_delta → input_json_delta (partial_json)
event: content_block_start → web_search_tool_result
event: content_block_start → text
event: content_block_delta → text_delta
"usage": {
"input_tokens": 105,
"output_tokens": 6039,
"server_tool_use": {
"web_search_requests": 1
}
}在 Gemini API 的 tools 数组中添加:
{"googleSearch": {}}GCLI/Antigravity 内部 API 使用此简化形式。
{
"groundingMetadata": {
"webSearchQueries": ["..."],
"groundingChunks": [
{"web": {"uri": "https://...", "title": "..."}}
],
"groundingSupports": [
{
"segment": {"startIndex": 0, "endIndex": 120, "text": "..."},
"groundingChunkIndices": [0],
"confidenceScores": [0.95]
}
]
}
}-
groundingChunks[].web.uri→ Anthropicurl -
groundingChunks[].web.title→ Anthropictitle -
groundingSupports[].segment→ 文本引用区间
修改 3 个核心文件,不改动路由层和 API 层:
Client Request (Anthropic web_search tool)
│
▼
[Router Layer] — 不变
│
▼
[Converter Layer] ← 主要改动
├── models.py: 扩展数据类型
├── anthropic2gemini.py: 工具分离 + 请求/响应/流式转换
└── gemini_fix.py: Antigravity 搜索注入
│
▼
[API Layer] — 不变
ClaudeContentBlock 添加可选字段用于 web_search 响应块:
| 字段 | 类型 | 用途 |
|---|---|---|
encrypted_content |
Optional[str] |
web_search_result 加密内容 |
page_age |
Optional[str] |
页面更新时间 |
citations |
Optional[List[Dict]] |
text 块的引用列表 |
从 Anthropic tools[] 中分离 web_search 工具和 function 工具:
def separate_web_search_tools(tools):
"""
Returns: (web_search_config, function_tools)
web_search_config: {"googleSearch": {}} 或 None
function_tools: 原有的 function 工具列表
"""检测逻辑:tool.get("type", "").startswith("web_search_")
在 tools 转换后,合并 googleSearch 到 tools 列表:
web_search_config, function_tools = separate_web_search_tools(payload.get("tools"))
tools = convert_tools(function_tools) # 复用现有函数
if web_search_config:
tools = tools or []
tools.append({"googleSearch": web_search_config})- 从
candidate.get("groundingMetadata")提取 ground 信息 - 如果存在:
- 构建
server_tool_use块(搜索查询) - 构建
web_search_tool_result块(搜索结果) - 在 text 块中注入
citations(基于 groundingSupports 的 segment 映射)
- 构建
-
stop_reason设为"end_turn"(server_tool 不走 tool_use 循环)
流式处理的难点:Gemini 的 groundingMetadata 出现在响应末尾,而 Anthropic 格式要求 server_tool_use → web_search_tool_result → text with citations。
实现策略(简化方案):
- 缓存所有完整响应数据
- 在流结束前,从缓存的
groundingMetadata构建web_search_tool_result事件 - 将 citations 合并到最后一个 text 块中
-
提取
inject_google_search_tool()公共函数(从当前 geminicli 分支 line 281-285) -
在 antigravity 分支添加搜索注入:检测 model 名中的
-search后缀,注入{"googleSearch": {}} -
去重保护:如果 tools 中已有从 Anthropic converter 传入的
googleSearch,不重复添加
| Anthropic | Gemini |
|---|---|
tools[{type: "web_search_*"}] |
tools[{"googleSearch": {}}] |
server_tool_use.input.query |
groundingMetadata.webSearchQueries[0] |
web_search_result.url |
groundingChunks[].web.uri |
web_search_result.title |
groundingChunks[].web.title |
citation.url |
groundingChunks[n].web.uri |
citation.cited_text |
groundingSupports[].segment.text |
usage.server_tool_use.web_search_requests |
(从 groundingMetadata 推断) |
以下 Anthropic web_search 参数在 GCLI/Antigravity 的 googleSearch 中无对应,将记录 warning 后忽略:
-
allowed_domains/blocked_domains— Gemini googleSearch 不支持域名过滤 -
user_location— Gemini googleSearch 使用 Google 账号的地区设置 -
max_uses— Gemini 内部自行管理搜索次数 -
web_search_20260209的动态过滤 — 需要 code_execution 工具支持
格式转换本身不损失搜索广度——groundingMetadata 是 Gemini 启用 googleSearch 工具后返回搜索结果的原生标准格式,桥接层仅做翻译,不是重定向或包装。但以下因素可能实际影响搜索效果:
-
googleSearch: {}简化形式:GCLI/Antigravity 传入的是空对象,使用 Gemini 的默认搜索策略。Gemini 完整 API 支持dynamic_retrieval_config(如MODE_DYNAMIC+dynamic_threshold)来调节搜索触发阈值和广度,但 GCLI/Antigravity 内部 API 未必支持这些参数。如果后续发现这些后端支持更多参数,可进一步映射。 -
域名过滤丢失:由于 Gemini 的
googleSearch不支持域名白名单/黑名单,无法通过 Anthropic 工具参数限制搜索范围,这些参数在转换时被丢弃。 -
地域本地化丢失:
user_location参数无法传递,Gemini 会使用账号关联的默认地区设置。
所有改动遵循"条件分支追加、默认不变"原则,对原有各类接口零副作用。以下逐层论证。
ClaudeTool 新增 5 个 Optional 字段。原有客户端发送的 tools 不含 type、max_uses 等字段时,Pydantic 将其设为 None(默认值),model_dump(exclude_none=True) 的输出与之前完全一致。路由器调用 model_to_dict(claude_request) 后传给 converter 的数据结构不变。
ClaudeContentBlock 新增 3 个 Optional 字段,外加 class Config: extra = "allow"。原有消息中不含新字段时它们为 None,不参与序列化。extra = "allow" 是纯宽松化——从原来的"拒绝未知字段"变为"容忍未知字段",不会导致任何已有的严格校验通过而现在失败。
ClaudeUsage 新增 server_tool_use: Optional。仅在 _has_grounding_content() 为 True 时才写入该字段。无 ground 的请求走原来的 {"input_tokens": n, "output_tokens": n} 路径,完全不变。
anthropic_to_gemini_request() 的关键路径:
原有: tools = convert_tools(payload.get("tools"))
现在: web_search_config, function_tools = separate_web_search_tools(payload.get("tools"))
tools = convert_tools(function_tools)
if web_search_config is not None: tools += [{"googleSearch": ...}]
-
无 web_search 工具时:
separate_web_search_tools返回(None, anthropic_tools),function_tools即原始tools,convert_tools()收到的参数和原来完全一致;web_search_config is None分支不执行,结果等价于原代码。 -
有 web_search 工具时:在 function tools 之外额外追加
{"googleSearch": {}},不影响原有 function calling 行为。
gemini_to_anthropic_response() 中:
原有: content = [text_blocks..., tool_use_blocks...]
现在: [server_tool_use, web_search_tool_result] + [text_blocks..., tool_use_blocks...]
-
无
groundingMetadata的响应:_has_grounding_content(grounding_meta)为False,两个新增代码块完全跳过,text 块不加 citations。生成的content数组与原一致。 -
stop_reason逻辑完全保留:has_tool_use and finish_reason == "STOP"→"tool_use"的判断不受影响。ground 用的是server_tool_use而非tool_use,两者互不干扰。 -
Usage 不变:无 ground 时
usage_dict仅含input_tokens和output_tokens,和原来一致。
gemini_stream_to_anthropic_stream() 中:
原有: ...text events... → message_delta → message_stop
现在: ...text events... → [可选: server_tool_use + web_search_tool_result events] → message_delta → message_stop
-
无
groundingMeta:grounding_meta保持None,_has_grounding_content(None)为False,整段新增代码不执行。usage 中的**({...} if False else {})展开为空。流事件序列与原完全一致。 -
有
groundingMeta:在message_delta之前插入新事件,不影响已发送的 text/thinking/tool_use 事件,仅在流末尾追加信息。标准 Anthropic 客户端能正确处理额外的content_block_start/delta/stop事件。
Geminicli 分支中的搜索注入用 inject_google_search_tool() 替代内联代码,行为等价,并加了去重保护。原代码中 tool.get("googleSearch") 对空 dict {} 的 falsy 判断 bug 也已修复(改用 "googleSearch" in tool)。
Antigravity 分支新增的 -search 检测和注入是纯新增代码,不影响原有非搜索请求路径。
-
OpenAI-compatible (
/v1/chat/completions):使用openai2gemini.py,不经过anthropic2gemini.py -
Gemini-native (
/v1/models/*:generateContent):直接使用 Gemini 格式,converter 不走 Anthropic 路径 -
-search模型名后缀机制:在gemini_fix.py中完整保留 - Thinking / anti-truncation / fake-streaming:代码路径完全不接触 web_search 逻辑
-
路由层和 API 层:
src/router/、src/api/目录零改动,请求解析、认证、Retry 逻辑全部保持原状
-
-search模型名后缀机制保持不变 - 不传 web_search 工具的请求行为完全不变
- function calling 工具不受影响
- web_search + function tools 可同时使用