A polished, ready project that demonstrates a production‑grade chat experience with authentication, per‑chat modes (offline / web / auto), document ingestion + retrieval, streaming tokens via SSE, source citations, evaluation hooks, and optional tracing.
- Modern full‑stack: FastAPI (Python) backend + React/TypeScript (Vite) frontend
- Local LLM via Ollama: default model
aya-expanse:8b(configurable), streamed via/api/generate - Accounts & sessions: Email/password with Argon2, secure cookie sessions
- Per‑chat modes:
offline(local RAG),web(internet search),auto(routing) - RAG pipeline: stage → ingest → retrieve → cite sources
- Live streaming: Server‑Sent Events (SSE) for token‑wise generation
- Evidence UI: Sources bar, tool log, optional trace id (Langfuse)
- Attachments UX: Drag‑and‑drop, shelf preview, PDF export of a conversation
- MySQL 8 + SQLAlchemy: Async engine, clean models & migrations‑ready layout
- Architecture
- Project structure
- Backend (FastAPI)
- Frontend (React/TS)
- Environment & configuration
- Running locally
- API reference (selected)
- Retrieval‑Augmented Generation
- Tracing & evaluation
- Troubleshooting
- Roadmap
┌────────────┐ SSE /chat/stream ┌────────────────────────┐
│ Frontend │ ───────────────────────────▶ │ FastAPI app │
│ React/TS │ ◀──── tokens / tool events ─ │ • Auth (cookies) │
│ │ │ • Chats & messages │
│ Chat UI │ uploads / REST │ • Files: stage/ingest │
│ Sources │ ───────────────────────────▶ │ • RAG retriever │
│ Tools log │ │ • Tools: web_search, │
│ PDF export│ citations / trace id │ py_eval (sandboxed) │
└────────────┘ ◀─────────────────────────── │ • SSE streaming │
│ • Tracing (Langfuse) │
└─────────┬──────────────┘
│
┌────────────▼─────────────┐
│ Storage │
│ • MySQL (users/chats) │
│ • Chroma / local docs │
│ • uploads_staged/var │
└──────────────────────────┘
Modes
- offline — answers only from ingested local files (RAG)
- web — answers backed by the internet search tool
- auto — smart routing between offline and web
Essential folders & files (trimmed):
backend/
auth.py # register/login/logout, cookie sessions
chats.py # chat CRUD + list messages + auto-title
db.py # async engine, .env loading
main.py # app factory, SSE /chat/stream, file ingest endpoints
memory.py # lightweight conversation memory helpers
models.py # SQLAlchemy models (User, Session, Chat, ChatMessage, ...)
requirements.txt # backend deps
tracing.py # optional Langfuse + local JSONL traces
rag/ingest.py # bytes → chunks → vector store
rag/retriever.py # semantic retrieval for answers
tools/py_eval.py # safe(ish) python evaluation tool
tools/web_search.py # Tavily‑backed web search tool
uploads_staged/ # temporary upload staging
var/, logs/, chroma_db/
frontend/
src/features/chat/ # Chat experience (Messages, Sources, Tools, ExportPDF)
src/features/layout/ # Header, Sidebar, Account Section
src/lib/ # api.ts, sse.ts, chats.ts, auth, modes, markdown utils
src/routes/ # Chat, Landing, Login/Register, Profile, Reset
src/components/ui/ # Small, reusable UI primitives
Core capabilities
- Auth & sessions — email/password with Argon2; sessions as HTTP‑only cookies
- Chats — create, rename, delete; list chat messages; auto‑title from user’s first turn and/or file snippets
- Streaming — SSE endpoint streams tokens, tool events, source citations, and a
trace_id - Files & RAG — stage uploads, ingest to the vector store, and cite retrieved passages
- Tools —
web_search(Tavily) and a minimalpy_eval(sandboxed) for code snippets under guardrails - Tracing — lightweight JSONL audit log; optional Langfuse emission if configured
Selected models
User,Session(sid/expiry, user agent, IP),Chat,ChatMessageUserProfile(display name, avatar stored as bytes with MIME & updated_at)
Selected endpoints
POST /auth/register,POST /auth/login,POST /auth/logout,GET /auth/meGET /chats,POST /chats,PATCH /chats/{id},DELETE /chats/{id}GET /chats/{id}/messages,POST /chats/{id}/auto-titleGET /chat/stream(SSE) — query params:q,mode(offline|web|auto),scope(user|chat), optionalchat_id,cid
The SSE stream emits events:
token(partial text),tool(name/args/result),sources(citations),trace(UUID),done(status),error.
UX features
- Chat first: token‑streamed reply, automatic scroll, code‑aware formatting
- Modes UI: persist per‑chat
offline | web | auto, sensible defaults for guests - Evidence surfaces: sources bar (filename, URL host, line ranges), tool calls panel, trace id pill
- Attachments: drag‑and‑drop shelf, staged preview, one‑click ingest & commit
- PDF export: render current conversation (with sources) into a neat A4 PDF
- Profile: display name + avatar (stored and served from backend)
Client plumbing
- Small
api.tswrapper (JSON + credentials) sse.tsabstraction with handlers:onToken,onSources,onTool,onTrace,onDone,onErrorreasoningStorage.ts: saves last tool/sources/trace per chat insessionStorage
Create backend/.env with the following keys (values are examples/placeholders):
# Database & Auth
DB_URL="mysql+aiomysql://root:password@localhost:3306/agent2"
SESSION_TTL_HOURS=168
COOKIE_NAME=sid
FRONTEND_ORIGIN="http://localhost:5173"
# Ollama (local LLM)
MODEL_NAME="aya-expanse:8b" # any locally installed model, e.g., llama3.1:8b-instruct
OLLAMA_HOST="http://127.0.0.1:11434"
OLLAMA_NUM_CTX=8192 # optional; backend sends options.num_ctx when >0
# Web search (Tavily)
TAVILY_API_KEY="<your tavily api key>"
TAVILY_BASE="https://api.tavily.com"
RESOLVE_REDIRECTS=1 # follow aggregator links to publisher URLs
# (Optional) Tracing with Langfuse
LANGFUSE_HOST="https://cloud.langfuse.com"
LANGFUSE_PUBLIC_KEY="<pk>"
LANGFUSE_SECRET_KEY="<sk>"The backend loads
.envautomatically on import and fails fast ifDB_URLis missing, so configuration errors are caught early. IfOLLAMA_HOSTpoints to localhost and theollamabinary is discoverable, the server will attempt to autostartollama serveand wait ~30s for readiness.
- Install & start
- Windows: Install the Ollama desktop app, then ensure the service is running (you should see the Ollama tray or be able to run
ollama serve). - macOS / Linux: Install Ollama via your platform’s standard method and run
ollama serve.
- Windows: Install the Ollama desktop app, then ensure the service is running (you should see the Ollama tray or be able to run
- Pull a model (match your
MODEL_NAME):ollama pull aya-expanse:8b # or: ollama pull llama3.1:8b-instruct - Smoke test:
If you point
curl http://127.0.0.1:11434/api/version
OLLAMA_HOSTto a remote server, skip local install and ensure the remote is reachable.
The backend will try to autostart
ollama serveon localhost if it’s not up yet and the binary is found, then poll for readiness for up to ~30 seconds.
cd backend
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8000cd frontend
npm install
# If your backend is not on the default http://localhost:8000, set VITE_API_BASE in a .env file
npm run dev # Vite dev serverDefault CORS/credentials are aligned for
localhost:5173⇄localhost:8000. AdjustFRONTEND_ORIGINin.envif you change ports or host.
Auth
POST /auth/register→{ id, email, display_name }POST /auth/login→ setssidcookiePOST /auth/logout→ clears sessionGET /auth/me→ current user ornull
Chats
GET /chats→ list of{ id, title, updated_at, last_preview }POST /chats→ create new chat (optionaltitle)PATCH /chats/{id}→ rename chatDELETE /chats/{id}→ remove chatGET /chats/{id}/messages→ ordered{ id, role, content, created_at }POST /chats/{id}/auto-title→ concise title seeded from first user turn and/or attached files
Streaming
GET /chat/stream?q=...&mode=offline|web|auto&scope=user|chat[&chat_id=...][&cid=...]- Events:
token,sources,tool,trace,done,error
- Events:
Files
- Stage → ingest → commit workflow; files are kept under
backend/uploads_stageduntil committed. Ingested text becomes retrievable context with line‑ranges for citations.
- Upload & Stage: Files are uploaded to a staging area and inspected for text‑like content.
- Ingest: Text is chunked and embedded into the local vector store.
- Retrieve: On a user query in
offline/auto, top passages are fetched by semantic similarity. - Answer with Evidence: The model answers and the UI displays source filename, optional URL host, and line ranges.
In
web/auto, the web_search tool queries the internet (Tavily) and normalizes publisher URLs. Citations from the tool are merged with local RAG sources.
- Tracing: a tiny
Tracerwrites compact JSONL logs tobackend/logs/traces.jsonland, ifLANGFUSE_*is set, emits to Langfuse. The trace id appears in the UI for quick correlation. - Evaluation: a
ragas_eval.pyhook (offline) can score retrieval quality and save summaries underbackend/logs/ragas/.
- DB_URL missing: ensure
backend/.envexists andDB_URLis set to a reachable MySQL DSN. - CORS / cookie issues: confirm
FRONTEND_ORIGINmatches your Vite dev URL; backend setsSameSite=LaxandHttpOnlycookies for local dev. - No streaming: verify the frontend points to the correct API base (
VITE_API_BASE) and that/chat/streamis reachable. Firewalls/proxies can block SSE. - No sources shown: for
offline, confirm you ingested files; forweb, verifyTAVILY_API_KEY. - Ollama connection failed / 11434 not reachable: start
ollama serve(or let backend autostart on localhost), verifyOLLAMA_HOST. ERROR: Ollama /api/show ...: the model is not installed locally — runollama pull <MODEL_NAME>.- Context window issues: if you need a larger context, set
OLLAMA_NUM_CTXto a supported value for your model; the backend forwards it asoptions.num_ctx.
- Upload progress + multi‑file batching
- Simple migrations scaffold (Alembic)
- Per‑file privileges & redaction
- More tools (tables/plots, structured browsing)
- Dark‑mode PDF theme
Private, academic submission. All third‑party services and trademarks belong to their respective owners.