annator-command-center / api /composition_routes.py
techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 2)
a10e62e verified
Raw
History Blame Contribute Delete
3.93 kB
"""
Composition API Routes - Multi-skill workflow execution.
Endpoints:
- POST /composition/execute - Execute skill composition workflow
- POST /composition/validate - Validate workflow DAG
- GET /composition/status/{id} - Get workflow execution status
Reference: Phase 60 Plan 03
"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from core.database import get_db
from core.skill_composition_engine import SkillCompositionEngine, SkillStep
router = APIRouter(prefix="/composition", tags=["composition"])
class StepModel(BaseModel):
step_id: str = Field(..., description="Unique step identifier")
skill_id: str = Field(..., description="Skill ID to execute")
inputs: Dict[str, Any] = Field(default_factory=dict, description="Input parameters")
dependencies: List[str] = Field(default_factory=list, description="Step IDs this depends on")
condition: Optional[str] = Field(None, description="Conditional execution")
timeout_seconds: int = Field(30, ge=1, le=300, description="Step timeout")
class WorkflowRequest(BaseModel):
workflow_id: str = Field(..., description="Unique workflow identifier")
agent_id: str = Field(..., description="Agent ID executing workflow")
steps: List[StepModel] = Field(..., min_items=1, description="Workflow steps")
class WorkflowResponse(BaseModel):
success: bool
workflow_id: str
execution_id: Optional[str] = None
results: Optional[Dict[str, Any]] = None
error: Optional[str] = None
duration_seconds: Optional[float] = None
@router.post("/execute", response_model=WorkflowResponse)
async def execute_workflow(
request: WorkflowRequest,
db: Session = Depends(get_db)
):
"""Execute a skill composition workflow."""
engine = SkillCompositionEngine(db)
# Convert to SkillStep objects
steps = [
SkillStep(
step_id=s.step_id,
skill_id=s.skill_id,
inputs=s.inputs,
dependencies=s.dependencies,
condition=s.condition,
timeout_seconds=s.timeout_seconds
)
for s in request.steps
]
result = await engine.execute_workflow(
workflow_id=request.workflow_id,
steps=steps,
agent_id=request.agent_id
)
return result
@router.post("/validate")
def validate_workflow(
request: WorkflowRequest,
db: Session = Depends(get_db)
):
"""Validate workflow DAG without executing."""
engine = SkillCompositionEngine(db)
steps = [
SkillStep(
step_id=s.step_id,
skill_id=s.skill_id,
inputs=s.inputs,
dependencies=s.dependencies
)
for s in request.steps
]
result = engine.validate_workflow(steps)
return result
@router.get("/status/{execution_id}")
def get_workflow_status(
execution_id: str,
db: Session = Depends(get_db)
):
"""Get workflow execution status."""
from core.models import SkillCompositionExecution
workflow = db.query(SkillCompositionExecution).filter(
SkillCompositionExecution.id == execution_id
).first()
if not workflow:
raise HTTPException(status_code=404, detail="Workflow execution not found")
return {
"execution_id": workflow.id,
"workflow_id": workflow.workflow_id,
"status": workflow.status,
"validation_status": workflow.validation_status,
"current_step": workflow.current_step,
"completed_steps": workflow.completed_steps or [],
"rollback_performed": workflow.rollback_performed,
"started_at": workflow.started_at.isoformat(),
"completed_at": workflow.completed_at.isoformat() if workflow.completed_at else None,
"duration_seconds": workflow.duration_seconds,
"error": workflow.error_message
}