Introduce the 评估活动 (Campaign) aggregate above Run: a single-target, service-cycle window driving a static plan. Adds Campaign/CampaignPlanEntry models, CampaignDB table, nullable eval_runs.campaign_id, CampaignRepository, Alembic migration, and POST/GET /api/campaigns with validation. Ticket 01 of v0.6; no scheduling or child-run spawning yet (ADR-0003 v1).
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
"""API routes for evaluation campaigns (评估活动).
|
|
|
|
This ticket covers persistence and create/query only — no scheduling or child
|
|
Run spawning. Those arrive in later tickets.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from sqlmodel import Session
|
|
|
|
from agenteval.models import Campaign, CampaignPlanEntry
|
|
from agenteval.storage.repository import CampaignRepository, ScenarioRepository, TargetRepository
|
|
from agenteval.web.deps import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class CreateCampaignRequest(BaseModel):
|
|
name: str
|
|
target_id: str
|
|
window_seconds: int = Field(gt=0)
|
|
time_scale: float = Field(default=1.0, gt=0)
|
|
plan: list[CampaignPlanEntry] = Field(min_length=1)
|
|
|
|
|
|
@router.get("")
|
|
async def list_campaigns(session: Session = Depends(get_db)) -> list[dict]:
|
|
return [c.model_dump() for c in CampaignRepository(session).list_all()]
|
|
|
|
|
|
@router.post("")
|
|
async def create_campaign(
|
|
request: CreateCampaignRequest,
|
|
session: Session = Depends(get_db),
|
|
) -> dict:
|
|
if not TargetRepository(session).get(request.target_id):
|
|
raise HTTPException(status_code=404, detail="target not found")
|
|
|
|
scenario_repo = ScenarioRepository(session)
|
|
for entry in request.plan:
|
|
if not scenario_repo.get(entry.scenario_id):
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"scenario not found: {entry.scenario_id}",
|
|
)
|
|
|
|
campaign = Campaign(
|
|
name=request.name,
|
|
target_id=request.target_id,
|
|
window_seconds=request.window_seconds,
|
|
time_scale=request.time_scale,
|
|
plan=request.plan,
|
|
)
|
|
campaign = CampaignRepository(session).create(campaign)
|
|
return campaign.model_dump()
|
|
|
|
|
|
@router.get("/{campaign_id}")
|
|
async def get_campaign(campaign_id: str, session: Session = Depends(get_db)) -> dict:
|
|
campaign = CampaignRepository(session).get(campaign_id)
|
|
if not campaign:
|
|
raise HTTPException(status_code=404, detail="campaign not found")
|
|
return campaign.model_dump()
|