Part 05 · Building LLM Applications
RAG (Retrieval-Augmented Generation)
Model এর memory থেকে না, তোমার নিজের document থেকে উত্তর: chunking, indexing, retrieve করা, result prompt এ বসানো, source দেখানো, আর index টা টাটকা রাখা।
⏱ 2-3 ঘণ্টা · Station 39 / 92
জিনিসটা কী, কেন দরকার
একটা সাধারণ model কে জিজ্ঞেস করো "How long does a bKash refund take?" সে জানতে পারে না: তোমার refund policy কখনো তার training data তে ছিল না, আর একই রকম কিছু থাকলেও সেটা পুরনো হতে পারে। তাই সে না বলে দেয়, বা আরো খারাপ, বিশ্বাসযোগ্য একটা সংখ্যা বানিয়ে বলে।
Retrieval-augmented generation (RAG) হলো বেশিরভাগ team যে সমাধান ব্যবহার করে: model answer দেওয়ার আগে তোমার নিজের document এ দরকারি অংশগুলো খোঁজো আর prompt এ বসিয়ে দাও, সাথে নির্দেশ যে শুধু ওগুলো থেকেই answer দিতে হবে আর প্রতিটা তথ্য কোন অংশ থেকে এলো সেটা cite করতে হবে। Model এর কাজ বদলে যায় "মনে করো" থেকে "পড়ো আর সারাংশ করো" তে। Answer হয় হালনাগাদ (document বদলালে পরের answer বদলায়), যাচাইযোগ্য (citation ধরে যাও) আর তোমার company র জন্য নির্দিষ্ট, কোনো training ছাড়াই।
ভেতরে কীভাবে চলে
Offline: indexing pipeline।
- Document load করো (policy, help article, manual, ticket)।
- সেগুলোকে টুকরো (chunk) করো, এমন অংশে যেগুলো নির্দিষ্ট হওয়ার মতো ছোট আর একা অর্থ বহন করার মতো বড়, সাধারণত 200 থেকে 800 token, প্রায়ই heading এ ভাগ করা, একটু overlap সহ।
- প্রতিটা chunk embed করো (এই part এর lesson 4) আর vector + text + metadata (উৎস, অংশ, তারিখ, permission) একটা vector database এ রাখো, সাথে hybrid search এর জন্য একটা keyword index।
Online: একটা প্রশ্নের answer দেওয়া।
- Retrieve: প্রশ্ন embed করো আর প্রথম k টা chunk খোঁজো (hybrid search আর reranking, lesson 5 আর 7)।
- Augment: chunk গুলোকে নম্বর দেওয়া source হিসেবে রেখে একটা prompt বানাও, সাথে পরিষ্কার নিয়ম: শুধু এই source ব্যবহার করো, cite করো, source এ answer না থাকলে "জানি না" বলো।
- Generate: LLM citation সহ answer লেখে; তোমার app পাশে source গুলো দেখাতে পারে।
RAG কোথায় ফেল করে, আর নিচে তিনটাই দেখবে:
- Retrieval এর ব্যর্থতা: সঠিক অংশটা prompt এ পৌঁছায়ই না (খারাপ chunking, ভুল search)। Model তখন যা পেয়েছে তা থেকেই answer দেয়।
- পড়ার ব্যর্থতা: সঠিক অংশটা আছে, আর model সেটা ভুল পড়ে।
- ভিত্তিহীন answer: প্রাসঙ্গিক কিছুই আসেনি, আর model "জানি না" না বলে answer দিয়েই দেয়।
এজন্যই retrieval আর answer আলাদা করে evaluate করা হয় (Part 8)।
একটা RAG bot answer দিল "bKash refunds take 24 hours", অথচ policy বলে 3 working days। Log দেখাচ্ছে 3 দিনের বাক্যটা retrieve হওয়া chunk গুলোর মধ্যে ছিল না। আগে কী ঠিক করবে?
ছবিতে দেখো
Code এ পুরো RAG
একটা ছোট policy document এর উপর পুরো একটা RAG loop, local embedding model আর local Qwen2.5-0.5B দিয়ে। চারটা প্রশ্নের answer RAG ছাড়া আর সহ। তারপর chunking বদলে বাক্যের দলের বদলে প্রতি section এ একটা chunk।
# Refund policy
Refunds for cancelled orders are returned to the original payment method. bKash and Nagad refunds arrive within 3 working days. Card refunds can take up to 7 working days. Cash on delivery orders are refunded as app credit within 24 hours.
# Late delivery
If an order arrives more than 45 minutes after the promised time, the customer gets a 100 taka voucher automatically. During heavy rain the promised time is extended by 20 minutes and no voucher is given for that extra time.
# Missing or wrong items
Report a missing or wrong item within 2 hours of delivery from the order page. Attach a photo for wrong items. Confirmed reports get a full refund for that item as app credit.
# Rider safety
Riders are not allowed to enter apartment buildings after 10 pm. Customers must collect the order at the gate after 10 pm.
# Account
You can have one account per phone number. To change your number, go to Profile, then Settings, and verify the new number with an OTP.
"""Retrieval-augmented generation: chunk, embed, retrieve, then answer only from the retrieved text."""
import re
import numpy as np
from embed import embed
from llm import chat
# 1) Indexing (offline): split the document into chunks, embed every chunk once.
sections = re.split(r"\n(?=# )", open("policies.md").read().strip())
chunks = []
for sec in sections:
title, body = sec.split("\n", 1)
for sentence_group in re.split(r"(?<=\.) (?=[A-Z])", body.strip()):
chunks.append({"title": title.lstrip("# "), "text": sentence_group})
vectors = embed([f"{c['title']}: {c['text']}" for c in chunks])
print(f"indexed {len(chunks)} chunks from {len(sections)} sections")
def retrieve(question, k=3):
scores = vectors @ embed([question], kind="query")[0]
return [chunks[i] | {"score": float(scores[i])} for i in np.argsort(-scores)[:k]]
SYSTEM = ("You are the support assistant of a Dhaka food delivery app. Answer the question using ONLY the "
"numbered sources. Cite sources like [1]. If the sources don't contain the answer, say you don't know.")
def answer(question, use_rag=True):
if not use_rag:
return chat([{"role": "system", "content": "You are the support assistant of a Dhaka food delivery app."},
{"role": "user", "content": question}], max_new_tokens=70)
hits = retrieve(question)
sources = "\n".join(f"[{i + 1}] ({h['title']}) {h['text']}" for i, h in enumerate(hits))
for i, h in enumerate(hits):
print(f" retrieved [{i + 1}] {h['score']:.3f} {h['title']}: {h['text'][:60]}")
return chat([{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Sources:\n{sources}\n\nQuestion: {question}"}], max_new_tokens=70)
for q in ["How long does a bKash refund take?",
"My food came 50 minutes late. Do I get anything?",
"Can the rider come up to my flat at 11 pm?",
"Do you deliver alcohol?"]:
print(f"\nQ: {q}")
print(f" without RAG: {answer(q, use_rag=False)}")
print(f" with RAG: {answer(q)}")
# Chunking matters: the bKash sentence never made the top 3 above. Try one chunk per section.
print("\n--- one chunk per section instead of per sentence ---")
chunks = [{"title": s.split("\n", 1)[0].lstrip("# "), "text": s.split("\n", 1)[1].strip()} for s in sections]
vectors = embed([f"{c['title']}: {c['text']}" for c in chunks])
q = "How long does a bKash refund take?"
print(f"Q: {q}")
print(f" with RAG: {answer(q)}")
Output:
indexed 12 chunks from 5 sections
Q: How long does a bKash refund take?
without RAG: I'm sorry, but I can't assist with that.
retrieved [1] 0.908 Refund policy: Refunds for cancelled orders are returned to the original pa
retrieved [2] 0.853 Refund policy: Cash on delivery orders are refunded as app credit within 24
retrieved [3] 0.845 Refund policy: Card refunds can take up to 7 working days.
with RAG: bKash refunds take up to 24 hours.
Q: My food came 50 minutes late. Do I get anything?
without RAG: If your food arrived 50 minutes late, you may be eligible for a refund or compensation from the delivery service provider. Here are some steps to consider:
1. **Check Your Receipt**: Ensure that your receipt shows the time of arrival and any additional charges (if applicable).
2. **Contact the Delivery Service Provider**: Reach out to the delivery
retrieved [1] 0.862 Late delivery: If an order arrives more than 45 minutes after the promised
retrieved [2] 0.847 Late delivery: During heavy rain the promised time is extended by 20 minute
retrieved [3] 0.842 Missing or wrong items: Report a missing or wrong item within 2 hours of delivery fr
with RAG: If your food arrived 50 minutes late, you would not receive any vouchers as per the information provided in the sources.
Q: Can the rider come up to my flat at 11 pm?
without RAG: As an AI language model, I cannot provide specific information about your location or the availability of your apartment. However, it is generally not advisable for riders to attempt to enter someone else's home at night without permission from the owner or their legal representatives.
In most cases, landlords have strict rules regarding visitors and may require proof of residency or other forms of
retrieved [1] 0.855 Rider safety: Riders are not allowed to enter apartment buildings after 10
retrieved [2] 0.850 Rider safety: Customers must collect the order at the gate after 10 pm.
retrieved [3] 0.808 Late delivery: During heavy rain the promised time is extended by 20 minute
with RAG: No, the rider cannot come up to your flat at 11 pm. According to the information provided in the sources:
- Source [1]: "Riders are not allowed to enter apartment buildings after 10 pm."
- Source [2]: "Customers must collect the order at the gate after 10 pm."
These sources indicate that
Q: Do you deliver alcohol?
without RAG: As an AI language model, I do not have access to real-time information about specific delivery services or their policies regarding alcohol consumption. However, it is important for individuals to be aware of the laws and regulations surrounding alcohol consumption in different regions and countries.
In general, many cities and towns have laws that prohibit the sale of alcoholic beverages to minors, as
retrieved [1] 0.794 Late delivery: If an order arrives more than 45 minutes after the promised
retrieved [2] 0.789 Late delivery: During heavy rain the promised time is extended by 20 minute
retrieved [3] 0.788 Refund policy: Cash on delivery orders are refunded as app credit within 24
with RAG: No, I do not deliver alcohol.
--- one chunk per section instead of per sentence ---
Q: How long does a bKash refund take?
retrieved [1] 0.915 Refund policy: Refunds for cancelled orders are returned to the original pa
retrieved [2] 0.828 Missing or wrong items: Report a missing or wrong item within 2 hours of delivery fr
retrieved [3] 0.804 Late delivery: If an order arrives more than 45 minutes after the promised
with RAG: bKash refunds arrive within 3 working days.
কী দেখলাম:
- RAG ছাড়া model সাহায্য করতে পারে না: bKash এর প্রশ্নে না বলে দেয়, দেরির order এ সাধারণ পরামর্শ দেয় ("check your receipt"), আর বাড়িওয়ালা আর মদের আইন নিয়ে কথা বলে। কোনোটাই তোমার policy না।
- রাত 11 টায় rider: RAG যেভাবে কাজ করার কথা। Rider safety র দুইটা chunk ই আসে, আর answer [1] আর [2] দিয়ে সেগুলো উদ্ধৃত করে।
- bKash refund: retrieval এর ব্যর্থতা। বাক্য ভিত্তিক chunking "bKash and Nagad refunds arrive within 3 working days" কে আলাদা chunk এ রেখেছে, আর সেই chunk প্রথম 3 এ আসেনি। Model যে cash-on-delivery chunk টা পেয়েছে সেখান থেকে বলেছে "24 hours"। আত্মবিশ্বাসী, citation ওয়ালা মনে হয়, ভুল।
- 50 মিনিট দেরি: পড়ার ব্যর্থতা। সঠিক chunk টা প্রথমেই এসেছে ("more than 45 minutes ... 100 taka voucher"), আর model সিদ্ধান্ত নিয়েছে "you would not receive any vouchers"। 0.5B model এরকম তুলনায় দুর্বল; বড় model অনেক কম ভুল পড়ে, কিন্তু একেবারে কখনো না, তা না।
- Alcohol: ভিত্তিহীন answer। প্রাসঙ্গিক কিছুই আসেনি (সর্বোচ্চ score 0.794, সবই অপ্রাসঙ্গিক), আর "জানি না" না বলে model বলেছে "No"। শুনতে ঠিক মনে হয়; কোনো source এ এর সমর্থন নেই।
- Chunking এর সমাধান: প্রতি section এ একটা chunk হলে পুরো refund policy source [1] হিসেবে আসে, আর answer হয় "bKash refunds arrive within 3 working days"। একই model, একই prompt; বদলেছে শুধু chunking।
Production এ যা মাথায় রাখবে
- গঠন ধরে chunk করো। Heading আর paragraph এ ভাগ করো, একটা section এর নিয়ম একসাথে রাখো, প্রতিটা chunk এ document আর section এর title যোগ করো, আর একটু overlap রাখো। আসল প্রশ্নে chunk এর সাইজ test করো।
- Relevance threshold রাখো। সেরা chunk এর score তার নিচে হলে generation বাদ দাও আর বলো "help centre এ এটা পাইনি" (বা একজন মানুষের কাছে পাঠাও)। Alcohol এর প্রশ্নটা এতে ধরা পড়ত।
- Citation check করো। প্রতিটা cite করা source আছে কিনা যাচাই করো, আর জরুরি তথ্যের (টাকা, দিন) জন্য, answer এর সংখ্যাটা cite করা chunk এ আছে কিনা (Part 8 faithfulness check দেখায়)।
- Index হালনাগাদ রাখো। বদলানো document নিজে থেকে আবার embed করো, আর প্রতিটা chunk এর উৎসের version আর তারিখ রাখো, যাতে বলতে পারো কোন answer কোন policy version ব্যবহার করেছে।
- Retrieval এর সময়েই permission মানো। Prompt এ পৌঁছানোর আগে কে জিজ্ঞেস করছে দেখে chunk ছাঁকো (Part 11)।
- Context এর সাইজ চোখে রাখো। বেশি chunk মানে বেশি token, খরচ আর latency, আর model এর জন্য বেশি বিভ্রান্তি। বেশি তোলো, rerank করো, আর শুধু সেরা কয়েকটা পাঠাও।
- প্রতিটা answer এর সাথে retrieve হওয়া chunk log করো। এগুলো ছাড়া retrieval এর ব্যর্থতা আর পড়ার ব্যর্থতা আলাদা করতে পারবে না।
এক কথায়
RAG offline এ তোমার document chunk আর embed করে, তারপর প্রতিটা প্রশ্নে সবচেয়ে কাছের chunk গুলো আনে, নম্বর দেওয়া source হিসেবে prompt এ বসায়, আর model কে শুধু সেগুলো থেকে citation সহ answer দিতে বলে; ফেল করে যখন সঠিক chunk আসে না, যখন model সেটা ভুল পড়ে, বা যখন সমর্থন ছাড়াই answer দেয়, তাই যত্ন করে chunk করো, relevance threshold রাখো আর কী retrieve হলো log করো।
নিজে করে দেখো
answer এ একটা relevance threshold যোগ করো: সেরা retrieve হওয়া score 0.80 এর নিচে হলে LLM না ডেকেই ফেরত দাও "I couldn't find that in our policies."। চারটা প্রশ্নের কোনটা এতে বদলায়, আর threshold বেশি উঁচু রাখার ঝুঁকি কী?
Answer
Alcohol এর প্রশ্নের সেরা score 0.794, তাই এটা এখন সৎ "couldn't find" reply পায়। বাকি তিনটার সর্বোচ্চ score 0.855 বা বেশি, তাই প্রভাব পড়ে না। Threshold বেশি উঁচু করলে (ধরো 0.86), একটু অন্যভাবে বলা আসল প্রশ্নও, যেমন 0.855 এর rider এর প্রশ্ন, বাদ পড়ে যায়। Threshold নির্ভর করে embedding model এর score এর ঘরের উপর (E5 এর score উঁচুতে থাকে), তাই আন্দাজে না, answer দেওয়া যায় আর যায় না এমন প্রশ্নের label দেওয়া একটা set এ tune করো।
Station stamp
Chapter শেষ করো
পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।
Pro নিয়ে XP পাও →