This commit is contained in:
@@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.377"
|
||||
SYSTEM_VERSION = "V10.378"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
## 📅 詳細更新日誌 (考古存檔)
|
||||
|
||||
### 2026-05-21:瀏覽器測試守門與 PChome 熱路徑優化
|
||||
- **V10.378 AI 推薦頁首屏 Gemini 防漏**: `/ai_recommend` 首屏狀態快照新增 provider sanitization,即使舊 cache / env 內出現 `default_provider='gemini'` 或 `recommended_provider='gemini'`,也會回到 `ollama`,避免 UI 把 Gemini 顯示成主推薦路徑;`/api/ai/set_provider` 同步正規化 provider 輸入,保留 Gemini 只能作 Ollama 失敗備援的拒絕訊息。
|
||||
- **V10.377 Gemini 主路徑防漏補強**: `AIProviderService._get_recommended_provider()` 不再於 Ollama 不通時推薦 `gemini` 作為主提供者;`llm_model_router` 的 `ea_engine` 即使 caller 傳入 `gemini-2.0-flash` default,也會改回 `hermes3:latest`,需要深推理才升 `deepseek-r1:14b`;`ElephantAlphaOrchestrator` 的 OpenClaw registry / system prompt 改為 Ollama-first,避免 L3 HITL prompt 繼續把 Gemini 當主模型描述。同步補 AI SOT 與防回歸測試。
|
||||
- **V10.376 Recipe Box 同款防曬漂移比對**: `services/marketplace_product_matcher.py` 對 Recipe Box 多效提亮防曬霜新增 shared identity anchor 加分,當 MOMO 長標含兒童/無毒/天然彩妝等行銷詞、PChome 以「韓兔 多效提亮防曬霜」呈現時仍可判定同款;同步測試鎖住 `shared_identity_anchor_recipe_box_line`,避免平台名稱漂移讓同款價格告警漏報。
|
||||
- **V10.375 過期活動爬蟲排程 opt-in**: `run_scheduler.py` 將固定 LPN 的 `edm_task` / `festival_task` 改為 `MOMO_ENABLE_LEGACY_EDM_SCHEDULE=true` 才註冊,季節活動 `mothers_day_2026` / `valentine_520_2026` / `labor_day_2026` 改為 `MOMO_ENABLE_SEASONAL_PROMO_SCHEDULE=true` 才註冊;`services/data/crawler_config.json` 同步暫停已失效的 mothers_day LPN,避免 scheduler 定時打過期 MOMO 活動頁造成 Selenium browser loop 與無效負載。手動 API / CLI 指定 LPN 仍保留;同版整合 NIVEA/OPI 等比價搜尋 noise 與 identity anchor 補強。
|
||||
|
||||
@@ -45,15 +45,35 @@ ai_history_service = AIHistoryService()
|
||||
ai_template_service = AITemplateService()
|
||||
|
||||
|
||||
def _safe_primary_provider(provider: str | None) -> str:
|
||||
normalized = (provider or 'ollama').strip().lower()
|
||||
if normalized == 'gemini':
|
||||
return 'ollama'
|
||||
if normalized in ('ollama', 'elephant'):
|
||||
return normalized
|
||||
return 'ollama'
|
||||
|
||||
|
||||
def _safe_recommended_provider(provider: str | None) -> str:
|
||||
"""Initial render must never advertise Gemini as a primary provider."""
|
||||
normalized = _safe_primary_provider(provider)
|
||||
return normalized if normalized in ('ollama', 'elephant') else 'none'
|
||||
|
||||
|
||||
def _get_ai_status_for_initial_render():
|
||||
"""取得首屏用 AI 狀態快照,不做同步網路健康檢查。"""
|
||||
status_cache = getattr(ai_provider_service, '_status_cache', {}) or {}
|
||||
cached_status = status_cache.get('data')
|
||||
if cached_status:
|
||||
cached_status = dict(cached_status)
|
||||
cached_status['default_provider'] = _safe_primary_provider(cached_status.get('default_provider'))
|
||||
cached_status['recommended_provider'] = _safe_recommended_provider(
|
||||
cached_status.get('recommended_provider') or cached_status.get('default_provider')
|
||||
)
|
||||
return cached_status
|
||||
|
||||
default_model = getattr(ollama_service, 'model', None) or 'gemma3:4b'
|
||||
default_provider = getattr(ai_provider_service, 'default_provider', 'ollama')
|
||||
default_provider = _safe_primary_provider(getattr(ai_provider_service, 'default_provider', 'ollama'))
|
||||
return {
|
||||
'default_provider': default_provider,
|
||||
'ollama': {
|
||||
@@ -77,7 +97,7 @@ def _get_ai_status_for_initial_render():
|
||||
'type': 'cloud',
|
||||
'cost': 'efficient',
|
||||
},
|
||||
'recommended_provider': default_provider,
|
||||
'recommended_provider': _safe_recommended_provider(default_provider),
|
||||
'timestamp': None,
|
||||
}
|
||||
|
||||
@@ -122,7 +142,7 @@ def api_set_provider():
|
||||
"""切換預設 AI 提供者"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
provider = data.get('provider', 'ollama')
|
||||
provider = (data.get('provider', 'ollama') or 'ollama').strip().lower()
|
||||
|
||||
if provider == 'gemini':
|
||||
return jsonify({
|
||||
|
||||
41
tests/test_ai_routes_ollama_first.py
Normal file
41
tests/test_ai_routes_ollama_first.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""AI recommendation route Ollama-first display contract."""
|
||||
|
||||
|
||||
def test_initial_ai_status_sanitizes_cached_gemini_recommendation(monkeypatch):
|
||||
import routes.ai_routes as ai_routes
|
||||
|
||||
monkeypatch.setattr(
|
||||
ai_routes.ai_provider_service,
|
||||
"_status_cache",
|
||||
{
|
||||
"data": {
|
||||
"default_provider": "gemini",
|
||||
"recommended_provider": "gemini",
|
||||
"ollama": {"connected": None},
|
||||
"gemini": {"connected": True},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
status = ai_routes._get_ai_status_for_initial_render()
|
||||
|
||||
assert status["default_provider"] == "ollama"
|
||||
assert status["recommended_provider"] == "ollama"
|
||||
|
||||
|
||||
def test_initial_ai_status_never_recommends_gemini_without_cache(monkeypatch):
|
||||
import routes.ai_routes as ai_routes
|
||||
|
||||
class FakeProvider:
|
||||
default_provider = "gemini"
|
||||
_status_cache = {}
|
||||
|
||||
monkeypatch.setattr(ai_routes, "ai_provider_service", FakeProvider())
|
||||
|
||||
status = ai_routes._get_ai_status_for_initial_render()
|
||||
|
||||
assert status["default_provider"] == "ollama"
|
||||
assert status["recommended_provider"] == "ollama"
|
||||
|
||||
Reference in New Issue
Block a user