1. 为什么我要把 openclaw 的 web_search 换掉
openclaw 这类 Agent 框架默认的web_search工具,背后通常接的是公共搜索接口。跑 demo 的时候没感觉,一旦让它连续做几十轮检索、抓取、总结,问题就冒出来了:请求频率一高就被限流,返回结构偶尔缺字段,最要命的是搜索词和结果会经过第三方,做内部资料整理时心里总不踏实。我试过在几个项目里直接用它查技术文档,前几次还行,后面开始间歇性返回空结果,Agent 拿不到内容就反复重试,token 消耗直接翻倍。
SearXNG 正好能补上这块。它是一个开源的元搜索引擎,本身不存搜索历史、不做用户画像,把 Google、Bing、DuckDuckGo 等多个来源的结果聚合后统一返回。你可以把它理解成一个「搜索路由器」:Agent 只跟你的私有实例说话,具体去哪个上游引擎查、怎么合并去重,都由 SearXNG 在本地完成。对 openclaw 来说,只要把web_search的 base URL 指向这个实例,就能用同一套工具协议拿到结构化 JSON,不用改 Agent 的推理逻辑。
这套方案适合谁?一是本地或内网跑 Agent、不想让检索流量外露的开发者;二是被公共搜索接口限流搞烦、想要稳定 QPS 的团队;三是想给 openclaw 加一个可控搜索后端、方便做结果缓存和审计的人。整篇我会按「Docker 拉起 SearXNG → 改 settings.yml 开 JSON → 配 openclaw 指向私有实例 → 验证请求 → 排错」的顺序走,配置都能直接复制。如果你后面还要接 Claude Code 或做长期编码 Agent,可以顺带了解下 TaoToken 的 Coding Plan,模型调用和搜索后端分开管,排查问题时边界更清楚。
需要先说明一点:SearXNG 默认只给浏览器用,JSON 输出是关着的,很多人部署完发现format=json返回 403,就是卡在这一步。下面会专门处理。
2. 用 Docker 拉起 SearXNG 并开放 JSON 接口
先说前置条件:一台能跑 Docker 的机器(本地或服务器都行),装好 Docker 和 Docker Compose 插件,docker compose version能输出版本号即可。不需要公网 IP,内网访问完全够用,openclaw 和 SearXNG 在同一台机器或同一内网时最省事。
先建目录,把配置和容器数据分开,方便后面回滚:
mkdir -p /opt/searxng cd /opt/searxng接着写docker-compose.yml。这里用官方镜像加一个 Redis 做缓存,Redis 不是必须的,但加上之后重复查询会快很多,Agent 反复搜同一批关键词时体感明显:
services: searxng: image: searxng/searxng:latest container_name: searxng ports: - "8888:8080" volumes: - ./settings.yml:/etc/searxng/settings.yml:ro environment: - SEARXNG_BASE_URL=http://127.0.0.1:8888/ - SEARXNG_SECRET=change-this-to-a-random-string depends_on: - redis restart: unless-stopped redis: image: redis:alpine container_name: searxng-redis restart: unless-stopped注意SEARXNG_BASE_URL要和你实际访问地址一致。如果 openclaw 在另一台机器上,把127.0.0.1换成 SearXNG 所在机器的内网 IP,比如http://192.168.1.20:8888/。这个值影响返回结果里的链接拼接,填错会导致结果 URL 指向错误。
然后是关键的settings.yml。SearXNG 对配置结构校验很严,最稳的写法是用use_default_settings: true继承默认配置,只覆盖你要改的字段。JSON 输出必须显式在search.formats里加上json,否则接口不认:
use_default_settings: true general: instance_name: "searxng-private" enable_metrics: true search: safe_search: 0 autocomplete: "" default_lang: "zh-CN" formats: - html - json server: secret_key: "change-this-to-a-random-string" limiter: false image_proxy: true ui: default_theme: simple几个点解释一下。limiter: false是关掉内置限流,因为 openclaw 的请求来自本机、频率可控,开着反而容易误伤;如果你把实例暴露到公网,建议保持开启并配合反向代理。autocomplete设成空字符串是关掉自动补全,Agent 用不到,还能少一次外部请求。formats里同时保留html和json,这样浏览器调试和程序调用都不耽误。
配置写完直接起:
docker compose up -d docker compose psdocker compose ps里两个容器都显示running就对了。如果 searxng 反复重启,先看日志:
docker compose logs --tail=50 searxng最常见的报错是Invalid settings.yml,基本都是 YAML 缩进或字段层级写错,对照上面这份改就行。
3. 把 openclaw 的 web_search 指向私有实例
容器起来后,先确认 SearXNG 本身能返回 JSON,再动 openclaw 的配置,这样出问题好定位。用 curl 打一发:
curl -s "http://127.0.0.1:8888/search?q=openclaw&format=json" | head -c 500能吐出一段 JSON、里面有results数组,说明接口通了。如果返回 403,回到上一节检查formats里有没有json,以及limiter是不是还开着。
接下来是 openclaw 侧。openclaw 的搜索工具配置一般放在项目根目录的config或settings文件里,不同版本字段名略有差异,核心是三件套:Base URL、API Key(SearXNG 本地实例通常不需要,留空或填占位)、以及工具类型。下面是一份可直接改的 JSON 片段,路径按你项目实际的配置文件来:
{ "tools": { "web_search": { "provider": "searxng", "base_url": "http://127.0.0.1:8888", "api_key": "", "search_path": "/search", "default_params": { "format": "json", "language": "zh-CN", "safesearch": 0 }, "timeout_ms": 8000, "max_results": 8 } } }如果你的 openclaw 版本用的是 TOML 配置,等价写法是这样:
[tools.web_search] provider = "searxng" base_url = "http://127.0.0.1:8888" api_key = "" search_path = "/search" timeout_ms = 8000 max_results = 8 [tools.web_search.default_params] format = "json" language = "zh-CN" safesearch = 0这里provider字段是关键,它决定 openclaw 用哪套请求和解析逻辑。如果框架内置了searxng这个 provider,直接填它;如果没有,就选一个「自定义 HTTP 搜索」类的 provider,然后把base_url和search_path指过去,返回结构 SearXNG 是标准的results[].title/url/content,大部分通用解析器都能吃。
max_results建议别设太大,8 到 10 条足够 Agent 做摘要,设到 20 以上会明显拖慢单轮响应,而且后面的结果相关性下降。timeout_ms给 8000 是留了余量,SearXNG 聚合多个上游时偶尔会慢,超时太短会导致 Agent 误判搜索失败。
改完配置重启 openclaw 进程,让它重新加载工具定义。到这一步,链路就通了:openclaw 调web_search→ 打到本地 SearXNG → SearXNG 聚合上游 → 返回 JSON → openclaw 解析成工具结果。
4. 验证一次真实搜索请求与结果解析
配置改完不能只看「没报错」,得跑一次完整请求,确认返回结构能被 openclaw 正确解析。分两步验证。
第一步,直接对 SearXNG 发请求,看原始返回长什么样:
curl -s "http://127.0.0.1:8888/search?q=Docker%20compose%20healthcheck&format=json&language=zh-CN" \ | python3 -m json.tool | head -40正常返回里会有query、number_of_results、results这几个顶层字段,results里每条包含title、url、content、engine。engine字段能告诉你这条结果来自哪个上游,排查「为什么某类结果缺失」时很有用。
第二步,在 openclaw 里触发一次工具调用。最直接的方式是给它一个必须联网的问题,比如让它查某个库的最新版本号,然后看日志里web_search的调用记录。如果框架有 debug 日志,打开后能看到请求 URL 和返回条数。我实测下来,一次正常调用应该在 1 到 3 秒内返回,条数和max_results接近。
如果 openclaw 侧拿不到结果,但 curl 是通的,问题多半在解析层。常见情况是框架期望的字段名和 SearXNG 不一致,比如它找snippet而 SearXNG 给的是content。这时候有两个办法:一是在 openclaw 的 provider 配置里加字段映射;二是写一层很薄的适配,把 SearXNG 的返回转成框架要的结构。多数情况下改配置就能解决,不用动代码。
再补一个健康检查动作,方便你以后做监控。SearXNG 自带/healthz端点:
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8888/healthz返回200就说明服务活着。可以把这个塞进定时任务,容器挂了能第一时间知道,而不是等 Agent 报错才发现。
5. 部署与接入常见报错排查
这一节按真实会撞到的报错来对,每条给出原因和动作。
报错一:ValueError: Invalid settings.yml,容器起不来。原因基本是 YAML 结构不合法,SearXNG 对字段层级校验严格,多一层少一层都会挂。动作:确认顶层有use_default_settings: true,search.formats是列表而不是字符串,缩进统一用两个空格。改完docker compose restart searxng再看日志。
报错二:format=json返回 403 Forbidden。这是最高频的坑。SearXNG 默认只开html格式,JSON 要手动加。动作:检查settings.yml的search.formats里有没有json,同时确认server.limiter为false。两个都对了还 403,看请求有没有带正常 User-Agent,某些版本对空 UA 会拦。
报错三:openclaw 日志出现local proxy failed或连接被拒。说明 openclaw 根本没连上 SearXNG。动作:先docker compose ps确认容器在跑,再确认base_url里的 IP 和端口对得上。如果 openclaw 跑在容器里、SearXNG 在宿主机,127.0.0.1是不通的,要换成宿主机内网 IP 或 Docker 网络别名。
报错四:返回 200 但results为空,或报reading choices之类的解析错误。空结果通常是上游引擎被限流或查询词太偏,换个词再试;如果换词也空,检查default_lang和safesearch设置。解析错误则是 openclaw 拿到的结构和预期不符,动作:用 curl 看原始 JSON,对照框架文档确认字段名,必要时加映射。
报错五:401 Unauthorized。SearXNG 本地实例默认不需要鉴权,出现 401 一般是 openclaw 配置里api_key填了非空值、而实例又没开鉴权,或者反过来实例开了鉴权但 key 没填。动作:本地部署把api_key留空;如果确实要鉴权,在反向代理层加,别在 SearXNG 里硬塞。
报错六:OAuth 相关报错。这个和 SearXNG 无关,通常是 openclaw 里其他模型 provider 的凭证过期了,搜索工具本身不涉及 OAuth。动作:单独测web_search工具,把模型调用和搜索调用分开验证,别混在一起排查。
排查时记住一个原则:先用 curl 确认 SearXNG 这一层是好的,再去查 openclaw 的配置和解析。两层分开测,定位速度快很多。
6. 后续怎么把这套搜索后端用顺
跑通之后,有几个小调整能让它更耐用。一是给 Redis 设个内存上限,避免长时间运行把内存吃满,在 compose 里给 redis 加command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru就行。二是如果 openclaw 的检索量上来了,可以在 SearXNG 前面加一层 Nginx 做缓存和限速,把重复查询挡在容器外。三是定期看docker compose logs searxng里哪些上游引擎报错多,在settings.yml的engines里把不稳定的关掉,结果质量会稳一些。
如果你后面要把这套 Agent 接到更强的模型上做长期编码任务,模型调用和搜索后端建议分开管理。搜索这块你已经有了私有实例,模型这块可以看下 TaoToken 的 Coding Plan,专门给长期编码和 Agent 场景做的额度方案,和本地搜索后端配合起来,整条链路的可控性会好很多。需要的话从 API Keys 页面拿凭证,接入文档里有各框架的配置示例,照着改 base URL 和 model ID 即可。