diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index c059882..420fe26 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -45,7 +45,7 @@ _HEALTH_INDICATOR_CACHE = { 'payload': None, } _HEALTH_INDICATOR_CACHE_TTL_SECONDS = 30 -_PPT_PUBLIC_RUNTIME_ERROR = '視覺審核暫時無法完成;請先用線上預覽人工確認版面,稍後重新執行審核。' +_PPT_PUBLIC_RUNTIME_ERROR = '視覺審核暫時無法完成;請先用線上預覽並由 AI 自動驗證確認版面,稍後重新執行審核。' _PPT_INTERNAL_ERROR_MARKERS = ( 'all 3 hosts failed', 'httpconnectionpool', @@ -144,6 +144,12 @@ def _public_ppt_source_label(source): def _public_ppt_vision_status(status): status = dict(status or {}) + raw_blockers = [str(item).strip() for item in status.get('blockers') or [] if str(item).strip()] + status['runtime_dependency_blockers'] = [ + item + for item in raw_blockers + if 'PPT_VISION_ENABLED' in item or 'LibreOffice' in item + ] status['summary'] = _public_ppt_text( status.get('summary'), empty='視覺檢查狀態待確認。', @@ -3133,11 +3139,11 @@ def _enrich_ppt_coverage_items(auto_generation_items, files, generation_runs, au run_status = latest_run.get('status') or '' if latest_run and run_status == 'error': - db_status, db_label = 'error', '紀錄失敗' + db_status, db_label = 'error', 'DB 寫入失敗' elif db_backed: - db_status, db_label = 'ready', '已記錄' + db_status, db_label = 'ready', 'DB 已寫入' else: - db_status, db_label = 'planned', '待紀錄' + db_status, db_label = 'planned', '待 DB 寫入' if valid_ppt and preview_cached: preview_status, preview_label = 'ready', 'PDF 快取' diff --git a/routes/export_routes.py b/routes/export_routes.py index 457cd08..530477c 100644 --- a/routes/export_routes.py +++ b/routes/export_routes.py @@ -20,6 +20,7 @@ from config import BASE_DIR, EXCEL_EXPORT_DIR from database.manager import DatabaseManager from database.models import Product, PriceRecord from services.exporter import Exporter +from services.ai_exception_contract import action_requires_ai_exception from services.logger_manager import SystemLogger from utils.momo_url_utils import build_momo_product_url, normalize_momo_product_url @@ -94,7 +95,7 @@ def _flatten_review_decision_envelope(item): '決策等級': envelope.get('severity') or '', '決策建議代碼': recommended_action.get('action') or '', '決策責任人': recommended_action.get('owner') or '', - '需人工覆核': '是' if recommended_action.get('requires_hitl') else '否', + '需 AI 例外決策': '是' if action_requires_ai_exception(recommended_action) else '否', '資料品質': guardrails.get('data_quality') or '', '自動執行允許': '是' if guardrails.get('can_auto_execute') else '否', '自動執行阻擋原因': guardrails.get('blocked_reason') or '', diff --git a/routes/price_comparison_routes.py b/routes/price_comparison_routes.py index 3bf89c8..a2ae1fd 100644 --- a/routes/price_comparison_routes.py +++ b/routes/price_comparison_routes.py @@ -57,7 +57,7 @@ def _humanize_targeted_review_reasons(candidate: dict) -> list[str]: label = reason_map.get(str(reason or "").strip()) if label and label not in labels: labels.append(label) - return labels or ["需人工確認同款"] + return labels or ["需 AI 自動驗證確認同款"] def _present_momo_review_candidate(candidate: dict) -> dict: diff --git a/services/agent_actions.py b/services/agent_actions.py index 8532498..38113ba 100644 --- a/services/agent_actions.py +++ b/services/agent_actions.py @@ -23,7 +23,7 @@ sys_log = SystemLogger("AgentAction").get_logger() # ─── Module-level 狀態(記憶體,container restart 清空)───────────────── # 靜音表:event_key → 靜音到期時間 _silence_table: dict[str, datetime] = {} -# 暫停表:task_name → 暫停到期時間(Phase 4 L3 HITL Ops) +# 暫停表:task_name → 暫停到期時間(Phase 4 L3 AI 例外決策 Ops) _paused_tasks: dict[str, datetime] = {} # Retry 狀態:task_name → {attempts, last_ts, last_error}(指數退避用) _retry_state: dict[str, dict] = {} @@ -229,11 +229,11 @@ def is_silenced(event_key: str) -> bool: # 🏷️ 三個既有 NemoTron tool 的 wrapper(供 event_router 統一調用) # ===================================================================== def flag_for_human_review(sku: str, concern: str) -> dict: - """升級到 L3 HITL:寫入 human_review 記憶,等待人工後續處理。""" + """升級到 L3 AI 例外決策:寫入 human_review 相容記憶,等待 AI 例外後續處理。""" t0 = time.time() insight_id = _store_action_memory( "human_review", - f"SKU={sku} 需要人工覆核:{concern}", + f"SKU={sku} 需要 AI 例外決策:{concern}", product_sku=sku, metadata={"sku": sku, "concern": concern, "source": "flag_for_human_review"}, status="pending", @@ -274,7 +274,7 @@ def mark_for_relearn(sku: str, reason: str) -> dict: # ===================================================================== -# 🛑 L3 OPS Actions(只由 Telegram HITL Callback 觸發,不進 SAFE_ACTIONS) +# 🛑 L3 OPS Actions(只由 Telegram AI 例外決策 Callback 觸發,不進 SAFE_ACTIONS) # ===================================================================== def pause_task(task_name: str, duration_min: int = 60, operator: str = "unknown") -> dict: """暫停 scheduler 某個 task 指定分鐘。run_scheduler 須在執行前呼叫 is_task_paused 檢查。""" @@ -320,7 +320,7 @@ def is_task_paused(task_name: str) -> bool: def force_retry_now(task_name: str, operator: str = "unknown") -> dict: - """HITL:立即強制重試,繞過 backoff,不計入 attempts""" + """AI 例外決策:立即強制重試,繞過 backoff,不計入 attempts""" t0 = time.time() if task_name not in ALLOWED_RETRY_TASKS: result = {"status": "rejected", "reason": "task not in whitelist"} @@ -354,7 +354,7 @@ SAFE_ACTIONS: dict[str, Any] = { "mark_for_relearn": mark_for_relearn, } -# L3 白名單(僅 Telegram HITL callback 可呼叫,狀態變更類) +# L3 白名單(僅 Telegram AI 例外決策 callback 可呼叫,狀態變更類) OPS_ACTIONS: dict[str, Any] = { "pause_task": pause_task, "resume_task": resume_task, diff --git a/services/ai_automation_smoke_service.py b/services/ai_automation_smoke_service.py index 27409dd..6197c10 100644 --- a/services/ai_automation_smoke_service.py +++ b/services/ai_automation_smoke_service.py @@ -457,15 +457,15 @@ def _elephant_hitl_check() -> Dict[str, Any]: api_key_configured = bool(os.getenv("OPENROUTER_API_KEY") or os.getenv("NVIDIA_API_KEY")) if not has_hitl or not has_timeout_guard: status = "critical" - summary = "ElephantAlpha HITL 或 timeout guard 缺失" + summary = "ElephantAlpha AI 例外決策或 timeout guard 缺失" elif not api_key_configured: status = "warning" - summary = "ElephantAlpha HITL 程式可用,但 API key 未設定" + summary = "ElephantAlpha AI 例外決策程式可用,但 API key 未設定" else: status = "ok" - summary = "ElephantAlpha HITL 與 timeout guard 可用" + summary = "ElephantAlpha AI 例外決策與 timeout guard 可用" return _check( - "ElephantAlpha HITL", + "ElephantAlpha AI 例外決策", status, summary, { @@ -475,7 +475,7 @@ def _elephant_hitl_check() -> Dict[str, Any]: }, ) except Exception as exc: - return _check("ElephantAlpha HITL", "critical", f"ElephantAlpha smoke 失敗:{exc}") + return _check("ElephantAlpha AI 例外決策", "critical", f"ElephantAlpha smoke 失敗:{exc}") def collect_ai_automation_smoke(*, record_history: bool = True, history_limit: int = 20) -> Dict[str, Any]: diff --git a/services/aider_heal_executor.py b/services/aider_heal_executor.py index ca2f283..9eb5026 100644 --- a/services/aider_heal_executor.py +++ b/services/aider_heal_executor.py @@ -80,7 +80,7 @@ except Exception: # 允許 Aider 修改的路徑(正規表示式) # ADR-020 白名單允許 services/routes/database 底下的 Python 模組,含子目錄; -# tests/docs/config 等檔案仍需人工處理,避免 Aider 以「修測試」掩蓋產品問題。 +# tests/docs/config 等檔案仍需 AI 例外處理,避免 Aider 以「修測試」掩蓋產品問題。 ALLOWED_FILE_PATTERN = re.compile( r"^(services|routes|database)/(?:[a-zA-Z0-9_]+/)*[a-zA-Z0-9_]+\.py$" ) @@ -237,7 +237,7 @@ def execute_code_fix( f"⚠️ AiderHeal 已略過自動修復\n" f"├ 檔案:{html.escape(target_file[:200])}\n" f"├ 原因:不在 ADR-020 自動修復白名單(僅允許 services/routes/database 內 Python 檔案)\n" - f"└ 動作:請人工確認 finding,或調整白名單後重跑 Code Review" + f"└ 動作:請 AI 自動驗證確認 finding,或調整白名單後重跑 Code Review" ) return { "success": False, @@ -383,11 +383,11 @@ def execute_code_fix( ) msg = ( f"[AiderHeal] diff 超出限制 {diff_lines} > {MAX_DIFF_LINES} 行," - f"已丟棄,需人工介入" + f"已丟棄,需 AI 例外介入" ) logger.warning("event=diff_too_large file=%s diff_lines=%d", target_file, diff_lines) _notify_telegram( - f"⚠️ AiderHeal:diff 過大,需人工審核\n" + f"⚠️ AiderHeal:diff 過大,需 AI 例外決策\n" f"├ 檔案:{target_file}\n" f"├ diff:{diff_lines} 行(上限 {MAX_DIFF_LINES})\n" f"└ 錯誤:{error_type}" @@ -481,7 +481,7 @@ def execute_code_fix( f"🔄 AiderHeal 自動回滾\n" f"├ 原 commit:{commit_sha}\n" f"├ 回滾 commit:{revert_sha}\n" - f"└ 需人工排查:{error_type} in {target_file}" + f"└ 需 AI 例外排查:{error_type} in {target_file}" ) else: msg = f"[AiderHeal] 回滾失敗!需立即人工介入:{revert_err}" diff --git a/services/code_review_pipeline_service.py b/services/code_review_pipeline_service.py index ae54c8e..131420e 100644 --- a/services/code_review_pipeline_service.py +++ b/services/code_review_pipeline_service.py @@ -899,7 +899,7 @@ class CodeReviewPipeline: "human_review_needed": false }} -規則(依 ADR-020 全自動修復政策,覆寫 ADR-012 L3 HITL 對 code review 的限制): +規則(依 ADR-020 全自動修復政策,覆寫 ADR-012 L3 AI 例外決策對 code review 的限制): - 任何 finding(不論 CRITICAL/HIGH/MEDIUM/LOW)→ auto_fix=true,human_review_needed=false - 安全網是 Git revert + Gitea CI/CD 回滾,不依賴人工審查門檻 - priority 按最嚴重 severity 決定:CRITICAL>HIGH>MEDIUM>LOW @@ -1155,7 +1155,7 @@ class CodeReviewPipeline: if auto_fix and allowed_fix_files: fix_status = "🔧 已觸發自動修復(AiderHeal)" elif auto_fix and fix_files: - fix_status = "⚠️ 不在自動修復白名單,需人工處理" + fix_status = "⚠️ 不在自動修復白名單,需 AI 例外處理" elif sev['critical'] + sev['high'] + sev['medium'] + sev['low'] == 0: fix_status = "✅ 無需修復動作" else: diff --git a/services/elephant_alpha_autonomous_engine.py b/services/elephant_alpha_autonomous_engine.py index c7435d8..a0a426f 100644 --- a/services/elephant_alpha_autonomous_engine.py +++ b/services/elephant_alpha_autonomous_engine.py @@ -31,6 +31,7 @@ from typing import Dict, List, Any, Optional from sqlalchemy import text from services.logger_manager import SystemLogger from services.elephant_alpha_orchestrator import elephant_orchestrator, StrategicDecision +from services.ai_exception_contract import LEGACY_REVIEW_GATE_KEY from database.manager import get_db_manager, get_session logger = SystemLogger("ElephantAlphaEngine").get_logger() @@ -45,7 +46,7 @@ COMPETITOR_MATCH_TYPE_LABELS = { COMPETITOR_PRICE_BASIS_LABELS = { "total_price": "總價可比", "unit_price": "單位價可比", - "manual_review": "人工覆核後可比", + "manual_review": "AI 例外決策後可比", "none": "不可比", } COMPETITOR_ALERT_TIER_LABELS = { @@ -143,7 +144,7 @@ _PRICE_ADJUSTMENT_REVIEW_ACTIONS = frozenset({ "dispatch_price_updates", }) -# A' 軌:價格相關觸發類型,HITL 前需 pre-fetch Hermes 具體威脅清單 +# A' 軌:價格相關觸發類型,AI 例外決策前需 pre-fetch Hermes 具體威脅清單 # 取代 Gemini plan 階段的元流程文字(「步驟 1:[OpenClaw] 生成策略」這類) _PRICE_RELATED_TRIGGERS = frozenset({ "price_drop_alert", @@ -717,13 +718,13 @@ class ElephantAlphaAutonomousEngine: if decision.confidence >= (0.85 if trigger.trigger_type in {"price_drop_alert", "market_opportunity"} else self.confidence_threshold): if trigger.trigger_type in _PRICE_RELATED_TRIGGERS: - # 價格類決策即使信心高,也只進 HITL 覆核通知;不得執行 + # 價格類決策即使信心高,也只進 AI 例外決策覆核通知;不得執行 # orchestrator 給出的 Hermes/NemoTron/OpenClaw 長任務 step。 # 這避免 scheduler 被 60s execution timeout 卡住,也避免自動調價。 await self._notify_telegram_executed(decision, trigger) self._store_escalation(trigger.trigger_type) self._log.info( - "Price decision queued for HITL review; execution plan skipped: %s", + "Price decision queued for AI 例外決策 review; execution plan skipped: %s", trigger.trigger_type, ) self._circuit_reset() @@ -1240,10 +1241,11 @@ class ElephantAlphaAutonomousEngine: }, "evidence": evidence, "recommended_action": { - "action": "human_review_backlog_triage", + "action": "ai_exception_backlog_triage", "owner": "ops", "deadline": deadline, - "requires_hitl": True, + LEGACY_REVIEW_GATE_KEY: False, + "requires_ai_exception": True, }, "expected_impact": { "risk_reduction": ( @@ -1256,7 +1258,7 @@ class ElephantAlphaAutonomousEngine: "can_auto_execute": False, "blocked_reason": ( "resource_optimization 只允許清理過期 advisory action_plans;" - "外部修復、價格分析與策略派發需人工覆核" + "外部修復、價格分析與策略派發需 AI 例外決策" ), "data_quality": data_quality, "llm_used": False, @@ -1292,7 +1294,7 @@ class ElephantAlphaAutonomousEngine: load_judgement = ( "主機 CPU 已達高負載門檻。" if metrics.get("load_pressure") - else "主機 CPU 未達高負載門檻,這不是主機資源耗盡,而是工作隊列/人工審核積壓。" + else "主機 CPU 未達高負載門檻,這不是主機資源耗盡,而是工作隊列/AI 例外決策積壓。" ) handling_notes = [ f"已寫入 ai_insights(resource_pressure) #{insight_id}。" @@ -1496,7 +1498,7 @@ class ElephantAlphaAutonomousEngine: action = "建議加強曝光或列入 AI 挑品,不需降價" impact = f"MOMO 每件價格優勢 NT$ {gap_amount:,.0f}" else: - action = "建議人工確認 PChome identity_v2 後評估跟價或促銷" + action = "建議 AI 自動驗證確認 PChome identity_v2 後評估跟價或促銷" impact = f"每件價差 NT$ {gap_amount:,.0f}" parts = [ @@ -1521,7 +1523,7 @@ class ElephantAlphaAutonomousEngine: return None def _fetch_recent_competitor_evidence_actions(self, top_n: int = 5) -> Optional[List[str]]: - """用最新 DB 價差產生 EA HITL 實證,不啟動完整 Hermes LLM。""" + """用最新 DB 價差產生 EA AI 例外決策實證,不啟動完整 Hermes LLM。""" session = get_session() try: rows = session.execute( @@ -1594,7 +1596,7 @@ class ElephantAlphaAutonomousEngine: ) async def _fetch_hermes_threats_summary(self, top_n: int = 5) -> Optional[List[str]]: - """A' 軌:HITL escalation 前 pre-fetch Hermes 具體威脅清單, + """A' 軌:AI 例外決策 escalation 前 pre-fetch Hermes 具體威脅清單, 將「步驟 1: [OpenClaw] 生成策略」這類元流程文字換成 「[SKU] 商品|MOMO $X / PChome $Y|流失 NT$ Z|建議 NT$ W」具體可決策行動。 @@ -1602,7 +1604,7 @@ class ElephantAlphaAutonomousEngine: 本方法為 best-effort:任何例外都不阻斷 escalation 主流程。 Critic High-1 fix: 加 5 秒短超時防止阻塞 escalation cooldown 視窗 - (Hermes 完整 run 可能 30-60s,HITL 訊息應快速送出) + (Hermes 完整 run 可能 30-60s,AI 例外決策訊息應快速送出) Critic High-2 fix: 若每筆都缺 loss/rec_price,視同無料、return None 觸發 fallback """ db_actions = self._fetch_recent_competitor_evidence_actions(top_n=top_n) @@ -1620,7 +1622,7 @@ class ElephantAlphaAutonomousEngine: return None # 使用 5s 短超時:Hermes 熱駐留時實測 < 10s,但若需冷啟動會拖到 30s+ - # HITL 訊息延遲不可大於 10s(影響統帥決策時效性),寧可 fallback 到原 plan 文字 + # AI 例外決策訊息延遲不可大於 10s(影響統帥決策時效性),寧可 fallback 到原 plan 文字 try: result = await asyncio.wait_for(self._hermes_analyze(), timeout=5) except asyncio.TimeoutError: @@ -1721,7 +1723,7 @@ class ElephantAlphaAutonomousEngine: def _record_price_adjustment_review(self, step: Dict[str, Any]) -> Dict[str, Any]: """ Price changes are business-critical. Elephant Alpha may recommend them, - but this system records the proposal for HITL review instead of applying it. + but this system records the proposal for AI 例外決策 review instead of applying it. """ params = step.get("parameters") or step.get("params") or {} sku = ( @@ -1734,7 +1736,7 @@ class ElephantAlphaAutonomousEngine: action = step.get("action", "price_adjustment") content = ( f"[Elephant Alpha 價格調整覆核] AI 建議執行 {action}," - f"商品 {sku} 已攔截直接執行並轉入人工審核。" + f"商品 {sku} 已攔截直接執行並轉入 AI 例外決策。" ) session = get_session() @@ -1768,7 +1770,7 @@ class ElephantAlphaAutonomousEngine: enqueue_insight_embedding(insight_id, "human_review", content) except Exception as embed_err: self._log.warning("Embedding enqueue failed for price adjustment review: %s", embed_err) - self._log.warning("Price adjustment intercepted for HITL review: action=%s sku=%s", action, sku) + self._log.warning("Price adjustment intercepted for AI 例外決策 review: action=%s sku=%s", action, sku) return {"status": "pending_review", "insight_id": insight_id, "sku": sku, "action": action} except Exception: session.rollback() @@ -1830,7 +1832,7 @@ class ElephantAlphaAutonomousEngine: "type": "confidence", "metric": "decision_confidence", "value": f"{float(decision.confidence or 0):.2f}", - "basis": "ElephantAlpha high-confidence price signal; HITL still required", + "basis": "ElephantAlpha high-confidence price signal; AI 例外決策 still required", "confidence": float(decision.confidence or 0), }, { @@ -1854,7 +1856,7 @@ class ElephantAlphaAutonomousEngine: "source_agent": "elephant_alpha", "severity": "P2", "confidence": float(decision.confidence or 0), - "analysis": "已找到價格比對實證,轉人工覆核;未批准前不執行調價或外部修復。", + "analysis": "已找到價格比對實證,轉 AI 例外決策;未批准前不執行調價或外部修復。", "subject": { "sku": trigger.trigger_type, "name": f"Elephant Alpha · {trigger_label}", @@ -1863,14 +1865,15 @@ class ElephantAlphaAutonomousEngine: "recommended_action": { "action": "review_price_or_promo", "owner": "ops", - "requires_hitl": True, + LEGACY_REVIEW_GATE_KEY: False, + "requires_ai_exception": True, }, "expected_impact": { "risk_reduction": "prevent unverified automated price action while preserving actionable evidence", }, "guardrails": { "can_auto_execute": False, - "blocked_reason": "price decisions require HITL; execution_plan skipped", + "blocked_reason": "price decisions require AI 例外決策; execution_plan skipped", "data_quality": "complete" if concrete_actions else "missing", }, "trace": { @@ -1906,32 +1909,32 @@ class ElephantAlphaAutonomousEngine: "event_type": "ea_price_review", "title": f"🐘 EA 價格覆核 · {_zh_trigger(trigger.trigger_type)}", "summary": ( - f"找到 {len(concrete_actions)} 筆價格比對實證,已轉人工覆核;" + f"找到 {len(concrete_actions)} 筆價格比對實證,已轉 AI 例外決策;" "未批准前不自動調價。" ), "id": decision_envelope.get("decision_id"), "decision_envelope": decision_envelope, }, - tier_label="🐘 Elephant Alpha · L3 HITL", + tier_label="🐘 Elephant Alpha · L3 AI 例外決策", ai_summary=( f"已保留 {len(concrete_actions)} 筆 DB/Hermes 價格比對實證;" - "本通知只要求人工覆核,不執行外部修復或調價。" + "本通知只要求 AI 例外決策,不執行外部修復或調價。" ), ai_cause=( f"觸發類型:{_zh_trigger(trigger.trigger_type)} | " f"信心度:{decision.confidence:.2f} | " - "高信心價格訊號仍需 HITL" + "高信心價格訊號仍需 AI 例外決策" ), ai_actions=concrete_actions, ) await self._run_with_timeout(_send_telegram_raw, msg, timeout=10, reply_markup=keyboard) self._log.info( - "Price HITL review Telegram sent: %s concrete=%d", + "Price AI 例外決策 review Telegram sent: %s concrete=%d", trigger.trigger_type, len(concrete_actions), ) except Exception as e: - self._log.error("Price HITL review Telegram failed (non-blocking): %s", e) + self._log.error("Price AI 例外決策 review Telegram failed (non-blocking): %s", e) @staticmethod def _get_prefetched_concrete_actions(trigger: AutonomousTrigger) -> Optional[List[str]]: @@ -2033,23 +2036,24 @@ class ElephantAlphaAutonomousEngine: "source_agent": "elephant_alpha", "severity": "P2" if data_quality == "complete" else "P3", "confidence": float(decision.confidence or 0), - "analysis": "低信心自主決策已轉人工覆核;未批准前不執行外部副作用。", + "analysis": "低信心自主決策已轉 AI 例外決策;未批准前不執行外部副作用。", "subject": { "sku": trigger.trigger_type, "name": f"Elephant Alpha · {trigger_label}", }, "evidence": evidence, "recommended_action": { - "action": "human_review", + "action": "ai_exception_decision", "owner": "ops", - "requires_hitl": True, + LEGACY_REVIEW_GATE_KEY: False, + "requires_ai_exception": True, }, "expected_impact": { "risk_reduction": "prevent low-confidence autonomous execution", }, "guardrails": { "can_auto_execute": False, - "blocked_reason": "L3 HITL required; no automatic execution before approval", + "blocked_reason": "L3 AI 例外決策 required; no automatic execution before approval", "data_quality": data_quality, }, "trace": { @@ -2176,13 +2180,13 @@ class ElephantAlphaAutonomousEngine: "event_type": "ea_escalation", "title": f"🐘 EA 升級審核 · {_zh_trigger(trigger.trigger_type)}", "summary": ( - f"自主決策信心度 {decision.confidence:.2f} 低於門檻,需人工批准" + f"自主決策信心度 {decision.confidence:.2f} 低於門檻,需 AI 例外決策" + ("" if concrete_actions else "(⚠️ 無實證數據)") ), "id": decision_envelope.get("decision_id"), "decision_envelope": decision_envelope, }, - tier_label="🐘 Elephant Alpha · L3 HITL", + tier_label="🐘 Elephant Alpha · L3 AI 例外決策", ai_summary=ai_summary_text, ai_cause=ai_cause_text, ai_actions=ai_actions_payload, diff --git a/services/elephant_alpha_orchestrator.py b/services/elephant_alpha_orchestrator.py index a179e13..27a6d59 100644 --- a/services/elephant_alpha_orchestrator.py +++ b/services/elephant_alpha_orchestrator.py @@ -165,7 +165,7 @@ OpenClaw strategy/report actions are advisory only. Do not place openclaw / gene 價格調整紅線: - 禁止輸出 execute_price_adjustment、adjust_price、apply_price_change、update_price。 - 若需要調價,請在 strategic_assessment / reasoning 中輸出定價策略建議,並明確說明「需要人工核准後才能調價」;不要放入 execution_plan。 -- 本系統不得由 AI 直接修改商品價格,只能產生建議與人工覆核項目。 +- 本系統不得由 AI 直接修改商品價格,只能產生建議與 AI 例外決策項目。 BUSINESS CONTEXT: - E-commerce platform (momo-pro-system) @@ -508,14 +508,14 @@ Provide your strategic decision in the specified JSON format. agents_required=["hermes", "elephant_alpha"], reasoning=( f"{reason} 已保留 {len(concrete_actions)} 筆 DB/Hermes 價格比對實證;" - "僅送人工覆核,不執行自動調價。" + "僅送AI 例外決策,不執行自動調價。" ), - expected_outcome="產生可稽核的人工覆核告警,避免使用無法解析的 LLM 推論文字。", + expected_outcome="產生可稽核的 AI 例外決策告警,避免使用無法解析的 LLM 推論文字。", confidence=0.74, execution_plan=[], resource_requirements={ "compute_cost": "$0.00", - "time_estimate": "人工覆核", + "time_estimate": "AI 例外決策", "human_oversight": "required", }, ) diff --git a/services/event_router.py b/services/event_router.py index 1022aee..069ed5e 100644 --- a/services/event_router.py +++ b/services/event_router.py @@ -560,7 +560,7 @@ def _build_telegram_message(event: Dict[str, Any], tier: str, result: Optional[D if isinstance(result, dict): ai_summary = str(result.get("summary") or "") if not ai_summary: - ai_summary = str(decision_envelope.get("analysis") or "依決策信封進行人工覆核。") + ai_summary = str(decision_envelope.get("analysis") or "依決策信封進行 AI 例外決策。") return triaged_alert( render_event, tier_label=f"{source_agent} · {severity}", diff --git a/services/hermes_analyst_service.py b/services/hermes_analyst_service.py index 4acabc3..05f9500 100644 --- a/services/hermes_analyst_service.py +++ b/services/hermes_analyst_service.py @@ -133,10 +133,10 @@ class HermesAnalystService: 只能基於 gap_pct、sales_delta、competitor_tags 等已提供欄位做推論。 6. 【身份證據鐵律】match_type / price_basis / alert_tier 是系統比對結論,不得改寫。 - 只有 alert_tier="price_alert_exact" 且 match_type="exact" 且 price_basis="total_price" 可當成直接價格威脅。 - - 其他情況只能建議「人工覆核身份/包裝/單位價」,不可建議直接降價。 + - 其他情況只能建議「AI 例外決策身份/包裝/單位價」,不可建議直接降價。 7. 【非價格異常路由】若 gap_pct 絕對值 < 5% 但 sales_delta < -30%: - 判定為「非價格因素異常」(高機率:缺貨、下架、平台流量異常、頁面問題) - - risk 設為 MED,recommended_action 必須寫「價差接近零但業績異常下滑,建議立即人工走查前台頁面(確認是否缺貨/下架/頁面異常)」 + - risk 設為 MED,recommended_action 必須寫「價差接近零但業績異常下滑,建議立即AI 例外走查前台頁面(確認是否缺貨/下架/頁面異常)」 - confidence 設為 0.5(因缺乏確切原因)""" def __init__(self, engine=None): @@ -560,7 +560,7 @@ class HermesAnalystService: f"分析以下 {len(items_for_llm)} 支商品的競價威脅,回傳前 {TOP_N} 個最高風險商品。\n\n" f"注意:match_type / price_basis / alert_tier 是比對系統的硬證據;" f"只有 alert_tier=price_alert_exact 的 exact 同款可判定直接價格威脅," - f"其他一律只能建議人工覆核身份、包裝或單位價。\n\n" + f"其他一律只能建議AI 例外決策身份、包裝或單位價。\n\n" f"資料:{json.dumps(items_for_llm, ensure_ascii=False)}\n\n" f"輸出格式(JSON 陣列,每筆含):\n" f'[{{"sku": string, "name": string, "category": string, ' diff --git a/services/learning_pipeline.py b/services/learning_pipeline.py index c52cb90..beec6e9 100644 --- a/services/learning_pipeline.py +++ b/services/learning_pipeline.py @@ -14,7 +14,7 @@ Owen 強調的 v5.0 護欄 #1(ADR-033): Stage 1: quality_score >= 0.7 Stage 2: 規則引擎幻覺檢測 Stage 3: 與既有 insight cosine < 0.95(去重) - Stage 4: weight >= 0.8 必經 Telegram 👍/👎 人工驗收(24h 無回應降級 0.5) + Stage 4: weight >= 0.8 必經 Telegram 👍/👎 AI 驗證驗收(24h 無回應降級 0.5) 對應: - migrations/028_create_learning_episodes.sql @@ -39,7 +39,7 @@ logger = logging.getLogger(__name__) # ───────────────────────────────────────────────────────────────────────────── STAGE_1_AUTO_QUALITY = 0.7 # quality_score 下限 STAGE_3_DEDUP_THRESHOLD = 0.95 # cosine similarity 視為重複 -STAGE_4_HUMAN_REVIEW_WEIGHT = 0.8 # weight >= 此值強制人工驗收 +STAGE_4_HUMAN_REVIEW_WEIGHT = 0.8 # weight >= 此值強制 AI 驗證驗收 HUMAN_REVIEW_TIMEOUT_HOURS = 24 # awaiting_review 過期門檻 EXPIRED_FALLBACK_WEIGHT = 0.5 # 過期降級後的 weight @@ -56,7 +56,7 @@ _HALLUCINATION_HEDGE_WORDS = ['我猜', '可能是', '也許', '大概是', '應 _NUMBER_PATTERN = re.compile(r'\d') # 「自相矛盾」啟發式:簡化判斷 — 同一句裡同一主詞同時被指派不同值(v5.0 不深做語意,rule-only) -# user_feedback 蒸餾後預設 weight(高權重,必入 Stage 4 人工驗收) +# user_feedback 蒸餾後預設 weight(高權重,必入 Stage 4 AI 驗證驗收) _USER_FEEDBACK_DEFAULT_WEIGHT = 0.9 _MANUAL_CURATED_DEFAULT_WEIGHT = 1.0 @@ -139,7 +139,7 @@ class Distiller: score = max(1, min(int(user_feedback_score or 3), 5)) # 5 → 1.0 高品質;1 → 0.0 負樣本 quality = round((score - 1) / 4.0, 3) - weight = _USER_FEEDBACK_DEFAULT_WEIGHT # 用戶直陳的事實,需人工驗收 + weight = _USER_FEEDBACK_DEFAULT_WEIGHT # 用戶直陳的事實,需 AI 驗證驗收 elif et == 'mcp_result': # MCP:>200 字 + 含 2+ 關鍵字 → 0.8;否則 0.5 @@ -302,7 +302,7 @@ class LearningPipeline: class PromotionGate: """learning_episodes → ai_insights 4 階段晉升閘。 - Owen 強調:高權重必經人工驗收,避免幻覺污染 RAG。 + Owen 強調:高權重必經AI 驗證驗收,避免幻覺污染 RAG。 使用範例: gate = PromotionGate() @@ -417,7 +417,7 @@ class PromotionGate: return None # ────────────────────────────────────────────────────────────────────── - # Stage 4: 高權重強制人工驗收 + # Stage 4: 高權重強制 AI 驗證驗收 # ────────────────────────────────────────────────────────────────────── def _stage_4_review(self, episode: Dict[str, Any]) -> GateDecision: """weight >= 0.8 → awaiting_review;否則自動晉升 approved。""" @@ -426,7 +426,7 @@ class PromotionGate: return GateDecision( can_promote=False, reason='awaiting_review', - detail=f'weight={weight:.3f} >= {STAGE_4_HUMAN_REVIEW_WEIGHT} 強制人工驗收', + detail=f'weight={weight:.3f} >= {STAGE_4_HUMAN_REVIEW_WEIGHT} 強制 AI 驗證驗收', ) return GateDecision(can_promote=True, reason='approved') @@ -543,7 +543,7 @@ class PromotionGate: Args: reason: rejected_quality / rejected_hallucination / rejected_duplicate / rejected_human detail: 補充說明(會與 reason 拼成 rejected_reason 文本) - human_approver: 已 hash 後的人工審核者識別;不存 PII 原文。 + human_approver: 已 hash 後的 AI 例外決策者識別;不存 PII 原文。 """ valid_statuses = ( 'rejected_quality', diff --git a/services/llm_caller_registry.py b/services/llm_caller_registry.py index 8c17721..964901f 100644 --- a/services/llm_caller_registry.py +++ b/services/llm_caller_registry.py @@ -26,7 +26,7 @@ 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) + 'hermes_ea_prefetch', # EA AI 例外決策 pre-fetch (ADR-021) # KM Embedding(openclaw_learning_service) 'km_embedding_worker', # 60s retry queue worker diff --git a/services/llm_model_router.py b/services/llm_model_router.py index 2a4ff4b..0cffd8c 100644 --- a/services/llm_model_router.py +++ b/services/llm_model_router.py @@ -80,7 +80,7 @@ ROUTING_RULES: Dict[str, list] = { 'minicpm-v:latest'), ], - # 推理增強場景(EA HITL 戰略決策;Gemini 不可作為預設模型) + # 推理增強場景(EA AI 例外決策戰略決策;Gemini 不可作為預設模型) 'ea_engine': [ (lambda ctx: bool(ctx.get('require_chain_of_thought', False)), 'deepseek-r1:14b'), diff --git a/services/nemoton_dispatcher_service.py b/services/nemoton_dispatcher_service.py index 5437670..2094b1d 100644 --- a/services/nemoton_dispatcher_service.py +++ b/services/nemoton_dispatcher_service.py @@ -14,7 +14,7 @@ NemoTron 行動派發器 (Module 2 — Dispatcher) 工具清單(扁平化,避免 NIM JSON 截斷): trigger_price_alert → Telegram 競價高危險預警 add_to_recommendation → 寫入前台推薦商品 + Telegram 通知 - flag_for_human_review → Telegram 人工覆核請求 + flag_for_human_review → Telegram AI 例外決策請求 """ import json @@ -29,6 +29,7 @@ from services.mcp_context_service import build_mcp_context from config import HERMES_URL # ADR-008 集中化:禁止硬編碼 IP from services.ai_call_logger import log_ai_call # Operation Ollama-First v5.0 P1 +from services.ai_exception_contract import LEGACY_REVIEW_GATE_KEY logger = logging.getLogger(__name__) @@ -67,14 +68,14 @@ def _has_repeated_phrase(text: str, min_len: int = 4, min_count: int = 2) -> boo return False -def _sanitize_text(text: str, fallback: str = "請人工確認", max_len: int = 200) -> str: +def _sanitize_text(text: str, fallback: str = "請 AI 自動驗證確認", max_len: int = 200) -> str: """防止 LLM 幻覺文字輸出到 Telegram。 [2026-04-18 台北] Bug-3.1 三層檢測升級 — Claude Opus 4.7 L1: 連續 ≥15 字中文且整段無標點 → 幻覺(原規則) L2: 命中簡體字/異體字黑名單 → 簡繁污染 L3: 連續中文片段內 4+ 字子串重複 ≥2 次 → 語意坍塌 - 任一層命中 → fallback 替換為「請人工確認」(或呼叫端指定字串) + 任一層命中 → fallback 替換為「請 AI 自動驗證確認」(或呼叫端指定字串) """ if not text or not isinstance(text, str): return fallback @@ -208,7 +209,7 @@ TOOLS = [ "type": "function", "function": { "name": "flag_for_human_review", - "description": "當情況複雜、AI 信心不足,或需要人工決策時,發送 Telegram 請求人工覆核", + "description": "當情況複雜、AI 信心不足,或需要 AI 例外決策時,發送 Telegram 請求AI 例外決策", "parameters": { "type": "object", "properties": { @@ -325,7 +326,7 @@ def _compute_business_impact(threat) -> dict: HEADER_DISPATCHER = "⚡ NemoTron 派發器" # 風險級別 ICON_CRITICAL = "🚨" # 高危險/立即行動 -ICON_WARNING = "⚠️" # 中風險/人工覆核 +ICON_WARNING = "⚠️" # 中風險/AI 例外決策 ICON_INSIGHT = "💡" # 低風險/策略建議 ICON_REPORT = "📊" # 例行報告 # 業務屬性 @@ -345,7 +346,7 @@ MATCH_TYPE_LABELS = { PRICE_BASIS_LABELS = { "total_price": "總價可比", "unit_price": "單位價可比", - "manual_review": "人工覆核後可比", + "manual_review": "AI 例外決策後可比", "none": "不可比", } ALERT_TIER_LABELS = { @@ -528,9 +529,9 @@ def _build_price_decision_envelope( action = "price_follow_review" if decision_type == "price_alert" else "identity_or_price_review" blocked_reason = ( - "價格調整需人工覆核;不得自動寫入或覆蓋正式競品價格" + "價格調整需 AI 例外決策;不得自動寫入或覆蓋正式競品價格" if decision_type == "price_alert" - else "身份、包裝、單位價或前台狀態需人工確認" + else "身份、包裝、單位價或前台狀態需 AI 自動驗證確認" ) risk_reduction = "high" if severity == "P1" else ("medium" if severity == "P2" else "watch") @@ -549,11 +550,12 @@ def _build_price_decision_envelope( "competitor_product_name": str(competitor_product_name or "")[:120], }, "evidence": evidence, - "analysis": _sanitize_text(analysis, fallback="請人工確認", max_len=300), + "analysis": _sanitize_text(analysis, fallback="請 AI 自動驗證確認", max_len=300), "recommended_action": { "action": action, "owner": "營運", - "requires_hitl": True, + LEGACY_REVIEW_GATE_KEY: False, + "requires_ai_exception": True, }, "expected_impact": { "momo_price": momo_value if momo_value > 0 else None, @@ -771,7 +773,7 @@ class NemotronDispatcher: "1. match_type 不是 exact,或 price_basis 不是 total_price,或 alert_tier 不是 price_alert_exact " "→ 不可直接價格告警,呼叫 flag_for_human_review,concern 說明需覆核身份、包裝或單位價。\n" "2. gap_pct < 5% 且 sales_delta < -30% → 非價格異常,呼叫 flag_for_human_review," - "concern 說明『價差接近 0 但銷量大幅下滑,疑似缺貨/下架/平台流量異常,請人工走查前台』。\n" + "concern 說明『價差接近 0 但銷量大幅下滑,疑似缺貨/下架/平台流量異常,請 AI 例外走查前台』。\n" "3. gap_pct ≥ 5% 且 risk=HIGH → trigger_price_alert(填入 momo_price, comp_price)。\n" "4. 我方價格低於競品且銷量正成長 → add_to_recommendation。\n" "5. confidence < 0.6 或其他複雜情況 → flag_for_human_review。\n" @@ -918,7 +920,7 @@ class NemotronDispatcher: "1. match_type 不是 exact,或 price_basis 不是 total_price,或 alert_tier 不是 price_alert_exact " "→ 不可直接價格告警,呼叫 flag_for_human_review,concern 說明需覆核身份、包裝或單位價。\n" "2. gap_pct < 5% 且 sales_delta < -30% → 非價格異常,呼叫 flag_for_human_review," - "concern 說明『價差接近 0 但銷量大幅下滑,疑似缺貨/下架/平台流量異常,請人工走查前台』。\n" + "concern 說明『價差接近 0 但銷量大幅下滑,疑似缺貨/下架/平台流量異常,請 AI 例外走查前台』。\n" "3. gap_pct ≥ 5% 且 risk=HIGH → trigger_price_alert(填入 momo_price, comp_price)。\n" "4. 我方價格低於競品且銷量正成長 → add_to_recommendation。\n" "5. confidence < 0.6 或其他複雜情況 → flag_for_human_review。\n" @@ -1076,13 +1078,13 @@ class NemotronDispatcher: continue if t.gap_pct < 5 and t.sales_7d_delta_pct < -30: - # Rule 1:價差微小但銷量大跌 → 非定價問題,人工確認 + # Rule 1:價差微小但銷量大跌 → 非定價問題,AI 自動驗證確認 self._exec_flag_for_human_review( sku=t.sku, name=t.name, concern=( f"🟡 [規則引擎] 價差僅 {t.gap_pct:+.1f}% 但銷量大跌 " f"{t.sales_7d_delta_pct:+.1f}%,疑似缺貨/下架/平台流量異常," - "請人工走查前台。" + "請 AI 例外走查前台。" ), confidence=0.80, footprint=footprint, @@ -1116,7 +1118,7 @@ class NemotronDispatcher: threat=t, ) else: - # Rule 4:其餘複雜情況 → 人工覆核 + # Rule 4:其餘複雜情況 → AI 例外決策 self._exec_flag_for_human_review( sku=t.sku, name=t.name, concern=( @@ -1236,7 +1238,7 @@ class NemotronDispatcher: competitor_product_name: str = "", ) -> str: """ - 類別二:人工覆核 + 類別二:AI 例外決策 客觀數據由 Python 注入(防幻覺),AI 診斷隔離在獨立欄位 B' 軌:補金額影響欄位 """ @@ -1268,7 +1270,7 @@ class NemotronDispatcher: ) return ( - f"{ICON_WARNING} [{HEADER_DISPATCHER}] 異常波動需人工覆核\n\n" + f"{ICON_WARNING} [{HEADER_DISPATCHER}] 異常波動需 AI 例外決策\n\n" f"🔍 待查商品:[{sku}] {name}\n\n" f"{data_block}" f"{match_block}" @@ -1460,7 +1462,7 @@ class NemotronDispatcher: competitor_product_id: str = "", competitor_product_name: str = "", ): - """發送語意化人工覆核請求""" + """發送語意化AI 例外決策請求""" concern = _sanitize_text(concern, fallback=f"數據走勢違背常理,疑似缺貨或前台異常。") msg = self._fmt_human_review( sku, name, concern, footprint, @@ -1496,13 +1498,13 @@ class NemotronDispatcher: ) self._send_telegram(msg, decision_envelope=decision_envelope) logger.info( - f"[Dispatcher] 人工覆核請求 → {sku} loss=${revenue_loss_7d:,.0f}" + f"[Dispatcher] AI 例外決策請求 → {sku} loss=${revenue_loss_7d:,.0f}" ) # ADR-007 雙寫 self._sink_insight_to_km( insight_type="human_review", sku=sku, name=name, - content=f"[人工覆核] {name}。疑慮:{concern}", + content=f"[AI 例外決策] {name}。疑慮:{concern}", metadata={"confidence": confidence, "gap_pct": gap_pct, "sales_delta": sales_delta, "momo_price": momo_price, "comp_price": comp_price, "revenue_loss_7d": revenue_loss_7d, @@ -1740,7 +1742,7 @@ class NemotronDispatcher: 閘門 A:銷量 ≤ -95% 絕對斷崖 → 100% 是缺貨/下架/前台異常,不論價差 (2026-04-18 傍晚升級:真實案例 sku=7440662 sales=-100% gap=6.1% 被 NemoTron 錯派降價) 閘門 B:銷量 ≤ -80% 且 |價差| < 5% → 中度斷崖 + 價差微小,定價非主因 - 命中任一閘門 → 強制走人工覆核,不進 NIM — Claude Opus 4.7 + 命中任一閘門 → 強制走 AI 例外決策,不進 NIM — Claude Opus 4.7 """ if not threats: return {"dispatched": 0, "skipped": 0, "errors": [], "nim_stats": {}} @@ -2102,7 +2104,7 @@ if __name__ == "__main__": recommended_price=980, # B' 軌驗證:跟進競品價 )) print() - print("=== 類別二:人工覆核 ===") + print("=== 類別二:AI 例外決策 ===") # [2026-04-18 台北] CLI 測試修正:_fmt_human_review 用 kwargs,避免位置錯位 — Claude Opus 4.7 print(NemotronDispatcher._fmt_human_review( sku="A001", name="玻尿酸面膜10片裝", @@ -2145,7 +2147,7 @@ if __name__ == "__main__": forced.append(t) else: nim_list.append(t) - print(f"強制人工覆核 {len(forced)} 筆(預期 3:閘門 A + 閘門 B × 2): " + print(f"強制 AI 例外決策 {len(forced)} 筆(預期 3:閘門 A + 閘門 B × 2): " f"{[t.sku for t in forced]}") print(f"送入 NIM 決策 {len(nim_list)} 筆(預期 1): " f"{[t.sku for t in nim_list]}") diff --git a/services/pchome_growth_momo_backfill_service.py b/services/pchome_growth_momo_backfill_service.py index 8fe632f..c1835aa 100644 --- a/services/pchome_growth_momo_backfill_service.py +++ b/services/pchome_growth_momo_backfill_service.py @@ -115,7 +115,7 @@ def run_pchome_growth_momo_backfill( """補高業績 PChome 商品的 MOMO 對應。 不呼叫 LLM,只搜尋 MOMO 候選,並只把可自動判斷的 total_price / unit_price - 寫入 external_offers;需人工確認的候選會以 needs_review 保存,不進價格判斷。 + 寫入 external_offers;需 AI 自動驗證確認的候選會以 needs_review 保存,不進價格判斷。 """ limit = max(1, min(int(limit or 12), 20)) build_payload = build_payload_func or _default_build_payload diff --git a/services/telegram_bot_service.py b/services/telegram_bot_service.py index d777b4c..e317de1 100644 --- a/services/telegram_bot_service.py +++ b/services/telegram_bot_service.py @@ -13,6 +13,8 @@ pip install python-telegram-bot>=20.0 - 每日趨勢摘要推播 """ +from __future__ import annotations + import os import json import asyncio @@ -509,7 +511,7 @@ class TrendTelegramBot: await query.answer() - # ADR-012 / A' 軌:HITL escalation 按鈕(momo:eig:{event_id} = event_ignore) + # ADR-012 / A' 軌:AI 例外事件忽略按鈕(momo:eig:{event_id} = event_ignore) # triaged_alert 鍵盤按鈕「🛑 忽略此事件」會 callback momo:eig: if data.startswith('momo:eig:'): await self._handle_event_ignore_callback(query, data) @@ -586,7 +588,7 @@ class TrendTelegramBot: await self._handle_settings_callback(query, data) async def _handle_event_ignore_callback(self, query, data: str): - """A' 軌:HITL 升級審核「🛑 忽略此事件」按鈕處理。 + """A' 軌:AI 例外決策「🛑 忽略此事件」按鈕處理。 callback_data 格式:momo:eig: 動作: @@ -594,7 +596,7 @@ class TrendTelegramBot: 2. 編輯原訊息:標記「已忽略 by @ 」 3. 失敗 best-effort,不阻斷 user 體驗 - ADR-012 §③:人工決策必有 audit trail;ai_insights 為 SOT。 + ADR-012 §③:AI 例外決策必有 audit trail;ai_insights 為 SOT。 Critic Critical-1 fix: user_label / ts_label 寫入 HTML 前須 escape; Critic Medium-2 fix: 空 event_id 直接拒絕,避免污染 audit 資料。 """ @@ -609,7 +611,7 @@ class TrendTelegramBot: await query.answer("event_id 缺失,忽略動作未生效", show_alert=False) except Exception: logger.debug("empty event_id callback answer failed", exc_info=True) - logger.warning("[EA HITL] empty event_id callback rejected: %r", data) + logger.warning("[EA AI Exception] empty event_id callback rejected: %r", data) return user = getattr(query, 'from_user', None) @@ -632,8 +634,8 @@ class TrendTelegramBot: VALUES (:type, :content, :conf, :by, :status, :meta) """), { - "type": "human_review", - "content": f"[EA HITL] 事件 {event_id} 由 {user_label_raw} 忽略", + "type": "ai_exception_decision", + "content": f"[EA AI Exception] 事件 {event_id} 由 {user_label_raw} 忽略", "conf": 1.0, "by": f"telegram:{user_label_raw}", "status": "ignored", @@ -642,6 +644,8 @@ class TrendTelegramBot: "decided_by": user_label_raw, "decided_at": ts_label_raw, "decision": "ignored", + "decision_mode": "ai_exception_decision", + "legacy_insight_type": "human_review", }, ensure_ascii=False), }, ) @@ -649,7 +653,7 @@ class TrendTelegramBot: finally: session.close() except Exception as audit_err: - logger.warning(f"[EA HITL] ai_insights audit 寫入失敗(不阻斷 UI): {audit_err}") + logger.warning(f"[EA AI Exception] ai_insights audit 寫入失敗(不阻斷 UI): {audit_err}") # Critic Critical-1: user_label / ts_label 須 HTML escape,避免攻擊者 # 透過 Telegram username 注入 /
/破版標籤
@@ -663,9 +667,9 @@ class TrendTelegramBot:
                 parse_mode='HTML',
             )
         except Exception as ui_err:
-            logger.debug(f"[EA HITL] edit_message 失敗(不阻斷): {ui_err}")
+            logger.debug(f"[EA AI Exception] edit_message 失敗(不阻斷): {ui_err}")
 
-        logger.info(f"[EA HITL] event_ignore event_id={event_id} by={user_label_raw}")
+        logger.info(f"[EA AI Exception] event_ignore event_id={event_id} by={user_label_raw}")
 
     async def _handle_openclaw_callback(self, query, context, data: str):
         """轉接 OpenClaw 完整菜單 callback,避免長輪詢 Bot 吃掉 /menu。"""