151 lines
6.7 KiB
Python
151 lines
6.7 KiB
Python
#!/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 truth,ai_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 Embedding(openclaw_learning_service)
|
||
'km_embedding_worker', # 60s retry queue worker
|
||
'km_embedding_realtime', # _build_semantic_rag_context
|
||
|
||
# AiderHeal(aider_heal_executor)
|
||
'aider_heal', # SSH CLI 跑 Aider(暫不接 logger)
|
||
|
||
# MCP Collector(mcp_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_daily_gemini_fallback',
|
||
'openclaw_daily_nim',
|
||
'openclaw_weekly', # 週一 06:00
|
||
'openclaw_weekly_gemini_fallback',
|
||
'openclaw_weekly_nim',
|
||
'openclaw_monthly', # 每月 1 日
|
||
'openclaw_monthly_gemini_fallback',
|
||
'openclaw_monthly_nim',
|
||
'openclaw_meta', # Meta 自審 12:00
|
||
'openclaw_meta_gemini_fallback',
|
||
'openclaw_meta_nim',
|
||
'openclaw_qa', # Telegram Q&A
|
||
'openclaw_qa_gemini_fallback', # Telegram Q&A Gemini fallback
|
||
'openclaw_qa_nim', # Telegram Q&A NIM fallback
|
||
'openclaw_daily_insight', # Phase 3 A8 拆分後的 200 字 Ollama-first 洞察
|
||
'openclaw_daily_insight_gemini_fallback',
|
||
'openclaw_daily_insight_nim',
|
||
'openclaw_strategist', # Phase 10.5 mcp_router caller
|
||
|
||
# NemoTron 派遣(nemoton_dispatcher_service)
|
||
'nemotron_dispatch', # NIM 8B 主路徑 / qwen3 主路徑
|
||
|
||
# Code Review(code_review_pipeline_service)
|
||
'code_review_hermes', # Step 2 Hermes 掃描
|
||
'code_review_openclaw', # Step 3 OpenClaw 評估(Ollama-first;Claude optional fallback)
|
||
'code_review_elephant', # Step 4 ElephantAlpha 49B
|
||
'code_review_openclaw_gemini', # Ollama/Claude 失敗後的 Gemini fallback
|
||
|
||
# ElephantAlpha(elephant_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 / Trend(routes/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_image', # Bot 圖片商品辨識 Ollama-first
|
||
'openclaw_bot_image_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 時 raise;False(預設)→ 只 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',
|
||
]
|