惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
MongoDB | Blog
MongoDB | Blog
腾讯CDC
V
V2EX
Martin Fowler
Martin Fowler
A
About on SuperTechFans
大猫的无限游戏
大猫的无限游戏
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
博客园 - 【当耐特】
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
K
Kaspersky official blog
Security Latest
Security Latest
H
Help Net Security
博客园_首页
美团技术团队
Spread Privacy
Spread Privacy
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
S
SegmentFault 最新的问题
G
Google Developers Blog
NISL@THU
NISL@THU
爱范儿
爱范儿
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic
P
Privacy International News Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
S
Security @ Cisco Blogs
Schneier on Security
Schneier on Security
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
P
Palo Alto Networks Blog
G
GRAHAM CLULEY
Hacker News: Ask HN
Hacker News: Ask HN
I
InfoQ
The Cloudflare Blog
F
Full Disclosure
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
N
Netflix TechBlog - Medium

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building a RAG-Based PDF Question Answering System: Engineering Decisions, Failures, and Lessons
Sanjana medidi · 2026-06-25 · via DEV Community
  • A technical deep-dive into StudyMate AI — a Retrieval-Augmented Generation system built with LangChain, FAISS, HuggingFace, and Groq. *

As an AI/ML student preparing applications for research internships at companies like Google, I wanted to build something that went beyond the typical classifier or fine-tuning demo. I wanted a project that demonstrated systems thinking — not just model calling. The result was StudyMate AI: a RAG pipeline that lets you upload any PDF and ask questions about it, grounded strictly in the document's content.

This post documents the real engineering decisions I made, the problems I ran into, and what I learned — including the parts that didn't work the first time.


What is RAG and Why Use It?

Retrieval-Augmented Generation (RAG) is a pattern where instead of asking an LLM to answer from memory, you first retrieve relevant context from a knowledge source and inject it into the prompt. This means:

  • The model only answers from what's in your document
  • You get source attribution
  • Hallucinations are dramatically reduced

The alternative — fine-tuning an LLM on your documents — is expensive, slow, and overkill for a single-document use case. RAG was the right architectural choice here.


System Architecture

PDF
 ↓
PyPDFLoader
 ↓
RecursiveCharacterTextSplitter  (chunk_size=800, overlap=100)
 ↓
HuggingFace Embeddings (all-MiniLM-L6-v2)  — runs locally
 ↓
FAISS Vector Store  (in-memory)
 ↓
Custom Two-Stage Retriever
(first_page_chunks + summary chunks + content chunks + broad search)
 ↓
Groq LLM  (llama-3.1-8b-instant)
 ↓
Answer


Engineering Decision 1: HuggingFace Embeddings over OpenAI

The first decision was how to embed the document chunks. OpenAI's embedding API is the popular choice, but it's pay-per-token — during development and testing, that cost accumulates quickly.

HuggingFace's all-MiniLM-L6-v2 runs locally on your machine, costs nothing, and requires no API key. For a single-user, single-PDF system, the performance tradeoff is negligible. This is the kind of decision that matters at scale — choosing the right tool for the actual constraints, not the most popular one.


Engineering Decision 2: FAISS over Hosted Vector Databases

Pinecone and Weaviate are the production choices for vector storage. They offer persistence across sessions, horizontal scaling, and multi-user support. None of that was needed here.

FAISS runs in-memory with zero setup cost. For one user processing one PDF at a time, it's the correct tradeoff. The rule I applied: use the simplest thing that satisfies your actual constraints. Reach for hosted infrastructure when you need persistence, concurrency, or datasets too large for memory — not before.


Engineering Decision 3: Migrating from RetrievalQA to LCEL

During development I noticed LangChain had deprecated RetrievalQA. Rather than ignore the warning and ship deprecated code, I migrated to the current LCEL (LangChain Expression Language) chain composition pattern.

The old approach was a black box. The new approach is explicit:

retrieval_chain = (
    RunnablePassthrough.assign(
        context=RunnableLambda(retrieve_with_summary) | format_docs
    )
    | prompt
    | llm
)

Every step is visible — retrieval, formatting, prompting, generation. This matters for debugging and for understanding what the system is actually doing.


The Problem Vanilla RAG Can't Solve

This is where it got interesting.

After building the basic pipeline, I tested it with: "What is the main purpose of this document?"

The response: "I cannot find the answer in the provided documents."

But the document's purpose was clearly stated in the abstract. What went wrong?

Similarity search surfaces locally similar chunks — chunks whose text is semantically close to the query. A query about "purpose" doesn't semantically match individual chunks about methodology or findings, even though the answer exists in the document.

Vanilla RAG is optimized for specific factual questions. Document-level questions — purpose, thesis, overview — require a global view of the document that chunk-level retrieval can't provide.


The Fix: Pre-Generated Summaries + First-Page Pinning

I solved this with two additions:

1. Pre-generated summary chunks

At build time, before any user query, I generate 5 targeted summaries from the first 2,500 characters of the document and store them as special chunks in the vector store:

summaries_to_create = {
    "research_question": "What is the exact research question?",
    "methodology":       "Describe the methodology in one sentence.",
    "findings":          "What are the main findings in one sentence?",
    "conclusions":       "What are the conclusions in one sentence?",
    "limitations":       "What are the limitations in one sentence?"
}

These give the retriever a global view of the document that similarity search alone can't provide.

2. First-page pinning

Pages 0 and 1 of any academic document almost always contain the abstract and introduction — where purpose and topic live. I pin these as always-included context regardless of the query:

def retrieve_with_summary(inputs):
    query = inputs["input"] if isinstance(inputs, dict) else inputs

    summary_results    = vector_store.similarity_search(query, k=2, filter={"chunk_type": "summary"})
    content_results    = vector_store.similarity_search(query, k=3, filter={"chunk_type": "content"})
    broad_results      = vector_store.similarity_search(query, k=2)
    first_page_chunks  = [c for c in chunks if c.metadata.get("page", 99) in (0, 1)]

    seen, all_docs = set(), []
    for doc in first_page_chunks + summary_results + content_results + broad_results:
        if doc.page_content not in seen:
            seen.add(doc.page_content)
            all_docs.append(doc)
    return all_docs

After this fix, document-level questions worked correctly.


Hitting Groq's Rate Limit — and Designing Around It

Groq's free tier allows 6,000 tokens per minute (TPM). My initial implementation used ThreadPoolExecutor with multiple workers to generate summaries in parallel. The result: all 5 API calls fired within milliseconds of each other, consuming ~5,000 tokens in one second and triggering a 429 error immediately.

Rate limit reached: Limit 6000, Used 5881, Requested 3378.
Please try again in 32.59s.

This is a real distributed systems constraint — and solving it required thinking about the problem like a systems engineer, not just a model user.

Solution:

  • max_workers=1 — sequential generation eliminates the burst
  • time.sleep(35) between calls — 35s gives a safe buffer above the 32.59s reset window
  • Input capped at [:2500] characters — keeps each prompt to ~150 tokens, so 5 summaries stay well within the TPM limit

The tradeoff is ~3 minutes of startup time on the free tier. On Groq's Dev tier (30,000 TPM), the sleep can be removed entirely and workers restored — startup drops to under 10 seconds.


Hallucination Guardrail

The system prompt strictly instructs the model to refuse answering if the context doesn't support it:

Strict Rules:
1. Rely ONLY on the clear facts directly mentioned in the context.
2. Do NOT assume, extrapolate, or bring in outside knowledge.
3. If the context does not contain the answer, reply exactly:
   "I cannot find the answer in the provided documents."

Test result with an out-of-scope question:

Q: What is the capital of France?

I cannot find the answer in the provided documents.

Source 1 — The EUROCALL Review, Volume 25, No. 2, September 2017...
Source 2 — ...research question, description of participants...
Source 3 — ...referred their students to electronic or online resources...

The system correctly refuses rather than hallucinating, and returns the chunks it did find — making the reasoning transparent.


What I Learned

Vanilla RAG is not enough for document-level questions. Chunk similarity search is optimized for factual, specific queries. Any question requiring a global view of the document — purpose, thesis, summary — needs a separate strategy: pre-summarization, large-k retrieval, or a dedicated summary index.

Rate limits are a systems design problem. The solution isn't just adding a sleep — it's understanding the constraint (TPM budget), calculating the safe parameters (tokens per call × calls per minute), and designing the pipeline around them.

Read the deprecation warnings. RetrievalQA and langchain-community both flagged deprecation during development. Ignoring them is technical debt. Evaluating them — deciding when to migrate and when to defer — is engineering judgment.


Stack

Component Choice
Frontend Streamlit
LLM Groq (llama-3.1-8b-instant)
Embeddings HuggingFace all-MiniLM-L6-v2
Vector Store FAISS
Chain LangChain LCEL

Links


Built as part of my AI/ML portfolio while preparing research internship applications. Feedback welcome.