websearch: 默认 auto 付费冻结,SearXNG 优先 + 显式解冻开关
- 默认 auto 只走免费链 searxng -> duckduckgo;即使环境里有 付费 key 也不调用 Perplexity/Exa/Tavily/Brave,SearXNG 失败也不回落付费,默认使用零付费花费。 - 付费后端代码/测试/文档全部保留;显式 pin(tavily/exa/ perplexity/brave)仍照常调用,是有意覆盖不是默认。 - 解冻开关 WEBSEARCH_ENABLE_PAID,仅精确值 "1" 才恢复旧链 (perplexity/exa/tavily/brave/searxng/duckduckgo),默认关; 开关经 worker search_env 透传进 MCP config env。 - HttpClient 超时/DNS 失败改报显式错误(timeout after Ns / URL error),绝不把超时写成“无结果”。 - SearXNG 默认参数核实:代码无 SEARXNG_URL 硬编码默认值; SEARXNG_ENGINES 默认 bing,360search 已在基线;WEBSEARCH_TIMEOUT 默认 20 秒。主机 env 实测仅 SEARXNG_URL=http://searx.diante.me 一行,故默认 pin 生效;文档 https 漂移改回 http(主机 nginx 仅听 80、无 443 vhost,https 在 TLS 层即失败)。 - 旧测试更新原因:test_websearch_mcp 原断言默认 auto 为 perplexity 优先,与新冻结语义直接冲突,故改为断言冻结后 的 searxng -> duckduckgo 回退 + 不泄漏 key;旧 perplexity -> exa 回退断言移至解冻开关测试(WEBSEARCH_ENABLE_PAID=1 下)。 未删断言、未加 skip、未放宽门禁。 - 文档另删 WEBSEARCH_PROVIDERS 说法:代码从未实现该变量。 - 未推、未建 PR、未合并、未部署、未改主机 env/nginx/filter/ 证书、未做付费后端 live 验证、未创建生产任务。
This commit is contained in:
1 parent
0602d06136
commit
3a56f83114
7 files changed
+492
-43
No files matched your search
+82
-27
@@ -20,6 +20,11 @@ pi 的工具),只复用它的模式:统一工具 + provider 抽象 +
|
||||
- `cloud/tests/test_code_search_ext.py`:任务级
|
||||
`allowed_hosts` 扩展的单测(指定 / 未指定 / 点边界 /
|
||||
空值与非法值 / Perplexity 超 20 域 / MCP 透传)。
|
||||
- `cloud/tests/test_websearch_paid_freeze.py`:付费冻结单测
|
||||
(默认 auto 无付费后端、SearXNG 失败不回落付费、显式
|
||||
pin 仍工作、解冻开关关时不变 / 开时恢复旧链、
|
||||
DuckDuckGo 兜底失败报错清晰、超时报错清晰;全部
|
||||
mock HTTP,不联网、不需要真实 key)。
|
||||
- `cloud/tests/test_js_heavy_extract.py`:JS 重型页面
|
||||
抽取评估的 mock 单测(直接抓取壳-only、provider 抽取
|
||||
补与不补、回退语义;live SearXNG 烟囱默认跳过)。
|
||||
@@ -39,7 +44,48 @@ pi 的工具),只复用它的模式:统一工具 + provider 抽象 +
|
||||
超长按头 75% + 尾 25% 截断并标 `[TRUNCATED]`;只接受 http(s),拒绝
|
||||
localhost 与字面私有 IP;抽取与直接抓取都失败时报错,不伪造页面。
|
||||
|
||||
## Provider 链(auto 顺序)
|
||||
## Provider 链:默认冻结,显式 pin 与显式解冻除外
|
||||
|
||||
冻结语义(本轨起,默认 `auto` 只走免费路线,顺序固定):
|
||||
|
||||
1. `searxng`:`GET $SEARXNG_URL/search?format=json`,需自托管
|
||||
实例且 JSON 格式已打开。默认参数:`engines=bing,360search`
|
||||
(2026-10-30 主机实测:默认 general 类别 20.0s 撞超时,
|
||||
pinned 两引擎 0.47s;`SEARXNG_ENGINES` 可覆盖,空值恢复
|
||||
不 pin)。`SEARXNG_URL` 在代码里无默认值:未配时该后端
|
||||
直接报 `searxng not configured (SEARXNG_URL unset)`,链
|
||||
只剩 DuckDuckGo;生产值 `http://searx.diante.me` 来自主
|
||||
机 env(见“生产接线”),不在代码里写死;主机 env 实测
|
||||
仅有 `SEARXNG_URL` 这一行、无 `SEARXNG_ENGINES` 行,
|
||||
所以默认 pin(bing,360search)生效。注意是 http 不是
|
||||
https:主机 nginx 只听 80、无 443 vhost,https 会在 TLS
|
||||
层证书主机名不匹配失败,根本到不了 SearXNG。
|
||||
2. `duckduckgo`:解析 `https://html.duckduckgo.com/html/`,
|
||||
免 key 兜底,布局漂移时只会返回空结果并继续走链,
|
||||
不会把工具打崩;但它在两种出口下都不可作可用兜底:
|
||||
本 VM 直连回 202 + 机器人挑战页、0 条,主机 Tinyproxy
|
||||
路径约 25 秒超时。兜底失败时记为 `no results` / 超时并
|
||||
如实报错,不伪造结果;因此 SearXNG 挂时冻结默认链可能
|
||||
以诚实失败告终,这是已知边界,不是静默降级。
|
||||
|
||||
冻结保证:即使环境里配了付费 key,默认 auto 也绝不调用
|
||||
Perplexity / Exa / Tavily / Brave、绝不产生付费花费;
|
||||
SearXNG 失败后默认也不回落到付费后端,只走 DuckDuckGo。
|
||||
付费后端的代码、测试、文档一律保留,未删除。
|
||||
|
||||
显式 pin 仍工作:`provider="tavily"` / `"exa"` /
|
||||
`"perplexity"` / `"brave"` 显式指定时照常调用,这是用户
|
||||
有意的覆盖,不是默认行为;pin 失败不回退(与冻结前一致)。
|
||||
|
||||
解冻开关:`WEBSEARCH_ENABLE_PAID=1`(且仅精确值 `1`,
|
||||
`true` / `yes` / `0` 都不算开,默认关)时,默认 auto
|
||||
恢复旧链,顺序为 Perplexity → Exa → Tavily → Brave →
|
||||
SearXNG → DuckDuckGo(旧链里只试有 key 的付费后端 +
|
||||
已配 URL 的 SearXNG + DuckDuckGo)。解冻是显式动作,
|
||||
会产生付费花费,绝不能被默认链静默触发;开关走 worker
|
||||
环境透传进 MCP config env(见“生产接线”)。
|
||||
|
||||
旧链各后端(解冻后 / 显式 pin 时)的请求形态不变:
|
||||
|
||||
1. `perplexity`:`POST https://api.perplexity.ai/chat/completions`,
|
||||
模型 `sonar`,`Authorization: Bearer $PERPLEXITY_API_KEY`。
|
||||
@@ -53,18 +99,16 @@ pi 的工具),只复用它的模式:统一工具 + provider 抽象 +
|
||||
结果,总结留给模型)。
|
||||
4. `brave`:`GET https://api.search.brave.com/res/v1/web/search`,
|
||||
头 `X-Subscription-Token`,`extra_snippets: true`。
|
||||
5. `searxng`:`GET $SEARXNG_URL/search?format=json`,需自托管
|
||||
实例且 JSON 格式已打开。
|
||||
6. `duckduckgo`:解析 `https://html.duckduckgo.com/html/`,
|
||||
免 key 兜底,布局漂移时只会返回空结果并继续走链,
|
||||
不会把工具打崩。
|
||||
|
||||
规则:auto 只试已配置的后端(有 key 的付费后端 + 已配
|
||||
URL 的 SearXNG + DuckDuckGo);任一后端 HTTP 失败或
|
||||
0 结果都继续下一个;全部失败返回各后端错误(不含 key)。
|
||||
`provider` pin 单个名字时不回退,失败即报错。
|
||||
可用 `WEBSEARCH_PROVIDERS`(逗号列表)改默认链顺序,
|
||||
`WEBSEARCH_TIMEOUT`(秒,默认 20)改单次超时。
|
||||
规则:任一后端 HTTP 失败或 0 结果都继续下一个;全部失败
|
||||
返回各后端错误(不含 key)。超时失败报
|
||||
`timeout after Ns waiting for HTTP response`(N 为
|
||||
`WEBSEARCH_TIMEOUT`,默认 20 秒),并记在该后端的
|
||||
attempted 条目里,绝不把超时写成“无结果”或空答案。
|
||||
`WEBSEARCH_TIMEOUT`(秒,默认 20)改单次超时。另注意:
|
||||
旧文档曾写可用 `WEBSEARCH_PROVIDERS` 改默认链顺序,
|
||||
代码里从未实现该变量,本轨已删此说法(不另新加该变量,
|
||||
保持改动最小)。
|
||||
|
||||
## 接入 codex(测试项目做法)
|
||||
|
||||
@@ -97,15 +141,19 @@ Docker 租户必须传容器内路径
|
||||
- egress 白名单:`deploy/egress/tinyproxy-filter` 已加
|
||||
Perplexity、Exa、Tavily、Brave、DuckDuckGo 与
|
||||
`searx.diante.me`(精确匹配,不用 diante.me 通配,否则
|
||||
任务经代理也能摸到 api.diante.me 控制面)。主机上须把
|
||||
任务经代理也能摸到 api.diante.me 控制面;filter 行即
|
||||
`^searx\.diante\.me$`,本轨不改这行)。主机上须把
|
||||
该文件拷到 `/etc/tinyproxy/tinyproxy-filter` 并重载
|
||||
tinyproxy,重载后先验中转域名还通,再验新域名。
|
||||
- 自托管 SearXNG(公开子域名路):主机 Docker 跑
|
||||
`searxng/searxng`,经 nginx 反代到 `searx.diante.me`
|
||||
(DNS A 记录 + certbot 证书),MCP 侧
|
||||
`SEARXNG_URL=https://searx.diante.me` 走 worker 进程
|
||||
`searxng/searxng`,经 nginx(仅听 80,无 443 vhost、无
|
||||
证书)反代到 `searx.diante.me`,MCP 侧
|
||||
`SEARXNG_URL=http://searx.diante.me` 走 worker 进程
|
||||
环境(`/etc/codex-app-server/env`)继承到 codex 再到
|
||||
MCP,不写进 config.toml。公开实例的代价是可能被外人
|
||||
MCP,不写进 config.toml。`WEBSEARCH_ENABLE_PAID` 同理
|
||||
走 worker 的 search_env 透传进 `[mcp_servers.websearch.env]`
|
||||
(worker 代码已含该键);主机侧改 env 文件后需重启相关
|
||||
unit 才生效——那是部署动作,不在本轨。公开实例的代价是可能被外人
|
||||
刷:SearXNG limiter 保持开启,JSON 格式是 MCP 要用的
|
||||
唯一格式。未加域名进白名单之前,Docker 任务的搜索
|
||||
会被代理 403 挡住(fail closed,工具报错,不会静默绕过)。
|
||||
@@ -267,6 +315,10 @@ filter 行须用 POSIX BASIC 正则(`^\(.*\.\)*域名$`)、
|
||||
- 本批只做代码 + 单测 + 测试租户目录的配置合并验证,
|
||||
未推分支、未建 PR、未合并、未部署、未改生产 worker
|
||||
与 egress 配置、未跑真实 codex 任务。
|
||||
- 付费冻结本轨同样只在本地提交里:生产默认链要等部署
|
||||
新代码后才变;解冻开关在主机上默认仍是关(除非主机
|
||||
env 显式设为 `WEBSEARCH_ENABLE_PAID=1`,且那需要
|
||||
主机侧改 env + 重启 unit,属部署动作)。
|
||||
- DuckDuckGo 的 HTML 解析是兜底手段,布局一变就可能
|
||||
0 结果;它存在的意义是“没 key 也能走通链路”,不是
|
||||
长期依赖的搜索后端。实测(2026-10-28,VM 出口):
|
||||
@@ -335,16 +387,19 @@ mock 补不了、也证明不了的部分:
|
||||
|
||||
### Live 对照(与 mock 分开写,未验证的不写成已验证)
|
||||
|
||||
- 无 key 的 SearXNG 路线:本轨在 VM 测试环境实测一次
|
||||
(`SEARXNG_URL=https://searx.diante.me`,`provider="searxng"`,
|
||||
产品自身 `search()` 代码):失败,`URLError: Remote end
|
||||
closed connection without response`。这是在本 VM 出口下
|
||||
的一次结果,不能据此断言 SearXNG 服务本身挂了(生产
|
||||
文档记载主机侧经代理 pinned 引擎曾 0.47s 通),live
|
||||
对照在本测试环境记为**未完成**,需在主机侧可达的环境
|
||||
重测;测试文件里的 live 烟囱测试默认跳过,只在
|
||||
`WEBSEARCH_EVAL_LIVE_SEARXNG=1` 且配了 `SEARXNG_URL`
|
||||
时才跑。
|
||||
- 无 key 的 SearXNG 路线:旧 VM 测试曾用
|
||||
`SEARXNG_URL=https://searx.diante.me` 失败(`URLError:
|
||||
Remote end closed connection without response`)——该失败
|
||||
现已归因:https 是错的,主机 nginx 只听 80、无 443
|
||||
vhost,https 在 TLS 层证书主机名不匹配失败,根本没到
|
||||
SearXNG,不能据此断言服务挂了。2026-11-01 探针实测:
|
||||
http 经同一 Tinyproxy 0.34 秒回 200、16 条结果,服务
|
||||
活着;主机 env 仅 `SEARXNG_URL=http://searx.diante.me`
|
||||
一行、无 `SEARXNG_ENGINES`,默认 pin 生效。容器内真实
|
||||
任务派发仍是 blocker(不由本轨创建生产任务),live
|
||||
对照在该项上仍记为**未完成**;测试文件里的 live 烟囱
|
||||
测试默认跳过,只在 `WEBSEARCH_EVAL_LIVE_SEARXNG=1`
|
||||
且配了 `SEARXNG_URL` 时才跑。
|
||||
- 付费后端 live 效果(Tavily extract / Exa contents /
|
||||
Perplexity / Brave 的真实 JS 渲染能力):无 key,本轨
|
||||
**一律未验证**,不编造效果,逐项列为 blocker(见回执),
|
||||
|
||||
Reference in new issue
Block a user