Full-Stack GenAI App: FastAPI, React, Supabase in 4 Hours

A complete walkthrough of building a production-ready document-grounded AI chatbot for investment analysts. Covers database setup with Alembic migrations, hybrid retrieval with embeddings and full-text search, agentic search loops, React frontend with authentication, and deployment to Railway. Demonstrates real-world AI engineering practices including incremental AI prompting, architecture documentation, and handling hallucination through grounding and validation.

Project Brief & Architecture

Driftwood Capital Use Case

Investment research firm where analysts spend half their time manually reading SEC filings (10-Ks, 10-Qs, earnings transcripts) to extract insights. The solution is an internal document copilot that lets analysts ask natural-language questions and receive sourced answers citing specific pages and passages, saving ~3 hours per week per analyst.

Why Custom Over ChatGPT

Enterprise clients cannot use public AI tools due to data privacy/compliance requirements and need customization for standardized output. Off-the-shelf tools hallucinate across large document volumes and lack grounding mechanisms. A custom solution with role-based access, audit trails, and citation enforcement is necessary.

Full-Stack Architecture

Frontend (React/TypeScript on Railway) handles authentication via Supabase and displays chat UI. Backend (FastAPI/Python) manages API endpoints, orchestrates retrieval, and runs the agentic search loop. Supabase provides PostgreSQL database with pgvector extension for embeddings. OpenAI generates embeddings and powers the LLM. Document ingestion pipeline (local, using Dockling) parses HTML filings into markdown chunks with vector embeddings.

Phase 1: Database & Setup

Environment & Dependencies

Project uses UV for Python dependency management with strict version pinning (exact bounds, exclude packages <7 days old) to prevent supply-chain attacks. Frontend uses pnpm with Tailwind CSS and shadcn/ui components. Both backend and frontend have agent.md files to guide AI coding assistants on project conventions.

Supabase Project Setup

Create free Supabase project with PostgreSQL database. Store connection credentials (URL, anon key, service role key) in .env files. Enable row-level security (RLS) for data isolation. Configure authentication to disable new user signups (manual admin creation only for internal tool). Set region to match backend deployment for latency optimization.

SQLAlchemy Models & Alembic Migrations

Define database tables as Python dataclasses using SQLAlchemy ORM (User, SourceDocument, DocumentChunk, ChatThread, ChatMessage, MessageCitation). Use Alembic to generate and track schema migrations. Run migrations to sync Python models with remote PostgreSQL. This avoids manual SQL and enables version control of schema changes.

Phase 2: Authentication & API Scaffold

Supabase Auth Integration

Frontend uses @supabase/auth-helpers-react for login/signup. Backend validates JWT tokens via Supabase service role key. Disable email confirmation for internal tool; manually create users in Supabase dashboard. Set redirect URLs for local (localhost:3000) and production (Railway domain) deployments.

FastAPI Dependency Injection

Create dependencies.py with get_current_user() to extract and validate JWT from request headers. Use Pydantic settings to load environment variables (database URL, API keys) with validation. All protected endpoints require valid token; unauthenticated requests return 401.

React Frontend Scaffold

Initialize Vite + React + TypeScript project. Install Supabase client, Tailwind CSS, and shadcn/ui components. Create login/signup pages, protected chat layout with sidebar (conversation history), and message display area. Use React Router for navigation between auth and chat views.

Phase 3: Chat Vertical Slice

Chat API Endpoints

POST /chats: Create new chat thread (returns thread_id). POST /chats/{thread_id}/messages: Send user message, trigger backend retrieval, return assistant response with citations. GET /chats: List user's chat threads. DELETE /chats/{thread_id}: Delete conversation. All endpoints require authentication.

Frontend Chat UI

Sidebar shows list of chat threads with delete buttons. Main area displays conversation history (user messages left, assistant right). Input box at bottom sends queries. Use shadcn/ui Card, Button, Input components for consistent styling. Show loading state while backend processes query.

Phase 4: Document Ingestion Pipeline

HTML Parsing with Dockling

Download SEC filings (10-K, 10-Q) as HTM files from SEC API for Apple, Microsoft, Nvidia, Amazon, Google (5 years each). Use Dockling library to convert HTM to markdown, extracting text, tables, and structure. Save markdown files locally for inspection and chunking.

Custom Table Extraction

Dockling initially struggled with HTML table formatting in markdown. Implemented custom HTML table parser to extract structured data as JSON and render as proper markdown tables. This ensures citations show clean, readable tables instead of malformed text.

Chunking & Embedding

Split markdown documents into overlapping chunks (~512 tokens, 50% overlap). Generate OpenAI embeddings (text-embedding-3-small) for each chunk. Store chunk text, embedding vector, source document ID, and page reference in DocumentChunk table. Enable pgvector extension in Supabase for vector similarity search.

Full-Text Search Index

Create PostgreSQL full-text search index on chunk text for keyword-based retrieval. Combine with vector search for hybrid retrieval: vector search finds semantically similar chunks, full-text search catches exact keyword matches. Rank and fuse results before passing to LLM.

Phase 5: Retrieval & Agentic Search

Hybrid Retrieval Pipeline

User query triggers two parallel searches: (1) Embed query and search pgvector for top-K semantically similar chunks. (2) Extract 3–5 keywords from query using LLM, then full-text search PostgreSQL. Fuse results, deduplicate, and rank by relevance. Return top chunks with neighboring context (±1 chunk).

Pydantic AI Agent with Tool Loop

Use Pydantic AI to create an agent with access to tools: search_filings (hybrid retrieval), read_chunks (fetch specific chunk), read_surrounding_chunks (expand context). Agent runs in a loop: searches, evaluates if enough info, reads more if needed, synthesizes answer. This agentic approach prevents naive single-pass retrieval and enables reasoning over results.

Grounding & Validation

After agent synthesizes answer, run validator that checks: (1) Every citation references an actual retrieved chunk. (2) Quoted text is copied verbatim from chunk (no paraphrasing). (3) No hallucinated facts outside retrieved context. If validation fails, return 'insufficient information' instead of risky answer. This enforces trust contract with analysts.

Phase 6: Frontend Chat Integration

Streaming & Loading States

Frontend calls backend API to send query. Display real-time status updates (Analyzing → Searching → Reading → Verifying) as agent progresses. Use gradient text animation for visual feedback. Once response arrives, parse markdown and render citations as clickable links.

Citation & Source Display

Inline citations in answer text are clickable. Clicking opens side panel showing source document metadata (ticker, filing date, page), the cited chunk as markdown table, and neighboring chunks for context. Users can verify grounding before trusting answer.

Markdown Rendering

Use react-markdown with syntax highlighting to render LLM responses. Support tables, code blocks, bold/italic. Handle edge cases (malformed markdown from extraction). Display error messages if backend validation fails.

Phase 7: Deployment to Railway

Docker Setup

Create Dockerfile for backend: installs Python, UV, dependencies, exposes port 8000, runs 'uv run uvicorn app.main:app'. Create Dockerfile for frontend: installs Node, builds Vite app, serves via Caddy web server on port 3000. Both Dockerfiles enable consistent local-to-production parity.

Railway Project Configuration

Create Railway project linked to GitHub repo. Deploy backend and frontend as separate services. Configure environment variables (database URL, API keys, CORS origins) in Railway dashboard. Set region to Europe for latency optimization. Backend auto-exposes at production.up.railway.app, frontend at document-copilot-frontend-production.up.railway.app.

Environment Variable Management

Frontend needs VITE_API_BASE_URL (backend URL), VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY. Backend needs DATABASE_URL (with pgvector), SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, OPENAI_API_KEY. Use Railway UI or CLI to set variables; never commit secrets to git.

CI/CD & Redeployment

Use Railway CLI (railway up) to manually redeploy after code changes. Alternatively, configure GitHub Actions for auto-deploy on push to main/development branch. Monitor deployment logs in Railway dashboard for errors. First deployment may require troubleshooting (missing dependencies, port conflicts, etc.).

AI Engineering Best Practices

Incremental Prompting Over Big Plans

Instead of asking AI to 'build entire backend', break into bite-sized tasks: 'Create config.py', 'Set up database models', 'Implement chat endpoint'. Review each output, understand code, catch issues early. Prevents vibe-coding and maintains mental map of codebase. Faster iteration with smaller, focused prompts.

Model Selection by Task

Use fast models (Claude 3.5 Sonnet, GPT-4o mini) for straightforward coding tasks. Switch to reasoning models (GPT-4 Turbo, Claude Opus) for complex debugging, architecture decisions, or design work. For frontend/UI, use models with design skills (Claude Opus + design skill). Adjust reasoning level based on problem complexity.

Agent.md Files for Consistency

Create agent.md at project root and in each subdirectory (backend, frontend) to document project conventions, tech stack, coding standards, and preferences. AI assistants automatically inject this into system prompts, ensuring consistent code style and architectural decisions across the codebase.

Dependency Minimization

Avoid bloated dependency trees. Pin exact versions and exclude packages <7 days old (supply-chain risk). Prefer built-in libraries or well-maintained alternatives. In Python, use Pydantic for validation, SQLAlchemy for ORM, Uvicorn for server. In TypeScript, use shadcn/ui (headless components) over full UI frameworks.

Context Window Management

When AI chat gets stuck or produces errors, start a fresh thread with a beefier model and higher reasoning. Provide clear error messages and ask for a plan before implementation. Use MCP servers (Railway, GitHub) to give AI structured access to tools instead of manual CLI commands.

Real-World Considerations & Next Steps

Hallucination & Grounding Trade-offs

Agentic search with validation is slower and more expensive (~$1–2 per query with GPT-4 Turbo) but prevents hallucinations. Naive single-pass retrieval is fast and cheap but risky for high-stakes use (investment analysis). Choose based on use case: internal tool = prioritize accuracy; consumer app = optimize speed/cost.

Query Classification & Routing

Not all queries need full agentic search. Implement query classifier to detect: simple factual questions (answer from system prompt), follow-ups (reuse previous context), deep analytical questions (full pipeline). Route accordingly to reduce latency and cost for simple queries.

Data Quality & Chunking Optimization

Current pipeline chunks at ~512 tokens with 50% overlap. Experiment with chunk size, overlap, and separator strategies. Monitor for duplicate chunks (ingestion pipeline may have run multiple times). Validate that retrieved chunks actually answer user questions. Iteratively refine based on real user feedback.

Security & Compliance for Production

Current setup is proof-of-concept. For production: (1) Use private VPC for database & backend. (2) Rotate API keys regularly; store in secret vault, not .env. (3) Implement audit logging (who queried what, when). (4) Add rate limiting & authentication per user. (5) Encrypt data at rest & in transit. (6) Comply with data residency (GDPR, etc.).

Iterative Improvement Loop

Deploy MVP to pilot group of analysts. Collect feedback: Which queries fail? Which are slow? Which hallucinate? Use real queries to benchmark and optimize. Adjust prompts, retrieval strategy, chunking, model selection. This feedback loop is where real AI engineering happens—not in the initial build.

Notable quotes

This is engineering in the real world, so sometimes I make mistakes, I get stuck, I have to debug things. — Dave Ebbelaar
If you don't have intermediate review points and decide what good looks like, it's going to get messy. — Dave Ebbelaar
The beauty of this is that we're going to cover the entire architecture, something you rarely see on YouTube. — Dave Ebbelaar

Action items

  • Clone the main branch of the repository and set up a Python virtual environment with UV.
  • Create a free Supabase project, configure authentication (disable new signups), and store connection credentials in .env files.
  • Define SQLAlchemy ORM models for User, SourceDocument, DocumentChunk, ChatThread, ChatMessage, and MessageCitation tables.
  • Run Alembic migrations to sync Python models with remote PostgreSQL database.
  • Set up FastAPI backend with Pydantic settings, dependency injection for JWT validation, and chat endpoints (POST /chats, POST /chats/{thread_id}/messages, GET /chats).
  • Initialize Vite + React + TypeScript frontend with Supabase auth, Tailwind CSS, and shadcn/ui components.
  • Download SEC filings (HTM files) using the provided download.py script for Apple, Microsoft, Nvidia, Amazon, Google (5 years).
  • Parse HTM files to markdown using Dockling; implement custom HTML table extractor for clean table rendering.
  • Chunk markdown documents (~512 tokens, 50% overlap) and generate OpenAI embeddings; store in DocumentChunk table with pgvector.
  • Implement hybrid retrieval pipeline: keyword extraction (LLM), parallel vector + full-text search, result fusion and ranking.
  • Build Pydantic AI agent with tools (search_filings, read_chunks, read_surrounding_chunks) and agentic loop for multi-step reasoning.
  • Add grounding validator to ensure every citation references actual retrieved chunks and prevent hallucinations.
  • Wire frontend chat UI to backend API; display streaming status updates and clickable citations with source panel.
  • Create Dockerfiles for backend (FastAPI on port 8000) and frontend (Vite on port 3000); configure Caddy for frontend serving.
  • Deploy to Railway: link GitHub repo, configure environment variables, set region, and test backend/frontend connectivity.
  • Use Railway CLI (railway up) to redeploy after code changes; monitor logs for errors.
  • Manually create analyst users in Supabase dashboard and share credentials for pilot testing.
  • Collect user feedback on query accuracy, latency, and hallucinations; iterate on prompts, retrieval strategy, and chunking.
  • Implement query classification to route simple questions away from full agentic search (reduce cost/latency).
  • For production: add private VPC, secret vault for API keys, audit logging, rate limiting, encryption, and compliance checks.
Dave Ebbelaar
3 hr 52 min video
3 min read
Full-Stack GenAI App: FastAPI, React, Supabase in 4 Hours
You just saved 3 hr 49 min.
The big takeaway
A complete walkthrough of building a production-ready document-grounded AI chatbot for investment analysts. Covers database setup with Alembic migrations, hybrid retrieval with embeddings and full-text search, agentic search loops, React frontend with authentication, and deployment to Railway. Demonstrates real-world AI engineering practices including incremental AI prompting, architecture documentation, and handling hallucination through grounding and validation.
Project Brief & Architecture
Driftwood Capital Use Case
Investment research firm where analysts spend half their time manually reading SEC filings (10-Ks, 10-Qs, earnings transcripts) to extract insights. The solution is an internal document copilot that lets analysts ask natural-language questions and receive sourced answers citing specific pages and passages, saving ~3 hours per week per analyst.
Why Custom Over ChatGPT
Enterprise clients cannot use public AI tools due to data privacy/compliance requirements and need customization for standardized output. Off-the-shelf tools hallucinate across large document volumes and lack grounding mechanisms. A custom solution with role-based access, audit trails, and citation enforcement is necessary.
Full-Stack Architecture
Frontend (React/TypeScript on Railway) handles authentication via Supabase and displays chat UI. Backend (FastAPI/Python) manages API endpoints, orchestrates retrieval, and runs the agentic search loop. Supabase provides PostgreSQL database with pgvector extension for embeddings. OpenAI generates embeddings and powers the LLM. Document ingestion pipeline (local, using Dockling) parses HTML filings into markdown chunks with vector embeddings.
1
User logs in via Supabase auth
2
Frontend sends query to FastAPI backend
3
Backend searches embeddings + full-text in pgvector
4
Agentic loop retrieves and validates chunks
5
LLM synthesizes grounded answer with citations
6
Frontend displays markdown + source links
End-to-end data flow from user query to cited answer
Phase 1: Database & Setup
Environment & Dependencies
Project uses UV for Python dependency management with strict version pinning (exact bounds, exclude packages <7 days old) to prevent supply-chain attacks. Frontend uses pnpm with Tailwind CSS and shadcn/ui components. Both backend and frontend have agent.md files to guide AI coding assistants on project conventions.
Supabase Project Setup
Create free Supabase project with PostgreSQL database. Store connection credentials (URL, anon key, service role key) in .env files. Enable row-level security (RLS) for data isolation. Configure authentication to disable new user signups (manual admin creation only for internal tool). Set region to match backend deployment for latency optimization.
SQLAlchemy Models & Alembic Migrations
Define database tables as Python dataclasses using SQLAlchemy ORM (User, SourceDocument, DocumentChunk, ChatThread, ChatMessage, MessageCitation). Use Alembic to generate and track schema migrations. Run migrations to sync Python models with remote PostgreSQL. This avoids manual SQL and enables version control of schema changes.
1
Users
Email, display name, created_at
2
SourceDocuments
Ticker, filing type, date, file path
3
DocumentChunks
Text, embedding vector, full_text_search, source_id
4
ChatThreads
User ID, title, created_at
5
ChatMessages
Thread ID, role, content, citations
Core database tables for chat application
Phase 2: Authentication & API Scaffold
Supabase Auth Integration
Frontend uses @supabase/auth-helpers-react for login/signup. Backend validates JWT tokens via Supabase service role key. Disable email confirmation for internal tool; manually create users in Supabase dashboard. Set redirect URLs for local (localhost:3000) and production (Railway domain) deployments.
FastAPI Dependency Injection
Create dependencies.py with get_current_user() to extract and validate JWT from request headers. Use Pydantic settings to load environment variables (database URL, API keys) with validation. All protected endpoints require valid token; unauthenticated requests return 401.
React Frontend Scaffold
Initialize Vite + React + TypeScript project. Install Supabase client, Tailwind CSS, and shadcn/ui components. Create login/signup pages, protected chat layout with sidebar (conversation history), and message display area. Use React Router for navigation between auth and chat views.
Phase 3: Chat Vertical Slice
Chat API Endpoints
POST /chats: Create new chat thread (returns thread_id). POST /chats/{thread_id}/messages: Send user message, trigger backend retrieval, return assistant response with citations. GET /chats: List user's chat threads. DELETE /chats/{thread_id}: Delete conversation. All endpoints require authentication.
Frontend Chat UI
Sidebar shows list of chat threads with delete buttons. Main area displays conversation history (user messages left, assistant right). Input box at bottom sends queries. Use shadcn/ui Card, Button, Input components for consistent styling. Show loading state while backend processes query.
Phase 4: Document Ingestion Pipeline
HTML Parsing with Dockling
Download SEC filings (10-K, 10-Q) as HTM files from SEC API for Apple, Microsoft, Nvidia, Amazon, Google (5 years each). Use Dockling library to convert HTM to markdown, extracting text, tables, and structure. Save markdown files locally for inspection and chunking.
Custom Table Extraction
Dockling initially struggled with HTML table formatting in markdown. Implemented custom HTML table parser to extract structured data as JSON and render as proper markdown tables. This ensures citations show clean, readable tables instead of malformed text.
Chunking & Embedding
Split markdown documents into overlapping chunks (~512 tokens, 50% overlap). Generate OpenAI embeddings (text-embedding-3-small) for each chunk. Store chunk text, embedding vector, source document ID, and page reference in DocumentChunk table. Enable pgvector extension in Supabase for vector similarity search.
Full-Text Search Index
Create PostgreSQL full-text search index on chunk text for keyword-based retrieval. Combine with vector search for hybrid retrieval: vector search finds semantically similar chunks, full-text search catches exact keyword matches. Rank and fuse results before passing to LLM.
Phase 5: Retrieval & Agentic Search
Hybrid Retrieval Pipeline
User query triggers two parallel searches: (1) Embed query and search pgvector for top-K semantically similar chunks. (2) Extract 3–5 keywords from query using LLM, then full-text search PostgreSQL. Fuse results, deduplicate, and rank by relevance. Return top chunks with neighboring context (±1 chunk).
1
Extract keywords from user query (LLM)
2
Parallel: vector search + full-text search
3
Deduplicate and rank results
4
Fetch neighboring chunks for context
5
Pass top-K chunks to agentic loop
Hybrid retrieval combines semantic and keyword matching
Pydantic AI Agent with Tool Loop
Use Pydantic AI to create an agent with access to tools: search_filings (hybrid retrieval), read_chunks (fetch specific chunk), read_surrounding_chunks (expand context). Agent runs in a loop: searches, evaluates if enough info, reads more if needed, synthesizes answer. This agentic approach prevents naive single-pass retrieval and enables reasoning over results.
1
Agent receives user query
2
Call search_filings tool
3
Evaluate: enough info?
4
If no: call read_chunks or read_surrounding_chunks
5
Repeat until confident
6
Synthesize grounded answer
Agentic loop enables multi-step reasoning and fallback retrieval
Grounding & Validation
After agent synthesizes answer, run validator that checks: (1) Every citation references an actual retrieved chunk. (2) Quoted text is copied verbatim from chunk (no paraphrasing). (3) No hallucinated facts outside retrieved context. If validation fails, return 'insufficient information' instead of risky answer. This enforces trust contract with analysts.
Phase 6: Frontend Chat Integration
Streaming & Loading States
Frontend calls backend API to send query. Display real-time status updates (Analyzing → Searching → Reading → Verifying) as agent progresses. Use gradient text animation for visual feedback. Once response arrives, parse markdown and render citations as clickable links.
Citation & Source Display
Inline citations in answer text are clickable. Clicking opens side panel showing source document metadata (ticker, filing date, page), the cited chunk as markdown table, and neighboring chunks for context. Users can verify grounding before trusting answer.
Markdown Rendering
Use react-markdown with syntax highlighting to render LLM responses. Support tables, code blocks, bold/italic. Handle edge cases (malformed markdown from extraction). Display error messages if backend validation fails.
Phase 7: Deployment to Railway
Docker Setup
Create Dockerfile for backend: installs Python, UV, dependencies, exposes port 8000, runs 'uv run uvicorn app.main:app'. Create Dockerfile for frontend: installs Node, builds Vite app, serves via Caddy web server on port 3000. Both Dockerfiles enable consistent local-to-production parity.
Railway Project Configuration
Create Railway project linked to GitHub repo. Deploy backend and frontend as separate services. Configure environment variables (database URL, API keys, CORS origins) in Railway dashboard. Set region to Europe for latency optimization. Backend auto-exposes at production.up.railway.app, frontend at document-copilot-frontend-production.up.railway.app.
Environment Variable Management
Frontend needs VITE_API_BASE_URL (backend URL), VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY. Backend needs DATABASE_URL (with pgvector), SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, OPENAI_API_KEY. Use Railway UI or CLI to set variables; never commit secrets to git.
CI/CD & Redeployment
Use Railway CLI (railway up) to manually redeploy after code changes. Alternatively, configure GitHub Actions for auto-deploy on push to main/development branch. Monitor deployment logs in Railway dashboard for errors. First deployment may require troubleshooting (missing dependencies, port conflicts, etc.).
AI Engineering Best Practices
Incremental Prompting Over Big Plans
Instead of asking AI to 'build entire backend', break into bite-sized tasks: 'Create config.py', 'Set up database models', 'Implement chat endpoint'. Review each output, understand code, catch issues early. Prevents vibe-coding and maintains mental map of codebase. Faster iteration with smaller, focused prompts.
Model Selection by Task
Use fast models (Claude 3.5 Sonnet, GPT-4o mini) for straightforward coding tasks. Switch to reasoning models (GPT-4 Turbo, Claude Opus) for complex debugging, architecture decisions, or design work. For frontend/UI, use models with design skills (Claude Opus + design skill). Adjust reasoning level based on problem complexity.
1
Fast model (Sonnet 3.5)
Scaffolding, boilerplate, simple endpoints
2
Standard model (GPT-4o)
Moderate complexity, debugging, refactoring
3
Reasoning model (Opus)
Architecture, complex logic, design decisions
Model selection strategy by task complexity
Agent.md Files for Consistency
Create agent.md at project root and in each subdirectory (backend, frontend) to document project conventions, tech stack, coding standards, and preferences. AI assistants automatically inject this into system prompts, ensuring consistent code style and architectural decisions across the codebase.
Dependency Minimization
Avoid bloated dependency trees. Pin exact versions and exclude packages <7 days old (supply-chain risk). Prefer built-in libraries or well-maintained alternatives. In Python, use Pydantic for validation, SQLAlchemy for ORM, Uvicorn for server. In TypeScript, use shadcn/ui (headless components) over full UI frameworks.
Context Window Management
When AI chat gets stuck or produces errors, start a fresh thread with a beefier model and higher reasoning. Provide clear error messages and ask for a plan before implementation. Use MCP servers (Railway, GitHub) to give AI structured access to tools instead of manual CLI commands.
Real-World Considerations & Next Steps
Hallucination & Grounding Trade-offs
Agentic search with validation is slower and more expensive (~$1–2 per query with GPT-4 Turbo) but prevents hallucinations. Naive single-pass retrieval is fast and cheap but risky for high-stakes use (investment analysis). Choose based on use case: internal tool = prioritize accuracy; consumer app = optimize speed/cost.
Query Classification & Routing
Not all queries need full agentic search. Implement query classifier to detect: simple factual questions (answer from system prompt), follow-ups (reuse previous context), deep analytical questions (full pipeline). Route accordingly to reduce latency and cost for simple queries.
Data Quality & Chunking Optimization
Current pipeline chunks at ~512 tokens with 50% overlap. Experiment with chunk size, overlap, and separator strategies. Monitor for duplicate chunks (ingestion pipeline may have run multiple times). Validate that retrieved chunks actually answer user questions. Iteratively refine based on real user feedback.
Security & Compliance for Production
Current setup is proof-of-concept. For production: (1) Use private VPC for database & backend. (2) Rotate API keys regularly; store in secret vault, not .env. (3) Implement audit logging (who queried what, when). (4) Add rate limiting & authentication per user. (5) Encrypt data at rest & in transit. (6) Comply with data residency (GDPR, etc.).
Iterative Improvement Loop
Deploy MVP to pilot group of analysts. Collect feedback: Which queries fail? Which are slow? Which hallucinate? Use real queries to benchmark and optimize. Adjust prompts, retrieval strategy, chunking, model selection. This feedback loop is where real AI engineering happens—not in the initial build.
Worth quoting
"This is engineering in the real world, so sometimes I make mistakes, I get stuck, I have to debug things."
— Dave Ebbelaar, at [2:35]
"If you don't have intermediate review points and decide what good looks like, it's going to get messy."
— Dave Ebbelaar, at [23:12]
"The beauty of this is that we're going to cover the entire architecture, something you rarely see on YouTube."
— Dave Ebbelaar, at [0:00]
Try this
Clone the main branch of the repository and set up a Python virtual environment with UV.
Create a free Supabase project, configure authentication (disable new signups), and store connection credentials in .env files.
Define SQLAlchemy ORM models for User, SourceDocument, DocumentChunk, ChatThread, ChatMessage, and MessageCitation tables.
Run Alembic migrations to sync Python models with remote PostgreSQL database.
Set up FastAPI backend with Pydantic settings, dependency injection for JWT validation, and chat endpoints (POST /chats, POST /chats/{thread_id}/messages, GET /chats).
Initialize Vite + React + TypeScript frontend with Supabase auth, Tailwind CSS, and shadcn/ui components.
Download SEC filings (HTM files) using the provided download.py script for Apple, Microsoft, Nvidia, Amazon, Google (5 years).
Parse HTM files to markdown using Dockling; implement custom HTML table extractor for clean table rendering.
Chunk markdown documents (~512 tokens, 50% overlap) and generate OpenAI embeddings; store in DocumentChunk table with pgvector.
Implement hybrid retrieval pipeline: keyword extraction (LLM), parallel vector + full-text search, result fusion and ranking.
Build Pydantic AI agent with tools (search_filings, read_chunks, read_surrounding_chunks) and agentic loop for multi-step reasoning.
Add grounding validator to ensure every citation references actual retrieved chunks and prevent hallucinations.
Wire frontend chat UI to backend API; display streaming status updates and clickable citations with source panel.
Create Dockerfiles for backend (FastAPI on port 8000) and frontend (Vite on port 3000); configure Caddy for frontend serving.
Deploy to Railway: link GitHub repo, configure environment variables, set region, and test backend/frontend connectivity.
Use Railway CLI (railway up) to redeploy after code changes; monitor logs for errors.
Manually create analyst users in Supabase dashboard and share credentials for pilot testing.
Collect user feedback on query accuracy, latency, and hallucinations; iterate on prompts, retrieval strategy, and chunking.
Implement query classification to route simple questions away from full agentic search (reduce cost/latency).
For production: add private VPC, secret vault for API keys, audit logging, rate limiting, encryption, and compliance checks.
Made with Glimpse by Wozart
glimpse.wozart.com/v/juyc8pfe
Share this infographic

More like this