limina-engine / main.py
sdawdsdw's picture
Upload 8 files
31f2a28 verified
Raw
History Blame Contribute Delete
3.27 kB
import asyncio
from fastapi import FastAPI, HTTPException, Security, Depends
from fastapi.security.api_key import APIKeyHeader
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from starlette.status import HTTP_403_FORBIDDEN, HTTP_429_TOO_MANY_REQUESTS
from evaluator import evaluate_trajectories_batch
from report_generator import generate_ai_report
from database import verify_api_key_db, save_evaluation_to_db
app = FastAPI(
title="Limina AI API",
description="High-performance infrastructure for multi-turn AI agent evaluation",
version="1.0.0"
)
API_KEY_NAME = "X-API-Key"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
async def get_api_key_context(header: str = Security(api_key_header)) -> Dict[str, Any]:
if not header:
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Missing API Key (X-API-Key).")
auth_context = verify_api_key_db(header)
if not auth_context:
raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Access Denied. Invalid API Key.")
if auth_context.get("quota_exceeded"):
raise HTTPException(
status_code=HTTP_429_TOO_MANY_REQUESTS,
detail=f"Monthly Free Quota Exceeded ({auth_context['usage']}/{auth_context['limit']}). Upgrade to Limina Pro."
)
return auth_context
cpu_semaphore = asyncio.Semaphore(4)
class NodeInput(BaseModel):
id: str
type: str
text: str
expected_keys: Optional[List[str]] = None
runs: Optional[List[str]] = None
execution_time_ms: Optional[float] = None
class EdgeInput(BaseModel):
from_node: str = Field(..., alias="from")
to_node: str = Field(..., alias="to")
class Config:
allow_population_by_alias = True
class TrajectoryInput(BaseModel):
session_id: str
description: str
nodes: List[NodeInput]
edges: List[EdgeInput]
class EvaluationReport(BaseModel):
executive_summary: dict
regression_report: dict
results: list
narrative_report: str
@app.get("/")
async def read_root():
return {
"status": "online",
"engine": "Limina AI Trajectory Engine",
"version": "1.0.0",
"cloud_sync": "active"
}
@app.post("/evaluate/trajectory", response_model=EvaluationReport)
async def evaluate_trajectory(
payload: List[TrajectoryInput],
auth_ctx: Dict[str, Any] = Depends(get_api_key_context)
):
if not payload:
raise HTTPException(status_code=400, detail="Payload is empty.")
raw_payload = [p.dict(by_alias=True) for p in payload]
async with cpu_semaphore:
report = await asyncio.to_thread(evaluate_trajectories_batch, raw_payload, "standard", False)
if not report:
raise HTTPException(status_code=500, detail="Evaluation engine failed.")
ai_markdown_report = await asyncio.to_thread(generate_ai_report, report)
report["narrative_report"] = ai_markdown_report
if auth_ctx.get("project_id"):
asyncio.create_task(
asyncio.to_thread(save_evaluation_to_db, auth_ctx["project_id"], report)
)
return report