--- title: FastAPI ML Model on Hugging Face Spaces emoji: 🚀 colorFrom: green colorTo: indigo sdk: docker pinned: false --- # FastAPI Multi-Agent + RAG Service A FastAPI backend that routes a user's question to the right specialist agent — web search, RAG over uploaded documents, SQL over a database, or image generation — with caching, conversation memory, and observability built in. > Product requirements: see [PRD.md](PRD.md) > HF Spaces config reference: https://huggingface.co/docs/hub/spaces-config-reference ## Contents - [Quick Start](#quick-start) - [Environment Variables](#environment-variables) - [Docker](#docker) - [Authentication & Admin Approval](#authentication--admin-approval) - [API Overview](#api-overview) - [Multi-Agent Chat](#multi-agent-chat) - [RAG Pipeline](#rag-pipeline) --- ## Quick Start ```bash # 1. Create & activate a virtual environment python -m venv .venv .venv\Scripts\activate # Windows source .venv/bin/activate # macOS / Linux # 2. Install dependencies pip install -r requirements.txt # 3. Set environment variables (see below), then run uvicorn app:app --reload --host 127.0.0.1 --port 7860 ``` Server runs at `http://localhost:7860`. --- ## Environment Variables **Required** | Variable | Purpose | |----------|---------| | `DEEPSEEK_API_KEY` | LLM used by the RAG, SQL, and image-prompt agents | | `SUPABASE_URL` | Supabase project URL (or `NEXT_PUBLIC_SUPABASE_URL`) | | `SUPABASE_SERVICE_ROLE_KEY` | Supabase key (or `SUPABASE_ANON_KEY` / `NEXT_PUBLIC_SUPABASE_ANON_KEY`) | **Optional** | Variable | Default | Purpose | |----------|---------|---------| | `DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | LLM endpoint | | `DEEPSEEK_MODEL` | `deepseek-chat` | Chat model | | `EMBEDDING_MODEL` | `BAAI/bge-base-en-v1.5` | HuggingFace embedding model | | `REDIS_URL` | `redis://localhost:6379` | Cache + session memory | | `TAVILY_API_KEY` | – | Enables the web-search agent (falls back to RAG if unset) | | `TOP_K` / `FETCH_K` | `7` / `20` | Chunks returned / candidates fetched (fewer candidates → faster reranking) | | `MATCH_THRESHOLD` | `0.3` | Min cosine similarity for a vector match | | `RERANK_ENABLED` | `true` | Toggle cross-encoder reranking | | `RAG_CONDENSE_QUERY` | `true` | Rewrite conversational follow-ups into standalone retrieval queries | **SQL route only** — set one database URL: `SUPABASE_DB_POOLER_URL`, `SQL_DATABASE_URL`, `SUPABASE_DB_URL`, `DATABASE_URL`, or `SUPABASE_DATABASE_URL`. --- ## Docker | Setting | Value | |---------|-------| | Python | 3.11.9 | | Port | 7860 | | User | `user` (uid 1000) | | Workdir | `/app` | | Entrypoint | `uvicorn app:app --host 0.0.0.0 --port 7860` | --- ## Authentication & Admin Approval Every endpoint (except `/` and `/admin/*`) requires a valid Supabase JWT **and** an admin-approved account. New signups start as `pending` in `public.profiles.status` and cannot call the API until approved. | Endpoint | Who | What it does | |----------|-----|--------------| | `GET /admin/users?status=` | Admin (`profiles.role = 'admin'`) | List users, optionally filtered by `pending` / `approved` / `rejected` | | `POST /admin/users/{user_id}/status` | Admin | Set a user's status to `approved` or `rejected` (admins cannot change their own) | Status lookups are cached and invalidated on change. CORS is restricted to the known frontend origins; SlowAPI rate limiting applies to all routes. --- ## API Overview | Area | Endpoints | Notes | |------|-----------|-------| | Multi-agent chat | `POST /multi_agent_chat`, `POST /feedback`, `POST /log_login` | See [Multi-Agent Chat](#multi-agent-chat) | | RAG ingestion | `POST /chunk_pdf`, `POST /chunk_url` | Streams NDJSON progress; see [RAG Pipeline](#rag-pipeline) | | Document CRUD | `GET/PUT/DELETE /documents…`, `GET /documents/{id}/public_url` | Always scoped to the calling user | | Form filling | `POST /fill_pdf_form` | Extracts fields from an AI response and renders a pre-fill PDF (Great Eastern PSF02) | | ML/DL catalog | `POST /models/*`, `GET /predict/stock-lstm` | ~28 demo models: regression, classification, clustering, PCA/ICA, association rules (Apriori/FP-Growth/ECLAT), CNN digits, LSTM stock, sentiment, book/movie recommenders — notebooks in [ml/](ml/), datasets in [public/data/](public/data/) | --- ## Multi-Agent Chat **Endpoint:** `POST /multi_agent_chat` **In one line:** authenticate → pick an agent by keywords → serve from cache if possible → run the agent → return the answer, logging in the background. ### Routing | Keywords in the query | Agent | What it does | |-----------------------|-------|--------------| | draw, image, picture, logo… | 🖼️ Image | Generates an image | | memory, document, pdf, recall… | 📚 RAG | Answers from uploaded docs | | sales, orders, inventory, stock… | 🗄️ SQL | Queries the database | | latest, news, today, trending… | 🔎 Tavily | Searches the web | | _no clear match_ | 🔎 Tavily | Default fallback | ### Request flow ```mermaid flowchart LR A([Question]) --> B[1. Check login] B --> C[2. Pick agent
by keywords] C --> D{3. Seen this
before?} D -->|Yes, cached| Z([Answer]) D -->|No| E[4. Run the agent] E --> F[5. Score the answer
+ save to cache] F --> Z F -.background.-> G[(Save chat history
& logs)] ``` Fallbacks: the Tavily agent falls back to RAG when no API key is set, and the SQL agent falls back to RAG on error. > **Memory:** with a `session_id`, the last 10 turns are loaded from Redis for > context, and the cache is skipped to keep the answer personal. --- ## RAG Pipeline The RAG agent ([routes/agents/agent_rag.py](routes/agents/agent_rag.py)) combines semantic chunking, hybrid retrieval, and cross-encoder reranking. Ingestion lives in [routes/Memory_Upload.py](routes/Memory_Upload.py); shared chunking/embedding utilities in [routes/rag_chunking.py](routes/rag_chunking.py). ### How it works, in five steps 1. **Ingest** — pull clean text out of PDFs (text + tables) and web pages, with an SSRF guard blocking requests to private/internal addresses. 2. **Chunk** — split prose by *meaning* (semantic chunking) and tables by row windows, prepending a `Document: | Page: N` header so every chunk carries its source context. 3. **Embed & store** — turn each chunk into a vector and store it in Supabase/pgvector, **keyed per user** so users can never see each other's documents. 4. **Search** — rewrite follow-up questions into standalone queries, run keyword + vector search in parallel, fuse the results (RRF), then let a stricter cross-encoder rerank the top candidates and drop near-duplicates. 5. **Answer** — the LLM must answer *only* from the retrieved chunks; a final groundedness check scores the answer against its sources and floors hallucinated output. The table below maps each step to the exact libraries, models, and settings. ### Techniques | Stage | Technique | Notes | |-------|-----------|-------| | Parsing | Text + table extraction | `pdfplumber` text, `camelot` tables (lattice → stream), `PyMuPDFLoader` fallback | | Parsing | URL scraping | `BeautifulSoup` text with an SSRF guard (private/loopback IPs blocked) | | Enrichment | Header prepending | Adds `Document: \| Page: N` so chunks carry source context | | Chunking | **Semantic** (prose) | LangChain `SemanticChunker`, embedding-based splits by meaning | | Chunking | **Row-window** (tables) | 16 rows/chunk, 3-row overlap — keeps tables intact | | Chunking | Recursive fallback | 400 chars / 80 overlap; re-splits semantic chunks > 1200 chars | | Embedding | HuggingFace BGE | `BAAI/bge-base-en-v1.5`, L2-normalized, batched (32) | | Storage | pgvector + Chroma | Supabase `rag_user_documents`, keyed per user with source metadata | | Query | **History-aware rewriting** | Follow-ups condensed into a standalone retrieval query (`RAG_CONDENSE_QUERY`); a lexical guard skips the extra LLM rewrite for already-standalone questions, and the original phrasing is kept for generation | | Retrieval | **Hybrid + RRF** | pgvector cosine + full-text keyword search fused via Reciprocal Rank Fusion (RPC `match_rag_documents`) | | Retrieval | Metadata filtering | By `user_id`, `source_type` (pdf/url), `url`, `created_after` | | Reranking | **Cross-encoder** | `ms-marco-MiniLM-L-6-v2` rescores query–passage pairs; lexical token-overlap fallback | | Diversity | **Near-duplicate dedup** | Drops repeated chunks (e.g. overlapping table row-windows) before the final top_k | | Generation | Grounded synthesis | DeepSeek over numbered context + last 3 conversation turns | | Eval | **Groundedness scoring** | Post-hook scores answer's lexical coverage by retrieved context (floors ungrounded answers) | ### Ingestion sequence — `POST /chunk_pdf`, `POST /chunk_url` ```mermaid sequenceDiagram participant U as Client participant API as Upload route participant S as Supabase (storage + DB) participant E as BGE embedder U->>API: PDF / URL (+ user_id, JWT) API->>API: Validate (MIME, size, %PDF magic / SSRF guard) API->>S: Upload file + insert documents row API->>API: Parse (text + tables) & enrich headers API->>API: Chunk (semantic prose, row-window tables) loop batches of 32 API->>E: embed_documents(chunk_texts) API->>S: insert rows into rag_user_documents API-->>U: NDJSON progress event (55→90%) end API-->>U: done (inserted_rows, 100%) ``` ### Retrieval + generation sequence — RAG route of `POST /multi_agent_chat` ```mermaid sequenceDiagram participant U as Client participant R as RAGAgent participant DB as Supabase RPC participant X as Cross-encoder participant LLM as DeepSeek U->>R: query (+ top_k, fetch_k, filters) R->>R: Condense follow-up → standalone query (if history) R->>R: Normalize query, embed with BGE R->>DB: match_rag_documents (vector + keyword, RRF, filters) DB-->>R: candidate chunks (ordered, up to fetch_k) Note over R,DB: Falls back to Chroma similarity/MMR if the RPC errors R->>R: Dedup near-duplicate chunks R->>X: rerank query–passage pairs → top_k Note over R,X: Falls back to token-overlap reranking if unavailable R->>R: build_context (numbered sources) R->>LLM: system prompt + history + context + original question LLM-->>R: answer + token usage R->>R: Score groundedness (answer vs. context) R-->>U: answer + sources + groundedness ``` > The RAG and SQL agents run in a threadpool (`run_in_threadpool`) so their CPU > and blocking-network work don't stall the async event loop under concurrency.