Repository navigation
Episode 14 – AI Document Analyzer: Chat with Your PDFs (RAG + Qdrant + React)
·
10 commits
to series/dotnet-ai
since this release
📄 Episode 14 – AI Document Analyzer: Chat with Your PDFs
Upload any PDF, ask questions about it, and get streamed answers with page citations — built with .NET, Azure OpenAI, Qdrant and React.
If the answer isn't in the document, the AI says so: "I couldn't find that in this document."
🎥 Watch the episode:
📺 Full series playlist:
🚀 What's New in This Release
Backend — DotNetAI.API
- PdfPig added — reads PDF text page by page in pure .NET
Services/DocumentService.cs(new)ExtractPages— PDF → text, keeping each page numberChunkPages— ~1000-character chunks with 200-character overlap, ending on a sentence where possibleIngestPdfAsync— batch embeddings (16 chunks per call) → upsert into QdrantEnsureCollectionAsync— creates thepdf-documentscollection (1536 dims, cosine) +documentIdpayload indexSearchAsync— vector search filtered to one document (MatchKeyword("documentId", id))StreamAnswerAsync— grounded prompt: answer only from sources, cite[p. n], refuse when not foundDeleteAsync— removes the document and all its vectors
Controllers/DocumentsController.cs(new)
| Method | Route | Purpose |
|---|---|---|
| POST | /api/Documents |
Upload a PDF (multipart/form-data, max 20 MB) |
| GET | /api/Documents |
List uploaded documents |
| DELETE | /api/Documents/{id} |
Delete a document and its vectors |
| POST | /api/Documents/{id}/ask/stream |
Ask a question — SSE stream: sources event, then token events, then [DONE] |
Program.cs— registersDocumentServiceas a singletonappsettings.json— new settingQdrant:PdfCollectionName=pdf-documents
Frontend — dotnetai-chat (React + Vite)
src/api/docApi.js(new) — upload, list, delete and a stream reader for{ type, ... }eventssrc/DocumentsPage.jsx+src/Documents.css(new) — upload panel, document list, per-document chat, page-citation chips, Stop buttonsrc/Root.jsx+src/Root.css(new) — 💬 Chat / 📄 Documents tabs (each tab keeps its state)src/main.jsx— now renders<Root />instead of<App />
🧠 How It Works
⚙️ Prerequisites
- .NET 10 SDK, Node.js (for the React app)
- Azure OpenAI deployments: gpt-4.1 (chat) and text-embedding-ada-002 (embeddings)
- Qdrant running locally:
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrantThe .NET client uses gRPC on 6334; the dashboard is at http://localhost:6333/dashboard
- Keys set with user-secrets (never commit them):
dotnet user-secrets set "AzureOpenAI:ApiKey" "<your-key>"▶️ Run It
# API
cd DotNetAI.API
dotnet run # https://localhost:7036/scalar
# React app
cd dotnetai-chat
npm install
npm run dev # http://localhost:5173 → Documents tab🧪 Try These Questions (sample handbook PDF)
- "How many days of annual leave do I get?" → 24 days [p. 3]
- "What's the hotel limit when I travel?" → ₹6,000 per night [p. 4]
- "What is the CEO's salary?" → "I couldn't find that in this document."
📝 Notes
- The document list is kept in memory and resets when the API restarts; the vectors stay in Qdrant. Saving to a database comes in Phase 4.
- Scanned (image-only) PDFs have no text layer and return a clear 400 error — OCR is out of scope for this episode.
- If
MatchKeywordisn't found, addusing static Qdrant.Client.Grpc.Conditions;
⏭️ Next Episode
AI Red Teaming — attacking this app with prompt injection hidden inside a PDF, and defending against it.
⭐ If this helped, star the repo and subscribe to Sandeep | AI Solutions (@Aiwithsandeep) for the rest of the series.