- SQLite 启用 WAL,允许读写并发 - 新增 6 个索引(eval_runs.status/campaign_id、eval_results.run_id、 turns.run_id、intelligent_evals.status、task_queue.assigned_at) - 幂等 Alembic 迁移(列/索引存在性检查) - domain.py 计数改 func.count 聚合,get_attention_reason 单次加载 sessions - scenario list_all 批量加载 bindings(1+N → 2 查询) - mark_orphans_failed 批量加载 campaigns(N → 1 IN 查询)
216 lines
7.1 KiB
Python
216 lines
7.1 KiB
Python
"""Intelligent evaluation tables (智能评估)."""
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Optional
|
|
|
|
import sqlalchemy as sa
|
|
from sqlmodel import Field, SQLModel
|
|
|
|
from agenteval.storage.db.core import _json_dumps, _json_loads, new_uuid, utc_now
|
|
|
|
|
|
class IntelligentEvalDB(SQLModel, table=True):
|
|
"""Intelligent evaluation (智能评估) — independent entity, peer to Campaign."""
|
|
|
|
__tablename__ = "intelligent_evals"
|
|
__table_args__ = (sa.Index("ix_intelligent_evals_status", "status"),)
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
name: str
|
|
target_id: str = Field(foreign_key="eval_targets.id")
|
|
status: str = "draft"
|
|
|
|
# User input (四件套)
|
|
goal: str = ""
|
|
seeds: str = "{}"
|
|
intent: str = ""
|
|
role_description: str = ""
|
|
|
|
# Coarse plan (OpenClaw produces)
|
|
plan: Optional[str] = None
|
|
plan_feedback: Optional[str] = None
|
|
|
|
# Time window
|
|
time_window_hours: int = 24
|
|
|
|
# Report
|
|
report: Optional[str] = None
|
|
|
|
# Timestamps
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
updated_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
started_at: Optional[datetime] = None
|
|
completed_at: Optional[datetime] = None
|
|
|
|
def get_seeds(self) -> dict[str, Any]:
|
|
return _json_loads(self.seeds)
|
|
|
|
def set_seeds(self, seeds: dict[str, Any]) -> None:
|
|
self.seeds = _json_dumps(seeds)
|
|
|
|
def get_plan(self) -> Optional[dict[str, Any]]:
|
|
return _json_loads(self.plan) if self.plan else None
|
|
|
|
def set_plan(self, plan: dict[str, Any]) -> None:
|
|
self.plan = _json_dumps(plan)
|
|
|
|
def get_report(self) -> Optional[dict[str, Any]]:
|
|
return _json_loads(self.report) if self.report else None
|
|
|
|
def set_report(self, report: dict[str, Any]) -> None:
|
|
self.report = _json_dumps(report)
|
|
|
|
|
|
class IntelligentEvalSessionDB(SQLModel, table=True):
|
|
"""A virtual-user session within an intelligent evaluation."""
|
|
|
|
__tablename__ = "intelligent_eval_sessions"
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
eval_id: str = Field(index=True, foreign_key="intelligent_evals.id")
|
|
target_id: str = Field(foreign_key="eval_targets.id")
|
|
persona: str = "{}"
|
|
goal: str = ""
|
|
dimension: Optional[str] = None
|
|
status: str = "running"
|
|
verdict: Optional[str] = None
|
|
turn_count: int = 0
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
closed_at: Optional[datetime] = None
|
|
|
|
def get_persona(self) -> dict[str, Any]:
|
|
return _json_loads(self.persona)
|
|
|
|
def set_persona(self, persona: dict[str, Any]) -> None:
|
|
self.persona = _json_dumps(persona)
|
|
|
|
def get_verdict(self) -> Optional[dict[str, Any]]:
|
|
return _json_loads(self.verdict) if self.verdict else None
|
|
|
|
def set_verdict(self, verdict: dict[str, Any]) -> None:
|
|
self.verdict = _json_dumps(verdict)
|
|
|
|
|
|
class IntelligentEvalMessageDB(SQLModel, table=True):
|
|
"""One chat message inside an intelligent eval session."""
|
|
|
|
__tablename__ = "intelligent_eval_messages"
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
session_id: str = Field(index=True, foreign_key="intelligent_eval_sessions.id")
|
|
role: str = "user"
|
|
content: str = ""
|
|
latency_ms: Optional[int] = None
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
|
|
|
|
class IntelligentEvalTaskQueueDB(SQLModel, table=True):
|
|
"""Task queue for intelligent evaluations (任务队列).
|
|
|
|
Platform scans executing evals every minute and enqueues tasks for
|
|
OpenClaw workers to pick up.
|
|
"""
|
|
|
|
__tablename__ = "intelligent_eval_task_queue"
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
eval_id: str = Field(index=True, foreign_key="intelligent_evals.id")
|
|
|
|
# Task status
|
|
status: str = Field(index=True) # pending / assigned / completed / failed
|
|
priority: int = Field(index=True) # Lower is higher priority
|
|
reason: str # Why this task needs attention (e.g., "slot_due", "all_sessions_completed")
|
|
|
|
# Retry budget (ADR-0011): each stale requeue increments; >= MAX caps to failed
|
|
attempts: int = Field(default=0)
|
|
|
|
# Assignment info
|
|
assigned_cron_id: Optional[str] = None
|
|
assigned_at: Optional[datetime] = None
|
|
|
|
# Completion info
|
|
completed_at: Optional[datetime] = None
|
|
error: Optional[str] = None
|
|
|
|
# Timestamps
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
updated_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
|
|
__table_args__ = (
|
|
sa.Index("idx_task_queue_status_priority", "status", "priority"),
|
|
sa.Index("idx_task_queue_eval_status", "eval_id", "status"),
|
|
sa.Index("idx_task_queue_assigned_at", "assigned_at"),
|
|
)
|
|
|
|
|
|
class IntelligentEvalConfigSnapshotDB(SQLModel, table=True):
|
|
"""Config snapshot for intelligent evaluations (配置快照).
|
|
|
|
Automatically saved when eval is created, plan is submitted, or config is updated.
|
|
"""
|
|
|
|
__tablename__ = "intelligent_eval_config_snapshots"
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
eval_id: str = Field(index=True, foreign_key="intelligent_evals.id")
|
|
|
|
# Snapshot type
|
|
snapshot_type: str # created / plan_submitted / config_updated
|
|
|
|
# Config snapshot
|
|
goal: str
|
|
seeds: str # JSON
|
|
intent: str
|
|
role_description: str
|
|
time_window_hours: int
|
|
plan: Optional[str] = None # JSON, coarse plan snapshot
|
|
|
|
# Metadata
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
created_by: str = "user" # user / openclaw
|
|
|
|
__table_args__ = (sa.Index("idx_config_snapshots_eval_created", "eval_id", "created_at"),)
|
|
|
|
def get_seeds(self) -> dict[str, Any]:
|
|
return _json_loads(self.seeds)
|
|
|
|
def set_seeds(self, seeds: dict[str, Any]) -> None:
|
|
self.seeds = _json_dumps(seeds)
|
|
|
|
def get_plan(self) -> Optional[dict[str, Any]]:
|
|
return _json_loads(self.plan) if self.plan else None
|
|
|
|
def set_plan(self, plan: dict[str, Any]) -> None:
|
|
self.plan = _json_dumps(plan)
|
|
|
|
|
|
class IntelligentEvalDecisionLogDB(SQLModel, table=True):
|
|
"""Decision log for intelligent evaluations (决策日志).
|
|
|
|
Records every decision made by OpenClaw workers (execute_session / wait / start_analysis).
|
|
"""
|
|
|
|
__tablename__ = "intelligent_eval_decision_logs"
|
|
|
|
id: Optional[str] = Field(default_factory=new_uuid, primary_key=True)
|
|
eval_id: str = Field(index=True, foreign_key="intelligent_evals.id")
|
|
|
|
# Decision info
|
|
decision_type: str # execute_session / wait / start_analysis
|
|
reason: str # Why this decision was made
|
|
context: str # JSON, decision context (current time slot, completed sessions, etc.)
|
|
|
|
# Execution info
|
|
cron_id: str # Which cron made this decision
|
|
|
|
# Timestamp
|
|
created_at: Optional[datetime] = Field(default_factory=utc_now)
|
|
|
|
__table_args__ = (sa.Index("idx_decision_logs_eval_created", "eval_id", "created_at"),)
|
|
|
|
def get_context(self) -> dict[str, Any]:
|
|
return _json_loads(self.context)
|
|
|
|
def set_context(self, context: dict[str, Any]) -> None:
|
|
self.context = _json_dumps(context)
|