refactor(search): provider 配置面去 env 化 —— 加新家不再需要动 .env
动因:`.env` 已冻结(基础键不加/不改/不删),而「加一家 provider」原本会逼出 新的 env 键(一家一个 struct 字段 + searchProviderVector 逐家读 + 逐家写死的 switch)⇒ 想接腾讯云元宝/秘塔就只能破红线。面板本来就该是插件配置的一等来源 ($DATA_DIR/config/plugins/web-search.json),故让**文件成为权威**。 - plugins/tools/io/search/registry.go:接入位表加 defaultBaseURL(「没配 baseUrl 回落代码默认值」从此只有一个出处);新增 Descriptor(s)/DefaultBaseURL。 - cmd/cyrene/websearch_providers.go(新):按接入位表逐家算出生效投影 —— 加一家时这里**零改动**,这正是本片的核心;顺手把 wire_io_tools.go 从 430 行 降到 342(它逼近 §10 的 500 行上限)。 - cmd/cyrene/wire_plugin_actions.go / websearch_action_data.go / websearch_selection.go:基地址判定统一走「文件值 → 接入位默认值」的进位链; checkPrimaryUsable 判据不放松(真没配齐仍 400 primary_not_configured)。 🔴 **一处对我任务书的纠正(子代理提出、我采纳)**:我要求「既有 6 家在文件里 没有该条目时仍回落 .env」。仓库现状**没有**这层回落,且有回归用例把它钉死 (TestWireIOTools_PanelConfigClearedProviderDoesNotReviveFromEnv:「文件里没有 的家不得从 .env 复活」)。本片按「不改变既有行为」实现 —— 我的要求才是错的: 文件一旦存在就是权威,env 只在文件缺失/为空时播种。 验收(主会话复跑 + 亲自变异):go build ./... + 两包 vet 绿;search 包与 cmd/cyrene 全绿;我用 -overlay 让文件里的 apiKey 被忽略 ⇒ 4 条用例变红 (含「无 env 字段的家只认文件也能进注册表」这条核心判据)。 ⚠️ 新家仍未接:腾讯云元宝(tencent_wsa)/ 秘塔(metaso)两片随后串行落地。
This commit is contained in:
1 parent
e5e5207c94
commit
bc1dee3f56
8 files changed
+825
-175
No files matched your search
@@ -103,6 +103,69 @@ func TestRegistry_EmptyAndNilSafe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistry_DefaultBaseURLMatchesEnvDefaults 钉住接入位表里的**默认基地址**逐字等于
|
||||
// `.env` 的标签默认值(`internal/config/websearch.go` 的 `*BaseURL` 那一组,`.env` 冻结的
|
||||
// 基础键)。
|
||||
//
|
||||
// 为什么值得一条逐字表:接入位表是本片新增的「这家没配 baseUrl ⇒ 回落哪个地址」的
|
||||
// **唯一出处**,而 `.env` 侧那几个键仍然存在、仍然参与播种。两处一旦漂移,同一家 provider
|
||||
// 会因为「值从哪来」指向两个不同的上游 —— 只有逐字对得上的表拦得住这种不一致。
|
||||
func TestRegistry_DefaultBaseURLMatchesEnvDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
want := map[string]string{
|
||||
NameZhipu: "https://open.bigmodel.cn",
|
||||
NameAliyunIQS: "https://cloud-iqs.aliyuncs.com",
|
||||
NameBocha: "https://api.bocha.cn",
|
||||
NameVolcengine: "https://open.feedcoopapi.com",
|
||||
NameBaiduQianfan: "https://qianfan.baidubce.com",
|
||||
}
|
||||
for name, base := range want {
|
||||
if got := DefaultBaseURL(name); got != base {
|
||||
t.Errorf("DefaultBaseURL(%q) = %q, want %q(必须与 .env 的标签默认值逐字一致)", name, got, base)
|
||||
}
|
||||
}
|
||||
// searxng 没有默认基地址:它的地址为空本身就是「关掉这家」(组合根按 ResolveEndpoint
|
||||
// 裁决整把工具的存亡),回落一个本机地址会让「清空地址」变成假话。
|
||||
if got := DefaultBaseURL(NameSearXNG); got != "" {
|
||||
t.Errorf("DefaultBaseURL(%q) = %q, want 空串", NameSearXNG, got)
|
||||
}
|
||||
// 未登记的接入位:不猜地址。
|
||||
if got := DefaultBaseURL("tavily"); got != "" {
|
||||
t.Errorf("未登记的接入位不该有默认地址, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistry_DescriptorsMatchDefinitions 钉住「只读描述」与接入位表同源:
|
||||
// 组合根按它逐家投影配置,两处一旦不同步,加一家 provider 就会「漏在注册面外」。
|
||||
func TestRegistry_DescriptorsMatchDefinitions(t *testing.T) {
|
||||
t.Parallel()
|
||||
desc := Descriptors()
|
||||
if len(desc) != len(KnownNames()) {
|
||||
t.Fatalf("Descriptors() 有 %d 家,KnownNames() 有 %d 家(两者必须同一张表)",
|
||||
len(desc), len(KnownNames()))
|
||||
}
|
||||
byName := make(map[string]Descriptor, len(desc))
|
||||
for _, d := range desc {
|
||||
if _, dup := byName[d.Name]; dup {
|
||||
t.Fatalf("Descriptors() 里 %q 出现了两次", d.Name)
|
||||
}
|
||||
byName[d.Name] = d
|
||||
}
|
||||
for _, name := range KnownNames() {
|
||||
d, ok := byName[name]
|
||||
if !ok {
|
||||
t.Fatalf("KnownNames() 里的 %q 不在 Descriptors() 里", name)
|
||||
}
|
||||
if d.NeedKey != (name != NameSearXNG) {
|
||||
t.Errorf("Descriptor(%q).NeedKey = %v(只有 searxng 不需要 key)", name, d.NeedKey)
|
||||
}
|
||||
if d.DefaultBaseURL != DefaultBaseURL(name) {
|
||||
t.Errorf("Descriptor(%q).DefaultBaseURL = %q, DefaultBaseURL(%q) = %q(必须同源)",
|
||||
name, d.DefaultBaseURL, name, DefaultBaseURL(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestProviders_ErrorClassification 是验收 ⑤:鉴权 / 限流 / 额度 / 上游 各一条。
|
||||
// 用同一家 provider + 不同应答,确认分类**如实区分**(错误分类决定降级链是否前进)。
|
||||
func TestProviders_ErrorClassification(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user