from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from fastapi import FastAPI from openai import AsyncOpenAI from auth import ApiKeyAuthenticator from config import Settings from routes import router from service import ChatService from storage import JsonSessionStorage from tools import ToolRegistry def create_app( settings: Settings | None = None, client: AsyncOpenAI | None = None, ) -> FastAPI: resolved_settings = settings or Settings.from_env() @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: resolved_client = client if resolved_client is None: if not resolved_settings.api_key: raise RuntimeError("DEEPSEEK_API_KEY is required") resolved_client = AsyncOpenAI( api_key=resolved_settings.api_key, base_url=resolved_settings.base_url, ) app.state.chat_service = ChatService( storage=JsonSessionStorage(resolved_settings.data_dir), client=resolved_client, model=resolved_settings.model, default_system_prompt=resolved_settings.default_system_prompt, tool_registry=ToolRegistry(resolved_settings.tool_timeout_seconds), max_tool_rounds=resolved_settings.max_tool_rounds, max_tool_calls_per_turn=resolved_settings.max_tool_calls_per_turn, ) app.state.authenticator = ApiKeyAuthenticator( resolved_settings.api_key_whitelist ) yield if client is None: await resolved_client.close() app = FastAPI(title="Simple Chat API", version="0.1.0", lifespan=lifespan) app.include_router(router) return app app = create_app()