বেশিরভাগ RAG টিউটোরিয়াল ঠিক সেখানেই শেষ হয় যেখানে প্রোডাকশন শুরু হয়। আপনি আপনার ডকুমেন্টগুলোকে ৫১২-টোকেন চঙ্কে বিভক্ত করেন, সেগুলোকে একটি মাত্র এমবেডিং মডেলের মাধ্যমে পাঠান এবং সাধারণ top-k রিট্রিভাল ব্যবহার করে একটি ভেক্টর ডাটাবেস কল করেন। একটি ডেমোতে এটি বেশ বিশ্বাসযোগ্য মনে হয়। বটটিকে আপনার কোম্পানির ছুটির নীতি সম্পর্কে জিজ্ঞাসা করুন এবং এটি একটি সুসংগত অনুচ্ছেদ প্রদান করবে। সবাই মাথা নাড়বে। দুর্ভাগ্যবশত, ডেমো বিভ্রান্তিকর হতে পারে।

প্রোডাকশন প্রতিটি শর্টকাট বা সংক্ষিপ্ত পথকে প্রকাশ করে দেয়। ফিক্সড চঙ্কগুলো আইনি চুক্তির ক্ষতিপূরণ সংক্রান্ত ধারার (indemnification clauses) মাঝখান দিয়ে কেটে ফেলে। API ডকুমেন্টেশন ওভারল্যাপিং নয়েজে পরিণত হয় যা আপনার প্রয়োজনীয় সিগন্যালকে ডুবিয়ে দেয়। ল্যাটেন্সি বা বিলম্ব বাড়তে থাকে যতক্ষণ না ব্যবহারকারী উত্তর আসার আগেই কুয়েরিটি ছেড়ে চলে যান। আমরা এই বাধার সম্মুখীন হয়েছিলাম এবং আমাদের সবকিছু নতুন করে তৈরি করতে হয়েছিল। আমাদের রিট্রিভাল লেয়ার "সিম্যান্টিক সার্চ এবং আশা" থেকে একটি পরিমাপযোগ্য এবং ইনস্ট্রুমেন্টেড পাইপলাইনে বিবর্তিত হয়েছে। এর ফলাফল ছিল ৯৫% রিকল এবং ল্যাটেন্সিতে ৪০% হ্রাস। এখানে দেওয়া হলো আসলে যা কাজ করেছে।

ডকুমেন্টের সাথে চঙ্কিং স্ট্র্যাটেজি মেলান

৫১২-টোকেন ডিফল্ট হিসেবে টিকে আছে কারণ এটি সহজ, সঠিক হওয়ার কারণে নয়। বিভিন্ন ডকুমেন্ট ভিন্ন ভিন্ন উপায়ে অর্থ বহন করে এবং আপনার চঙ্কিং স্ট্র্যাটেজি সেই অনুযায়ী হওয়া উচিত।

আইনি চুক্তির (legal contracts) জন্য, রিকার্সিভ চঙ্কিং ব্যবহার করুন যা কাঠামোগত সীমানা বজায় রাখে। আইনি ভাষা নেস্টেড বা স্তরীভূত থাকে। একটি ধারা তার উপরের সেকশনের ওপর নির্ভর করে, এবং বাক্যের মাঝখানে একটি ফিক্সড কাট বা নির্দিষ্ট অংশ কেটে ফেলা বাধ্যবাধকতার যুক্তি নষ্ট করে দেয়। রিকার্সিভ চঙ্কিং টোকেন লিমিট আরোপ করার আগে প্রাকৃতিক সেপারেটর—প্রথমে প্যারাগ্রাফ, তারপর বাক্য—অনুসারে বিভাজন করার চেষ্টা করে। এটি ক্ষতিপূরণ বা দায়বদ্ধতা সংক্রান্ত ধারাগুলোকে অক্ষত রাখে।

API ডকুমেন্টেশনের জন্য, ফাংশন-অ্যাওয়ার (function-aware) চঙ্কিং ব্যবহার করুন। ডেভেলপাররা এলোমেলো অনুচ্ছেদ খোঁজেন না; তারা এন্ডপয়েন্ট, প্যারামিটার এবং এরর সিগনেচার খোঁজেন। একটি চঙ্কে একটি সম্পূর্ণ ফাংশন সিগনেচার, এর বর্ণনা এবং রিটার্ন স্কিমা একটি যৌক্তিক ইউনিট হিসেবে থাকা উচিত। আপনি যদি সেই ব্লকটিকে অর্ধেক করে ফেলেন, তবে রিট্রিভাল সিস্টেম অর্ধেক কনটেক্সট প্রদান করবে এবং জেনারেশন মডেল বাকি অংশটি হ্যালুসিনেশন করবে।

সাপোর্ট টিকিটের জন্য, কথোপকথনের ধাপ (conversation turns) অনুসরণকারী সিম্যান্টিক চঙ্কিংয়ের ওপর নির্ভর করুন। সাপোর্ট থ্রেডগুলো রৈখিক এবং পুনরাবৃত্তিমূলক। একজন গ্রাহক সমস্যাটি বারবার বলেন, একজন এজেন্ট লগ চান, গ্রাহক সেগুলো সংযুক্ত করেন। প্রতিটি ধাপ বা টার্ন নিজস্ব একটি সিম্যান্টিক ইউনিট। টার্ন অনুযায়ী চঙ্কিং করলে কে কী বলেছে এবং কখন বলেছে তা সংরক্ষিত থাকে, যা তখন গুরুত্বপূর্ণ হয়ে ওঠে যখন ব্যবহারকারী জিজ্ঞাসা করেন, "মঙ্গলবার এজেন্ট কী পরামর্শ দিয়েছিলেন?"

অভ্যন্তরীণ উইকিবুক বা উইকির (internal wikis) জন্য, এজেন্টিক চঙ্কিং (agentic chunking) চেষ্টা করুন। একটি LLM-কে একটি সেকশন দিন এবং জিজ্ঞাসা করুন একটি টপিক কোথায় শেষ হচ্ছে এবং অন্যটি কোথায় শুরু হচ্ছে। এটি ইনজেস্ট টাইমে বেশি খরচ করে, তবে উইকিবুকগুলো অগোছালো হয়। পৃষ্ঠাগুলোতে বিভিন্ন টিমের অপ্রাসঙ্গিক আপডেট থাকে এবং মানুষের দ্বারা নির্ধারিত সীমানা খুব কমই কাজে আসে। টপিক পরিবর্তনের ওপর ভিত্তি করে একটি মডেলকে সীমানা নির্ধারণ করতে দিলে নয়েজ নাটকীয়ভাবে কমে যায়।

একটি পাইপলাইনে একাধিক স্ট্র্যাটেজি চালানোর জন্য ইনজেস্ট করার সময় ডকুমেন্টগুলোকে টাইপ অনুযায়ী ট্যাগ করা প্রয়োজন। স্কিমা ডিসিপ্লিনের এই সামান্য অংশটি তাৎক্ষণিকভাবে সুফল দেয়।

সার্চ মেথডগুলো একত্রিত করুন, একটি বেছে নেবেন না

ভেক্টর সার্চ ইনটেন্ট বা উদ্দেশ্য বুঝতে পারে, কিন্তু এটি নিয়মিতভাবে সঠিক মিলের (exact matches) ক্ষেত্রে ব্যর্থ হয়। কোনো এরর কোড ERR_CONNECTION_REFUSED বা একটি নির্দিষ্ট SKU এর জন্য জিজ্ঞাসা করলে, ডেন্স এমবেডিং প্রায়শই ধারণাগতভাবে একই রকম কিন্তু তথ্যগতভাবে ভুল ফলাফল প্রদান করে। BM25, যা একটি ক্লাসিক কিওয়ার্ড স্পার্স রিট্রিভাল মেথড, সঠিক স্ট্রিংগুলো চমৎকারভাবে হ্যান্ডেল করে কিন্তু সিম্যান্টিক সূক্ষ্মতা মিস করে। আপনার উভয়ের প্রয়োজন।

হাইব্রিড রিট্রিভাল ব্যবহার করুন। ভেক্টর সার্চ এবং BM25 সমান্তরালভাবে চালান। তারপর সেগুলোকে Reciprocal Rank Fusion (RRF) দিয়ে একত্রিত করুন। RRF সেই ডকুমেন্টগুলোকে পুরস্কৃত করে যেগুলোতে উভয় পদ্ধতিই প্রাসঙ্গিকতা খুঁজে পায়, পাশাপাশি যেকোনো একটি পদ্ধতি থেকে শক্তিশালী প্রার্থীকেও সামনে নিয়ে আসে। গণিতটি সহজ এবং ফলাফলটি স্থিতিশীল: কোনো একক রিট্রিভাল মেথড চূড়ান্ত র‍্যাঙ্কিংয়ে আধিপত্য বিস্তার করে না।

ফিউশনের পরে, একটি ক্রস-এনকোডার র র‍্যাঙ্কার (cross-encoder reranker) যোগ করুন। প্রথম ধাপটি — ভেক্টর প্লাস স্পার্স রিট্রিভাল — দ্রুত এবং ব্যাপক। ক্রস-এনকোডার তখন প্রতিটি কুয়েরি-ডকুমেন্ট জোড়াকে পূর্ণ মনোযোগের সাথে স্কোর করে, যার অর্থ এটি মূল প্রশ্নের বিপরীতে প্রার্থীটিকে আসলে পড়ে দেখে। হ্যাঁ, এটি ল্যাটেন্সি বাড়ায়। আমাদের ক্ষেত্রে, প্রায় পঞ্চাশ থেকে একশ মিলিসেকেন্ড। কিন্তু প্রিসিশন বা নির্ভুলতার যে বৃদ্ধি ঘটে তা এতটাই বেশি যে এই বিনিময়টি স্পষ্ট। আপনি যদি রিকল নিয়ে চিন্তিত হন, তবে এটি বাদ দেওয়ার সামর্থ্য আপনার নেই।

ইনডেক্স ঠিক করার আগে কুয়েরি ঠিক করুন

ব্যবহারকারীরা আপনার সার্চ ইঞ্জিনের জন্য কুয়েরি লেখেন না। তারা মানুষের জন্য লেখেন। “এটি কাজ করছে না” একটি সাধারণ সাপোর্ট কুয়েরি। একটি অস্পষ্ট ফিচারের বর্ণনা একটি সাধারণ ইন্টারনাল উইকি সার্চ। আপনি যদি সেই র (raw) ইনপুট দিয়ে ইনডেক্স সার্চ করেন, তবে আপনি আবর্জনা বা ভুল তথ্য ফেরত পাবেন।

কুয়েরিটি রিট্রিভারের কাছে পৌঁছানোর আগেই এটিকে রূপান্তরিত করুন।

ব্যবহারকারীর প্রশ্নের একাধিক সংস্করণ তৈরি করতে query expansion ব্যবহার করুন। কেউ যদি “server down” টাইপ করে, তবে আপনার সিস্টেমের উচিত “service unavailable,” “502 error,” এবং “connection timeout”-এর মতো বিষয়গুলোও খোঁজা। এই ধরনের intent variants কভার করার ফলে আমাদের recall 78% থেকে বেড়ে 96% হয়েছে। এটি একটি মাত্র ধাপ, এবং লাভের তুলনায় এর খরচ প্রায় নগণ্য।

জটিল প্রশ্নের জন্য query decomposition ব্যবহার করুন। যখন কোনো ব্যবহারকারী “How do I migrate from the legacy billing API to the new one and what breaking changes affect enterprise accounts?”-এর মতো কিছু জিজ্ঞাসা করেন, তখন সেটিকে উপ-প্রশ্নে (sub-questions) বিভক্ত করুন। একটি উপ-প্রশ্ন মাইগ্রেশন ধাপগুলোকে টার্গেট করবে। অন্যটি এন্টারপ্রাইজ-নির্দিষ্ট breaking changes-কে টার্গেট করবে। প্রতিটি প্রশ্ন ইনডেক্সের ভিন্ন ভিন্ন অংশকে হিট করবে। ডাউনস্ট্রিম ল্যাঙ্গুয়েজ মডেলটি একটি নয়েজি context window-তে অনুমান করার পরিবর্তে সঠিকভাবে রিট্রিভ করা চাঙ্ক (chunks) থেকে চূড়ান্ত উত্তরটি সংশ্লেষণ (synthesize) করবে।

হাইপারপ্যারামিটার নিয়ে অনুমান করা বন্ধ করুন

একবার আপনার কাছে একাধিক chunking strategies, hybrid retrieval এবং query transformation থাকলে, আপনি একটি combinatorial problem-এর সম্মুখীন হবেন। Chunk size, overlap, fusion weights, reranker depth এবং expansion count—সবগুলো একে অপরের সাথে সম্পর্কিত। একটিকে আলাদাভাবে পরিবর্তন করলে অন্যটির ক্ষতি হয়। এই স্পেস জুড়ে grid search করা অপচয় এবং ধীরগতির।

পরিবর্তে Bayesian optimization ব্যবহার করুন। এটিকে একটি machine learning tuning job হিসেবে বিবেচনা করুন। আপনার objective স্পষ্টভাবে সংজ্ঞায়িত করুন: ল্যাটেন্সি একটি নির্দিষ্ট সীমার নিচে রেখে রিকল সর্বোচ্চ করা। একটি golden dataset তৈরি করুন—কয়েকশ প্রতিনিধি প্রশ্ন যেখানে আপনি সুনির্দিষ্টভাবে জানেন কোন চাঙ্কগুলো (chunks) রিট্রিভ করা উচিত। তারপর Bayesian search-কে দক্ষতার সাথে configuration space অন্বেষণ করতে দিন। এটি কী কাজ করে তার একটি probabilistic model তৈরি করে এবং পরবর্তী ধাপে সবচেয়ে সম্ভাবনাময় অঞ্চলগুলো পরীক্ষা করে।

প্রতিটি candidate configuration-কে স্টেজিন (staging)-এ পৌঁছানোর আগে golden dataset-এ উত্তীর্ণ হতে হবে। যদি নতুন কোনো chunk size রিকল কমিয়ে দেয় বা একটি ভারী reranker আপনার ল্যাটেন্সি বাজেটের বাইরে চলে যায়, তবে অপ্টিমাইজেশন সেটি স্বয়ংক্রিয়ভাবে শনাক্ত করবে। এটি আলোচনার ক্ষেত্রে ব্যক্তিগত মতামতের প্রভাব দূর করে। আপনি ২৫৬ নাকি ৫১২ টোকেন “better” তা নিয়ে বিতর্ক করা বন্ধ করবেন এবং সরাসরি ফলাফল দেখতে শুরু করবেন।

ফলাফল

পাইপলাইনের পরিবর্তনগুলো ঠিক আমরা যেমনটি আশা করেছিলাম সেভাবেই কাজ করেছে।

  • Recall@10 78% থেকে বেড়ে 95% হয়েছে।
  • P95 latency 850 ms থেকে কমে 320 ms হয়েছে।
  • Hallucination rate 12% থেকে কমে 3% হয়েছে।
  • প্রতিটি কোয়েরির খরচ ৩৮% কমেছে, মূলত উন্নত রিট্রিভালের কারণে আমরা একটি ছোট জেনারেশন মডেল এবং কম প্রম্পট টোকেন ব্যবহার করতে পেরেছি।

ল্যাটেন্সি হ্রাস টিমের কিছু মানুষকে অবাক করেছে। rerankers এবং query expansion যোগ করলে কাজ ধীর হয়ে যাওয়ার কথা। কিন্তু যেহেতু রিট্রিভালের মান উন্নত হয়েছে, তাই জেনারেশন মডেলের কম prompting, কম speculation এবং কম retries প্রয়োজন হয়েছে। ভালো রিট্রিভাল পরবর্তী ধাপের সবকিছুকে সাশ্রয়ী করে তোলে।

রিট্রিভালকে ইনফ্রাস্ট্রাকচারের মতো বিবেচনা করুন

রিট্রিভাল এমন কোনো নোটবুক নয় যা আপনি একবার চালিয়ে ভুলে যাবেন। এটি একটি ইনফ্রাস্ট্রাকচার, এবং এটিকে কোডের মতো পরিচালনা করা উচিত। আপনার chunking strategies-এর ভার্সন বজায় রাখুন। যখন লিগ্যাল টিম একটি নতুন কন্ট্রাক্ট টেমপ্লেট প্রকাশ করে, তখন প্রোডাকশনে যাওয়ার আগেই আপনার recursive splitter পরীক্ষা করে নিন। আপনার golden dataset-কে একটি জীবন্ত ডকুমেন্ট হিসেবে বজায় রাখুন, গত কোয়ার্টারের কোনো স্থির CSV হিসেবে নয়। আপনার ইভ্যালুয়েশনগুলো CI-তে স্বয়ংক্রিয় করুন যাতে কোনো এম্বেডিং মডেল বা ফিউশন ওয়েট পরিবর্তনকারী pull request মানুষের রিভিউ করার আগেই রিকল এবং ল্যাটেন্সি নম্বরসহ একটি কমেন্ট পায়।

আপনার ব্যবহারকারীরা কখনোই জিজ্ঞাসা করবে না যে আপনি কোন এম্বেডিং মডেল ব্যবহার করছেন। তারা আপনার chunking heuristic বা reranker architecture নিয়ে মাথা ঘামাবে না। তারা শুধু দেখে যে উত্তরটি সঠিক কি না, এটি দ্রুত আসছে কি না এবং তারা এটি বিশ্বাস করতে পারে কি না। এমন একটি পাইপলাইন তৈরি করুন যা সেই বিশ্বাস অর্জন করে, সততার সাথে তা পরিমাপ করুন এবং রিট্রিভালকে অবহেলার বিষয় হিসেবে দেখা বন্ধ করুন।

উৎস: Optimizing RAG At Scale
আলোচনায় যোগ দিন: GyaanSetu AI Community