2026-02-27 07:27:02 +00:00
|
|
|
"""
|
|
|
|
|
Script Flow Management API.
|
|
|
|
|
[AC-AISVC-71, AC-AISVC-72, AC-AISVC-73] Script flow CRUD endpoints.
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
[AC-AISVC-101, AC-AISVC-102] Flow simulation endpoints.
|
2026-02-27 07:27:02 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import uuid
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Header, HTTPException
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
from pydantic import BaseModel
|
2026-02-27 07:27:02 +00:00
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
from app.core.database import get_session
|
|
|
|
|
from app.models.entities import ScriptFlowCreate, ScriptFlowUpdate
|
|
|
|
|
from app.services.flow.flow_service import ScriptFlowService
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
from app.services.flow.tester import ScriptFlowTester
|
2026-02-27 07:27:02 +00:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/admin/script-flows", tags=["Script Flows"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tenant_id(x_tenant_id: str = Header(..., alias="X-Tenant-Id")) -> str:
|
|
|
|
|
"""Extract tenant ID from header."""
|
|
|
|
|
if not x_tenant_id:
|
|
|
|
|
raise HTTPException(status_code=400, detail="X-Tenant-Id header is required")
|
|
|
|
|
return x_tenant_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("")
|
|
|
|
|
async def list_flows(
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
is_enabled: bool | None = None,
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-72] List all script flows for a tenant.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"[AC-AISVC-72] Listing script flows for tenant={tenant_id}, is_enabled={is_enabled}")
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
|
|
|
|
flows = await service.list_flows(tenant_id, is_enabled)
|
|
|
|
|
|
|
|
|
|
data = []
|
|
|
|
|
for f in flows:
|
|
|
|
|
linked_rule_count = await service._get_linked_rule_count(tenant_id, f.id)
|
|
|
|
|
data.append({
|
|
|
|
|
"id": str(f.id),
|
|
|
|
|
"name": f.name,
|
|
|
|
|
"description": f.description,
|
|
|
|
|
"step_count": len(f.steps),
|
|
|
|
|
"is_enabled": f.is_enabled,
|
|
|
|
|
"linked_rule_count": linked_rule_count,
|
|
|
|
|
"created_at": f.created_at.isoformat(),
|
|
|
|
|
"updated_at": f.updated_at.isoformat(),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {"data": data}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("", status_code=201)
|
|
|
|
|
async def create_flow(
|
|
|
|
|
body: ScriptFlowCreate,
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-71] Create a new script flow.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"[AC-AISVC-71] Creating script flow for tenant={tenant_id}, name={body.name}")
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
|
2026-02-27 07:27:02 +00:00
|
|
|
try:
|
|
|
|
|
flow = await service.create_flow(tenant_id, body)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": str(flow.id),
|
|
|
|
|
"name": flow.name,
|
|
|
|
|
"description": flow.description,
|
|
|
|
|
"step_count": len(flow.steps),
|
|
|
|
|
"is_enabled": flow.is_enabled,
|
|
|
|
|
"created_at": flow.created_at.isoformat(),
|
|
|
|
|
"updated_at": flow.updated_at.isoformat(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/{flow_id}")
|
|
|
|
|
async def get_flow_detail(
|
|
|
|
|
flow_id: uuid.UUID,
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-73] Get script flow detail with complete step definitions.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"[AC-AISVC-73] Getting flow detail for tenant={tenant_id}, id={flow_id}")
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
|
|
|
|
detail = await service.get_flow_detail(tenant_id, flow_id)
|
|
|
|
|
|
|
|
|
|
if not detail:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
|
|
|
|
|
|
|
|
return detail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/{flow_id}")
|
|
|
|
|
async def update_flow(
|
|
|
|
|
flow_id: uuid.UUID,
|
|
|
|
|
body: ScriptFlowUpdate,
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-73] Update script flow definition.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"[AC-AISVC-73] Updating flow for tenant={tenant_id}, id={flow_id}")
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
|
2026-02-27 07:27:02 +00:00
|
|
|
try:
|
|
|
|
|
flow = await service.update_flow(tenant_id, flow_id, body)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
if not flow:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"id": str(flow.id),
|
|
|
|
|
"name": flow.name,
|
|
|
|
|
"description": flow.description,
|
|
|
|
|
"step_count": len(flow.steps),
|
|
|
|
|
"is_enabled": flow.is_enabled,
|
|
|
|
|
"created_at": flow.created_at.isoformat(),
|
|
|
|
|
"updated_at": flow.updated_at.isoformat(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{flow_id}", status_code=204)
|
|
|
|
|
async def delete_flow(
|
|
|
|
|
flow_id: uuid.UUID,
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Delete a script flow.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"Deleting flow for tenant={tenant_id}, id={flow_id}")
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
|
|
|
|
success = await service.delete_flow(tenant_id, flow_id)
|
|
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
feat(v0.7.0-window2): implement flow simulation and guardrail testing/monitoring
Refs: AC-AISVC-101, AC-AISVC-102, AC-AISVC-103, AC-AISVC-104, AC-AISVC-105, AC-AISVC-106, AC-AISVC-107
Refs: AC-ASA-59, AC-ASA-60, AC-ASA-61, AC-ASA-62, AC-ASA-63, AC-ASA-64
Backend changes:
- New: ai-service/app/services/flow/tester.py (ScriptFlowTester)
- New: ai-service/app/services/guardrail/tester.py (GuardrailTester)
- New: ai-service/app/services/monitoring/flow_monitor.py (FlowMonitor)
- New: ai-service/app/services/monitoring/guardrail_monitor.py (GuardrailMonitor)
- Modified: ai-service/app/api/admin/script_flows.py (add POST /{flowId}/simulate)
- Modified: ai-service/app/api/admin/guardrails.py (add POST /test)
- Modified: ai-service/app/api/admin/monitoring.py (add flow/guardrail stats endpoints)
Frontend changes:
- New: SimulateDialog.vue (flow simulation dialog)
- New: TestDialog.vue (guardrail test dialog)
- New: ScriptFlows.vue (flow monitoring page)
- New: Guardrails.vue (guardrail monitoring page)
- Extended: API services (monitoring.ts, script-flow.ts, guardrail.ts)
- Updated: Router with new monitoring routes
2026-02-27 15:11:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class FlowSimulateRequest(BaseModel):
|
|
|
|
|
"""Request body for flow simulation."""
|
|
|
|
|
|
|
|
|
|
userInputs: list[str]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/{flow_id}/simulate")
|
|
|
|
|
async def simulate_flow(
|
|
|
|
|
flow_id: uuid.UUID,
|
|
|
|
|
body: FlowSimulateRequest,
|
|
|
|
|
tenant_id: str = Depends(get_tenant_id),
|
|
|
|
|
session: AsyncSession = Depends(get_session),
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-101, AC-AISVC-102] Simulate flow execution and analyze coverage.
|
|
|
|
|
|
|
|
|
|
This endpoint simulates the flow execution with provided user inputs
|
|
|
|
|
without modifying any database state. It returns:
|
|
|
|
|
- Step-by-step simulation results
|
|
|
|
|
- Coverage analysis (covered steps, coverage rate)
|
|
|
|
|
- Detected issues (dead loops, low coverage, etc.)
|
|
|
|
|
"""
|
|
|
|
|
logger.info(
|
|
|
|
|
f"[AC-AISVC-101] Simulating flow for tenant={tenant_id}, "
|
|
|
|
|
f"flow_id={flow_id}, inputs_count={len(body.userInputs)}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
service = ScriptFlowService(session)
|
|
|
|
|
flow = await service.get_flow(tenant_id, flow_id)
|
|
|
|
|
|
|
|
|
|
if not flow:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Flow not found")
|
|
|
|
|
|
|
|
|
|
if not flow.steps:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Flow has no steps")
|
|
|
|
|
|
|
|
|
|
tester = ScriptFlowTester()
|
|
|
|
|
result = tester.simulate_flow(flow, body.userInputs)
|
|
|
|
|
|
|
|
|
|
return tester.to_dict(result)
|