V10.538 對齊 ai_calls ollama_other provider
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s

This commit is contained in:
OoO
2026-06-01 02:37:12 +08:00
parent 6e70a6eb47
commit 12c8c7e94d
9 changed files with 105 additions and 5 deletions

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.538 修 ai_calls provider CHECK 對齊Hermes/Ollama 全失敗或未選定 host 時的 `ollama_other` 只作 telemetry bucketmigration 043 放行此值,`ai_call_logger` 也會將空值/unknown/非白名單 provider 正規化,避免觀測寫入失敗。
- V10.537 將 V10.536 focused exact 線接進 `run_retryable_candidate_revalidation()` 窄門:既有 `true_low_confidence` 舊候選若命中新品線且無 hard veto / 型別、款式、香味、件數、組合阻擋,就可重新走 matcher 寫入正式價差;有色號/香味/即期等阻擋仍不進回刷。
- V10.536 補 PChome 高分 `true_low_confidence` 安全救回線:新增花美水 Relax 薰衣草潤滑凝膠 1.7g x3、St.Clare 私密呼呼慕斯 x2 / 慕斯+噴霧組、BIOPEUTIC 果酸煥膚水凝乳 20% 150ml、台塑生醫嬰兒沐浴洗髮 3 件組、Elizabeth Arden 八小時護唇膏 SPF15 3.7g x3、理膚寶水全面修復潤唇膏 7.5ml focused total-price 規則;這些都要求同品牌、同品線與同規格/同組合,仍保留色號、香味、款式敏感品的 `variant_selection_review` 防線。
- V10.535 修 ElephantAlpha 價格 trigger statement timeout`price_drop_alert` / `market_opportunity` / DB evidence prefetch 改為先篩最近有效 PChome identity_v2再用 `JOIN LATERAL` 查單一 SKU 最新 MOMO 價格;保留 match_score/tags/diagnostic evidence避免 scheduler 週期性重查整張 `price_records`。

View File

@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.537"
SYSTEM_VERSION = "V10.538"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -32,6 +32,7 @@
- OpenClaw Telegram 圖片商品辨識也必須 Ollama-first`_identify_product_name_with_ollama_vision()` 透過 `OllamaService` 嘗試 GCP-A → GCP-B → 111Gemini 只允許以 `openclaw_bot_image_gemini` caller 作為失敗後備援。
- OpenClaw 週報、月報、Meta analysis、日報洞察、Telegram PPT 分析與 MCP fallback 也必須 Ollama-firstGemini caller 只能帶 `_gemini_fallback` 或明確 fallback caller 語意,且不得先於 Ollama/NIM 被呼叫。OpenClaw strategy 的 Ollama `keep_alive` 預設為 `5m`,避免報告型任務把 GCP-B/111 runner 長駐 24h。
- OpenClaw 週報、月報、Meta analysis、日報洞察與每日報告的 Gemini/NIM 備援 caller 必須登錄在 caller registry、AI 觀測台 agent group 與 Telegram 狀態統計,避免 fallback 用量被歸類為未知或漏算。
- `ai_calls.provider='ollama_other'` 只允許作為 unresolved/unknown Ollama telemetry bucket例如全 host 失敗、尚未選定實際 GCP-A/GCP-B/111 host 或舊 caller 未帶 host不得把 `ollama_other` 當成實際路由目標或新增非核准 Ollama host。
- Gemini API 出站有第二道 kill switch`GEMINI_FALLBACK_ENABLED` 預設為 `false`。即使 `GEMINI_API_KEY` 存在,通用 AI fallback、OpenClaw 報告/QA/PPT/圖片、MCP Grounding 與 Code Review L3 都不得呼叫 Gemini只有操作員明確設為 `true`Gemini 才能作緊急備援。
- `docker-compose.yml``momo-app``scheduler``telegram-bot` 必須明確設定 `GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}``GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}``.env` 可保留 `GEMINI_API_KEY`,但不得因 key 存在就讓核心容器產生 Gemini 付費出站。
- Gemini 不可被任何狀態面板或 router 推薦為主提供者:`AIProviderService._get_recommended_provider()` 不得回傳 `gemini`,只能顯示為 fallback 狀態;`llm_model_router``ea_engine` 若收到 `gemini-*` default 必須改回 `hermes3:latest`,需要深推理時才升本地 `deepseek-r1:14b`

View File

@@ -13,6 +13,7 @@
## 📅 詳細更新日誌 (考古存檔)
### 2026-06-01PChome 比價新鮮度操作閉環
- **V10.538 ai_calls provider CHECK 對齊**: 正式 scheduler Hermes/Ollama 全失敗時會以 `provider=ollama_other` 記錄未知/未選定 host`ai_calls.chk_ai_calls_provider` 舊白名單未包含此 telemetry bucket導致觀測寫入再撞 CHECK。新增 migration 043 放行 `ollama_other`,並讓 `ai_call_logger` 將空值、unknown、非白名單 provider 字串正規化為允許值;`ollama_other` 僅作遙測分類,不是模型路由目標。
- **V10.537 focused exact revalidation queue 接線**: V10.536 只補 matcher 規則會影響新抓取,但無法批次回收既有 `true_low_confidence` 舊候選。`run_retryable_candidate_revalidation()` 新增 focused true-low 窄門:花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫、Elizabeth Arden 與理膚寶水若無 hard veto 與型別/款式/香味/件數/組合阻擋,可進重評;`variant_selection_review` 僅在這些具名安全線允許重跑,其他人工池不打開。
- **V10.536 高分 true_low_confidence focused exact 救回**: production 抽樣顯示剩餘未覆蓋集中在 `identity_veto``true_low_confidence`,其中部分 1.0 分樣本是同品牌、同品線、同規格/同組合,但因多件組或護唇/私密護理類型辨識保守而停在 manual review。新增花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏、理膚寶水全面修復潤唇膏的 focused total-price 規則;不放寬 `MIN_MATCH_SCORE`,也不移除色號/香味/款式防線。
- **V10.535 ElephantAlpha price trigger 查詢瘦身**: 正式 scheduler 日誌顯示 `price_drop_alert` trigger 對整張 `price_records``DISTINCT ON` 最新價造成 statement timeout。`price_drop_alert``market_opportunity` 與 EA DB evidence prefetch 改為先篩最近有效 PChome identity_v2 競品,再用 `JOIN LATERAL` 只查該 SKU 最新 MOMO 價格,保留 match_score/tags/diagnostic evidence 給 Telegram HITL不再用全表最新價子查詢。

View File

@@ -0,0 +1,32 @@
-- Migration 043: allow telemetry-only ollama_other provider
-- Date: 2026-06-01
--
-- Runtime code maps unresolved or unknown Ollama hosts to `ollama_other`.
-- This is not a routing target; it is an observability bucket for failures
-- that happen before a concrete GCP-A/GCP-B/111 host is selected.
BEGIN;
ALTER TABLE IF EXISTS ai_calls
DROP CONSTRAINT IF EXISTS chk_ai_calls_provider;
ALTER TABLE IF EXISTS ai_calls
ADD CONSTRAINT chk_ai_calls_provider
CHECK (
provider IN (
'gcp_ollama',
'ollama_secondary',
'ollama_111',
'ollama_other',
'gemini',
'claude',
'nim',
'openrouter',
'nim_via_elephant'
)
) NOT VALID;
COMMENT ON CONSTRAINT chk_ai_calls_provider ON ai_calls IS
'Provider telemetry whitelist; ollama_other is unresolved/unknown Ollama host telemetry, not a route.';
COMMIT;

View File

@@ -85,6 +85,45 @@ _MAX_CONSECUTIVE_FAILURES = 10
_failure_counter_lock = threading.Lock()
_failure_state = {'count': 0, 'killed': False}
_AI_CALL_PROVIDER_WHITELIST = frozenset({
'gcp_ollama',
'ollama_secondary',
'ollama_111',
'ollama_other',
'gemini',
'claude',
'nim',
'openrouter',
'nim_via_elephant',
})
def _normalize_provider(provider: str) -> str:
"""Return an ai_calls.provider value that satisfies the DB CHECK.
`ollama_other` is telemetry-only: it represents an approved Ollama route
whose concrete host was unknown or failed before a successful host was
selected. It must not be used as a routing target.
"""
text = (provider or '').strip()[:32]
if text in _AI_CALL_PROVIDER_WHITELIST:
return text
lowered = text.lower()
if not lowered or lowered in {'unknown', 'none', 'null', 'ollama', 'ollama_local'}:
return 'ollama_other'
if lowered.startswith('gemini'):
return 'gemini'
if lowered.startswith(('claude', 'anthropic')):
return 'claude'
if lowered.startswith(('nim', 'nvidia', 'meta/')):
return 'nim'
if lowered.startswith('openrouter'):
return 'openrouter'
if 'ollama' in lowered:
return 'ollama_other'
return 'ollama_other'
def _record_failure() -> None:
with _failure_counter_lock:
@@ -145,7 +184,7 @@ class _CallState:
)
self.caller = caller
self.provider = provider
self.provider = _normalize_provider(provider)
self.model = model
self.request_id = request_id
self.input_tokens = 0
@@ -174,7 +213,7 @@ class _CallState:
def set_provider(self, provider: str) -> None:
"""更新實際 provider。適用於 Ollama 三主機 retry 後才知道落點的 caller。"""
if provider:
self.provider = provider[:32]
self.provider = _normalize_provider(provider)
def set_model(self, model: str) -> None:
"""更新實際模型。適用於 host-aware downgrade 後才知道落點模型的 caller。"""
@@ -387,7 +426,7 @@ def _write_to_db(state: _CallState) -> None:
"""),
{
'caller': state.caller[:64] if state.caller else 'unknown',
'provider': (state.provider or 'unknown')[:32],
'provider': _normalize_provider(state.provider),
'model': (state.model or 'unknown')[:128],
'input_tokens': int(state.input_tokens or 0),
'output_tokens': int(state.output_tokens or 0),

View File

@@ -32,6 +32,7 @@ from services.ai_call_logger import (
_calc_cost,
_CallState,
_is_logging_enabled,
_normalize_provider,
_reset_kill_switch,
log_ai_call,
logged_ai_call,
@@ -185,6 +186,22 @@ def test_context_manager_can_update_actual_provider_after_retry(reset_state):
assert rec['provider'] == 'ollama_secondary'
def test_provider_normalization_keeps_ai_calls_check_safe(reset_state):
assert _normalize_provider('') == 'ollama_other'
assert _normalize_provider('unknown') == 'ollama_other'
assert _normalize_provider('ollama_other') == 'ollama_other'
assert _normalize_provider('gemini-2.5-flash') == 'gemini'
assert _normalize_provider('anthropic') == 'claude'
assert _normalize_provider('nvidia/nemotron') == 'nim'
captured = reset_state
with log_ai_call('hermes_analyst', 'unknown', 'hermes3:latest'):
pass
assert _wait_for_async(captured, 1)
assert captured[0]['provider'] == 'ollama_other'
# ─────────────────────────────────────────────────────────────────────────────
# decorator 測試
# ─────────────────────────────────────────────────────────────────────────────

View File

@@ -65,6 +65,15 @@ def test_host_health_probe_label_check_accepts_runtime_labels():
assert f"'{label}'" in migration
def test_ai_calls_provider_check_accepts_ollama_other_telemetry_bucket():
migration = (ROOT / "migrations" / "043_allow_ollama_other_ai_calls_provider.sql").read_text(encoding="utf-8")
assert "DROP CONSTRAINT IF EXISTS chk_ai_calls_provider" in migration
assert "ADD CONSTRAINT chk_ai_calls_provider" in migration
assert "'ollama_other'" in migration
assert "NOT VALID" in migration
def test_rag_embedding_signature_migration_covers_query_and_learning_tables():
migration = (ROOT / "migrations" / "034_add_embedding_signature_to_rag_tables.sql").read_text(encoding="utf-8")

View File

@@ -76,7 +76,7 @@ class TestGetProviderTag:
assert get_provider_tag('http://192.168.0.188:11434') == 'ollama_other'
@pytest.mark.parametrize('host,expected', [
# 對齊 ai_calls 表 CHECK constraint 白名單
# 對齊 ai_calls 表 CHECK constraint 白名單migration 043 補 ollama_other
('http://34.143.170.20:11434', 'gcp_ollama'),
('http://192.168.0.110:11435', 'gcp_ollama'),
('http://34.21.145.224:11434', 'ollama_secondary'),