From d90f96fab37eb93b47beee27bf2880e5e6e7e8c8 Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 2 Jun 2026 10:09:36 +0800 Subject: [PATCH] =?UTF-8?q?V10.567=20=E6=94=B6=E6=96=82=20MCP=20=E5=B8=82?= =?UTF-8?q?=E5=A0=B4=E6=B4=9E=E5=AF=9F=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- .../current_execution_queue_20260524.md | 1 + docs/memory/history_logs.md | 1 + services/mcp_collector_service.py | 16 +++++---- tests/test_mcp_collector_ollama_first.py | 33 ++++++++++++++++++- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index 8b35fce..a2d274a 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.566" +SYSTEM_VERSION = "V10.567" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index 8100ed9..fe34135 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -104,6 +104,7 @@ - 2026-05-31 起,`V10.506` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval gate:在 review decision 通過後只審核 operator human approval 摘要,要求 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到後續 writer preflight 設計。 - 2026-05-31 起,`V10.509` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight gate:在 human approval 通過後只審核 operator writer preflight 摘要,要求 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、matched row exact-identity/variant/overwrite guard 與 operator boundary;仍不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。 - 2026-06-01 起,`V10.566` 新增市場情報 Professional Source Governance gate:將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract,並接上 `/api/market_intel/mcp_professional_source_governance`、UI preview panel、deployment readiness check 與 production smoke target;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler。 +- 2026-06-02 起,`V10.567` 將 MCP 市場洞察 fallback 收斂為 GCP-A / GCP-B only,不再讓 111 承接非即時市場分析長任務;預設 timeout 25 秒、`num_predict` 500,GCP 不可用時直接保守降級,避免 Elephant Alpha 60 秒 timeout 與 111 負載尖峰。 ## 3. 12 Agent 決策信封整合 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index e4e0cd9..71d067f 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.567 MCP 市場洞察 GCP-only fallback**: `MCPCollectorService._ollama_topic_fallback()` 改成只使用 GCP-A / GCP-B Ollama,失敗後保守回本地 fallback,不再把市場洞察長分析轉嫁到 111。預設 timeout 收斂為 25 秒、`num_predict` 收斂為 500,避免 Elephant Alpha 在 GCP-A/GCP-B 短暫不可用時撞 60 秒總上限或造成 111 負載尖峰;Gemini 仍維持 `GEMINI_API_HARD_DISABLED=true` 預設硬封鎖。 - **V10.565 PChome 覆蓋率操作建議**: 補強 `/api/ai/pchome-match/backfill/status`,將低覆蓋率拆成 `operation_backlog`:刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;並新增 `recommended_next_action`,Dashboard 狀態摘要會直接顯示建議下一步,避免使用者只看到低覆蓋率卻不知道該按哪條產線。 - **V10.563 正式 preview 假可救候選收斂**: 針對正式 `retryable_candidate_preview` 露出的 M.A.C 蜜粉與 SAUGELLA 菁萃潔浴凝露案例補 guard。M.A.C 單邊明確色號(如 `#絕絕紫`)會進 `variant_selection_review`,維持 `true_low_confidence`;SAUGELLA 潤澤 / 日用型 / 加強 / 黃金女郎型互斥,直接 hard veto,避免同品線不同私密清潔款式被當成 recoverable low_score。 - **V10.566 市場情報 Professional Source Governance**: 新增 `/api/market_intel/mcp_professional_source_governance`、preview service/gates/sample 與市場情報頁卡片,將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract。此 gate 只審核操作員治理摘要,不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler;deployment readiness 同步新增 preview-safe 檢查與 production smoke target。 diff --git a/services/mcp_collector_service.py b/services/mcp_collector_service.py index 301eed6..f903224 100644 --- a/services/mcp_collector_service.py +++ b/services/mcp_collector_service.py @@ -33,10 +33,13 @@ from services.gemini_guard import ( logger = logging.getLogger(__name__) MCP_CACHE_TTL_HOURS = int(os.getenv("MCP_CACHE_TTL_HOURS", "24")) -# MCP router 是即時情報主路徑;router 不可用時先走 Ollama 三主機級聯做離線洞察, +# MCP router 是即時情報主路徑;router 不可用時先走 GCP Ollama 做離線洞察。 +# 市場洞察屬非即時必需批次工作,不把長分析轉嫁到 111 fallback。 # Gemini Grounding 僅作最後備援,避免再次回到 Gemini-first。 MCP_MODEL = os.getenv("MCP_GEMINI_MODEL", "gemini-2.0-flash") MCP_FALLBACK_MODEL = "gemini-1.5-flash" +MCP_OLLAMA_TIMEOUT = int(os.getenv("MCP_OLLAMA_TIMEOUT", "25")) +MCP_OLLAMA_NUM_PREDICT = int(os.getenv("MCP_OLLAMA_NUM_PREDICT", "500")) try: from services.ollama_service import OllamaService @@ -266,11 +269,11 @@ class MCPCollectorService: return self._fallback_topic_content(topic, f"即時外部搜尋暫不可用:{type(e).__name__}") def _ollama_topic_fallback(self, topic: str, query: str) -> Optional[str]: - """MCP/搜尋不可用時先走 Ollama 三主機級聯;Gemini 只留最後備援。""" + """MCP/搜尋不可用時先走 GCP Ollama;111 不承接市場洞察長任務。""" if not _OLLAMA_AVAILABLE: return None try: - logger.info("[MCP] Using GCP-A/GCP-B/111 Ollama for market insight fallback topic=%s", topic) + logger.info("[MCP] Using GCP-A/GCP-B Ollama for market insight fallback topic=%s", topic) ollama_model = os.getenv('OPENCLAW_OLLAMA_MODEL', 'qwen2.5-coder:7b') ollama_prompt = ( f"你是一位精通台灣電商市場的分析師。目前無法取得即時搜尋結果," @@ -282,15 +285,16 @@ class MCPCollectorService: prompt=ollama_prompt, model=ollama_model, temperature=0.4, - timeout=45, - options={'num_predict': 800}, + timeout=MCP_OLLAMA_TIMEOUT, + options={'num_predict': MCP_OLLAMA_NUM_PREDICT}, + allow_111_fallback=False, ) content = (resp.content or '').strip() if resp.success else '' if content and not self._looks_unreliable(content): # 不進快取,因為這是預測性內容。 return content if not resp.success: - logger.warning("[MCP] Ollama cascade fallback failed: %s", resp.error) + logger.warning("[MCP] GCP Ollama fallback failed: %s", resp.error) except Exception as exc: logger.warning("[MCP] Ollama fallback failed: %s", exc) return None diff --git a/tests/test_mcp_collector_ollama_first.py b/tests/test_mcp_collector_ollama_first.py index 3693e1a..a0286e1 100644 --- a/tests/test_mcp_collector_ollama_first.py +++ b/tests/test_mcp_collector_ollama_first.py @@ -35,4 +35,35 @@ def test_mcp_collector_uses_ollama_before_gemini(monkeypatch): assert "基於歷史趨勢" in result assert calls assert calls[0]["model"] - assert calls[0]["options"]["num_predict"] == 800 + assert calls[0]["allow_111_fallback"] is False + assert calls[0]["timeout"] == mcp_mod.MCP_OLLAMA_TIMEOUT + assert calls[0]["options"]["num_predict"] == mcp_mod.MCP_OLLAMA_NUM_PREDICT + + +def test_mcp_collector_market_insight_does_not_use_111_fallback(monkeypatch): + import services.mcp_collector_service as mcp_mod + + service = mcp_mod.MCPCollectorService() + monkeypatch.setattr(mcp_mod, "_OLLAMA_AVAILABLE", True) + calls = [] + + class FakeOllamaService: + def __init__(self, model=None): + self.model = model + + def generate(self, **kwargs): + calls.append(kwargs) + return SimpleNamespace( + success=False, + content="", + error="111 fallback disabled; no approved GCP Ollama host available", + ) + + monkeypatch.setattr(mcp_mod, "OllamaService", FakeOllamaService) + + result = service._ollama_topic_fallback("market_trends", "台灣電商促銷趨勢") + + assert result is None + assert calls + assert calls[0]["allow_111_fallback"] is False + assert calls[0]["timeout"] <= 25