> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/console/search · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 联网搜索

很多模型自己不会上网搜索，但客户端（Claude Code 等）会在请求里声明联网搜索工具。原样转发时，这类模型要么无视搜索工具、要么直接报错。

**网关搜索**解决这个问题：当路由目标模型不支持原生搜索时，网关替模型完成搜索，把结果整理成参考资料交给模型回答，再把搜索结果包装成"模型自己搜的"原生形状还给客户端。网关代搜后**必定调用上游模型一次**——搜索结果（包括厂商自己合成的答案）作为标注清楚的参考资料注入用户消息，由模型基于资料组织回答；网关绝不直接把厂商文本当模型响应返回。客户端零感知。

入口：控制台 → **设置** → **搜索**（仅 admin 可见）。

## 快速开始

1. **添加搜索引擎**：点击"添加搜索引擎"，类型选 `serper`（首批支持的厂商，[serper.dev](https://serper.dev)），粘贴 API Key，保存。
2. **连通性测试**：列表行内点"测试"，网关会发起一次真实查询（短超时），返回结果条数与耗时。
3. **设默认引擎**（可选）：系统配置区选择默认搜索引擎；不设置时按优先级自动选择。
4. 之后，凡客户端声明了搜索工具、而路由模型无原生搜索能力的请求，自动走网关搜索。

## 劫持开关：三态与优先链

每个模型可在模型表单设置"网关搜索"三态开关（写入 `extra_config.search.enabled`）：

| 取值 | 行为 |
|------|------|
| 默认 · 跟随能力 | 模型无原生搜索 → 网关劫持代搜；有原生搜索 → 让位原生路径 |
| 强制开启 | 即使模型有原生搜索，也由网关劫持代搜（搜索工具会被剥离，上游不会双搜） |
| 强制关闭 | 任何情况下都不劫持 |

判定按**优先链**解析：**模型配置 > 上游配置 > 能力声明**。上游（upstream）同样可以在 `extra_config.search` 配置开关，模型级配置整体覆盖上游级配置；两者都未配置时，才回落到元数据能力声明（模型是否原生支持搜索）。

> **常见疑问**：qwen 等模型在元数据中声明了原生搜索能力（如 DashScope `enable_search`），默认模式下这类模型走原生路径、**不会**被网关劫持。若希望统一由网关代搜，把该模型的开关设为"强制开启"。

## 引擎选择：三级回落

一次劫持使用哪个引擎，按三级选择：

1. 模型/上游在 `extra_config.search.provider` 指定的引擎名；
2. 系统默认引擎（设置 → 搜索 → 默认搜索引擎）；
3. 优先级最高的启用引擎。

## 系统配置与调优

设置 → 搜索页下半部分提供全局调优参数（保存即热生效）：

| 参数 | 默认 | 含义 |
|------|------|------|
| 每查询页数 | 2 | 每次劫取向厂商取几页结果（每页约 10 条） |
| 单条摘要最大字符 | 500 | 注入给模型的单条摘要截断长度 |
| 注入总字符上限 | 16384 | 参考资料文本总封顶，超出从尾部丢弃 |
| 查询提取最大字符 | 400 | 从用户消息提取的查询长度上限 |
| 单引擎超时 | 10000 ms | 单个引擎的查询超时 |
| 劫持总预算 | 15000 ms | 含降级重试在内的整体时间预算 |
| 并发闸门容量 | 64 | 同时进行的搜索数上限 |
| 闸门排队超时 | 2000 ms | 排队等闸门的时限，超时减载 |
| 最大响应字节 | 512 KB | 厂商响应硬上限，超限断流 |

**内存天花板**：常驻占用 ≈ 闸门容量 × 每查询页数 × 最大响应字节（默认 64 × 2 × 512KB ≈ 64MB）。三个旋钮联动即可匹配实例内存预算。

## 降级行为（搜索失败不影响请求）

搜索是尽力而为的旁路能力，任何失败都**不阻塞请求**，且**不剥离客户端声明的搜索工具**：

- 引擎超时/HTTP 错误/解析失败/无结果 → 请求体原样转发（web_search 工具保留），响应里带一条降级通知；
- 内存压力高或并发闸门排队超时 → 减载，请求体同样原样转发；
- 未配置任何引擎 → 不劫持，请求按现状转发。

代搜失败时，保留的 web_search 工具会交给上游：有原生搜索能力的路由（如 qwen 的 `enable_search`）由上游模型继续搜索兜底；无原生能力的路由则按协议转换层既有规则处理（可能被静默丢弃、模型本轮无法搜索——与代搜失败前行为一致）。搜索工具仅在搜索成功、且结果成功注入用户消息后才被剥离，避免"搜索失败却把模型搜索能力一并剥夺"的情况。

降级时客户端视角：代搜失败的轮次响应没有网关搜索块（等价于"模型自己搜或没搜"），回答仍正常返回。运营侧可在请求日志的翻译通知中看到降级原因。

## 劫持请求的完整数据流

1. 网关从用户最后一条消息提取查询；
2. 向引擎发起搜索（分页取回标题/链接/摘要，不抓取正文）；厂商若返回自己合成的答案文本，一并保留；
3. 结果渲染成带编号的参考资料，追加到用户最后一条消息。注入文本开头明确标注"这是网关代搜的搜索结果，回答时引用来源 URL"——让模型清楚这是搜索资料而非用户输入；厂商合成答案作为"搜索厂商摘要"单独一段置于结果列表前，同样标注清楚；
4. **搜索成功且注入成功后**，网关才剥离客户端声明的 web_search 工具（避免上游重复搜索）；注入失败则保留工具、请求体原样转发（见降级行为）；
5. 上游模型只看到"问题 + 标注清楚的参考资料"，正常作答（**必定一次调用**，网关绝不替模型回答）；
6. 网关把保存的搜索结果按客户端协议合成原生搜索块拼进响应：
   - Anthropic → `server_tool_use` + `web_search_tool_result` 内容块；
   - Chat Completions → `url_citation` 引用标注；
   - Responses → `web_search_call` 输出项。

## 已知代价：上游前缀缓存与必调上游

网关代搜后**必定调用上游模型一次**——这是不变量。即便搜索厂商返回了现成的合成答案，网关也只把它作为资料注入、由模型基于资料组织回答，绝不直接把厂商文本当模型响应返回。

注入的参考资料只有上游看得见，客户端下一轮请求不会携带它。对无状态协议（Chat Completions / Anthropic），上游前缀缓存的命中范围会收缩到上一个被劫持轮次之前；有状态协议（Responses + store）不受影响。这是"客户端零感知"的固有代价。每轮劫持还会消耗厂商 2 次请求配额（默认 2 页）。

## 计费口径

劫持搜索**不计入用量与计费**：响应体 `usage.server_tool_use.web_search_requests` 只是协议伪装（与真实厂商响应同形），内部用量账本不记录搜索次数。搜索成本是网关运营者向厂商的采购支出，与上游计费体系分离。
