অধিকাংশ RAG প্রোটোটাইপ অভ্যন্তরীণভাবে দেখতে একই রকম। কেউ একজন একটি পাইপলাইনে একটি PDF ইনপুট দেয়, টেক্সটটিকে সুন্দরভাবে ৫১২-টোকেন বিশিষ্ট চাঙ্কে বিভক্ত করে, সেগুলোকে একটি ভেক্টর ডেটাবেসে জমা করে এবং কাজ শেষ বলে ধরে নেয়। একটি সাধারণ ডেমোর জন্য এটি চিত্তাকর্ষক মনে হতে পারে। কিন্তু প্রোডাকশনে এটি ব্যর্থ হয়।
একটি ফিক্সড চাঙ্ক কী কাটছে সেদিকে ভ্রুক্ষেপ করে না। এটি একটি আইনি চুক্তির (legal contract) মাঝখান থেকে ক্ষতিপূরণ সংক্রান্ত ধারা (indemnity clause) কেটে ফেলতে পারে। এটি পাঁচটি অসংলগ্ন API এন্ডপয়েন্টকে একই কনটেক্সট উইন্ডোতে ঠাসা করে দিতে পারে এবং মডেলটিকে অপ্রাসঙ্গিক তথ্যের ভিড়ে ডুবিয়ে দিতে পারে। এটি আপনাকে প্রয়োজনের চেয়ে বেশি ফ্র্যাগমেন্ট বা অংশ রিট্রিভ করতে বাধ্য করে, যা ল্যাটেন্সি বাড়িয়ে দেয় এবং টোকেন খরচ বৃদ্ধি করে। এর ফলাফল হলো অর্ধেক উত্তর, হ্যালুসিনেশন এবং হতাশ ব্যবহারকারী।
আমরা আমাদের রিট্রিভাল লেয়ারটিকে একেবারে গোড়া থেকে পুনর্গঠন করেছি। এর ফলে আমরা এমন একটি সিস্টেম তৈরি করতে পেরেছি যা ল্যাটেন্সি ৪০ শতাংশ কমিয়ে আনার পাশাপাশি ৯৫ শতাংশ রিকল (recall) অর্জন করেছে। আমরা ঠিক কীভাবে এটি করেছি তা নিচে দেওয়া হলো।
কেন ফিক্সড চাঙ্ক প্রোডাকশনে ব্যর্থ হয়
৫১২-টোকেন ডিফল্ট হিসেবে ব্যবহার করা কোনো সুপরিকল্পিত ডিজাইন নয়। এটি মূলত প্রাথমিক এমবেডিং মডেলের কনটেক্সট উইন্ডো এবং লাইব্রেরির ডিফল্ট সেটিংসের একটি উপজাত (by-product)। এটি ইমপ্লিমেন্ট করা সহজ, কিন্তু এর ওপর নির্ভর করা বিপর্যয়কর।
ডকুমেন্টগুলো সব সময় একরকম হয় না। একটি আইনি ধারা কোনো স্পষ্ট বিরতি ছাড়াই সাতশ টোকেন পর্যন্ত দীর্ঘ হতে পারে। আপনি যদি এটিকে পাঁচশ বারো টোকেনে ভাগ করেন, তবে আপনি দুটি বিচ্ছিন্ন অংশ তৈরি করবেন। যখন কোনো আইনজীবী বা কমপ্লায়েন্স অফিসার দায়ের সীমা (liability caps) সম্পর্কে জিজ্ঞাসা করেন, সিস্টেমটি বাধ্যবাধকতার অর্ধেক অংশ প্রদান করে। ল্যাঙ্গুয়েজ মডেল তখন হারিয়ে যাওয়া অর্ধেক অংশটি নিয়ে হ্যালুসিনেশন করে, অথবা আরও খারাপভাবে, দায়ের সীমাটি নেই বলে অস্বীকার করে।
API ডকুমেন্টেশন ঠিক উল্টো সমস্যায় ভোগে। একটি পাঁচশ টোকেনের চাঙ্ক একটি সম্পূর্ণ মডিউলকে গিলে ফেলতে পারে: অথেন্টিকেশন হেডার, এরর কোড, রেট লিমিট এবং ওয়েবহুক স্কিমা। যখন একজন ডেভেলপার AUTH_4027 কীভাবে হ্যান্ডেল করতে হয় তা জানতে চান, রিট্রিভার তখন অসংলগ্ন ফাংশনগুলোর একটি মিশ্রণ সামনে নিয়ে আসে। মডেলের কাছে তখন সেগুলোকে একটি সাধারণ অস্পষ্ট তথ্যে রূপান্তর করা ছাড়া আর কোনো উপায় থাকে না।
খারাপ চাঙ্কিং ল্যাটেন্সিও বাড়িয়ে দেয়। দুর্বল ফ্র্যাগমেন্টের কারণে কোনো একটি বিষয় কভার করার জন্য আপনাকে বড় 'top-k' ব্যবহার করতে হয়। বেশি চাঙ্ক মানে বড় প্রম্পট। বড় প্রম্পট মানে ধীরগতির জেনারেশন এবং উচ্চ বিল। ব্যবহারকারীর অভিজ্ঞতা হাজারো ছোটখাটো সমস্যার কারণে ধসে পড়ে।
ডকুমেন্টের সাথে সামঞ্জস্যপূর্ণ চাঙ্ক নির্বাচন করুন
আমরা টোকেন গণনা করা বন্ধ করে বিষয়বস্তু পড়া শুরু করেছি। সঠিক চাঙ্কিং কৌশল নির্ভর করে উৎসের কাঠামোর ওপর।
আইনি নথি (Legal documents) এর জন্য ক্লজ-সচেতন সীমানা সহ রিকার্সিভ ক্যারেক্টার চাঙ্কিং প্রয়োজন। স্প্লিটারটি এখানে হায়ারার্কি বা স্তরবিন্যাস মেনে চলে: এটি প্রথমে সেকশন হেডার, তারপর নম্বরযুক্ত অনুচ্ছেদ এবং সবশেষে স্বাভাবিক বাক্য বিরতি খোঁজে। এটি কখনোই কোনো সাব-ক্লজ বা কোনো বাধ্যবাধকতামূলক বাক্যাংশকে চাঙ্কের মাঝখান থেকে বিচ্ছিন্ন করে না। যখন আপনি ক্ষতিপূরণ সংক্রান্ত কোনো অনুচ্ছেদ রিট্রিভ করেন, আপনি সম্পূর্ণ ক্লজ, এর সীমা এবং ব্যতিক্রমগুলো একসাথে পান।
API ডকুমেন্টেশন এর জন্য স্ট্রাকচার-অ্যাওয়ার চাঙ্কিং প্রয়োজন। আমরা টোকেন বাজেটের পরিবর্তে ফাংশন ডেফিনিশন অনুযায়ী পার্স করি। প্রতিটি চাঙ্কে একটি সম্পূর্ণ ফাংশন সিগনেচার, এর প্যারামিটার বর্ণনা এবং এর সাথে সম্পর্কিত এরর হ্যান্ডলিং নোট থাকে। যদি একজন ডেভেলপার একটি নির্দিষ্ট মেথড সার্চ করেন, তবে তিনি সম্পূর্ণ কন্ট্রাক্টটি পান, কোনো এলোমেলো স্প্লিটের মাঝে আটকে থাকা অংশ নয়।
সাপোর্ট টিকিট (Support tickets) গুলো অনেক সময় অগোছালো এবং নন-লিনিয়ার হয়। একটি থ্রেড একটি বাগ রিপোর্ট দিয়ে শুরু হতে পারে, একটি ওয়ার্কআউন্ড প্রদান করতে পারে এবং একটি ইন্টারনাল এসকেলেশন নোট দিয়ে শেষ হতে পারে। সিম্যান্টিক চাঙ্কিং বাক্যগুলোর মধ্যে এমবেডিং সিমিলারিটি পরিমাপ করে বিষয়ের পরিবর্তন শনাক্ত করে। আমরা শুধুমাত্র প্রাকৃতিক থিম্যাটিক বা বিষয়গত সীমানায় বিরতি দিই, যাতে লগইন ফেইলিওর সংক্রান্ত আলোচনা বিলিং সাইকেল সংক্রান্ত ফলো-আপ থেকে আলাদা থাকে।
উইকি (Wikis) ছিল সবচেয়ে কঠিন। এগুলো অনেক বড়, ক্রস-লিঙ্কড এবং শিথিলভাবে সংগঠিত। আমরা এজেন্টিক চাঙ্কিং (agentic chunking) ব্যবহার করেছি, যেখানে একটি হালকা ওজনের LLM একটি পেজ পড়ে এবং বিষয়গত সামঞ্জস্যের ভিত্তিতে বিরতি নির্ধারণ করে। এটি ইনজেশন টাইমে কিছুটা বেশি খরচ করে, কিন্তু এর ফলে প্রাপ্ত চাঙ্কগুলো স্বয়ংসম্পূর্ণ এবং রিট্রিভালের জন্য প্রস্তুত থাকে। ডিপ্লয়মেন্ট বেস্ট প্র্যাকটিস সংক্রান্ত একটি পেজ এলোমেলো টেক্সট ব্লকের পরিবর্তে লজিক্যাল ইউনিটে বিভক্ত হয়: প্রি-ফ্লাইট চেক, রোলব্যাক প্রসিডিউর এবং মনিটরিং সেটআপ।
হাইব্রিড রিট্রিভাল: কিওয়ার্ড এবং ভেক্টরের সমন্বয়
ডেন্স ভেক্টর সার্চ অর্থ বুঝতে পারে। কিন্তু এটি সঠিক স্ট্রিং বা শব্দের ক্ষেত্রে খুব একটা কার্যকর নয়। যদি কোনো ব্যবহারকারী AUTH_4027 এর মতো একটি সুনির্দিষ্ট এরর কোড বা "Stark Industries" এর মতো কোনো গ্রাহকের নাম সার্চ করেন, তবে ভেক্টর এমবেডিং লক্ষ্যভ্রষ্ট হতে পারে। কারণ এগুলো ক্যারেক্টার-লেভেল নির্ভুলতার পরিবর্তে ধারণাগত নৈকট্যের (conceptual proximity) ওপর ভিত্তি করে কাজ করে।
BM25 এর মাধ্যমে করা পিওর কিওয়ার্ড সার্চের সমস্যাটি ঠিক উল্টো। এটি AUTH_4027 নিখুঁতভাবে খুঁজে পাবে, কিন্তু এটি "authorization failure" এবং "login denied" এর মধ্যে থাকা ধারণাগত সংযোগটি ধরতে পারবে না।
আমরা উভয়টি সমান্তরালভাবে চালাই। BM25 এবং vector search একই কর্পাস (corpus)-এর ওপর স্বাধীনভাবে কাজ করে। তাদের রেজাল্ট লিস্টগুলো Reciprocal Rank Fusion ব্যবহার করে মার্জ করা হয়, যা পজিশনাল র্যাঙ্কগুলোর ভারসাম্য বজায় রেখে ক্যান্ডিডেটগুলোকে পুনরায় সাজায়। আপনার ক্যালিব্রেটেড ওয়েট (calibrated weights)-এর প্রয়োজন নেই। আপনি একটিমাত্র র্যাঙ্কড লিস্টের মধ্যেই exact match-এর নির্ভুলতা এবং semantic search-এর অন্তর্দৃষ্টি পান।
তারপর আমরা একটি cross-encoder reranker যোগ করি। এটি একটি আলাদা মডেল যা মূল কুয়েরির বিপরীতে প্রতিটি প্যাসেজকে স্কোর প্রদান করে, যা যেকোনো একক রিট্রিভারের (retriever) তুলনায় অনেক সূক্ষ্ম একটি প্রাসঙ্গিকতা সংকেত (relevance signal) তৈরি করে। এটি প্রায় ৫০ মিলিসেকেন্ড ল্যাটেন্সি (latency) যোগ করে। এটি রিকল (recall) ১৫ শতাংশ বৃদ্ধি করে। আপনি যদি উত্তরের গুণমানের ব্যাপারে সচেতন হন, তবে এই বিনিময়টি (trade) অনস্বীকার্য।
Query Expansion: সার্চ শুরু হওয়ার আগেই তা সংশোধন করুন
খারাপ কুয়েরি প্রতিটি রিট্রিভাল সিস্টেমের একটি গোপন সমস্যা। ব্যবহারকারীরা আপনার embedding space-এর মতো করে লেখেন না। তারা টাইপ করে "it broke"। তারা অসম্পূর্ণ stack traces পেস্ট করে। তারা এমন অভ্যন্তরীণ পরিভাষা (internal jargon) ব্যবহার করে যা আপনার ইনডেক্স আগে কখনও দেখেনি।
ইনডেক্সে পৌঁছানোর আগেই আমরা কুয়েরিটিকে রূপান্তরিত করি। প্রথমে, আমরা একটি একক কুয়েরিকে তিনটি থেকে পাঁচটি বৈচিত্র্যময় সার্চ টার্মে প্রসারিত করি। যদি মূল কুয়েরিটি "payment failed" হয়, তবে আমরা "transaction error," "billing declined," এবং "charge unsuccessful"-এর জন্যও সার্চ করি।
