Files
ewoooc/services/llm_caller_registry.py
OoO 98063059c2
All checks were successful
CD Pipeline / deploy (push) Successful in 2m50s
feat(p14-18): PPT vision + DeepSeek 直連 + caller_registry + Hermes 強化 + postmortem
Operation Ollama-First v5.0 / Phase 14-18 全套(statesman 批准全部)

Phase 14 — services/ppt_vision_service.py (新檔, 200+ 行)
- minicpm-v:latest(GCP Primary 已拉 5.5GB,代替 qwen2-vl 不存在)
- check_image(image_path) → VisionResult.issues_found 視覺異常清單
- 走 resolve_ollama_host 三主機 retry + mark_unhealthy
- 繁中強制 system prompt + 結構化解析 ⚠️ marker
- feature flag PPT_VISION_ENABLED 預設 OFF

Phase 15 — services/deepseek_service.py (新檔, 170+ 行)
- DeepSeek API 直連 (api.deepseek.com/v1),OpenAI-compatible
- 取代部分 OpenRouter 路徑(直連便宜 ~30-50% + 延遲低)
- deepseek-chat ($0.014/$0.28) / deepseek-reasoner ($0.14/$2.19)
- feature flag DEEPSEEK_DIRECT_ENABLED 預設 OFF
- DeepSeekResponse 含 input_tokens/output_tokens/duration_ms

Phase 16 — services/llm_caller_registry.py (新檔, 130+ 行)
- CALLER_REGISTRY frozenset 集中管理 35+ 個 caller 名(ADR-028 白名單)
- assert_known_caller(strict=False) 整合到 ai_call_logger __init__
- 不在 registry → log warning(不 raise,保留擴展彈性)
- list_callers_by_service() 分組除錯
- 解 critic-A11 第 3 輪 L4 修補(命名分散三層)

Phase 17 — _is_low_quality_response 4 條新規則(A2 警訊深化)
- 規則 5:純英文回應(中文字元 < 30%)
- 規則 6:thinking-mode 漏洞(<think>...</think> 洩漏)
- 規則 7:重複迴圈偵測(前 50 字出現 ≥ 3 次)
- 規則 8:佔位符未填充({{var}} / [TODO] / <待填>)

Phase 18 — docs/operation_ollama_first_v5_postmortem.md (新檔)
- 戰役完整時間軸(Day 1-2)
- 3 大決策替代分析
- 4 個 critical hotfix 教訓
- Owen 三護欄落地對照
- KPI 達成度(Wave 1 提前 4 天 / Wave 2 提前 10 天)
- 統帥手動清單 + 7 條未來戰役教訓

Phase 13 補強(合併本 commit):
- ai_call_logger COST_TABLE 補 7 個新模型(qwen3:14b / qwen2.5:7b-instruct
  / qwen2.5-coder:32b / qwen2-vl:7b / deepseek-r1:14b / gemma3:4b / minicpm-v)

regression: 214 unit tests 全綠(4:02 跑完),2 skipped

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:19:13 +08:00

137 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
services/llm_caller_registry.py
Operation Ollama-First v5.0 / Phase 16 — caller 名稱集中註冊critic-A11 L4 修補)
問題caller 命名分散在 ai_call_logger 註解 / migrations/024 SQL 註解 /
各 service hardcode三層不一致時報表會看到鬼影gemini / Gemini / gemini-flash
修補:本檔為 single source of truthai_call_logger 啟動時驗證;
service 寫死 caller 字串若不在 registry → log warning不 raise保留擴展彈性
依 ADR-028 caller 白名單30+ 個 caller
"""
from __future__ import annotations
import logging
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────────────────────────────────────
# Caller 白名單(按服務分組,與 ai_call_logger 註解 + migration 024 對齊)
# ─────────────────────────────────────────────────────────────────────────────
CALLER_REGISTRY: frozenset = frozenset({
# Hermes 競價分析hermes_analyst_service
'hermes_analyst', # _call_hermes_batch
'hermes_intent', # intent_classify (L1 NLP)
'hermes_ea_prefetch', # EA HITL pre-fetch (ADR-021)
# KM Embeddingopenclaw_learning_service
'km_embedding_worker', # 60s retry queue worker
'km_embedding_realtime', # _build_semantic_rag_context
# AiderHealaider_heal_executor
'aider_heal', # SSH CLI 跑 Aider暫不接 logger
# MCP Collectormcp_collector_service
'mcp_l1_grounding', # Gemini 2.0 Flash Grounding
'mcp_l2_grounding', # Gemini 1.5 Flash Grounding
'mcp_l3_ollama', # Ollama 知識庫兜底
'mcp_collector', # Phase 10.5 omnisearch 入口
# OpenClaw 戰略openclaw_strategist_service
'openclaw_daily', # 每日報告
'openclaw_weekly', # 週一 06:00
'openclaw_monthly', # 每月 1 日
'openclaw_meta', # Meta 自審 12:00
'openclaw_qa', # Telegram Q&A
'openclaw_daily_insight', # Phase 3 A8 拆分後的 200 字 Gemini 洞察
'openclaw_strategist', # Phase 10.5 mcp_router caller
# NemoTron 派遣nemoton_dispatcher_service
'nemotron_dispatch', # NIM 8B 主路徑 / qwen3 主路徑
# Code Reviewcode_review_pipeline_service
'code_review_hermes', # Step 2 Hermes 掃描
'code_review_openclaw', # Step 3 OpenClaw 評估Gemini 或 Claude
'code_review_elephant', # Step 4 ElephantAlpha 49B
'code_review_openclaw_gemini', # Phase 7 Claude 失敗 fallback Gemini
# ElephantAlphaelephant_alpha_*
'ea_engine', # _execute_autonomous_decision (Gemini orchestrate)
# PPT 簡報routes/openclaw_bot_routes
'ppt_gemini', # Gemini Flash 主分析
'ppt_ollama', # Ollama 失敗 fallback
'ppt_nim', # NIM deepseek-v3.2 主分析
'ppt_vision', # Phase 14 PPT 視覺檢查qwen2-vl
# Sales / Trendroutes/ai_routes + routes/trend_routes
'sales_copy', # 文案生成
'trend_match', # 商品比對
'trend_qa', # Web Search Q&A
'product_insights', # 商品洞察
'trend_keywords', # 趨勢關鍵字
# Telegram Bot
'tg_bot_copy', # /copy 文案
'tg_bot_copy_v2', # second copy entrance
'openclaw_bot_main', # OpenClaw Bot 主鏈 Ollama
'openclaw_bot_gemini', # Bot Gemini fallback
'openclaw_bot_nim', # Bot NIM fallback
# 其他
'bot_api_copy', # bot_api_routes
'trend_crawler', # trend_crawler_service
'ai_provider_generic', # ai_provider 抽象層
})
def is_known_caller(caller: str) -> bool:
"""檢查 caller 是否在白名單"""
return caller in CALLER_REGISTRY
def assert_known_caller(caller: str, strict: bool = False) -> None:
"""ai_call_logger 啟動時或寫入時驗證。
Args:
caller: 待驗證的 caller 名
strict: True → 不在 registry 時 raiseFalse預設→ 只 log warning
依 ADR-028新增 caller 必須先入 ADR + registry再上 commit。
"""
if not is_known_caller(caller):
msg = f"unknown caller: {caller!r} not in CALLER_REGISTRY (ADR-028)"
if strict:
raise ValueError(msg)
logger.warning(f"[CallerRegistry] {msg} — see services/llm_caller_registry.py")
def list_callers_by_service() -> dict:
"""除錯/文件用:分組列出所有合法 caller"""
return {
'hermes': [c for c in CALLER_REGISTRY if c.startswith('hermes_')],
'openclaw': [c for c in CALLER_REGISTRY if c.startswith('openclaw_') and not c.startswith('openclaw_bot_')],
'openclaw_bot': [c for c in CALLER_REGISTRY if c.startswith('openclaw_bot_')],
'mcp': [c for c in CALLER_REGISTRY if c.startswith('mcp_')],
'code_review': [c for c in CALLER_REGISTRY if c.startswith('code_review_')],
'ppt': [c for c in CALLER_REGISTRY if c.startswith('ppt_')],
'tg_bot': [c for c in CALLER_REGISTRY if c.startswith('tg_bot_')],
'km_embedding': [c for c in CALLER_REGISTRY if c.startswith('km_embedding_')],
'sales_trend': ['sales_copy', 'trend_match', 'trend_qa',
'product_insights', 'trend_keywords'],
'misc': ['ea_engine', 'aider_heal', 'nemotron_dispatch',
'bot_api_copy', 'trend_crawler', 'ai_provider_generic'],
}
__all__ = [
'CALLER_REGISTRY',
'is_known_caller',
'assert_known_caller',
'list_callers_by_service',
]