feat(adr-082): Phase 2 多 Agent 協作 — 5 角色辯證系統骨架上線

新增 5 個 Agent + Orchestrator + DecisionManager 接線:
- protocol.py: DiagnosisReport / ActionPlan / ReviewVerdict / CriticReport / DecisionPackage 型別系統
- DiagnosticianAgent: RCA 根因分析,confidence < 0.4 → ABSTAIN
- SolverAgent: 修復方案軍師,blast_radius 評分 + 降級 rule-based mock
- ReviewerAgent: 安全審查,HARD_RULES 靜態 pattern + blast_radius 閾值 (>50 revision, >80 reject)
- CriticAgent: 刻意唱反調,強制 3 問批判性思維,critical challenge → REJECT
- CoordinatorAgent: 純規則聚合,6 級決策閘,REQUEST_REVISION → 強制人工
- AgentOrchestrator: 30s 全局超時,Reviewer ‖ Critic 並行,DB Immutable Event Sourcing + Redis Streams
- DecisionManager: AIOPS_P2_ENABLED gate + _package_to_proposal_data 橋接既有 proposal_data 格式
- AgentSession DB table + 4 個複合 index
- ADR-082 決策記錄

Gate 2 修復(7 項):
- CRITICAL: DELETE FROM regex lookahead 位置錯誤(移至 FROM 後)
- CRITICAL: REQUEST_REVISION 可抵達 auto-execute 路徑(改回 requires_human_approval=True)
- IMPORTANT: _extract_json flat regex 不支援巢狀 JSON(改 find/rfind 邊界提取)
- IMPORTANT: all_degraded 遺漏 verdict.degraded(補全 4 個 Agent)
- IMPORTANT: Solver ABSTAIN guard 放行降級假設(改為無論 hypotheses 有無均跳過)
- IMPORTANT: dataclasses.asdict() Enum 未序列化導致 DB 寫入靜默失敗(加 json.dumps default handler)
- IMPORTANT: P2 gate 直讀屬性繞過父 Phase 守衛(改用 is_phase_enabled(2))

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-15 13:33:13 +08:00
parent d51705b4ec
commit 5ddba6d6e0
11 changed files with 2171 additions and 4 deletions

View File

@@ -0,0 +1,253 @@
"""
AWOOOI AIOps Phase 2 — 多 Agent 協作訊息協定
==============================================
定義 5 個 Agent 間傳遞的不可變資料型別。
設計原則:
1. 每個 Agent 有明確的 Input / Output 型別(不共用 dict
2. 所有型別都是 dataclass快速、可序列化、無外部依賴
3. 降級 / 棄權用明確 AgentVote.ABSTAIN不用 None 代替
4. 全程 immutable — Agent 不得修改彼此的輸出(防 prompt 污染)
ADR-082: 多 Agent 協作架構Phase 2
2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 2 初始建立
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# Enums
# ─────────────────────────────────────────────────────────────────────────────
class AgentRole(str, Enum):
"""Phase 2 五角色標識"""
DIAGNOSTICIAN = "diagnostician"
SOLVER = "solver"
REVIEWER = "reviewer"
CRITIC = "critic"
COORDINATOR = "coordinator"
class AgentVote(str, Enum):
"""Agent 投票結果"""
APPROVE = "approve"
REJECT = "reject"
REQUEST_REVISION = "request_revision"
ABSTAIN = "abstain" # 熔斷 / 超時 / 無足夠資訊
DEGRADED = "degraded" # 降級路徑rule-based mock
class AgentSessionStatus(str, Enum):
"""AgentSession 整體狀態"""
RUNNING = "running"
COMPLETED = "completed"
DEGRADED = "degraded" # 部分 Agent 熔斷但仍完成
FAILED = "failed" # Coordinator 無法輸出任何結論
TIMEOUT = "timeout" # 全流程 > 30s
# ─────────────────────────────────────────────────────────────────────────────
# Diagnostician Output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Hypothesis:
"""單一根因假設"""
description: str
confidence: float # 0.0 ~ 1.0
evidence_chain: list[str] # 支持此假設的 evidence key
category: str = "" # alert_categoryKubePod / HostDisk 等)
@dataclass
class DiagnosisReport:
"""
Diagnostician Agent 輸出
包含多個根因假設(按信心排序),
top-1 confidence < 0.4 觸發 Coordinator 回退 Investigator 重抓。
"""
hypotheses: list[Hypothesis]
evidence_snapshot_id: str
latency_ms: int
vote: AgentVote = AgentVote.APPROVE # 資訊足夠 = APPROVE不足 = ABSTAIN
degraded: bool = False # 熔斷降級標記
@property
def top_hypothesis(self) -> Hypothesis | None:
"""最高信心假設"""
return self.hypotheses[0] if self.hypotheses else None
@property
def top_confidence(self) -> float:
return self.top_hypothesis.confidence if self.top_hypothesis else 0.0
# ─────────────────────────────────────────────────────────────────────────────
# Solver Output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class CandidateAction:
"""單一候選修復動作"""
action: str # 動作描述e.g. "restart_service:awoooi-api"
blast_radius: int # 0-100影響範圍評分
rollback_cost: int # 0-100回滾難度
confidence: float # 0.0 ~ 1.0
rationale: str = "" # 為什麼選此方案
@dataclass
class ActionPlan:
"""
Solver Agent 輸出
對每個根因假設提出 ≥1 個候選方案(含 blast_radius / rollback_cost
blast_radius > 50 → Reviewer 必須標 `request_revision`。
"""
candidates: list[CandidateAction]
diagnosis_report: DiagnosisReport
latency_ms: int
vote: AgentVote = AgentVote.APPROVE
degraded: bool = False
@property
def top_candidate(self) -> CandidateAction | None:
"""最高信心候選方案"""
return max(self.candidates, key=lambda c: c.confidence) if self.candidates else None
# ─────────────────────────────────────────────────────────────────────────────
# Reviewer Output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class ReviewVerdict:
"""
Reviewer Agent 輸出(安全審查)
硬核拒絕 HARD_RULES 觸碰動作delete node / DROP TABLE / force push 等)。
vote = REJECT 時Coordinator 不得執行任何候選方案。
"""
vote: AgentVote
reason: str
blocked_actions: list[str] # 被拒絕的動作清單
safe_actions: list[str] # 通過安全審查的動作
latency_ms: int
degraded: bool = False
@property
def is_safe(self) -> bool:
return self.vote == AgentVote.APPROVE and bool(self.safe_actions)
# ─────────────────────────────────────────────────────────────────────────────
# Critic Output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Challenge:
"""Critic 的單一質疑"""
target: str # "diagnosis" | "action:{action_str}"
argument: str # 質疑的具體理由
severity: str # "minor" | "major" | "critical"
@dataclass
class CriticReport:
"""
Critic Agent 輸出(刻意唱反調)
challenge_count > 0 是 Phase 2 退出條件之一。
major/critical challenge 觸發 Coordinator 降低對 Solver 方案的信心。
"""
challenges: list[Challenge]
overall_assessment: str
latency_ms: int
vote: AgentVote = AgentVote.APPROVE # APPROVE=無重大反對REJECT=有 critical challenge
degraded: bool = False
@property
def challenge_count(self) -> int:
return len(self.challenges)
@property
def has_critical_challenge(self) -> bool:
return any(c.severity == "critical" for c in self.challenges)
# ─────────────────────────────────────────────────────────────────────────────
# Coordinator Output
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class DecisionPackage:
"""
Coordinator Agent 輸出(最終決策包)
包含:
- recommended_action: 最終推薦動作None = 棄權 / 升級人工)
- confidence: 綜合信心Solver × Reviewer × Critic 加權)
- requires_human_approval: 是否需要人工審核
- debate_summary: 辯證歷程摘要(供 Audit Trail + 學習閉環)
- session_status: 整體辯證狀態
"""
recommended_action: str | None
confidence: float
requires_human_approval: bool
debate_summary: str
session_status: AgentSessionStatus
latency_ms: int
# 保留各 Agent 原始輸出(供學習閉環查詢)
diagnosis: DiagnosisReport | None = None
action_plan: ActionPlan | None = None
reviewer_verdict: ReviewVerdict | None = None
critic_report: CriticReport | None = None
# 棄選方案(含原因)
rejected_actions: list[dict[str, Any]] = field(default_factory=list)
# 阻擋原因requires_human_approval=True 時說明)
blocked_reason: str | None = None
# 全部 Agent 都降級(更嚴格的人工審核信號)
all_agents_degraded: bool = False
@property
def is_actionable(self) -> bool:
"""可以執行(有推薦動作且信心 > 0.4 且 Reviewer 通過)"""
if not self.recommended_action:
return False
if self.confidence < 0.4:
return False
if self.reviewer_verdict and self.reviewer_verdict.vote == AgentVote.REJECT:
return False
return True
# ─────────────────────────────────────────────────────────────────────────────
# Agent Session RecordDB 寫入用)
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class AgentTurn:
"""
單次 Agent 發言記錄
寫入 `agent_sessions` 表的一行,
session_id + agent_role 唯一確定一次辯證發言。
"""
session_id: str
incident_id: str
agent_role: AgentRole
input_hash: str # sha256(input_json)[:16]
output_json: dict[str, Any] # Agent 原始輸出
latency_ms: int
vote: AgentVote
degraded: bool = False