آج کل RAG کو کبھی کبھار برا سمجھا جاتا ہے، یا تو اس لیے کہ اب لوگ اسے بالکل معمولی سمجھتے ہیں (شروع کرنا آسان ہے، وسیع پیمانے پر چلانا نہیں) یا انہیں لگتا ہے کہ ’ایجنٹ پر مبنی نظام‘ اس سے آگے نکل گئے ہیں (حالاں کہ کئی صورتوں میں سطح ذرا کھرچیں تو وہ جلد ہی RAG جیسے دکھائی دینے لگتے ہیں…)۔
یہ بلاگ ایک دو عملی مثالوں کے ذریعے بتاتا ہے کہ ہم چند عام مسائل سے کیسے نمٹتے ہیں، یعنی:
متن اور عددی ڈیٹا کے امتزاج کو سنبھالنا اور یہ سادہ RAG کو کیوں ناکام کرتا ہے: کلیدی الفاظ باہم ٹکراتے ہیں اور اعداد کا کوئی معنوی مفہوم نہیں ہوتا۔
پہلے خلاصہ تیار کرنے والی ایمبیڈنگز کا ڈیزائن کیوں مددگار ہے: ہر حصے کا مختصر وضاحتی خلاصہ بنائیں، پھر اسی خلاصے کو ایمبیڈ کر کے اس پر استفسار کریں۔
سیاقی خلاصے کیسے بنائیں: بنیادی دستاویز کا سیاق شامل کریں تاکہ یکساں ساخت کے اعدادوشمار میں فرق واضح رہے۔
کوڈ اور Pydantic ماڈلز پر کب انحصار کریں: جہاں لفظ بہ لفظ مواد اہم ہو، قابلِ اعتماد نتائج کے لیے حسبِ ضرورت کوڈ اور/یا Pydantic ماڈل کو LLM کالز کے ساتھ استعمال کریں۔
بنیادی باتیں
RAG نظام معاونتی بوٹس سے لے کر داخلی علمی معاونین تک کئی چیزوں کو تقویت دیتے ہیں۔
پسِ پردہ عموماً آپ یہ کرتے ہیں:
اپنی ماخذ دستاویزات کے حصے بنائیں۔
ہر حصے کو ویکٹر اسپیس میں ایمبیڈ کریں۔
استفسار کے وقت بہترین K حصے بازیافت کریں۔
ان حصوں کی بنیاد پر جواب تیار کریں۔
LangChain، LlamaIndex اور OpenAI کا Filestore جیسے مقبول ٹول کٹس ان مراحل کو تقریباً معمولی بنا دیتے ہیں۔ لیکن حقیقی دنیا کی پائپ لائنز میں ایسا ڈیٹا بھی ملتا ہے جو محض گنجان متن نہیں ہوتا، اور بنیادی RAG کو اس میں دشواری ہو سکتی ہے۔ اگلے حصوں میں ہم ڈیٹا کے مسائل کی ٹھوس مثالیں دکھائیں گے اور پیچیدگی بڑھنے کے ساتھ بتدریج حل تیار کریں گے۔
جب آپ کا ڈیٹا صرف متن نہ ہو (جو دراصل عام بات ہے)
گیمنگ کے تناظر میں ڈیٹا کے اس حصے پر غور کریں:
JSON
ایمبیڈنگز اس لیے کام کرتی ہیں کہ وہ معنوی مفہوم اور قواعد کے ذریعے الفاظ کے مابین سیکھے گئے تعلقات استعمال کرتی ہیں۔ اوپر کے ڈیٹا میں متن اور اعداد ملے ہوئے ہیں، اور اس مخصوص سیاق سے باہر ان اعداد کا الفاظ سے کوئی تعلق نہیں۔ لہٰذا کہہ سکتے ہیں کہ ڈیٹا کا یہ حصہ کچھ وضاحتی الفاظ اور ان کے بعد آنے والے چند بے ترتیب اعداد کا مجموعہ ہے۔
اگر ہمارے پاس صرف اسی نوعیت کا ڈیٹا ہوتا تو یہ مسئلہ نہ بنتا، کیونکہ دستیاب چند وضاحتی الفاظ کی ایمبیڈنگز سے ہم اسے پھر بھی بازیافت کر سکتے تھے (یا محض متن سے ایس کیو ایل استعمال کر لیتے)۔ لیکن اگر یہ حصہ متن سے بھرے بہت سے ایسے حصوں میں دبا ہو جہاں یہی الفاظ بھی موجود ہوں تو کیا ہوگا؟ مثلاً:
JSON
اب تصور کریں کہ ہم یہ بازیافت کرنا چاہتے ہیں: ”ڈریکونک اسینشن کے ساتھ حملے کی حد کیا ہے؟“ غالب امکان ہے کہ ہم مطلوبہ متعلقہ حصہ بازیافت نہیں کر پائیں گے، کیونکہ وہ انہی کلیدی الفاظ والے دوسرے حصوں کے شور میں کہیں دبا ہوا ہے۔
بنیادی مسئلہ یہ ہے کہ ہم ڈیٹا کے ان حصوں میں اچھی طرح فرق نہیں کر سکتے، حالاں کہ ان میں ایک ہی موضوع کے بارے میں مختلف اقسام کی معلومات ہیں۔ کیا ہم کسی طرح اسے مزید بہتر اور بامعنی بنا سکتے ہیں؟ یقیناً بنا سکتے ہیں:smile:
خلاصہ بنا کر اپنے ڈیٹا کو بامعنی بنائیں، جی ہاں، آپ نے درست پڑھا
حصے کو براہِ راست ایمبیڈ کرنے کے بجائے پہلے ایک خلاصہ بنائیں جو بتائے کہ ڈیٹا کس بارے میں ہے، پھر اسی خلاصے کو ایمبیڈ کر کے اس پر بازیافت کریں۔ جواب تیار کرتے وقت ہم پھر بھی خلاصے سے منسلک اصل ڈیٹا استعمال کریں گے۔
لہٰذا اوپر دکھائے گئے دونوں حصوں کے لیے ہم کچھ ایسے خلاصے بنائیں گے:
حملے کی حد، رفتار اور نقصان کے اعدادوشمار (عام حالت اور ڈریکونک اسینشن کے ساتھ)۔
ڈریکونک اسینشن کی تفصیل، بشمول فعال ہونے کی شرائط، بصری اثرات اور پس منظر کی کہانی۔
پھر ہم استفسار کو بھی بڑھاتے ہیں تاکہ اسے خلاصے کے ساتھ ”ہم آہنگ“ کیا جا سکے۔ مثلاً ہم ”ڈریکونک اسینشن کے ساتھ حملے کی حد کیا ہے؟“ کو ”ڈریکونک اسینشن کے ساتھ حملے کی حد کے اعدادوشمار کیا ہیں؟“ میں بدل دیں گے۔یہ خاص طور پر تب اہم ہے جب بازیافت کا استفسار غیر تکنیکی شعبوں کے صارفین کی ~~”من موجی“~~ عام انسانی زبان میں ہو، کیونکہ آخرکار یہ جاننا نہ ان کے علم کا حصہ ہے نہ ان کی فکر کہ RAG درستگی اور جامعیت زیادہ سے زیادہ کرنے کے لیے کیسے کام کرتا ہے۔


باتوں کو سیاق سے الگ نہ کریں (یہ اصول زندگی میں بھی عموماً کارآمد ہے)
اب اگلی صورتِ حال میں ہمیں ذیل کی طرح ایک جیسے دکھائی دینے والے بے شمار ڈیٹا حصوں سے نمٹنا ہے:
Plain Text
اگر ہم یہی طریقہ اپنائے رکھیں تو تصور کریں کہ کوئی پوچھے: ”کردار X کے حملے کی حد کیا ہے؟“ تب ابھی بنائے گئے ان خلاصوں کے ساتھ ہم قسمت کے سہارے اندازے لگا رہے ہوں گے، کیونکہ یہ بھی بہت یکساں دکھائی دیں گے۔ تو پھر ہم ان میں فرق کیسے کریں؟
سادہ جواب ہے: سیاق فراہم کریں۔ ہم ڈیٹا کے حصے میں اس کی بنیادی دستاویز کا حوالہ شامل کر سکتے ہیں، مثلاً اس صورت میں {”character”: “X”}۔ اب کردار Y اور Z کا یہی ڈیٹا موجود ہونے پر بھی ہم کردار X کا درست ڈیٹا ٹھیک طور پر بازیافت کر سکیں گے۔
تاہم اس سے بہتر اور زیادہ قابلِ اطلاق طریقہ یہ ہوگا کہ حصے کا سیاقی خلاصہ بنایا جائے۔ یعنی صرف ڈیٹا کے حصے کا خلاصہ بنانے کے بجائے ہم اس کی بنیادی دستاویز اور حصہ دونوں دے کر ایک عمومی سیاقی خلاصہ بنا سکتے ہیں۔ اس میں یہ بھی شامل ہوگا کہ یہ حصہ بنیادی دستاویز میں کیسے موزوں بیٹھتا ہے، مثلاً:
یہ حصہ کردار X کے لیے … کے تفصیلی اعدادوشمار فراہم کرتا ہے۔ یہ حصہ حملے کی رفتار میں X کی طاقت دکھا کر مکمل دستاویز سے جڑتا ہے…
یہ حصہ کردار Y کے لیے … کے تفصیلی اعدادوشمار فراہم کرتا ہے۔ یہ حصہ Y کی خاص صلاحیت سے بڑھے ہوئے اعدادوشمار دکھا کر مکمل دستاویز سے جڑتا ہے…
یہ حصہ کردار Z کے لیے … کے تفصیلی اعدادوشمار فراہم کرتا ہے۔ یہ حصہ Z کے وہ اعدادوشمار دکھا کر مکمل دستاویز سے جڑتا ہے جو ٹیم مقابلوں میں دفاعی کردار کے لیے نہایت موزوں ہیں…
یہ طریقہ (جو جزوی طور پر Anthropic سے متاثر ہے) اوپر کی مثال کے لیے ضرورت سے زیادہ معلوم ہو سکتا ہے، مگر ایسے حصوں کے لیے بہت مؤثر ہے جنہیں ”سیاق سے الگ“ کر کے غلط سمجھا جا سکتا ہو۔ مزید یہ کہ یہ تمام حصوں کے لیے ایک یکساں طریقہ فراہم کر کے انجینئرنگ پائپ لائن کو منظم رکھتا ہے۔


جب آپ کو ~~ہر چیز قابو میں رکھنے کا جنون~~ پوری سختی درکار ہو
عموماً ہمیں ڈیٹا مکمل شکل میں ملتا ہے اور ہم اسے RAG نظام کے لیے حصوں میں تقسیم کرتے ہیں۔ اس مثال میں صورتِ حال ذرا مختلف ہے: ڈیٹا حصوں میں بٹا ہوا ہے مگر یہ ناقص حصے ہیں۔ یہ ایک منطقی حصے کے بے ترتیب ٹکڑے ہیں جنہیں دراصل دوبارہ اکٹھا کرنا ضروری ہے۔ منطقی حصے سے مراد ایسا مواد ہے جو فطری طور پر یکجا ہونا چاہیے، جیسے کسی دستاویز کا ذیلی حصہ یا کوئی مربوط پیراگراف۔


اس ڈیٹا کے ساتھ ہماری پہلی کوشش یہ ہے کہ سب کچھ ایک LLM کال کو دیں، اس سے مناسب انداز میں گروپ بنانے کو کہیں، اور پھر گروپ کیا ہوا مواد واپس لیں۔ LLM کو یہ کام خاصا اچھا کرنا چاہیے، ہے نا؟ بات کچھ ہاں اور کچھ نہیں والی ہے۔
ہم نے کئی دیگر مواقع پر بھی دیکھا ہے کہ LLM سستی کا مظاہرہ کرتے ہیں اور جب مکمل اور عین مواد درکار ہو، خصوصاً طویل سیاق میں، تو قابلِ اعتماد نہیں رہتے۔ اور یہ بات بالکل معقول ہے۔ لیکن استعمال کی اس مخصوص صورت میں یہ ناقابلِ قبول تھا، کیونکہ ہمیں مواد لفظ بہ لفظ عین اسی طرح چاہیے تھا: نہ خلاصے، نہ اصل مواد کا کوئی حصہ چھوڑنا۔ ہم کوئی تفصیل نظر انداز نہیں کر سکتے۔
اور یقیناً ”ہاں“ والا پہلو یہ تھا کہ اس نے ٹوٹے ہوئے حصوں کے معنی اور ساخت سمجھنے میں شاندار کام کیا۔ بشرطیکہ وہ عین مواد واپس نقل کرنے سے انکار نہ کرے۔ افسوس:/
تو پھر ہم LLM کی خوبیوں سے فائدہ اٹھاتے ہوئے اس کے ناقابلِ اعتماد پہلو سے کیسے بچیں؟ ہم نے اپنے پرانے قابلِ اعتماد دوست، یعنی کوڈ، سے رجوع کیا (مراد حسبِ ضرورت پائتھن فنکشن ہے)۔ اور ایک ”اس سے آسان ممکن نہیں“ Pydantic ماڈل سے۔ حل یہ ہے:
موجودہ منطقی حصے کو برقرار رکھتے ہوئے تمام سیکشنز پر یکے بعد دیگرے کارروائی کریں۔
ہر سیکشن پر LLM سے پوچھیں: کیا یہ سیکشن موجودہ منطقی حصے کا حصہ ہے؟ Pydantic ماڈل کے مطابق ہاں یا نہیں میں جواب دیں۔
اگر ہاں، تو سیکشن کو حصے سے جوڑ دیں؛ اگر نہیں، تو مکمل ہو چکا موجودہ منطقی حصہ جوں کا توں نکال دیں اور اس سیکشن سے نیا حصہ شروع کریں۔


یقیناً ہم مکمل مواد کو ایک بار میں گزارنے کے مقابلے میں کچھ زیادہ ٹوکن استعمال کر رہے ہیں، مگر استعمال کی اس مخصوص صورت میں عین مواد برقرار رکھنا اولین ترجیح تھا، اس لیے یہ معمولی اضافی لاگت پوری طرح قابلِ قدر تھی۔
یہ نہایت سادہ حل ہے، مگر ایک اہم اصول کی پیروی کرتا ہے: جہاں سختی ضروری ہو وہاں ہمیں صرف LLM پر انحصار نہیں کرنا چاہیے، کیونکہ آخرکار وہ احتمالی ہوتے ہیں۔
حسبِ ضرورت کوڈ اور فنکشنز نیز Pydantic ماڈلز استعمال کر کے قابلِ پیش گوئی اور قابلِ اعتماد نتیجہ حاصل کیا جا سکتا ہے، جبکہ LLM کی صلاحیتوں سے بھی بھرپور فائدہ اٹھایا جا سکتا ہے۔
تخلیقی مصنوعی ذہانت کا حل بنانا جتنا مصنوعی ذہانت کا چیلنج ہے، اتنا ہی انجینئرنگ کا بھی۔ ہمیں امید ہے کہ ان مثالوں نے آپ کو اپنے منفرد مسائل حل کرنے کی تحریک دی ہوگی۔ انجینئرنگ کو ترجیح دینے والے تخلیقی مصنوعی ذہانت کے حل کے بارے میں مزید جاننے کے لیے روٹر پر مبنی ایجنٹ نظام کے ڈیزائن پر ہماری بلاگ پوسٹ پڑھیں۔