from __future__ import annotations from contextlib import asynccontextmanager import httpx from fastapi import Depends, FastAPI, HTTPException, Request from app.exceptions import ProviderAPICallError, ProviderConfigurationError from app.models import ( DataImportAnalysisRequest, DataImportAnalysisResponse, LLMRequest, LLMResponse, ) from app.services import LLMGateway from app.services.import_analysis import build_import_messages, resolve_provider_from_model @asynccontextmanager async def lifespan(app: FastAPI): client = httpx.AsyncClient(timeout=httpx.Timeout(30.0)) gateway = LLMGateway() try: app.state.http_client = client # type: ignore[attr-defined] app.state.gateway = gateway # type: ignore[attr-defined] yield finally: await client.aclose() def create_app() -> FastAPI: application = FastAPI( title="Unified LLM Gateway", version="0.1.0", lifespan=lifespan, ) @application.post( "/v1/chat/completions", response_model=LLMResponse, summary="Dispatch chat completion to upstream provider", ) async def create_chat_completion( payload: LLMRequest, gateway: LLMGateway = Depends(get_gateway), client: httpx.AsyncClient = Depends(get_http_client), ) -> LLMResponse: try: return await gateway.chat(payload, client) except ProviderConfigurationError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc except ProviderAPICallError as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc @application.post( "/v1/import/analyze", response_model=DataImportAnalysisResponse, summary="Analyze import sample data via configured LLM", ) async def analyze_import_data( payload: DataImportAnalysisRequest, gateway: LLMGateway = Depends(get_gateway), client: httpx.AsyncClient = Depends(get_http_client), ) -> DataImportAnalysisResponse: try: provider, model_name = resolve_provider_from_model(payload.llm_model) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc messages = build_import_messages(payload) llm_request = LLMRequest( provider=provider, model=model_name, messages=messages, temperature=payload.temperature if payload.temperature is not None else 0.2, max_tokens=payload.max_tokens, ) try: llm_response = await gateway.chat(llm_request, client) except ProviderConfigurationError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc except ProviderAPICallError as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc return DataImportAnalysisResponse( import_record_id=payload.import_record_id, llm_response=llm_response, ) return application async def get_gateway(request: Request) -> LLMGateway: return request.app.state.gateway # type: ignore[return-value, attr-defined] async def get_http_client(request: Request) -> httpx.AsyncClient: return request.app.state.http_client # type: ignore[return-value, attr-defined] app = create_app()