Files
Cyrene_v2/cmd/cyrene/wire_io_tools.go
T
AskaEth 8e6c77ad10
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
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 条全红
2026-10-05 20:29:21 +08:00

67 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}