将「已删即 404」语义收进 IntelligentEvalRepository 单一接缝,消除三处独立裁决; 任务监控开始隐藏已删评估的任务(本 Phase 唯一刻意行为变化)。 - repository.py 新增 visible() 谓词与 require_live_eval() 服务接缝; 六处裸谓词统一走它,get()/get_including_deleted() 语义不变。 - decision_logs.py 删除本地 _require_eval,三处调用迁至 repository 接缝; count_decisions 由 len(.all()) 改为 func.count。 - task_queue.py list_tasks 与 stats 过滤已删评估的任务(行为变化)。 - web/routers/intelligent_evals.py: _require_eval_exists → _require_live_eval, 把 LookupError 翻译为 404;expired 会话 Markdown 标注下沉至 read_model.report_markdown_by_eval;配置快照 11 字段序列化收至 config_snapshot.snapshot_to_dict 单一出口。 - AGENTS.md 登记可见性纪律(已知陷阱 #6)。 - 补 characterization 测试锁定四处契约;更新 task_queue 测试以使用 真实 eval_id(可见性过滤后字面 eval_id 不再可见)。
127 lines
3.7 KiB
Python
127 lines
3.7 KiB
Python
"""Config snapshot management for intelligent evaluations (配置快照管理).
|
|
|
|
Automatically saves config snapshots when:
|
|
- Eval is created (snapshot_type: created)
|
|
- Plan is submitted (snapshot_type: plan_submitted)
|
|
- Config is updated (snapshot_type: config_updated)
|
|
"""
|
|
|
|
from typing import Any, Optional
|
|
|
|
from sqlmodel import Session, select
|
|
|
|
from agenteval.storage.db import IntelligentEvalConfigSnapshotDB, IntelligentEvalDB
|
|
|
|
|
|
def save_snapshot(
|
|
eval_db: IntelligentEvalDB,
|
|
snapshot_type: str,
|
|
created_by: str,
|
|
session: Session,
|
|
) -> IntelligentEvalConfigSnapshotDB:
|
|
"""Save a config snapshot.
|
|
|
|
Args:
|
|
eval_db: Evaluation database record
|
|
snapshot_type: Type of snapshot (created / plan_submitted / config_updated)
|
|
created_by: Who created the snapshot (user / openclaw)
|
|
session: Database session
|
|
|
|
Returns:
|
|
Created snapshot
|
|
"""
|
|
snapshot = IntelligentEvalConfigSnapshotDB(
|
|
eval_id=eval_db.id,
|
|
snapshot_type=snapshot_type,
|
|
goal=eval_db.goal,
|
|
seeds=eval_db.seeds,
|
|
intent=eval_db.intent,
|
|
role_description=eval_db.role_description,
|
|
time_window_hours=eval_db.time_window_hours,
|
|
plan=eval_db.plan,
|
|
created_by=created_by,
|
|
)
|
|
session.add(snapshot)
|
|
session.commit()
|
|
session.refresh(snapshot)
|
|
return snapshot
|
|
|
|
|
|
def list_snapshots(eval_id: str, session: Session) -> list[IntelligentEvalConfigSnapshotDB]:
|
|
"""List all snapshots for an evaluation.
|
|
|
|
Returns:
|
|
List of snapshots, ordered by created_at descending (newest first)
|
|
"""
|
|
snapshots = session.exec(
|
|
select(IntelligentEvalConfigSnapshotDB)
|
|
.where(IntelligentEvalConfigSnapshotDB.eval_id == eval_id)
|
|
.order_by(IntelligentEvalConfigSnapshotDB.created_at.desc())
|
|
).all()
|
|
return list(snapshots)
|
|
|
|
|
|
def get_snapshot(snapshot_id: str, session: Session) -> Optional[IntelligentEvalConfigSnapshotDB]:
|
|
"""Get a single snapshot by ID.
|
|
|
|
Returns:
|
|
Snapshot, or None if not found
|
|
"""
|
|
return session.get(IntelligentEvalConfigSnapshotDB, snapshot_id)
|
|
|
|
|
|
def snapshot_to_dict(s: IntelligentEvalConfigSnapshotDB) -> dict[str, Any]:
|
|
"""快照的稳定序列化形状(列表与详情共用的单一出口)。"""
|
|
return {
|
|
"id": s.id,
|
|
"eval_id": s.eval_id,
|
|
"snapshot_type": s.snapshot_type,
|
|
"goal": s.goal,
|
|
"seeds": s.get_seeds(),
|
|
"intent": s.intent,
|
|
"role_description": s.role_description,
|
|
"time_window_hours": s.time_window_hours,
|
|
"plan": s.get_plan(),
|
|
"created_at": s.created_at.isoformat() if s.created_at else None,
|
|
"created_by": s.created_by,
|
|
}
|
|
|
|
|
|
def compare_snapshots(
|
|
snapshot1: IntelligentEvalConfigSnapshotDB,
|
|
snapshot2: IntelligentEvalConfigSnapshotDB,
|
|
) -> dict[str, Any]:
|
|
"""Compare two snapshots and return differences.
|
|
|
|
Returns:
|
|
Dict with differences, format:
|
|
{
|
|
"field_name": {
|
|
"old": value1,
|
|
"new": value2,
|
|
}
|
|
}
|
|
"""
|
|
diffs = {}
|
|
|
|
# Compare simple fields
|
|
fields = ["goal", "intent", "role_description", "time_window_hours"]
|
|
for field in fields:
|
|
val1 = getattr(snapshot1, field)
|
|
val2 = getattr(snapshot2, field)
|
|
if val1 != val2:
|
|
diffs[field] = {"old": val1, "new": val2}
|
|
|
|
# Compare JSON fields
|
|
seeds1 = snapshot1.get_seeds()
|
|
seeds2 = snapshot2.get_seeds()
|
|
if seeds1 != seeds2:
|
|
diffs["seeds"] = {"old": seeds1, "new": seeds2}
|
|
|
|
plan1 = snapshot1.get_plan()
|
|
plan2 = snapshot2.get_plan()
|
|
if plan1 != plan2:
|
|
diffs["plan"] = {"old": plan1, "new": plan2}
|
|
|
|
return diffs
|