বড় ভাষা মডেল (LLMs) যেমন GPT-4 এবং Claude অসাধারণভাবে শক্তিশালী, কিন্তু তারা একটি মৌলিক সীমাবদ্ধতায় ভোগে: তাদের জ্ঞান প্রশিক্ষণের সময়ে স্থির হয়ে যায়। তারা আপনার অভ্যন্তরীণ নথি, আপনার ডাটাবেস বা রিয়েল-টাইম তথ্য অ্যাক্সেস করতে পারে না। রিট্রিভাল-অগমেন্টেড জেনারেশন (RAG) ঠিক এই সমস্যাটি সমাধান করে, LLM-এর জেনারেটিভ ক্ষমতার সাথে বাহ্যিক উৎস থেকে তথ্য পুনরুদ্ধারের ক্ষমতা সংযুক্ত করে।
সমস্যা: LLM-এর সীমাবদ্ধতা
RAG নিয়ে কথা বলার আগে, আমাদের কেন এটি প্রয়োজন তা বোঝা গুরুত্বপূর্ণ।
- স্থির জ্ঞান: একটি LLM শুধুমাত্র সেটাই জানে যা সে প্রশিক্ষণের সময় দেখেছে। আপনি যদি এর কাটঅফের পরে ঘটে যাওয়া কোনো ঘটনা সম্পর্কে জিজ্ঞেস করেন, এটি উত্তর দিতে পারে না।
- হ্যালুসিনেশন: যখন একটি LLM উত্তর জানে না, তখন এটি একটি তৈরি করার প্রবণতা রাখে, বিশ্বাসযোগ্য কিন্তু সম্পূর্ণ মিথ্যা তথ্য তৈরি করে।
- ব্যক্তিগত ডেটায় অ্যাক্সেস নেই: একটি সাধারণ LLM-এর আপনার কোম্পানির অভ্যন্তরীণ ডকুমেন্টেশন, টিকেট বা কোডবেসে অ্যাক্সেস নেই।
RAG এই তিনটি সমস্যারই সমাধান করে, কুয়েরির সময় বাহ্যিক উৎস থেকে পুনরুদ্ধার করা প্রাসঙ্গিক প্রসঙ্গ মডেলকে প্রদান করে।
RAG কী?
রিট্রিভাল-অগমেন্টেড জেনারেশন হলো একটি আর্কিটেকচার যা একটি বাহ্যিক জ্ঞান ভিত্তি থেকে পুনরুদ্ধার করা তথ্য দিয়ে LLM-এ পাঠানো প্রম্পটকে সমৃদ্ধ করে। মডেলের প্যারামেট্রিক জ্ঞানের উপর সম্পূর্ণভাবে নির্ভর করার পরিবর্তে, RAG প্রথমে প্রাসঙ্গিক তথ্য অনুসন্ধান করে এবং তারপর সেটি প্রম্পটে ইনজেক্ট করে, মডেলকে সঠিক, ভিত্তিযুক্ত প্রতিক্রিয়া তৈরি করতে সক্ষম করে।
RAG কীভাবে বিস্তারিতভাবে কাজ করে
RAG আর্কিটেকচার দুটি প্রধান ধাপ নিয়ে গঠিত: ইনডেক্সিং (অফলাইন) এবং রিট্রিভাল + জেনারেশন (অনলাইন)।
ধাপ ১: ইনডেক্সিং (ডকুমেন্ট ইনজেশন)
ইনডেক্সিং ধাপ আপনার নথিগুলোকে সেমান্টিক সার্চের জন্য প্রস্তুত করে। এটি চারটি পদক্ষেপ নিয়ে গঠিত।
১. ডকুমেন্ট লোডিং
নথিগুলো যেকোনো উৎস থেকে আসতে পারে: PDF ফাইল, ওয়েব পেজ, ডাটাবেস, Markdown ফাইল, API। Document Loader এই নথিগুলো পড়ে এবং কাঠামোবদ্ধ টেক্সটে রূপান্তর করে।
২. টেক্সট বিভাজন (Chunking)
LLM-গুলোর সীমিত কনটেক্সট উইন্ডো আছে, এবং নথিগুলো খুব দীর্ঘ হতে পারে। Text Splitter নথিগুলোকে chunks নামক ছোট খণ্ডে ভাগ করে। Chunking-এর মান অত্যন্ত গুরুত্বপূর্ণ: অত্যন্ত ছোট chunks প্রসঙ্গ হারায়, আবার অত্যন্ত বড় chunks প্রাসঙ্গিকতা কমিয়ে দেয়।
সবচেয়ে সাধারণ কৌশলগুলো হলো:
- রিকার্সিভ ক্যারেক্টার স্প্লিটিং:
\n\n,\n,.এর মতো বিভাজক ব্যবহার করে পুনরাবৃত্তিমূলকভাবে টেক্সট বিভক্ত করে, নথির কাঠামো সম্মান করে। - সেমান্টিক স্প্লিটিং: টেক্সটে প্রাকৃতিক ব্রেকপয়েন্ট খুঁজতে embeddings ব্যবহার করে।
- Chunk ওভারল্যাপ: সীমানায় প্রসঙ্গ সংরক্ষণ করতে পরপর chunks-এর মধ্যে ওভারল্যাপ অন্তর্ভুক্ত করে।
৩. Embedding
প্রতিটি chunk একটি embedding মডেলের (যেমন OpenAI-এর text-embedding-3-small) মাধ্যমে একটি সংখ্যাসূচক ভেক্টরে (embedding) রূপান্তরিত হয়। এই ভেক্টরগুলো টেক্সটের সেমান্টিক অর্থ ধারণ করে: একই রকম অর্থযুক্ত বাক্যগুলোর ভেক্টর বহুমাত্রিক স্থানে কাছাকাছি থাকবে।
৪. Vector Store
ভেক্টরগুলো একটি Vector Store-এ (বা ভেক্টর ডাটাবেস) সংরক্ষিত হয়, যেমন ChromaDB, Pinecone, Weaviate বা FAISS। এই ডাটাবেস সাদৃশ্য অনুসন্ধানের জন্য অপ্টিমাইজড: একটি কুয়েরি দেওয়া হলে, এটি সবচেয়ে সাদৃশ্যপূর্ণ ভেক্টর (এবং তাই সবচেয়ে প্রাসঙ্গিক টেক্সট chunks) খুঁজে বের করে।
ধাপ ২: রিট্রিভাল + জেনারেশন
যখন ব্যবহারকারী একটি প্রশ্ন করেন:
- প্রশ্নটি একই embedding মডেল ব্যবহার করে একটি embedding-এ রূপান্তরিত হয়।
- Vector Store সাদৃশ্য অনুসন্ধানের মাধ্যমে সবচেয়ে সাদৃশ্যপূর্ণ chunks খুঁজে বের করে (সাধারণত কোসাইন সাদৃশ্য বা ইউক্লিডীয় দূরত্ব)।
- পুনরুদ্ধার করা chunks প্রসঙ্গ হিসেবে প্রম্পটে ঢোকানো হয়।
- LLM প্রদত্ত প্রসঙ্গের ভিত্তিতে একটি প্রতিক্রিয়া তৈরি করে।
LangChain দিয়ে একটি RAG পাইপলাইন তৈরি
LangChain হলো LLM-চালিত অ্যাপ্লিকেশন তৈরির জন্য সবচেয়ে জনপ্রিয় Python (এবং JavaScript) ফ্রেমওয়ার্ক। এটি RAG পাইপলাইনের প্রতিটি উপাদানের জন্য উচ্চ-স্তরের অ্যাবস্ট্রাকশন প্রদান করে।
ইনস্টলেশন
pip install langchain langchain-openai langchain-community chromadb
পদক্ষেপ ১: ডকুমেন্ট লোড করুন
LangChain বিভিন্ন ডেটা সোর্সের জন্য ডজনখানেক Document Loader প্রদান করে।
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # Load a PDF pdf_loader = PyPDFLoader("docs/manual.pdf") pdf_docs = pdf_loader.load() # Load a web page web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # Load all .md files from a directory dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
পদক্ষেপ ২: ডকুমেন্টগুলোকে Chunks-এ ভাগ করুন
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"Original documents: {len(all_docs)}, Chunks: {len(chunks)}")
chunk_overlap প্যারামিটারটি অত্যন্ত গুরুত্বপূর্ণ: এটি পরপর chunks-এর মধ্যে ওভারল্যাপ তৈরি করে যাতে সীমানায় প্রসঙ্গ হারিয়ে না যায়।
পদক্ষেপ ৩: Embeddings এবং Vector Store তৈরি করুন
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
পদক্ষেপ ৪: Retriever তৈরি করুন
Retriever হলো সেই উপাদান যা, একটি কুয়েরি দেওয়া হলে, vector store থেকে সবচেয়ে প্রাসঙ্গিক chunks সংগ্রহ করে।
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("How does authentication work?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
পদক্ষেপ ৫: RAG চেইন তৈরি করুন
এখন একটি LLM এবং একটি প্রম্পট টেমপ্লেটের সাথে সবকিছু একসাথে যুক্ত করা যাক।
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" Answer the question based only on the provided context. If the context does not contain enough information, say you don't know. Context: {context} Question: {question} Answer: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("How does authentication work in the system?") print(response)
উন্নত RAG কৌশল
বেসিক পাইপলাইন ভালো কাজ করে, কিন্তু প্রতিক্রিয়ার মান উল্লেখযোগ্যভাবে উন্নত করতে বেশ কিছু কৌশল রয়েছে।
মাল্টি-কুয়েরি রিট্রিভাল
কখনো কখনো ব্যবহারকারীর কুয়েরি অস্পষ্ট হয় বা নথিতে ব্যবহৃত ভাষার সাথে সামঞ্জস্যপূর্ণ নয়। Multi-Query Retriever স্বয়ংক্রিয়ভাবে মূল প্রশ্নের বিভিন্ন সংস্করণ তৈরি করে বিভিন্ন দৃষ্টিভঙ্গি ক্যাপচার করতে।
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("What are the security best practices?")
প্রাসঙ্গিক সংকোচন
একটি chunk-এর সমস্ত বিষয়বস্তু কুয়েরির জন্য প্রাসঙ্গিক নয়। Contextual Compression Retriever প্রতিটি পুনরুদ্ধার করা chunk থেকে শুধুমাত্র প্রাসঙ্গিক অংশগুলো বের করতে একটি LLM ব্যবহার করে।
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
হাইব্রিড সার্চ
সম্পূর্ণ সেমান্টিক সার্চ সবসময় আদর্শ নয়। হাইব্রিড সার্চ ভালো ফলাফল অর্জনের জন্য সেমান্টিক সার্চ (embeddings) এর সাথে লেক্সিকাল সার্চ (BM25, কীওয়ার্ড ম্যাচিং) একত্রিত করে।
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
কথোপকথনমূলক RAG (মেমোরি সহ)
একটি RAG চ্যাটবট তৈরি করতে যা কথোপকথনের প্রসঙ্গ মনে রাখে, কথোপকথনের ইতিহাস বিবেচনায় নিয়ে ব্যবহারকারীর প্রশ্নগুলো পুনর্গঠন করে এমন মেমোরি যোগ করতে হবে।
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "Given the chat history and the user's latest question, " "reformulate the question so it is understandable without the history."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
সর্বোত্তম অনুশীলন
- সঠিক chunk আকার বেছে নিন: বিভিন্ন আকার নিয়ে পরীক্ষা করুন (৫০০-১৫০০ টোকেন)। সুনির্দিষ্ট উত্তরের জন্য ছোট chunks, বৃহত্তর প্রসঙ্গের জন্য বড় chunks।
- ডকুমেন্ট মেটাডেটা ব্যবহার করুন: chunks-এ মেটাডেটা হিসেবে উৎস, তারিখ এবং বিভাগ যোগ করুন। এটি রিট্রিভালের সময় ফলাফল ফিল্টার করতে দেয়।
- গুণমান মূল্যায়ন করুন: faithfulness, relevancy এবং context precision এর মতো মেট্রিক্স পরিমাপ করতে RAGAS এর মতো ফ্রেমওয়ার্ক ব্যবহার করুন।
- ডকুমেন্ট আপডেট পরিচালনা করুন: আপনার ডেটা সোর্সের সাথে vector store সিঙ্ক্রোনাইজড রাখতে একটি পুনরায়-ইনজেশন পাইপলাইন বাস্তবায়ন করুন।
- একটি re-ranker যোগ করুন: প্রাথমিক রিট্রিভালের পরে, প্রকৃত প্রাসঙ্গিকতার ভিত্তিতে ফলাফলগুলো পুনরায় সাজাতে একটি re-ranking মডেল (যেমন Cohere Rerank) ব্যবহার করুন।
উপসংহার
RAG নির্দিষ্ট, আপ-টু-ডেট জ্ঞানে অ্যাক্সেস প্রয়োজন এমন AI অ্যাপ্লিকেশন তৈরির জন্য মানক আর্কিটেকচারে পরিণত হয়েছে। LangChain পাইপলাইনের প্রতিটি উপাদানের জন্য অ্যাবস্ট্রাকশন প্রদান করে বাস্তবায়নকে ব্যাপকভাবে সরল করে।
পরবর্তী পদক্ষেপ:
- স্থানীয়ভাবে পরীক্ষা করুন: পাইপলাইনের সাথে পরিচিত হতে ChromaDB এবং কিছু নথি দিয়ে শুরু করুন।
- LangSmith অন্বেষণ করুন: প্রোডাকশনে আপনার চেইনগুলো মনিটর এবং ডিবাগ করতে LangSmith ব্যবহার করুন।
- বিভিন্ন embedding মডেল ব্যবহার করে দেখুন:
text-embedding-3-small,text-embedding-3-largeএবং Sentence Transformers এর ওপেন-সোর্স মডেলগুলো তুলনা করুন। - ডকুমেন্টেশন দেখুন: LangChain ডকুমেন্টেশন একটি চমৎকার এবং ক্রমাগত আপডেটেড রিসোর্স।