Most RAG demos look brilliant on a laptop. Feed a script a twenty-page PDF, ask a question, watch it cite the right paragraph. But shipping that same pipeline into production is where the romance ends. Legal documents get sliced in half at the sentence level. Thick API references drown the important signal in boilerplate noise. Latency balloons. Users wait, twitch, and leave. We hit that wall hard. So we tore the retrieval layer down to the studs and rebuilt it as a measured, tunable system. The result was a pipeline that hits 95% recall without turning the user experience into a slideshow.
Why Demo RAG Dies in Production
The standard stack is surprisingly uniform across hobby projects and early-stage products: fixed token chunks, off-the-shelf embeddings, and a single vector search call. That simplicity is seductive, and it works when your corpus is clean, small, and syntactically predictable. Production data is none of those things. A fixed chunk of 512 tokens will happily cut through the middle of an indemnification clause in a SaaS contract. Suddenly your retrieval layer is feeding the language model half of a legal obligation and asking it to answer a liability question. The model hallucinates because the context is broken.
Large technical documents compound the issue. API documentation is full of function signatures, tables, and code blocks. A fixed window might capture the middle of a TypeScript interface but miss the function name above it and the usage example below it. The embedding vector ends up representing syntax fragments and inline noise instead of the actual capability the user is asking about. Garbage in, hallucination out.
Chunk by Structure, Not by Token Count
The first change we made was to stop thinking about chunks as bags of tokens. Chunks are semantic units. The right strategy depends entirely on what you are indexing.
For legal documents, we moved to recursive chunking that respects the document hierarchy. It treats sections, subsections, and clauses as boundaries. A clause stays intact because a clause is a unit of meaning. If you slice through it, the legal logic leaks out.
For API docs, structure-aware chunking treats functions, classes, and endpoints as atomic. One chunk might contain a function signature, its arguments, and its docstring. It does not spill arbitrarily into the next utility function just because a token counter clicked over. This keeps the embedding focused on a discrete capability.
Support tickets are messier. They are conversational, threaded, and nonlinear. Fixed chunks will grab a status update from an engineer and a customer complaint from the same thread and pretend they form a coherent unit. We switched to semantic chunking, splitting when the topic or speaker shifts rather than when the token budget runs out.
Internal wikis are often the sloppiest data in an organization. Formatting is inconsistent, headers are missing, and sections run together. For these, we use LLM-based chunking. A small model reads ahead and identifies logical boundaries before we ever generate an embedding. It costs more upfront than a character split, but the retrieval quality pays for itself immediately.
Hybrid Retrieval: Combine Signals
Vector search is powerful but it has blind spots. Paste in an exact error code like ERR_CONNECTION_REFUSED_0x800 and similarity search can return a troubleshooting guide for an unrelated module because the embedding space clustered them close together. Exact matches matter, and vector search alone can smooth them away.
Keyword search with BM25 solves the exact-match problem beautifully. But it chokes on conceptual distance. If a user asks about "performance degradation under heavy load," BM25 will miss a diagnostic note that describes "slow throughput during traffic spikes" because there is not enough keyword overlap.
We stopped choosing sides and started running both in parallel. Vector and keyword searches each return their own ranked lists. We merge them with Reciprocal Rank Fusion. RRF is simple and brutal in its effectiveness. It scores each document based on where it sits in each list. Documents that sit near the top in both systems get a massive boost. Documents that only one engine loves still earn a place in the final candidate set.
ফিউশনের পরে, আমরা শীর্ষ প্রার্থীদের একটি ক্রস-এনকোডার রির্যাঙ্কারের (cross-encoder reranker) মধ্য দিয়ে চালাই। এটি বিনামূল্যে পাওয়া যায় না। এটি প্রায় ৫০ মিলিসেকেন্ড অতিরিক্ত কম্পিউট যোগ করে। এটি রিকল (recall) ১৫% বৃদ্ধি করে। ক্রস-এনকোডার সম্পূর্ণ কুয়েরি এবং প্রতিটি প্রার্থী চাঙ্ককে (chunk) একত্রে মূল্যায়ন করে, যা একটি বাই-এনকোডার এমবেডিংয়ের (bi-encoder embedding) তুলনায় অনেক বেশি সূক্ষ্ম প্রাসঙ্গিকতা স্কোর প্রদান করে। সেই অতিরিক্ত ৫০ মিলিসেকেন্ড একটি লাভজনক বিনিয়োগ। এটি আপনাকে LLM-এ একটি আবর্জনাপূর্ণ কনটেক্সট উইন্ডো পাঠাতে এবং একটি বিভ্রান্তিকর বা হ্যালুসিনেশনযুক্ত উত্তরের জন্য দুই সেকেন্ড অপেক্ষা করা থেকে রক্ষা করে।
সার্চ করার আগেই কুয়েরি ঠিক করুন
ব্যবহারকারীরা সার্চ ইঞ্জিনিয়ারদের মতো কুয়েরি লেখে না। তারা টাইপ করে "app broken"। তারা রহস্যময় লগ ফ্র্যাগমেন্ট পেস্ট করে। তারা অস্পষ্ট বা দ্ব্যর্থবোধক প্রশ্ন করে। আপনি যদি সেই র (raw) স্ট্রিংগুলো সরাসরি ইনডেক্সে পাঠান, তবে আপনি আবর্জনা বা ভুল ফলাফল পাবেন।
আমরা রিট্রিভাল ইঞ্জিনে পৌঁছানোর আগেই প্রতিটি কুয়েরিকে রূপান্তরিত করি।
প্রথমত, কুয়েরি এক্সপ্যানশন (query expansion)। সিস্টেম একটি মাত্র ছোট প্রশ্ন থেকে একাধিক সার্চ টার্ম তৈরি করে। একজন ব্যবহারকারী জিজ্ঞাসা করেন, "How do I fix the timeout?" ইঞ্জিন এটিকে কানেকশন টাইমআউট, রিড টাইমআউট, গেটওয়ে টাইমআউট এবং রিট্রাই লজিক কভার করার জন্য প্রসারিত করে। শুধুমাত্র এই পদ্ধতিটি আমাদের রিকল ৭৮% থেকে বাড়িয়ে ৯৬% করেছে।
দ্বিতীয়ত, কুয়েরি ডিকম্পোজিশন (query decomposition)। জটিল প্রশ্নগুলোকে ছোট ছোট উপ-প্রশ্নে (sub-questions) ভাগ করা হয়। "What's the refund policy for enterprise customers past 90 days and how does it differ from monthly plans?" এর মতো একটি কুয়েরি একটি বিশাল এমবেডিং লুকআপের পরিবর্তে দুটি সুনির্দিষ্ট সার্চে পরিণত হয়। প্রতিটি উপ-প্রশ্ন স্বাধীনভাবে ইনডেক্স হিট করে। ফলাফলগুলো পরবর্তীতে (downstream) আবার একত্রিত করা হয়। এটি রিট্রিভালকে সুনির্দিষ্ট ও নির্ভুল রাখে, যা একটি মাত্র এমবেডিং যখন একসাথে ডজনখানেক ধারণার সাথে মিলানোর চেষ্টা করে তখন যে তথ্য বিচ্ছুরণ (dilution) ঘটে তা রোধ করে।
আপনার পাইপলাইন টিউন করতে Bayesian Search ব্যবহার করুন
আপনি যদি এখনও চঙ্ক সাইজ (chunk size), ওভারল্যাপ রেশিও এবং রিট্রিভাল ওয়েট হাতে টিউন করতে থাকেন, তবে আপনি পারফরম্যান্সের অনেক সুযোগ হাতছাড়া করছেন। আমরা অনুমান করা বন্ধ করেছি।
আমরা একটি সার্চ স্পেস সংজ্ঞায়িত করেছি যেখানে চঙ্ক সাইজ, ওভারল্যাপ শতাংশ, ভেক্টর-বনাম-BM25 ওয়েট এবং রির্যাঙ্কার থ্রেশহোল্ড সবই ভেরিয়েবল। তারপর আমরা Bayesian optimization প্রয়োগ করেছি। শত শত র্যান্ডম কনফিগারেশনের মধ্য দিয়ে গ্রিড-সার্চ (grid-searching) করার পরিবর্তে, Bayesian search কী কাজ করে তার একটি প্রোবাবিলিস্টিক মডেল তৈরি করে। এটি একটি কনফিগারেশন প্রস্তাব করে, রিকল এবং ল্যাটেন্সি পর্যবেক্ষণ করে, তার ধারণা আপডেট করে এবং পরবর্তীটি প্রস্তাব করে। সময়ের সাথে সাথে এটি এমন একটি ভারসাম্য খুঁজে পায় যা একজন মানুষ ম্যানুয়ালি কখনোই করতে পারত না।
এটি এমন কিছু কম্বিনেশন খুঁজে পেয়েছে যা আমরা কখনোই চেষ্টা করতাম না। বেশি ওভারল্যাপ সহ ছোট চঙ্ক। ডেন্স ভেক্টর সার্চের কিছুটা কম ওয়েট এবং এর সাথে আরও আগ্রাসী (aggressive) রির্যাঙ্কার থ্রেশহোল্ড। এই অস্পষ্ট ট্রেডঅফগুলো আমাদের উচ্চতর রিকল এবং নিম্নতর ল্যাটেন্সি উভয়ই দিয়েছে।
এটি কোনো এককালীন সেটআপ টাস্ক নয়। আমরা প্রতি মাসে হাইপারপ্যারামিটার অপ্টিমাইজেশন (hyperparameter optimization) পুনরায় চালাই। আপনার কর্পাস (corpus) পরিবর্তিত হয়। ব্যবহারকারীর আচরণ পরিবর্তিত হয়। আপনার পাইপলাইনকে স্থবির হয়ে থাকার পরিবর্তে মানিয়ে নিতে হবে।
ফলাফল
এই পুনর্গঠনের ফলাফল নিয়ে তর্ক করার অবকাশ নেই।
পজিশন টেন-এ রিকল ৭৮% থেকে বেড়ে ৯৫% হয়েছে। যখন সঠিক উত্তরটি আমাদের নলেজ বেসে থাকে, তখন আমরা কুড়িবারের মধ্যে উনিশবার সেটি সামনে আনতে পারি। ৯৫তম পার্সেন্টাইলে ল্যাটেন্সি ৮৫০ মিলিসেকেন্ড থেকে কমে ৩২০ মিলিসেকেন্ডে নেমে এসেছে। চ্যাটটি এখন ধীরগতির পরিবর্তে তাৎক্ষণিক মনে হয়।
উন্নত রিট্রিভাল ল্যাঙ্গুয়েজ মডেলকে আরও ভালো গ্রাউন্ডিং (grounding) দিয়েছে। হ্যালুসিনেশন রেট ১২% থেকে কমে ৩% হয়েছে। মডেলের সামনে যখন সঠিক কনটেক্সট থাকে, তখন এটি তথ্য উদ্ভাবন করা বন্ধ করে দেয়। কুয়েরি প্রতি খরচ ৩৮% কমে গেছে। দ্রুত এবং আরও নির্ভুল রিট্রিভাল মানে অপ্রাসঙ্গিক কনটেক্সট, রিট্রাই লুপ এবং অতিরিক্ত কিন্তু অকেজো প্রম্পটে কম টোকেন অপচয়।
এটিকে ইনফ্রাস্ট্রাকচারের মতো তৈরি করুন
আপনি যদি প্রোটোটাইপ থেকে প্রোডাকশনে যাচ্ছেন, তবে রিট্রিভালকে কনফিগারেশনের পরিবর্তে ইনফ্রাস্ট্রাকচার কোড হিসেবে বিবেচনা করুন।
