t181 daemon 端:萃取預設走 Workers AI(免金鑰)+托盤可切換 AI 來源
【leo 08-04 列為最優先】「daemon 的 AI 改用 workers AI」 ——「這是我的用戶**最大障礙**,造成首輪測試用戶的**好評或惡評**」。 【新增】collector/extract_workersai.go:打自己雲端實例的 /portal/daemon/extract, 那端用 env.AI binding ⇒ **完全不需要任何金鑰**。與 gemma 路架構完全對稱 (leo 的判斷:「不論用 Claude/Gemini/Workers AI…應該是同一件事」——是, 只有「送到哪」不同,讀檔/轉檔/淨化/落卡全共用)。 舊實例回 404 時講人話:「你的知識庫還是舊版 ⇒ 請到 portal 按『立即更新』」。 【預設改為一律 Workers AI】leo 特別交代: 「default 用 Workers AI,你要用 Gemini 要**特別去選取**,**不管你現在是否有填金鑰**」 「只要更新版本,就已經 default workers AI 了,除非去一個地方切換」 「不然我會有很多質疑,**花在解釋為什麼 Gemini 不管用上**」 ⇒ 判準是新欄位 ExtractorExplicit(使用者主動選過),**不是「有沒有金鑰」**。 舊 config 沒這欄=false ⇒ 更新版本後自動走 Workers AI;金鑰留著不動,改選 Gemini 立刻可用。 【托盤=唯一切換處】「AI 設定…」改成引擎選擇:雲端 AI(預設)/Gemini(選配)。 選 Gemini 才顯示金鑰欄,並附「Billing Tier: Unavailable ⇒ 換 Google 帳號」的排難提示 (今天 oscar 撞的那題)。Gemini 不推廣(leo:「特定人告訴他怎麼做就好」),文案只說明不慫恿。 【標籤】leo:「用 workers AI 就**不顯示**,用 Gemini 會顯示 Gemini」 ⇒ 預設路徑不佔版面;只有主動選了別的才標出來(延續 t178「標籤必須與實際行為一致」)。 【測試】新增 TestT181DefaultsToWorkersAI 六則,含最關鍵的 **「有金鑰但沒主動選 ⇒ 仍走 workers-ai」**;既有測試補 ExtractorExplicit 以隔離變因 (它們測的是萃取管線,不是預設邏輯)。兩模組全綠。 ⚠️ 未送達:要打包 v0.15.5 並部署雲端端點才會到用戶手上。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+55
-6
@@ -63,7 +63,7 @@ func TestDirectExtractorModeE2E(t *testing.T) {
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: srv.URL, Namespace: "demo", APIKey: "demo",
|
||||
Library: "kb", Extractor: "gemma", GeminiAPIKey: "k-test",
|
||||
Library: "kb", Extractor: "gemma", ExtractorExplicit: true, GeminiAPIKey: "k-test",
|
||||
CardIngestWF: "rag_ingest_card", MaxRemoved: DefaultMaxRemovedRatio,
|
||||
}
|
||||
results, exit, _ := RunDirectOnce(cfg, false)
|
||||
@@ -126,7 +126,7 @@ func TestDirectExtractorRemovedClearsLocalCard(t *testing.T) {
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: srv.URL, Namespace: "demo", APIKey: "demo",
|
||||
Library: "kb", Extractor: "gemma", GeminiAPIKey: "k-test",
|
||||
Library: "kb", Extractor: "gemma", ExtractorExplicit: true, GeminiAPIKey: "k-test",
|
||||
CardIngestWF: "rag_ingest_card", RemovedWF: "rag_takedown_direct",
|
||||
// 單檔刪除=removed ratio 100%,預設 0.4 防呆會壓下事件;本測試聚焦下架路,放寬到 1.0
|
||||
//(1 > 1.0×1 為 false → 事件放行)。
|
||||
@@ -179,7 +179,7 @@ func TestDirectExtractorRemovedNoLocalCardOK(t *testing.T) {
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: srv.URL, Namespace: "demo", APIKey: "demo",
|
||||
Extractor: "gemma", GeminiAPIKey: "k-test",
|
||||
Extractor: "gemma", ExtractorExplicit: true, GeminiAPIKey: "k-test",
|
||||
CardIngestWF: "rag_ingest_card", RemovedWF: "rag_takedown_direct",
|
||||
MaxRemoved: 1.0,
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func TestDirectExtractorFailKeepsRetry(t *testing.T) {
|
||||
WatchFolders: []string{root},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: "https://x.example", Namespace: "demo", APIKey: "demo",
|
||||
Extractor: "gemma", GeminiAPIKey: "k-test", MaxRemoved: DefaultMaxRemovedRatio,
|
||||
Extractor: "gemma", ExtractorExplicit: true, GeminiAPIKey: "k-test", MaxRemoved: DefaultMaxRemovedRatio,
|
||||
}
|
||||
results, exit, _ := RunDirectOnce(cfg, false)
|
||||
if exit != 1 || len(results) != 1 || results[0].Status != "failed" {
|
||||
@@ -253,7 +253,7 @@ func TestMultiAccountInheritsExtractor(t *testing.T) {
|
||||
cfg := &DirectConfig{
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
Library: "kb",
|
||||
Extractor: "gemma",
|
||||
Extractor: "gemma", ExtractorExplicit: true,
|
||||
GeminiAPIKey: "k-test",
|
||||
CardIngestWF: "rag_ingest_card",
|
||||
IngestWF: "rag_ingest_direct",
|
||||
@@ -312,7 +312,56 @@ func TestExtractorEmptyBlocksNonTextDirect(t *testing.T) {
|
||||
if len(results) != 1 || results[0].Status != "failed" {
|
||||
t.Fatalf("results=%+v", results)
|
||||
}
|
||||
if !strings.Contains(results[0].Error, "萃取器未設定") {
|
||||
// t181:預設改走 workers-ai(免金鑰)後,「沒設 extractor」不再是失敗原因
|
||||
// ⇒ 這隻壞 PDF 會停在**轉檔層**(更早、更正確的把關點)。
|
||||
// 本測真正要守的契約沒變、也仍然綠:**PDF 不得被直送上雲**(上面的 serverCalled)。
|
||||
if !strings.Contains(results[0].Error, "轉檔失敗") {
|
||||
t.Errorf("錯誤訊息不符:%q", results[0].Error)
|
||||
}
|
||||
}
|
||||
|
||||
// ── t181:預設一律走 Workers AI(免金鑰)──────────────────────────────────────
|
||||
//
|
||||
// leo 2026-08-04 特別交代(這是本測存在的理由):
|
||||
// 「default 用 Workers AI,你要用 Gemini 要**特別去選取**,**不管你現在是否有填金鑰**」
|
||||
// 「只要更新版本,就已經 default workers AI 了,除非去一個地方切換你指定的 AI 來源」
|
||||
// 「不然我會有很多質疑,**花在解釋為什麼 Gemini 不管用上**」
|
||||
// ⇒ 判準是 ExtractorExplicit(使用者主動選過),**不是**「有沒有金鑰」。
|
||||
func TestT181DefaultsToWorkersAI(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
extractor string
|
||||
explicit bool
|
||||
key string
|
||||
want string
|
||||
}{
|
||||
{"新用戶(什麼都沒設)", "", false, "", "workers-ai"},
|
||||
{"舊 config 有 gemma 但沒主動選", "gemma", false, "", "workers-ai"},
|
||||
// 🔴 最關鍵的一則:**有金鑰也照樣先走 Workers AI**
|
||||
{"有金鑰但沒主動選=仍走雲端 AI", "gemma", false, "AIza-xxx", "workers-ai"},
|
||||
{"殘留 claude 且沒主動選", "claude", false, "", "workers-ai"},
|
||||
// 主動選過才尊重他的選擇
|
||||
{"主動選了 Gemini", "gemma", true, "AIza-xxx", "gemma"},
|
||||
{"主動選了雲端 AI", "workers-ai", true, "", "workers-ai"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
cfg := &DirectConfig{
|
||||
WatchFolders: []string{t.TempDir()},
|
||||
Manifest: filepath.Join(t.TempDir(), "m.json"),
|
||||
CypherURL: "https://unused.example",
|
||||
Namespace: "demo",
|
||||
APIKey: "demo",
|
||||
Extractor: c.extractor,
|
||||
ExtractorExplicit: c.explicit,
|
||||
GeminiAPIKey: c.key,
|
||||
MaxRemoved: DefaultMaxRemovedRatio,
|
||||
}
|
||||
RunDirectOnce(cfg, false) // 空資料夾=零事件,只看預設邏輯把 Extractor 定成什麼
|
||||
if cfg.Extractor != c.want {
|
||||
t.Errorf("Extractor=%q want=%q(explicit=%v key=%q)",
|
||||
cfg.Extractor, c.want, c.explicit, c.key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user