feat(search): web-search 接线本机 SearXNG 兜底 + 显式开关(她不再拿一把用不了的锤子)
主人报的故障:她调 web-search 必失败(no search endpoint configured)。根因两条:组合根从未注入 WithSearchEndpoint + 配置层根本没有搜索配置项。 - internal/config/websearch.go:WEB_SEARCH_ENDPOINT(默认本机兜底 http://127.0.0.1:38471)+ WEB_SEARCH_TIMEOUT;ResolveEndpoint() 三态裁决(可用/显式关闭/非法),非法值启动即报错 - cmd/cyrene/wire_io_tools.go:组合根唯一的 io 选项装配点;端点不可用 ⇒ WithoutSearch() + 带 source 的 WARN(只记键名与原因,不打印 URL 值) - plugins/tools/io:新增 WithoutSearch()(**根本不进注册面** ⇒ 模型看不见)+ WithSearchTimeout();协议解析逻辑一字未动 - 隐蔽坑(额外查出并修):SearXNG 对 /?q= 回 308 → /search,重定向那一跳走**共享 SSRF 白名单** ⇒ 只注入端点照样被拦。改为**只给 web-search 一件**加宽自己的客户端与白名单,共享白名单与 http/web-fetch 客户端一字未动(有测试钉住它仍拒绝本机网关地址) - .env.example 新增 2 键 + 注释说明「空串/off/none = 不注册」 - 真机 e2e 两次绿(SearXNG 返回 9 条);变异:不注入端点 / 关闭态仍注册 / 默认值改错 / 不并入端点主机 / 重定向用共享白名单 ⇒ 5 条全红
This commit is contained in:
1 parent
8e56163418
commit
8e6c77ad10
11 files changed
+1137
-20
No files matched your search
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
// 本文件装配 io 工具集(plugins/tools/io)的选项:知识检索器、日志,以及
|
||||
// **web-search 的端点与超时**(切片 WS1-A)。
|
||||
//
|
||||
// 为什么单独成文件而不是塞在 wire_runtime.go 里:那条装配线本就接近文件行数上限,
|
||||
// 而本片新增的裁决(端点关闭/非法 ⇒ 工具不进注册面)需要一个能单测的落点。
|
||||
//
|
||||
// 三条口径:
|
||||
//
|
||||
// 1. **端点默认开箱即用**:`WEB_SEARCH_ENDPOINT` 未配置时取 config 标签默认值
|
||||
// (本机兜底 http://127.0.0.1:38471,配套服务见 scripts/searxng/settings.yml)。
|
||||
// 2. **拿不到可用端点就不注册工具**(诚意要求,主人 2026-10-05 报障的核心诉求):
|
||||
// 端点显式关闭(空串 / off / none)或不是 http(s) 绝对地址时,web-search
|
||||
// **不进注册面** —— 模型根本看不见它,而不是看得见、一调就报错。
|
||||
// 3. **超时不另造默认值**:`WEB_SEARCH_TIMEOUT` 未配置(0)时沿用 io 包既有超时;
|
||||
// 负值是笔误,按未配置处理并记一条带 env 键名的 WARN(不静默)。
|
||||
|
||||
import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||||
iotools "git.yeij.top/AskaEth/Cyrene_v2/plugins/tools/io"
|
||||
)
|
||||
|
||||
// web-search 的两个 env 键名:必须与 `.env.example` 逐字一致(AGENTS §10 的 E6 条目),
|
||||
// 由 wire_web_search_test.go 逐字核对。
|
||||
const (
|
||||
webSearchEndpointEnv = "WEB_SEARCH_ENDPOINT"
|
||||
webSearchTimeoutEnv = "WEB_SEARCH_TIMEOUT"
|
||||
)
|
||||
|
||||
// ioLogSource 是本文件日志的来源标签(项目规范 §10:禁止无来源裸日志)。
|
||||
const ioLogSource = "cmd/cyrene/wire_io_tools"
|
||||
|
||||
// ioToolOptions 组装 io 工具集的全部选项。wire_runtime 把它原样交给 iotools.New,
|
||||
// 因此「注册面里有哪些 io 工具」只由这一处决定。
|
||||
func (a *app) ioToolOptions() []iotools.Option {
|
||||
opts := []iotools.Option{
|
||||
iotools.WithKnowledgeRetriever(a.retriever),
|
||||
iotools.WithLogger(a.log),
|
||||
}
|
||||
return append(opts, a.webSearchOptions()...)
|
||||
}
|
||||
|
||||
// webSearchOptions 把 web-search 的配置翻译成 io 选项。端点裁决见
|
||||
// config.WebSearchConfig.ResolveEndpoint(关闭 / 非法 / 可用三种去处都在那里定死)。
|
||||
func (a *app) webSearchOptions() []iotools.Option {
|
||||
log := logger.WithSource(a.log, ioLogSource)
|
||||
decision := a.cfg.WebSearch.ResolveEndpoint()
|
||||
if !decision.Enabled {
|
||||
// 端点的值**绝不写进日志**(URL 里可能带凭据),只记键名与原因。
|
||||
log.Warn("web-search tool not registered",
|
||||
"env", webSearchEndpointEnv, "reason", decision.Reason,
|
||||
"effect", "the model will not see a web-search tool at all")
|
||||
return []iotools.Option{iotools.WithoutSearch()}
|
||||
}
|
||||
|
||||
opts := []iotools.Option{iotools.WithSearchEndpoint(decision.URL)}
|
||||
switch {
|
||||
case a.cfg.WebSearch.Timeout > 0:
|
||||
opts = append(opts, iotools.WithSearchTimeout(a.cfg.WebSearch.Timeout))
|
||||
case a.cfg.WebSearch.Timeout < 0:
|
||||
log.Warn("web-search timeout is negative, treated as unset",
|
||||
"env", webSearchTimeoutEnv, "effect", "io package default timeout applies")
|
||||
}
|
||||
return opts
|
||||
}
|
||||
Reference in new issue
Block a user