feat: complete phase 1 -- core framework with chat loop, agents, and React UI
Backend: - FastAPI WebSocket /ws endpoint with streaming via LangGraph astream - LangGraph Supervisor connecting 3 mock agents (order_lookup, order_actions, fallback) - YAML Agent Registry with Pydantic validation and immutable configs - PostgresSaver checkpoint persistence via langgraph-checkpoint-postgres - Session TTL with 30-min sliding window and interrupt extension - LLM provider abstraction (Anthropic/OpenAI/Google) - Token usage + cost tracking callback handler - Input validation: message size cap, thread_id format, content length - Security: no hardcoded defaults, startup API key validation, no input reflection Frontend: - React 19 + TypeScript + Vite chat UI - WebSocket hook with reconnect + exponential backoff - Streaming token display with agent attribution - Interrupt approval/reject UI for write operations - Collapsible tool call viewer Testing: - 87 unit tests, 87% coverage (exceeds 80% requirement) - Ruff lint + format clean Infrastructure: - Docker Compose (PostgreSQL 16 + backend) - pyproject.toml with full dependency management
This commit is contained in:
83
backend/app/main.py
Normal file
83
backend/app/main.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""FastAPI application entry point."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.callbacks import TokenUsageCallbackHandler
|
||||
from app.config import Settings
|
||||
from app.db import create_checkpointer, create_pool, setup_app_tables
|
||||
from app.graph import build_graph
|
||||
from app.llm import create_llm
|
||||
from app.registry import AgentRegistry
|
||||
from app.session_manager import SessionManager
|
||||
from app.ws_handler import dispatch_message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AGENTS_YAML = Path(__file__).parent.parent / "agents.yaml"
|
||||
FRONTEND_DIST = Path(__file__).parent.parent.parent / "frontend" / "dist"
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
settings = Settings()
|
||||
|
||||
pool = await create_pool(settings)
|
||||
checkpointer = await create_checkpointer(pool)
|
||||
await setup_app_tables(pool)
|
||||
|
||||
registry = AgentRegistry.load(AGENTS_YAML)
|
||||
llm = create_llm(settings)
|
||||
graph = build_graph(registry, llm, checkpointer)
|
||||
session_manager = SessionManager(
|
||||
session_ttl_seconds=settings.session_ttl_minutes * 60,
|
||||
)
|
||||
|
||||
app.state.graph = graph
|
||||
app.state.session_manager = session_manager
|
||||
app.state.settings = settings
|
||||
app.state.pool = pool
|
||||
|
||||
logger.info(
|
||||
"Smart Support started: %d agents loaded, LLM=%s/%s",
|
||||
len(registry),
|
||||
settings.llm_provider,
|
||||
settings.llm_model,
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
await pool.close()
|
||||
|
||||
|
||||
app = FastAPI(title="Smart Support", version="0.1.0", lifespan=lifespan)
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(ws: WebSocket) -> None:
|
||||
await ws.accept()
|
||||
graph = app.state.graph
|
||||
session_manager = app.state.session_manager
|
||||
settings = app.state.settings
|
||||
callback_handler = TokenUsageCallbackHandler(model_name=settings.llm_model)
|
||||
|
||||
try:
|
||||
while True:
|
||||
raw_data = await ws.receive_text()
|
||||
await dispatch_message(ws, graph, session_manager, callback_handler, raw_data)
|
||||
except WebSocketDisconnect:
|
||||
logger.info("WebSocket client disconnected")
|
||||
|
||||
|
||||
if FRONTEND_DIST.is_dir():
|
||||
app.mount("/", StaticFiles(directory=str(FRONTEND_DIST), html=True), name="frontend")
|
||||
Reference in New Issue
Block a user