|
|
""" |
|
|
FlowGraph - FastAPI Application Entry Point. |
|
|
|
|
|
A lightweight, async-first workflow orchestration engine for building agent pipelines. |
|
|
""" |
|
|
|
|
|
from contextlib import asynccontextmanager |
|
|
from fastapi import FastAPI |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from fastapi.responses import JSONResponse |
|
|
import logging |
|
|
|
|
|
from app.config import settings |
|
|
from app.api.routes import graph, tools, websocket |
|
|
from app.workflows.code_review import register_code_review_workflow |
|
|
|
|
|
|
|
|
import app.tools.builtin |
|
|
|
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=getattr(logging, settings.LOG_LEVEL), |
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", |
|
|
) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
@asynccontextmanager |
|
|
async def lifespan(app: FastAPI): |
|
|
"""Application lifespan handler.""" |
|
|
|
|
|
logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}") |
|
|
|
|
|
|
|
|
await register_code_review_workflow() |
|
|
|
|
|
yield |
|
|
|
|
|
|
|
|
logger.info("Shutting down...") |
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title=settings.APP_NAME, |
|
|
description=""" |
|
|
## Workflow Engine API |
|
|
|
|
|
A minimal but powerful workflow/graph engine for building agent workflows. |
|
|
|
|
|
### Features |
|
|
- **Nodes**: Python functions that read and modify shared state |
|
|
- **Edges**: Define execution flow between nodes |
|
|
- **Branching**: Conditional routing based on state values |
|
|
- **Looping**: Support for iterative workflows |
|
|
- **Real-time Updates**: WebSocket support for live execution streaming |
|
|
|
|
|
### Quick Start |
|
|
1. List available tools: `GET /tools` |
|
|
2. Create a graph: `POST /graph/create` |
|
|
3. Run the graph: `POST /graph/run` |
|
|
4. Check execution state: `GET /graph/state/{run_id}` |
|
|
|
|
|
### Demo Workflow |
|
|
A pre-registered Code Review workflow is available with ID: `code-review-demo` |
|
|
""", |
|
|
version=settings.APP_VERSION, |
|
|
docs_url="/docs", |
|
|
redoc_url="/redoc", |
|
|
lifespan=lifespan, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
app.include_router(graph.router) |
|
|
app.include_router(tools.router) |
|
|
app.include_router(websocket.router) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/", tags=["Root"]) |
|
|
async def root(): |
|
|
"""API root - returns basic info and links.""" |
|
|
return { |
|
|
"name": settings.APP_NAME, |
|
|
"version": settings.APP_VERSION, |
|
|
"description": "A minimal workflow/graph engine for agent workflows", |
|
|
"docs": "/docs", |
|
|
"redoc": "/redoc", |
|
|
"endpoints": { |
|
|
"graphs": "/graph", |
|
|
"tools": "/tools", |
|
|
"websocket_run": "/ws/run/{graph_id}", |
|
|
"websocket_subscribe": "/ws/subscribe/{run_id}", |
|
|
}, |
|
|
"demo_workflow": "code-review-demo", |
|
|
} |
|
|
|
|
|
|
|
|
@app.get("/health", tags=["Root"]) |
|
|
async def health(): |
|
|
"""Health check endpoint.""" |
|
|
from app.storage.memory import graph_storage, run_storage |
|
|
|
|
|
return { |
|
|
"status": "healthy", |
|
|
"version": settings.APP_VERSION, |
|
|
"graphs_count": len(graph_storage), |
|
|
"runs_count": len(run_storage), |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.exception_handler(Exception) |
|
|
async def global_exception_handler(request, exc): |
|
|
"""Global exception handler for unhandled errors.""" |
|
|
logger.exception(f"Unhandled error: {exc}") |
|
|
return JSONResponse( |
|
|
status_code=500, |
|
|
content={ |
|
|
"error": "Internal Server Error", |
|
|
"detail": str(exc) if settings.DEBUG else "An unexpected error occurred", |
|
|
}, |
|
|
) |
|
|
|