chore(observability): clarify quick review completion copy
All checks were successful
CD Pipeline / deploy (push) Successful in 1m4s

This commit is contained in:
OoO
2026-05-06 19:49:28 +08:00
parent dc7fe371bd
commit 308efdce25
23 changed files with 863 additions and 341 deletions

View File

@@ -8,8 +8,8 @@ AI 3.0 Autonomous Operations:
- Continuous improvement loop
ADR-012 Compliance:
§③ 單一 audit trail — 所有基行完畢後必發 triaged_alert Telegram
§⑤ 雙寫強制 — ai_insights (由 orchestrator._log_decision) + Telegram
§③ 單一 audit trail — 有實證的行動/升級需寫入 ai_insights 並發 triaged_alert Telegram
§⑤ 雙寫強制 — 無實證低信心升級只寫 suppressed cooldown避免 human_review 噪音
ADR-013 Compliance:
resource_optimization trigger → auto_heal_service.handle_exception
"""
@@ -42,10 +42,18 @@ SSH_PORT = int(os.getenv("ELEPHANT_ALPHA_SSH_PORT", "22"))
SSH_CONNECT_TIMEOUT = int(os.getenv("ELEPHANT_ALPHA_SSH_CONNECT_TIMEOUT", "10"))
SSH_COMMAND_TIMEOUT = int(os.getenv("ELEPHANT_ALPHA_SSH_COMMAND_TIMEOUT", "60"))
CACHE_DB_PATH = os.getenv("ELEPHANT_ALPHA_CACHE_DB", ":memory:")
_DEFAULT_CACHE_DB_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"data",
"elephant_alpha_cache.db",
)
CACHE_DB_PATH = os.getenv("ELEPHANT_ALPHA_CACHE_DB", _DEFAULT_CACHE_DB_PATH)
ESCALATION_COOLDOWN_MIN = int(os.getenv("ELEPHANT_ALPHA_ESCALATION_COOLDOWN_MIN", "30"))
NO_EVIDENCE_ESCALATION_COOLDOWN_MIN = int(os.getenv("ELEPHANT_ALPHA_NO_EVIDENCE_COOLDOWN_MIN", "360"))
CONFIDENCE_THRESHOLD = float(os.getenv("ELEPHANT_ALPHA_CONFIDENCE_THRESHOLD", "0.7"))
MAX_AUTONOMOUS_DECISIONS_PER_HOUR = int(os.getenv("ELEPHANT_ALPHA_MAX_AUTONOMOUS_DECISIONS_PER_HOUR", "10"))
RESOURCE_QUEUE_THRESHOLD = int(os.getenv("ELEPHANT_ALPHA_RESOURCE_QUEUE_THRESHOLD", "10"))
RESOURCE_LOAD_THRESHOLD = float(os.getenv("ELEPHANT_ALPHA_RESOURCE_LOAD_THRESHOLD", "80"))
# ---- Constants ----
_ALLOWED_ACTION_TYPES = frozenset({
@@ -123,6 +131,15 @@ _PRICE_RELATED_TRIGGERS = frozenset({
"threat_escalation",
})
_OPERATIONAL_PENDING_ACTION_TYPES = frozenset({
"agent_safe_action",
"auto_heal",
"resource_optimization",
"scheduler_retry",
})
_NO_EVIDENCE_DEDUP_PREFIX = "no_evidence:"
def _zh_trigger(trigger_type: str) -> str:
return _TRIGGER_ZH.get(trigger_type, trigger_type)
@@ -210,6 +227,8 @@ class ElephantAlphaAutonomousEngine:
# ---- DB ----
def _init_cache_db(self) -> None:
self._db_lock = threading.Lock()
if CACHE_DB_PATH != ":memory:":
os.makedirs(os.path.dirname(CACHE_DB_PATH), exist_ok=True)
self._conn = sqlite3.connect(CACHE_DB_PATH, check_same_thread=False)
self._conn.execute("""
CREATE TABLE IF NOT EXISTS escalation_dedup (
@@ -315,9 +334,7 @@ class ElephantAlphaAutonomousEngine:
for trigger in self.triggers:
if not trigger.enabled:
continue
cooldown_min = self._get_cooldown(trigger.trigger_type)
last = trigger.last_triggered
if last and (datetime.now() - last).total_seconds() / 60 < cooldown_min:
if self._is_trigger_in_cooldown(trigger):
continue
if await self._evaluate_trigger(trigger):
await self._execute_autonomous_decision(trigger)
@@ -327,8 +344,32 @@ class ElephantAlphaAutonomousEngine:
return self._get_cooldown_min(trigger_type)
def _get_cooldown_min(self, trigger_type: str) -> int:
if trigger_type.startswith(_NO_EVIDENCE_DEDUP_PREFIX):
return NO_EVIDENCE_ESCALATION_COOLDOWN_MIN
return ESCALATION_COOLDOWN_MIN
def _is_trigger_in_cooldown(self, trigger: AutonomousTrigger) -> bool:
"""同時使用記憶體與持久化冷卻,避免 thread/container 重啟後重送噪音告警。"""
def _within_cooldown(trigger_key: str, last_ts: Optional[float]) -> bool:
stored_ts = self._load_escalation(trigger_key)
if stored_ts:
last_ts = max(last_ts or 0, float(stored_ts))
if not last_ts:
return False
cooldown_sec = self._get_cooldown_min(trigger_key) * 60
return (datetime.now().timestamp() - last_ts) < cooldown_sec
last_ts: Optional[float] = None
if trigger.last_triggered:
last_ts = trigger.last_triggered.timestamp()
if _within_cooldown(trigger.trigger_type, last_ts):
return True
return _within_cooldown(f"{_NO_EVIDENCE_DEDUP_PREFIX}{trigger.trigger_type}", None)
async def _evaluate_trigger(self, trigger: AutonomousTrigger) -> bool:
try:
if trigger.trigger_type == "price_drop_alert":
@@ -415,8 +456,10 @@ class ElephantAlphaAutonomousEngine:
session.close()
async def _check_resource_optimization_trigger(self, trigger: AutonomousTrigger) -> bool:
return (self._get_action_queue_size() > 10
or self._get_system_load_percentage() > 80)
operational_queue_size = self._get_action_queue_size()
system_load_pct = self._get_system_load_percentage()
return (operational_queue_size > RESOURCE_QUEUE_THRESHOLD
or system_load_pct > RESOURCE_LOAD_THRESHOLD)
async def _check_code_exception_trigger(self, trigger: AutonomousTrigger) -> bool:
containers = trigger.conditions.get("scan_containers", ["momo-pro-system", "momo-scheduler"])
@@ -696,19 +739,19 @@ class ElephantAlphaAutonomousEngine:
將「步驟 1: [OpenClaw] 生成策略」這類元流程文字換成
「[SKU] 商品MOMO $X / PChome $Y流失 NT$ Z建議 NT$ W」具體可決策行動。
失敗回 None由呼叫端 fallback 至既有 execution_plan 文字。
本方法為 best-effort任何例外都不阻斷 escalation 流程。
失敗回 None由呼叫端判斷是否 suppressed不得 fallback 至 LLM plan 文字。
本方法為 best-effort任何例外都不阻斷 escalation 判斷流程。
Critic High-1 fix: 加 5 秒短超時防止阻塞 escalation cooldown 視窗
Hermes 完整 run 可能 30-60sHITL 訊息應快速送出)
Critic High-2 fix: 若每筆都缺 loss/rec_price視同無料、return None 觸發 fallback
Critic High-2 fix: 若每筆都缺 loss/rec_price視同無料、return None 觸發 suppressed
"""
# 使用 5s 短超時Hermes 熱駐留時實測 < 10s但若需冷啟動會拖到 30s+
# HITL 訊息延遲不可大於 10s(影響統帥決策時效性),寧可 fallback 到原 plan 文字
# HITL 訊息延遲不可大於 10stimeout 後由呼叫端 suppressed避免無實證文字洗版
try:
result = await asyncio.wait_for(self._hermes_analyze(), timeout=5)
except asyncio.TimeoutError:
self._log.warning("Pre-fetch Hermes 5s timeout; falling back to plan text")
self._log.warning("Pre-fetch Hermes 5s timeout; no concrete data for escalation")
return None
except Exception as e:
self._log.warning("Pre-fetch Hermes threats failed (non-blocking): %s", e)
@@ -716,7 +759,7 @@ class ElephantAlphaAutonomousEngine:
threats = getattr(result, "threats", None) or []
if not threats:
self._log.info("Pre-fetch Hermes returned 0 threats; falling back to plan text")
self._log.info("Pre-fetch Hermes returned 0 threats; no concrete data for escalation")
return None
# 模組頂部 import 較乾淨,但這裡保留 lazy import 避免兩服務循環依賴
@@ -756,8 +799,8 @@ class ElephantAlphaAutonomousEngine:
if not any_concrete:
# Critic High-2: 全部都只有「MOMO $X vs PChome $Y」乾巴巴兩行
# 比原本「步驟 1:OpenClaw 生成策略」更空泛。返回 None 觸發 plan fallback
self._log.info("Pre-fetch threats lacked impact figures on all rows; falling back")
# 比原本「步驟 1:OpenClaw 生成策略」更空泛。返回 None 觸發 suppressed
self._log.info("Pre-fetch threats lacked impact figures on all rows; no concrete data for escalation")
return None
self._log.info("Pre-fetch Hermes threats produced %d concrete actions", len(lines))
@@ -881,8 +924,106 @@ class ElephantAlphaAutonomousEngine:
except Exception as e:
self._log.error("Telegram audit failed (non-blocking): %s", e)
def _build_resource_escalation_actions(self) -> Optional[List[str]]:
queue_size = self._safe_metric(self._get_action_queue_size, default=0)
system_load_pct = self._safe_metric(self._get_system_load_percentage, default=0.0)
evidence = []
if queue_size > RESOURCE_QUEUE_THRESHOLD:
evidence.append(f"auto action queue {queue_size} 筆 > 門檻 {RESOURCE_QUEUE_THRESHOLD}")
if system_load_pct > RESOURCE_LOAD_THRESHOLD:
evidence.append(f"CPU load {system_load_pct:.1f}% > 門檻 {RESOURCE_LOAD_THRESHOLD:.0f}%")
if not evidence:
return None
return [
f"📊 實測資源訊號:{''.join(evidence)}",
"🔎 優先檢查 action_plans 的 auto_pending/running 任務是否卡住",
"🔧 僅允許處置 app/scheduler 類服務;禁止操作 momo-db 容器生命週期",
]
@staticmethod
def _build_code_exception_escalation_actions(trigger: AutonomousTrigger) -> Optional[List[str]]:
error_msg = (trigger.temp_error_msg or "").strip()
if not error_msg:
return None
first_line = next((line.strip() for line in error_msg.splitlines() if line.strip()), "容器日誌偵測到 Python 例外")
target = trigger.temp_target_file or "未能自動定位檔案"
return [
f"🧾 例外摘要:{first_line[:180]}",
f"📍 疑似檔案:{target}",
"🔎 建議先查最近 5 分鐘 app/scheduler logs確認是否仍持續重現",
]
def _suppress_no_evidence_escalation(
self,
decision: StrategicDecision,
trigger: AutonomousTrigger,
reason: str,
) -> None:
dedup_key = f"{_NO_EVIDENCE_DEDUP_PREFIX}{trigger.trigger_type}"
self._store_escalation(dedup_key)
self._log.warning(
"EA no-evidence escalation suppressed: trigger=%s confidence=%.2f reason=%s cooldown_min=%s",
trigger.trigger_type,
decision.confidence,
reason,
NO_EVIDENCE_ESCALATION_COOLDOWN_MIN,
)
async def _escalate_to_human(self, decision: StrategicDecision, trigger: AutonomousTrigger) -> None:
self._log.warning("Escalating to human: %s", trigger.trigger_type)
concrete_actions: Optional[List[str]] = None
ai_summary_text = (decision.reasoning or "")[:300]
ai_cause_text = (
f"觸發類型:{_zh_trigger(trigger.trigger_type)} | "
f"信心度:{decision.confidence:.2f} | "
f"參與模組:{', '.join(_AGENT_LABEL.get(a.lower(), a) for a in decision.agents_required)}"
)
if trigger.trigger_type in _PRICE_RELATED_TRIGGERS:
concrete_actions = (trigger.conditions or {}).get("_prefetched_hermes_threats")
if not concrete_actions:
try:
concrete_actions = await self._fetch_hermes_threats_summary(top_n=5)
except Exception as e:
self._log.warning("Pre-fetch threats raised (non-blocking): %s", e)
concrete_actions = None
if not concrete_actions:
self._suppress_no_evidence_escalation(decision, trigger, "price_trigger_without_hermes_threats")
return
elif trigger.trigger_type == "resource_optimization":
concrete_actions = self._build_resource_escalation_actions()
if concrete_actions:
ai_summary_text = "資源類升級含實測 queue/load 訊號,請依安全邊界判斷是否處置。"
ai_cause_text = (
f"觸發類型:{_zh_trigger(trigger.trigger_type)} | "
f"信心度:{decision.confidence:.2f} | "
"證據來源action_plans queue / host CPU load"
)
else:
self._suppress_no_evidence_escalation(decision, trigger, "resource_trigger_without_operational_metrics")
return
elif trigger.trigger_type == "code_exception":
concrete_actions = self._build_code_exception_escalation_actions(trigger)
if concrete_actions:
ai_summary_text = "容器日誌偵測到具體例外,已轉人工審核避免自動修復風險擴大。"
ai_cause_text = (
f"觸發類型:{_zh_trigger(trigger.trigger_type)} | "
f"信心度:{decision.confidence:.2f} | "
"證據來源:最近容器 logs"
)
else:
self._suppress_no_evidence_escalation(decision, trigger, "code_exception_without_log_context")
return
else:
if not decision.execution_plan and not (decision.reasoning or "").strip():
self._suppress_no_evidence_escalation(decision, trigger, "empty_decision_payload")
return
concrete_actions = [_zh_step(s) for s in decision.execution_plan[:3]]
session = get_session()
try:
row = session.execute(
@@ -930,68 +1071,16 @@ class ElephantAlphaAutonomousEngine:
if not dedup_ts or (datetime.now().timestamp() - dedup_ts) / 60 >= cooldown_min:
self._store_escalation(trigger.trigger_type)
# A' 軌:價格類觸發前 pre-fetch Hermes 具體威脅清單,
# 取代「步驟 1:[OpenClaw] 生成策略」這類元流程文字。
# — Claude Opus 4.7 (2026-05-02)
concrete_actions: Optional[List[str]] = None
if trigger.trigger_type in _PRICE_RELATED_TRIGGERS:
try:
concrete_actions = await self._fetch_hermes_threats_summary(top_n=5)
except Exception as e:
self._log.warning("Pre-fetch threats raised (non-blocking): %s", e)
concrete_actions = None
# ─── Operation Ollama-First v5.0 修補:消除空泛幻覺訊息 ───
# 統帥反饋2026-05-03fallback 路徑帶 OpenClaw Gemini plan 文字 +
# decision.reasoning 全是「312 SKU / 23% / 14 項任務」幻覺數字,無 DB 鉤住,
# 嚴重誤導決策。修法concrete=Hermes 實證 vs concrete=None 兩條路徑徹底分離。
# - 有實證 → 完整訊息(含 SKU 流失金額)
# - 無實證 → 極簡訊息「Hermes 即時數據不可用」+ 不再灌 LLM 幻覺
from services.telegram_templates import triaged_alert, _send_telegram_raw
if concrete_actions:
# 有實證數據路徑:保留完整訊息
ai_actions_payload = concrete_actions
ai_summary_text = (decision.reasoning or "")[:300]
ai_cause_text = (
f"觸發類型:{_zh_trigger(trigger.trigger_type)} | "
f"信心度:{decision.confidence:.2f} | "
f"參與模組:{', '.join(_AGENT_LABEL.get(a.lower(), a) for a in decision.agents_required)}"
)
else:
# 無實證數據路徑:極簡訊息,明確標註無數據
self._log.warning(
"EA escalation 落入 no-concrete-data fallback (trigger=%s)"
"送極簡訊息避免 LLM 幻覺數字誤導統帥",
trigger.trigger_type
)
ai_actions_payload = [
"⚠️ Hermes 即時威脅清單不可用5s timeout 或無 SKU 命中)",
"📋 建議:手動下 SQL 查詢過去 24h competitor_price_history 確認狀況",
"🔧 或SSH 188 跑 docker exec momo-pro-system python -c "
"'from services.hermes_analyst_service import HermesAnalystService;"
" print(HermesAnalystService().run().threats[:5])'",
]
ai_summary_text = (
f"⚠️ 本訊息為**無實證**告警Hermes pre-fetch 失敗,"
f"以下原始決策內容含 LLM 自由發揮數字(非 DB 數據),請審慎參考。"
)
ai_cause_text = (
f"觸發類型:{_zh_trigger(trigger.trigger_type)} | "
f"信心度:{decision.confidence:.2f} | "
f"⚠️ 無 Hermes SKU 數據(不顯示 LLM 幻覺 plan 文字)"
)
ai_actions_payload = concrete_actions
try:
msg, keyboard = triaged_alert(
base_event={
"event_type": "ea_escalation",
"title": f"🐘 EA 升級審核 · {_zh_trigger(trigger.trigger_type)}",
"summary": (
f"自主決策信心度 {decision.confidence:.2f} 低於門檻,需人工批准"
+ ("" if concrete_actions else "(⚠️ 無實證數據)")
),
"summary": f"自主決策信心度 {decision.confidence:.2f} 低於門檻,需人工批准",
"id": f"ea_review_{int(datetime.now().timestamp())}",
},
tier_label="🐘 Elephant Alpha · L3 HITL",
@@ -1059,10 +1148,19 @@ class ElephantAlphaAutonomousEngine:
def _get_action_queue_size(self) -> int:
session = get_session()
try:
operational_types = "', '".join(sorted(_OPERATIONAL_PENDING_ACTION_TYPES))
row = session.execute(
text("SELECT COUNT(*) AS count FROM action_plans WHERE status = 'pending'")
text(f"""
SELECT COUNT(*) AS count
FROM action_plans
WHERE status IN ('auto_pending', 'running')
OR (
status = 'pending'
AND COALESCE(action_type, '') IN ('{operational_types}')
)
""")
).fetchone()
return row.count if row else 0
return int(row[0] or 0) if row else 0
finally:
session.close()