Spaces:
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
HF Spaces config reference: https://huggingface.co/docs/hub/spaces-config-reference
Contents
- Quick Start
- Environment Variables
- Docker
- Authentication & Admin Approval
- API Overview
- Multi-Agent Chat
- RAG Pipeline
Quick Start
# 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 |
| RAG ingestion | POST /chunk_pdf, POST /chunk_url |
Streams NDJSON progress; see 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/, datasets in 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
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) combines semantic chunking, hybrid retrieval, and cross-encoder reranking. Ingestion lives in routes/Memory_Upload.py; shared chunking/embedding utilities in routes/rag_chunking.py.
How it works, in five steps
- Ingest — pull clean text out of PDFs (text + tables) and web pages, with an SSRF guard blocking requests to private/internal addresses.
- Chunk — split prose by meaning (semantic chunking) and tables by
row windows, prepending a
Document: <name> | Page: Nheader so every chunk carries its source context. - 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.
- 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.
- 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
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
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.