পুনরুদ্ধার-অগমেন্টেড জেনারেশন (RAG) হল 2026 সালে উত্পাদন AI অ্যাপ্লিকেশন তৈরির জন্য প্রভাবশালী প্যাটার্ন। RAG বাস্তব ডেটাতে LLM প্রতিক্রিয়াগুলিকে ভিত্তি করে, ডোমেন জ্ঞানের উপর হ্যালুসিনেশন দূর করে এবং ব্যয়বহুল ফাইন-টিউনিং ছাড়াই আপনার AI আপ-টু-ডেট রাখে। এই গাইড স্ক্র্যাচ থেকে একটি উত্পাদন RAG সিস্টেম তৈরি করে।
📋 Table of Contents
কেন RAG?
এলএলএম-এর নলেজ কাটঅফ থাকে এবং আপনার ব্যক্তিগত ডেটা অ্যাক্সেস করতে পারে না। RAG উভয় সমস্যা সমাধান করে:
- তথ্যের উপর কোন হ্যালুসিনেশন নেই— পুনরুদ্ধার করা নথি থেকে মডেল উত্তর, মেমরি নয়
- ব্যক্তিগত তথ্য– আপনার নিজস্ব পিডিএফ, ডাটাবেস, উইকি সূচী করুন
- আপ-টু-ডেট উত্তর– নলেজ বেস আপডেট করুন, মডেল নয়
- ফাইন-টিউনিংয়ের চেয়ে সস্তা– কোন প্রশিক্ষণ খরচ, তাত্ক্ষণিক আপডেট
- উত্স অ্যাট্রিবিউশন– প্রতিটি উত্তরের জন্য কোন নথি ব্যবহার করা হয়েছে তা উল্লেখ করুন
RAG আর্কিটেকচার
RAG Pipeline:
[Your Documents]
↓ chunk + embed
[Vector Database] (Pinecone, Qdrant, ChromaDB)
↑ semantic search
[User Query] → embed → search → [Top-K Chunks]
↓
[LLM Prompt]
"Using these documents: {chunks}
Answer: {query}"
↓
[Grounded Answer]
সেটআপ: নির্ভরতা ইনস্টল করা
pip install anthropic chromadb sentence-transformers pypdf langchain langchain-community fastapi uvicorn python-dotenv
ধাপ 1: ডকুমেন্ট ইনজেশন
import anthropic
from pathlib import Path
from pypdf import PdfReader
import chromadb
from chromadb.utils import embedding_functions
# Initialize ChromaDB (local vector store)
client = chromadb.PersistentClient(path="./chroma_db")
# Use sentence-transformers for embeddings (free, local)
ef = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2" # fast, good quality, 384 dims
)
collection = client.get_or_create_collection(
name="documents",
embedding_function=ef
)
def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
# Split text into overlapping chunks.
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = " ".join(words[i:i + chunk_size])
chunks.append(chunk)
return chunks
def ingest_pdf(pdf_path: str) -> int:
# Ingest a PDF into the vector store.
reader = PdfReader(pdf_path)
all_chunks = []
metadatas = []
ids = []
for page_num, page in enumerate(reader.pages):
text = page.extract_text()
if not text.strip():
continue
chunks = chunk_text(text)
for i, chunk in enumerate(chunks):
chunk_id = f"{Path(pdf_path).stem}_p{page_num}_c{i}"
all_chunks.append(chunk)
metadatas.append({
"source": pdf_path,
"page": page_num + 1,
"chunk": i
})
ids.append(chunk_id)
# Add to ChromaDB
collection.add(documents=all_chunks, metadatas=metadatas, ids=ids)
return len(all_chunks)
# Ingest documents
print(f"Ingested: {ingest_pdf('company_docs.pdf')} chunks")
print(f"Ingested: {ingest_pdf('product_manual.pdf')} chunks")
ধাপ 2: পুনরুদ্ধার
def retrieve(query: str, n_results: int = 5) -> list[dict]:
# Retrieve relevant chunks for a query.
results = collection.query(
query_texts=[query],
n_results=n_results,
include=["documents", "metadatas", "distances"]
)
chunks = []
for doc, meta, dist in zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0]
):
chunks.append({
"text": doc,
"source": meta["source"],
"page": meta["page"],
"similarity": 1 - dist # convert distance to similarity
})
# Filter low-relevance chunks
return [c for c in chunks if c["similarity"] > 0.3]
ধাপ 3: ক্লডের সাথে প্রজন্ম
import anthropic
claude = anthropic.Anthropic() # uses ANTHROPIC_API_KEY env var
def rag_query(question: str, n_results: int = 5) -> dict:
# Answer a question using RAG.
# Retrieve relevant context
chunks = retrieve(question, n_results)
if not chunks:
return {
"answer": "I couldn't find relevant information in the documents.",
"sources": []
}
# Build context string
context = "
---
".join([
f"[Source: {c['source']}, Page {c['page']}]
{c['text']}"
for c in chunks
])
# Create prompt
system = (
"You are a helpful assistant that answers questions based ONLY on "
"the provided documents. If the answer is not in the documents, say so clearly. "
"Always cite the source document and page number for your answers."
)
user_message = (
f"Documents:
{context}
"
f"Question: {question}
"
"Answer based only on the provided documents, citing sources."
)
# Call Claude
response = claude.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": user_message}]
)
return {
"answer": response.content[0].text,
"sources": [{"source": c["source"], "page": c["page"]} for c in chunks],
"tokens_used": response.usage.input_tokens + response.usage.output_tokens
}
# Use it
result = rag_query("What is the return policy?")
print(result["answer"])
print("Sources:", result["sources"])
FastAPI RAG API
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="RAG API")
class QueryRequest(BaseModel):
question: str
n_results: int = 5
class QueryResponse(BaseModel):
answer: str
sources: list[dict]
tokens_used: int
@app.post("/query", response_model=QueryResponse)
async def query(request: QueryRequest):
if not request.question.strip():
raise HTTPException(400, "Question cannot be empty")
result = rag_query(request.question, request.n_results)
return QueryResponse(**result)
@app.post("/ingest")
async def ingest_document(file_path: str):
count = ingest_pdf(file_path)
return {"status": "ok", "chunks_indexed": count}
# Run: uvicorn main:app --reload
উন্নত RAG প্যাটার্নস
হাইব্রিড অনুসন্ধান (কীওয়ার্ড + শব্দার্থিক)
# Combine BM25 keyword search with vector search
from rank_bm25 import BM25Okapi
def hybrid_search(query: str, docs: list[str], alpha: float = 0.5) -> list[str]:
# Semantic search scores
semantic_results = collection.query(query_texts=[query], n_results=10)
semantic_scores = {doc: score for doc, score in
zip(semantic_results["ids"][0], semantic_results["distances"][0])}
# BM25 keyword scores
tokenized = [doc.split() for doc in docs]
bm25 = BM25Okapi(tokenized)
bm25_scores = bm25.get_scores(query.split())
# Combine (Reciprocal Rank Fusion)
combined = alpha * (1 - semantic_scores.get(id, 1)) + (1-alpha) * bm25_score
return sorted_by_combined_score
রির্যাঙ্কিং
# Use a cross-encoder to rerank retrieved chunks
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank(query: str, chunks: list[str], top_k: int = 3) -> list[str]:
pairs = [(query, chunk) for chunk in chunks]
scores = reranker.predict(pairs)
ranked = sorted(zip(chunks, scores), key=lambda x: x[1], reverse=True)
return [chunk for chunk, score in ranked[:top_k]]
উৎপাদন বিবেচনা
- চঙ্কিং কৌশল— খণ্ড আকার (200-1000 টোকেন), ওভারল্যাপ (10-20%) নিয়ে পরীক্ষা
- এমবেডিং মডেল— OpenAI টেক্সট-এম্বেডিং-3-গুণমানের জন্য ছোট, খরচের জন্য স্থানীয় মডেল
- ভেক্টর ডিবি— স্ব-হোস্টের জন্য ChromaDB/Qdrant, পরিচালনার জন্য Pinecone
- ক্যাশিং– ক্যাশে এম্বেডিং এবং ঘন ঘন ক্যোয়ারী ফলাফল
- মূল্যায়ন– RAG-নির্দিষ্ট মেট্রিক্সের জন্য RAGAS কাঠামো (বিশ্বস্ততা, প্রাসঙ্গিকতা)
- স্ট্রিমিং— আরও ভালো UX-এর জন্য ক্লডের প্রতিক্রিয়া স্ট্রিম করুন
RAG এখন 2026 সালে এন্টারপ্রাইজ AI-এর বেসলাইন প্যাটার্ন৷ একটি সাধারণ ChromaDB + Claude সেটআপ দিয়ে শুরু করুন, RAGAS দিয়ে উত্তরের গুণমান পরিমাপ করুন, তারপরে চঙ্কিং এবং পুনরুদ্ধার অপ্টিমাইজ করুন৷ ভেক্টর অনুসন্ধান এবং LLM যুক্তির সমন্বয় জ্ঞান-নিবিড় অ্যাপ্লিকেশনের জন্য অবিশ্বাস্যভাবে শক্তিশালী।
🔗 Share this article
✍️ Leave a Comment