websearch: task-level allowed_hosts extension for scope=code + JS-heavy extract evaluation (mock-first)
- search(..., allowed_hosts=[...]) extends (never replaces) the 15-domain code list for that call only; scope=general + allowed_hosts raises, empty/illegal values raise before any HTTP, dot-boundary matching, extension values never in error strings or attempted entries. - Native-filter parity: Tavily/Exa get the effective list, Perplexity skips its native filter past its 20-domain cap (post-filter stays authoritative), Brave/SearXNG/DuckDuckGo rely on post-filtering. - MCP web_search gains allowed_hosts passthrough (only when supplied, so older search_fn fakes keep working). - JS-heavy evaluation documented in docs/web-search-mcp.md, mock-first: direct fetch is shell-only by construction; provider extract can supply rendered text in mock only; live SearXNG in this VM env failed (remote closed connection) and paid-backend live effects are unvalidated blockers, not claimed. - Tests: test_code_search_ext (9) + test_js_heavy_extract (5 + 1 live skipped); existing websearch/code-search suites still green. (cherry picked from commit 9c26bbe848438e1f34bcb000700aeb4c284f4f23)
This commit is contained in:
1 parent
125b436ab3
commit
e9fa43dd8b
5 files changed
+630
-12
No files matched your search
+120
-2
@@ -17,6 +17,12 @@ pi 的工具),只复用它的模式:统一工具 + provider 抽象 +
|
||||
mock HTTP,不联网、不需要真实 key。
|
||||
- `cloud/tests/test_code_search.py`:编程作用域与抽取回退链
|
||||
的单测,同样全部 mock HTTP、不联网、不需要真实 key。
|
||||
- `cloud/tests/test_code_search_ext.py`:任务级
|
||||
`allowed_hosts` 扩展的单测(指定 / 未指定 / 点边界 /
|
||||
空值与非法值 / Perplexity 超 20 域 / MCP 透传)。
|
||||
- `cloud/tests/test_js_heavy_extract.py`:JS 重型页面
|
||||
抽取评估的 mock 单测(直接抓取壳-only、provider 抽取
|
||||
补与不补、回退语义;live SearXNG 烟囱默认跳过)。
|
||||
|
||||
## 工具
|
||||
|
||||
@@ -163,6 +169,50 @@ changelog / release notes 只有落在上述域名时才在
|
||||
results`),链继续走下一个后端;全部后端都空才
|
||||
报错。
|
||||
|
||||
### 清单外项目官网域:任务级显式扩展 `allowed_hosts`
|
||||
|
||||
这不是“任意站搜索”,仍是显式白名单机制,只是白名单
|
||||
可以由调用方在当次调用里显式加域。选型是任务级参数
|
||||
`(a)`,不是配置级扩展清单 `(b)`,理由:改动最小
|
||||
(只动 `search()` 签名与 MCP 透传,不动 worker、
|
||||
unit、env、播种与主机配置);按调用隔离(某任务要
|
||||
某项目官网,不会把该域放宽给所有任务——配置级清单
|
||||
一旦写入就是全局放宽,安全姿态更差);用完即弃
|
||||
(不持久化,下次调用不带就不生效)。
|
||||
|
||||
用法:`search(..., scope="code",
|
||||
allowed_hosts=["docs.example-project.dev"])`,MCP
|
||||
侧 `web_search` 的 `allowed_hosts` 数组字段同名透传。
|
||||
规则(全部由 `cloud/websearch.py` 的
|
||||
`_normalize_allowed_hosts` 在发任何 HTTP 之前强制):
|
||||
|
||||
- 只在 `scope="code"` 下生效;`scope="general"` 下传
|
||||
`allowed_hosts` 直接报 `ValueError`,绝不静默忽略;
|
||||
不传时行为与本版之前完全一致(仍是 15 域)。
|
||||
- 生效清单 = 内置 15 域 + 本次显式 extras(去重、保序);
|
||||
返回结果的 `code_domains` 即为该生效清单。
|
||||
- 匹配仍是点边界:子域名算(`api.docs.example-project.dev`
|
||||
算),`evildocs.example-project.dev` 与
|
||||
`docs.example-project.dev.evil.com` 都不算。
|
||||
- 空值 / 非法值报错不静默退化:`[]`、裸字符串
|
||||
(可迭代但不是域名列表)、`*` 通配、带 scheme /
|
||||
端口 / 路径 / userinfo 的值、前导点、空标签、非字符串
|
||||
元素、以及全部等于内置域(即“扩展了个寂寞”)都报
|
||||
`ValueError`,且此时尚未发任何 HTTP。
|
||||
- 扩展值绝不写进日志:错误串与 `attempted` 条目里不
|
||||
出现扩展值;扩展值只出现在调用方自己那次结果的
|
||||
`code_domains` 回显里(调用方自己指定的值回显给
|
||||
自己,不是日志)。
|
||||
- 与原生后端限定方式的关系,和现有逻辑一致:原生
|
||||
支持的后端(Tavily / Exa 的 `include_domains`)直接传
|
||||
生效清单;Perplexity 的 `search_domain_filter` 上限
|
||||
20 个域,内置 15 + 扩展超 20 时跳过其原生过滤、只靠
|
||||
事后过滤(事后过滤永远是权威,语义不变);原生不
|
||||
支持的后端(Brave、SearXNG、DuckDuckGo)本来就不传
|
||||
参数,只靠 `search()` 返回后的统一事后过滤。
|
||||
- `web_fetch` 不受 `allowed_hosts` 影响:fetch 本来就是
|
||||
按指定 URL 抓取,没有域名白名单可扩展。
|
||||
|
||||
### 抓取回退链与 403 语义(web_fetch)
|
||||
|
||||
`web_fetch` 实际调 `fetch_best`,顺序固定:
|
||||
@@ -230,5 +280,73 @@ filter 行须用 POSIX BASIC 正则(`^\(.*\.\)*域名$`)、
|
||||
主机 unit / filter / env 接线、GitHub API token
|
||||
(本设计走网页搜索后端限定域名,不调 GitHub API,
|
||||
所以不需要 token;真要 GitHub 代码内容搜索 API 是
|
||||
另一功能,不在本轨)、清单外项目官网域、老租户
|
||||
脚本升级。
|
||||
另一功能,不在本轨)、老租户脚本升级。清单外项目
|
||||
官网域本轨已做(见上节 `allowed_hosts`,本地提交、
|
||||
未推未部署),仍缺真实 key 的 live 验证。
|
||||
|
||||
## JS 重型站点抽取效果评估(本轨新增,mock 为主)
|
||||
|
||||
评估对象是已有抽取链,不改默认抽取顺序、不因评估换
|
||||
后端:`fetch_best` 固定为 Tavily extract → Exa contents
|
||||
→ 直接抓取。结论只写在这里,不改代码行为。
|
||||
测试文件:`cloud/tests/test_js_heavy_extract.py`(全部
|
||||
mock HTTP,只有一个显式开关的 live 烟囱测试,默认跳过)。
|
||||
|
||||
### 阶段 1:直接抓取能拿到什么、拿不到什么
|
||||
|
||||
直接抓取是标准库 HTML 解析:`<script>` / `<style>` /
|
||||
`<noscript>` 内容一律剥离,只留静态文本与绝对 http(s)
|
||||
链接(相对链接不进 `links` 列表)。所以:
|
||||
|
||||
- 能拿到:静态壳里的标题、占位文本(如 "Loading…")、
|
||||
静态正文、绝对链接;首屏静态内容本身就是正文的页面
|
||||
不受影响。
|
||||
- 拿不到:正文靠 JS 渲染的段落(mock 壳里
|
||||
`RENDERED-BODY-PARAGRAPH` 只存在于 `<script>` 数据块
|
||||
中,直接抓取结果里没有它,也不把数据块原文泄漏进
|
||||
文本);分页靠 JS 加载的后续项(mock 里第 2 页
|
||||
`ITEM-PAGE-2` 只经 `/api/items?page=2` 到达,直接抓取
|
||||
只有第 1 页静态项,也拿不到该 API 地址本身)。
|
||||
- 为什么:解析器没有 JS 引擎,也从不执行脚本或追
|
||||
JS 发起的请求;这是结构性缺失,不是解析器 bug,
|
||||
调解析参数补不回来。直接抓取在 JS 重型站上只能
|
||||
给出“壳 + 首屏静态部分”,不能谎称是全文。
|
||||
|
||||
### 阶段 2:provider 抽取在 mock 层面能补什么、补不了什么
|
||||
|
||||
mock 层面(伪造的 Tavily extract 响应返回完整渲染文本)
|
||||
证明链路本身能补:`fetch_via_provider` 返回
|
||||
`source=tavily-extract` 的完整文本;抽取与直接都失败时
|
||||
`fetch_best` 回退到直接抓取的壳(`source=direct-fetch`),
|
||||
仍拿不到渲染正文——回退保证的是“不断链”,不是“补全文”。
|
||||
|
||||
mock 补不了、也证明不了的部分:
|
||||
|
||||
- 真实 provider 是否真的渲染该站的 JS(取决于各家
|
||||
extract 实现是否跑无头浏览器、等多久、反爬策略),
|
||||
mock 响应是“我假设它渲染了”,不是实测证据。
|
||||
- JS 分页 / 交互(点“加载更多”、登录后内容、滚动加载)
|
||||
provider 抽取一般只取单 URL 的渲染结果,不会替调用方
|
||||
翻页或交互;多页内容仍需逐 URL fetch。
|
||||
- 抽取返回空时是失败,不是空页:`fetch_via_provider`
|
||||
空结果报 `WebSearchError`,不伪造页面;但 `fetch_best`
|
||||
随后回退直接抓取会返回壳文本——调用方须以 `source`
|
||||
字段分辨“渲染全文”与“壳”,不能只看 `text` 非空就当全文。
|
||||
|
||||
### 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`
|
||||
时才跑。
|
||||
- 付费后端 live 效果(Tavily extract / Exa contents /
|
||||
Perplexity / Brave 的真实 JS 渲染能力):无 key,本轨
|
||||
**一律未验证**,不编造效果,逐项列为 blocker(见回执),
|
||||
在拿到 key 之前,文档里关于 provider 渲染能力的任何
|
||||
表述都只是 mock 层面的链路结论。
|
||||
Reference in new issue
Block a user