☰
彻底关闭Gemini API联网搜索:grounding机制与tools参数详解
2026/10/20 23:05:55 网站建设 项目流程

我遇到过好几个开发者,拿着几乎一样的代码来问我:为什么我明明没感觉自己开了联网,生成的回复里却会出现“根据网页信息……”这类带检索痕迹的内容?后来排查下来,几乎全都是tools参数里藏了一个GoogleSearchRetrieval()。这个问题在用 Google 官方 Python SDK(也就是google/genai库)调用 Gemini 时尤其常见,因为很多示例都会教你这样开联网搜索接地,但很少有人说清楚怎么关。严格来讲,web search grounding在 API 层面并不是靠一个grounding_enabled=False之类的参数来控制的,它是请求里的一种工具声明。你只要没把检索工具放进请求,它就等于关着。

这篇文章就是围绕这个机制展开的,把“grounding 从哪进来”“怎么在代码级别彻底关闭”“关完以后怎么验证”以及“为什么有人坚持要关”这几个问题完整过一遍。它适合正在用google/genai库做 Gemini 应用,希望模型只依赖静态训练知识、不希望每次回复都去实时检索网页的开发者,也适合被各种抄来的配置搞到头大、想一次性搞清楚tools体系的同学。

1. 它不是一个全局开关:grounding 其实是“请求级工具”

1.1 误解主要来自调试台界面

在 AI Studio 这类网页调试工具里,生成面板通常有“Grounding with Google Search”相关的开关或徽标,用来提示当前回复是不是参考了搜索结果。于是很多人想当然地认为 API 也应该有一个全局 boolean 参数,比如grounding_enabled=False,或者以为账号在 Google 后端默认开启了搜索。

实际不是这样。Gemini API 的联网搜索接地是挂在“请求的工具列表”上的。一次请求带了检索工具,模型在生成时就会先发起搜索,把网页材料拿回来再组织答案;不带这个工具,模型就完全基于自身的参数和预训练知识来回答。它和“开关”的区别在于:这是一种显式的工具声明,不是环境配置。

换句话说,对程序来说,“不传这个工具”就是关闭。你找不到那个隐藏 flag,因为它根本不存在。可以类比一个提供可选插件功能的接口:你只需要不给它传入某个可选参数,它就不会启用该功能,而不是去接口内部找一个 invisible 开关。

1.2 官方库里的检索工具长什么样

在google/genai库中,开启联网搜索的标准写法是这样:

from google import genai from google.genai import types client = genai.Client(api_key="YOUR_API_KEY") response = client.models.generate_content( model="gemini-2.0-flash", contents="今天科技领域有什么重要进展?", config=types.GenerateContentConfig( tools=[ types.Tool( google_search_retrieval=types.GoogleSearchRetrieval() ) ], ), )

注意这里有两层对象:tools列表里每个元素是types.Tool,而当某一个Tool实例的google_search_retrieval字段被填上非空值时,就表示这一路调用会使用 Google Search 做 grounding。所以“关闭”在代码层面也很直观:不要让GoogleSearchRetrieval()出现在tools里。

另外一个容易混淆的点是:模型本身并不会因为你选了个模型名就自动具备联网能力。Gemini 有知识截止时间,但那是训练数据的事;端到端去连接搜索后端,必须显式声明检索工具,或者所处的集成环境确实带有动态检索能力。理解这一点,后续排查时就不会手忙脚乱。

1.3 判断生效与否的唯一现场:grounding_metadata

一次请求真的触发了搜索接地时,服务端返回的候选结果里会带一个专门的字段。你可以这样打印出来:

metadata = response.candidates[0].grounding_metadata print(metadata)

开启时,这个metadata里通常会有grounding_chunks(命中的网页片段)和grounding_supports(引用支持关系)等结构化内容;关闭时,它一般是None,或者是一个没有任何 chunks/supports 的空壳对象。我排查问题时从来不依赖“回复里有没有[1]这种角标”,因为模型有时候即使没有联网,也会模仿出类似引用标记的格式,纯属文本层面的幻觉。真正可靠的判断依据就是这个 metadata 字段,它是响应协议里的正式载体。

2. 排查:你代码里的搜索工具到底是怎么混进来的

2.1 最典型:照抄示例,config 里还躺着检索工具

最常见的来源就是教程和文档。官方要演示“让模型实时搜索”,几乎都会给一段带tools=[types.Tool(google_search_retrieval=...)]的代码。你复制下来做实验没问题,但改造成自己的业务时往往只改了contents,忘了清掉tools,于是每次请求都在悄悄联网。

这种问题最好查:在项目代码里搜google_search_retrieval,或者GoogleSearchRetrieval,看它出现在哪。尤其是一些“新闻摘要”“行业动态播报”类的项目,教程几乎必然开检索,很多人最后上线时也没有发现自己一直在付费买搜索工具,因为模型照样能回答,只是慢了一点、偶尔多一些引用标注。

2.2 藏在封装层和团队共享配置里

如果你不是直接调用client.models.generate_content,而是通过一个内部封装的llm.chat(prompt)之类的函数去发请求,那问题可能不在你眼前这一层,而在封装函数的内部实现里。我帮某个项目排查过类似问题,现象是“明明 config 里什么都没写,但响应中就是有 grounding_metadata”。

最后发现是团队网关层在转发请求时默认附加了检索工具,外层传入的 config 会被网关注入或合并。这种情况下直接看自己的调用代码是没用的,要一层层往上游翻:看封装的公共方法、看 HTTP 拦截器、看自定义 client 工厂。排查建议是先加一行日志,在真正调用 SDK 前把config打印出来:

print(config.model_dump()) # 或 print(config)

如果底层封装太深,至少要在最内层打印一次,确认发给 SDK 的最终配置到底是什么状态。

2.3 从旧版 SDK 或另一套代码迁移来的残留

Google 早期 Python 库google-generativeai的写法和新版google/genai并不完全一样。老代码里可能写的是tools=[{'google_search_retrieval': {}}]这种字典结构,迁移到新版时如果没做完整映射,就可能被转换成一个结构奇怪但依然会被识别的检索工具。

还有一个常见场景是从某个项目里复用了它的Client初始化代码和GenerateContentConfig构造代码,两段代码分别来自不同时期,拼在一起后搜索工具就这么混进去了。遇到这类问题,我建议不要靠肉眼猜,直接在项目目录里跑一次文本搜索,把所有包含search、retrieval、grounding的代码片段都列出来,再逐个判断哪一处是真正生效的。

3. 关掉 grounding 的三套可复现配置

3.1 最简单路径:请求里不传 tools

新版本 SDK 的默认GenerateContentConfig不会附带任何工具。因此,最简单、也最不容易出问题的关闭方式,就是让tools这个参数不出现在请求里:

from google import genai client = genai.Client(api_key="YOUR_API_KEY") response = client.models.generate_content( model="gemini-2.0-flash", contents="请用两句话解释什么是光合作用", ) print(response.text)

你没有写config,SDK 会使用默认配置,默认配置里没有检索工具,也就等于关闭了 web search grounding。如果你想写得更显式一点,也可以:

config=types.GenerateContentConfig(tools=[])

我个人的习惯是“不写”而不是“写空列表”。一是代码更少,二是避免后续维护的人误以为这里是一个可以继续添加工具的地方。从语义上讲,关闭 grounding 不是从一个 flag 变成另一个 flag,而是“这个请求本来就不携带检索工具”。

3.2 已有 config 对象:清理而不是猜

如果你的代码里已经有一个 config 对象,并且你确认它没带搜索工具,那就没必要大动干戈。但如果是从一个公共对象、配置文件或外部接口拿到的 config,你需要先检查再清理:

import copy new_config = copy.deepcopy(existing_config) new_config.tools = []

这里我故意先深拷贝再修改,原因是公共配置对象可能被多个请求共享,原地修改会造成请求间相互污染。你第一次把tools清空了,下一个请求也可能拿到这个被改过的对象,调试起来非常痛苦。如果 config 是以字典形式存在的,那就更简单:

if "tools" in config_dict: del config_dict["tools"]

然后把这个字典再转成types.GenerateContentConfig(...)传给请求。核心思路一句话:找到真正传进 SDK 的最终配置,把它里面的tools解除绑定。

3.3 在封装层统一强制移除

如果你维护的是团队共享的封装模块,或者多个项目都调用同一个generate_content入口,最好的做法是在最内层统一做一次工具过滤。不需要让每个调用方都去关心“要不要关搜索”,封装层直接保证:任何情况下传入的检索工具都被移除。

def generate_without_search(client, model, contents, config=None): if config is None: config = types.GenerateContentConfig() if config.tools: config.tools = [ tool for tool in config.tools if tool.google_search_retrieval is None ] return client.models.generate_content( model=model, contents=contents, config=config, )

这里的关键点在于“审查工具列表”,而不是“审查回复文本”。过滤掉检索工具不会影响function calling等其他功能,因为你只是把列表里google_search_retrieval非空的那一项剔除,剩下的工具原样保留。把这段逻辑放在封装层,以后任何调用方传进来的 config 都会经过这一道保险。

3.4 想保留其他工具时,只精确剔除检索项

有些项目确实在用 function calling,同时又希望彻底关掉联网搜索。此时不能简单地把tools置空,否则业务功能也一起没了。正确的做法是遍历tools列表,只过滤掉检索工具这一项:

config.tools = [ tool for tool in (config.tools or []) if tool.google_search_retrieval is None ]

判断条件是tool.google_search_retrieval is None。之所以用这个字段而不是isinstance,是因为一个types.Tool对象上可能挂有多种能力,比如function_declaration、google_search_retrieval、code_execution等,字段为空就代表该能力未启用。如果你的 SDK 版本字段名在 IDE 自动补全里显示为其他名称,以你本地实际 SDK 版本为准,思路是不变的:找到代表检索能力的字段,把它筛掉。

4. 验证你真的关掉了:别信回复内容,信 grounding_metadata

4.1 开与关的状态对比

很多人在关闭之后喜欢通过“回复里还出不出引用标记”来判断是否生效,这并不完全可靠。真正有区分度的还是响应结构里的grounding_metadata。下面这张表总结了两种状态的差异:

响应字段开启联网搜索时关闭联网搜索后
candidates[0].grounding_metadata非空,包含 chunks/supports通常为 None,或空对象
grounding_chunks有命中网页片段无
grounding_supports有引用支持信息无
回复中的角标式引用可能出现可能仍出现(模型模仿),不能作为判据

也就是说,关闭后最可靠的判断方式不是“看文字”,而是“看结构”。模型有没有真的去检索网页,服务端一定会通过 metadata 反映出来。这个字段才是真正的指纹。

4.2 把验证逻辑写进流水线

如果你在跑批量任务或者做回归测试,建议把这个检查写成一个小断言,别靠人工抽查:

def has_grounding(response) -> bool: md = response.candidates[0].grounding_metadata if not md: return False return bool(md.grounding_chunks or md.grounding_supports) # 使用示例 result = client.models.generate_content(...) assert not has_grounding(result), "这条请求仍然触发了联网搜索"

我在自己的项目里就这么做过:跑一个一百条的回归集,断言每条响应的grounding_metadata都为空。一旦有人不小心在某个新分支里把检索工具加回 config,测试立刻报警,不会等到上线才发现延迟飙升、输出不稳定。

4.3 关了但好像还在:可能出在这几个地方

如果你确实按要求移除了检索工具,但grounding_metadata依然有内容,先从这几个方向排查。

第一,确认你请求的入口是不是 Gemini API 本身。如果你走的是一层平台聚合接口,那网关层可能在你没有感知的情况下合并了搜索工具。这种情况和第二节里的“封装层注入”是一样的,需要检查上游。

第二,检查模型标识。如果你用的是某个早期实验版本,某些入口可能默认带动态检索行为。建议把模型名换成当前正式发布的稳定版本,再跑一次同样的验证逻辑。

第三,看是不是缓存或旧进程。改动代码后如果服务没重启,内存里可能还在跑旧配置。尤其长时间运行的 worker,这个问题特别容易让人误判。

第四,确认你用的是generate_content系列接口。不同接口的默认行为不完全一致,如果你实际调用的是其他封装好的高层接口,它可能不是简单的生成调用。

5. 为什么值得费这个劲:稳定性、延迟与内容可控

5.1 可复现性:自动化评测和批处理场景的硬需求

做批量信息抽取、自动化评测、离线任务回放的时候,最怕的就是外部变量。联网搜索接地会让模型参考当前网页内容,而网页是实时变化的,同一个 prompt 早上和晚上可能得到完全不同的内容。对一个评测集来说,这等于把“随机误差”放大到不可控的程度,回归测试结果根本没法对齐。

我遇到过的一个典型例子是:一个结构化抽取任务本来跑得好好的,某天输出格式批量异常,最后发现是某次改代码时有人复制了一段带GoogleSearchRetrieval的 config。检索回来的网页文本自动塞进上下文,挤占了原始任务的注意力,格式跟着全乱了。关掉 grounding 之后,行为立刻稳定下来。

5.2 延迟与成本:搜索不是免费的

联网搜索工具不是说“开着不用”就没代价的。一旦搜索真的被触发,请求需要等外部检索完成,延迟经常从几百毫秒涨到几秒,上下文里还会塞入网页正文,token 消耗也会明显上涨。如果在 Agent 循环里用了带搜索工具的多轮生成,多轮叠加之后成本差异会非常可观。

而且,很多生成场景其实并不需要实时互联网信息。比如内部知识问答、日志摘要、代码解释、固定规则改写,这些任务靠模型自身能力就够用了。开着 grounding 不仅慢,还可能带来时间线错乱的问题——网页内容里的“今天”跟你业务里的“今天”可能根本不是同一个日期,这会让下游解析踩坑。

5.3 一个可以直接抄的最终模板

综合前面的内容,这是我目前比较推荐的一个完整配置示例,兼顾了“不联网”“保留其他工具能力”和“便于验证”三个目标:

from google import genai from google.genai import types client = genai.Client(api_key="YOUR_API_KEY") def generate_without_search(prompt: str) -> str: config = types.GenerateContentConfig( temperature=0.2, ) # 保险兜底:无论上层传了什么,检索工具一律移除 if config.tools: config.tools = [ tool for tool in config.tools if tool.google_search_retrieval is None ] resp = client.models.generate_content( model="gemini-2.0-flash", contents=prompt, config=config, ) md = resp.candidates[0].grounding_metadata if md and (md.grounding_chunks or md.grounding_supports): print("[warning] 本次请求仍触发了 grounding,请检查上游配置") return resp.text if __name__ == "__main__": text = generate_without_search("简单解释一下什么是闰年") print(text)

这段代码把三件事做完了:发起不带检索工具的请求、在入口保证检索工具即使被传入也会过滤、在返回后主动检查是否真的关住了。对小项目够用,放到团队封装里也足够清晰。

我在实际项目里的体会是:遇到“联网搜索关不掉”的问题,不要先去界面上找开关,也不要在回复里数引用标记,直接打印请求里的tools终态和响应里的grounding_metadata,整个链路两分钟就能定位。把 grounding 当做一个请求级工具,而不是一个全局配置,这套思路放到任何 SDK 版本里都不会过时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询