Spaces:
Running
Running
| 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<br/>by keywords] | |
| C --> D{3. Seen this<br/>before?} | |
| D -->|Yes, cached| Z([Answer]) | |
| D -->|No| E[4. Run the agent] | |
| E --> F[5. Score the answer<br/>+ save to cache] | |
| F --> Z | |
| F -.background.-> G[(Save chat history<br/>& 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: <name> | 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: <name> \| 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. | |