diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 862463c..27bf9a2 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.465 修正 embedding fallback-disabled 控制流:`allow_111_fallback=False` 時若 resolver 回 111,不再直接退出或只試單台 GCP-B,會強制改試尚未嘗試的 GCP-A/GCP-B;背景 embedding 仍不落 111。 - V10.464 補 rescore audit 精準 SKU pilot:`audit_competitor_match_attempt_rescore.py --sku` 可只掃指定 SKU,再搭配 `--apply-accepted` 只把通過新版 matcher 的目標 SKU 追加到 `rescore_accepted_current` 人工覆核隊列,不寫正式價格表。 - V10.463 補 DR.WU / 達爾膚品牌 alias:同規格 `DR.WU 達爾膚` 與 `DR.WU` 候選不再被當成 brandless identity review,會以既有 exact_identity / total_price / price_alert_exact 閘門處理;未調整 `MIN_MATCH_SCORE`,保留 variant / hard veto 保護。 - V10.462 進一步收斂 PChome 補抓 UI 語意:Dashboard 區塊標題改為「PChome 補抓產線」,AI 中樞按鈕、前端確認與 API 訊息改為「補抓未搜尋 / 未搜尋補抓」,避免操作員把尚未搜尋的工作誤判成已有候選待審。 diff --git a/config.py b/config.py index 8f53c9c..299d911 100644 --- a/config.py +++ b/config.py @@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.464" +SYSTEM_VERSION = "V10.465" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 2233ef6..d431e93 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-05-24 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 備援預設關閉 -> **適用版本**: V10.464 +> **適用版本**: V10.465 --- @@ -21,6 +21,7 @@ - Code Review Hermes LLM scan 啟用時才使用本地模型矩陣,且預設只跑 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b`;`CODE_REVIEW_ALLOW_111_FALLBACK=true` 時才允許落到 111,並由 `OllamaService` 降級到 `llama3.2:latest`。不啟用 Gemini 備援,本地掃描失敗時只回空 findings 並交由 OpenClaw 本地矩陣續跑。 - Code Review OpenClaw assessment 預設只跑 GCP-A → GCP-B:GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`;primary timeout 預設 `15s`、secondary timeout 預設 `60s`,讓 A 掛時快速讓位給 B,且 B 有足夠時間完成審查 prompt。111 是最後救急節點,但部署後重分析預設不打 111;只有 `CODE_REVIEW_ALLOW_111_FALLBACK=true` 才允許 111 接手,並降級到 `llama3.2:latest`。Code Review 的 Ollama `keep_alive` 預設為 `5m`,不得再用 `24h` 長駐 runner 壓住 GCP-B/111。GCP-A/GCP-B 都失敗且 Claude/Gemini 未顯式開啟時,必須回 deterministic 本地降級摘要,不呼叫 Gemini、不落 111、不走其他雲端模型。 - Embedding / semantic RAG 背景任務預設只跑 GCP-A → GCP-B:`OpenClawLearningService` embedding worker 與 `RAGService` 查詢 embedding 呼叫 `OllamaService.generate_embedding(..., allow_111_fallback=False)`;111 只可作人工明確指定的救急路徑,不承接 `bge-m3` 背景批次。`OLLAMA_EMBED_TIMEOUT` / `OLLAMA_EMBED_MAX_TIMEOUT` 預設 `15s`、`OLLAMA_EMBED_KEEP_ALIVE=1m`、`OLLAMA_EMBED_MAX_CHARS=4000`,避免 embedding worker 長時間卡住 GCP-B 或 111。 +- `allow_111_fallback=False` 時,若 resolver 因 unhealthy cache 回傳 111,不得直接結束 embedding;必須強制改試尚未嘗試的 GCP-A / GCP-B,避免正式 log 出現 `tried=[]` 或只試單台 GCP-B。 - BGE-M3 一致性檢查是監測任務,不是 fallback 壓測;預設只比對 GCP-A / GCP-B。111 Mac fallback 只有 `EMBED_CONSISTENCY_INCLUDE_111=true` 時才納入,避免每週背景檢查把 `bge-m3` 載入 111。 - OpenClaw Telegram Q&A 主路徑也不得綁單一 host:`_call_qwen3_qa()` 必須透過 `OllamaService` 跑 GCP-A → GCP-B → 111,並把實際落點寫入 `ai_calls.provider`。 - OpenClaw Telegram 圖片商品辨識也必須 Ollama-first:`_identify_product_name_with_ollama_vision()` 透過 `OllamaService` 嘗試 GCP-A → GCP-B → 111;Gemini 只允許以 `openclaw_bot_image_gemini` caller 作為失敗後備援。 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index 8a4b16b..bd41a23 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -65,6 +65,10 @@ - 告警不得再輸出空泛「預期效益」;必須帶資料品質、證據來源、HITL 邊界與 trace id。 - Agent 建議只能輔助排序與分析,不得繞過 matcher / feeder / review service 寫正式價格。 +## 3.1 Ollama / Embedding 健康 + +- 2026-05-25 08:48 CST 起,`OllamaService.generate_embedding(..., allow_111_fallback=False)` 若 resolver 回 111,會強制改試尚未嘗試的 GCP-A/GCP-B,不再讓背景 embedding 在 111 disabled 情境直接退出或只試單台 GCP-B;111 仍不承接背景 `bge-m3`。 + ## 4. 業績分析資料與圖表修復 - 修正即時業績匯入 `snapshot_date text = date` 類型錯誤。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index 2832ee5..e5f01a3 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-05-24:PChome 近門檻身份回收第二輪 +- **V10.465 Embedding GCP fallback 修正**: `OllamaService.generate_embedding(..., allow_111_fallback=False)` 若 resolver 因 unhealthy cache 回 111,會強制改試尚未嘗試的 GCP-A/GCP-B,不再直接 `break` 造成 `tried=[]` 或只試單台 GCP-B;背景 embedding 仍不允許落 111。 - **V10.464 Rescore SKU pilot 篩選**: `audit_competitor_match_attempt_rescore.py` 與 `fetch_match_attempt_rescore_rows()` 增加 `--sku` / `skus` 篩選,可針對 DR.WU 這類明確 cohort 做 3-10 筆精準 materialize,不必為了 pilot 掃整批 `true_low_confidence`。 - **V10.463 DR.WU / 達爾膚品牌 alias**: `marketplace_product_matcher` 補 `DR.WU / DR WU / DRWU / 達爾膚` 正規化,讓正式樣本中同規格玻尿酸保濕精華乳、杏仁酸亮白煥膚精華不再因品牌 token 不同被降成 brandless identity review;測試鎖住 exact / total_price / price_alert_exact。 - **V10.462 PChome 補抓 UI 語意收斂**: Dashboard 補抓區塊標題、AI 中樞按鈕、前端 confirm 與 API 回覆全數改用「PChome 補抓產線 / 補抓未搜尋 / 未搜尋補抓」,避免「待比對」殘留在操作入口,和低信心待人工覆核混淆。 diff --git a/services/ollama_service.py b/services/ollama_service.py index 8c1aa7a..c0bd1d7 100644 --- a/services/ollama_service.py +++ b/services/ollama_service.py @@ -1056,8 +1056,15 @@ class OllamaService: logger.warning("[Embed] 111 fallback disabled; ignoring EMBEDDING_HOST=%s", configured_host) target_host = resolve_ollama_host().rstrip("/") if not allow_111_fallback and _is_111_fallback_host(target_host): - logger.warning("[Embed] 111 fallback disabled; no approved GCP embedding host available") - break + next_host = next((candidate for candidate in allowed_hosts if candidate not in attempted_hosts), None) + if not next_host: + logger.warning("[Embed] 111 fallback disabled; no approved GCP embedding host available") + break + logger.warning( + "[Embed] 111 fallback disabled; forcing approved GCP embedding host=%s", + next_host, + ) + target_host = next_host if target_host in attempted_hosts: next_host = None if target_host in allowed_hosts: diff --git a/tests/test_ollama_retry_chain.py b/tests/test_ollama_retry_chain.py index 3e9b22b..d57028c 100644 --- a/tests/test_ollama_retry_chain.py +++ b/tests/test_ollama_retry_chain.py @@ -378,7 +378,32 @@ def test_embedding_can_disable_111_fallback_for_background_rag_work(): posted_hosts = [call.args[0].split('/api/embed')[0] for call in mock_post.call_args_list] assert vec == [] - assert posted_hosts == [oss.OLLAMA_HOST_SECONDARY] + assert posted_hosts == [oss.OLLAMA_HOST_SECONDARY, oss.OLLAMA_HOST_PRIMARY] + assert oss.OLLAMA_HOST_FALLBACK not in posted_hosts + + +def test_embedding_fallback_disabled_uses_gcp_chain_when_resolver_returns_111(): + """resolver 若因 unhealthy cache 回 111,背景 embedding 仍要嘗試 GCP-A/GCP-B。""" + import requests + from services import ollama_service as oss + from services.ollama_service import OllamaService + + svc = OllamaService() + + with patch( + 'services.ollama_service.resolve_ollama_host', + side_effect=[oss.OLLAMA_HOST_FALLBACK, oss.OLLAMA_HOST_FALLBACK], + ), patch.dict('os.environ', {}, clear=False), patch( + 'services.ollama_service.requests.post', + side_effect=requests.Timeout('gcp timeout'), + ) as mock_post: + import os + os.environ.pop('EMBEDDING_HOST', None) + vec = svc.generate_embedding('test text', allow_111_fallback=False) + + posted_hosts = [call.args[0].split('/api/embed')[0] for call in mock_post.call_args_list] + assert vec == [] + assert posted_hosts == [oss.OLLAMA_HOST_PRIMARY, oss.OLLAMA_HOST_SECONDARY] assert oss.OLLAMA_HOST_FALLBACK not in posted_hosts