fastapi_hf / README.md
looh2's picture
Add detailed Product Requirements Document for FastAPI Multi-Agent + RAG Service
0fb7b50
|
Raw
History Blame Contribute Delete
10.7 kB
---
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.