211 lines
7.9 KiB
Python
211 lines
7.9 KiB
Python
from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, Index, Float
|
||
from sqlalchemy.orm import relationship
|
||
from database.models import Base
|
||
from datetime import datetime
|
||
|
||
|
||
class AgentContext(Base):
|
||
"""
|
||
共享上下文表(替代硬編碼鏈),支援多 Agent 存取與 TTL。
|
||
索引:(session_id, agent_name, context_key) 以加速跨 Agent 查詢。
|
||
"""
|
||
__tablename__ = 'agent_context'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
session_id = Column(String(64), nullable=False, index=True)
|
||
agent_name = Column(String(50), nullable=False, index=True)
|
||
context_key = Column(String(100), nullable=False)
|
||
context_val = Column(Text) # JSON string
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
ttl_minutes = Column(Integer, default=60)
|
||
|
||
__table_args__ = (
|
||
Index('idx_agent_context_session_key', 'session_id', 'agent_name', 'context_key'),
|
||
Index('idx_agent_context_session_ttl', 'session_id', 'created_at'),
|
||
{'extend_existing': True},
|
||
)
|
||
|
||
|
||
class ActionPlan(Base):
|
||
"""
|
||
行動計畫表 — 統一 schema(超集)。
|
||
Group A(01-init.sql / CodeReview / OpenClaw):
|
||
action_type, description, priority, metadata_json
|
||
Group B(migration 017 / watcher_agent / ai_orchestrator):
|
||
session_id, plan_type, sku, payload, created_by, approved_by, executed_at
|
||
migration 019 已在 DB 補齊所有欄位。
|
||
"""
|
||
__tablename__ = 'action_plans'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
# Group A columns
|
||
action_type = Column(String(100), nullable=True) # code_review_fix / openclaw_recommendation
|
||
description = Column(Text) # 人類可讀的行動說明
|
||
status = Column(String(50), default='pending') # pending/auto_pending/pending_review/executed
|
||
priority = Column(Integer, default=3) # 1=critical 2=high 3=medium 4=low
|
||
metadata_json = Column(Text) # JSON: pipeline_id/commit_sha/findings
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
# Group B columns (ADR-012 / NemoTron)
|
||
session_id = Column(String(64), nullable=True)
|
||
plan_type = Column(String(50), nullable=True) # price_adjust / restock / campaign
|
||
sku = Column(String(100), nullable=True)
|
||
payload = Column(Text) # JSON 行動內容
|
||
created_by = Column(String(50)) # nemotron / openclaw
|
||
approved_by = Column(String(100), nullable=True) # Telegram user_id
|
||
executed_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index('idx_action_plans_type', 'action_type'),
|
||
Index('idx_action_plan_sku_status', 'sku', 'status'),
|
||
Index('idx_action_plan_created', 'created_at'),
|
||
{'extend_existing': True},
|
||
)
|
||
|
||
|
||
class ActionOutcome(Base):
|
||
"""
|
||
行動結果追蹤(閉環學習核心)。
|
||
"""
|
||
__tablename__ = 'action_outcomes'
|
||
__table_args__ = {'extend_existing': True}
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
plan_id = Column(Integer, ForeignKey('action_plans.id'), nullable=False)
|
||
metric_type = Column(String(50), nullable=True) # sales_7d / price_rank / conversion
|
||
before_val = Column(Float)
|
||
after_val = Column(Float)
|
||
measured_at = Column(DateTime)
|
||
verdict = Column(String(20)) # effective / neutral / backfired
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
|
||
plan = relationship("ActionPlan", backref="outcomes")
|
||
|
||
|
||
class AgentStrategyWeights(Base):
|
||
"""
|
||
Agent strategy weights (OpenClaw learning accumulation).
|
||
Index: strategy_key for fast updates/query.
|
||
"""
|
||
__tablename__ = 'agent_strategy_weights'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
strategy_key = Column(String(100), unique=True, nullable=False) # e.g. price_cut_when_gap_gt_5pct
|
||
weight = Column(Float, default=1.0)
|
||
success_cnt = Column(Integer, default=0)
|
||
fail_cnt = Column(Integer, default=0)
|
||
updated_at = Column(DateTime, default=datetime.now)
|
||
|
||
__table_args__ = (
|
||
Index('idx_strategy_key', 'strategy_key'),
|
||
{'extend_existing': True},
|
||
)
|
||
|
||
|
||
class Incident(Base):
|
||
"""
|
||
Incident tracking for AIOps auto-healing.
|
||
"""
|
||
__tablename__ = 'incidents'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
task_name = Column(String(100), nullable=False)
|
||
error_type = Column(String(50), nullable=False)
|
||
error_message = Column(Text, nullable=False)
|
||
error_traceback = Column(Text)
|
||
traceback_str = Column(Text)
|
||
severity = Column(String(20), default='medium')
|
||
status = Column(String(20), default='open') # open/healing/closed/escalated
|
||
retry_count = Column(Integer, default=0)
|
||
playbook_id = Column(Integer, ForeignKey('playbooks.id'), nullable=True)
|
||
matched_playbook_id = Column(Integer, ForeignKey('playbooks.id'), nullable=True)
|
||
resolved_at = Column(DateTime, nullable=True)
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
updated_at = Column(DateTime, default=datetime.now)
|
||
|
||
# Relationship
|
||
playbook = relationship("Playbook", foreign_keys=[matched_playbook_id], backref="incidents")
|
||
|
||
|
||
class Playbook(Base):
|
||
"""
|
||
Playbook definitions for auto-healing actions.
|
||
"""
|
||
__tablename__ = 'playbooks'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
name = Column(String(100), nullable=False)
|
||
description = Column(Text)
|
||
error_type = Column(String(50), nullable=False)
|
||
match_pattern = Column(Text) # JSON array of patterns
|
||
action_type = Column(String(50), nullable=False) # DOCKER_RESTART/SSH_CMD/ALERT_ONLY/WAIT_RETRY
|
||
action_params = Column(Text) # JSON params
|
||
max_retries = Column(Integer, default=3)
|
||
cooldown_min = Column(Integer, default=30)
|
||
is_active = Column(Boolean, default=True)
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
updated_at = Column(DateTime, default=datetime.now)
|
||
|
||
def get_match_patterns(self):
|
||
"""Parse match_pattern JSON to list"""
|
||
if self.match_pattern:
|
||
try:
|
||
import json
|
||
return json.loads(self.match_pattern)
|
||
except:
|
||
return []
|
||
return []
|
||
|
||
def get_action_params(self):
|
||
"""Parse action_params JSON to dict"""
|
||
if self.action_params:
|
||
try:
|
||
import json
|
||
return json.loads(self.action_params)
|
||
except:
|
||
return {}
|
||
return {}
|
||
|
||
|
||
class HealLog(Base):
|
||
"""
|
||
Healing execution logs.
|
||
"""
|
||
__tablename__ = 'heal_logs'
|
||
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
incident_id = Column(Integer, ForeignKey('incidents.id'), nullable=False)
|
||
playbook_id = Column(Integer, ForeignKey('playbooks.id'), nullable=False)
|
||
action_type = Column(String(50), nullable=False)
|
||
action_detail = Column(Text)
|
||
result = Column(String(20), default='unknown') # success/failed/skipped
|
||
result_output = Column(Text)
|
||
duration_ms = Column(Float)
|
||
created_at = Column(DateTime, default=datetime.now)
|
||
|
||
# Relationships
|
||
incident = relationship("Incident", backref="heal_logs")
|
||
playbook = relationship("Playbook", backref="heal_logs")
|
||
|
||
|
||
# Seed playbooks for common issues
|
||
SEED_PLAYBOOKS = [
|
||
{
|
||
'name': 'Database Connection Recovery',
|
||
'error_type': 'database',
|
||
'match_pattern': '["connection", "timeout", "unreachable"]',
|
||
'action_type': 'WAIT_RETRY',
|
||
'action_params': '{"wait_minutes": 5}',
|
||
'max_retries': 3,
|
||
'cooldown_min': 15
|
||
},
|
||
{
|
||
'name': 'Disk Space Alert',
|
||
'error_type': 'disk_space',
|
||
'match_pattern': '["disk", "space", "full", "no space"]',
|
||
'action_type': 'ALERT_ONLY',
|
||
'action_params': '{"message": "Disk space critical - manual intervention required"}',
|
||
'max_retries': 1,
|
||
'cooldown_min': 60
|
||
}
|
||
]
|