Skip to content

Episode 14 – AI Document Analyzer: Chat with Your PDFs (RAG + Qdrant + React)

Choose a tag to compare

@sandeeppatil-newvision sandeeppatil-newvision released this 17 Oct 16:26
· 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 number
    • ChunkPages — ~1000-character chunks with 200-character overlap, ending on a sentence where possible
    • IngestPdfAsync — batch embeddings (16 chunks per call) → upsert into Qdrant
    • EnsureCollectionAsync — creates the pdf-documents collection (1536 dims, cosine) + documentId payload index
    • SearchAsync — vector search filtered to one document (MatchKeyword("documentId", id))
    • StreamAnswerAsync — grounded prompt: answer only from sources, cite [p. n], refuse when not found
    • DeleteAsync — 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 — registers DocumentService as a singleton
  • appsettings.json — new setting Qdrant:PdfCollectionName = pdf-documents

Frontend — dotnetai-chat (React + Vite)

  • src/api/docApi.js (new) — upload, list, delete and a stream reader for { type, ... } events
  • src/DocumentsPage.jsx + src/Documents.css (new) — upload panel, document list, per-document chat, page-citation chips, Stop button
  • src/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/qdrant

The .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 MatchKeyword isn't found, add using 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.