清除 AI 自動化 P2 人工語意債
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
ogt
2026-07-01 14:01:21 +08:00
parent 0a7bdd819b
commit e15d543aa2
17 changed files with 124 additions and 107 deletions

View File

@@ -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 快取'

View File

@@ -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 '',

View File

@@ -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:

View File

@@ -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,

View File

@@ -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]:

View File

@@ -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"⚠️ <b>AiderHeal 已略過自動修復</b>\n"
f"├ 檔案:<code>{html.escape(target_file[:200])}</code>\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"⚠️ <b>AiderHealdiff 過大,需人工審核</b>\n"
f"⚠️ <b>AiderHealdiff 過大,需 AI 例外決策</b>\n"
f"├ 檔案:<code>{target_file}</code>\n"
f"├ diff{diff_lines} 行(上限 {MAX_DIFF_LINES}\n"
f"└ 錯誤:<code>{error_type}</code>"
@@ -481,7 +481,7 @@ def execute_code_fix(
f"🔄 <b>AiderHeal 自動回滾</b>\n"
f"├ 原 commit<code>{commit_sha}</code>\n"
f"├ 回滾 commit<code>{revert_sha}</code>\n"
f"└ 需人工排查:<code>{error_type}</code> in <code>{target_file}</code>"
f"└ 需 AI 例外排查:<code>{error_type}</code> in <code>{target_file}</code>"
)
else:
msg = f"[AiderHeal] 回滾失敗!需立即人工介入:{revert_err}"

View File

@@ -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=truehuman_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:

View File

@@ -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-60sHITL 訊息應快速送出)
Hermes 完整 run 可能 30-60sAI 例外決策訊息應快速送出)
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,

View File

@@ -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",
},
)

View File

@@ -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}",

View File

@@ -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 設為 MEDrecommended_action 必須寫「價差接近零但業績異常下滑,建議立即人工走查前台頁面(確認是否缺貨/下架/頁面異常)」
- risk 設為 MEDrecommended_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, '

View File

@@ -14,7 +14,7 @@ Owen 強調的 v5.0 護欄 #1ADR-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',

View File

@@ -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 Embeddingopenclaw_learning_service
'km_embedding_worker', # 60s retry queue worker

View File

@@ -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'),

View File

@@ -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_reviewconcern 說明需覆核身份、包裝或單位價。\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_reviewconcern 說明需覆核身份、包裝或單位價。\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]}")

View File

@@ -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

View File

@@ -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:<id>
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:<event_id>
動作:
@@ -594,7 +596,7 @@ class TrendTelegramBot:
2. 編輯原訊息:標記「已忽略 by <user> @ <ts>」
3. 失敗 best-effort不阻斷 user 體驗
ADR-012 §③:人工決策必有 audit trailai_insights 為 SOT。
ADR-012 §③:AI 例外決策必有 audit trailai_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 注入 <a>/<pre>/破版標籤
@@ -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。"""