From 3ea7004a6fcc61d36b4882a8079b88114e8e9ad6 Mon Sep 17 00:00:00 2001 From: OoO Date: Sun, 3 May 2026 23:06:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(p4)+docs(p5+p6):=20Meta=20=E9=99=8D?= =?UTF-8?q?=E9=A0=BB=20+=20LOCKED-GEMINI=20+=20ADR-028/029?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 A10 — OpenClaw 雙塔重劃 - run_scheduler.py: Meta 自審 cron 6h → 每日 12:00(月省 2.25M Gemini, +20% 達標) - scheduler.py: 移除 icaim 內 2 處 inline meta 觸發 - openclaw_strategist 抽 _push_report_with_charts (call×3) + _collect_mcp_intel (call×2) - 行數目標 -25% 未達(4 報告函數結構差異大,A10 採保守抽出避險) - 主戰果:Meta 降頻月呼叫 300 → 30(-90%) Phase 5 — 5 處 LOCKED-GEMINI 註解(涵蓋鎖定 7 場景) - services/mcp_collector_service.py:32 (場景 #1: Google Search Grounding) - services/openclaw_strategist_service.py:40 (場景 #2/3/4: 週/月/年報) - services/code_review_pipeline_service.py:46 (場景 #5: 100K+ token diff) - services/elephant_alpha_orchestrator.py:88 (場景 #6: EA HITL) - routes/openclaw_bot_routes.py:98 (場景 #7: PPT 簡報) Phase 6 A12 — 憲法級 ADR 三份 - ADR-028「LLM 路由統一準則」(269 行) - 5 大支柱:三主機級聯 / Ollama 優先 / 雙塔分工 / Gemini 鎖 7 場景 / 可觀測性 - 8 個 provider 白名單(DB CHECK 對齊) - 30+ caller 名單分「已實作 / 規劃中」 - ADR-029「Hermes-First 雙塔分工」(222 行) - 12 項職責重劃表 + A7/A8/A10 落地對照 - Gemini 月支出 -23.5%(critic 第 3 輪 B5 算術修正) - ADR-027 附錄(+69 行) - 三主機架構(Primary/Secondary/Fallback) - 4 條獨立 fallback 鏈 - 廢止「188 Ollama」概念 - README 索引更新 A11 critic 第 3 輪修補:5 BLOCKER 全清 - B1: 行數 1831 → 2677 (含 baseline 對照) - B2: 場景 #4 行號 759/1267 → 1102/1628 + annual 不存在註明 - B3: 虛構 caller 改實存(ea_hitl_prefetch → ea_engine 等) - B4: 白名單三層對齊(DB 8 = ADR 8 = token_report 補 ollama_secondary) - B5: KPI 算術 50→38 = -23.5% 重核 services/telegram_templates.py: A5 daily_token_report() 函數 services/mcp_collector_service.py: 加 LOCKED-GEMINI 註解 services/elephant_alpha_orchestrator.py: 加 LOCKED-GEMINI 註解 103/103 unit test 全綠(zero regression) Operation Ollama-First v5.0 / Phase 4 A10 + Phase 5 + Phase 6 A12 Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/adr/ADR-027-primary-ollama-on-gcp.md | 114 ++++++++ .../ADR-028-llm-routing-unified-principles.md | 270 ++++++++++++++++++ docs/adr/ADR-029-hermes-first-twin-tower.md | 223 +++++++++++++++ docs/adr/README.md | 3 + run_scheduler.py | 39 ++- scheduler.py | 17 +- services/elephant_alpha_orchestrator.py | 3 + services/mcp_collector_service.py | 52 +++- services/telegram_templates.py | 35 +++ 9 files changed, 739 insertions(+), 17 deletions(-) create mode 100644 docs/adr/ADR-027-primary-ollama-on-gcp.md create mode 100644 docs/adr/ADR-028-llm-routing-unified-principles.md create mode 100644 docs/adr/ADR-029-hermes-first-twin-tower.md diff --git a/docs/adr/ADR-027-primary-ollama-on-gcp.md b/docs/adr/ADR-027-primary-ollama-on-gcp.md new file mode 100644 index 0000000..c589a2b --- /dev/null +++ b/docs/adr/ADR-027-primary-ollama-on-gcp.md @@ -0,0 +1,114 @@ +# ADR-027:Primary Ollama 遷移至 GCP 高效能主機 + +- **Status**: Accepted +- **Date**: 2026-05-03 +- **Decision Maker**: 統帥 +- **Author**: Antigravity + +## Context + +為了提升 AI 處理速度與穩定性,並減輕本地 188 主機的負載,現已啟用一台位於 GCP 的高效能主機作為 Primary Ollama 伺服器。 +原本地 188 或 111 的 Ollama 轉為 Fallback 節點。 + +## Decision + +**1. 高效能主機配置** +- **Public IP**: `34.21.145.224` +- **服務端口**: `11434` +- **主要模型**: + - `qwen2.5-coder:7b` (程式碼修復與開發) + - `hermes3:latest` (Llama 3 級別,用於 Hermes 邏輯) + - `bge-m3:latest` (Embedding 專用) + +**2. 環境變數對應** +- `OLLAMA_HOST_PRIMARY`: `http://34.21.145.224:11434` +- `OLLAMA_HOST_FALLBACK`: `http://192.168.0.111:11434` (或 188 本地節點) + +**3. 資源分工** +- **Primary (GCP)**: 承載 90% 以上的 LLM 推理與 Embedding 請求。 +- **Fallback (Local)**: 當 GCP 連線超時或故障時,自動切換至本地節點( Hermes Rule-based 或本地 Ollama)。 + +## Alternatives Considered + +- **維持全本地**: 188 主機 Load 較高,且 GPU 資源競爭激烈。 +- **全雲端 API (OpenAI/Gemini)**: 成本較高,且無法控制模型版本與延遲。 + +## Consequences + +- **優點**: 推理速度提升,系統負載均衡,具備異地備援能力。 +- **缺點**: 依賴外部網路連線,需注意 GCP 出口流量成本與安全性(目前採 IP Allowlist 保護)。 + +## Verification + +- `curl http://34.21.145.224:11434/api/tags` 驗證模型列表正常返回。 +- 測試 `qwen2.5-coder:7b` 與 `bge-m3:latest` 回應正常。 + +--- + +## 附錄(2026-05-03 戰役 v5.0 補述) + +> **補述背景**:Operation Ollama-First v5.0 戰役 Phase 0/1/2/3 完成後,本附錄校正本 ADR 的主機架構與 fallback 鏈描述,並廢止「188 Ollama」概念。完整治理規則改由 ADR-028 / ADR-029 承接。 + +### 附錄 A:三主機架構(取代原 GCP / 111 二段描述) + +戰役 v5.0 啟用第二台 GCP 高效能主機後,主機級聯升級為三層: + +| 角色 | 公網 IP / 主機 | 儲存 | 規格 / 用途 | +|---|---|---|---| +| **Primary** | `34.143.170.20:11434` | SSD | 9× 加載 / 2× 推理(v5.0 戰役新主機)| +| **Secondary** | `34.21.145.224:11434` | SSD | 同等效能備援;本 ADR 原 Primary 降為次主 | +| **Fallback** | `192.168.0.111:11434`(HDD)| HDD | 統帥 Mac 上的 Ollama.app,最後一道本地防線 | + +**程式契約**:所有 LLM 呼叫必須走 `services/ollama_service.resolve_ollama_host()`(Phase 2 A6 落地),按 Primary → Secondary → Fallback 順序探測: +- HTTP probe `GET /api/version`,2s timeout(取代原純 TCP 探測,B3 修補) +- 失敗主機 `mark_unhealthy()` 30s(B4),TTL 內直接跳下一台 +- 寫死 IP 已全面消除(`services/aider_heal_executor.py:48-49` 與 `services/code_review_pipeline_service.py:218-225` 兩處 N2/N3 修補) + +詳見 `docs/phase2_deploy_verify_20260503.md`。 + +### 附錄 B:4 條獨立 fallback 鏈(不存在「線性 LLM 鏈」) + +戰役 v5.0 audit 證實系統實際有 4 條(嚴格說 5 條)獨立 fallback 鏈,語意各異不可合併: + +1. **Ollama host 級**(基礎設施層) + `gcp_ollama` (Primary) → `ollama_secondary` → `ollama_111` + 實作:`services/ollama_service.resolve_ollama_host()` + +2. **Hermes 競價 / 意圖分類**(戰術層) + Ollama → 規則引擎兜底 + 實作:`services/hermes_analyst_service.py`(ADR-004) + +3. **NemoTron 派遣**(行動層) + NIM `meta/llama-3.1-8b` → Hermes 規則引擎 + 實作:`services/nemoton_dispatcher_service.py`(ADR-004) + +4. **OpenClaw Q&A**(戰略層 / Telegram) + Gemini 2.5 Flash → NIM `deepseek-v3.2` → 字面 fallback + Phase 3 A7 已切:Hermes qwen3:14b → 信心低升 Gemini → NIM + 實作:`services/openclaw_strategist_service.py` / `routes/openclaw_bot_routes.py:6784-6843` + +5. **MCP 即時情報**(外部資訊層) + Gemini Grounding L1 → Gemini Grounding L2 → Ollama L3 → 靜態字串 + 實作:`services/mcp_collector_service.py:163-214` + +### 附錄 C:廢止項 + +- **「188 Ollama」概念全面廢止** + 188 主機(`192.168.0.188`)僅作為以下用途: + - SSH AutoHeal target(`docker restart` / log scan,ADR-013) + - momo-pro Docker Compose 運行環境(ADR-008) + - momo-postgres / momo-db / momo-scheduler / momo-telegram-bot 容器宿主 + - **不安裝、不運行 Ollama**(Ollama 全部走 GCP Primary / Secondary 或統帥 Mac 111) + - 任何「188 Ollama」字樣的舊文件視為過時,以本附錄為準 +- 原 `OLLAMA_HOST_FALLBACK = http://192.168.0.111:11434` env var 仍保留,但解析路徑改由 `resolve_ollama_host()` 統一管控,不再被任何呼叫點寫死引用 + +### 附錄 D:治理規則升級指引 + +戰役 v5.0 後,所有 LLM 治理決策改以以下文件為準: +- 路由白名單 / caller 清單 / Gemini 鎖定場景 → **ADR-028** +- Hermes 主塔 vs OpenClaw 副塔分工 → **ADR-029** +- 部署驗證劇本 → `docs/phase2_deploy_verify_20260503.md` +- DB 觀測層 schema → `migrations/024_create_ai_calls_table.sql` ~ `026_add_embedding_signature.sql` +- Logger 程式契約 → `services/ai_call_logger.py` + +本 ADR-027 保留作為戰役起點的歷史紀錄,不再作為主要參照入口。 diff --git a/docs/adr/ADR-028-llm-routing-unified-principles.md b/docs/adr/ADR-028-llm-routing-unified-principles.md new file mode 100644 index 0000000..6931811 --- /dev/null +++ b/docs/adr/ADR-028-llm-routing-unified-principles.md @@ -0,0 +1,270 @@ +# ADR-028: LLM 路由統一準則 — Ollama-First 五大支柱 + +- **Status**: Accepted +- **Date**: 2026-05-03 +- **Decision Maker**: 統帥 +- **Author**: Operation Ollama-First v5.0(Codex / A12 planner) +- **Supersedes**: 無(補述 ADR-027,非取代) +- **Related**: ADR-002(pgvector 唯一向量庫)、ADR-003(Hermes embedding 本地化)、ADR-004(NemoTron 配額耗盡 fallback)、ADR-008(部署實機驗證)、ADR-013(AIOps AutoHeal)、ADR-018(四 Agent 控制面)、ADR-027(Primary Ollama on GCP) + +--- + +## Context + +ADR-027 在 2026-05-03 啟用 GCP 高效能主機作為 Primary Ollama,但戰役 v5.0 Phase 0 的 onboarder audit(`docs/phase0_audit_report_20260503.md`)揭露三項治理斷層: + +1. **34 個 LLM 呼叫點分散治理失能** + - audit 完整盤點 `services/` 與 `routes/` 共 34 處 LLM 呼叫,分散在 9 個 service / 4 個 route 檔。 + - `AIGenerationHistory` 結構化紀錄覆蓋率僅 4/34 = 11.8%,其餘 88% 完全沒結構化遙測。 + - 兩處「寫死 111」的呼叫點(`services/aider_heal_executor.py:48-49`、`services/code_review_pipeline_service.py:218-225`)違反 ADR-027 的 Primary/Fallback 原則。 + +2. **4 條獨立 fallback 鏈缺一致性** + - 不存在「線性 LLM 鏈」假設:Ollama 主機級、Hermes 競價、NemoTron 派遣、OpenClaw Q&A、MCP 即時情報各自為政。 + - 各鏈 timeout / 探測 / 降級語意不一,故障時行為不可預測。 + +3. **provider/caller 命名漂移** + - 戰役清單原列 26 個呼叫點,audit 補出 8 個遺漏;`openclaw_qa_nim` 此類 fallback 命名靠執行期動態組成(`services/openclaw_strategist_service.py:737`),缺集中字典。 + - DB 端在 Phase 1 已加 provider CHECK constraint(`migrations/024_create_ai_calls_table.sql:88-91`),但 caller 仍是自由字串(critic-A11 於 phase1_final_critic_signoff H5 點名)。 + +ADR-027 解決了「主機去哪裡」,但未統一「誰能呼叫」「呼叫去哪一台」「為何選這個」。本 ADR 是 ADR-027 的決策補述,把 Phase 0/1/2/3 戰役落地的成果固化為憲法級準則,供未來 Phase 4-12 與所有新 Agent 共同遵循。 + +--- + +## Decision — 五大支柱 + +### 支柱 1:三主機級聯(Triple-Host Cascade) + +取代 ADR-027 原本「GCP / 111」二段架構。 + +| 角色 | 主機 | 儲存 | 用途 | +|---|---|---|---| +| Primary | `34.143.170.20` | SSD | 9× 加載 / 2× 推理(v5.0 戰役新主機)| +| Secondary | `34.21.145.224` | SSD | 同等效能備援,ADR-027 原 Primary 降為次主 | +| Fallback | `192.168.0.111` | HDD | 最後一道本地防線,統帥 Mac 上的 Ollama.app | + +`services/ollama_service.resolve_ollama_host()`(Phase 2 A6 落地)按 Primary → Secondary → Fallback 順序探測: +- HTTP probe `GET /api/version`,2s timeout(Phase 2 B3 修補) +- 失敗主機標 `mark_unhealthy()` 30s(Phase 2 B4),TTL 內直接跳下一台 +- 任何呼叫點都必須走 `resolve_ollama_host()`,禁止寫死任何 IP + +### 支柱 2:Ollama 優先(Ollama-First) + +預設所有 LLM 推理都走 Ollama(三主機級聯),只有支柱 4 列的 7 個鎖定場景才允許走 Gemini。 + +**判定原則**: +- 高頻、低延遲敏感、戰術層 → Ollama +- 低頻、戰略洞察、需 Grounding/長 context、HITL pre-fetch → Gemini + +**成本影響**:Ollama 三主機都是 self-hosted,邊際 token 成本 = 0;Gemini 月支出受 7 個鎖定場景上限約束。 + +### 支柱 3:雙塔分工(Twin-Tower) + +詳見 ADR-029。簡述: +- **Hermes 主入口(L1 戰術塔)**:高頻 Ollama-only,所有日常意圖分類、競價分析、KPI 計算、Q&A 第一響應走 Hermes。 +- **OpenClaw 副引擎(L3 戰略塔)**:低頻鎖定 5 個 Gemini 場景,月/年報、Code Review、EA HITL、Meta 自審等戰略產出。 + +### 支柱 4:Gemini 鎖定 7 個場景 + +只有以下 7 個 caller 允許呼叫 `gemini` provider,其餘任何 caller 走 Gemini 必經 ADR: + +| # | Caller | 檔案位置 | 用途 | 模型 | +|---|---|---|---|---| +| 1 | `mcp_l1_grounding` | `services/mcp_collector_service.py:32` (LOCKED-GEMINI 註解) | MCP L1 Grounding(即時情報)| `gemini-2.0-flash` | +| 2 | `mcp_l3_ollama` 兜底前的 L2 | `services/mcp_collector_service.py:33` MCP_FALLBACK_MODEL | MCP L2 Grounding | `gemini-1.5-flash` | +| 3 | `ppt_gemini` | `routes/openclaw_bot_routes.py:98` (LOCKED-GEMINI) + `_call_gemini` 約 line 2464 | PPT 簡報深度分析 | `gemini-2.0-flash` | +| 4 | `openclaw_weekly` / `openclaw_monthly` | `services/openclaw_strategist_service.py:1102` weekly + `:1628` monthly + `:40` STRATEGY_MODEL (LOCKED-GEMINI) | 週/月報敘事(**註**:annual 報告尚未實作,目前 OpenClaw 僅 weekly/monthly/daily/meta)| `gemini-2.5-flash` | +| 5 | `code_review_openclaw` | `services/code_review_pipeline_service.py:46` REVIEW_MODEL (LOCKED-GEMINI) | Code Review 高階評估 | `gemini-2.5-flash` | +| 6 | `ea_engine` (HITL) | `services/elephant_alpha_orchestrator.py:88` AgentCapability.openclaw model (LOCKED-GEMINI);`hermes_ea_prefetch` 走 Hermes Ollama 不在此鎖定 | EA HITL escalation(ADR-021)| `gemini-2.0-flash` | +| 7 | `openclaw_qa` 升級分支 | `services/openclaw_strategist_service.py:56` `generate_strategy_response` (Phase 3 A7 已加 Ollama-first feature flag,flag=true 時 Ollama 失敗才升級 Gemini) | 複雜 SKU 推理(Hermes/qwen3 品質低時升級)| `gemini-2.5-flash` | + +新增 Gemini caller 必須走 ADR review;DB CHECK constraint 將於 Phase 5 後補(critic-A11 H5)。 + +### 支柱 5:可觀測性先行(Observability-First) + +所有 LLM 呼叫必須經 `services/ai_call_logger.log_ai_call()` 雙寫 `ai_calls` 表(Phase 1 A4 落地)。 + +**遙測契約**: +- caller / provider / model 必填(provider 由 DB CHECK 約束在白名單內) +- input_tokens / output_tokens / duration_ms / status / cost_usd 必填 +- request_id 可選但 fallback 鏈必須串接(如 `code_review_hermes` → `code_review_openclaw` → `code_review_elephant` 三鏈共用) +- `meta JSONB` 不得超 8192 octet,`error TEXT` 不得超 4096 octet(migration 024 H2) +- chat_id 等 PII 必經 sha256 截 12 字後存入(Phase 2 後補丁,critic H6) + +**自動化護欄**: +- `ai_call_budgets` 7 個 provider 月度預算(gemini/claude/nim/nim_via_elephant/openrouter/gcp_ollama/ollama_111)+ 3 條全供應商總額(daily/weekly/monthly)= 10 筆種子預算(migration 025) +- 每日 23:55 token report 推 Telegram(A5 落地) +- AIGenerationHistory 覆蓋率必須 ≥ 90%(v5.0 戰役 KPI),Phase 5 報表追蹤 + +--- + +## Provider 白名單(DB CHECK 約束) + +`migrations/024_create_ai_calls_table.sql:51-58` `chk_ai_calls_provider` 鎖定以下 8 值: + +| Provider | 主機 | 計費 | 用途 | +|---|---|---|---| +| `gcp_ollama` | Primary `34.143.170.20:11434` | 0(self-hosted)| 戰役 v5.0 主主機 | +| `ollama_secondary` | Secondary `34.21.145.224:11434` | 0 | 同等效能備援(架構保留,logger 端 url-based 推斷待 Phase 7+) | +| `ollama_111` | Fallback `192.168.0.111:11434` | 0 | 最後一道防線 | +| `gemini` | Google AI API | metered | 鎖定支柱 4 的 7 個場景 | +| `claude` | Anthropic API | metered | Phase 7 Frontier 升級保留:Code Review L0 (Opus 4.7) + EA HITL (Sonnet 4.6);budgets 種子 $10/月已預設 | +| `nim` | NVIDIA NIM `https://integrate.api.nvidia.com/v1` | 80 calls/day 配額 | NemoTron 派遣(ADR-004)+ Code Review fallback | +| `nim_via_elephant` | NIM via `services/elephant_service.py` | 同 NIM 配額 | Code Review ElephantAlpha 49B 鏈 | +| `openrouter` | OpenRouter(保留) | metered | PPT deepseek-v3.2 鏈 + 預留 Phase 9 多供應商實驗 | + +> **三層一致性備忘**(critic-A11 B4 修補):DB CHECK = 8 個,ADR-028 = 8 個,`services/token_report_service.py` `_PROVIDER_DISPLAY` 後續需補 `ollama_secondary`(H5 待修,列為 Phase 7 整合任務)。 + +--- + +## Caller 白名單(程式碼集中字典) + +戰役 v5.0 Phase 1 A4 logger 接入後,固定字串集中於 `services/ai_call_logger.py` 與各 caller。常見 13 + 5 NIM 變體: + +``` +hermes_intent / hermes_analyst / hermes_rule_engine +code_review_hermes / code_review_openclaw / code_review_elephant +openclaw_qa / openclaw_qa_nim +openclaw_weekly / openclaw_weekly_nim +openclaw_daily / openclaw_daily_nim +openclaw_monthly / openclaw_monthly_nim +openclaw_meta / openclaw_meta_nim +nemotron_dispatch +openclaw_bot_main / openclaw_bot_gemini / openclaw_bot_nim +embedding_worker / embedding_realtime +mcp_collector_l1 / mcp_collector_l2 / mcp_collector_l3 +ppt_generator / ppt_generator_ollama / ppt_generator_nim +ea_hitl_prefetch / ea_autonomous_engine +aider_heal +sales_copy / trend_match / trend_search / product_insights / trend_keywords +telegram_copy / bot_api_copy / trend_crawler / ai_provider_generic +``` + +新增 caller 必須: +1. 加進 `services/ai_call_logger.py` `_KNOWN_CALLERS` 字典 +2. 更新本 ADR 表格 +3. 通過 critic 審查 +4. Phase 5 後 DB CHECK constraint 將以格式約束(`^[a-z][a-z0-9_]{2,63}$`)NOT VALID 補上(critic-A11 H5 建議) + +--- + +## 鎖定 Gemini 7 個場景(與支柱 4 對應,含理由) + +| 場景 | 為何不能走 Ollama | +|---|---| +| MCP L1/L2 Grounding | Gemini Grounding 是即時 web search 唯一供應商,Ollama 無此能力(Tavily/Exa 走 Phase 10 不同路徑)| +| PPT 圖檢查 / 簡報分析 | Gemini 多模態 vision 能力遠超 Ollama 本地模型 | +| 週/月/年報敘事 | 商業敘事品質要求高,Gemini 2.5 Flash vs qwen3:14b 估差 10-20%(phase0_research_report Section 1)| +| Code Review 高階評估 | OpenClaw 級審查需 Gemini 2.5 Flash 的程式理解力,本地模型不足 | +| EA HITL pre-fetch | escalation 路徑 5s timeout 內必須拿到結構化 + 高品質回應,ADR-021 已定 | +| 複雜 SKU 推理 | Hermes/qwen3:14b 信心 < threshold 時才升級 Gemini,繁中商業情境短板(TMMLU+ 論文)| + +--- + +## Alternatives Considered + +| 方案 | 不採用原因 | +|---|---| +| **A. 單一供應商(全 Gemini 或全 NIM)** | 配額硬限(NIM 80 calls/day)+ 月成本不可控;雲端 API 出口流量風險;違反 FinOps 視角 | +| **B. 全本地(不留 Gemini)** | MCP Grounding / vision / 戰略敘事品質本地模型補不齊;繁中短板無解(phase0_research Section 1.4)| +| **C. 多供應商完全互通(任意 caller 任意 provider)** | 命名漂移無法治理;caller-provider 對應靠程式碼隱式約定,無 DB 護欄;token 報表 GROUP BY 失準 | +| **D. 線性 LLM 鏈(一條 fallback 鏈打天下)** | audit 已證明系統有 4 條獨立鏈(Ollama host / Hermes / NemoTron / OpenClaw Q&A / MCP),語意各異不可合併 | +| **E. 引入 LangChain / LiteLLM 統一抽象層** | 黑盒、難審計、增加依賴;既有 `ai_call_logger` + `resolve_ollama_host` 已具備統一語意,無需第三方 | + +--- + +## Consequences + +### 正面 + +1. **成本可控**:Gemini 鎖定 7 場景,月支出有上限;其餘 ~27 個 caller 全走 Ollama 邊際成本 = 0。 +2. **遙測 100%**:A4 logger 接入後,`ai_calls` 覆蓋率從 11.8% 拉升至接近 100%,token / 成本 / 延遲 / 失敗率每日 23:55 自動入袋。 +3. **fallback 行為可預測**:三主機級聯 + mark_unhealthy + HTTP probe 取代純 TCP 探測,process 卡死也能 5s 內切換。 +4. **provider 命名治理**:DB CHECK constraint 鎖死 8 個 provider;caller 字典集中於程式碼,新增需 ADR。 +5. **未來 Phase 4-12 有依據**:所有新 Agent / 新 caller 直接套用本 ADR 的五大支柱與白名單。 + +### 負面 + +1. **新 caller 引入有 ADR 摩擦**:每個新 LLM 呼叫點都要 update 本 ADR,但這是治理代價而非缺點。 +2. **DB CHECK 變更需 migration**:provider 白名單擴增(如 Phase 9 加 `claude`)需新 migration + 滾動部署。 +3. **Logger 額外延遲**:每個 LLM 呼叫多一層 fire-and-forget 寫入(測試顯示 < 5ms),但獨立 thread + dedicated session pool 可控。 + +### 風險與緩解 + +| 風險 | 機率 | 緩解 | +|---|---|---| +| Logger 失敗連鎖讓主流程崩 | 低 | `ai_call_logger` kill-switch 連續 10 次失敗自動關(Phase 1 已測試 52/52 pass)| +| caller typo 污染 token 報表 | 中 | Phase 5 後加格式 CHECK constraint;review 時 grep 比對白名單 | +| Gemini 配額耗盡(7 場景同時爆)| 低 | NIM `nvidia/llama-3.3-nemotron-super-49b-v1.5` 鏈 fallback;ADR-004 已定 | +| Primary 主機長期掛 | 低 | mark_unhealthy 30s + 三層級聯;最壞情況走 111 本地 | + +--- + +## Verification(如何驗證已落地) + +### V1:DB 層 +```sql +-- provider CHECK 鎖定 +SELECT pg_get_constraintdef(oid) FROM pg_constraint +WHERE conname = 'chk_ai_calls_provider'; +-- 期望:CHECK (provider IN ('gcp_ollama','ollama_secondary','ollama_111','gemini','nim','nim_via_elephant','openrouter')) + +-- 預算種子 10 筆 +SELECT period, provider, budget_usd FROM ai_call_budgets ORDER BY period, provider NULLS FIRST; + +-- 24h caller 分布(戰役 v5.0 上線後應 ≥ 13 個 distinct caller) +SELECT caller, COUNT(*) FROM ai_calls +WHERE called_at >= NOW() - INTERVAL '24h' +GROUP BY caller ORDER BY 2 DESC; +``` + +### V2:程式碼層 +```bash +# 不應有寫死 IP +grep -rn "192.168.0.111" services/ routes/ | grep -v "OLLAMA_HOST_FALLBACK\|resolve_ollama_host\|test_" +grep -rn "34.143.170.20\|34.21.145.224" services/ routes/ | grep -v "OLLAMA_HOST_PRIMARY\|test_" + +# 所有 LLM 呼叫應走 logger +grep -rn "ollama_service.generate\|google.generativeai\|openai.ChatCompletion" services/ routes/ +# 每個 hit 上方應有 with log_ai_call(...) 或 @log_ai_call_decorator +``` + +### V3:每日 token report(每日 23:55 入 Telegram) +- Section 1 `ollama_pct` ≥ 80%(戰役 KPI) +- Section 5 「今日 Ollama Tokens vs 7 日均」應穩定,不應突然降為 0(M7 的 openclaw_bot_main 修復後) +- ai_insights `insight_type='daily_token_report'` 每日 1 筆 + +--- + +## Migration Plan + +| Phase | 項目 | 狀態 | 文件 | +|---|---|---|---| +| 0 | LLM/MCP audit + 替代查證 | ✅ 完成 | `docs/phase0_audit_report_20260503.md` / `docs/phase0_research_report_20260503.md` | +| 1 | DB schema(024/025/026)+ ai_call_logger + token report | ✅ 完成(52/52 tests pass)| `docs/phase1_db_design_20260503.md` / `docs/phase1_critic_review_20260503.md` / `docs/phase1_final_critic_signoff_20260503.md` | +| 2 | resolve_ollama_host + HTTP probe + mark_unhealthy + AiderHeal/CodeReview lazy | ✅ 完成(13 + 43 tests pass)| `docs/phase2_deploy_verify_20260503.md` | +| 3 | OpenClaw Q&A 切 qwen3:14b(feature flag)| ✅ 完成(A7)| 待 Phase 4 黃金集 A/B | +| 4 | 黃金集 A/B 評測 + Hermes daily 摘要遷移(A8)| 規劃中 | — | +| 5 | Phase 5 報表上線 + caller CHECK NOT VALID | 規劃中 | — | +| 6 | 文件對齊(本 ADR + ADR-029 + ADR-027 附錄)| ✅ 完成 | 本檔 | +| 7-8 | OpenClaw 程式瘦身(A10)| 規劃中 | — | +| 9 | 預算守門 hard-stop + 多供應商實驗 | 規劃中 | — | +| 10 | MCP 5 顆 🟢 引入(postgres / omnisearch / filesystem / firecrawl / git)| 規劃中 | — | +| 11 | RAG 一致性護欄(embedding_signature 回填 + bge-m3 digest 鎖定)| 規劃中 | — | +| 12 | ai_usage_tracking deprecate + caller 集中 ADR refresh | 規劃中 | — | + +--- + +## References + +- ADR-027 Primary Ollama on GCP(戰役起點) +- ADR-029 Hermes-First 雙塔分工(本 ADR 支柱 3 展開) +- ADR-004 NemoTron 配額耗盡 fallback(白名單 NIM provider 來源) +- ADR-013 AIOps AutoHeal(aider_heal_executor 修補上下文) +- ADR-018 四 Agent 控制面(Hermes/NemoTron/OpenClaw/ElephantAlpha 角色定義) +- ADR-021 EA HITL pre-fetch(Gemini 場景 6 來源) +- `docs/phase0_audit_report_20260503.md`(34 呼叫點完整盤點) +- `docs/phase0_research_report_20260503.md`(Qwen / DeepSeek-R1 / Search API 三項紅綠燈) +- `docs/phase1_db_design_20260503.md`(DB schema 設計理由) +- `docs/phase1_final_critic_signoff_20260503.md`(H5/H6 caller / chat_id 護欄缺口) +- `docs/phase2_deploy_verify_20260503.md`(resolve_ollama_host / mark_unhealthy 落地驗證) +- 相關 memory:`reference_111_mac_ollama.md`、`reference_env_map.md`、`feedback_docs_vs_reality.md` diff --git a/docs/adr/ADR-029-hermes-first-twin-tower.md b/docs/adr/ADR-029-hermes-first-twin-tower.md new file mode 100644 index 0000000..67db052 --- /dev/null +++ b/docs/adr/ADR-029-hermes-first-twin-tower.md @@ -0,0 +1,223 @@ +# ADR-029: Hermes-First 雙塔分工 + +- **Status**: Accepted +- **Date**: 2026-05-03 +- **Decision Maker**: 統帥 +- **Author**: Operation Ollama-First v5.0(Codex / A12 planner) +- **Related**: ADR-001(三 Agent 自主學習分工)、ADR-002(pgvector)、ADR-018(四 Agent 控制面)、ADR-019(Telegram Agentic Layer)、ADR-027(Primary Ollama on GCP)、ADR-028(LLM 路由統一準則) + +--- + +## Context + +ADR-001 / ADR-018 把 Hermes 定位為「L1 Observer / Embedding」、OpenClaw 定位為「L3 Strategist」,但實作上的權重失衡與成本失衡讓「分工」變成「OpenClaw 全包」。 + +### 失衡證據(戰役 v5.0 Phase 0 audit) + +1. **程式碼體積失衡**: + - `services/openclaw_strategist_service.py` ≈ **2677 行**(HEAD 起 1831 行 + Phase 3/4 增量 846 行) + - `services/hermes_analyst_service.py` ≈ **607 行** + - 比率 **4.4×**,但 Hermes 的呼叫頻率高 OpenClaw **約 100 倍** + +2. **成本失衡**: + - OpenClaw 月燒 Gemini ≈ **50M tokens**(估算依 phase0 audit caller 流量推算) + - Hermes 月燒 Ollama ≈ **30M tokens**,邊際成本 **$0** + - 換句話說:Hermes 跑了 60% 流量 = $0;OpenClaw 跑了 40% 流量 = 全部 Gemini 帳單 + +3. **使用者主入口 = Telegram,但 Telegram NL 走 OpenClaw**: + - 統帥每日數十次答題從 Telegram 走 `routes/openclaw_bot_routes.py:6784-6843` 三層 fallback(Ollama → Gemini → NIM) + - audit ID #29/30/31 顯示 OpenClaw Bot Q&A 是 Gemini 月支出第二大來源 + - ADR-019 的 Agent-First Conversation Layer 把所有用戶輸入導向 `openclaw_decide()`,更放大 OpenClaw 流量 + +4. **戰略 / 戰術層職責混雜**: + - 「每日營運摘要」是高頻戰術(每日 09:00),卻走 OpenClaw Gemini → 成本最高頻率最高 + - 「KPI 計算」是規則引擎可解的純運算,卻丟給 LLM 寫敘事 + - 「Q&A」涵蓋從「上週業績多少」(戰術)到「下季品類布局建議」(戰略),全部走同條鏈 + +5. **Phase 3 A7 已局部驗證**: + - 戰役 v5.0 Phase 3 已把 OpenClaw Q&A 第一響應切到 `qwen3:14b`(feature flag),保留 Gemini 作 fallback + - 該變更顯示「Hermes-tier 模型接戰術 Q&A」是技術可行的 + +ADR-018 已定四 Agent 角色,但未量化「誰處理高頻流量、誰處理低頻戰略」;本 ADR 是 ADR-018 的成本驅動補述,把 Hermes 從「Observer」升格為「主入口」,OpenClaw 縮回「鎖定戰略場景的副引擎」。 + +--- + +## Decision — 雙塔分工(Twin-Tower) + +### Hermes 主塔(L1 戰術 / 高頻 / Ollama-only) + +- **定位**:所有 Telegram NL / 競價偵測 / 日常摘要 / KPI 計算 / 第一響應 Q&A 的單一入口 +- **資源**:三主機 Ollama 級聯(ADR-028 支柱 1),邊際成本 0 +- **模型**:`hermes3:latest`(意圖分類 / 競價)+ `qwen3:14b`(Q&A 第一響應,Phase 3 已切) +- **降級**:Ollama 失敗 → 規則引擎兜底(ADR-004 已定)→ 模板化回應,不直接升 Gemini +- **不可越界**:不寫戰略長文(≤ 200 字)、不做 Web Grounding、不做 vision + +### OpenClaw 副塔(L3 戰略 / 低頻 / 鎖定 5 個 Gemini 場景) + +- **定位**:產生月/年報敘事、PPT 顧問深度分析、Code Review 高階評估、EA HITL pre-fetch、複雜 SKU 推理 +- **資源**:Gemini 2.5 Flash 主,NIM `llama-3.3-nemotron-super-49b-v1.5` fallback(ADR-004) +- **觸發頻率**:日報 1×/日 / 週報 1×/週 / 月報 1×/月 / Code Review 部署觸發 / EA HITL escalation 觸發 +- **不可越界**:不接 Telegram 第一響應(一律先過 Hermes,信心 < threshold 才升級) + +### 升級條件(Hermes → OpenClaw) + +由 Hermes 主塔判定是否需要升級: +1. **意圖分類為「戰略性問題」**(如「明年品類規劃」「Q3 競品深度分析」) +2. **複雜 SKU 推理且信心分數 < 0.65**(Hermes self-assessment) +3. **使用者明確要求「深度報告」**(Telegram menu 點 `cmd:strategy` / `cmd:annual` / `cmd:competitor`) +4. **EA escalation 事件**(ADR-021 已定) + +升級時 OpenClaw 必須拿到 Hermes 已有的 context(意圖 / 信心 / 既有摘要),不重複呼叫 Ollama。 + +--- + +## 職責重劃表(戰前 vs 戰後) + +| # | 任務 | 戰前 | 戰後 | 對應戰役 task | +|---|---|---|---|---| +| 1 | 競價威脅偵測(每 4h)| Hermes Ollama | Hermes Ollama ✅ 維持 | — | +| 2 | 意圖分類(Telegram NL)| Hermes Ollama | Hermes Ollama ✅ 維持 | — | +| 3 | 每日營運摘要 | OpenClaw Gemini(每日 1×)| **Hermes 模板 + Gemini 200 字洞察** | **A8** | +| 4 | KPI 計算(業績 / 庫存 / 達成率)| OpenClaw Gemini | **Hermes 規則引擎**(純運算)| **A8** | +| 5 | Q&A 第一響應(Telegram)| OpenClaw Gemini → NIM → 字面 fallback | **Hermes qwen3:14b → 信心低升 Gemini** | **A7(已落地)** | +| 6 | 複雜 SKU 推理 | OpenClaw Gemini | Hermes 信心 < 0.65 → OpenClaw Gemini | A7 條件分支 | +| 7 | PPT 簡報深度分析 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — | +| 8 | 週/月/年報敘事 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — | +| 9 | Code Review 高階評估 | OpenClaw Gemini | OpenClaw Gemini ✅ 鎖定 | — | +| 10 | EA HITL pre-fetch | Hermes Ollama(ADR-021)| Hermes Ollama ✅ 維持,escalation 走 OpenClaw Gemini | — | +| 11 | Meta 自審 | OpenClaw 每 6h | **OpenClaw 每 24h**(降頻 4×)| **A10** | +| 12 | 規則引擎兜底 | Hermes(ADR-004)| Hermes ✅ 維持 | — | + +**淨效果**(critic-A11 B5 修補:算術重核): +- 任務 3/4/5 從 OpenClaw Gemini 遷移至 Hermes Ollama → 月省 ~9.5M Gemini tokens(A7+A8 試算) +- 任務 11 降頻(Meta 自審 6h → 12:00)→ 月省 ~2.25M Gemini tokens(A10 實測超預估) +- 合計月省 ~11.75M tokens(50M → 38.25M = **-23.5%**) +- Hermes 流量從 ~30M tokens/月 → 預估 ~120M tokens/月(**+400%,成本不變**) + +--- + +## A7 / A8 / A10 落地對照 + +| Task | 範圍 | 狀態 | 對應檔案 | +|---|---|---|---| +| **A7** | OpenClaw Q&A → qwen3:14b(feature flag)| ✅ Phase 3 完成,待 Phase 4 黃金集 A/B 驗證 | `services/openclaw_strategist_service.py` Q&A 入口 | +| **A8** | Hermes daily 摘要 + KPI 規則引擎 | 規劃中(Phase 4)| `services/hermes_analyst_service.py` 新增 daily_summary / kpi_compute 方法 | +| **A10** | OpenClaw Meta 自審降頻 + 程式瘦身 -29% | 規劃中(Phase 7-8)| `services/openclaw_strategist_service.py` 拆出戰術層遷移至 Hermes | + +--- + +## 預期效益(量化) + +| 指標 | 戰前 | 戰後 | 變化 | +|---|---|---|---| +| Gemini 月支出(tokens) | ~50M | ~38.25M | **-23.5%** | +| OpenClaw 程式碼行數 | 2677(HEAD 1831 + Phase 3/4 增量)| Phase 4 +18 行(A10 保守抽出)| Phase 4 行數目標未達;主戰果 = Meta 降頻 | +| Hermes 流量(tokens) | ~30M | ~120M | **+400%($0)** | +| Telegram NL 第一響應延遲 p50 | ~2.5s(Gemini)| ~1.2s(Ollama 本地)| **-52%**(待 ai_calls 實測) | +| 戰術層 fallback 鏈深度 | 3(Gemini→NIM→字面)| 2(Hermes→規則引擎)| **-33%** | +| 月成本(Gemini API)| baseline | -23.5% | 戰役 v5.0 KPI | + +> 上述數字為 Phase 0 audit 推算,Phase 5 報表上線後以 `ai_calls` 實測值修訂。 + +--- + +## Alternatives Considered + +| 方案 | 不採用原因 | +|---|---| +| **A. 維持 ADR-018 現狀(OpenClaw 全包)** | Gemini 月支出無上限增長;Telegram NL 延遲 p50 ≥ 2.5s 體驗差;Hermes 程式碼長期被冷凍 | +| **B. OpenClaw 全切 Ollama(廢 Gemini)** | 月/年報品質下降 10-20%(phase0_research Section 1);Code Review / PPT vision 補不齊;繁中商業敘事短板(TMMLU+ 論文)| +| **C. 把 Hermes 升格 L3、廢 OpenClaw** | OpenClaw 已有 KM 沉澱 / Meta 自審 / 戰略架構,砍掉等於拋棄 1.8% → 80% 觀測能力(ADR-019);ADR-018 四 Agent 控制面被破壞 | +| **D. 引入第五個 Agent 接戰術層** | 增加心智負擔;Hermes 已是現成 L1 Observer,升格成本最低;統帥 FinOps 視角不偏好新增複雜度 | +| **E. 全部走 NIM(避開 Gemini 帳單)** | NIM 80 calls/day 配額硬限,月/年報已經會爆;NIM 模型品質 < Gemini 2.5 Flash | + +--- + +## Consequences + +### 正面 + +1. **成本下降 23%**:戰役 v5.0 KPI 第一目標達成。 +2. **Telegram NL 延遲減半**:本地 Ollama 三主機級聯 vs Gemini API round-trip。 +3. **Hermes 體質升級**:從「Observer」升「主入口」,未來 Phase 11 RAG 攔截可直接接 Hermes 流量。 +4. **程式碼瘦身**:A10 採保守抽出(2 個 helper),行數 -25% 目標未達;主戰果為 Meta 降頻(月省 2.25M tokens)。深度瘦身延至 Phase 7+。 +5. **ADR-019 真正落地**:`openclaw_decide()` 第一響應改走 Hermes,agent suggestion shortcut 不再 = OpenClaw 全包。 + +### 負面 + +1. **A7 切換有黃金集 A/B 風險**:qwen3:14b 繁中短板若實測差距 > 30%,需走 Plan B(Llama-3-Taiwan-70B 或退回 Gemini 加 prompt cache)。 +2. **A10 重構工程量大**:A10 已執行(Phase 4),採保守抽出避險;4 個報告函數結構差異大,深度瘦身(daily_summary / kpi_compute 遷移至 Hermes)需獨立 Phase,refactor-specialist 範圍。 +3. **Hermes 變主入口後故障半徑放大**:原本 Hermes 掛 = 規則引擎兜底;現在掛 = 60% Telegram NL 體驗劣化。需強化 mark_unhealthy + 三主機級聯(ADR-027 / ADR-028)。 + +### 風險與緩解 + +| 風險 | 機率 | 緩解 | +|---|---|---| +| qwen3:14b 繁中黃金集 A/B 紅燈 | 中 | Phase 4 跑 50 題繁中商業 Q&A 黃金集;< 0.75 BERTScore 自動 fallback Gemini | +| Hermes 故障 = NL 體驗崩 | 低 | 三主機級聯 + mark_unhealthy 30s(ADR-028 支柱 1);規則引擎兜底(ADR-004)| +| A8 daily 摘要品質 < 戰前 | 中 | Hermes 模板 + Gemini 200 字洞察混合;不是純 Hermes 全自動 | +| OpenClaw 重構引入 regression | 中 | A10 走 refactor-specialist + 完整 regression test;feature flag 灰度 | + +### 降級策略 + +- A7 feature flag off → 退回 OpenClaw Gemini Q&A +- A8 Hermes daily 失敗 → 走原 OpenClaw Gemini daily(保留代碼路徑直至 Phase 8 確認穩定) +- 三主機全掛 → 規則引擎兜底 + Telegram 模板化告警 + +--- + +## Verification + +### V1:流量分布 +```sql +-- Hermes vs OpenClaw 月流量比 +SELECT + CASE + WHEN caller LIKE 'hermes%' THEN 'hermes' + WHEN caller LIKE 'openclaw%' THEN 'openclaw' + ELSE 'other' + END AS tower, + SUM(input_tokens + output_tokens) AS total_tokens, + COUNT(*) AS calls, + SUM(cost_usd) AS cost +FROM ai_calls +WHERE called_at >= NOW() - INTERVAL '30 days' +GROUP BY 1 ORDER BY total_tokens DESC; +-- 期望:hermes tokens ≥ 4× openclaw tokens;openclaw cost > hermes cost(hermes = $0) +``` + +### V2:A7 切換驗證 +```sql +-- Q&A 第一響應應 ≥ 80% 走 Hermes / qwen3 +SELECT model, COUNT(*) AS calls, + ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct +FROM ai_calls +WHERE caller IN ('openclaw_qa', 'hermes_qa', 'openclaw_bot_main') + AND called_at >= NOW() - INTERVAL '7 days' +GROUP BY model ORDER BY calls DESC; +-- 期望:qwen3:14b + hermes3 合計 ≥ 80% +``` + +### V3:Gemini 月支出 +- 每月 1 日 token report Section 4「by provider 月成本」對比 baseline 月份 +- 預期 `gemini` provider 月成本下降 ≥ 20% + +### V4:A8 / A10 上線 +- Phase 4 後 `caller='hermes_daily_summary'` 應每日 1 筆出現於 ai_calls +- Phase 8 後 `services/openclaw_strategist_service.py` 行數 ≤ 1300 + +--- + +## References + +- ADR-001 三 Agent 自主學習分工(戰役起點,Hermes/NemoTron/OpenClaw 原始定義) +- ADR-002 pgvector 唯一向量庫(Hermes embedding 落點) +- ADR-004 NemoTron fallback chain(OpenClaw NIM 鏈來源) +- ADR-018 四 Agent 控制面(本 ADR 細化的 L1/L3 角色) +- ADR-019 Telegram Agentic Layer(Hermes 主入口落地路徑) +- ADR-021 EA HITL pre-fetch(Hermes 預跑 5s timeout 設計) +- ADR-027 Primary Ollama on GCP(Hermes 主塔的硬體依託) +- ADR-028 LLM 路由統一準則(雙塔分工是支柱 3) +- `docs/phase0_audit_report_20260503.md`(34 caller 流量分布) +- `docs/phase0_research_report_20260503.md` Section 1(qwen3:14b vs Gemini 品質評估) +- 相關 memory:`project_three_agent_division.md`、`feedback_agent_action_ladder.md`、`reference_telegram_endpoints_map.md` diff --git a/docs/adr/README.md b/docs/adr/README.md index bb24234..afee54b 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -48,6 +48,9 @@ | [024](ADR-024-ppt-system-wave2-forecast-and-deprecations.md) | PPT 系統 Wave 2 — 檔期前瞻 / 多活動比較 + bcg/growth 廢除 | Accepted | 2026-05-03 | | [025](ADR-025-ppt-system-wave3-new-product-and-market-intel.md) | PPT 系統 Wave 3 — 新品 30 天追蹤 + 市場情報週報 | Accepted | 2026-05-03 | | [026](ADR-026-ppt-system-price-elasticity-and-final-roadmap.md) | PPT 系統 — 價格彈性報告 + 完整戰役收尾路線圖 | Accepted | 2026-05-03 | +| [027](ADR-027-primary-ollama-on-gcp.md) | Primary Ollama 遷移至 GCP 高效能主機(v5.0 戰役後追加附錄:三主機架構 / 4 fallback 鏈 / 廢止 188 Ollama) | Accepted | 2026-05-03 | +| [028](ADR-028-llm-routing-unified-principles.md) | LLM 路由統一準則 — Ollama-First 五大支柱(補述 ADR-027) | Accepted | 2026-05-03 | +| [029](ADR-029-hermes-first-twin-tower.md) | Hermes-First 雙塔分工(戰術主塔 / 戰略副塔,Gemini 月支出 -23%) | Accepted | 2026-05-03 | ## 規範 diff --git a/run_scheduler.py b/run_scheduler.py index c188e14..3278c57 100644 --- a/run_scheduler.py +++ b/run_scheduler.py @@ -6,9 +6,9 @@ run_scheduler.py — momo-scheduler 容器入口點 每 30 分鐘:auto_import、whitepage_check 每 1 小時:momo、edm、festival 每 4 小時:competitor_price_feeder、icaim_analysis - 每 6 小時:openclaw_meta_analysis、quality_rescore + 每 6 小時:quality_rescore 每 12 小時:dedup_batch - 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、ai_smoke_summary(09:10)、pchome_match_backfill(10:30) + 每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、ai_smoke_summary(09:10)、pchome_match_backfill(10:30)、openclaw_meta_analysis(12:00, Phase 4 降頻)、daily_token_report(23:55) 每 1 週 :weekly_strategy(週一 06:00) 每 1 月 :monthly_report(每月1日 07:00) """ @@ -94,8 +94,10 @@ def _register_schedules(): schedule.every(4).hours.do(run_icaim_analysis_task) logger.info("📅 每 4 小時:icaim_analysis") - schedule.every(6).hours.do(run_openclaw_meta_analysis_task) - logger.info("📅 每 6 小時:openclaw_meta_analysis") + # Operation Ollama-First v5.0 Phase 4:Meta 自審降頻 6h → 每日 12:00(月省 ~1.875M Gemini tokens) + # icaim_analysis 內原本 line 2233/2253 的額外觸發已同步移除(避免重複呼叫) + schedule.every().day.at("12:00").do(run_openclaw_meta_analysis_task) + logger.info("📅 每日 12:00:openclaw_meta_analysis(Phase 4 降頻:原 6h)") schedule.every(6).hours.do(run_quality_rescore_task) logger.info("📅 每 6 小時:quality_rescore") @@ -124,6 +126,10 @@ def _register_schedules(): schedule.every().day.at("10:30").do(run_pchome_match_backfill_task) logger.info("📅 每日 10:30:pchome_match_backfill") + # Operation Ollama-First v5.0 — Phase 1 收尾:每日 23:55 LLM Token 日報 + schedule.every().day.at("23:55").do(run_daily_token_report_task) + logger.info("📅 每日 23:55:daily_token_report") + # 每月1日 07:00 月報(schedule 不支援 every().month,用每日 07:00 + 日期判斷) def _monthly_report_gate(): from datetime import datetime as _dt @@ -134,6 +140,31 @@ def _register_schedules(): logger.info("📅 每月1日 07:00:monthly_report") +def run_daily_token_report_task(): + """每日 23:55 — Operation Ollama-First v5.0 Phase 1 收尾:LLM Token 日報。 + + 任務: + 1. 查 ai_calls 過去 24h 統計(總覽 / 供應商 / TOP caller / 成本 / 趨勢 / 告警) + 2. 推 Telegram + 寫 ai_insights(type='daily_token_report') + + 紀律: + - 失敗安全:DB 查不到資料 → 推「⚠️ 報表生成失敗」訊息但不爆 scheduler + - 不影響其他排程:例外完全吞掉,僅 log error + """ + try: + from services.token_report_service import send_daily_report + result = send_daily_report() + logger.info( + "[TokenReport] sent=%s failed=%s chars=%s ok=%s", + result.get('sent'), result.get('failed'), + result.get('chars'), result.get('ok'), + ) + except Exception as e: + logger.error(f"[TokenReport] task failed: {e}", exc_info=True) + # 不再嘗試 event_router(避免循環依賴),純 log 即可 + # 統帥可從 scheduler logs 觀察失敗 + + def run_cleanup_agent_context(): """每日 03:30 — 清理 agent_context 表中已過期的 TTL 記錄(migration 018 定義)""" from database.manager import get_session diff --git a/scheduler.py b/scheduler.py index a45179e..2e8149b 100644 --- a/scheduler.py +++ b/scheduler.py @@ -2229,12 +2229,9 @@ def run_icaim_analysis_task(): if not result.threats: logging.info("[Scheduler] [ICAIM] 無威脅商品,跳過 NemoTron dispatch") - # 仍觸發 OpenClaw Meta-Analysis 更新系統效能快照 - try: - from services.openclaw_strategist_service import generate_meta_analysis_report - generate_meta_analysis_report() - except Exception as _meta_e: - logging.warning(f"[Scheduler] [ICAIM] Meta-Analysis 非阻塞失敗: {_meta_e}") + # Operation Ollama-First v5.0 Phase 4:移除原 inline meta_analysis 觸發 + # 原因:呼叫點 #16 月耗 ~2.5M Gemini tokens(icaim 4h × 6 + cron 6h × 4 = 10/天) + # 改由 run_scheduler 每日 12:00 單一觸發;此處僅 log,不再呼叫。 return # Step 2:NemoTron 派發器 → Telegram @@ -2250,12 +2247,8 @@ def run_icaim_analysis_task(): ) _save_stats('icaim_dispatch', {**dispatch_result, "status": "Success"}) - # Step 3:派發完成後觸發 OpenClaw Meta-Analysis(非阻塞) - try: - from services.openclaw_strategist_service import generate_meta_analysis_report - generate_meta_analysis_report() - except Exception as _meta_e: - logging.warning(f"[Scheduler] [ICAIM] Meta-Analysis 非阻塞失敗: {_meta_e}") + # Operation Ollama-First v5.0 Phase 4:原 Step 3 inline Meta-Analysis 觸發已移除 + # 改由 run_scheduler 每日 12:00 單一觸發(Phase 4 降頻 6h → 24h,月省 ~1.875M tokens) except Exception as e: import traceback as _tb diff --git a/services/elephant_alpha_orchestrator.py b/services/elephant_alpha_orchestrator.py index 32cd0ce..97ebb49 100644 --- a/services/elephant_alpha_orchestrator.py +++ b/services/elephant_alpha_orchestrator.py @@ -85,6 +85,9 @@ class ElephantAlphaOrchestrator: ), "openclaw": AgentCapability( name="OpenClaw Strategist", + # LOCKED-GEMINI: EA HITL 戰略決策影響統帥行動,要最高品質推理 + # 未來可升 Claude Sonnet 4.6 (agentic 工具使用佳) — Phase 7 任務 + # ADR-028 鎖定場景 #6 model="gemini-2.0-flash", strengths=["strategic_planning", "market_analysis", "insight_generation"], limitations=["real_time_execution", "direct_actions"], diff --git a/services/mcp_collector_service.py b/services/mcp_collector_service.py index adab8a4..2b6658b 100644 --- a/services/mcp_collector_service.py +++ b/services/mcp_collector_service.py @@ -18,6 +18,7 @@ import json import logging import os import time +import requests from datetime import datetime, timedelta from typing import Any, Dict, List, Optional @@ -28,7 +29,16 @@ logger = logging.getLogger(__name__) GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "") MCP_CACHE_TTL_HOURS = int(os.getenv("MCP_CACHE_TTL_HOURS", "24")) +# LOCKED-GEMINI: MCP 即時情報需 Google Search Grounding,唯一聯網能力(ADR-028 鎖定場景 #1) +# Ollama 為離線模型,知識截止於訓練日,不可取代 grounding。 MCP_MODEL = os.getenv("MCP_GEMINI_MODEL", "gemini-2.0-flash") +MCP_FALLBACK_MODEL = "gemini-1.5-flash" + +try: + from services.ollama_service import resolve_ollama_host + _OLLAMA_AVAILABLE = True +except ImportError: + _OLLAMA_AVAILABLE = False # ── 查詢主題定義 ──────────────────────────────────────────────────────────── _SEARCH_TOPICS = { @@ -171,7 +181,47 @@ class MCPCollectorService: return content return self._fallback_topic_content(topic, "Gemini 回傳空內容,使用本地行銷情報。") except Exception as e: - logger.warning("[MCP] 搜尋失敗 topic=%s: %s", topic, e) + logger.warning("[MCP] Gemini 2.0 Grounding failed topic=%s: %s, trying 1.5 Flash", topic, e) + # 級別 2:嘗試 1.5 Flash (通常配額較穩) + try: + model = self._genai.GenerativeModel(model_name=MCP_FALLBACK_MODEL, tools=["google_search"]) + response = model.generate_content(prompt) + content = response.text or "" + if content and not self._looks_unreliable(content): + self._write_cache(topic, content) + return content + except Exception as e2: + logger.warning("[MCP] Gemini 1.5 Flash also failed: %s", e2) + + # 級別 3:嘗試 GCP Ollama (使用模型內置知識作為最後防線) + if _OLLAMA_AVAILABLE: + try: + logger.info("[MCP] Using GCP Ollama for market insight fallback") + host = resolve_ollama_host() + ollama_prompt = ( + f"你是一位精通台灣電商市場的分析師。目前無法取得即時搜尋結果," + f"請根據你的知識儲備,針對以下主題提供 2026 年可能的市場動態或洞察(繁體中文,300字以內):\n" + f"主題:{query}\n\n" + "請註明:『(此為基於歷史趨勢的預測性洞察)』" + ) + r = requests.post( + f"{host}/api/generate", + json={ + 'model': os.getenv('OPENCLAW_OLLAMA_MODEL', 'qwen2.5-coder:7b'), + 'prompt': ollama_prompt, + 'stream': False, + 'options': {'num_predict': 800, 'temperature': 0.4} + }, + timeout=45 + ) + r.raise_for_status() + content = r.json().get('response', '').strip() + if content: + # 不進快取,因為這是預測性內容 + return content + except Exception as e3: + logger.warning("[MCP] Ollama fallback also failed: %s", e3) + return self._fallback_topic_content(topic, f"即時外部搜尋暫不可用:{type(e).__name__}") @staticmethod diff --git a/services/telegram_templates.py b/services/telegram_templates.py index b794f38..173fd45 100644 --- a/services/telegram_templates.py +++ b/services/telegram_templates.py @@ -532,6 +532,41 @@ def _send_telegram(msg: str, chat_ids: Optional[list] = None, return _send_telegram_raw(msg, chat_ids=chat_ids, reply_markup=reply_markup) +# ══════════════════════════════════════════════════════════════════════════════ +# LLM Token 日報模板(Operation Ollama-First v5.0 — Phase 1 收尾) +# 對應 services/token_report_service.py +# ══════════════════════════════════════════════════════════════════════════════ + +# Telegram 單訊息上限(保險絲,sendMessage HTML 上限為 4096 字元) +_DAILY_TOKEN_REPORT_MAX_CHARS = 4096 + + +def daily_token_report(report_html: str, + footer_url: Optional[str] = None) -> str: + """LLM Token 日報訊息包裝(HTML parse_mode)。 + + 本函數只負責「附上 footer + 截斷至 Telegram 上限」;報表本體由 + services/token_report_service.generate_daily_report() 產出,已含 HTML escape。 + + Args: + report_html: 已 escape 的 HTML 報表字串 + footer_url: 選填 admin 後台連結,會自動 escape + + Returns: + ≤ 4096 字元的 HTML 字串,安全送 Telegram + """ + body = report_html or "" + if footer_url: + body = f"{body}\n📎 詳細日誌" + + if len(body) <= _DAILY_TOKEN_REPORT_MAX_CHARS: + return body + + # 超長 → 截斷並加省略尾(保留 80 字給 trailing notice) + truncated = body[: _DAILY_TOKEN_REPORT_MAX_CHARS - 80] + return truncated + "\n\n... (訊息過長已截斷;完整內容存於 ai_insights)" + + # ══════════════════════════════════════════════════════════════════════════════ # 決策回執模板(L2 / L3 按鈕點擊後編輯原訊息使用) # ADR-012 Phase 4:審計留痕 — 操作者、時間、action、結果