Download backend/app/main.py from fardeen16/finrag: direct link, hf CLI and curl.
- Browser
- Download file 3.34 kB
-
https://huggingface.co/spaces/fardeen16/finrag/resolve/main/backend/app/main.py
- Command line
-
hf download hf://spaces/fardeen16/finrag/backend/app/main.py
-
curl -L -o main.py https://huggingface.co/spaces/fardeen16/finrag/resolve/main/backend/app/main.py
3.34 kB
| """FastAPI surface for the agent. | |
| `AGENT_MODE=mock` (default) replays canned traces so the UI can be developed | |
| without a GPU. `AGENT_MODE=live` runs the LangGraph supervisor. The event | |
| contract is the same either way; only the generator behind StreamingResponse | |
| changes. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| from typing import AsyncIterator | |
| from fastapi import FastAPI, Query | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import StreamingResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from .config import get_settings | |
| from .events import ChatRequest | |
| from .mock_agent import replay | |
| from .stream import live_stream | |
| SSE_HEADERS = { | |
| "Cache-Control": "no-cache, no-transform", | |
| "Connection": "keep-alive", | |
| "X-Accel-Buffering": "no", | |
| } | |
| def _warmup() -> None: | |
| """Load embedding + reranker weights and open stores before the first query. | |
| The first Librarian call otherwise spends ~15s downloading/loading models, | |
| which on Fargate is paid by whoever happens to ask first. | |
| """ | |
| from .agent.embeddings import get_embeddings | |
| from .agent.resources import get_qdrant, get_reranker, get_sql_database | |
| get_embeddings() | |
| get_reranker() | |
| get_qdrant() | |
| get_sql_database() | |
| async def lifespan(_app: FastAPI): | |
| settings = get_settings() | |
| if settings.agent_mode == "live" and settings.warmup_on_start: | |
| await asyncio.to_thread(_warmup) | |
| yield | |
| app = FastAPI(title="FinRAG API", version="0.2.0", lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["http://localhost:5173", "http://127.0.0.1:5173"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| async def health() -> dict[str, object]: | |
| settings = get_settings() | |
| return { | |
| "status": "ok", | |
| "agent": settings.agent_mode, | |
| "vllm": settings.vllm_base_url, | |
| "embeddings": settings.embedding_provider, | |
| "qdrant": str(settings.qdrant_url or settings.qdrant_path), | |
| } | |
| async def chat_stream( | |
| body: ChatRequest, | |
| speed: float = Query(1.0, gt=0, le=50, description="Mock replay speed. Ignored in live mode."), | |
| ) -> StreamingResponse: | |
| """Stream agent state and answer tokens as Server-Sent Events. | |
| POST rather than GET because the query goes in the body and, once auth | |
| lands, the JWT goes in an Authorization header. That rules out `EventSource` | |
| on the client — it is GET-only and cannot set headers. | |
| """ | |
| settings = get_settings() | |
| async def stream() -> AsyncIterator[bytes]: | |
| frames = ( | |
| live_stream(body.query, body.conversation_id) | |
| if settings.agent_mode == "live" | |
| else replay(body.query, body.conversation_id, speed) | |
| ) | |
| async for frame in frames: | |
| yield frame.encode("utf-8") | |
| return StreamingResponse(stream(), media_type="text/event-stream", headers=SSE_HEADERS) | |
| _dist = Path(__file__).resolve().parents[2] / "frontend" / "dist" | |
| if _dist.is_dir(): | |
| app.mount("/", StaticFiles(directory=str(_dist), html=True), name="spa") | |