کسی سوال کا جواب دینے یا تلاش چلانے کے لیے ایک واحد AI ایجنٹ بنانا ایک حل شدہ مسئلہ ہے۔ چند سبق اور چند گھنٹوں کے کام کے ساتھ، آپ اپنے مقصد تک پہنچ سکتے ہیں۔
جو سب سے زیادہ سبق چھوڑتے ہیں وہ انجینئرنگ کی پرت ہے جو اگلی آتی ہے۔ یعنی وہ حصہ جو ملٹی ایجنٹ سسٹم کو پیداواری ماحول میں چلانے کے لیے کافی مستحکم بناتا ہے۔
میں عمل کے کریش کے بعد حالت کو کیسے بحال کروں؟ آپ ہر انضمام کے لیے ملکیتی اڈاپٹر لکھے بغیر ایجنٹوں کو ٹولز تک معیاری رسائی کیسے دیتے ہیں؟ آپ مختلف فریم ورک پر بنائے گئے ایجنٹوں کو کیسے مربوط کرتے ہیں؟ مجھے کیسے پتہ چلے گا کہ جب ایجنٹ کی پیداوار کا معیار گر رہا ہے؟
یہ بنیادی ڈھانچے کے بارے میں سوالات ہیں، اور یہ کتاب ان کا جواب ورکنگ کوڈ کے ساتھ دیتی ہے جسے آپ اپنے کمپیوٹر پر چلا سکتے ہیں۔ کوئی کلاؤڈ اکاؤنٹس، API کیز، یا جاری اخراجات نہیں ہیں۔
ہم پروٹوکول کی سطح پر ان مسائل کو حل کرنے کے لیے چار تکنیک استعمال کریں گے۔
-
لینگراف اسٹیٹفول ایجنٹ آرکیسٹریشن کے لیے
-
ماڈل سیاق و سباق پروٹوکول (MCP) معیاری ٹول انضمام کے لیے،
-
A2A (ایجنٹ سے ایجنٹ پروٹوکول) فریم ورک میں ایجنٹوں کو مربوط کرنا
-
مستقبل میں، مقامی LLM تخمینہ کے لیے۔
آپ ہر تصور کو ٹھوس بنانے، اپنے نوٹوں میں موضوعات کی وضاحت کرنے، کوئز چلانے، اور ایک حقیقی دنیا کا نظام بنانے کے لیے سیکھنے کا ایک روڈ میپ تیار کریں گے – ایک سیکھنے کا سرعت کار جو نتائج کی بنیاد پر ایڈجسٹمنٹ کرتا ہے۔ استعمال کے معاملات ایک تدریسی آلہ ہیں۔ فن تعمیر اصل موضوع ہے۔
وہ آرکیٹیکچرل پیٹرن (اوپن پروٹوکولز پر کوآرڈینیشن کرنے والے ماہر ایجنٹ) فی الحال سیلز ایبلمنٹ (ایجنٹس آن بورڈنگ نمائندوں اور تربیت کے راستوں کو مربوط کرنے)، تعمیل کی تربیت (ریگولیٹری نصاب کے ذریعے ملازمین کی تصدیق کرنے والے ایجنٹ)، کسٹمر سپورٹ (ایجنٹس ایک نالج بیس بنا رہے ہیں) اور انجینئرنگ بورڈ کے ذریعے نئے سراغ لگانا (تعریف کرنے والے ایجنٹس) کی تیاری میں چل رہے ہیں۔ کوڈ بیس)۔
ڈومین بدل جاتا ہے۔ بنیادی ڈھانچے کے نمونے ایسا نہیں کرتے ہیں۔
مکمل کوڈ حاصل کریں۔
اس ہینڈ بک کے لیے مکمل، چلانے کے لیے تیار ذخیرہ GitHub پر موجود ہے۔ اس کو کلون کریں اور اس کے ساتھ ساتھ یا پڑھتے ہوئے اسے حوالہ کے نفاذ کے طور پر استعمال کریں۔
انڈیکس
تعارف
کیا تعمیر کرنا ہے
بنائے جانے والے سسٹم میں چار ایجنٹس شامل ہیں جو لینگ گراف کے ذریعے مربوط ہیں، ان ایجنٹوں کو بیرونی ٹولز تک رسائی فراہم کرنے کے لیے دو MCP سرورز، فریم ورک کے درمیان ایجنٹ کے وفد کو اجازت دینے کے لیے دو A2A خدمات، مکمل نشانات حاصل کرنے کے لیے Langfuse، اور خودکار معیار کی جانچ کو چلانے کے لیے DeepEval شامل ہیں۔
یہ آخر سے آخر تک کیسا لگتا ہے:
شکل 1. مکمل نظام۔ لینگ گراف چار ایجنٹوں کو مربوط کرتا ہے۔ ہر ایجنٹ MCP کے ذریعے ٹول تک رسائی حاصل کرتا ہے۔ پروگریس کوچ بیرونی ایجنٹوں کو A2A کے ذریعے مندوب کرتا ہے، بشمول CrewAI ایجنٹس، ایک بالکل مختلف فریم ورک۔ اولاما مقامی طور پر تمام تخمینہ چلاتے ہیں۔ Langfuse تمام نشانات پر قبضہ کرتا ہے۔
آپ ہر پرت کو بتدریج بناتے ہیں۔ سسٹم مکمل ہونے کے بعد، آپ نہ صرف یہ سمجھیں گے کہ یہ ٹیکنالوجیز کیسے ایک ساتھ فٹ بیٹھتی ہیں، بلکہ یہ بھی سمجھیں گے کہ ہر ٹیکنالوجی کیوں موجود ہے اور یہ کون سے پیداواری ناکامی کے طریقوں کو روکتی ہے۔
ٹیکنالوجی اسٹیک
| ٹیکنالوجی | ورژن | کردار |
|---|---|---|
| لینگراف | 1.1.0 | اسٹیٹفول ملٹی ایجنٹ گراف آرکیسٹریشن |
| ایم سی پی | 1.26.0 | معیاری ایجنٹ ٹول پروٹوکول |
| A2A SDK | 0.3.25 | کراس فریم ورک ایجنٹ سے ایجنٹ پروٹوکول |
| مستقبل میں، | حالیہ | مقامی LLM تخمینہ (کوئی API کلید نہیں) |
| عملہ AI | 1.13.0 | A2A کے ذریعے فریم ورک کے درمیان انٹرآپریبلٹی |
| Langfuss | 4.0.1 | تقسیم شدہ ٹریکنگ اور مشاہدہ |
| ڈیپ ایول | 3.9.1 | ایل ایل ایم جج کی تشخیص |
شرطیں
آپ کو اس کے ساتھ آرام دہ ہونا چاہئے:
-
Python 3.11 یا اس سے زیادہ: اشارے، ڈیٹا کلاسز، async/await کی بنیادی باتیں ٹائپ کریں۔
-
ایل ایل ایم کے بنیادی تصورات: فوری، تکمیل، ٹول کال
-
کمانڈ لائن: ورچوئل ماحول بنائیں، اسکرپٹ چلائیں۔
LangGraph، MCP، A2A، یا ایجنٹ فریم ورک کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے۔ یہ کتابچہ پہلے اصولوں پر مبنی ہے۔
ہارڈ ویئر کے تقاضے
| ترتیب | رام | VRAM | ماڈل | میمو |
|---|---|---|---|---|
| کم از کم | 16 جی بی | 8 جی بی | qwen2.5:7b |
مکمل طور پر فعال |
| تجویز | 32 جی بی | 24 جی بی | qwen2.5-coder:32b |
بہترین ٹول کال قابل اعتماد |
| صرف سی پی یو | 32 جی بی | موجود نہیں ہے | qwen2.5:7b |
کام کرتا ہے، لیکن 5-10x سست |
ماڈل کا سائز ایجنٹوں کے لیے کیوں اہمیت رکھتا ہے۔
ایجنٹ ساختی JSON دلائل بنا کر ٹول کو کال کرتا ہے۔ وہ ماڈل جو ٹول کے نام میں غلطی کرتے ہیں یا اس کے دلائل کو غلط طریقے سے فارمیٹ کرتے ہیں وہ خود بخود ناکام ہو جائیں گے۔ ٹول کال پر عمل نہیں کیا جاتا ہے، ایجنٹ تکرار کرتا ہے، اور تکرار کی حد بغیر کسی واضح غلطی کے پہنچ جاتی ہے۔
7B پیرامیٹرز کے تحت ماڈلز کو اکثر اس JSON فارمیٹ کی خرابی کا سامنا کرنا پڑتا ہے۔ 7-9B رینج پروڈکشن میں قابل اعتماد ٹول کالز کے لیے کم از کم قابل عمل درجہ ہے۔
باب 1: متعدد ایجنٹوں کو کب استعمال کرنا ہے۔
اس سے پہلے کہ آپ کوئی کوڈ لکھیں، آپ کو ایک سوال کا جواب دینا ہوگا جسے زیادہ تر ملٹی ایجنٹ ٹیوٹوریل مکمل طور پر چھوڑ دیتے ہیں: کیا واقعی اس مسئلے کو متعدد ایجنٹوں کی ضرورت ہے؟
یہ اہم ہے کیونکہ ایجنٹوں کو شامل کرنا حقیقی اخراجات کے ساتھ آتا ہے۔ زیادہ ایجنٹوں کا مطلب ہے زیادہ حرکت پذیر حصے، ناکامی کے زیادہ ممکنہ پوائنٹس، مشترکہ حالت جو متعدد سمتوں میں کرپٹ ہو سکتی ہے، اور ڈیبگنگ جس کے لیے عمل کی حدود میں بعد میں عمل درآمد کی ضرورت ہوتی ہے۔ اچھے اوزاروں کے ساتھ ایک ہی ایجنٹ اکثر آسان، تیز، اور زیادہ قابل اعتماد حل ہوتا ہے۔
تو سوال یہ نہیں ہے کہ "کیا مجھے ایک سے زیادہ ایجنٹ استعمال کرنا چاہئے؟” گویا ملٹی ایجنٹ فطری طور پر برتر ہے۔ سوال یہ ہے کہ، "کیا میرے مسئلے میں کوئی ایسی خصوصیات ہیں جو ٹیوننگ اوور ہیڈ کو درست ثابت کرتی ہیں؟”
1.1 جب ایک ہی ایجنٹ جواب ہوتا ہے۔
اگر آپ کے مسئلے میں ایک بنیادی کام ہے جو ایک سیاق و سباق کی کھڑکی میں فٹ بیٹھتا ہے، تو ایک ایجنٹ عام طور پر صحیح فن تعمیر ہوتا ہے۔
ایک ایجنٹ جو موضوعات کی تحقیق اور خلاصہ کرتا ہے: ایک کام، ایک سیاق و سباق کی کھڑکی، ایک ایجنٹ۔ ایجنٹ پل کی درخواستوں کا جائزہ لے رہے ہیں اور تبصرے پوسٹ کر رہے ہیں: ایک کام۔ علمی بنیاد سے گاہک کے سوالات کا جواب دینے والے ایجنٹ: ایک کام۔ ایک ایجنٹ جو دستاویزات سے ساختی ڈیٹا نکالتا ہے: ایک کام۔
ان صورتوں میں، دوسرے ایجنٹ کو شامل کرنے سے کچھ بھی آسان نہیں ہوتا ہے۔ تعمیراتی فائدے کے بغیر، اس میں کوآرڈینیشن لیئرز، مشترکہ ریاستی معاہدوں، نئی خرابی کی سطحیں، اور ڈیبگنگ کی پیچیدگی شامل ہوتی ہے۔ ایک ہی ایجنٹ پورا کام انجام دیتا ہے۔ اسے اچھے اوزار دیں اور یہ کام کرے گا۔
سنگل ایجنٹ کا ماڈل آسان ہے۔
User input → Agent (with tools) → Response
ایجنٹ ٹولز کو ایک لوپ میں کال کر سکتے ہیں ( بازیافت، پڑھنا، لکھنا، چیک کرنا)، لیکن صحیح ٹول تک رسائی کے ساتھ واحد LLM پورے کام کو ہینڈل کرتا ہے۔ یہ سب سے زیادہ AI آٹومیشن کاموں کے لیے صحیح نقطہ آغاز اور اکثر صحیح اختتامی نقطہ ہے۔
1.2 ملٹی ایجنٹ کے عملی معیارات
اگر کوئی مسئلہ ہے تو آپ کو متعدد ایجنٹوں کی ضرورت ہوگی۔ واقعی ایک امتیازی خصوصیت: ذیلی کاموں میں اتنے مختلف ٹولز، LLM کال پیٹرن، درجہ حرارت کے تقاضے، یا ناکامی کے موڈز ہوتے ہیں جنہیں ایک ایجنٹ میں جوڑنے سے اس کے حل سے زیادہ مسائل پیدا ہوتے ہیں۔
کوآرڈینیشن اوور ہیڈ کو جواز فراہم کرنے والی مخصوص شرائط میں شامل ہیں:
مختلف ذیلی کاموں کے لیے مختلف ٹولز
جب ورک فلو کے ایک حصے کو فائل سسٹم تک رسائی کی ضرورت ہوتی ہے، دوسرے کو ڈیٹا بیس رائٹ کی ضرورت ہوتی ہے، اور تیسرے کو بیرونی APIs کو کال کرنے کی ضرورت ہوتی ہے، ایجنٹ کی علیحدگی کے لیے قدرتی حدود ہوتی ہیں۔
چونکہ ہر ایجنٹ صرف وہی ٹولز استعمال کرتا ہے جس کی اسے ضرورت ہوتی ہے، اس لیے انفرادی طور پر ہر ایجنٹ کے بارے میں جانچ اور استدلال کرنا آسان ہے۔
ایل ایل ایم کال کے مختلف نمونے۔
کچھ آپریشنز کے لیے سنگل سٹرکچرڈ آؤٹ پٹ کال کی ضرورت ہوتی ہے، جیسے: temperature=0. دوسروں کو ملٹی روٹیشن ٹول کال لوپ کی ضرورت ہوتی ہے جو اس وقت ختم ہو جاتی ہے جب LLM اس بات کا تعین کرتا ہے کہ کافی سیاق و سباق موجود ہے۔
ان نمونوں کو ایک ایجنٹ میں ملانے سے ایک ایسا فنکشن بنتا ہے جو بہت زیادہ کام کرتا ہے اور مختلف طریقوں سے ناکام ہو جاتا ہے اس راستے پر منحصر ہے۔
مختلف درجہ حرارت اور ماڈل کی ضروریات
ساختی منصوبہ بندی کے نتائج مستقل مزاجی کے لیے کم درجہ حرارت چاہتے ہیں۔ تخلیقی وضاحتیں مختلف قسم کے لیے قدرے زیادہ درجہ حرارت چاہتی ہیں۔ اسکورنگ میں، تجزیاتی مستقل مزاجی کے لیے کم درجہ حرارت مطلوب ہے۔
جب یہ تین کام ایک درجہ حرارت کی ترتیب کے ساتھ ایک ایجنٹ کا اشتراک کرتے ہیں، تو تمام سمتوں میں سمجھوتہ ہوتا ہے۔
فالٹ آئسولیشن کی ضروریات
سب ٹاسک کے درمیان حدود کی ضرورت ہوتی ہے جب ایک سب ٹاسک دوسرے سب ٹاسک کو روکے بغیر ناکام ہو سکتا ہے۔ نصاب کی منصوبہ بندی کرنے والے ایجنٹ کامیاب ہو سکتے ہیں چاہے کوئز گریڈنگ کی خدمات عارضی طور پر معطل کر دی جائیں۔ اگر آپ اسی ناکامی کی سطح کے ساتھ ایک ہی عمل میں ہیں، تو درجہ بندی کی خرابی بھی آپ کے منصوبے کو خراب کر دے گی۔
آزاد تعیناتی کے تقاضے
جب نظام کے مختلف حصوں کو مختلف پیمانے پر چلانے، آزادانہ طور پر اپ ڈیٹ ہونے، یا مختلف فریم ورکس کا استعمال کرتے ہوئے متعدد ٹیموں کے ذریعے تعمیر کرنے کی ضرورت ہو تو تعیناتی علیحدگی کے لیے ایجنٹ سے علیحدگی کا نقشہ۔ A2A پروٹوکول (باب 8) اس کو ٹھوس بناتا ہے۔
فریم ورک میں تعاون
اگر آپ ایک کام کے لیے CrewAI ایجنٹ اور دوسرے کام کے لیے LangGraph ایجنٹ استعمال کرنا چاہتے ہیں، تو آپ کو مواصلات کے لیے ایک پروٹوکول کی ضرورت ہوگی کیونکہ ہر فریم ورک کے مختلف فوائد ہوتے ہیں۔ پروٹوکول A2A ہے۔
ان حالات میں سے کوئی بھی ایک سے زیادہ ایجنٹوں کی ضرورت نہیں ہے۔ ان میں سے دو شاید ہیں۔ یہ سب مضبوط دعوے کرتے ہیں۔
1.3 جو آپ ادا کرتے ہیں۔
ملٹی ایجنٹ فن تعمیر کو اپنانے سے پہلے، اس بات کی وضاحت کریں کہ آپ اس کے لیے کتنی رقم ادا کرنے کو تیار ہیں۔
مشترکہ ریاست کی پیچیدگی: تمام ایجنٹ مشترکہ ریاستی اشیاء کو پڑھتے اور لکھتے ہیں۔ اگر دو ایجنٹ ایک ہی فیلڈ میں لکھ رہے ہیں تو انضمام کی حکمت عملی درکار ہے۔ اگر ایک ایجنٹ غلط ڈیٹا لکھتا ہے، تو اس کے بعد کے تمام ایجنٹوں کو غلط ان پٹ ملے گا۔
ریاست کی تعریف ایک معاہدہ بن جاتی ہے جس پر تمام ایجنٹوں کو عمل کرنا چاہیے، اور اس معاہدے میں تبدیلیوں کے لیے تمام ایجنٹوں کو اپ ڈیٹ کرنے کی ضرورت ہوتی ہے۔
ڈیبگ کرنا زیادہ مشکل: ایک ہی ایجنٹ کی خامیاں ایک اسٹیک ٹریس میں ظاہر ہوتی ہیں۔ ملٹی ایجنٹ سسٹم میں خرابیاں مرحلہ 3 سے پہلے غلط آؤٹ پٹ کی وجہ سے ہو سکتی ہیں، جو پھر حالت میں برقرار رہتی ہے اور دوسرے ایجنٹ کو منتقل ہو جاتی ہے، جس کی وجہ سے وہ آؤٹ پٹ پیدا ہوتا ہے جو آپ فی الحال دیکھ رہے ہیں۔ causal سلسلہ اداکار کی حدود کو عبور کرتا ہے۔
تاخیر میں اضافہ: ہر ایجنٹ ایک یا زیادہ LLM کال کرتا ہے۔ چار ایجنٹوں کا نظام فی سیشن کم از کم چار LLM کال کرے گا، اکثر زیادہ اگر ایجنٹ ٹولز کو ایک لوپ میں استعمال کریں۔ 2-5 سیکنڈ فی اولاما کال پر، اس میں تیزی سے اضافہ ہوتا ہے۔
مزید بنیادی ڈھانچہ: ملٹی ایجنٹ سسٹم ریاستی استقامت، مشاہدہ، تشخیص، اور انسانی نگرانی سے فائدہ اٹھاتے ہیں، جن کو ترتیب دینے میں وقت لگتا ہے۔ ایک واحد ایجنٹ اکثر ان خصوصیات کے بغیر چل سکتا ہے۔ پیداواری ماحول میں ملٹی ایجنٹ سسٹم واقعی ایسا نہیں کر سکتا۔
ہمیں ان اخراجات سے آگاہ ہونے کی ضرورت ہے اور ان مخصوص فوائد کی وضاحت کرنے کے قابل ہونا چاہیے جو ملٹی ایجنٹ فن تعمیر میں جانے کا جواز پیش کرتے ہیں۔
1.4 یہ سسٹم 4 ایجنٹوں کو کیوں استعمال کرتا ہے؟
لرننگ ایکسلریٹر چار ایجنٹوں کا استعمال کرتا ہے۔ یہاں ہر علیحدگی کے لئے ایک واضح تکنیکی جواز ہے۔ ایک بار پھر، یہ اس لیے نہیں ہے کہ ملٹی ایجنٹ بہتر ہے، بلکہ اس لیے کہ یہ چار کام کافی حد تک مختلف ہیں کہ دونوں کو ملانے کے نتیجے میں مشترکہ ایجنٹ دونوں میں بدتر ہو جائے گا۔
| نائب | فنکشن | الگ ایجنٹ کیوں؟ |
|---|---|---|
| نصاب منصوبہ ساز | سیکھنے کے اہداف طے کریں اور ایک منظم سیکھنے کا روڈ میپ بنائیں۔ | ایک LLM کال؛ temperature=0.1، format="json". زیرو ٹولز۔ یہ تیز، تعیین پسند ہے، اور غلط ان پٹ پر تیزی سے ناکام ہو جاتا ہے۔ یہاں ٹول کال کے رویے کو مکس کرنے سے ساختی آؤٹ پٹ میں شور شامل ہوتا ہے۔ |
| وضاحت کرنے والا | MCP سورس نوٹس پڑھتا ہے اور طلباء کو موضوعات کی وضاحت کرتا ہے۔ | ایک سے زیادہ گردش ٹول کال لوپس۔ temperature=0.3. لوپس کی تعداد غیر مقررہ ہے۔ LLM طے کرتا ہے کہ سیاق و سباق کب کافی ہے۔ عمل درآمد کا نمونہ پلانر سے بالکل مختلف ہے۔ |
| کوئز جنریٹر | سوالات (تخلیقی) اور اسکور جوابات (تجزیاتی) بنائیں۔ | مختلف درجہ حرارت کے ساتھ دو الگ الگ ایل ایل ایم کالز۔ انٹرایکٹو: صارف کے ان پٹ کے لیے موقوف۔ یہ اسٹینڈ اسٹون A2A سروس (باب 8) کے طور پر بھی چلتا ہے۔ اگر یہ کسی دوسرے ایجنٹ کے ساتھ بنڈل ہو تو ایسا نہیں کیا جا سکتا۔ |
| ترقی کوچ | مجموعی نتائج، موضوع کی حیثیت کو اپ ڈیٹ کریں، اور اگلے موضوع یا باہر نکلنے کا راستہ۔ | منفرد ایجنٹ سے ایجنٹ A2A کالز کرتا ہے (CrewAI Study Buddy کو)۔ MCP میموری کو پڑھتا اور لکھتا ہے۔ روٹنگ کے فیصلوں کا انتظام کرتا ہے جو اس بات کا تعین کرتا ہے کہ آیا گراف لوپ ہوتا ہے یا ختم ہوتا ہے۔ |
نصاب کے منصوبہ ساز اور وضاحت کنندہ ہی علیحدگی کا جواز پیش کرتے ہیں۔ ایک بغیر کسی ٹولز کے سٹرکچرڈ JSON آؤٹ پٹ انجام دیتا ہے، اور دوسرا ملٹی ٹرن ٹول کال لوپ انجام دیتا ہے۔ اسے ایک ایجنٹ میں ڈالنے کا مطلب ہے ایک فنکشن جو کبھی کبھی ٹول کو لوپ میں کال کرتا ہے اور کبھی کبھی مختلف درجہ حرارت پر مختلف قسم کی آؤٹ پٹ واپس کرتا ہے۔ یہ صلاحیتوں کی ایک وسیع رینج کے ساتھ ایک ایجنٹ نہیں ہے. ایک ہونے کا بہانہ کرنے والے دو ایجنٹ ہیں۔
کوئز جنریٹر کا دوہرا درجہ حرارت پیٹرن (تخلیقی سوال پیدا کرنے کے لیے 0.4، تجزیاتی اسکورنگ کے لیے 0.1) اور یہ حقیقت کہ اسے اسٹینڈ اسٹون A2A سروس کے طور پر چلانے کی ضرورت ہے اس کی اپنی حدود کی حمایت کرتا ہے۔
سہولت کوچ کوآرڈینیٹر ہے۔ یہ سب ایک ساتھ رکھیں اور روٹنگ کے فیصلے کریں۔ دوسرے ایجنٹوں کے ساتھ اشتراک کرنا بالکل غلط چیز ہے۔
یہ آپ کے اپنے مسائل میں تلاش کرنے کے قابل ایک نمونہ ہے۔ اگر آپ اس بات کی وضاحت نہیں کر سکتے ہیں کہ دونوں اعمال کو ایک ہی ایجنٹ کیوں ہونا چاہیے، تو شاید وہ نہیں ہونا چاہیے۔
یہی استدلال پیداواری نظام پر لاگو ہوتا ہے۔ تعمیل کے تربیتی پلیٹ فارم میں ایک نصاب ایجنٹ (ایک سرٹیفیکیشن کا راستہ بناتا ہے)، ایک مواد کی ترسیل کا ایجنٹ (مواد MCP سرورز سے ریگولیٹڈ مواد فراہم کرتا ہے)، ایک تشخیصی ایجنٹ (ٹیسٹ فہم، ریکارڈ کے نتائج)، اور ایک سرٹیفیکیشن ایجنٹ (آمادگی کا اندازہ کرتا ہے، سرٹیفکیٹ جاری کرتا ہے)۔
ہر ایک کے پاس مختلف ٹولز، مختلف ناکامی کے طریقے، اور مختلف اپ ڈیٹ سائیکل ہوتے ہیں۔ علیحدگی ایک تعمیراتی فلسفہ نہیں ہے۔ یہ ہر کام کی ضرورت کا براہ راست نتیجہ ہے۔
1.5 پروجیکٹ کی ترتیبات
اب جب کہ ہمارا تعمیراتی استدلال قائم ہو چکا ہے، آئیے نظام کی تعمیر کریں۔
اولاما انسٹال کریں اور اپنا ماڈل درآمد کریں۔
اولاما مقامی LLM کو OpenAI کے موافق سرور کے طور پر چلاتا ہے۔ localhost:11434.
میکوس اور لینکس:
curl -fsSL https://ollama.com/install.sh | sh
ونڈوز: ollama.com سے انسٹالر ڈاؤن لوڈ اور چلائیں۔
وہ ماڈل منتخب کریں جو آپ کے ہارڈ ویئر سے مماثل ہو:
# 8 GB VRAM
ollama pull qwen2.5:7b
# 24 GB VRAM: stronger tool calling, recommended if you have it
ollama pull qwen2.5-coder:32b
# Verify it works
ollama run qwen2.5:7b "Say hello in one sentence."
ایک مختصر جواب دکھایا جائے گا۔ اولاما کو بیک گراؤنڈ سرور کے طور پر چلاتے رہیں۔ کالوں کے درمیان فعال رہتا ہے۔
ذخیرہ نقل
git clone https://github.com/sandeepmb/freecodecamp-multi-agent-ai-system
cd freecodecamp-multi-agent-ai-system
ورچوئل ماحول کا سیٹ اپ
python -m venv .venv
source .venv/bin/activate # Windows: .venvScriptsactivate
pip install -r requirements.txt
کہ requirements.txt ٹیسٹ شدہ ورژن پر تمام انحصار پن کریں۔
# requirements.txt
langgraph==1.1.0
langgraph-checkpoint-sqlite==3.0.3
langchain-core==1.0.0
langchain-ollama==1.0.0
mcp==1.26.0
a2a-sdk==0.3.25
crewai==1.13.0
langfuse==4.0.1
deepeval==3.9.1
litellm==1.82.4
openai==2.8.0
httpx==0.28.1
fastapi==0.115.0
uvicorn==0.34.0
streamlit==1.43.2
pydantic==2.11.9
python-dotenv==1.1.1
tenacity==8.5.0
pytest==8.3.0
pytest-asyncio==0.25.0
انحصاری ورژن کو اپ گریڈ نہ کریں۔ اس اسٹیک میں ایجنٹ کے فریم ورک، خاص طور پر LangGraph، langchain-core، اور A2A SDK میں معمولی ورژن کے درمیان تبدیلیاں ہوتی ہیں۔ فکسڈ ورژن ایک ساتھ آزمائے جاتے ہیں۔ چل رہا ہے pip install --upgrade ان میں سے کسی میں بھی آپ کی آمدنی یا اعمال کے ٹوٹنے کا خطرہ ہے۔
ماحولیات کی ترتیب
cp .env.example .env
کھلا .env اپنا ماڈل ترتیب دیں۔
# .env: set this to match what you pulled
OLLAMA_MODEL=qwen2.5:7b
OLLAMA_BASE_URL=http://localhost:11434
# Storage
CHECKPOINT_DB=data/checkpoints.db
NOTES_PATH=study_materials/sample_notes
# A2A services (used in Chapter 8)
QUIZ_SERVICE_URL=http://localhost:9001
STUDY_BUDDY_URL=http://localhost:9002
USE_A2A_QUIZ=true
USE_STUDY_BUDDY=true
# Langfuse: leave empty for now, configured in Chapter 6
LANGFUSE_PUBLIC_KEY=
LANGFUSE_SECRET_KEY=
LANGFUSE_HOST=http://localhost:3000
ترتیبات چیک کریں۔
python main.py --help
آپ کو بغیر کسی غلطی کے argparse مدد آؤٹ پٹ دیکھنا چاہئے۔ اگر آپ درآمد کی خرابیاں دیکھتے ہیں، تو یقینی بنائیں کہ آپ کا ورچوئل ماحول فعال ہے۔
چوکی: اولاما چل رہا ہے، انحصار انسٹال ہے، اور ماحول کو ترتیب دیا گیا ہے۔ منصوبے کا ڈھانچہ مندرجہ ذیل ہے:
freecodecamp-multi-agent-ai-system/
├── src/
│ ├── agents/ # LangGraph agent nodes
│ ├── graph/ # State definition and workflow
│ ├── mcp_servers/ # MCP tool servers
│ ├── a2a_services/ # A2A protocol services and client
│ ├── crewai_agent/ # CrewAI agent served via A2A
│ └── observability/ # Langfuse setup
├── tests/ # Unit and evaluation tests
├── study_materials/
│ └── sample_notes/ # Markdown files the Explainer reads
├── docs/
├── data/ # SQLite checkpoint DB (created at runtime)
├── main.py
├── Makefile
├── docker-compose.yml # Langfuse local stack
├── requirements.txt
└── .env.example
تمام src/ یہ معیاری ازگر کی پیروی کرتا ہے۔ src/ احتیاط سے درج ہے۔ کہ pyproject.toml شامل کریں src/ اپنے ازگر کے راستے پر تاکہ آپ کے ٹیسٹ اسے درآمد کرسکیں۔ from graph.state import AgentState پاتھ جمناسٹک کے بغیر۔
اگلے باب میں، آپ سسٹم کا پہلا حصہ بنائیں گے، ایک لینگ گراف گراف جو چاروں ایجنٹوں کو مربوط کرتا ہے۔ ہم ایک مشترکہ ریاست کی وضاحت کرتے ہوئے شروع کرتے ہیں جسے تمام ایجنٹ پڑھتے لکھتے ہیں۔
باب 2: لینگ گراف کے ساتھ ریاستی آرکیسٹریشن
لینگ گراف ماڈلز ملٹی ایجنٹ ورک فلوز کو ہدایت شدہ گراف کے طور پر پیش کرتا ہے۔ نوڈز ازگر کے افعال ہیں، یعنی ایجنٹ کوڈ۔ کنارے ان کے درمیان روٹنگ کی وضاحت کرتے ہیں۔ تمام نوڈس مشترکہ اسٹیٹ آبجیکٹ سے پڑھتے اور لکھتے ہیں۔ ایک لینگ گراف چیک پوائنٹ جو تمام نوڈس کے چلنے کے بعد SQLite میں ریاست کی نمائندگی کرتا ہے۔
آخری حصہ وہ ہے جو اسے سہولت ریپر کے بجائے پروڈکشن ٹول بناتا ہے۔ ایک بولی ملٹی ایجنٹ لوپ اس طرح لکھا ہوا ہے: for جس لمحے ایک لوپ کریش ہوتا ہے، وہ سب کچھ کھو دیتا ہے۔ لینگ گراف نہیں کرتا۔ چوکیاں حادثات سے بچ جاتی ہیں۔ graph.invoke() اگر آپ وہی سیشن ID استعمال کرتے ہیں، تو یہ بالکل وہیں سے دوبارہ شروع ہو جائے گا جہاں سے اس نے چھوڑا تھا۔
اس باب میں، ہم گراف فاؤنڈیشن بناتے ہیں، جس میں چاروں ایجنٹوں کے ذریعے استعمال ہونے والی مشترکہ ریاست کی تعریف، پہلا ٹاسک ایجنٹ نوڈ، اور گراف جو ان کو جوڑتا ہے۔
2.1 مشترکہ ریاست
گراف میں تمام نوڈس کی مکمل حالت ہوتی ہے۔ dict ایک جزوی اپ ڈیٹ لوٹاتا ہے، بشمول صرف بدلی ہوئی چابیاں۔ لینگ گراف ان اپ ڈیٹس کو مجموعی حالت میں ضم کرتا ہے اور اگلے نوڈ کو کال کرنے سے پہلے چیک پوائنٹ کو محفوظ کرتا ہے۔
ریاست کی تعریف src/graph/state.py چار ڈیٹا کلاسز کے ساتھ شروع کریں جن میں سٹرکچرڈ ڈیٹا ہوتا ہے، پھر AgentState TypedDict لینگ گراف کے زیر انتظام:
# src/graph/state.py
from __future__ import annotations
import json
from dataclasses import dataclass, field, asdict
from typing import Annotated, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
@dataclass
class Topic:
"""A single topic within the study roadmap."""
title: str
description: str
estimated_minutes: int
prerequisites: list[str] = field(default_factory=list)
# pending → in_progress → completed | needs_review
status: str = "pending"
def to_dict(self) -> dict:
return asdict(self)
@classmethod
def from_dict(cls, data: dict) -> "Topic":
return cls(
title=data["title"],
description=data["description"],
estimated_minutes=data["estimated_minutes"],
prerequisites=data.get("prerequisites", []),
status=data.get("status", "pending"),
)
@dataclass
class StudyRoadmap:
"""The full study plan produced by the Curriculum Planner."""
goal: str
total_weeks: int
topics: list[Topic]
weekly_hours: int = 5
def is_complete(self) -> bool:
return all(t.status in ("completed", "needs_review") for t in self.topics)
@dataclass
class QuizResult:
"""The complete result of one quiz session on a single topic."""
topic: str
questions: list
score: float # 0.0 to 1.0
weak_areas: list[str]
timestamp: str = ""
def passed(self) -> bool:
return self.score >= 0.5
class AgentState(TypedDict):
"""
The shared state for the Learning Accelerator graph.
Partial updates: when a node returns {"approved": True}, LangGraph
merges that into the existing state. It does NOT replace the whole dict.
Nodes only return the keys they changed.
The one exception is `messages`: it uses the add_messages reducer,
which appends to the list instead of replacing it.
"""
messages: Annotated[list[BaseMessage], add_messages]
session_id: str
goal: str
roadmap: StudyRoadmap | None
approved: bool
current_topic_index: int
quiz_results: list[QuizResult]
weak_areas: list[str]
study_materials_path: str
error: str | None
یہاں ڈیزائن کے چند فیصلے ہیں جو سمجھنے کے قابل ہیں۔
باقاعدہ کلاس کیوں اور ٹائپڈکٹ کیوں نہیں؟ LangGraph ایک dict مطابقت پذیر آبجیکٹ کی ضرورت ہے۔ TypedDict لغت کی مطابقت کو برقرار رکھتے ہوئے قسم کی حفاظت فراہم کرتا ہے (IDE غلط ہجے والی کلیدوں کو پکڑتا ہے)۔ اس مخصوص استعمال کے کیس کے لیے یہ ایک بہترین ٹول ہے۔
کیوں add_messages کو messages میدان؟ دیگر تمام شعبوں AgentState آخری تحریر جیت سیمنٹکس کا استعمال کرتا ہے۔ جب دو نوڈ لکھتے ہیں: roadmapدوسرا جیت جاتا ہے۔ لیکن بات چیت کے پیغامات مجموعی ہونے چاہئیں۔ کہ add_messages ریڈوسر لینگ گراف کو فہرست کو تبدیل کرنے کے بجائے نئے پیغامات شامل کرنے کی ہدایت کرتا ہے۔ یہ تمام ایجنٹ کالوں کی مکمل گفتگو کی تاریخ کو محفوظ رکھتا ہے۔
ہمیں ڈیٹا کلاسز کی ضرورت کیوں ہے؟ Topic، StudyRoadmapاور QuizResult? اس کی وجہ یہ ہے کہ ایجنٹوں کو غلطی سے چابیاں داخل کیے بغیر ساختی ڈیٹا کو پڑھنا اور اپ ڈیٹ کرنا چاہیے۔ topic.title اٹھانا AttributeError اگر میدان موجود نہ ہو تو فوراً۔ topic["titl"] خاموشی سے واپس آ جاؤ None. سٹرکچرڈ ڈیٹا کے لیے جسے متعدد ایجنٹوں کے ذریعے چھو لیا جاتا ہے، ڈیٹا کی کلاسیں باقاعدہ لغات سے زیادہ محفوظ ہوتی ہیں۔
کہ src/graph/state.py فائل میں تین افادیت کے افعال بھی شامل ہیں جو ایجنٹ نوڈس محفوظ طریقے سے حالت کو پڑھنے کے لیے استعمال کرتے ہیں۔
# src/graph/state.py (continued)
def initial_state(
goal: str,
session_id: str,
study_materials_path: str = "study_materials/sample_notes",
) -> dict:
"""Create the initial state for a new study session."""
return {
"messages": [],
"session_id": session_id,
"goal": goal,
"roadmap": None,
"approved": False,
"current_topic_index": 0,
"quiz_results": [],
"weak_areas": [],
"study_materials_path": study_materials_path,
"error": None,
}
def get_current_topic(state: dict) -> Topic | None:
"""Get the topic currently being studied, or None if done."""
roadmap = state.get("roadmap")
if roadmap is None:
return None
idx = state.get("current_topic_index", 0)
if idx >= len(roadmap.topics):
return None
return roadmap.topics[idx]
def session_is_complete(state: dict) -> bool:
"""True when all topics have been studied."""
roadmap = state.get("roadmap")
if roadmap is None:
return True
idx = state.get("current_topic_index", 0)
return idx >= len(roadmap.topics)
initial_state() اس طرح میں ہمیشہ ایک نیا سیشن بناتا ہوں۔ دستی طور پر کبھی بھی ڈکٹ نہ بنائیں۔ اس بات کو یقینی بنائیں کہ تمام فیلڈز میں درست ڈیفالٹ اقدار ہیں اور مطلوبہ کلیدیں نادانستہ طور پر غائب نہیں ہیں۔
2.2 نصاب کا منصوبہ ساز: پہلا ایجنٹ نوڈ
کریکولم پلانر سسٹم کا سب سے آسان ایجنٹ ہے، ایک LLM کال، ایک JSON جواب، اور ایک ڈیٹا کلاس آؤٹ پٹ کے ساتھ۔ کوئی ٹولز، کوئی لوپس نہیں۔ یہ اس پیٹرن کو ظاہر کرتا ہے جس پر تمام ایجنٹ پیروی کرتے ہیں: اسٹیٹ پڑھیں، ایل ایل ایم کو کال کریں، آؤٹ پٹ کو پارس کریں، جزوی اسٹیٹ اپ ڈیٹ واپس کریں۔
# src/agents/curriculum_planner.py
import json
import os
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_ollama import ChatOllama
from graph.state import StudyRoadmap, Topic
MODEL_NAME = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
PLANNER_SYSTEM_PROMPT = """You are an expert curriculum designer. Your job is to
create a structured study roadmap when given a learning goal.
Return ONLY valid JSON with no prose, no markdown code fences, no explanation.
The JSON must match this exact schema:
{
"goal": "the original learning goal exactly as given",
"total_weeks": ,
"weekly_hours": ,
"topics": [
{
"title": "Short topic name (3-6 words)",
"description": "One clear sentence explaining what this topic covers",
"estimated_minutes": ,
"prerequisites": ["title of earlier topic if required, else empty list"],
"status": "pending"
}
]
}
Rules:
- Order topics from foundational to advanced
- prerequisites must reference earlier topic titles exactly as written
- Aim for 4 to 6 topics
- status must always be "pending"
"""
یہاں ماڈل ترتیب دینے کے بارے میں دو چیزیں ہیں۔ سب سے پہلے temperature=0.1. یہ بہت کم ہے کیونکہ ساختی JSON آؤٹ پٹ کو مستقل مزاجی کی ضرورت ہوتی ہے۔ زیادہ درجہ حرارت ایسی تبدیلیاں متعارف کراتے ہیں جو JSON کی تجزیہ کو ناقابل اعتبار بنا دیتے ہیں۔
دوسرا format="json". یہ اولاما کا JSON موڈ ہے، جو ایک قیاس کی سطح کی رکاوٹ ہے۔ اس سے قطع نظر کہ پرامپٹ کیا پوچھتا ہے، آپ کا ماڈل ایسا آؤٹ پٹ نہیں بنا سکتا جو درست JSON نہیں ہے۔ یہ ماڈل کو JSON کو سسٹم پرامپٹ پر آؤٹ پٹ کرنے کے کہنے سے زیادہ طاقتور ہے۔
def build_planner_llm() -> ChatOllama:
return ChatOllama(
model=MODEL_NAME,
base_url=OLLAMA_BASE_URL,
temperature=0.1,
format="json",
)
تجزیہ کار کو جان بوجھ کر نوڈ کی فعالیت سے الگ کیا گیا ہے۔ یہ آپ کو LLM کو کال کیے بغیر آزادانہ طور پر ٹیسٹ کرنے کی اجازت دیتا ہے۔ تمام 11 یونٹ ٹیسٹ tests/test_curriculum_planner.py کال parse_roadmap_json() براہ راست:
def parse_roadmap_json(json_string: str) -> StudyRoadmap:
"""Parse the LLM's JSON output into a StudyRoadmap dataclass."""
try:
data = json.loads(json_string)
except json.JSONDecodeError as e:
raise ValueError(
f"LLM returned invalid JSON.n"
f"Error: {e}n"
f"Raw output (first 300 chars): {json_string[:300]}"
)
required = ["goal", "total_weeks", "topics"]
for field in required:
if field not in data:
raise ValueError(f"LLM JSON missing required field: '{field}'")
if not isinstance(data["topics"], list) or len(data["topics"]) == 0:
raise ValueError("LLM JSON 'topics' must be a non-empty list")
topics = []
for i, t in enumerate(data["topics"]):
for field in ["title", "description", "estimated_minutes"]:
if field not in t:
raise ValueError(f"Topic {i} missing required field: '{field}'")
topics.append(Topic(
title=t["title"],
description=t["description"],
estimated_minutes=int(t["estimated_minutes"]),
prerequisites=t.get("prerequisites", []),
status=t.get("status", "pending"),
))
return StudyRoadmap(
goal=data["goal"],
total_weeks=int(data["total_weeks"]),
weekly_hours=int(data.get("weekly_hours", 5)),
topics=topics,
)
نوڈ کی فعالیت خود اس نظام میں تمام ایجنٹوں کے ذریعہ استعمال کردہ ایک ہی پیٹرن کی پیروی کرتی ہے۔
def curriculum_planner_node(state: dict) -> dict:
"""
LangGraph node: Curriculum Planner
Reads: state["goal"]
Writes: state["roadmap"], state["messages"], state["error"]
"""
goal = state.get("goal", "").strip()
if not goal:
return {"error": "No learning goal provided."}
print(f"n[Curriculum Planner] Building roadmap for: '{goal}'")
llm = build_planner_llm()
messages = [
SystemMessage(content=PLANNER_SYSTEM_PROMPT),
HumanMessage(content=f"Create a study roadmap for: {goal}"),
]
print(f"[Curriculum Planner] Calling {MODEL_NAME}...")
response = llm.invoke(messages)
try:
roadmap = parse_roadmap_json(response.content)
except ValueError as e:
print(f"[Curriculum Planner] Parse error: {e}")
return {
"error": str(e),
"messages": messages + [response],
}
print(f"[Curriculum Planner] Created {len(roadmap.topics)} topics")
# Return ONLY the keys this node changed
return {
"roadmap": roadmap,
"messages": messages + [response],
"error": None,
}
واپسی کی قیمت چیک کریں۔ {"roadmap": roadmap, "messages": ..., "error": None}. یہ ایک مکمل ریاست نہیں ہے۔ اس نوڈ کے ذریعے چھونے والی صرف تین چابیاں ہیں۔ لینگ گراف اسے موجودہ حالت میں ضم کرتا ہے۔ باقی تمام فیلڈز بدستور برقرار ہیں۔
2.3 گراف کی تعریف
گراف کنکشن ہیں، منطق نہیں۔ تمام کاروباری منطق ایجنٹ ماڈیول میں رہتی ہے۔ src/graph/workflow.py یہ صرف اس بات کی وضاحت کرتا ہے کہ کون سے نوڈس موجود ہیں، وہ کیسے جڑے ہوئے ہیں، اور روٹنگ فنکشن کیا فیصلے کرتا ہے۔
# src/graph/workflow.py
import os
import sqlite3
from pathlib import Path
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import END, START, StateGraph
from agents.curriculum_planner import curriculum_planner_node
from agents.explainer import explainer_node
from agents.human_approval import human_approval_node
from agents.progress_coach import progress_coach_node
from agents.quiz_generator import quiz_generator_node
from graph.state import AgentState, session_is_complete
def route_after_approval(state: dict) -> str:
if state.get("approved", False):
return "explainer"
return "curriculum_planner"
def route_after_coach(state: dict) -> str:
if session_is_complete(state):
return "end"
return "explainer"
def build_graph(
db_path: str = "data/checkpoints.db",
interrupt_before: list | None = None,
):
Path("data").mkdir(exist_ok=True)
if db_path == "data/checkpoints.db":
db_path = os.getenv("CHECKPOINT_DB", db_path)
builder = StateGraph(AgentState)
# Register all five nodes
builder.add_node("curriculum_planner", curriculum_planner_node)
builder.add_node("human_approval", human_approval_node)
builder.add_node("explainer", explainer_node)
builder.add_node("quiz_generator", quiz_generator_node)
builder.add_node("progress_coach", progress_coach_node)
# Static edges
builder.add_edge(START, "curriculum_planner")
builder.add_edge("curriculum_planner", "human_approval")
builder.add_edge("explainer", "quiz_generator")
builder.add_edge("quiz_generator", "progress_coach")
# Conditional edges
builder.add_conditional_edges(
"human_approval",
route_after_approval,
{"explainer": "explainer", "curriculum_planner": "curriculum_planner"},
)
builder.add_conditional_edges(
"progress_coach",
route_after_coach,
{"explainer": "explainer", "end": END},
)
# IMPORTANT: create the connection directly, not via context manager.
# SqliteSaver.from_conn_string() returns a context manager. If you use
# `with SqliteSaver.from_conn_string(...) as checkpointer:`, the connection
# closes when the `with` block exits. The graph object lives longer than
# build_graph(), so the connection must stay open for the process lifetime.
conn = sqlite3.connect(db_path, check_same_thread=False)
checkpointer = SqliteSaver(conn)
return builder.compile(
checkpointer=checkpointer,
interrupt_before=interrupt_before or [],
)
graph = build_graph()
SqliteSaver کنکشن پیٹرن
کہ check_same_thread=False ایک جھنڈا درکار ہے۔ SQLite کا ڈیفالٹ رویہ ایک تھریڈ میں بنائے گئے کنکشن کو دوسرے تھریڈ کے استعمال سے روکتا ہے۔
لینگ گراف نوڈ کے افعال کو انجام دیتا ہے اور اندرونی طور پر دوسرے دھاگوں پر چوکیاں لکھتا ہے۔ اس جھنڈے کے بغیر ProgrammingError: SQLite objects created in a thread can only be used in that same thread رن ٹائم پر۔ چونکہ لینگ گراف چیک پوائنٹ رائٹ کو سیریلائز کرتا ہے، اس لیے کوئی ہم آہنگ تحریری تنازعہ نہیں ہے، اس لیے جھنڈا یہاں محفوظ ہے۔
روٹنگ فنکشن خالص ازگر ہے۔ کوئی LLM کالز نہیں ہیں۔ ریاست سے پڑھتا ہے اور ایک تار لوٹاتا ہے۔ وہ تار اس بات کا تعین کرتا ہے کہ کون سا نوڈ آگے چلتا ہے۔ کنٹرول فلو منطق کو پائتھون میں رکھیں، ایل ایل ایم میں نہیں۔ ایل ایل ایم روٹنگ کے فیصلے گراف کے کنٹرول فلو میں عدم استحکام کو متعارف کراتے ہیں، جس سے ان کے بارے میں استدلال کرنا اور جانچنا بہت مشکل ہو جاتا ہے۔
کہ interrupt_before پیرامیٹر کے لیے ڈیفالٹ ویلیو ایک خالی فہرست ہے۔ ٹرمینل انٹرفیس استعمال کرتا ہے: interrupt() اندر human_approval_node جیسا کہ آپ باب 5 میں دیکھیں گے، ہم روڈ میپ کی منظوری کے لیے توقف کرتے ہیں، اس لیے مرتب وقت میں کسی رکاوٹ کی ضرورت نہیں ہے۔
Streamlit UI پاس کریں (باب 9) interrupt_before=["quiz_generator"] کوئز نوڈ کے چلنے سے پہلے گراف کو روکنے کے لیے: input() اسے گراف تھریڈ کے اندر نہیں کہا جاتا ہے۔ ایک ہی گراف بلڈر دونوں طریقوں کو سپورٹ کرتا ہے۔
مکمل گراف درج ذیل ہے:
![LangGraph، MCP، اور A2A کا استعمال کرتے ہوئے ملٹی ایجنٹ AI سسٹم کیسے بنایا جائے۔ [Full Book] 1 لینگ گراف ورک فلو کا فلو چارٹ عمل درآمد کا آرڈر دکھا رہا ہے: START curriculum_planner میں جاتا ہے، پھر ایک رکاوٹ کے ساتھ human_approval جو صارف کے ان پٹ کو روکتا ہے، Route_after_approval ایک فیصلے کے ساتھ ڈائمنڈ برانچنگ آف ڈاٹڈ مشروط کنارے پر ہوتا ہے (approved=true جاری رہتا ہے اور Approved = loops میں looped in the backprovf وضاحت کرتا ہے reject لوپ میں curriculum_planner)۔ وضاحت کنندہ کوئز_جنریٹر میں بہتا ہے، پھر: پروگریس_کوچ میں بہتا ہے، ایک روٹ_بعد_کوچ کا فیصلہ ڈائمنڈ کی شاخ بندی والے مشروط کنارے سے باہر نکلتا ہے۔ (مطالعہ کے لوپ کے طور پر، مزید عنوانات وضاحت کنندہ کی طرف لوٹ جاتے ہیں اور تمام تکمیلیں END کی طرف جاتی ہیں۔) ٹھوس تیر جامد کناروں کی نشاندہی کرتے ہیں، اور ڈیشڈ تیر مشروط کناروں کی نشاندہی کرتے ہیں۔](https://umang.pk/wp-content/uploads/2026/04/1777579829_0_LangGraph،-MCP،-اور-A2A-کا-استعمال-کرتے-ہوئے-ملٹی-ایجنٹ.png)
تصویر 2. مکمل لینگ گراف گراف۔ جامد کنارے سخت ہیں۔ مشروط کناروں کو ڈیشڈ لائنوں سے ظاہر کیا جاتا ہے۔ روٹنگ فنکشن رن ٹائم پر عمل میں لائے جانے والے راستے کا تعین کرتا ہے۔
2.4 چلائیں اور چیک کریں۔
ایک بار جب آپ کے پاس اپنے نصاب کے منصوبہ ساز نوڈس اور گرافس موجود ہیں، تو آپ اپنے پہلے اختتام سے آخر تک ٹیسٹ چلا سکتے ہیں۔
python main.py "Learn Python closures and decorators from scratch"
آپ کو درج ذیل کو چیک کرنا چاہئے:
============================================================
Learning Accelerator
Session ID: a3f1b2c4
Goal: Learn Python closures and decorators from scratch
============================================================
[Curriculum Planner] Building roadmap for: 'Learn Python closures...'
[Curriculum Planner] Calling qwen2.5:7b...
[Curriculum Planner] Created 5 topics
Proposed Study Plan
============================================================
Goal: Learn Python closures and decorators from scratch
Duration: 2 weeks @ 5 hrs/week
1. Python Functions Review (45 min)
Review function definition, arguments, return values, and scope basics
2. Scope and the LEGB Rule (60 min)
Understand how Python resolves variable names across nested scopes
3. Closures Explained (75 min) (needs: Scope and the LEGB Rule)
...
گراف یہیں رک جاتا ہے۔ کہ interrupt() اندر بلاؤ human_approval_node روکتا ہے، چوکی کو بچاتا ہے، اور کالر کو کنٹرول واپس کرتا ہے۔ آپ کا آلہ انتظار کر رہا ہے۔ زمرہ yes جاری رکھیں یا no کھیلیں
چوکی: میرے پاس ریاستی استقامت کے ساتھ ایک ٹاسک گراف ہے۔ سب سے اوپر پرنٹ کردہ سیشن ID درج ذیل جگہ پر محفوظ ہے: data/checkpoints.db. اگر میں ابھی اس عمل کو مار کر چلا دوں python main.py --resume a3f1b2c4اسے منظوری کے فوری طور پر منتخب کیا گیا ہے۔ چیک پوائنٹ پہلے ہی کام کر رہا ہے۔
اب پارسنگ منطق کی تصدیق کے لیے یونٹ ٹیسٹ چلائیں۔
pytest tests/test_state.py tests/test_curriculum_planner.py -v
توقع: 35 ٹیسٹ، سب پاس، اولامہ کی ضرورت نہیں۔ یہ ٹیسٹ ورزش ہے۔ parse_roadmap_json()اسٹیٹ ڈیٹا کلاسز اور یوٹیلیٹی فنکشنز: اصل LLM کالز کے علاوہ سب کچھ۔
یہاں کارپوریٹ پیٹرن: سیلز قابل بنانے کا نظام اسی گراف ڈھانچے کی پیروی کرتا ہے۔ نصاب کے منصوبہ ساز نئے سیلز نمائندوں کے لیے آن بورڈنگ کا راستہ بناتے ہیں، مینیجرز ٹریننگ شروع ہونے سے پہلے اس کی منظوری دیتے ہیں، اور سیکھنے کا طریقہ پروڈکٹ کے علمی موضوعات کے ذریعے چلتا ہے۔ ہر موضوع کے بعد گراف چیک پوائنٹ ہوتا ہے۔ جب نمائندہ دوپہر کے کھانے کے بعد واپس آجاتا ہے، تو نظام بالکل وہیں سے دوبارہ شروع ہوجاتا ہے جہاں اس نے چھوڑا تھا۔
اگلے باب میں، ہم ایک ماڈل سیاق و سباق کا پروٹوکول شامل کرتے ہیں تاکہ ایجنٹوں کو معیاری ٹول تک رسائی حاصل ہو، اور پھر پہلا ایجنٹ، وضاحت کنندہ بنائیں، جو ٹول کو لوپ میں کال کرتا ہے اور اس وقت تک اعادہ کرتا ہے جب تک کہ زمینی وضاحت کے لیے کافی سیاق و سباق موجود نہ ہو۔
وضاحتی ایجنٹ کو کسی بھی چیز کی وضاحت کرنے سے پہلے تحقیقی نوٹ پڑھ لینا چاہیے۔ پروگریس کوچ کو سیشن ڈیٹا کو ذخیرہ اور بازیافت کرنا چاہیے۔ اگرچہ دونوں پائتھون فنکشنز کو براہ راست کال کرسکتے ہیں، ہم نے تمام ایجنٹوں کو فائل سسٹم لے آؤٹ، اسٹوریج اسکیما سے جوڑ دیا اور متعلقہ فنکشنز کو لاگو کیا۔
ماڈل سیاق و سباق پروٹوکول انہیں صاف طور پر الگ کرکے حل کرتا ہے۔ کیا آپ کو صرف ٹولز سرور کو ہینڈل کرنے کی ضرورت ہے۔ کس طرح یہ ختم ہو گیا ہے۔ اسٹوریج بیک اینڈ کو تبدیل کرنے سے ایجنٹ کوڈ تبدیل نہیں ہوتا ہے۔ آپ ایک ہی ٹول سرور کو ایک بار تعینات کر سکتے ہیں اور کسی بھی MCP سے ہم آہنگ ایجنٹ (LangGraph، CrewAI، Claude Desktop، وغیرہ) استعمال کر سکتے ہیں۔
3.1 MCP کے تین بنیادی عناصر
MCP کی تین قسم کی فعالیت ہے جسے سرور بے نقاب کر سکتا ہے:
-
سامان ایک قابل عمل فنکشن جسے ایجنٹ دلائل کے ساتھ کال کرتا ہے۔
read_study_file(filename)یہ ایک ٹول ہے۔ ایجنٹ کنٹرول کرتا ہے کہ اسے کب بلایا جاتا ہے اور یہ کون سے دلائل لیتا ہے۔ سرور عمل درآمد کو سنبھالتا ہے۔ -
وسائل ایجنٹ کے ذریعے پڑھا جانے والا ساختی ڈیٹا، جس کی شناخت URI سے ہوتی ہے۔
notes://indexیہ ایک وسیلہ ہے۔ اسے صرف پڑھنے کے لیے HTTP GET اینڈ پوائنٹ کے طور پر سوچیں۔ سرور کنٹرول کرتا ہے کہ کون سا ڈیٹا دستیاب ہے، اور ایجنٹ درخواست پر اس ڈیٹا کو پڑھتا ہے۔ -
فوری ایک دوبارہ قابل استعمال پرامپٹ ٹیمپلیٹ جو سرور کے پاس ہے اور جس کی ایجنٹ نام کے ذریعے درخواست کرتے ہیں۔ یہ سسٹم پرامپٹس کا زیادہ استعمال نہیں کرتا، لیکن اس صورت میں موجود ہے جب ٹولز سرور اپنے ڈومین کے لیے پرامپٹ ڈیزائن کا مالک بننا چاہے۔
اہم فرق یہ ہے کہ ٹولز کاموں کے بارے میں ہیں اور وسائل ڈیٹا کے بارے میں ہیں۔ جب آپ کو ایجنٹ کی ضرورت ہو۔ کرو کچھ، یہ ایک آلہ ہے. جب آپ کو ایجنٹ کی ضرورت ہو۔ پڑھیں کچھ بھی ساختہ ہے، یہ ایک وسیلہ ہے۔
MCP ایک مستحکم معاہدہ کے طور پر
ایم سی پی کو ایجنٹ اور ٹول کے درمیان ایک مستحکم معاہدہ سمجھیں۔ وضاحت کرنے والا ایجنٹ جانتا ہے کہ ٹول بلایا جا رہا ہے۔ read_study_file اور filename تنازعہ ایجنٹ یہ نہیں دکھاتا ہے کہ آیا نفاذ ڈسک سے پڑھتا ہے، S3 بالٹی سے کھینچتا ہے، یا ڈیٹا بیس سے استفسار کرتا ہے۔
یہ قدر ہے۔ عمل درآمد ایجنٹ کوڈ کو چھوئے بغیر تبدیل کیا جا سکتا ہے۔
3.2 فائل سسٹم MCP سرور بنانا
ایک فائل سسٹم سرور ایجنٹوں کو مطالعہ کے نوٹس تک رسائی فراہم کرتا ہے۔ یہ تین ٹولز اور ایک وسائل کو بے نقاب کرتا ہے۔
# src/mcp_servers/filesystem_server.py
import os
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Filesystem Server")
# Path configured via environment variable
NOTES_BASE = Path(os.getenv("NOTES_PATH", "study_materials/sample_notes"))
@mcp.tool()
def list_study_files() -> list[str]:
"""
List all available study note files.
Returns a list of filenames relative to the notes directory.
Example: ['closures.md', 'decorators.md', 'python_basics.md']
Always call this first to discover what materials are available
before attempting to read specific files.
"""
if not NOTES_BASE.exists():
return []
return sorted([
str(f.relative_to(NOTES_BASE))
for f in NOTES_BASE.rglob("*.md")
])
@mcp.tool()
def read_study_file(filename: str) -> str:
"""
Read the full content of a study note file.
Args:
filename: The filename to read, exactly as returned by
list_study_files(). Example: 'closures.md'
Returns the full text content, or an error string if not found.
Never raises. Errors are returned as strings so the agent
can handle them gracefully.
"""
file_path = NOTES_BASE / filename
# Security: path traversal prevention.
# Without this, an agent could call read_study_file("../../.env")
# and expose your API keys. We resolve both paths and verify
# the requested file is inside the notes directory.
try:
resolved = file_path.resolve()
resolved.relative_to(NOTES_BASE.resolve())
except ValueError:
return (
f"Error: path traversal attempt blocked for '{filename}'. "
f"Only files within the notes directory are accessible."
)
if not file_path.exists():
available = list_study_files()
return f"Error: '{filename}' not found. Available: {available}"
if file_path.suffix != ".md":
return f"Error: only .md files are accessible, got '{file_path.suffix}'"
try:
return file_path.read_text(encoding="utf-8")
except (PermissionError, OSError) as e:
return f"Error reading '{filename}': {e}"
@mcp.tool()
def search_notes(query: str) -> list[dict]:
"""
Search across all study notes for a keyword or phrase.
Args:
query: The search term. Case-insensitive substring match.
Returns a list of matches, each with keys: 'file', 'line_number', 'line'.
Maximum 20 results to avoid overwhelming the context window.
"""
if not NOTES_BASE.exists():
return []
results = []
query_lower = query.lower()
for file_path in sorted(NOTES_BASE.rglob("*.md")):
rel_path = str(file_path.relative_to(NOTES_BASE))
try:
lines = file_path.read_text(encoding="utf-8").splitlines()
except (UnicodeDecodeError, PermissionError, OSError):
continue
for line_num, line in enumerate(lines, 1):
if query_lower in line.lower():
results.append({
"file": rel_path,
"line_number": line_num,
"line": line.strip(),
})
if len(results) >= 20:
return results
return results
@mcp.resource("notes://index")
def get_notes_index() -> str:
"""
Resource: index of all available study materials with file sizes.
URI: notes://index
"""
files = list_study_files()
if not files:
return "# Study Materials IndexnnNo study materials found."
lines = ["# Study Materials Indexn"]
for filename in files:
file_path = NOTES_BASE / filename
try:
size_kb = file_path.stat().st_size / 1024
lines.append(f"- **{filename}** ({size_kb:.1f} KB)")
except OSError:
lines.append(f"- **{filename}** (size unknown)")
lines.append(f"nTotal: {len(files)} file(s)")
return "n".join(lines)
if __name__ == "__main__":
print(f"[Filesystem MCP] Starting server")
print(f"[Filesystem MCP] Serving files from: {NOTES_BASE.resolve()}")
mcp.run()
@mcp.tool() اور @mcp.resource() مکمل مربوط سطح۔ FastMCP فنکشن کا نام (جو ٹول کا نام بن جاتا ہے)، دستاویز کی تار (جو وہ تفصیل بن جاتی ہے جسے LLM اس ٹول کو استعمال کرنے کا فیصلہ کرنے کے لیے پڑھتا ہے)، اور ٹائپ اینوٹیشن (جو کہ دلیل کا اسکیما بن جاتا ہے) پڑھتا ہے۔ یہ سرور اور سرور سے منسلک کلائنٹ کے درمیان مکمل معاہدہ ہے۔
Docstrings قابل توجہ ہیں۔ LLM جو ان ٹولز کو کال کرتا ہے وہ دستاویز کی تار کو پڑھتا ہے اور طے کرتا ہے کہ ٹول کو کب اور کن دلائل کے ساتھ استعمال کرنا ہے۔ مبہم دستاویزات (مثال کے طور پر "فائل پڑھیں”) ٹول کے غلط انتخاب کا باعث بنتی ہیں۔ سرور کی ڈاکسٹرنگ ایجنٹ کو بالکل بتاتی ہے کہ ہر ٹول کو کب کال کرنا ہے اور دلیل کی شکل کیا ہے۔
3.3 میموری MCP سرور بنانا
میموری سرور ایجنٹ کو سیشن کے دائرہ کار میں کلیدی قدر کا ذخیرہ فراہم کرتا ہے۔ وضاحت کنندہ ریکارڈ کرتا ہے کہ کس موضوع کی وضاحت کی گئی ہے۔ آگے کیا کرنا ہے اس کا فیصلہ کرنے سے پہلے ترقی کا کوچ نوٹ پڑھتا ہے۔
# src/mcp_servers/memory_server.py
from datetime import datetime, timezone
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Memory Server")
# In-process store: {session_id: {key: {"value": str, "updated_at": str}}}
# For production: replace with Redis or PostgreSQL.
# The MCP interface stays identical. Only this dict changes.
_store: dict[str, dict] = {}
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
@mcp.tool()
def memory_set(session_id: str, key: str, value: str) -> str:
"""
Store a value in session memory.
Values are always strings. Use JSON for complex data:
memory_set(session_id, 'quiz_scores', json.dumps([0.8, 0.6]))
Args:
session_id: Scopes this data to one study session.
key: Descriptive name. Examples: 'explained_topics', 'last_quiz_score'
value: String value. Use JSON for lists or dicts.
"""
if session_id not in _store:
_store[session_id] = {}
_store[session_id][key] = {"value": value, "updated_at": _now_iso()}
return f"Stored '{key}' for session '{session_id}'"
@mcp.tool()
def memory_get(session_id: str, key: str) -> str:
"""
Retrieve a value from session memory.
Returns the stored value, or the string "null" if the key doesn't exist.
Returns "null" (not Python None) so the LLM can handle the missing case
without type errors.
"""
session = _store.get(session_id, {})
entry = session.get(key)
return "null" if entry is None else entry["value"]
@mcp.tool()
def memory_list_keys(session_id: str) -> list[str]:
"""List all keys stored for a session. Returns [] if none exist."""
return list(_store.get(session_id, {}).keys())
@mcp.tool()
def memory_delete(session_id: str, key: str) -> str:
"""Delete a specific key from session memory."""
session = _store.get(session_id, {})
if key in session:
del session[key]
return f"Deleted '{key}' from session '{session_id}'"
return f"Key '{key}' not found in session '{session_id}'"
@mcp.resource("notes://session/{session_id}")
def get_session_summary(session_id: str) -> str:
"""Full summary of everything stored for a session. URI: notes://session/{session_id}"""
session = _store.get(session_id, {})
if not session:
return f"# Session Memory: {session_id}nnNo data stored yet."
lines = [f"# Session Memory: {session_id}n"]
for key, entry in sorted(session.items()):
lines.append(f"## {key}")
lines.append(f"- Value: {entry['value']}n")
return "n".join(lines)
if __name__ == "__main__":
print("[Memory MCP] Starting server")
mcp.run()
کہ _store ڈکٹ جان بوجھ کر آسان ہے۔ آپ پورے میموری سرور کو Redis بیک اینڈ سے بدل سکتے ہیں اور آپ کا ایجنٹ کوڈ تبدیل نہیں ہوگا۔ صرف عمل درآمد memory_set اور memory_get کرے گا یہ پروٹوکول باؤنڈری کی قدر ہے۔
سلیکٹ ایک تار لوٹاتا ہے۔ "null" ازگر کے بجائے None سے memory_get یہ جان بوجھ کر ہے۔ جب ToolMessage شامل Noneکچھ ماڈل ورژن اس کو صحیح طریقے سے ہینڈل نہیں کرتے ہیں۔ واپسی کے راستے میں "null" LLM کو ایک سٹرنگ فراہم کرتا ہے جس کا اندازہ اسپیشل کیسز کو سنبھالے بغیر لگایا جا سکتا ہے ("کلید ابھی موجود نہیں ہے”)۔
3.4 ایجنٹ MCP ٹولز کا استعمال کیسے کریں: ٹول کال لوپ
وضاحت کرنے والا ایجنٹ وہ ہے جہاں باب 2 (ریاستوں) اور باب 3 (MCP) سے سب کچھ ایک ساتھ آتا ہے۔ یہ سسٹم میں ایک سے زیادہ LLM کال کرنے والا پہلا ایجنٹ بھی ہے۔ یعنی، iterate، ایک فی ٹول کال، جب تک کہ LLM اس بات کا تعین نہ کرے کہ تفصیل بنانے کے لیے کافی معلومات موجود ہیں۔
میں src/agents/explainer.pyMCP سرور فنکشنز براہ راست Python فنکشنز کے طور پر درآمد کیے جاتے ہیں اور براہ راست LangChain سے درآمد کیے جاتے ہیں۔ @tool ڈیکوریٹر:
# src/agents/explainer.py (setup section)
import json, os
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from graph.state import get_current_topic
from mcp_servers.filesystem_server import list_study_files, read_study_file, search_notes
from mcp_servers.memory_server import memory_get, memory_set
MODEL_NAME = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
@tool
def tool_list_files() -> list[str]:
"""
List all available study note files in the notes directory.
Returns filenames like ['closures.md', 'decorators.md'].
Call this FIRST to discover what materials exist before reading any file.
"""
return list_study_files()
@tool
def tool_read_file(filename: str) -> str:
"""
Read the complete content of a study note file.
Args:
filename: Exact filename as returned by tool_list_files().
Returns the full file text, or an error string if not found.
"""
return read_study_file(filename)
@tool
def tool_search_notes(query: str) -> str:
"""
Search across all study notes for a keyword or phrase.
Args:
query: Search term (case-insensitive). Example: 'nonlocal', 'closure'
Returns a JSON string with matching lines and their file locations.
"""
results = search_notes(query)
if not results:
return "No matches found."
return json.dumps(results, indent=2)
@tool
def tool_memory_get(session_id: str, key: str) -> str:
"""
Retrieve a value from session memory.
Args:
session_id: The current session ID (from state).
key: The memory key to look up.
Returns the stored value, or 'null' if not found.
"""
return memory_get(session_id, key)
@tool
def tool_memory_set(session_id: str, key: str, value: str) -> str:
"""
Store a value in session memory for later agents to read.
Args:
session_id: The current session ID (from state).
key: Descriptive key name.
value: String value. Use JSON for complex data.
"""
return memory_set(session_id, key, value)
EXPLAINER_TOOLS = [
tool_list_files, tool_read_file, tool_search_notes,
tool_memory_get, tool_memory_set,
]
TOOL_MAP = {t.name: t for t in EXPLAINER_TOOLS}
براہ راست درآمد بمقابلہ ذیلی عمل نقل و حمل
اس ٹیوٹوریل میں، ہم MCP ٹول کو Python فنکشن میں امپورٹ کرتے ہیں اور اسے اس کے ساتھ لپیٹتے ہیں: @tool. یہ سب کچھ ایک عمل میں چلاتا ہے۔ ترقی آسان ہے، کوئی سب پروسیس اوور ہیڈ نہیں ہے، اور جانچ آسان ہے۔
پیداواری ماحول میں، MCP سرور ایک الگ عمل کے طور پر چلتا ہے جو stdio یا HTTP کے ذریعے بات چیت کرتا ہے۔ تم ہو MultiServerMCPClient سے langchain-mcp-adapters کنیکٹ ایجنٹ کوڈ دونوں طریقوں میں عملی طور پر ایک جیسا ہے، صرف ٹول ریپنگ تبدیل ہونے کے ساتھ۔
وضاحت کنندہ کا سسٹم پرامپٹ ایل ایل ایم کو بھی بتاتا ہے کہ کون سے ٹولز دستیاب ہیں۔ ترتیب میں ان کا استعمال کیسے کریں۔:
EXPLAINER_SYSTEM_PROMPT = """You are an expert tutor explaining topics to a student.
Your explanations must be grounded in the student's actual study materials.
Use the available tools to find and read relevant notes before explaining.
APPROACH (follow this sequence):
1. Call tool_list_files() to see what materials are available
2. Call tool_search_notes(topic) to find which files cover this topic
3. Call tool_read_file(filename) to read the most relevant file(s)
4. Check prior context: call tool_memory_get(session_id, 'explained_topics')
5. Write your explanation based on what you found in the notes
EXPLANATION FORMAT:
- Start with a real-world analogy (1-2 sentences)
- State the core concept clearly (2-3 sentences)
- Show a concrete code example from the student's notes
- End with one common mistake or gotcha to watch out for
After writing the explanation, store what you explained:
tool_memory_set(session_id, 'explained_topics', )
"""
ٹول کال لوپ explainer_node غور سے سمجھنے کے قابل کلیدی میکانزم ہیں:
# src/agents/explainer.py (node function)
def execute_tool_call(tool_call: dict) -> str:
"""Execute a tool call and return the result as a string. Never raises."""
name = tool_call["name"]
args = tool_call["args"]
if name not in TOOL_MAP:
return f"Error: unknown tool '{name}'. Available: {list(TOOL_MAP.keys())}"
try:
result = TOOL_MAP[name].invoke(args)
if isinstance(result, (list, dict)):
return json.dumps(result)
return str(result)
except Exception as e:
return f"Error executing {name}({args}): {type(e).__name__}: {e}"
def explainer_node(state: dict) -> dict:
"""
LangGraph node: Explainer Agent
Reads: state["roadmap"], state["current_topic_index"], state["session_id"]
Writes: state["messages"], state["error"]
"""
topic = get_current_topic(state)
if topic is None:
return {"error": "No current topic found."}
session_id = state.get("session_id", "unknown")
print(f"n[Explainer] Topic: '{topic.title}'")
llm = ChatOllama(
model=MODEL_NAME,
base_url=OLLAMA_BASE_URL,
temperature=0.3,
).bind_tools(EXPLAINER_TOOLS)
messages = [
SystemMessage(content=EXPLAINER_SYSTEM_PROMPT),
HumanMessage(content=(
f"Please explain this topic to me: '{topic.title}'n"
f"Context: {topic.description}n"
f"Session ID for memory calls: {session_id}"
)),
]
max_iterations = 8
final_response = None
for iteration in range(max_iterations):
print(f"[Explainer] LLM call {iteration + 1}/{max_iterations}...")
response = llm.invoke(messages)
messages.append(response)
if not response.tool_calls:
final_response = response
print(f"[Explainer] Complete after {iteration + 1} LLM call(s)")
break
print(f"[Explainer] {len(response.tool_calls)} tool call(s) requested:")
for tool_call in response.tool_calls:
print(f" → {tool_call['name']}({tool_call['args']})")
result = execute_tool_call(tool_call)
log_result = result[:100] + "..." if len(result) > 100 else result
print(f" ← {log_result}")
# The tool_call_id must match the ID the LLM assigned to the request.
# Without this, the LLM can't correlate result to request.
messages.append(ToolMessage(
content=result,
tool_call_id=tool_call["id"],
))
if final_response is None:
return {
"messages": messages,
"error": f"Explainer reached max iterations ({max_iterations}).",
}
print(f"[Explainer] Explanation: {len(final_response.content)} characters")
return {"messages": messages, "error": None}
آئیے دیکھتے ہیں کہ ایک رن کے دوران کیا ہوتا ہے۔
ایل ایل ایم کال 1: LLM کو سسٹم پرامپٹس اور انسانی پیغامات موصول ہوتے ہیں جن میں "بند ہونے کی وضاحت کی گئی” کے لیے وضاحت کی درخواست کی جاتی ہے۔ یہ ٹول کال کے ساتھ جواب دیتا ہے۔ tool_list_files() اور tool_search_notes("closure"). ابھی تک کوئی متن کی تفصیل نہیں ہے۔
ٹول چلائیں: tool_list_files() رپورٹ ["closures.md", "decorators.md", "python_basics.md"]. tool_search_notes("closure") مماثل قطاریں یہاں سے لوٹاتا ہے: closures.md. دونوں نتائج کو پیغام کی فہرست میں اس طرح شامل کیا گیا ہے: ToolMessage مماثل آبجیکٹ tool_call_id.
ایل ایل ایم کال 2: اب ایل ایل ایم کے پاس فائلوں اور تلاش کے نتائج کی فہرست ہے۔ یہ پوچھتا ہے tool_read_file("closures.md").
ٹول چلائیں: مکمل تفصیلات closures.md واپس کر دیا جاتا ہے. ToolMessage.
ایل ایل ایم کال 3: ایل ایل ایم نے نوٹ پڑھا۔ یہ کال کرتا ہے tool_memory_set(session_id, "explained_topics", "Closures Explained") نوٹ کریں کہ اس موضوع پر توجہ دی گئی ہے۔
ایل ایل ایم کال 4: محفوظ شدہ سیاق و سباق کے ساتھ، LLM حتمی تفصیل تیار کرتا ہے۔ جواب میں مزید ٹول کالز نہیں ہیں۔ لوپ ختم ہوتا ہے۔ وضاحتیں اس بات پر مبنی ہیں کہ اصل میں آپ کے نوٹس میں کیا ہے، ماڈل کے تربیتی ڈیٹا پر نہیں۔
کہ tool_call_id آن لائن ملاپ tool_call_id=tool_call["id"] یہ قابل توجہ ہے۔ جب LLM ٹول کال کی درخواست کرتا ہے، تو یہ ایک ID تفویض کرتا ہے۔ کہ ToolMessage آپ کو وہی ID شامل کرنا چاہیے تاکہ LLM نتائج کو آپ کی درخواست کے ساتھ منسلک کر سکے۔ اس کے بغیر، ڈائیلاگ خراب ہو جائے گا اور ماڈل ردی کی ٹوکری یا غلطیاں پیدا کرے گا۔
کہ max_iterations = 8 حد پیداوار سرکٹ بریکر ہے. ایک مبہم ماڈل جو کسی ٹول کو غیر معینہ مدت تک کال کرتا ہے اس وقت تک چلے گا جب تک کہ ٹول ختم نہ ہو جائے۔ ایک جائز وضاحتی کام کے لیے آٹھ تکرار کافی ہیں۔ جب کوئی ماڈل اپنی حد تک پہنچ جاتا ہے، تو ایک خرابی کی حالت شروع ہو جاتی ہے اور سسٹم پرامپٹس کو ایڈجسٹ یا بڑے ماڈل میں تبدیل کیا جا سکتا ہے۔
3.5 وضاحتی عمل درآمد
اشارہ کرنے پر، روڈ میپ کو قبول کریں، پھر ٹول کال لوپ کو عمل میں دیکھیں۔
python main.py
منظوری کے بعد:
[Explainer] Topic: 'Python Functions Review'
[Explainer] LLM call 1/8...
→ tool_list_files({})
← ["closures.md", "decorators.md", "python_basics.md"]
[Explainer] LLM call 2/8...
→ tool_search_notes({'query': 'functions'})
← [{"file": "python_basics.md", "line_number": 12, "line": "## Functions"}]
[Explainer] LLM call 3/8...
→ tool_read_file({'filename': 'python_basics.md'})
← # Python Basicsnn## Variables and Types...
[Explainer] LLM call 4/8...
→ tool_memory_set({'session_id': 'a3f1b2c4', 'key': 'explained_topics', ...})
← Stored 'explained_topics' for session 'a3f1b2c4'
[Explainer] LLM call 5/8...
[Explainer] Complete after 5 LLM call(s)
[Explainer] Explanation: 487 characters
تمام تیر (→) LLM کے ذریعہ درخواست کردہ ایک ٹول کال ہے۔ تمام پچھلے تیر (←) LLM کو واپس کیے گئے نتائج ہیں۔ لوپ LLM کال 5 پر ختم ہوتا ہے کیونکہ اس جواب میں حتمی تبصرہ ہوتا ہے اور مزید ٹول کی درخواستیں نہیں کی جاتی ہیں۔
چوکی: یہ یقینی بنانے کے لیے کہ ٹول LLM سے آزادانہ طور پر کام کرتا ہے MCP سرور ٹیسٹ چلائیں۔
pytest tests/test_mcp_servers.py -v
پیشین گوئی: 36 ٹیسٹ، سب پاس، اولامہ کی ضرورت نہیں۔ یہ ٹیسٹ ٹول فنکشنز کو براہ راست Python فنکشن کہتے ہیں۔ کوئی ذیلی عمل نہیں ہے اور کوئی پروٹوکول اوور ہیڈ نہیں ہے۔ چونکہ ٹول کی فعالیت سادہ Python ہے، یہ ٹول دو طریقوں میں کام کرتا ہے: ڈائریکٹ Python امپورٹ اور MCP پروٹوکول۔
یہاں کارپوریٹ کنکشن: ایک کمپلائنس ٹریننگ سسٹم جو اسی پیٹرن کو استعمال کرتا ہے اس میں MCP سرور ہوگا جو اسٹڈی نوٹ کے بجائے ریگولیٹری مواد کی لائبریری کو بے نقاب کرتا ہے۔ ایجنٹس موضوع کے لحاظ سے استفسار کرتے ہیں، تقاضوں کو پڑھتے ہیں، اور اصل ریگولیٹری متن سے سرٹیفیکیشن کے جائزے تیار کرتے ہیں، نہ کہ وہ ریگولیٹری متن جس کے بارے میں ماڈل سوچتا ہے۔ گراؤنڈنگ کلید ہے۔
اگلے باب میں، آپ کوئز جنریٹر اور پروگریس کوچ شامل کریں گے، تمام موضوعات پر گراف کو خود بخود لوپ کرنے کے لیے مشروط روٹنگ کو جوڑیں گے، اور ایک مکمل چار ایجنٹ سسٹم کو اینڈ ٹو اینڈ چلائیں گے۔
باب 4: فور ایجنٹ سسٹم کی تعمیر
پہلے تین ابواب میں، ہم نے فاؤنڈیشن بنائی: ایک مشترکہ اسٹیٹ ڈیفینیشن، ایک گراف جو ہر نوڈ کے بعد چیک پوائنٹ کرتا ہے، دو MCP سرورز، اور ایک وضاحتی ایجنٹ جو ان سرورز کو اصل نوٹوں کی بنیاد پر وضاحت کے لیے استعمال کرتا ہے۔ آپ کے پاس ایک LLM ہے جو فائلوں کو پڑھتا ہے اور عنوانات کی وضاحت کرتا ہے۔
یہ باب نظام کو مکمل کرتا ہے۔ کوئز جنریٹر اور پروگریس کوچ شامل کریں، تمام عنوانات کے ذریعے گراف کو خود بخود لوپ کرنے کے لیے مشروط روٹنگ کو جوڑیں، اور ایک مکمل اینڈ ٹو اینڈ سیشن چلائیں۔
4.1 کوئز جنریٹر: LLM بطور جج
کوئز جنریٹر سسٹم میں ساختی لحاظ سے سب سے زیادہ دلچسپ ایجنٹ ہے کیونکہ یہ جان بوجھ کر مختلف مقاصد اور درجہ حرارت کے ساتھ الگ الگ ایل ایل ایم کالز کا استعمال کرتا ہے۔
ایک نسل کی پکار وضاحت کنندہ کے آؤٹ پٹ سے سوالات پیدا کرتا ہے۔ یہ استعمال کرتا ہے temperature=0.4 (متعدد موضوعات پر متنوع، غیر دہرائے جانے والے سوالات پیدا کرنے کے لیے کافی تخلیقی صلاحیت) format="json" تشکیل شدہ آؤٹ پٹ کو نافذ کریں۔
گریڈنگ کال طلباء کے جوابات کا اندازہ لگائیں۔ یہ استعمال کرتا ہے temperature=0.1. تجزیاتی اور مستقل۔ ایک ہی جواب کو دو بار گریڈ کرنے کے نتیجے میں ایک ہی اسکور ہوگا۔ پیدا کردہ درجہ حرارت کا استعمال تخلیقی ترتیبات پرکھ کی تشخیص کو متاثر کرنے کی اجازت دیتا ہے۔
یہ نام دینے کے قابل پروڈکشن پیٹرن ہے۔ اگر ایک ورک فلو میں ذیلی کام ہیں جن کے بنیادی طور پر مختلف تقاضے ہیں، الگ الگ کنفیگریشن کے ساتھ الگ الگ LLM کالز فراہم کرنے سے دونوں کام انجام دینے والی ایک کال سے بہتر نتائج حاصل ہو سکتے ہیں۔
# src/agents/quiz_generator.py
import json
import os
from datetime import datetime, timezone
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_ollama import ChatOllama
from graph.state import QuizQuestion, QuizResult, get_current_topic
MODEL_NAME = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
GENERATION_PROMPT = """You are a quiz designer for a student learning programming.
Given a topic and explanation, generate {n} quiz questions that test
genuine understanding, not just the ability to repeat memorized phrases.
Good questions require the student to:
- Apply a concept to a new situation
- Explain WHY something works, not just WHAT it does
- Identify edge cases or common mistakes
- Compare related concepts
Return ONLY valid JSON with no prose or markdown:
{{
"questions": [
{{
"question": "Clear, specific question text ending with ?",
"expected_answer": "Model answer in 1-3 sentences",
"difficulty": "easy|medium|hard"
}}
]
}}
Rules:
- Include at least one question about a common mistake or gotcha
- expected_answer should be concise but complete
- Avoid yes/no questions. Ask for explanation or demonstration
"""
GRADING_PROMPT = """You are a fair teacher grading a student's answer.
Question: {question}
Model answer: {expected_answer}
Student's answer: {student_answer}
Grade the student's answer honestly. Be generous with partial credit:
- Fundamentally correct with minor gaps: 0.7-0.9
- Correct concept but imprecise: 0.5-0.7
- Partially correct: 0.3-0.5
- Fundamentally wrong: 0.0-0.2
Return ONLY valid JSON with no prose or markdown:
{{
"correct": true,
"score": 0.85,
"feedback": "One specific sentence of feedback",
"missing_concept": "Key concept missed, or empty string if answer is correct"
}}
"""
کہ generate_questions اور grade_answer فنکشن ان دونوں کالوں کو آزادانہ طور پر نافذ کرتا ہے۔ دونوں کو سادہ Python سے درآمد اور کال کیا جا سکتا ہے۔ گراف کی ضرورت نہیں ہے۔ یہ آپ کو A2A سروس کے ساتھ تنہائی میں جانچ اور دوبارہ استعمال کرنے کی اجازت دیتا ہے جسے آپ باب 8 میں بنائیں گے۔
def generate_questions(topic: str, explanation: str, n: int = 3) -> list[dict]:
"""Generate n quiz questions from the Explainer's output."""
llm = ChatOllama(
model=MODEL_NAME,
base_url=OLLAMA_BASE_URL,
temperature=0.4,
format="json",
)
prompt = GENERATION_PROMPT.format(n=n)
try:
response = llm.invoke([
SystemMessage(content=prompt),
HumanMessage(content=f"Topic: {topic}nnExplanation:n{explanation}"),
])
data = json.loads(response.content)
questions = data.get("questions", [])
if questions and isinstance(questions, list):
return questions
except Exception as e:
print(f"[Quiz Generator] LLM call failed during question generation: {e}")
# Fallback: one generic question
return [{
"question": f"In your own words, explain the key concept of {topic} and why it matters.",
"expected_answer": "A clear explanation demonstrating conceptual understanding.",
"difficulty": "medium",
}]
def grade_answer(question: str, expected: str, student_answer: str) -> dict:
"""Grade a student's answer using the LLM as judge."""
llm = ChatOllama(
model=MODEL_NAME,
base_url=OLLAMA_BASE_URL,
temperature=0.1, # Analytical: grading must be consistent
format="json",
)
prompt = GRADING_PROMPT.format(
question=question,
expected_answer=expected,
student_answer=student_answer,
)
try:
response = llm.invoke([HumanMessage(content=prompt)])
return json.loads(response.content)
except Exception as e:
print(f"[Quiz Generator] LLM call failed during grading: {e}")
return {
"correct": False,
"score": 0.5,
"feedback": "Could not grade automatically. Please review manually.",
"missing_concept": "",
}
کہ run_quiz فنکشن ایک انٹرایکٹو ٹرمینل سیشن کو مربوط کرتا ہے۔ یہ کال کرتا ہے generate_questionsہر سوال کو طلباء کے سامنے پیش کریں بذریعہ: input()جیسے ہی ہر جواب آتا ہے، اس کی درجہ بندی کریں اور QuizResult:
def run_quiz(topic: str, explanation: str) -> QuizResult:
"""Run an interactive quiz session in the terminal."""
print(f"n{'='*60}")
print(f"Quiz: {topic}")
print(f"{'='*60}")
print("Answer each question in your own words. Press Enter to submit.n")
questions_data = generate_questions(topic, explanation, n=3)
graded_questions = []
total_score = 0.0
weak_areas = []
for i, q_data in enumerate(questions_data, 1):
question_text = q_data["question"]
expected = q_data["expected_answer"]
difficulty = q_data.get("difficulty", "medium")
print(f"Question {i} [{difficulty}]: {question_text}")
user_answer = input("Your answer: ").strip()
if not user_answer:
user_answer = "(no answer provided)"
print("Grading...")
grade = grade_answer(question_text, expected, user_answer)
score = float(grade.get("score", 0.0))
correct = bool(grade.get("correct", False))
feedback = grade.get("feedback", "")
missing = grade.get("missing_concept", "")
total_score += score
status = "✓" if correct else "✗"
print(f"{status} Score: {score:.0%}. {feedback}n")
if missing:
weak_areas.append(missing)
graded_questions.append(QuizQuestion(
question=question_text,
expected_answer=expected,
user_answer=user_answer,
correct=correct,
feedback=feedback,
score=score,
))
avg_score = total_score / len(questions_data) if questions_data else 0.0
correct_count = sum(1 for q in graded_questions if q.correct)
print(f"{'='*60}")
print(f"Quiz complete! Score: {avg_score:.0%} ({correct_count}/{len(graded_questions)} correct)")
if weak_areas:
print(f"Areas to review: {', '.join(set(weak_areas))}")
print(f"{'='*60}n")
return QuizResult(
topic=topic,
questions=graded_questions,
score=avg_score,
weak_areas=list(set(weak_areas)),
timestamp=datetime.now(timezone.utc).isoformat(),
)
لینگ گراف نوڈ میسج ہسٹری سے ڈسکرپٹر کا آؤٹ پٹ نکالتا ہے اور اسے کال کرتا ہے۔ run_quiz. اس کے بعد ہم نتائج اور کمزور علاقوں کو ریاستوں میں جمع کرتے ہیں۔
def quiz_generator_node(state: dict) -> dict:
"""
LangGraph node: Quiz Generator
Reads: state["roadmap"], state["current_topic_index"], state["messages"]
Writes: state["quiz_results"], state["weak_areas"], state["error"]
"""
topic = get_current_topic(state)
if topic is None:
return {"error": "No current topic. Curriculum Planner must run first"}
# Extract the Explainer's final response from message history.
# The Explainer's output is the last AIMessage that has no tool_calls.
# Tool-calling responses have content too, but they also have tool_calls set.
from langchain_core.messages import AIMessage
messages = state.get("messages", [])
explanation = ""
for msg in reversed(messages):
if isinstance(msg, AIMessage) and msg.content and not getattr(msg, "tool_calls", None):
explanation = msg.content
break
if not explanation:
print("[Quiz Generator] Warning: no explanation found, generating generic quiz")
explanation = f"Topic: {topic.title}. {topic.description}"
print(f"n[Quiz Generator] Generating quiz for: '{topic.title}'")
quiz_result = run_quiz(topic.title, explanation)
existing_results = state.get("quiz_results", [])
all_weak_areas = list(set(
state.get("weak_areas", []) + quiz_result.weak_areas
))
return {
"quiz_results": existing_results + [quiz_result],
"weak_areas": all_weak_areas,
"error": None,
# Pass state forward explicitly to preserve it across interrupt/resume
"roadmap": state.get("roadmap"),
"current_topic_index": state.get("current_topic_index", 0),
"session_id": state.get("session_id", ""),
}
کیوں quiz_results بدلنے کے بجائے جمع کریں۔
پروگریس کوچز کو کوئز کے موجودہ نتائج کی ضرورت ہوگی۔ آپ کو اپنے سیشن کے خلاصے میں ان تمام اشیاء کی ضرورت ہوگی۔ نوڈ ایک موجودہ فہرست ہے (existing_results + [quiz_result]) کو تبدیل کرنے کے بجائے۔
weak_areas یہ اسی پیٹرن کی پیروی کرتا ہے: set(existing + new) ہم تمام موضوعات میں نقل کو ہٹا دیتے ہیں، اس لیے کمزور علاقوں کی حتمی فہرست ہر اس چیز کو یکجا کرتی ہے جس کے ساتھ طالب علم نے سیشن میں جدوجہد کی۔
4.2 پروگریس کوچ: ترکیب اور روٹنگ
سہولت کوچ ترتیب وار تین کام انجام دیتا ہے: کوئز کے نتائج کا جائزہ لینا، طلباء کو فیڈ بیک فراہم کرنا، اور فیصلہ کرنا کہ آگے کیا ہوتا ہے۔ روٹنگ کے فیصلے (اگلے موضوع پر واپس جائیں یا سیشن ختم کریں) آپ کی سب سے اہم ذمہ داریاں ہیں۔
# src/agents/progress_coach.py
import json
import os
from datetime import datetime, timezone
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_ollama import ChatOllama
from graph.state import QuizResult, StudyRoadmap, get_latest_quiz_result
from mcp_servers.memory_server import memory_set
MODEL_NAME = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
PASS_THRESHOLD = 0.5
COACHING_PROMPT = """You are an encouraging learning coach reviewing a student's quiz results.
Provide a brief, warm coaching message (2-3 sentences max) based on:
- The topic studied
- Their score (0.0 = 0%, 1.0 = 100%)
- Any weak areas identified
Return ONLY valid JSON:
{{
"summary": "2-3 sentence encouraging summary",
"encouragement": "One short motivational sentence for next steps"
}}
Be specific. Reference the topic and any weak areas by name.
Never be discouraging. A low score means "more practice needed", not "you failed."
"""
کہ get_coaching_message فنکشن استعمال کرتے ہوئے ایک LLM کال کرتا ہے: temperature=0.4 اور format="json". رد عمل کی گرمی کے لیے کچھ درجہ حرارت کی ضرورت ہوتی ہے۔ temperature=0.1 اگرچہ تکنیکی طور پر درست ہے، یہ خشک رائے پیدا کرتا ہے۔
def get_coaching_message(topic: str, score: float, weak_areas: list[str]) -> dict:
"""Ask the LLM for a personalised coaching message."""
llm = ChatOllama(
model=MODEL_NAME,
base_url=OLLAMA_BASE_URL,
temperature=0.4,
format="json",
)
context = {
"topic": topic,
"score_percent": f"{score:.0%}",
"weak_areas": weak_areas if weak_areas else ["none identified"],
}
try:
response = llm.invoke([
SystemMessage(content=COACHING_PROMPT),
HumanMessage(content=json.dumps(context)),
])
return json.loads(response.content)
except Exception as e:
print(f"[Progress Coach] LLM call failed: {e}")
return {
"summary": f"You scored {score:.0%} on {topic}. Keep going!",
"encouragement": "Every topic builds on the last.",
}
نوڈ کے افعال ہر چیز کو ایک ساتھ باندھتے ہیں۔ کوئز کے تازہ ترین نتائج پڑھیں، روڈ میپ پر موضوع کی حیثیت کو اپ ڈیٹ کریں، اپنی پیش رفت کو MCP میموری میں رکھیں، تاثرات پرنٹ کریں، اور ٹاپک انڈیکس کو ایڈوانس کریں۔
def progress_coach_node(state: dict) -> dict:
"""
LangGraph node: Progress Coach
Reads: state["quiz_results"], state["roadmap"],
state["current_topic_index"], state["session_id"]
Writes: state["roadmap"], state["current_topic_index"],
state["messages"], state["error"]
"""
latest = get_latest_quiz_result(state)
if latest is None:
return {"error": "No quiz results. Quiz Generator must run first"}
roadmap = state.get("roadmap")
if roadmap is None:
return {"error": "No roadmap found"}
idx = state.get("current_topic_index", 0)
session_id = state.get("session_id", "unknown")
score = latest.score
print(f"n[Progress Coach] Topic: '{latest.topic}'")
print(f"[Progress Coach] Score: {score:.0%}")
if latest.weak_areas:
print(f"[Progress Coach] Weak areas: {', '.join(latest.weak_areas)}")
# Get coaching message from LLM
coaching = get_coaching_message(latest.topic, score, latest.weak_areas)
# Update topic status in the roadmap
topics = roadmap.get("topics", []) if isinstance(roadmap, dict) else roadmap.topics
if idx < len(topics):
topic = topics[idx]
new_status = "completed" if score >= PASS_THRESHOLD else "needs_review"
if isinstance(topic, dict):
topic["status"] = new_status
else:
topic.status = new_status
# Advance the topic index
next_idx = idx + 1
all_done = next_idx >= len(topics)
# Persist progress to MCP memory
memory_set(session_id, f"progress_topic_{idx}", json.dumps({
"topic": latest.topic,
"score": score,
"weak_areas": latest.weak_areas,
"timestamp": datetime.now(timezone.utc).isoformat(),
}))
# Print coaching feedback
print(f"n{'─'*60}")
print(f"Coach: {coaching['summary']}")
print(f"{coaching['encouragement']}")
if all_done:
results = state.get("quiz_results", [])
avg = sum(r.score for r in results) / max(len(results), 1)
print(f"nSession complete! Average: {avg:.0%}")
else:
next_topic = topics[next_idx]
next_title = next_topic.get("title") if isinstance(next_topic, dict) else next_topic.title
print(f"nNext topic: '{next_title}'")
print(f"{'─'*60}n")
return {
"roadmap": roadmap,
"current_topic_index": next_idx,
"messages": [AIMessage(content=coaching["summary"])],
"error": None,
}
اس فنکشن کے بارے میں سمجھنے کے لیے دو چیزیں ہیں:
انڈیکس کو آگے بڑھانے سے پہلے موضوع کی حیثیت کو کیوں اپ ڈیٹ کریں؟ کیونکہ ریاست بدلتی ہے ("pending" کو "completed" یا "needs_review") درج ذیل اوقات میں ہونا ضروری ہے: topics[idx]نہیں topics[next_idx]. انڈیکس بڑھتا ہے۔ ~ بعد ہم فی الحال موضوع کی حیثیت کو اپ ڈیٹ کر رہے ہیں۔ اس آرڈر کو غلط حاصل کرنے کا مطلب ہے کہ آپ کو غلط عنوانات نظر آئیں گے۔ یہ ایک لطیف بگ ہے جسے یاد کرنا آسان ہے کیونکہ سیشن اب بھی نمایاں طور پر صحیح طریقے سے چلتا ہے۔
MCP میموری پر کیوں لکھیں؟ پروگریس کوچ ہر عنوان کے لیے نتائج کو برقرار رکھتا ہے: memory_set. یہ پیداوار کے استعمال کے معاملات میں استعمال ہوتا ہے۔ جب کوئی سیشن کریش یا توقف کے بعد دوبارہ شروع ہوتا ہے، تو میموری سرور کے پاس اس بات کا ریکارڈ ہوتا ہے کہ کیا احاطہ کیا گیا تھا اور طلباء نے کیسی کارکردگی کا مظاہرہ کیا۔ وضاحت کنندگان اس ریکارڈ کو بذریعہ دیکھ سکتے ہیں: tool_memory_get بعد کے عنوانات کی وضاحت کرتے وقت، طالب علم کو کس چیز کے ساتھ مشکلات کا سامنا ہے اس پر انحصار کرتے ہوئے زور کو ایڈجسٹ کریں۔
4.3 پورے گراف کو جوڑنا
ایک بار جب تمام چار ایجنٹوں کی تعریف کی جاتی ہے۔ workflow.py اسے مجموعی گراف سے مربوط کریں۔ وائرنگ خود سسٹم کی مختصر ترین فائل ہے۔ ان میں سے تقریباً سبھی 50 لائنوں سے کم ہیں۔ add_node، add_edgeاور add_conditional_edges فون کال
# src/graph/workflow.py
import os
import sqlite3
from pathlib import Path
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import END, START, StateGraph
from agents.curriculum_planner import curriculum_planner_node
from agents.explainer import explainer_node
from agents.human_approval import human_approval_node
from agents.progress_coach import progress_coach_node
from agents.quiz_generator import quiz_generator_node
from graph.state import AgentState, session_is_complete
def route_after_approval(state: dict) -> str:
if state.get("approved", False):
return "explainer"
return "curriculum_planner"
def route_after_coach(state: dict) -> str:
if session_is_complete(state):
return "end"
return "explainer"
def build_graph(
db_path: str = "data/checkpoints.db",
interrupt_before: list | None = None,
):
"""
Build and compile the Learning Accelerator graph.
Args:
db_path: Path to the SQLite checkpoint database.
interrupt_before: Optional list of node names to pause before.
Used by the Streamlit UI to intercept quiz_generator.
"""
Path("data").mkdir(exist_ok=True)
if db_path == "data/checkpoints.db":
db_path = os.getenv("CHECKPOINT_DB", db_path)
builder = StateGraph(AgentState)
builder.add_node("curriculum_planner", curriculum_planner_node)
builder.add_node("human_approval", human_approval_node)
builder.add_node("explainer", explainer_node)
builder.add_node("quiz_generator", quiz_generator_node)
builder.add_node("progress_coach", progress_coach_node)
builder.add_edge(START, "curriculum_planner")
builder.add_edge("curriculum_planner", "human_approval")
builder.add_edge("explainer", "quiz_generator")
builder.add_edge("quiz_generator", "progress_coach")
builder.add_conditional_edges(
"human_approval",
route_after_approval,
{"explainer": "explainer", "curriculum_planner": "curriculum_planner"},
)
builder.add_conditional_edges(
"progress_coach",
route_after_coach,
{"explainer": "explainer", "end": END},
)
# CRITICAL: Create the connection directly. Do NOT use a context manager.
# The connection must stay open for the process lifetime.
# SqliteSaver requires check_same_thread=False because LangGraph runs
# node functions and checkpoint writes on different threads.
conn = sqlite3.connect(db_path, check_same_thread=False)
checkpointer = SqliteSaver(conn)
return builder.compile(
checkpointer=checkpointer,
interrupt_before=interrupt_before or [],
)
graph = build_graph()
کہ interrupt_before پیرامیٹرز یہاں قریب سے دیکھنے کے قابل ہیں۔ ٹرمینل انٹرفیس (main.py) استعمال کریں۔ interrupt() اندر human_approval_node روڈ میپ کی منظوری کے لیے وقفہ کریں۔ نہیں interrupt_before آپ کو اس کی ضرورت ہے۔
Streamlit UI (باب 9) کو مختلف قسم کے توقف کی ضرورت ہے۔ quiz_generator_node اس طرح چلتا ہے۔ input() اسے گراف تھریڈ کے اندر نہیں کہا جاتا ہے۔ کہ build_graph(interrupt_before=["quiz_generator"]) کال streamlit_app.py UI کے استعمال کے لیے ترتیب کردہ ایک الگ گراف مثال بنائیں۔
ٹرمینل گراف اور UI گراف ایک ہی بلڈر سے مرتب کیے گئے ہیں۔ صرف توقف کا نقطہ مختلف ہے۔
روٹنگ فنکشن خالص ازگر ہے جس میں ایل ایل ایم کالز نہیں ہیں۔ route_after_approval پڑھیں state["approved"]انسانی منظوری کے نوڈس کے ذریعے استعمال ہونے والا بولین۔ route_after_coach فون کال session_is_complete(state)یقینی بنائیں کہ سبجیکٹ انڈیکس روڈ میپ سے آگے بڑھتا ہے۔ تمام کنٹرول فلو سٹاکسٹک LLM آؤٹ پٹ کے بجائے deterministic Python ہیں۔
4.4 مجموعی طور پر عملدرآمد کا بہاؤ
جب آپ اسے چلاتے ہیں، تو درج ذیل ہوتا ہے: python main.py "Learn Python closures" اور ٹائپ کریں۔ yes منظوری کے فوری طور پر:
START
↓
curriculum_planner_node
reads: state["goal"]
writes: state["roadmap"], state["messages"]
↓
human_approval_node
interrupt() pauses here. Waits for user input.
user types "yes"
writes: state["approved"] = True + full state forward
↓ route_after_approval → "explainer"
explainer_node (topic 0)
reads: state["roadmap"], state["current_topic_index"]
calls: tool_list_files, tool_search_notes, tool_read_file
writes: state["messages"]
↓
quiz_generator_node (topic 0)
reads: state["messages"] (extracts explanation)
calls: run_quiz() → 3 questions, 3 graded answers
writes: state["quiz_results"], state["weak_areas"]
↓
progress_coach_node (topic 0)
reads: state["quiz_results"], state["roadmap"]
writes: state["roadmap"] (topic 0 status updated)
state["current_topic_index"] = 1
state["messages"] (coaching message)
↓ route_after_coach → "explainer" (more topics remain)
explainer_node (topic 1)
...
↓
[loop continues until current_topic_index >= len(roadmap.topics)]
↓ route_after_coach → "end"
END
لینگ گراف چوکیاں ہر نوڈ کے بعد ریاست کی نمائندگی کرتی ہیں۔ اگر کوئی عمل آپس میں متصادم ہو۔ quiz_generator_node اور progress_coach_nodeاگلا graph.invoke(None, config=config) اسے اسی سیشن ID کا استعمال کرتے ہوئے اگلا دوبارہ شروع کیا جائے گا۔ progress_coach_node. کوئز کے نتائج پہلے ہی جاری ہو چکے ہیں۔
4.5 پورے نظام کو چلانا
اگر چاروں نوڈس رجسٹرڈ ہیں:
rm -f data/checkpoints.db
python main.py "Learn Python closures and decorators from scratch"
آپ منصوبہ ساز، منظوری کے اشارے، اور پورا لوپ دیکھیں گے۔
[Curriculum Planner] Building roadmap for: 'Learn Python closures...'
[Curriculum Planner] Created roadmap: 5 topics, 4 weeks
1. Python Functions (60 min)
2. Scopes and Namespaces (45 min)
3. Inner Functions (60 min)
4. Creating Closures (75 min)
5. Decorator Basics (60 min)
[Human Approval] Pausing for roadmap review...
> yes
[Human Approval] Roadmap approved. Starting study session.
[Explainer] Topic: 'Python Functions'
[Explainer] LLM call 1/8...
→ tool_list_files({})
← ["closures.md", "decorators.md", "python_basics.md"]
[Explainer] LLM call 2/8...
→ tool_read_file({'filename': 'python_basics.md'})
← # Python Basics...
[Explainer] Complete after 4 LLM call(s)
[Explainer] Explanation: 1938 characters
[Quiz Generator] Generating quiz for: 'Python Functions'
============================================================
Quiz: Python Functions
============================================================
Question 1 [medium]: What is the difference between...
Your answer: Functions are first-class objects...
Grading...
✓ Score: 80%. Good explanation of first-class functions.
...
[Progress Coach] Topic: 'Python Functions'
[Progress Coach] Score: 73%
────────────────────────────────────────────────────────────
Coach: You have a solid grasp of Python functions, especially...
Keep building on this foundation as you move into closures!
Next topic: 'Scopes and Namespaces'
────────────────────────────────────────────────────────────
[Explainer] Topic: 'Scopes and Namespaces'
...
لوپ خود بخود چلتا ہے۔ جب progress_coach_node لکھنا current_topic_index = 1، route_after_coach رپورٹ "explainer"گراف کال explainer_node اپ ڈیٹ کردہ انڈیکس کے ساتھ کوئی بیرونی لوپ نہیں۔ main.py. گراف ٹوپولوجی تکرار کو سنبھالتی ہے۔
چوکی: پورے ٹیسٹ سویٹ کو چلائیں۔
pytest tests/ -v
متوقع: 184 ٹیسٹ اکٹھے کیے جائیں گے اور تشخیصی ٹیسٹ خود بخود غیر منتخب ہو جائیں گے۔ یونٹ ٹیسٹ اولاما کے بغیر کوئز اور کوچ نوڈس کا احاطہ کرتے ہیں۔
pytest tests/test_quiz_and_coach.py -v
یہ ٹیسٹ LLM کال کی نقل کرتا ہے اور ریاستی معاہدے کی تصدیق کرتا ہے۔ quiz_results صحیح طریقے سے جمع current_topic_index اضافہ کریں اور تصدیق کریں کہ روٹنگ فنکشن درست سٹرنگ لوٹاتا ہے۔
اگلے باب میں، ہم دو پیداواری خصوصیات پر گہری نظر ڈالتے ہیں جو باب 2 کے بعد سے خاموشی سے کام کر رہی ہیں: ریاست کی استقامت، جو تصادم سے بچ جاتی ہے، اور انسانی شراکت دار نگرانی، جو اعتراف کے لیے گراف کو روکتی ہے اور صارف کے جواب دینے پر دوبارہ شروع کرتی ہے۔
باب 5: ریاست کی پائیداری اور انسانی نگرانی
باب 2 کے بعد پس منظر میں دو مسائل خاموشی سے حل ہو گئے۔ نظام کریشوں سے بچ سکتا ہے اور انسانی فیصلوں کا انتظار کرنے کے لیے درمیانی عمل کو روک سکتا ہے۔ یہ باب واضح طور پر دونوں کی وضاحت کرتا ہے۔ یہ سمجھنا وہی ہے جو ڈیمو کو پروڈکشن سسٹم سے الگ کرتا ہے۔
5.1 چیک پوائنٹس دراصل کیا کرتے ہیں۔
جب بھی لینگ گراف نوڈ مکمل ہوتا ہے، فریم ورک پورے نوڈ کو سیریلائز کرتا ہے۔ AgentState میں اسے SQLite میں شامل کرتا ہوں اور اسے نیچے لکھتا ہوں۔ thread_id. وہ تھریڈ آئی ڈی سیشن آئی ڈی ہے جو آغاز پر بنائی گئی تھی۔ run_session.
ڈیٹا بیس کا ڈھانچہ آسان ہے۔
data/checkpoints.db
└── checkpoints table
thread_id = "a3f1b2c4" ← your session ID
checkpoint blob ← serialized AgentState after each node
فی سیشن ایک سے زیادہ چیک پوائنٹس جمع ہوتے ہیں، ہر نوڈ کے بعد ایک۔ LangGraph ہمیشہ تازہ ترین معلومات لوڈ کرتا ہے۔ جب آپ کال کرتے ہیں۔ graph.invoke(None, config={"configurable": {"thread_id": "a3f1b2c4"}})لینگ گراف اس تھریڈ آئی ڈی کے لیے حالیہ چیک پوائنٹ کو پڑھتا ہے اور وہاں سے انتخاب کرتا ہے۔
کہ get_langfuse_config فنکشن src/observability/langfuse_setup.py تھریڈ آئی ڈی کو پاس کرتے ہوئے ایک کنفیگریشن لغت بنائیں۔
def get_langfuse_config(session_id: str) -> dict:
"""
Build the graph run config with session ID as the checkpoint thread ID.
The config is passed to graph.invoke() on every call: both the initial
invocation and any subsequent resume calls. LangGraph uses the thread_id
to find and load the right checkpoint.
"""
config = {
"configurable": {
"thread_id": session_id,
}
}
# If Langfuse is configured, callbacks are added here (Chapter 6)
handler = get_langfuse_handler(session_id)
if handler:
config["callbacks"] = [handler]
return config
یہ کنفیگریشن آبجیکٹ سیاق و سباق کا واحد ٹکڑا ہے جو ہر چیز کو جوڑتا ہے۔ graph.invoke اسی چیک پوائنٹ کے ریکارڈ کے لیے سیشن کو کال کریں۔
SqliteSaver کنکشن پیٹرن
SqliteSaver کو دو طریقوں سے شروع کیا جا سکتا ہے: سیاق و سباق مینیجر فارم (with SqliteSaver.from_conn_string(...) as checkpointer) درج ذیل صورتوں میں کنکشن بند کریں: with بلاک ختم ہوتا ہے۔ سے graph = build_graph() یہ ماڈیول سطح کے متغیر ہیں جو پورے عمل پر لاگو ہوتے ہیں۔ with بلاک فوری طور پر کنکشن بند کر دیتا ہے۔ build_graph() رپورٹ سب کچھ کے بعد graph.invoke اگر آپ بند ڈیٹا بیس پر لکھنے کی کوشش کرتے ہیں، تو کال ناکام ہو جائے گی۔
صحیح نمونہ ہے۔ conn = sqlite3.connect(db_path, check_same_thread=False) پھر checkpointer = SqliteSaver(conn). کنکشن عمل کی زندگی بھر کے لیے کھلا رہتا ہے۔
کہ check_same_thread=False ایک جھنڈا درکار ہے۔ SQLite کا ڈیفالٹ ایک تھریڈ میں بنائے گئے کنکشن کو دوسرے تھریڈ کے استعمال سے روکتا ہے۔ لینگ گراف نوڈ کے افعال کو انجام دیتا ہے اور اندرونی طور پر دوسرے دھاگوں پر چوکیاں لکھتا ہے۔ اس جھنڈے کے بغیر آپ کو ملتا ہے: ProgrammingError: SQLite objects created in a thread can only be used in that same thread رن ٹائم پر۔
5.2 انسانی منظوری کے نوڈس: معطل اور دوبارہ شروع کریں۔
انسانی منظوری نوڈس کا استعمال: interrupt() عمل کے دوران گراف کو روکتا ہے۔ اس طرح لینگ گراف ہیومن-ان-دی-لوپ کو نافذ کرتا ہے۔ نوڈ کے اندر عملدرآمد روک دیا جاتا ہے، اس کی حالت کی جانچ پڑتال کی جاتی ہے، اور کنٹرول کال کرنے والے کو واپس کر دیا جاتا ہے. جب دوسرا فریق بلاتا ہے۔ graph.invoke(Command(resume=value), config=config)اسی نوڈ کے اندر درست لائن پر عملدرآمد دوبارہ شروع ہوتا ہے۔ interrupt() کے ساتھ بلایا گیا تھا۔ decision پر سیٹ کریں value.
# src/agents/human_approval.py
from langgraph.types import interrupt
from graph.state import StudyRoadmap
def human_approval_node(state: dict) -> dict:
"""
LangGraph node: Human Approval
Reads: state["roadmap"]
Writes: state["approved"]: True if approved, False if rejected.
Also returns all other state keys explicitly (see note below).
When approved=False, the conditional edge routes back to the
Curriculum Planner to generate a new roadmap.
When approved=True, the graph continues to the Explainer.
"""
roadmap = state.get("roadmap")
if roadmap is None:
return {"approved": True}
print(f"n[Human Approval] Pausing for roadmap review...")
# interrupt() pauses execution here.
# The dict passed to interrupt() is the payload. The caller reads this
# to know what to display to the user.
# Execution resumes when Command(resume=value) is called by the caller.
decision = interrupt({
"type": "roadmap_approval",
"roadmap": roadmap,
"prompt": (
"Does this study plan look good?n"
" Type 'yes' to start studyingn"
" Type 'no' to generate a different plan"
),
})
approved = str(decision).lower().strip() in ("yes", "y", "ok", "approve")
if approved:
print(f"[Human Approval] Roadmap approved. Starting study session.")
else:
print(f"[Human Approval] Roadmap rejected. Regenerating...")
# LangGraph 1.1.0: after Command(resume=...), the next node receives only
# the keys returned by this node. Not the full pre-interrupt checkpoint.
# Returning the complete state explicitly ensures downstream agents
# (explainer, quiz_generator, progress_coach) receive roadmap, session_id, etc.
return {
"approved": approved,
"roadmap": roadmap,
"goal": state.get("goal", ""),
"session_id": state.get("session_id", ""),
"current_topic_index": state.get("current_topic_index", 0),
"quiz_results": state.get("quiz_results", []),
"weak_areas": state.get("weak_areas", []),
"study_materials_path": state.get("study_materials_path",
"study_materials/sample_notes"),
"error": None,
}
اس فنکشن کے نچلے حصے میں لینگ گراف 1.1.0 کی تفصیل اصل رویے کی دستاویز کرتی ہے جو پیداوار میں پیش آئے گا۔ Command(resume=...)اگلے نوڈ کی حالت میں صرف وہی ہوتا ہے جو مداخلت شدہ نوڈ واضح طور پر واپس کرتا ہے۔ جب نوڈ صرف واپس آتا ہے۔ {"approved": True}وضاحت کنندہ نوڈ ایک خالی حالت حاصل کرتا ہے۔ roadmapنہیں session_idنہیں current_topic_indexیہ فوری طور پر ایک غلطی واپس کرتا ہے۔
یہ کوڈ میں کوئی بگ نہیں ہے۔ معطل/دوبارہ شروع کرنے کے بعد LangGraph 1.1.0 میں ریاستی پھیلاؤ کے لیے یہ ایک معروف رویہ ہے۔ ایک حل واضح طور پر مجموعی حالت کو واپس کرنا ہے۔
ڈاون اسٹریم نوڈس کے لیے درکار تمام اسٹیٹ کیز کو ریٹرن ڈکشنری میں ظاہر ہونا چاہیے۔ انٹرپٹ/ریزیوم باؤنڈری کے بعد چلنے والے نوڈس کے ساتھ ایسا سلوک کیا جانا چاہئے جیسے وہ ضم شدہ چوکی سے بجائے شروع سے ہی حالت حاصل کر رہے ہوں۔
Interrupt() بمقابلہ Interrupt_before
لینگ گراف گراف کو روکنے کے دو طریقے فراہم کرتا ہے: interrupt_before=["node_name"] کو builder.compile() توقف پہلے یہ ایک نامزد نوڈ ہے اور مرتب وقت پر بنایا گیا ہے۔ interrupt() بلایا اندر ایک نوڈ کو اس کے عمل کے دوران روک دیا جاتا ہے اور اس میں ایک پے لوڈ (ایک لغت جسے کال کرنے والا یہ جاننے کے لیے پڑھتا ہے کہ صارف کو کیا دکھانا ہے) پر مشتمل ہو سکتا ہے۔
یہ نظام interrupt() اندر human_approval_node اس کی وجہ یہ ہے کہ منظوری کے مرحلے کے لیے روڈ میپ آبجیکٹ کو کال کرنے والے کو منتقل کرنے کی ضرورت ہوتی ہے۔ کہ interrupt_before نوڈ چلنے سے پہلے نقطہ نظر رک جاتا ہے، لیکن روڈ میپ بنایا جاتا ہے۔ اندر نوڈ کا پیشرو (curriculum_planner_node)۔ استعمال کریں interrupt() نوڈس روڈ میپ حاصل کر سکتے ہیں، منظوری کا پے لوڈ بنا سکتے ہیں، اور توقف کر سکتے ہیں، یہ سب صحیح ترتیب میں ہے۔
Streamlit UI استعمال کرتا ہے: build_graph(interrupt_before=["quiz_generator"]) ایک اور وجہ: آپ کو گراف کو روکنے سے پہلے اسے روکنے کی ضرورت ہے۔ quiz_generator_node اس طرح چلتا ہے۔ input() اسے گراف تھریڈ کے اندر نہیں کہا جاتا ہے۔ دونوں میکانزم ان کے استعمال کے معاملات کے لیے موزوں ہیں۔
5.3 ہینڈلنگ میں خلل ڈالنا main.py
بھیجنے والا graph.invoke ہمیں ایسے معاملات کو سنبھالنے کی ضرورت ہے جہاں گراف موقوف ہے۔ لینگ گراف ایک توقف کا اشارہ کرتا ہے، بشمول: "__interrupt__" نتیجے میں ڈکٹ میں. رکاوٹ پے لوڈ (جو ڈکٹ آپ نے پاس کیا ہے۔ interrupt()) میں ہے۔ result["__interrupt__"][0].value.
# main.py: the interrupt/resume loop
from langgraph.types import Command
result = graph.invoke(state, config=config)
while "__interrupt__" in result:
interrupt_payload = result["__interrupt__"][0].value
roadmap = interrupt_payload.get("roadmap")
# Display the roadmap for the user to review
if roadmap:
print(f"n{'='*60}")
print("Proposed Study Plan")
print(f"{'='*60}")
print(f"Goal: {roadmap.goal}")
print(f"Duration: {roadmap.total_weeks} weeks @ "
f"{roadmap.weekly_hours} hrs/weekn")
for i, topic in enumerate(roadmap.topics, 1):
prereqs = (f" (needs: {', '.join(topic.prerequisites)})"
if topic.prerequisites else "")
print(f" {i}. {topic.title} ({topic.estimated_minutes} min){prereqs}")
print(f" {topic.description}")
print(f"n{interrupt_payload.get('prompt', 'Continue?')}")
user_input = input("> ").strip()
# Resume the graph with the user's decision.
# Command(resume=value) is how you pass input back to the interrupted node.
result = graph.invoke(Command(resume=user_input), config=config)
کہ while لوپ ایسے معاملات کو سنبھالتا ہے جہاں روڈ میپ کو مسترد کرنے سے منصوبہ ساز کو دوبارہ تخلیق کیا جاتا ہے، جس سے ایک اور رکاوٹ پیدا ہوتی ہے۔ جب صارف ان پٹ دیتا ہے۔ noگراف چلتا ہے۔ curriculum_planner_node ایک بار پھر، یہ ایک نیا روڈ میپ اور ہٹ دیتا ہے۔ interrupt() ایک بار پھر، آپ کو اپنا نیا منصوبہ نظر آئے گا۔ صارفین اس وقت تک آپٹ آؤٹ کرنا جاری رکھ سکتے ہیں جب تک وہ مطمئن نہ ہوں۔ لوپ صرف اس وقت ختم ہوتا ہے جب گراف مکمل ہوتا ہے بغیر کسی رکاوٹ کے۔
ساخت ٹھیک ٹھیک سمجھنے کے قابل ہے۔
graph.invoke(initial_state, config)
→ runs: curriculum_planner → human_approval (interrupt() fires)
→ returns: {"__interrupt__": [...]} ← caller reads roadmap from here
main.py shows roadmap, collects "yes"
graph.invoke(Command(resume="yes"), config)
→ resumes: human_approval (decision = "yes", approved = True)
→ continues: explainer → quiz_generator → progress_coach → ... → END
→ returns: final state dict ← no "__interrupt__" key
کہ config ڈکشنری thread_id دونوں ایک جیسے ہیں graph.invoke فون کال اس طرح لینگ گراف کو یہ معلوم ہوتا ہے کہ نئے سرے سے شروع کرنے کے بجائے روکے ہوئے نوڈ سے چیک پوائنٹ کو کیسے لوڈ کرنا ہے۔
5.4 کریش ہونے والے سیشنز کو دوبارہ شروع کریں۔
وہی طریقہ کار جو اعترافات کو سنبھالتا ہے تنازعات کی بحالی کو بھی سنبھالتا ہے۔ اگر عمل کے درمیان ختم ہوجاتا ہے۔ explainer_node اور quiz_generator_nodeSQLite چوکیوں کی اپنی مجموعی حالت آخری مکمل شدہ نوڈ کی بنیاد پر ہوتی ہے۔ ایک نیا عمل شروع کریں اور اسی عمل کو کال کریں۔ thread_id اسے وہاں سے اٹھاؤ۔
کہ --resume ایک جھنڈا لگائیں main.py اس کو نافذ کریں:
# main.py
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Learning Accelerator")
parser.add_argument("goal", nargs="?",
default="Learn Python closures and decorators from scratch")
parser.add_argument("--resume", metavar="SESSION_ID",
help="Resume an existing session by ID")
args = parser.parse_args()
if args.resume:
run_session(goal="", session_id=args.resume)
else:
run_session(goal=args.goal)
اندر run_sessionریزیومے اور نئی شروعات کے درمیان بالکل ایک لائن کا فرق ہے۔
# For a new session: provide initial state
state = initial_state(goal, session_id)
# For a resume: pass None. LangGraph loads from the checkpoint.
state = None if is_resume else initial_state(goal, session_id)
result = graph.invoke(state, config=config)
جب state ہے NoneLangGraph تازہ ترین چیک پوائنٹ کو لوڈ کرتا ہے۔ thread_id کو config یہ آخری مکمل نوڈ سے جاری ہے۔ اصل سیشن شروع ہونے پر آپ کو صرف سیشن ID پرنٹ کرنے کی ضرورت ہے۔
# Original session printed: Session ID: a3f1b2c4
# Process died mid-session
python main.py --resume a3f1b2c4
============================================================
Learning Accelerator
Session ID: a3f1b2c4
Resuming existing session...
============================================================
[Explainer] Topic: 'Creating Closures'
...
گراف کو اگلے نامکمل نوڈ سے منتخب کیا گیا ہے۔ وہ موضوعات جو پہلے ہی نافذ کیے جا چکے ہیں (بشمول وضاحتیں، کوئز کے نتائج، اور کوچنگ پیغامات) وہی رہیں گے۔ صرف باقی کاموں کو انجام دیا جائے گا۔
5.5 ڈی سیریلائزیشن کی تفصیلات جو آپ کو جاننے کی ضرورت ہے۔
جب LangGraph SQLite سے ایک چوکی لوڈ کرتا ہے، تو یہ محفوظ شدہ حالت کو دوبارہ ازگر آبجیکٹ میں تبدیل کر دیتا ہے۔ قدیم اقسام (سٹرنگ، انٹیجر، سٹرنگز کی فہرست) کے لیے یہ شفاف ہے۔ حسب ضرورت ڈیٹا کلاسز کے لیے (Topic، StudyRoadmap، QuizResult)، LangGraph اندرونی msgpack سیریلائزر استعمال کر سکتا ہے اور اسے ڈیٹا کلاس مثال کے بجائے ایک باقاعدہ لغت کے طور پر واپس کر سکتا ہے۔
یہی وجہ ہے۔ get_current_topic، session_is_completeاور get_latest_quiz_result کو state.py دونوں دونوں شکلیں سنبھالتے ہیں۔
def get_current_topic(state: dict) -> Topic | None:
roadmap = state.get("roadmap")
if roadmap is None:
return None
# After checkpoint deserialization, roadmap may be a dict
if isinstance(roadmap, dict):
topics_raw = roadmap.get("topics", [])
else:
topics_raw = roadmap.topics
idx = state.get("current_topic_index", 0)
if idx >= len(topics_raw):
return None
t = topics_raw[idx]
# Individual topics may also be dicts after deserialization
if isinstance(t, dict):
return Topic.from_dict
return t
اور وجہ یہ ہے۔ Topic، StudyRoadmapاور QuizResult سب کے پاس ہے from_dict کلاس کا طریقہ۔ یہ صرف ایک سہولت کی چیز نہیں ہے، یہ آپ کے ریزیومے کے صحیح طریقے سے کام کرنے کی ضرورت ہے۔
یہی پیٹرن کسی بھی پروڈکشن سسٹم پر لاگو ہوتا ہے جو اپنی مرضی کے مطابق اشیاء کی جانچ کرتا ہے۔ اگر آپ کی ریاست میں ڈیٹا کلاسز یا Pydantic ماڈل شامل ہیں، تو تمام ریاستی رسائی کاروں کو لائیو اور ڈی سیریلائزڈ فارمز کو ہینڈل کرنے کے لیے تیار کریں۔ یہ مت سمجھو کہ قسم وہی ہوگی جو آپ نے درج کی ہے۔ استعمال کے مقام پر چیک کریں۔
5.6 ٹیسٹ سیشن استقامت
ایک سیشن چلائیں، اسے درمیان میں چھوڑ دیں، اور چیک کریں کہ آیا آپ کا ریزیوم کام کرتا ہے۔
rm -f data/checkpoints.db
python main.py "Learn Python closures"
آپ کے داخل ہونے کے بعد روڈ میپ ظاہر ہوگا۔ yesانتظار کرو جب تک تم اسے نہ دیکھو [Explainer] Complete after N LLM call(s). پھر Ctrl+C عمل کو ختم کرتا ہے۔ سٹارٹ اپ پر پرنٹ کی گئی سیشن ID کو نوٹ کریں۔
اب دوبارہ شروع کریں۔
python main.py --resume
کوئز جنریٹر میں سیشن جاری رہنا چاہیے۔ چونکہ وضاحت پہلے ہی فراہم کی جا چکی ہے، آئیے براہ راست پہلے موضوع کے سوال کی طرف آتے ہیں۔
چوکی: چیک پوائنٹ ٹیسٹ چلائیں۔
pytest tests/test_checkpointing.py -v
توقع: 20 ٹیسٹ، سبھی پاس ہو گئے۔ یہ ٹیسٹ چیک پوائنٹ راؤنڈ ٹرپ کی تصدیق کرتے ہیں۔ یعنی، اس بات کو یقینی بنائیں کہ عمل درآمد کے دوران رکاوٹ پیدا ہونے والے سیشنز کو متوقع حالت پیدا کرنے کے لیے دوبارہ شروع کیا جا سکتا ہے، اور یہ کہ dict-vs-dataclass ڈیسیریلائزیشن کو درست طریقے سے ہینڈل کیا گیا ہے۔
انٹرپرائز کنیکٹ: سیلز ایبلمنٹ پلیٹ فارم مینیجر کی منظوریوں کے لیے ایک ہی چیک پوائنٹ پیٹرن کا استعمال کرتے ہیں۔
جب کریکولم ایجنٹ نئے کرایہ کے لیے تربیتی منصوبہ بناتا ہے، تو گراف رک جاتا ہے اور منتظم کو ایک اطلاع بھیجی جاتی ہے۔ منتظمین ویب ڈیش بورڈ سے منصوبوں کا جائزہ لیتے ہیں، منظوری دیتے ہیں یا ان میں ترمیم کرتے ہیں اور انہیں جمع کراتے ہیں۔ متعلقہ HTTP POST کال graph.invoke(Command(resume=decision), config=config). LangGraph کوڈ ٹرمینل ورژن کی طرح ہے۔ صرف اطلاع کا طریقہ کار اور ان پٹ جمع کرنے میں فرق ہے۔
اگلا باب مشاہدے کا اضافہ کرتا ہے۔ لینگ فیوز تمام ایجنٹ کالز، ایل ایل ایم کالز، اور ٹول ایگزیکیوشنز کو ساختی نشانات میں کیپچر کرتا ہے جن سے استفسار اور تصور کیا جا سکتا ہے۔
باب 6: لینگ فیوز کے ساتھ مشاہدہ
ایک ملٹی ایجنٹ سسٹم جو غلطیوں کے بغیر غلط آؤٹ پٹ پیدا کرتا ہے اسے کریش ہونے والے سسٹم سے ڈیبگ کرنا زیادہ مشکل ہے۔ معیاری انفراسٹرکچر میٹرکس (CPU، میموری، درخواست میں تاخیر، ایرر ریٹ) آپ کو بتاتے ہیں کہ سسٹم صحت مند ہے جبکہ ایجنٹ غلط اندازہ لگا رہا ہے۔ ہمیں ایک مختلف قسم کے مشاہدے کی ضرورت ہے جو نہ صرف اس بات کی گرفت کرے کہ آیا کال کی گئی تھی، بلکہ ماڈل نے کیا فیصلہ کیا تھا اور کیوں کیا تھا۔
لینگ فیوز یہ فراہم کرتا ہے۔ تمام LLM کالز، تمام ٹول کالز، اور ہر قدم کے لیے مکمل میسج ہسٹری، ٹریک اور سیشن کے لحاظ سے گروپ کیا جاتا ہے۔ اگر کچھ غلط ہو جاتا ہے، تو آپ اس سیشن کے لیے ایک ٹریس کھول سکتے ہیں اور دیکھ سکتے ہیں کہ ہر ایجنٹ کو کیا ملا، اسے کیا کہا گیا، اور اس نے کیا واپس کیا۔
اس باب میں، آپ ایک واحد انٹیگریشن پوائنٹ اور نارمل انحطاط کے نمونوں کا استعمال کرتے ہوئے اپنے سسٹم میں Langfuse شامل کریں گے۔ یہ نظام لینگ فیوز کے کنفیگر کردہ کے ساتھ یا اس کے بغیر یکساں طور پر چلتا ہے۔
6.1 ڈوکر کا استعمال کرتے ہوئے مقامی طور پر لینگ فیوز چلانا
لینگ فیوز اس ٹیوٹوریل کے لیے خود میزبان ہے۔ تمام نشانات آپ کے کمپیوٹر پر رکھے جاتے ہیں۔ کسی API کلید کی ضرورت نہیں ہے اور آپ کا ڈیٹا کبھی بھی نیٹ ورک کو نہیں چھوڑتا ہے۔ کہ docker-compose.yml ریپوزٹری سے مکمل لینگ فیوز اسٹیک لانچ کریں۔
# docker-compose.yml
services:
langfuse-server:
image: langfuse/langfuse:3
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/langfuse
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: local-dev-secret-change-in-production
SALT: local-dev-salt-change-in-production
ENCRYPTION_KEY: "0000000000000000000000000000000000000000000000000000000000000000"
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: "true"
TELEMETRY_ENABLED: "false"
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: langfuse
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- langfuse_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d langfuse"]
interval: 5s
retries: 10
volumes:
langfuse_postgres_data:
اسٹیک شروع کریں۔
docker compose up -d
پوسٹگریس شروع ہونے کے لیے تقریباً 20 سیکنڈ انتظار کریں۔ پھر http://localhost:3000 کھولیں، ایک اکاؤنٹ بنائیں (مقامی، ای میل کی تصدیق کی ضرورت نہیں) اور ایک پروجیکٹ بنائیں جس کا نام ہے: learning-accelerator.
لینگ فیوز نیچے API کلید دکھاتا ہے۔ ترتیبات → API کلید. عوامی اور نجی دونوں چابیاں کاپی کریں۔ .env:
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=http://localhost:3000
6.2 مشاہداتی ماڈیول
انضمام مکمل طور پر ہے۔ src/observability/langfuse_setup.py. پروجیکٹ میں دیگر تمام فائلیں غیر تبدیل شدہ رہیں۔ ایجنٹ نوڈ نہیں جانتا کہ آیا وہ اس ماڈیول سے درآمد کرتا ہے، لینگ فیوز فنکشنز کو کال کرتا ہے، یا اس میں کوئی مشاہداتی چل رہا ہے۔
مشاہدے کے لیے یہ صحیح فن تعمیر ہے۔ ایجنٹ فنکشن کے اندر لاگنگ کالز شامل کرکے، آپ ایجنٹ کی منطق کو مشاہداتی فریم ورک میں جوڑ رہے ہیں۔ لینگ فیوز کو دوسرے ٹول سے تبدیل کرنے کا مطلب ہے تمام ایجنٹوں کو چھو لینا۔ کال بیک پیٹرن کاروباری منطق سے اس مجموعہ کو مکمل طور پر خارج کرتا ہے۔
ماڈیول میں چار فنکشنز ہیں جن میں یک طرفہ انحصار ہوتا ہے: ہر ایک پچھلے پر بناتا ہے۔
# src/observability/langfuse_setup.py
import os
def _langfuse_configured() -> bool:
"""
Check whether Langfuse credentials are present in the environment.
Returns False if either key is missing or empty. In that case the
system runs without observability rather than raising an error.
"""
public_key = os.getenv("LANGFUSE_PUBLIC_KEY", "").strip()
secret_key = os.getenv("LANGFUSE_SECRET_KEY", "").strip()
return bool(public_key and secret_key)
_langfuse_configured() گارڈ دیگر تمام افعال کے ذریعہ استعمال کیا جاتا ہے۔ کوئی اسناد کا مطلب لینگ فیوز نہیں ہے، لیکن سسٹم پھر بھی چلے گا۔ یہ ایک خوبصورت انحطاط کا نمونہ ہے۔ مشاہدہ ایک پیداواری بہتری ہے، سخت انحصار نہیں۔
def get_langfuse_handler(session_id: str, user_id: str = "local"):
"""
Create a Langfuse callback handler for a session, or None if not configured.
The handler is a LangChain CallbackHandler that Langfuse provides.
When attached to graph.invoke(), it intercepts every LLM call, tool call,
and chain invocation automatically. No changes to agent code required.
"""
if not _langfuse_configured():
return None
try:
from langfuse.langchain import CallbackHandler
return CallbackHandler(
public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
host=os.getenv("LANGFUSE_HOST", "http://localhost:3000"),
session_id=session_id,
user_id=user_id,
tags=["learning-accelerator", "local-inference"],
metadata={
"model": os.getenv("OLLAMA_MODEL", "qwen2.5:7b"),
"framework": "langgraph",
},
)
except ImportError:
print("[Observability] langfuse not installed. Run: pip install langfuse")
return None
except Exception as e:
print(f"[Observability] Failed to create handler: {e}")
return None
کہ session_id منتقلی CallbackHandler Langfuse UI میں ایک مطالعاتی سیشن کے تمام نشانات کو ایک ساتھ گروپ کریں۔ اس سیشن کے لیے تمام LLM کالز، ٹول کالز، اور نوڈ ایگزیکیوشنز سنگل سیشن ویو کے تحت دکھائے جاتے ہیں۔ آپ اپنے مقصد میں داخل ہونے سے لے کر کوئز کے حتمی نتائج تک پورے انفرنس چین کی پیروی کر سکتے ہیں۔
کہ tags لسٹ لینگ فیوز میں فلٹر ایبل لیبلز کے ساتھ ظاہر ہوتی ہے۔ اگر آپ متعدد پروجیکٹ چلاتے ہیں، "learning-accelerator" آپ اس سسٹم سے صرف نشانات کو فلٹر کر سکتے ہیں۔
def get_langfuse_config(
session_id: str,
user_id: str = "local",
extra_config: dict | None = None,
) -> dict:
"""
Build the complete LangGraph run config for a session.
Merges the checkpoint thread_id with the Langfuse callback handler.
This is the only function main.py calls. One function, one config dict,
everything set up.
Returns a dict ready to pass as `config` to graph.invoke().
"""
config = {
"configurable": {"thread_id": session_id},
}
if extra_config:
config.update(extra_config)
handler = get_langfuse_handler(session_id, user_id)
if handler:
config["callbacks"] = [handler]
print(f"[Observability] Tracing session {session_id} → "
f"{os.getenv('LANGFUSE_HOST', 'http://localhost:3000')}")
else:
print(f"[Observability] Langfuse not configured. Running without tracing.")
return config
get_langfuse_config دونوں خدشات کو ایک ڈکٹ میں ضم کرتا ہے۔ thread_id لینگ گراف چیک پوائنٹنگ کے لیے کیا استعمال کرتا ہے۔ callbacks وہ فہرست جو LangChain مشاہداتی واقعات کو روٹ کرنے کے لیے استعمال کرتی ہے۔
ان دو کلیدوں کے ایک ساتھ رہنے کی وجوہات درج ذیل ہیں: graph.invoke(state, config=config) روٹنگ کے لیے پوری ترتیب کو LangGraph پر منتقل کریں۔ configurable پوائنٹر کلید چیک کریں اور callbacks کال بیک سسٹم میں۔ کوئی بھی نظام دوسرے میں مداخلت نہیں کرتا۔
def flush_langfuse() -> None:
"""
Flush pending traces before process exit.
Langfuse sends traces in a background thread. Without this call,
the last few seconds of traces may be lost when the process exits.
Call this at the end of main.py, after all graph.invoke() calls.
"""
if not _langfuse_configured():
return
try:
from langfuse import Langfuse
Langfuse().flush()
except Exception:
pass # Best-effort. Don't crash on exit.
کہ flush اصل میں اسے ایک مسئلہ کہتے ہیں۔ Langfuse بیچوں کا نشان لگاتا ہے اور انہیں غیر مطابقت پذیر طور پر بھیجتا ہے۔ مختصر چلنے والے عمل جیسے: python main.py آپ بیچ بھیجے جانے سے پہلے چھوڑ سکتے ہیں۔ flush() یہ اس وقت تک بلاک ہوجاتا ہے جب تک کہ قطار خالی نہ ہو۔
6.3 انضمام کا واحد نقطہ
مندرجہ بالا تمام main.py بالکل دو جگہوں پر:
# main.py
from observability.langfuse_setup import get_langfuse_config, flush_langfuse
def run_session(goal: str, session_id: str | None = None) -> None:
...
# One function call replaces: {"configurable": {"thread_id": session_id}}
# It returns that same dict, plus callbacks if Langfuse is configured.
config = get_langfuse_config(session_id)
result = graph.invoke(state, config=config)
while "__interrupt__" in result:
...
result = graph.invoke(Command(resume=user_input), config=config)
print_session_summary(result)
# Flush before exit
flush_langfuse()
یہ مکمل انضمام ہے۔ ایجنٹ فائل میں کوئی درآمدات نہیں ہیں۔ لینگ فیوز کالیں پورے کوڈ بیس میں تقسیم نہیں کی جاتی ہیں۔ نوڈ کے افعال میں مشروط چیک نہیں ہوتے ہیں۔ کال بیک ہینڈلر LangChain فریم ورک کی سطح پر کالوں کو روکتا ہے۔ آپ کا ایجنٹ کوڈ وہی رہے گا۔
کال بیک سسٹم خود بخود کیا حاصل کرتا ہے۔
کہ CallbackHandler LangChain کے کال بیک پروٹوکول سے جڑتا ہے۔ LangChain ہم آہنگ اشیاء (ChatOllamaکال بیک ایونٹ اس وقت فائر کیا جاتا ہے جب کوئی ٹول (ٹول، چین، گراف نوڈ) عمل درآمد شروع یا مکمل کرتا ہے۔ لینگ فیوز کا ہینڈلر اسے پکڑتا ہے اور اسے ٹریکنگ اسکوپ کے طور پر ریکارڈ کرتا ہے۔
اس نظام کے لیے یہ سب کا مطلب ہے۔ llm.invoke() تمام 5 ایجنٹوں کو کال کریں، TOOL_MAP[name].invoke(args) وضاحت کنندہ کے ٹول کال لوپ میں، کال، ہر نوڈ کے آغاز اور اختتام کا وقت، اور ہر مرحلے کی مکمل میسج ہسٹری کیپچر کی جاتی ہے، یہ سب ایجنٹ کے کوڈ کو تبدیل کیے بغیر۔
6.4 لینگ فیوز UI کیا دکھاتا ہے۔
Langfuse ترتیب شدہ سیشن چلاتا ہے۔
python main.py "Learn Python closures"
http://localhost:3000 کھولیں اور اس پر جائیں: ثبوت. آپ کو اپنے سیشن کے لیے ایک نشان نظر آئے گا۔ براہ کرم پھیلائیں:
Session: a3f1b2c4
├── curriculum_planner_node 245ms
│ └── ChatOllama.invoke 238ms
│ input: "Create a study roadmap for..."
│ output: {"goal": "Learn Python closures", "topics": [...]}
│
├── human_approval_node (interrupted, user input collected)
│
├── explainer_node 4,821ms
│ ├── ChatOllama.invoke 312ms → tool_list_files()
│ ├── tool_list_files 2ms ← ["closures.md", ...]
│ ├── ChatOllama.invoke 287ms → tool_read_file("closures.md")
│ ├── tool_read_file 1ms ← "# Python Closuresn..."
│ ├── ChatOllama.invoke 1,204ms → (no tool calls. final explanation)
│ └── tool_memory_set 1ms
│
├── quiz_generator_node 8,342ms
│ ├── ChatOllama.invoke 1,890ms (question generation)
│ ├── ChatOllama.invoke 892ms (grading Q1)
│ ├── ChatOllama.invoke 874ms (grading Q2)
│ └── ChatOllama.invoke 891ms (grading Q3)
│
└── progress_coach_node 1,102ms
└── ChatOllama.invoke 1,088ms
یہ ٹریس فوری طور پر تین چیزوں کو ظاہر کرتا ہے جو انفراسٹرکچر میٹرکس ظاہر نہیں کرتی ہیں:
-
ایجنٹ کے ذریعہ تاخیر کا تجزیہ۔ کوئز جنریٹر چار LLM کالوں کے لیے 8 سیکنڈ لیتا ہے۔ جب تاخیر کو بہتر بنانے کی ضرورت ہوتی ہے، تو درجہ بندی والی کالز کا مقصد ہوتا ہے۔ ہر ایک ~900ms کی تین کالیں، ممکنہ طور پر متوازی۔
-
ٹول کالنگ آرڈر۔ ایک وضاحت کنندہ نے بلایا۔
tool_list_filesپھرtool_read_fileپھر میں نے اسے صحیح ترتیب میں میموری پر لکھا۔ اگر وہ غلط ترتیب میں ہیں، تو کوڈ کو دیکھنے سے پہلے یہاں چیک کریں۔ -
ہر مرحلے پر ایل ایل ایم ان پٹ اور آؤٹ پٹ۔ اگر Curriculum Planner ایک غلط روڈ میپ تیار کرتا ہے، تو آپ کو ٹریس میں خام LLM آؤٹ پٹ نظر آئے گا۔ اگر گریڈر آپ کو غلط سکور دیتا ہے، تو آپ دیکھ سکتے ہیں کہ آپ کو کیا ملا اور آپ نے کیا واپس کیا۔
6.5 مکرم انحطاط
سسٹم کو لینگ فیوز کے ساتھ یا اس کے بغیر یکساں طور پر چلانے کے لیے ڈیزائن کیا گیا ہے۔ اگر آپ ماحولیاتی متغیرات مرتب نہیں کرتے ہیں۔ _langfuse_configured() False واپس کرتا ہے۔ get_langfuse_config یہ درج ذیل کم سے کم کنفیگریشن واپس کرتا ہے: thread_id:
# Without Langfuse configured
config = get_langfuse_config("a3f1b2c4")
# Returns: {"configurable": {"thread_id": "a3f1b2c4"}}
# With Langfuse configured
config = get_langfuse_config("a3f1b2c4")
# Returns: {"configurable": {"thread_id": "a3f1b2c4"},
# "callbacks": []}
ایجنٹ نوڈس کو اس کنفیگریشن کا کوئی ورژن موصول نہیں ہوگا۔ وہ صرف وصول کرتے ہیں state. کنفیگریشن کا استعمال LangGraph اور LangChain انفراسٹرکچر کے ذریعے کیا جاتا ہے، کاروباری منطق نہیں۔
یہ صحیح پیداوار پیٹرن ہے. مشاہداتی بنیادی ڈھانچہ خود بخود ناکام ہو جانا چاہئے اور خوبصورتی سے انحطاط پذیر ہونا چاہئے۔ ٹریکنگ بیک اینڈ میں بندش سے آپ کی ایپلیکیشن کریش نہیں ہونی چاہیے۔
6.6 مشاہداتی ٹیسٹ چلانا
pytest tests/test_observability.py -v
متوقع: 16 ٹیسٹ پاس ہوئے، لینگ فیوز سرور کی ضرورت نہیں ہے۔ ٹیسٹ کا مذاق اڑایا جاتا ہے: _langfuse_configured براہ کرم چیک کریں اور تصدیق کریں:
-
get_langfuse_configہمیشہ شاملthread_idکوconfigurable -
نہیں
callbacksکلید ظاہر ہوتی ہے اگر لینگ فیوز کنفیگر نہیں ہے۔ -
flush_langfuseاگر اسناد غائب ہیں تو یہ کام نہیں کرے گا۔ -
get_langfuse_handlerرپورٹNoneکوImportErrorاسے اٹھائے بغیر
ان میں سے کسی بھی ٹیسٹ کے لیے لینگ فیوز سرور کو چلانے کی ضرورت نہیں ہے۔ یہ اس بات کو یقینی بناتا ہے کہ انضمام کی منطق، یعنی ماڈیول ترتیب شدہ اور غیر تشکیل شدہ دونوں حالتوں میں صحیح طریقے سے کام کرتا ہے۔
انٹرپرائز کنیکٹیویٹی: ریگولیٹڈ صنعتوں میں پروڈکشن ملٹی ایجنٹ سسٹم مشاہدے کا اتنا ہی استعمال کرتے ہیں جتنا تعمیل کے لیے ڈیبگنگ کے لیے۔ لینگ فیوز ٹریس تمام LLM کالز (ان پٹ، آؤٹ پٹ، ٹائم اسٹیمپ، سیشن آئی ڈی) کا قابل سماعت ریکارڈ فراہم کرتے ہیں جنہیں ریگولیٹری جائزے کے لیے برآمد کیا جا سکتا ہے۔ وہی ٹریس جو آپ کو کوئز کے غلط اسکورز کو ڈیبگ کرنے میں مدد کرتا ہے وہ آڈیٹرز کو دکھا سکتا ہے کہ ماڈل نے کیا فراہم کیا اور اس نے کیا تیار کیا۔
اگلا باب خودکار معیار کی تشخیص کا اضافہ کرتا ہے۔ DeepEval ایک LLM-ججمنٹ ٹیسٹ چلاتا ہے تاکہ اس بات کو یقینی بنایا جا سکے کہ وضاحت کنندہ کا آؤٹ پٹ نوٹس کے ساتھ وفادار ہے اور کوئز جنریٹر کے سوالات موضوع سے متعلق ہیں۔
باب 7: ڈیپ ایول کے ساتھ ایجنٹ کے معیار کا جائزہ
مشاہدہ ہمیں بتاتا ہے کہ کیا ہوا ہے۔ تشخیص آپ کو یہ جاننے کی اجازت دیتا ہے کہ کیا ہوا اچھا ہے یا نہیں۔
ایک ملٹی ایجنٹ سسٹم حقائق بیان کرنے کی وضاحتیں، سوالات جو غلط ہونے کی جانچ کرتے ہیں، اور ریٹنگز جو غلط جوابات کو درست قرار دیتے ہیں، سب کچھ غلطی سے پاک مکمل کرتے ہوئے پیدا کر سکتا ہے۔
یہ خامیاں انفراسٹرکچر میٹرکس میں ظاہر نہیں ہوتی ہیں۔ یہ زیادہ تر یونٹ ٹیسٹوں میں نظر نہیں آتا۔ اس کو حاصل کرنے کا واحد قابل اعتماد طریقہ یہ ہے کہ ججوں کے طور پر دوسرے LLM کے ساتھ LLM کے نتائج کا جائزہ لیا جائے۔
اس باب میں، ہم تخصیصات کے ساتھ ڈیپ ایول کا استعمال کرتے ہوئے خودکار معیار کا جائزہ شامل کرتے ہیں۔ OllamaJudge کلاس تمام جائزے مقامی طور پر چلائے جاتے ہیں۔ یہاں کوئی کلاؤڈ API کیز نہیں ہیں اور کوئی قیمت فی تشخیص نہیں ہے۔
7.1 بطور جج ایل ایل ایم کی تشخیص
LLM-ججمنٹ ایک ایسا نمونہ ہے جو ایک LLM کال کو دوسری LLM کال کے نتائج کا اندازہ کرنے کے لیے استعمال کرتا ہے۔ وضاحت کنندہ کی طرف سے پیدا کردہ وضاحت کو دیکھتے ہوئے، جج ماڈل وضاحت اور سورس نوٹس کو پڑھتا ہے اور ساختی سوالات کے جوابات دیتا ہے۔ "کیا اس وضاحت کے تمام دعووں کی تائید نوٹوں سے ہوتی ہے؟”
یہ ایک کامل تشخیص نہیں ہے۔ جج کا ماڈل بھی غلط ہو سکتا ہے۔ لیکن معیار کے جائزوں کے لیے جو یہاں اہم ہیں (کیا وضاحتیں دیانتدار ہیں؟ کیا سوالات متعلقہ ہیں؟ کیا اسکورنگ کا عمل منصفانہ ہے؟)، احتیاط سے رہنمائی کرنے والے LLM ججز اصول پر مبنی ہیورسٹکس کو مستقل طور پر بہتر بناتے ہیں اور بڑے پیمانے پر انسانی جائزوں سے کہیں زیادہ عملی ہوتے ہیں۔
DeepEval ایک تشخیصی فریم ورک فراہم کرتا ہے۔ یہ ججوں کی فوری تشکیل، اسکورنگ روبرکس، اور میٹرک جمع کو سنبھالتا ہے۔ ٹیسٹ کیسز فراہم کرتا ہے اور اختیاری طور پر اپنی مرضی کے ماڈل فراہم کرتا ہے۔
7.2 اولاما جج کلاس
ڈیپ ایول بطور ڈیفالٹ OpenAI استعمال کرتا ہے۔ تشخیص کو مقامی رکھنے کے لیے، ذیلی کلاس لکھیں۔ DeepEvalBaseLLM اپنے اولاما مثال سے جڑیں۔
# tests/test_eval.py
import os
from deepeval.models import DeepEvalBaseLLM
from langchain_ollama import ChatOllama
class OllamaJudge(DeepEvalBaseLLM):
"""
Custom judge model using local Ollama.
DeepEval supports custom models via the DeepEvalBaseLLM interface.
We wrap ChatOllama to provide synchronous and async generation.
The judge runs at temperature=0.0 for consistency. The same answer
evaluated twice should produce the same score.
"""
def __init__(self):
self.model_name = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
self.base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
def load_model(self):
return ChatOllama(
model=self.model_name,
base_url=self.base_url,
temperature=0.0, # Deterministic for evaluation
)
def generate(self, prompt: str) -> str:
return self.load_model().invoke(prompt).content
async def a_generate(self, prompt: str) -> str:
return self.generate(prompt)
def get_model_name(self) -> str:
return f"ollama/{self.model_name}"
def get_judge_model():
"""Return an OllamaJudge, or None if deepeval is not installed."""
try:
return OllamaJudge()
except ImportError:
return None
temperature=0.0 جج جان بوجھ کر انتخاب ہوتا ہے۔ آپ چاہتے ہیں کہ آپ کی درجہ بندی مستحکم ہو۔ ایک ہی ٹیسٹ کو دو بار چلائیں اور ایک ہی سکور حاصل کریں۔ زیادہ درجہ حرارت اتار چڑھاؤ متعارف کرواتا ہے جس سے یہ بتانا مشکل ہو جاتا ہے کہ آیا اسکور میں تبدیلیاں معیار میں حقیقی تبدیلیوں کی عکاسی کرتی ہیں یا بے ترتیب نمونے لینے سے۔
7.3 دو درجے کی جانچ کی حکمت عملی
ٹیسٹ سویٹ مختلف ایگزیکیوشن پروفائلز کے ساتھ دو درجوں کا استعمال کرتا ہے۔
یونٹ ٹیسٹنگ یہ تیز ہے، اولاما کی ضرورت نہیں ہے، اور جب بھی آپ کا کوڈ تبدیل ہوتا ہے چلتا ہے۔ یہ ساختی معاہدے کی تصدیق کرتا ہے۔ generate_questions کیا آپ صحیح کلیدوں کے ساتھ لغات کی فہرست واپس کرنا چاہتے ہیں؟ کرو grade_answer ہمیشہ ایک ڈکٹ واپس کرتا ہے۔ correct، scoreاور feedback? کرو get_coaching_message ہمیشہ واپس آو summary اور encouragement?
تشخیص ٹیسٹ وہ سست ہیں (ہر ایک 30-120 سیکنڈ)، اولاما کو چلانے کی ضرورت ہوتی ہے، اور بڑی تبدیلیوں یا ریلیز سے پہلے دوڑتے ہیں۔ یہ معیار کی تصدیق کرتے ہیں۔ کیا وضاحت کنندہ کی پیداوار نوٹوں کے ساتھ وفادار ہے؟ کیا ریٹر کے اسکور اصل جواب کے معیار سے مماثل ہیں؟
جدائی دو جگہوں پر ہوتی ہے۔ سب سے پہلے pyproject.toml شامل کریں addopts = "-m 'not eval'" تو pytest tests/ پہلے سے طے شدہ طور پر، تشخیصی ٹیسٹ کو چھوڑ دیا جاتا ہے۔
[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
asyncio_mode = "auto"
addopts = "-m 'not eval'"
markers = [
"unit: fast tests, no external dependencies",
"eval: slow evaluation tests requiring Ollama (LLM-as-judge)",
]
دوسرا، تمام تشخیصی ٹیسٹ کی کلاسیں اور افعال اس کے ساتھ سجے ہوئے ہیں: @pytest.mark.eval:
@pytest.mark.eval
class TestExplainerQuality:
...
واضح طور پر تشخیصی ٹیسٹ چلائیں:
pytest tests/test_eval.py -m eval -v -s
کہ -s جھنڈا آؤٹ پٹ کیپچر کو غیر فعال کرتا ہے، جس سے آپ اپنے ماڈل کے اسکورز اور نتائج کو حقیقی وقت میں دیکھ سکتے ہیں۔
7.4 مشترکہ سہولیات conftest.py
tests/conftest.py تمام ٹیسٹ فائلوں میں مشترکہ فکسچر رکھتا ہے۔
# tests/conftest.py
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
def pytest_configure(config):
"""Register custom markers so pytest doesn't warn about unknown marks."""
config.addinivalue_line(
"markers",
"eval: marks tests requiring Ollama (deselect with -m 'not eval')"
)
config.addinivalue_line(
"markers",
"unit: marks fast tests with no external dependencies"
)
@pytest.fixture
def sample_roadmap():
"""A minimal StudyRoadmap for use in unit tests."""
from graph.state import StudyRoadmap, Topic
return StudyRoadmap(
goal="Learn Python closures",
total_weeks=2,
topics=[
Topic(
title="Closures Explained",
description="Understand how closures capture enclosing scope variables",
estimated_minutes=60,
),
Topic(
title="Practical Closure Patterns",
description="Apply closures to real problems: factories, memoisation",
estimated_minutes=45,
prerequisites=["Closures Explained"],
),
],
)
@pytest.fixture
def sample_state(sample_roadmap):
"""A minimal AgentState dict for use in unit tests."""
from graph.state import initial_state
state = initial_state("Learn Python closures", "test-session-001")
state["roadmap"] = sample_roadmap
state["current_topic_index"] = 0
return state
@pytest.fixture
def closures_note_content():
"""
The content of closures.md, used as retrieval context in faithfulness tests.
Falls back to an inline summary if the file doesn't exist.
"""
notes_path = (
Path(__file__).parent.parent
/ "study_materials/sample_notes/closures.md"
)
if notes_path.exists():
return notes_path.read_text(encoding="utf-8")
return (
"A closure is a nested function that remembers variables from its "
"enclosing scope even after the enclosing function returns."
)
کہ closures_note_content فکسچر مخلصی کی جانچ کے لیے تلاش کا سیاق و سباق ہے۔ ڈیپ ایول کا FaithfulnessMetric جج سے اس بیان میں ہر دعوے کی تصدیق کرنے کو کہیں۔ اگر وضاحت کنندہ ایسے حقائق تخلیق کرتا ہے جو نوٹس میں نہیں ہیں، تو میٹرکس اس کو پکڑ لیتے ہیں۔
7.5 وضاحتی معیار کی جانچ
وضاحت کنندگان کے لیے تشخیصی ٹیسٹ دو سوالات کے جوابات دیتے ہیں۔ کیا آؤٹ پٹ آپ کے نوٹس کے مطابق ہے اور آپ کے پوچھے گئے سوال سے متعلق ہے؟
# tests/test_eval.py
def run_explainer(topic_title: str, topic_description: str, session_id: str) -> str:
"""Run the Explainer agent and return its final explanation text."""
from graph.state import StudyRoadmap, Topic, initial_state
from agents.explainer import explainer_node
from langchain_core.messages import AIMessage
state = initial_state(f"Learn {topic_title}", session_id)
state["roadmap"] = StudyRoadmap(
goal=f"Learn {topic_title}",
total_weeks=1,
topics=[Topic(topic_title, topic_description, 60)],
)
state["current_topic_index"] = 0
result = explainer_node(state)
# Extract the final response: last AIMessage with no tool_calls
for msg in reversed(result.get("messages", [])):
if (isinstance(msg, AIMessage) and msg.content
and not getattr(msg, "tool_calls", None)):
return msg.content
return ""
@pytest.mark.eval
class TestExplainerQuality:
FAITHFULNESS_THRESHOLD = 0.6
RELEVANCY_THRESHOLD = 0.6
@pytest.fixture(autouse=True)
def setup(self, closures_note_content):
"""Run the Explainer once, reuse the output across all tests in this class."""
self.retrieval_context = [closures_note_content]
self.explanation = run_explainer(
topic_title="Closures Explained",
topic_description="Understand how closures capture enclosing scope variables",
session_id="eval-test-001",
)
if not self.explanation:
pytest.skip("Explainer returned empty output. Check Ollama is running.")
def test_explanation_is_faithful_to_notes(self):
"""
The explanation should not hallucinate facts not in the source notes.
FaithfulnessMetric asks the judge: is every claim in the output
supported by the retrieval context (the notes)?
A low score means the agent is making things up.
"""
from deepeval.test_case import LLMTestCase
from deepeval.metrics import FaithfulnessMetric
judge = get_judge_model()
if judge is None:
pytest.skip("Could not initialise judge model")
test_case = LLMTestCase(
input="Explain Python closures",
actual_output=self.explanation,
retrieval_context=self.retrieval_context,
)
metric = FaithfulnessMetric(
model=judge,
threshold=self.FAITHFULNESS_THRESHOLD,
include_reason=True,
)
metric.measure(test_case)
print(f"n[Faithfulness] Score: {metric.score:.3f}")
if hasattr(metric, "reason"):
print(f"[Faithfulness] Reason: {metric.reason}")
assert metric.score >= self.FAITHFULNESS_THRESHOLD, (
f"Faithfulness {metric.score:.3f} below {self.FAITHFULNESS_THRESHOLD}.n"
f"The explanation may contain hallucinated facts.n"
f"Reason: {getattr(metric, 'reason', 'not available')}"
)
def test_explanation_is_relevant_to_topic(self):
"""The explanation should address what was actually asked."""
from deepeval.test_case import LLMTestCase
from deepeval.metrics import AnswerRelevancyMetric
judge = get_judge_model()
if judge is None:
pytest.skip("Could not initialise judge model")
test_case = LLMTestCase(
input="Explain Python closures",
actual_output=self.explanation,
)
metric = AnswerRelevancyMetric(
model=judge,
threshold=self.RELEVANCY_THRESHOLD,
)
metric.measure(test_case)
print(f"n[Relevancy] Score: {metric.score:.3f}")
assert metric.score >= self.RELEVANCY_THRESHOLD, (
f"Relevancy {metric.score:.3f} below {self.RELEVANCY_THRESHOLD}.n"
f"The explanation may have wandered off-topic."
)
کہ autouse=True فکسچر TestExplainerQuality ڈسکرپٹر کو ایک بار چلائیں اور دونوں ٹیسٹوں میں آؤٹ پٹ کو دوبارہ استعمال کریں۔ یہ دو الگ الگ ایل ایل ایم کالز کرنے سے گریز کرتا ہے (ایک فی ٹیسٹ) جب ایک ہی تفصیل دونوں میٹرکس فراہم کر سکتی ہے۔
7.6 گریڈ کوالٹی ٹیسٹ
یہ ٹیسٹ یقینی بناتا ہے کہ ریٹر کا اسکور اصل جوابات کے معیار سے میل کھاتا ہے۔ ڈیپ ایول میٹرکس کی ضرورت نہیں ہے۔ وہ کہتے ہیں grade_answer براہ راست اسکور کی حدود پر اصرار کریں۔
@pytest.mark.eval
class TestGradingQuality:
def test_correct_answer_scores_high(self):
"""A clearly correct answer should score >= 0.65."""
from agents.quiz_generator import grade_answer
result = grade_answer(
question="What are the three requirements for a Python closure?",
expected=(
"A closure requires: 1) a nested inner function, "
"2) the inner function references a variable from the enclosing scope, "
"3) the enclosing function returns the inner function."
),
student_answer=(
"You need a nested function that uses variables from the outer "
"function's scope, and the outer function has to return the inner function."
),
)
print(f"n[GradeQuality] Correct answer: {result.get('score', 0):.2f}")
assert result.get("score", 0) >= 0.65, (
f"Correct answer scored too low: {result['score']:.2f}n"
f"Feedback: {result.get('feedback', '')}"
)
def test_wrong_answer_scores_low(self):
"""A clearly wrong answer should score <= 0.35."""
from agents.quiz_generator import grade_answer
result = grade_answer(
question="What is a Python closure?",
expected=(
"A closure is a nested function that captures and remembers "
"variables from its enclosing scope after the enclosing function returns."
),
student_answer=(
"A closure is a class that closes over its attributes "
"and prevents external access to them."
),
)
print(f"n[GradeQuality] Wrong answer: {result.get('score', 0):.2f}")
assert result.get("score", 0) <= 0.35, (
f"Wrong answer scored too high: {result['score']:.2f}n"
f"The grader may be too lenient."
)
def test_partial_answer_scores_middle(self):
"""A partially correct answer should score between 0.3 and 0.75."""
from agents.quiz_generator import grade_answer
result = grade_answer(
question="What is late binding in closures and how do you fix it?",
expected=(
"Late binding means closures look up variable values at call time, "
"not at definition time. Fix: use default argument values "
"(lambda i=i: i instead of lambda: i)."
),
student_answer=(
"Late binding means the closure uses the variable's current value "
"when called, not when defined." # Knows what, not how to fix
),
)
score = result.get("score", 0)
print(f"n[GradeQuality] Partial answer: {score:.2f}")
assert 0.3 <= score <= 0.75, (
f"Partial answer should score 0.3 to 0.75, got {score:.2f}"
)
یہ تینوں ٹیسٹ مل کر انشانکن کی وشوسنییتا فراہم کرتے ہیں۔ گریڈر درست جوابات کو انعام دیتا ہے، غلط جوابات کو سزا دیتا ہے، اور مناسب ہونے پر جزوی کریڈٹ دیتا ہے۔ اگر ماڈل کی تبدیلی یا تیز رفتار اپ ڈیٹ کے بعد تینوں میں سے کوئی بھی ناکام ہو جاتا ہے، تو آپ کو فوری طور پر معلوم ہو جائے گا کہ گریڈر کس سمت چلا گیا ہے۔
7.7 کوچنگ کوالٹی ٹیسٹ
کوچنگ ٹیسٹنگ کے لیے، ہم DeepEval استعمال کرتے ہیں۔ GEval میٹرک، آپ کے اپنے تشخیصی معیار کو سادہ انگریزی میں لکھنے کے لیے ایک عالمگیر تخمینہ لگانے والا:
@pytest.mark.eval
class TestProgressCoachQuality:
COACHING_QUALITY_THRESHOLD = 0.6
def test_coaching_message_is_encouraging_and_specific(self):
"""
Coaching messages should be warm, specific, and actionable.
GEval lets you write evaluation criteria in plain English.
The judge scores the output 0.0 to 1.0 against those criteria.
"""
from deepeval.test_case import LLMTestCase, LLMTestCaseParams
from deepeval.metrics import GEval
from agents.progress_coach import get_coaching_message
judge = get_judge_model()
if judge is None:
pytest.skip("Could not initialise judge model")
coaching = get_coaching_message(
topic="Python Closures",
score=0.67,
weak_areas=["late binding", "nonlocal keyword"],
)
coaching_text = (
f"Summary: {coaching.get('summary', '')}n"
f"Encouragement: {coaching.get('encouragement', '')}"
)
test_case = LLMTestCase(
input=(
"Generate coaching feedback for a student who scored 67% on "
"Python Closures and struggled with late binding and nonlocal"
),
actual_output=coaching_text,
)
metric = GEval(
name="CoachingQuality",
criteria=(
"Evaluate whether this coaching message is: "
"1) Encouraging without being dishonest about the score, "
"2) Specific to the topic and weak areas mentioned, "
"3) Actionable. Gives the student a clear next step. "
"4) Concise. 2 to 4 sentences total. "
"A poor message is generic, vague, or condescending."
),
evaluation_params=[LLMTestCaseParams.ACTUAL_OUTPUT],
model=judge,
threshold=self.COACHING_QUALITY_THRESHOLD,
)
metric.measure(test_case)
print(f"n[CoachingQuality] Score: {metric.score:.3f}")
assert metric.score >= self.COACHING_QUALITY_THRESHOLD, (
f"Coaching quality {metric.score:.3f} below threshold.n"
f"Message:n{coaching_text}"
)
GEval یہ سب سے زیادہ لچکدار میٹرک ہے جو DeepEval پیش کرتا ہے۔ آپ سادہ زبان میں وضاحت کرتے ہیں کہ "اچھا" کیا ہے، اور جج ان معیارات کی بنیاد پر آپ کو اسکور دیتے ہیں۔ اس کا استعمال اس وقت کریں جب آپ کے پاس معیاری تقاضے ہوں جن کا فارمولوں میں اظہار کرنا مشکل ہو لیکن الفاظ میں بیان کرنا آسان ہو۔
7.8 تشخیصی سیٹ چلانا
یونٹ ٹیسٹ (تیز، کوئی اولامہ نہیں):
pytest tests/ -v
# 184 tests, eval tests automatically excluded
تشخیصی ٹیسٹ (سست، اولامہ کی ضرورت ہے):
pytest tests/test_eval.py -m eval -v -s
آپ اس طرح آؤٹ پٹ دیکھیں گے:
[TestExplainerQuality] Running Explainer for closures topic...
[TestExplainerQuality] Explanation length: 1,847 chars
[Faithfulness] Score: 0.782 (threshold: 0.600)
[Faithfulness] Reason: All major claims trace back to the closures.md source material.
PASSED
[Relevancy] Score: 0.841
PASSED
[GradeQuality] Correct answer: 0.82
PASSED
[GradeQuality] Wrong answer: 0.15
PASSED
[GradeQuality] Partial answer: 0.55
PASSED
[CoachingQuality] Score: 0.731
PASSED
قدامت پسندی سے حد مقرر کریں۔
مقامی 7B ماڈل نے مخلصی اور مطابقت کے اشاریوں پر 0.6 اور 0.8 کے درمیان سکور کیا۔ کلاؤڈ ماڈلز میں عام طور پر 0.8 اور 0.95 کے درمیان سکور ہوتے ہیں۔ اس ٹیسٹ کی حد 0.6 پر سیٹ کی گئی ہے۔ یعنی مقامی ماڈل کو قابل اعتماد طریقے سے پاس کرنے کے لیے یہ کافی کم ہے اور کارکردگی میں شدید گراوٹ کو پکڑنے کے لیے کافی زیادہ ہے۔
اگر آپ سخت کوالٹی گیٹ چاہتے ہیں تو بڑے ماڈل میں اپ گریڈ کریں اور حد بڑھائیں۔ اگر کسی ایسے ماڈل پر ٹیسٹ مسلسل ناکام ہو جاتے ہیں جو موضوعی طور پر اچھے نتائج پیدا کرتا ہے، تو حد کو کم کریں اور اس کی وجہ دستاویز کریں۔
انٹرپرائز کنیکٹیویٹی: اس طرح کے ایویلیوایشن سویٹس پروڈکشن میں ماڈل اپ ڈیٹ کے مسائل کو منظم کرنے کا ایک طریقہ ہیں۔ اگر آپ ایک ماڈل ورژن سے دوسرے میں تبدیل کر رہے ہیں، تو تعینات کرنے سے پہلے تشخیصی ٹیسٹ چلائیں۔
اگر وفاداری ایک حد سے نیچے آتی ہے تو، ماڈل کی تبدیلیاں فریب کے خطرے کو متعارف کراتی ہیں۔ دوبارہ رول کریں۔ اگر گریڈر درست جوابات بہت کم اسکور کرنا شروع کر دیتے ہیں، تو حد بڑھنے سے طالب علم کے تجربے پر اثر پڑتا ہے۔ تشخیصی ٹیسٹ LLM رویے کے لیے ایک ریگریشن سوٹ ہیں، اسی طرح جیسے یونٹ ٹیسٹ کوڈ لاجک کے لیے ایک ریگریشن سوٹ ہیں۔
اگلا باب A2A پروٹوکول پرت کا اضافہ کرتا ہے۔ کوئز جنریٹر ایک اسٹینڈ لون سروس بن جاتا ہے جسے کوئی بھی ایجنٹ یا فریم ورک کال کرسکتا ہے، اور CrewAI ایجنٹ اس سسٹم میں حصہ لیتا ہے، جب طلباء کو اضافی مدد کی ضرورت ہوتی ہے تو ترقی کے کوچز کے ذریعہ تفویض کیا جاتا ہے۔
باب 8: A2A کے ساتھ پورے فریم ورک میں ہم آہنگی۔
اب تک، سسٹم میں تمام ایجنٹ Python فنکشنز ہیں جنہیں LangGraph کہتے ہیں۔ یہ ٹھیک ہے زیادہ تر پیداواری نظاموں کے لیے، ہر چیز کو ایک فریم ورک میں رکھنا صحیح انتخاب ہے۔
لیکن حقیقی بنیادی ڈھانچے کو بعض اوقات کچھ مختلف کی ضرورت ہوتی ہے۔ یعنی، ایجنٹ جو مختلف فریم ورک پر بنائے گئے ہیں، مختلف ٹیموں کے ذریعہ دیکھ بھال کی جاتی ہے، آزادانہ طور پر تعینات کیا جاتا ہے، اور HTTP استعمال کرنے والی کسی بھی چیز سے بلایا جا سکتا ہے۔
ایجنٹ سے ایجنٹ (A2A) پروٹوکول یہ ممکن بناتا ہے۔ A2A ایک کھلا معیار ہے (JSON-RPC 2.0 اور HTTP پر مبنی) جو ایجنٹوں کو اس بات کی تشہیر کرنے کا ایک معیاری طریقہ فراہم کرتا ہے کہ وہ کیا کر سکتے ہیں اور کال کرنے والوں کی کارروائیوں کو قبول کرتے ہیں، اس سے قطع نظر کہ کالر کس فریم ورک کا استعمال کرتا ہے۔
ایک LangGraph ایجنٹ اور ایک CrewAI ایجنٹ جس نے کبھی ایک دوسرے کے بارے میں نہیں سنا ہے، A2A پر اسی طرح ہم آہنگی پیدا کر سکتے ہیں جس طرح دو REST سروسز HTTP پر ہم آہنگ ہوتی ہیں۔
اس باب میں، آپ اپنے سسٹم میں دو A2A خدمات شامل کریں گے۔ کوئز جنریٹر، جو ایک اسٹینڈ لون سروس کے طور پر سامنے آیا ہے، اور CrewAI Study Buddy، جس کو سہولت فراہم کرنے والے کوچ اس وقت طلب کرتے ہیں جب طلباء کو وضاحت کے مختلف زاویے کی ضرورت ہوتی ہے۔
8.1 A2A کیسے کام کرتا ہے۔
A2A میں تین تصورات ہیں جو آپ کو کوڈ لکھنے سے پہلے سمجھنے کی ضرورت ہے:
ایجنٹ کارڈ یہ ایک JSON دستاویز ہے جو . /.well-known/agent-card.json. بیان کرتا ہے کہ ایجنٹ کیا کر سکتا ہے: اس کا نام، صلاحیتیں، مہارتیں، اور کام کو کیسے منتقل کیا جاتا ہے۔
تمام A2A کلائنٹس پہلے یہ دیکھنے کے لیے حاصل کرتے ہیں کہ آیا ایجنٹ درخواست کو سنبھال سکتا ہے۔ ایجنٹ کارڈ ایک ایجنٹ کا عوامی API معاہدہ ہوتا ہے، جیسا کہ REST خدمات کے لیے OpenAPI تفصیلات کی طرح ہے۔
اپنی اسائنمنٹ جمع کروائیں۔ ایک واحد اختتامی نقطہ استعمال کرتا ہے۔ POST /tasks/send. درخواست ایک کردار ہے ("user") اور حصوں کی فہرست (عام طور پر TextPart بشمول JSON مواد)۔ ایجنٹ کام پر کارروائی کرتا ہے اور اسی فارمیٹ کے پیغام کے ساتھ جواب دیتا ہے۔
فریم ورک کی آزادی یہی بات ہے۔ A2A سرور تمام HTTP اور پروٹوکول میکانزم کو ہینڈل کرتے ہیں۔ آپ کا ایجنٹ کوڈ ہے۔ AgentExecutor ذیلی طبقہ: execute() وہ طریقہ جو تجزیہ شدہ درخواست وصول کرتا ہے اور جواب خارج کرتا ہے۔ وہ فریم ورک جو ایگزیکیوٹرز (LangGraph، CrewAI، وغیرہ) بناتے ہیں پروٹوکول پرت میں ظاہر نہیں ہوتے ہیں۔ کال کرنے والے صرف HTTP دیکھ سکتے ہیں۔
Caller (any framework)
↓ GET /.well-known/agent-card.json ← discover capabilities
↓ POST /tasks/send ← submit task (JSON-RPC 2.0)
↑ response with result artifacts
A2A Server (Starlette + uvicorn)
↓ calls AgentExecutor.execute()
Your agent logic (LangGraph / CrewAI / anything)
8.2 کوئز جنریٹر بطور A2A سروس
src/a2a_services/quiz_service.py لیبارٹری generate_questions اور grade_answer (وہی فعالیت جو باب 4 میں استعمال کی گئی ہے) بطور A2A سروس۔ اس فعالیت میں کچھ بھی نہیں بدلتا۔
ایجنٹ کارڈ پہلا:
# src/a2a_services/quiz_service.py
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
QUIZ_SKILL = AgentSkill(
id="generate_and_grade_quiz",
name="Generate and Grade Quiz",
description=(
"Given a topic and optional explanation text, generates quiz questions "
"that test conceptual understanding. If answers are provided, grades "
"each answer and returns scores with identified weak areas."
),
tags=["quiz", "assessment", "education", "grading"],
examples=[
"Generate a quiz on Python closures",
"Grade these answers for a decorators quiz",
],
)
QUIZ_AGENT_CARD = AgentCard(
name="Quiz Generator Service",
description=(
"Generates and grades quizzes using LLM-as-judge. "
"Framework-agnostic: works with any A2A-compatible agent."
),
url="http://localhost:9001/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=False),
skills=[QUIZ_SKILL],
)
ایک ایجنٹ کارڈ خود بخود فراہم کیا جاتا ہے۔ GET /.well-known/agent-card.json آپ A2A فریم ورک کے ذریعے اس کے لیے ہینڈلر نہیں لکھتے ہیں۔
AgentExecutor اصل کوئز منطق پر مشتمل ہے۔ تجزیہ شدہ A2A درخواست وصول کرتا ہے اور کال کرتا ہے۔ generate_questions اور اختیاری طور پر grade_answerنتائج برآمد کریں۔
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.types import Message, TextPart
from agents.quiz_generator import generate_questions, grade_answer
class QuizAgentExecutor(AgentExecutor):
"""
Handles incoming A2A quiz tasks.
Request format (JSON in the TextPart):
{
"topic": "Python Closures",
"explanation": "A closure is...", (optional)
"answers": ["answer 1", ...] (optional. omit for questions only)
}
"""
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
# Parse request
request_text = ""
for part in context.current_request.params.message.parts:
if isinstance(part, TextPart):
request_text += part.text
try:
request_data = json.loads(request_text)
except json.JSONDecodeError:
request_data = {"topic": request_text}
topic = request_data.get("topic", "General Knowledge")
explanation = request_data.get("explanation", "")
provided_answers = request_data.get("answers", [])
# Generate questions (synchronous blocking call in thread pool)
questions_data = await asyncio.to_thread(
generate_questions, topic, explanation, 3
)
if not provided_answers:
# No answers. Return questions only.
result = {
"status": "questions_ready",
"topic": topic,
"questions": questions_data,
}
else:
# Grade provided answers
graded = []
total = 0.0
weak_areas = []
for q_data, answer in zip(questions_data, provided_answers):
grade = await asyncio.to_thread(
grade_answer,
q_data["question"],
q_data["expected_answer"],
answer,
)
score = float(grade.get("score", 0.0))
total += score
if grade.get("missing_concept"):
weak_areas.append(grade["missing_concept"])
graded.append({
"question": q_data["question"],
"answer": answer,
"score": score,
"correct": bool(grade.get("correct", False)),
"feedback": grade.get("feedback", ""),
})
result = {
"status": "graded",
"topic": topic,
"score": total / len(questions_data) if questions_data else 0.0,
"questions": questions_data,
"graded_questions": graded,
"weak_areas": list(set(weak_areas)),
}
# Emit result. A2A sends this back to the caller.
await event_queue.enqueue_event(
Message(
role="agent",
parts=[TextPart(text=json.dumps(result, indent=2))],
)
)
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
pass
asyncio.to_thread ہم آہنگ لپیٹتا ہے۔ generate_questions اور grade_answer فون کال A2A ایگزیکیوٹر غیر مطابقت پذیر ہے۔ یہ ایک ایونٹ لوپ میں چلتا ہے۔ بلاکنگ فنکشن کو کال کرنا براہ راست لوپ کو روکتا ہے اور دیگر تمام کارروائیوں کو روکتا ہے۔ to_thread تھریڈ پول پر بلاکنگ فنکشن کو انجام دیتا ہے اور ایونٹ لوپ کو بلاک کیے بغیر نتیجہ کا انتظار کرتا ہے۔
سرور شروع کریں:
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
def create_quiz_server():
handler = DefaultRequestHandler(
agent_executor=QuizAgentExecutor(),
task_store=InMemoryTaskStore(),
)
app = A2AStarletteApplication(
agent_card=QUIZ_AGENT_CARD,
http_handler=handler,
)
return app.build()
if __name__ == "__main__":
uvicorn.run(create_quiz_server(), host="0.0.0.0", port=9001, log_level="warning")
python src/a2a_services/quiz_service.py
# [Quiz A2A Service] Starting on http://localhost:9001
# [Quiz A2A Service] Agent Card: http://localhost:9001/.well-known/agent-card.json
یقینی بنائیں کہ یہ چل رہا ہے۔
curl http://localhost:9001/.well-known/agent-card.json
{
"name": "Quiz Generator Service",
"description": "Generates and grades quizzes...",
"url": "http://localhost:9001/",
"skills": [
{
"id": "generate_and_grade_quiz",
"name": "Generate and Grade Quiz"
}
]
}
8.3 A2A کلائنٹ
src/a2a_services/a2a_client.py اپنے ایجنٹ کوڈ میں HTTP اور پروٹوکول کی تفصیلات محفوظ کریں۔ پروگریس کوچ JSON-RPC لفافے نہیں بناتا ہے۔ یہ کال کرتا ہے delegate_quiz_task ہم نتیجہ خیز ڈکٹ واپس لیتے ہیں۔
# src/a2a_services/a2a_client.py
import httpx
import json
import uuid
QUIZ_SERVICE_URL = os.getenv("QUIZ_SERVICE_URL", "http://localhost:9001")
STUDY_BUDDY_URL = os.getenv("STUDY_BUDDY_URL", "http://localhost:9002")
DEFAULT_TIMEOUT = 120.0
def discover_agent(base_url: str) -> dict:
"""Fetch an Agent Card to discover capabilities. Returns {} if unreachable."""
card_url = f"{base_url.rstrip('/')}/.well-known/agent-card.json"
try:
response = httpx.get(card_url, timeout=5.0)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"[A2A Client] Cannot reach {card_url}: {e}")
return {}
def send_task(
base_url: str,
message_text: str,
task_id: str | None = None,
timeout: float = DEFAULT_TIMEOUT,
) -> dict:
"""
Submit a task to an A2A agent via JSON-RPC 2.0.
The JSON-RPC envelope is what A2A requires. Your caller doesn't
need to know about the envelope. It just passes a text payload.
Pass an explicit task_id when you need an idempotency key; otherwise
a UUID is generated for you.
"""
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"id": task_id or str(uuid.uuid4()),
"message": {
"role": "user",
"parts": [{"type": "text", "text": message_text}],
},
},
}
url = f"{base_url.rstrip('/')}/tasks/send"
try:
response = httpx.post(url, json=payload, timeout=timeout)
response.raise_for_status()
data = response.json()
# Extract text from the A2A response envelope:
# result.artifacts[0].parts[0].text
result = data.get("result", {})
artifacts = result.get("artifacts", [])
if artifacts:
for part in artifacts[0].get("parts", []):
if part.get("type") == "text":
try:
return json.loads(part["text"])
except json.JSONDecodeError:
return {"text": part["text"]}
# Fallback: check status message
status = result.get("status", {})
for part in status.get("message", {}).get("parts", []):
if part.get("type") == "text":
try:
return json.loads(part["text"])
except json.JSONDecodeError:
return {"text": part["text"]}
return result
except httpx.TimeoutException:
return {"error": f"Service timed out after {timeout}s"}
except httpx.ConnectError:
return {"error": f"Cannot connect to {url}"}
except Exception as e:
return {"error": f"A2A task failed: {e}"}
def delegate_quiz_task(
topic: str,
explanation: str,
answers: list[str] | None = None,
quiz_service_url: str = QUIZ_SERVICE_URL,
) -> dict:
"""High-level helper: delegate a quiz task to the Quiz A2A service."""
payload = json.dumps({
"topic": topic,
"explanation": explanation,
"answers": answers or [],
})
return send_task(quiz_service_url, payload)
def is_quiz_service_available(quiz_service_url: str = QUIZ_SERVICE_URL) -> bool:
"""Quick health check: is the quiz service reachable?"""
return bool(discover_agent(quiz_service_url))
discover_agent یہ ہیلتھ چیک اپ ہے۔ اپنا ایجنٹ کارڈ یہاں سے حاصل کریں: /.well-known/agent-card.json 5 سیکنڈ کی وقت کی حد ہے۔ اگر کامیاب ہو جاتا ہے، تو آپ سروس سے جڑ سکتے ہیں اور کام کو قبول کر سکتے ہیں۔ سہولت فراہم کرنے والے کوچز ڈیلیگیٹ کرنے سے پہلے اسے کال کرتے ہیں۔ جب واپس آیا {}کوچ پورا کام جمع کرانے کی کوشش نہیں کرے گا اور ایک مقامی کوئز بنانے کے لیے واپس آ جائے گا۔
8.4 CrewAI ریسرچ بڈی
Study Buddy بنیادی A2A قدر کی تجویز کو ظاہر کرتا ہے: لینگ گراف ایجنٹس ایک پروٹوکول پر CrewAI ایجنٹوں کو بلا رہے ہیں جس کے بارے میں کوئی نہیں جانتا ہے۔
src/crewai_agent/study_buddy.py ایک CrewAI ایجنٹ بنائیں اور اسے A2A سے لپیٹیں۔ AgentExecutorپورٹ 9002 پر پیش کیا گیا۔ لینگ گراف پروگریس کوچ CrewAI کو درآمد نہیں کرتا ہے۔ CrewAI ایجنٹ LangGraph درآمد نہیں کرتا ہے۔ صرف HTTP پر بات چیت کرتا ہے۔
عملہ کی طرف:
# src/crewai_agent/study_buddy.py
from crewai import Agent, Crew, LLM, Process, Task
from crewai.tools import BaseTool
MODEL_NAME = os.getenv("OLLAMA_MODEL", "qwen2.5:7b")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
class TopicAnalyserTool(BaseTool):
"""
Structures the Study Buddy's approach before generating its response.
In production this might query a knowledge graph or curriculum database.
For the tutorial, it produces structured guidance from the inputs.
"""
name: str = "topic_analyser"
description: str = (
"Analyse a study topic and weak areas to produce a structured "
"list of key concepts to focus on."
)
args_schema: type = TopicAnalyserInput
def _run(self, topic: str, weak_areas: list[str] | None = None) -> str:
areas = weak_areas or []
return json.dumps({
"topic": topic,
"focus_areas": areas or [f"Core concepts of {topic}"],
"suggested_approach": f"Start with fundamentals, then address: {', '.join(areas)}.",
"study_tip": (
"Try explaining the concept out loud in your own words. "
"If you can teach it simply, you understand it."
),
})
def build_study_buddy_crew(topic: str, explanation: str, weak_areas: list[str]) -> Crew:
"""Build a CrewAI crew for a specific study assistance request."""
llm = LLM(model=f"ollama/{MODEL_NAME}", base_url=OLLAMA_BASE_URL)
agent = Agent(
role="Study Buddy",
goal=(
"Provide clear, encouraging supplementary explanations that help "
"students understand difficult concepts from a fresh angle."
),
backstory=(
"You are an experienced tutor who specialises in finding alternative "
"explanations and analogies that make difficult ideas click."
),
llm=llm,
tools=[TopicAnalyserTool()],
verbose=False,
allow_delegation=False,
)
weak_text = (
f"The student struggled with: {', '.join(weak_areas)}"
if weak_areas else "No specific weak areas identified."
)
task = Task(
description=(
f"A student is studying '{topic}'. They received this explanation:nn"
f"{explanation[:1000]}nn"
f"{weak_text}nn"
f"Use the topic_analyser tool to structure your approach. Then provide:n"
f"1) A fresh analogy that explains the core concept differentlyn"
f"2) One concrete example targeting the weak area(s)n"
f"3) One practical tip for remembering this conceptn"
f"Keep your response concise and encouraging (150-250 words)."
),
agent=agent,
expected_output=(
"A study assistance response with a fresh analogy, "
"a targeted example, and a memory tip."
),
)
return Crew(
agents=[agent],
tasks=[task],
process=Process.sequential,
verbose=False,
)
A2A ریپر CrewAI ٹیم کو A2A پروٹوکول سے جوڑتا ہے۔ یہ ہے StudyBuddyExecutorجیسا کہ ڈھانچہ QuizAgentExecutorلیکن پھر مجھے ایک فون آیا۔ crew.kickoff() کوئز فنکشن کے بجائے:
class StudyBuddyExecutor(AgentExecutor):
"""
Bridges the A2A protocol to CrewAI execution.
The LangGraph system has no idea this is CrewAI.
The CrewAI crew has no idea it's serving an A2A request.
"""
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
# Parse request
request_text = ""
for part in context.current_request.params.message.parts:
if isinstance(part, TextPart):
request_text += part.text
try:
request_data = json.loads(request_text)
except json.JSONDecodeError:
request_data = {"topic": request_text}
topic = request_data.get("topic", "General Topic")
explanation = request_data.get("explanation", "")
weak_areas = request_data.get("weak_areas", [])
# CrewAI's kickoff() is synchronous. Run in thread pool
# to avoid blocking the async event loop.
try:
crew = build_study_buddy_crew(topic, explanation, weak_areas)
crew_result = await asyncio.to_thread(crew.kickoff)
result_text = crew_result.raw if hasattr(crew_result, "raw") else str(crew_result)
result = {
"source": "crewai_study_buddy",
"topic": topic,
"weak_areas": weak_areas,
"assistance": result_text,
"status": "complete",
}
except Exception as e:
result = {
"source": "crewai_study_buddy",
"topic": topic,
"assistance": f"Could not generate supplementary help for '{topic}'.",
"status": "error",
"error": str(e),
}
await event_queue.enqueue_event(
Message(
role="agent",
parts=[TextPart(text=json.dumps(result, indent=2))],
)
)
asyncio.to_thread(crew.kickoff) یہ ایک تنقیدی لکیر ہے۔ عملہ کا kickoff() یہ مطابقت پذیر اور مسدود ہے۔ ماڈل اور کام کی پیچیدگی پر منحصر ہے، یہ 30 سے 60 سیکنڈ تک چل سکتا ہے۔
سے براہ راست کال کریں۔ async یہ فیچر پورے A2A سرور کو اس وقت کے لیے منجمد کر دیتا ہے، اسے کسی بھی دوسری درخواست کو قبول کرنے سے روکتا ہے۔ asyncio.to_thread Python کے ڈیفالٹ تھریڈ پول پر چلتا ہے، ٹیم کے چلنے کے دوران دیگر درخواستوں کو ہینڈل کرنے کے لیے ایونٹ کے لوپ کو آزاد کرتا ہے۔
8.5 پیش رفت کوچ فال بیک پیٹرن
پروگریس کوچ ماڈیول A2A خدمات کے ساتھ بات چیت کے لیے دو مددگار فراہم کرتا ہے۔ ہر ایک پہلے بیرونی سروس کو آزمائے گا اور اگر کوئی خرابی پیش آتی ہے تو مقامی ڈیفالٹ پر واپس آجائے گی۔
اسٹڈی بڈی اسسٹنٹ اس سے منسلک ہیں: progress_coach_node جب بھی موضوع کا سکور گزرنے کی حد سے نیچے ہو تو چلتا ہے۔
کوئز ڈیلیگیشن اسسٹنٹ ان قارئین کے لیے استعمال کے لیے تیار عمارت کے بلاک کے طور پر فراہم کیا جاتا ہے جو A2A سروس کے ذریعے گریڈنگ کو ان لائن چلانے کے بجائے روٹ کرنا چاہتے ہیں۔ پہلے سے طے شدہ بہاؤ کوئز تخلیق کو سادگی کے لیے مقامی رکھتا ہے۔
دونوں مددگار ایک ہی سرکٹ بریکر پیٹرن استعمال کرتے ہیں۔ اس کا مطلب یہ ہے کہ ایجنٹ کارڈ کی پہلے جانچ پڑتال کی جاتی ہے، اصل آپریشن کال محدود وقت پر ہوتی ہے، اور صارف کو بیرونی غلطیاں ظاہر نہیں کی جاتی ہیں۔
# src/agents/progress_coach.py
QUIZ_SERVICE_URL = "http://localhost:9001"
def try_a2a_quiz_delegation(topic, explanation, answers) -> dict | None:
"""
Attempt to delegate quiz grading to the A2A Quiz Service.
Returns the grading result, or None on any failure.
Note: USE_A2A_QUIZ is read at call time, not at module load time.
Reading env vars at import time causes test isolation failures.
The env var state at import time gets baked in for the process lifetime.
"""
use_a2a = os.getenv("USE_A2A_QUIZ", "true").lower() == "true"
if not use_a2a:
return None
try:
from a2a_services.a2a_client import delegate_quiz_task, is_quiz_service_available
if not is_quiz_service_available(QUIZ_SERVICE_URL):
print(f"[Progress Coach] Quiz A2A service unavailable. Using local.")
return None
print(f"[Progress Coach] Delegating quiz to A2A: {QUIZ_SERVICE_URL}")
result = delegate_quiz_task(topic=topic, explanation=explanation, answers=answers)
if "error" in result:
print(f"[Progress Coach] A2A failed: {result['error']}")
return None
return result
except Exception as e:
print(f"[Progress Coach] A2A error: {e}")
return None
def try_study_buddy_assistance(topic, explanation, weak_areas) -> str | None:
"""
Request supplementary help from the CrewAI Study Buddy.
Returns assistance text, or None if the service is unavailable.
"""
study_buddy_url = os.getenv("STUDY_BUDDY_URL", "http://localhost:9002")
use_study_buddy = os.getenv("USE_STUDY_BUDDY", "true").lower() == "true"
if not use_study_buddy:
return None
try:
from a2a_services.a2a_client import request_study_assistance, is_study_buddy_available
if not is_study_buddy_available(study_buddy_url):
return None
result = request_study_assistance(
topic=topic,
explanation=explanation,
weak_areas=weak_areas,
study_buddy_url=study_buddy_url,
)
if result.get("status") == "error" or "error" in result:
return None
return result.get("assistance", "")
except Exception as e:
return None
پر تبصرے os.getenv کال کا وقت اندرونی بنانے کے قابل ہے۔ ماڈیولز درآمد کرتے وقت ماحولیاتی متغیرات کو پڑھنا (USE_A2A = os.getenv("USE_A2A_QUIZ", "true") == "true" فائل کے اوپری حصے میں) ان اقدار کو بیک کرتا ہے جو اس وقت موجود تھیں جب ماڈیول پہلی بار درآمد کیا گیا تھا۔ فنکشن کو کال کرنے سے پہلے env var سیٹ کرنے والے ٹیسٹ میں کوئی تبدیلی نظر نہیں آئے گی کیونکہ ماڈیول پہلے ہی چلایا جا چکا ہے۔ فنکشن کے اندر پڑھنا ہر کال پر موجودہ قدر کی ضمانت دیتا ہے۔
8.6 تمام 3 ٹرمینل سیٹنگز پر عمل کریں۔
ایک بار جب تمام خدمات اپنی جگہ پر آجائیں تو پورا نظام تین ٹرمینلز استعمال کرتا ہے۔
ٹرمینل 1: کلیدی سیکھنے کے سرعت کار:
source .venv/bin/activate
python main.py "Learn Python closures"
ٹرمینل 2: کوئز جنریٹر A2A خدمات:
source .venv/bin/activate
python src/a2a_services/quiz_service.py
ٹرمینل 3: CrewAI ریسرچ فرینڈز:
source .venv/bin/activate
python src/crewai_agent/study_buddy.py
یا میک کا استعمال کرتے ہوئے:
make services # Terminals 2 and 3 in background
make run # Terminal 1
جب پروگریس کوچ دونوں خدمات کے ساتھ چلتا ہے، تو آپ کو درج ذیل نظر آتا ہے:
[Progress Coach] Score: 35%
[Progress Coach] Delegating quiz to A2A: http://localhost:9001
[Quiz A2A] Task received: topic="Python Functions", answers_provided=3
[Quiz A2A] Task complete: status=graded
[Progress Coach] A2A quiz complete: score=35%
[Progress Coach] Requesting study assistance from CrewAI Study Buddy...
[Study Buddy A2A] Request: topic="Python Functions", weak_areas=['first-class functions']
[Study Buddy A2A] Task complete (287 chars)
────────────────────────────────────────────────────────────
Coach: You scored 35% on Python Functions. That's a solid foundation to build on...
📚 Study Buddy says:
Think of functions like variables with superpowers. Just as you can pass a number
to another function, you can pass a function too...
────────────────────────────────────────────────────────────
اگر دونوں میں سے کوئی بھی سروس نہیں چل رہی ہے تو پروگریس کوچ خوبصورتی سے واپس آجاتا ہے۔
[A2A Client] Cannot reach http://localhost:9001/.well-known/agent-card.json: Connection refused
[Progress Coach] Quiz A2A service unavailable. Using local.
سیشن جاری ہے۔ طالب علم کبھی غلطی نہیں دیکھتا۔
چوکی: A2A ٹیسٹ چلائیں۔
pytest tests/test_a2a.py tests/test_crewai_interop.py -v
متوقع: 44 ٹیسٹ، سبھی پاس ہو گئے۔ یہ ٹیسٹ HTTP کال کا مذاق اڑاتا ہے اور درج ذیل کی تصدیق کرتا ہے: delegate_quiz_task درست JSON-RPC پے لوڈ بنائیں۔ discover_agent کنکشن کی غلطیوں کو احسن طریقے سے ہینڈل کریں۔ build_study_buddy_crew مناسب طریقے سے تشکیل شدہ عملہ بنائیں۔ چلانے کی خدمات کی ضرورت نہیں ہے۔
انٹرپرائز کنیکٹیویٹی: A2A ایجنٹ کے نظام کو تنظیمی سطح پر قابل ترتیب بناتا ہے۔ ایک ٹیم (LangGraph) کی طرف سے بنایا گیا کمپلائنس ٹریننگ پلیٹ فارم دوسری ٹیم (CrewAI یا کوئی HTTP سروس) کی طرف سے بنائی گئی سرٹیفیکیشن تصدیقی سروس کو کال کر سکتا ہے، بغیر کسی ٹیم کو دوسری ٹیم کی عمل آوری کی تفصیلات جاننے کی ضرورت ہے۔ A2A پروٹوکول ایک معاہدہ ہے۔ دونوں فریق اس کا احترام کرتے ہیں۔ باقی اندرونی ہے۔
آخری باب میں پورے نظام کو سرے سے آخر تک چلتے ہوئے دیکھا گیا ہے، یہ دیکھتا ہے کہ اسے کیسے بڑھایا جائے، اور یہ دیکھتا ہے کہ ملٹی ایجنٹ ماحولیاتی نظام آگے کہاں جا رہا ہے۔
باب 9: مجموعی نظام اور اگلے اقدامات
سب کچھ بنایا گیا ہے۔ چار لینگ گراف ایجنٹس مشترکہ ریاست کے ذریعے مربوط ہیں، ٹول تک رسائی فراہم کرنے والے دو MCP سرورز، دو A2A سروسز جو آزادانہ عمل کے طور پر چل رہے ہیں، لینگ فیوز کیپچر کرنے والے فیصلے کی سطح کے نشانات، ڈیپ ایول چلانے والے کوالٹی گیٹس، اور ایک Streamlit UI ہر چیز کو بغیر ٹرمینل کے قابل استعمال بناتے ہیں۔
یہ باب ایک عملی کتاب ہے۔ کس طرح تمام ٹکڑے ایک ساتھ فٹ ہوتے ہیں، سیکھتے ہیں، پیمانہ بناتے ہیں، اور لرننگ ایکسلریٹر سے آگے پیٹرن کا اطلاق کرتے ہیں۔
9.1 main.py: انٹری پوائنٹ
main.py 140 لائنوں سے کم۔ یہ چار کام انجام دیتا ہے: کنفیگریشن لوڈ کریں، کمانڈ لائن آرگومنٹس پر عمل کریں، انٹرپٹ/ریزیوم لوپ کا استعمال کرتے ہوئے گراف کو چلائیں، اور سیشن کا خلاصہ پرنٹ کریں۔
دیگر تمام مسائل (ایجنٹ، ٹولز، مشاہدہ، استقامت) ماڈیولز میں سنبھالے جاتے ہیں۔ main.py درآمد
# main.py
import sys
import os
import uuid
from pathlib import Path
# Add src/ to Python path before any project imports
sys.path.insert(0, str(Path(__file__).parent / "src"))
from dotenv import load_dotenv
load_dotenv()
from graph.workflow import graph
from graph.state import initial_state
from observability.langfuse_setup import get_langfuse_config, flush_langfuse
def run_session(goal: str, session_id: str | None = None) -> None:
"""Run a complete interactive study session with Langfuse tracing."""
is_resume = session_id is not None
if not session_id:
session_id = str(uuid.uuid4())[:8]
# get_langfuse_config() builds the full run config:
# - thread_id for SQLite checkpointing
# - Langfuse callback handler (if LANGFUSE_PUBLIC_KEY is set)
config = get_langfuse_config(session_id)
print(f"n{'='*60}")
print(f"Learning Accelerator")
print(f"Session ID: {session_id}")
if is_resume:
print(f"Resuming existing session...")
else:
print(f"Goal: {goal}")
print(f"{'='*60}")
# For a new session: initial state. For resume: None. LangGraph loads from checkpoint.
state = None if is_resume else initial_state(goal, session_id)
result = graph.invoke(state, config=config)
# Interrupt/resume loop
from langgraph.types import Command
while "__interrupt__" in result:
interrupt_payload = result["__interrupt__"][0].value
roadmap = interrupt_payload.get("roadmap")
if roadmap:
# Display roadmap (abbreviated for chapter. See repo for the full version.)
print_roadmap(roadmap)
print(f"n{interrupt_payload.get('prompt', 'Continue?')}")
user_input = input("> ").strip()
result = graph.invoke(Command(resume=user_input), config=config)
if result.get("error"):
print(f"n[ERROR] {result['error']}")
return
print_session_summary(result)
flush_langfuse() # Ensure all traces are sent before exit
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Learning Accelerator")
parser.add_argument("goal", nargs="?",
default="Learn Python closures and decorators from scratch")
parser.add_argument("--resume", metavar="SESSION_ID",
help="Resume an existing session by ID")
args = parser.parse_args()
if args.resume:
run_session(goal="", session_id=args.resume)
else:
run_session(goal=args.goal)
اس فائل کے بارے میں تین چیزیں قابل غور ہیں:
گراف کو ماڈیول لیول سنگلٹن کے طور پر درآمد کیا گیا ہے۔ from graph.workflow import graph چلائیں build_graph() ایک بار درآمد پر۔ مرتب شدہ گراف پورے عمل پر لاگو ہوتا ہے، بشمول ایک ہی SqliteSaver کنکشن اور وہی رجسٹرڈ نوڈس۔
یہ جان بوجھ کر ہے۔ اکثریت graph.invoke کالز (ابتدائی اور کسی رکاوٹ کی وجہ سے دوبارہ شروع کی گئی) سبھی ایک ہی چیک پوائنٹر کا استعمال کرتے ہوئے ایک ہی مرتب کردہ گراف کا استعمال کرتی ہیں۔
ریزیومے کے لیے اسٹیٹس ہینڈلنگ ایک لائن ہے۔ state = None if is_resume else initial_state(...). گزرنا None LangGraph کو تازہ ترین چیک پوائنٹ لوڈ کرنے کو کہتا ہے۔ thread_id کو config. یہ کال کرنے والے کی طرف سے مکمل ریزیوم میکانزم ہے۔
کہ while لوپ منظوری اور مسترد دونوں کو ہینڈل کرتا ہے۔ جب صارف ان پٹ دیتا ہے۔ noمشروط کناروں کو واپس بھیج دیا گیا ہے: curriculum_plannerایک نیا روڈ میپ بنائیں اور دوسرا روڈ میپ چلائیں۔ interrupt(). لوپ نئے روڈ میپس کو ظاہر کرتا رہتا ہے جب تک کہ صارف انہیں منظور نہ کر دے۔
9.2 3 ٹرمینل شروع
پورے نظام کو بیک وقت چلانے کے لیے تین عمل درکار ہوتے ہیں۔ کہ Makefile ایک واحد کمانڈ ہدف فراہم کرتا ہے:
make setup # First time only: create venv and install dependencies
make langfuse # Optional: start self-hosted Langfuse
make services # Start both A2A services in background
make run # Start main application (foreground)
کہ services ہدف:
services: stop
@echo "Starting A2A services..."
$(PYTHON) src/a2a_services/quiz_service.py &
@sleep 1
$(PYTHON) src/crewai_agent/study_buddy.py &
@sleep 1
@echo ""
@echo "Services started:"
@echo " Quiz: http://localhost:9001"
@echo " Study Buddy: http://localhost:9002"
یقینی بنائیں کہ آپ ہر چیز سے جڑ سکتے ہیں۔
curl http://localhost:9001/.well-known/agent-card.json
curl http://localhost:9002/.well-known/agent-card.json
curl http://localhost:3000 # Langfuse UI
9.3 پورا سیشن، آخر سے آخر تک
Ollama چل رہا ہے، A2A سروس شروع ہو گئی ہے، اور Langfuse کو ترتیب دیا گیا ہے۔
make services
make run
گول ان پٹ، منظوری اور ٹاپک لوپس:
============================================================
Learning Accelerator
Session ID: 8660e1d6
Goal: Learn Python closures and decorators from scratch
============================================================
[Observability] Tracing session 8660e1d6 → http://localhost:3000
[Curriculum Planner] Building roadmap for: 'Learn Python closures...'
[Curriculum Planner] Calling qwen2.5:7b...
[Curriculum Planner] Created roadmap: 5 topics, 4 weeks
1. Python Functions: 60 min
2. Scopes and Namespaces (needs: Python Functions): 45 min
3. Inner Functions (needs: Scopes and Namespaces): 60 min
4. Creating Closures (needs: Inner Functions): 75 min
5. Decorator Basics (needs: Creating Closures): 60 min
[Human Approval] Pausing for roadmap review...
============================================================
Proposed Study Plan
============================================================
Goal: Learn Python closures and decorators from scratch
Duration: 4 weeks @ 5 hrs/week
1. Python Functions (60 min)
Understand how functions are first-class objects in Python.
...
Does this study plan look good?
Type 'yes' to start studying
Type 'no' to generate a different plan
> yes
[Human Approval] Roadmap approved. Starting study session.
[Explainer] Topic: 'Python Functions'
[Explainer] LLM call 1/8...
→ tool_list_files({})
← ["closures.md", "decorators.md", "python_basics.md"]
[Explainer] LLM call 2/8...
→ tool_read_file({'filename': 'python_basics.md'})
← # Python Basics...
[Explainer] Complete after 4 LLM call(s)
[Quiz Generator] Generating quiz for: 'Python Functions'
[Progress Coach] Delegating quiz to A2A: http://localhost:9001
[Quiz A2A] Task received: topic="Python Functions", answers_provided=3
[Quiz A2A] Task complete: status=graded
[Progress Coach] Score: 67%
[Progress Coach] Requesting study assistance from CrewAI Study Buddy...
[Study Buddy A2A] Task complete (287 chars)
────────────────────────────────────────────────────────────
Coach: You've got a solid foundation in Python functions...
📚 Study Buddy says:
Think of functions like variables with superpowers...
Next topic: 'Scopes and Namespaces'
────────────────────────────────────────────────────────────
وہ واحد سیشن سسٹم کے تمام اجزاء کو چلاتا ہے، بشمول لینگ گراف آرکیسٹریشن، ایس کیو ایلائٹ چیک پوائنٹس، ہیومن-ان-دی-لوپ انٹرپٹس، ایم سی پی ٹول کالز، کوئز سروس اور کریو اے آئی اسٹڈی بڈی دونوں کے لیے A2A وفد، اور لینگ فیوز ٹریکنگ۔ ایک سیشن کا خلاصہ آخر میں پرنٹ کیا جاتا ہے۔ ٹریس لینگ فیوز میں سیکنڈوں میں ظاہر ہو جائے گا۔
9.4 آسان UI
ٹرمینل انٹرفیس ترقی کے لیے موزوں ہے۔ اگر آپ اپنے سسٹم کو کسی ایسے شخص کو دکھانا چاہتے ہیں جو اسے ہر روز استعمال کرتا ہے اور ٹرمینل نہیں کھولتا ہے، تو آپ کو اپنے سسٹم کے لیے ایک ویب UI کی ضرورت ہوگی۔
streamlit_app.py اپنے پروجیکٹ کی جڑ میں ایک فراہم کریں۔ تعمیراتی پہلو سمجھنے کے قابل ہے۔ لینگ گراف کوڈ src/ کوئی تبدیلی نہیں. اسی گراف کے ذریعے main.py اپنی ویب ایپس کو طاقتور بنائیں۔ صرف I/O میکانزم مختلف ہے۔ input() اور print() یہ ایک Streamlit ویجیٹ بن جاتا ہے اور معطل/دوبارہ شروع کرنے کا پیٹرن بٹن پر کلک کرنے پر اس طرح بن جاتا ہے: st.session_state دوبارہ چلانے کے دوران سیاق و سباق فراہم کریں۔
Streamlit ہر صارف کے تعامل پر پوری Python اسکرپٹ کو دوبارہ چلاتا ہے۔ کوئی بھی چیز جو دوبارہ چلانے کے بعد باقی رہنی چاہئے۔ st.session_stateStreamlit ایک حکم ہے جو پھانسیوں کے درمیان محفوظ ہے۔ لینگ گراف سیشن IDs، رن کنفیگریشنز، روڈ میپ، ٹاپک انڈیکس، اور کوئز پروگریس سب یہاں موجود ہیں۔
ایپ ایک ریاستی مشین پر مشتمل ہے جس میں پانچ اسکرینیں ہیں (اہداف درج کریں، روڈ میپ کو منظور کریں، تبصرہ، کوئز، اور ہو گیا)۔ st.session_state.screen ہر دوبارہ چلنے کا فیصلہ کرتا ہے کہ کیا پیش کرنا ہے۔
آرکیٹیکچرل شکن ہے quiz_generator_node فون کال run_quiz() استعمال کیا جاتا ہے input() اپنے ٹرمینل سے جوابات جمع کریں۔ Streamlit سے کال کرنے پر، براؤزر منجمد ہو جاتا ہے۔ فکس اس طرح ایک مرتب کردہ UI مخصوص گراف ہے: interrupt_before=["quiz_generator"]:
# streamlit_app.py (key excerpt)
from graph.workflow import build_graph
from graph.state import initial_state, StudyRoadmap, QuizResult
from agents.quiz_generator import generate_questions, grade_answer
# UI-specific graph: pauses BEFORE quiz_generator so the UI can
# handle quiz I/O without input() being called inside the graph.
ui_graph = build_graph(
db_path="data/checkpoints_ui.db",
interrupt_before=["quiz_generator"],
)
UI کوئز کو خود کال کرکے ہینڈل کرتا ہے: generate_questions اور grade_answer جب کوئز براہ راست ایپ کی پرت میں مکمل ہو جاتا ہے (ایک جیسی فعالیت، مختلف کالر)، ایپ استعمال کرتی ہے: graph.update_state() انجیکشن QuizResult جیسے چیک پوائنٹ پر واپس جانا quiz_generator_node چلانے کے بعد، پروگریس کوچ کو چلانے کے لیے گراف کو دوبارہ شروع کریں۔
def advance_after_quiz(quiz_result: QuizResult):
"""After UI-handled quiz completes, inject result and resume graph."""
config = st.session_state.graph_config
# Tell LangGraph quiz_generator has already run with this result
ui_graph.update_state(
config,
{
"quiz_results": existing + [quiz_result],
"weak_areas": all_weak,
"roadmap": st.session_state.roadmap,
"current_topic_index": st.session_state.current_topic_index,
},
as_node="quiz_generator",
)
# Resume. Runs progress_coach, then either explainer (next topic) or END.
# Because interrupt_before=["quiz_generator"], if a next topic exists
# the graph pauses again before its quiz_generator.
result = ui_graph.invoke(None, config=config)
یاد رکھنے کے قابل پیٹرن میں شامل ہیں: graph.update_state(config, values, as_node=...) کال کرنے والا چیک پوائنٹ کو اس طرح پیچ کر سکتا ہے جیسے کسی مخصوص نوڈ نے اس قدر کو پیدا کیا ہو۔ یہ گراف کے باہر چلنے والے کوڈ کے نتائج کو گراف کے اسٹیٹ فلو میں داخل کرنے کا ایک طریقہ ہے۔
چلائیں:
make streamlit
# or: streamlit run streamlit_app.py
![LangGraph، MCP، اور A2A کا استعمال کرتے ہوئے ملٹی ایجنٹ AI سسٹم کیسے بنایا جائے۔ [Full Book] 2 Streamlit ویب انٹرفیس کا اسکرین شاٹ جس میں لرننگ ایکسلریٹر کے روڈ میپ کی منظوری کی اسکرین دکھائی دے رہی ہے: لرننگ ایکسلریٹر آئٹم کے ساتھ بائیں طرف لیبل لگا ہوا نیویگیشن، اور گریجویشن کیپ ٹائٹل کے ساتھ مرکزی مواد کا علاقہ۔](https://umang.pk/wp-content/uploads/2026/04/1777579829_572_LangGraph،-MCP،-اور-A2A-کا-استعمال-کرتے-ہوئے-ملٹی-ایجنٹ.png)
شکل 3۔ اسٹریم لائٹ ویب انٹرفیس۔ وہی لینگ گراف کوڈ، وہی MCP سرور، وہی A2A سروس۔ دیگر I/O
براؤزر http://localhost:8501 پر کھلتا ہے۔ آپ ویب UI کا استعمال کرتے ہوئے اسی نظام کو حاصل کرسکتے ہیں۔ ٹارگٹ ان پٹ فارم میں ہوگا روڈ میپ کی منظوری کے دو بٹن ہوں گے۔ تفصیل کو فارمیٹ شدہ مارک ڈاؤن کے طور پر پیش کیا جاتا ہے۔ کوئز سوالات ایک وقت میں ایک جواب کے میدان کے ساتھ ظاہر ہوتے ہیں۔ کوچ کی رائے اگلے موضوع سے پہلے معلوماتی خانے میں ظاہر ہوتی ہے۔
سیشن مکمل ہونے کے بعد، سمری اسکرین ہر موضوع کے لیے آپ کا سکور اور ٹرمینل کو دوبارہ شروع کرنے کے لیے سیشن ID دکھاتی ہے۔
اسٹریملیٹ session_state پیٹرن
Streamlit ہر صارف کے تعامل پر پوری اسکرپٹ کو دوبارہ چلاتا ہے۔ ہر وہ چیز جس کو دوبارہ چلانے کے ذریعے زندہ رہنا ہے۔ st.session_stateایک لغت جو Streamlit رنز کے درمیان برقرار رکھتی ہے۔ لینگراف session_id اور graph_config تم دونوں وہاں جاؤ۔ یہی حال موجودہ اسکرین، روڈ میپ، موجودہ سوالات کا اشاریہ، درجہ بند جوابات، اور تکمیلی فہرستوں کے لیے بھی ہے۔ QuizResult اشیاء
ایپس مؤثر طریقے سے ریاستی مشینیں ہیں۔ st.session_state.screen اس بات کا تعین کرتا ہے کہ بٹن کے کلک کے جواب میں کیا رینڈرنگ اور اسٹیٹ مشین ٹرانزیشن ہوتی ہے۔
یہ پروٹوکول فرسٹ فن تعمیر کا کارنامہ ہے۔ سسٹم میں ایک ٹرمینل UI، ایک ویب UI ہے، جس کے بعد React فرنٹ اینڈ، ایک Slack bot، یا iOS ایپ شامل کرنے کا آپشن ہے، اور اس میں LangGraph کوڈ ہے۔ src/ یہ ان سب سے متاثر نہیں ہوتا ہے۔
9.5 پروجیکٹ کا ڈھانچہ، حتمی
سب کچھ بننے کے بعد، ذخیرہ کی ترتیب اس طرح نظر آتی ہے:
freecodecamp-multi-agent-ai-system/
├── src/
│ ├── agents/
│ │ ├── curriculum_planner.py # JSON roadmap generation
│ │ ├── explainer.py # MCP tool-calling loop
│ │ ├── quiz_generator.py # Two-call pattern + grading
│ │ ├── progress_coach.py # Synthesis + A2A delegation
│ │ └── human_approval.py # interrupt() / Command resume
│ ├── graph/
│ │ ├── state.py # AgentState + 4 dataclasses
│ │ └── workflow.py # StateGraph definition
│ ├── mcp_servers/
│ │ ├── filesystem_server.py # Tools: list, read, search
│ │ └── memory_server.py # Tools: get, set, delete, list
│ ├── a2a_services/
│ │ ├── quiz_service.py # Quiz agent on :9001
│ │ └── a2a_client.py # JSON-RPC client + discovery
│ ├── crewai_agent/
│ │ └── study_buddy.py # CrewAI agent on :9002
│ └── observability/
│ └── langfuse_setup.py # Callback handler + config
├── tests/ # 182 unit + 12 eval tests
├── study_materials/sample_notes/ # Explainer's source content
├── docs/ # ARCHITECTURE.md, MODEL_SELECTION.md
├── data/ # SQLite checkpoints (created at runtime)
├── main.py # Terminal entry point
├── streamlit_app.py # Web UI entry point
├── Makefile # One-command targets
├── docker-compose.yml # Self-hosted Langfuse
├── requirements.txt # Pinned versions
└── pyproject.toml # pythonpath + pytest config
9.6 سسٹم کی توسیع
یہ فن تعمیر موجودہ کوڈ کو چھوئے بغیر متعدد سمتوں میں توسیع کی حمایت کرتا ہے۔
ایک نیا ایجنٹ شامل کریں۔ نوڈ فنکشن لکھیں۔ src/agents/your_agent.py. رجسٹر کریں۔ workflow.py کے ساتھ builder.add_node("your_agent", your_agent_node). موجودہ نوڈس سے جڑنے والے کناروں کو شامل کریں۔ باقی تمام ایجنٹ بغیر کسی تبدیلی کے کام کرتے رہتے ہیں کیونکہ وہ ایک دوسرے کے بارے میں نہیں جانتے۔ وہ صرف ریاست کے بارے میں جانتے ہیں۔
انفرنس بیک اینڈ کو تبدیل کریں۔ تمام ایجنٹوں کے ذریعہ استعمال کیا جاتا ہے۔ ChatOllama اشارہ کیا OLLAMA_BASE_URL. اس یو آر ایل کو LiteLLM گیٹ وے پر سیٹ کرنا (سامنے میں اولاما کا API بولتا ہے اور اوپن اے آئی، اینتھروپک، یا دوسرے فراہم کنندگان کو پیچھے کی طرف لے جاتا ہے) چاروں ایجنٹوں کو بغیر کسی کوڈ میں تبدیلی کے نئے بیک اینڈ میں تبدیل کر دے گا۔ API ایک معاہدہ ہے۔
MCP ٹولز شامل کریں۔ اضافہ @mcp.tool() فنکشن filesystem_server.py یا memory_server.py. اسے شامل کریں۔ @tool ریپر explainer.py اور اسے شامل کریں EXPLAINER_TOOLS. ایجنٹ کا سسٹم پرامپٹ LLM کو بتاتا ہے کہ نیا ٹول کب استعمال کرنا ہے۔ کسی دوسری تبدیلی کی ضرورت نہیں ہے۔
نئی A2A سروس شامل کریں۔ ذیل میں ایک نیا ماڈیول بنائیں۔ a2a_services/ اگلا quiz_service.py پیٹرن: ایجنٹ کارڈ، ایگزیکیوٹر سب کلاس، یوویکورن سرور۔ کلائنٹ کی خصوصیات شامل کریں۔ a2a_client.py. کوئی بھی ایجنٹ جس کی ضرورت ہو اسے کلائنٹ فنکشن کہتے ہیں۔ خدمات الگ الگ عمل ہیں اور بنیادی درخواست سے آزادانہ طور پر تعینات، اسکیل، اور دوبارہ شروع کی جا سکتی ہیں۔
اپنی ریاست کو PostgreSQL میں منتقل کریں۔ تبدیلی SqliteSaver کے ساتھ PostgresSaver کو workflow.py. کنکشن سٹرنگ کو اپنی پوسٹگریس مثال پر سیٹ کریں۔ اور کچھ نہیں بدلتا۔ LangGraph کا چیک پوائنٹ انٹرفیس بیک اینڈ ایگنوسٹک ہے۔
اپنی A2A سروس میں تصدیق شامل کریں۔ پیک create_quiz_server()توثیق مڈل ویئر کے ساتھ اسٹارلیٹ ایپ۔ A2A پروٹوکول اس کی حمایت کرتا ہے۔ ایجنٹ کارڈ ایک تصدیقی اسکیم کا اعلان کر سکتا ہے اور کلائنٹ اپنے اسناد کو ایکشن لفافے میں پاس کرتا ہے۔ یہ قابل اعتماد نیٹ ورک کے باہر پروڈکشن کی تعیناتیوں میں کیا جانا چاہیے۔
ان میں سے ہر ایک ایکسٹینشن فن تعمیر کی ایک مخصوص پرت کو انجام دیتا ہے۔ اس میں سے کسی کو بھی نیچے کی تہوں کو دوبارہ لکھنے کی ضرورت نہیں ہے۔
چوکی: ہر چیز کے چلنے کے ساتھ، پورا ٹیسٹ سویٹ چلائیں۔
make services
pytest tests/ -v
# 184 tests, eval tests skipped by default
اس کے بعد ہم Ollama کا استعمال کرتے ہوئے تشخیصی ٹیسٹ چلاتے ہیں۔
pytest tests/test_eval.py -m eval -s -v
# 12 eval tests: checks quality, faithfulness, grading calibration
آخر میں، پورے نظام کو دستی طور پر چلائیں.
make run
# Follow the prompts, complete a session
# Check Langfuse UI for the trace
تصدیق کے تینوں مراحل گزر چکے ہیں۔ نظام مکمل ہے۔
9.7 کوشش کے لحاظ سے ترتیب دی گئی پانچ ایکسٹینشنز
آپریشن میں چار ایجنٹ کے نظام ہیں. یہ مشکل حصہ ہے۔ باقی انکریمنٹل ہیں۔ نیچے کی ہر سمت ایک قدرتی اگلا مرحلہ ہے، دوبارہ لکھنا نہیں۔
1. اپنے انفرنس بیک اینڈ کو ایک منظم گیٹ وے (کام کے ایک گھنٹے سے کم) پر سوئچ کریں۔
سسٹم کے تمام ایجنٹ استعمال کرتے ہیں: ChatOllama اشارہ کیا OLLAMA_BASE_URL. اس کے بجائے، اس URL کو LiteLLM گیٹ وے پر سیٹ کریں۔ LiteLLM سامنے پر اولاما کا API استعمال کرتا ہے اور OpenAI، Anthropic، Together، یا پچھلے حصے پر دوسرے فراہم کنندگان کے لیے روٹ کرتا ہے۔ چاروں ایجنٹس ایک ماحولیاتی متغیر کو تبدیل کرکے نئے پسدید پر سوئچ کرتے ہیں۔
ہم فال بیک روٹنگ کو اسی نقطہ نظر سے ہینڈل کرتے ہیں۔ GPT-4 کو آزمانے کے لیے LiteLLM کو کنفیگر کریں، اگر وہ ناکام ہو جاتا ہے تو واپس Claude پر جائیں، اور اگر دونوں نیچے ہوں تو مقامی ماڈل پر واپس جائیں۔ آپ کے ایجنٹ کوڈ کو اندازہ نہیں ہے کہ یہ ہو رہا ہے۔
2. اپنی A2A سروس میں ایک تصدیقی پرت شامل کریں (کچھ گھنٹے لگتے ہیں)۔
ایجنٹ کارڈ تصدیقی اسکیموں کا اعلان کر سکتے ہیں۔ پیداوار A2A کی تعیناتیوں کے لیے بیئرر ٹوکن یا mTLS سرٹیفکیٹ کی ضرورت ہوتی ہے۔ پیک create_quiz_server()اپنی Starlette ایپ کو FastAPI کے موافق تصدیقی مڈل ویئر کے ساتھ اپ ڈیٹ کریں۔ a2a_client.py ایکشن لفافے میں اسناد کو پاس کرنا سروس کو محفوظ بناتا ہے جب بھروسہ مند نیٹ ورک سے باہر ظاہر ہوتا ہے۔
A2A پروٹوکول مقامی طور پر اس کی حمایت کرتا ہے۔ بیئرر ٹوکن HTTP میں جاتا ہے۔ Authorization دیگر REST خدمات کی طرح، یہ ایک ہیڈر ہے۔
3. SQLite چوکیوں کو PostgreSQL میں منتقل کریں (ٹیسٹنگ سمیت آدھا دن لگتا ہے)۔
تبدیلی SqliteSaver کے ساتھ PostgresSaver کو workflow.py. کنکشن سٹرنگ کو اپنی پوسٹگریس مثال پر سیٹ کریں۔ LangGraph کا چیک پوائنٹ انٹرفیس بیک اینڈ ایگنوسٹک ہے۔
یہ کثیر مثالی تعیناتیوں کے لیے اہم ہے۔ SQLite ایک ہی عمل میں کام کرتا ہے، جبکہ PostgreSQL متعدد مثالوں کو چلانے کی اجازت دیتا ہے۔ main.py (یا Streamlit ایپس) کا موازنہ ایک ہی چیک پوائنٹ اسٹور سے کیا جاتا ہے، لہذا سیشن مثال کے دوبارہ شروع ہونے پر برقرار رہتا ہے اور اسے تمام مثالوں میں منتخب کیا جا سکتا ہے۔
4. ایک سلسلہ بندی کا جواب شامل کریں (کام کے ایک یا دو دن)۔
لینگ گراف سپورٹ کرتا ہے: graph.astream() ایجنٹ نوڈس سے ٹوکن لیول اسٹریمنگ کے لیے۔ اسٹریمز کو استعمال کرنے اور تیار کردہ تفصیل پیش کرنے کے لیے Streamlit UI کو اپ ڈیٹ کریں۔ مکمل جواب کے لیے 3-4 سیکنڈ انتظار کرنے کے بجائے، صارفین 500ms کے بعد آؤٹ پٹ اسٹارٹ دیکھیں گے۔
وضاحت کرنے والا وہ ایجنٹ ہے جو سب سے زیادہ فائدہ اٹھاتا ہے۔ 1,500-2,500 حروف کی وضاحتیں تیار کرتا ہے اور سمجھی جانے والی تاخیر میں بہتری نمایاں ہے۔
5. موبائل کے لیے دوستانہ فرنٹ اینڈ بنانا (ایک ہفتہ کا گہرا کام)
اپنے Streamlit UI کو ایک React یا Next.js فرنٹ اینڈ سے تبدیل کریں جو آپ کے گراف کے ارد گرد FastAPI ریپر کو کال کرتا ہے۔ ریپر وہی پانچ اسکرین فلوز کو ظاہر کرتا ہے جیسے REST اینڈ پوائنٹ: اہداف درج کریں، روڈ میپ کو منظور کریں، تبصرے، کوئز، اور مکمل کریں۔ لینگ گراف کوڈ ہے۔ src/ یہ بالکل نہیں بدلتا۔ کوئز کا مجموعہ اور درجہ بندی کا پیٹرن وہی رہے گا جیسا کہ Streamlit ایپ فی الحال کرتی ہے۔ API معاہدہ مندرجہ ذیل ہے:
POST /api/sessions → create session, return session_id + roadmap
POST /api/sessions/:id/approval → body: {"approved": true/false}
GET /api/sessions/:id/current → current topic, explanation, questions
POST /api/sessions/:id/answer → submit one quiz answer, get graded response
GET /api/sessions/:id/summary → final summary when complete
یہ وہ فن تعمیر ہے جسے لرننگ ایکسلریٹر تعمیر کرے گا اگر یہ ایک حقیقی پروڈکٹ بن جائے۔ گراف بیک اینڈ پر چلتا ہے۔ فرنٹ اینڈ ایک پتلا کلائنٹ ہے۔ ضمیمہ C میں پیداوار بڑھانے کی چیک لسٹ لاگو ہوتی ہے۔
9.8 پیداوار کو مضبوط بنائیں
لکھا ہوا سسٹم ٹیوٹوریل لیول ہے۔ یہ مقامی طور پر چلتا ہے، غلطیوں کو احسن طریقے سے ہینڈل کرتا ہے، اور تمام تصورات کو صحیح طریقے سے ظاہر کرتا ہے۔ یہ انٹرپرائز پیمانے پر ہزاروں ساتھی صارفین کی خدمت کے لیے تیار نہیں ہے۔
اس بات پر منحصر ہے کہ ہر شے کو کتنے کام کی ضرورت ہے، تبدیلیوں میں شامل ہو سکتے ہیں:
شرح کی حد فی درخواست۔ آرکیسٹریٹر کی سطح پر نافذ کردہ فی ایجنٹ ٹوکن بجٹ شامل کرتا ہے۔ یہ سخت پابندیاں ہیں، ہدایات نہیں۔
فی ایجنٹ پانچ ٹول کالز کے ساتھ چار ایجنٹ کا نظام فی صارف کی درخواست پر 20 LLM کالز سے زیادہ ہے۔ پیمانے پر، لاگت فن تعمیر سے پہلے انجینئرنگ کا مسئلہ بن جاتی ہے۔ LiteLLM گیٹ وے اسے آسان بنا دیتا ہے۔ آپ فی سیشن اپنے اخراجات کو ٹریک کرسکتے ہیں اور حدود کا اطلاق کرسکتے ہیں۔
چیک پوائنٹ ہجرت کی حفاظت۔ براہ کرم ورژن کی وضاحت کریں۔ AgentState خاکہ جب آپ سسٹم کا نیا ورژن تعینات کرتے ہیں، تو جاری ورک فلو جو پرانے اسکیما کے خلاف چیک پوائنٹ ہوتے ہیں نئے کوڈ کو ڈی سیریلائز کرنے کی کوشش کریں گے۔ اگر فیلڈز کو شامل یا ہٹا دیا جاتا ہے، تو ورک فلو درمیان میں ناکام ہو جائے گا۔
چیک پوائنٹ فارمیٹ کو عوامی API کے طور پر سمجھیں۔ پہلے سے طے شدہ اقدار کے ساتھ اختیاری کے طور پر نئے فیلڈز شامل کریں، ہٹائے گئے فیلڈز کو اپنے ریلیز سائیکل کے لیے حذف کرنے سے پہلے فرسودہ کریں، اور اپنی تعیناتی پائپ لائن کے حصے کے طور پر اسکیما منتقلی کی جانچ کریں۔
کولڈ ہینڈلنگ شروع کریں۔ اعلی ماڈل کے وزن اور انحصار والے ایجنٹ کنٹینرز کو کولڈ اسٹارٹ ہونے میں 30 سے 60 سیکنڈ لگ سکتے ہیں۔ پیداوار کی درخواست کی شرح صارفین کو کنٹینر کے شروع ہونے کے دوران ایک منٹ انتظار کرنے کی اجازت نہیں دے سکتی ہے۔ ایک متبادل راستہ ڈیزائن کریں جو کنٹینرز کے گرم تالاب کو برقرار رکھ کر یا ایک آسان، تیز بیک اپ ایجنٹ کا استعمال کرکے کولڈ اسٹارٹ میں تاخیر کی اجازت دیتا ہے۔ کوئی تیسرا آپشن نہیں ہے۔ سردی شروع نہ ہونے کا بہانہ نہ کریں۔
پیمانے پر مشاہدہ۔ مقامی لینگ فیوز ترقی کے لیے کام کرتا ہے۔ پیداوار کی تعیناتیوں کے لیے ایک منظم لینگ فیوز یا اسی طرح کے تقسیم شدہ ٹریسنگ بیک اینڈ کی ضرورت ہوتی ہے جو روزانہ لاکھوں نشانات کو سنبھال سکے۔
فیصلہ کی سطح سے باخبر رہنے کی آپ کو ضرورت ہے۔ صرف انفراسٹرکچر میٹرکس آپ کو یہ نہیں بتاتے کہ آپ کے ملٹی ایجنٹ انفرنس چین میں کیا غلط ہے۔ درخواست میں تاخیر اس وقت تک ٹھیک ہوسکتی ہے جب تک کہ ماڈل غلط جوابات تیار کرتا ہے۔
CI کی تشخیص۔ باب 7 میں DeepEval ٹیسٹ آپ کی تعیناتی پائپ لائن کے حصے کے طور پر چلائے جائیں۔ کوئی بھی نیا ماڈل، پرامپٹ، یا ایجنٹ کی تبدیلی تشخیص کے مکمل مجموعہ کو متحرک کرتی ہے۔ اگر وفاداری ایک حد سے نیچے آتی ہے تو تبدیلیاں مسدود ہوجاتی ہیں۔ یہ LLM رویے کے لیے ایک ریگریشن سویٹ ہے، معیار میں بتدریج کمی کے خلاف انشورنس۔
مواد کی حفاظت۔ ایجنٹ آؤٹ پٹ کو صارفین یا پروڈکشن سسٹم تک پہنچنے سے پہلے مواد کے فلٹرز کو پاس کرنا چاہیے۔ اگرچہ وضاحت کنندہ آپ کے نوٹس پر مبنی ہے، LLM پھر بھی فریب یا مواد پیدا کر سکتا ہے جو اس کی پالیسیوں کی خلاف ورزی کرتا ہے۔
پیداواری ماحول میں جہاں آؤٹ پٹ کے ڈیٹا بیس تک پہنچنے سے پہلے اسکیما کی توثیق کی پرت اور مواد کا فلٹر یا غلط آؤٹ پٹ کے نتائج اہم ہوتے ہیں، یہ صارف کے لیے ناقابلِ مذاکرات ہے۔
ضمیمہ C میں ایک مکمل سختی چیک لسٹ شامل ہے۔
9.9 2026 میں ماحولیاتی نظام کہاں جائے گا؟
متعدد رجحانات نئے سرے سے تشکیل دے رہے ہیں کہ کس طرح ملٹی ایجنٹ سسٹمز بنائے جاتے ہیں، اور جب آپ اپنے اگلے پروجیکٹ کی منصوبہ بندی کرتے ہیں تو دونوں پر توجہ دینے کے قابل ہیں۔
پروٹوکول انضمام
MCP اور A2A دونوں نے 2025 میں v1.0 وضاحتیں جاری کیں۔ Google، Anthropic، Salesforce، SAP اور درجنوں دیگر دکانداروں نے دستخط کیے ہیں۔ ایجنٹ کا دور اسی معیاری کاری کے عمل کی پیروی کر رہا ہے جو REST نے ویب سروسز کے لیے کیا تھا۔ یہ پہلے تو گندا ہے، لیکن پھر باقی سب کچھ واضح فاتحوں میں بدل جاتا ہے۔
آپ کے کام کے لیے اس کا کیا مطلب ہے: MCP میں ٹول تک رسائی کو معیاری بنانا اور A2A میں ایجنٹ کوآرڈینیشن اب کم خطرہ ہے۔ یہ پروٹوکول تین سال بعد بھی نافذ رہیں گے۔ فریم ورک کا انتخاب آگے پیچھے جائے گا۔
مقامی-پہلا بنیادی ڈھانچہ
مقامی اور کلاؤڈ انفرنس کے معیار کے درمیان فرق کم ہوتا جا رہا ہے۔ صرف ایک سال پہلے، مقامی 7B ماڈل پر ملٹی ایجنٹ سسٹم چلانا ایک ڈیمو تھا، پروڈکشن ٹول نہیں۔ فی الحال، Qwen 2.5 پیرامیٹرز کے ساتھ 7-32B ہینڈل ٹول کالز پروڈکشن ورک فلو کے لیے کافی قابل اعتماد ہے۔
مقامی تخمینہ کی رازداری، قیمت، اور تاخیر کے فوائد اہم ہیں۔ کچھ صنعتیں بیرونی APIs کو ڈیٹا بھیجنے کی اجازت نہیں دیتی ہیں۔ ایک فن تعمیر جو مقامی طور پر اچھی طرح سے کام کرتا ہے ایک منظم گیٹ وے پر بھی اچھا کام کرے گا۔ ایک مخصوص کلاؤڈ فراہم کنندہ کی صلاحیتوں کے ارد گرد بنائے گئے فن تعمیرات کو منتقل کرنا زیادہ مشکل ہوتا ہے۔
لمبا سیاق و سباق، تنگ ایجنٹ
سیاق و سباق کی کھڑکی بڑھتی جارہی ہے۔ فی الحال، متعدد تجارتی ماڈلز میں 1 ملین سے زیادہ ٹوکن دستیاب ہیں۔ یہ عام طور پر ملٹی ایجنٹ سسٹمز کا معاملہ ہے۔ اگر ایک ایجنٹ پوری بات چیت اور ہر چیز کے بارے میں دلیل برقرار رکھ سکتا ہے، تو کام کو کیوں تقسیم کیا جائے؟
جواب بدل گیا ہے۔ ملٹی ایجنٹ اب صرف سیاق و سباق ونڈو کے انتظام کے بارے میں نہیں ہے۔ یہ تخصص، غلطی کی تنہائی، اور آزاد تعیناتی کے بارے میں ہے۔
اس کی وجوہات پر باب 1 میں بحث کی گئی ہے۔ جیسے جیسے سنگل ایجنٹ کی صلاحیتیں بڑھ جاتی ہیں، "کیا یہ مسئلہ ملٹی ایجنٹ کی ضمانت دیتا ہے" کا بار بڑھ جاتا ہے۔ ملٹی ایجنٹ سسٹم بنانے والی بہت سی ٹیمیں آج ایک ہی ایجنٹ اور بہتر ٹولز کا استعمال کرتے ہوئے وہی نتائج حاصل کر سکتی ہیں۔
اس ہینڈ بک کے نمونے اب بھی لاگو ہوتے ہیں۔ سوال یہ ہے کہ آپ کو ان تک کب پہنچنا چاہئے؟
9.10 ان نمونوں کو کہاں لاگو کرنا ہے۔
لرننگ ایکسلریٹر تعلیمی گاڑیاں ہیں۔ پیٹرن نیچے منتقل کر رہے ہیں. یہ پیداواری نظام آج اس فن تعمیر کا استعمال کرتے ہیں۔
1. سیلز ایکٹیویشن
نصاب کے ایجنٹ نئے سیلز نمائندوں کے لیے آن بورڈنگ کا راستہ بناتے ہیں۔ مواد کے ایجنٹ MCP کے ذریعے اندرونی معلومات کی بنیاد میں مصنوعات کی خصوصیات کو بیان کرتے ہیں۔ تشخیصی ایجنٹ فہم کی جانچ کرتا ہے۔ پروگریس ایجنٹ متعدد پروڈکٹ ایریاز میں سرٹیفیکیشن کو ٹریک کرتے ہیں۔ تربیت شروع ہونے سے پہلے منتظمین نصاب کو انسانوں سے منسلک گیٹ کے ذریعے منظور کرتے ہیں۔
2. قانون کی تعمیل کی تربیت
HIPAA، SOX، اور GDPR کے لیے ڈومین کے لیے مخصوص نصاب کا ایجنٹ۔ MCP سرور کے ذریعے حقیقی ریگولیٹری متن پر مبنی مواد کا ایجنٹ (ماڈل کا تربیتی ڈیٹا نہیں)۔ زیادہ سخت درجہ بندی کی حد اور آڈٹ لاگز کے ساتھ ریٹنگ ایجنٹ جو ریگولیٹرز کے لیے برآمد کیے جا سکتے ہیں۔ تربیت تفویض کرنے سے پہلے ہیومن-ان-دی-لوپ گیٹ قانونی جائزہ کا مرحلہ بن جاتا ہے۔
3. کسٹمر سپورٹ
ایک ریسپشنسٹ ٹکٹوں کو ترتیب دے گا۔ تحقیقی ایجنٹ MCP کے ذریعے نالج بیس مضامین پڑھتے ہیں۔ ڈرافٹر جواب لکھتا ہے۔ جائزہ لینے والے بھیجنے سے پہلے پالیسی کی تعمیل چیک کرتے ہیں۔ A2A پرت Salesforce ایجنٹوں کو ServiceNow ایجنٹوں کو اپنی مرضی کے مطابق LangGraph ایجنٹوں کو کال کرنے کی اجازت دیتی ہے۔ یعنی حسب ضرورت انضمام کے بغیر سسٹمز کے درمیان۔
4. انجینئرنگ آن بورڈنگ
کوڈبیس ایجنٹ آپ کے ذخیرے کے ذریعے نئے ملازمین کی رہنمائی کرتا ہے۔ ٹولنگ ایجنٹ ترقی کے ماحول کو بیان کرتا ہے۔ جائزہ لینے والے ایجنٹ کوڈنگ کے معیارات کے بارے میں سوالات کے جوابات دیتے ہیں۔ وہ سب اصل کوڈبیس اور دستاویزات پر مبنی ہیں، MCP سرور اندرونی ذخیرہ کی طرف اشارہ کرتا ہے۔
مشترکہ دھاگہ: ان میں سے ہر ایک میں ایک آرکیٹیکچرل مارکر ہوتا ہے۔ مختلف ذیلی کاموں کے لیے مختلف ٹولز۔ ایل ایل ایم کال کے مختلف نمونے۔ ایک تخصص جو ایک مشترکہ ایجنٹ سے سمجھوتہ کرتی ہے۔ فالٹ آئسولیشن کی ضروریات۔
ملٹی ایجنٹ فن تعمیر کو اس کی نیاپن کے لیے منتخب نہیں کیا گیا تھا۔ ان کا انتخاب اس لیے کیا گیا کہ وہ مسائل کی اقسام سے مماثل ہیں۔
9.11 آگے کیا بنانا ہے۔
ہلکی سے لے کر سب سے بڑی لفٹوں تک، یہاں کچھ تجاویز ہیں کہ انہیں کہاں لے جانا ہے۔
-
اپنے ایم سی پی ٹولز شامل کریں۔ اپنی نوٹس ڈائرکٹری میں فائل سسٹم سرور کی وضاحت کریں۔ ایک MCP سرور لکھیں جو آپ کے پسندیدہ علمی ذرائع سے استفسار کرتا ہے: تصور، سنگم، اور ٹیم دستاویزی سائٹس۔ ٹول کال لوپ اسی طرح کام کرتا ہے۔ صرف سرور کے نفاذ میں تبدیلی آتی ہے۔
-
نصاب کی شاخیں بنائیں۔ لرننگ ایکسلریٹر ایک پروگرامنگ موضوع کو فرض کرتا ہے۔ اپنا پرامپٹ تبدیل کریں۔
curriculum_planner.pyآپ کے ڈومین میں: طبی تعلیم، زبان سیکھنا، قانونی تربیت۔ گراف کا ڈھانچہ وہی رہتا ہے۔ -
ایک ساتھی تجزیاتی ایجنٹ بنائیں۔ ہم ایک چھٹا ایجنٹ شامل کرتے ہیں جو وقتاً فوقتاً چلتا ہے (پہلے سے طے شدہ گراف نہیں) اور سیشنوں میں سیکھنے کے نمونوں کا خلاصہ کرتا ہے۔ چیک پوائنٹ ڈیٹا بیس، لینگ فیوز ٹریس، اور MCP میموری سے پڑھتا ہے۔ ہفتہ وار پیشرفت کی رپورٹ بنائیں۔ یہ ایک زبردست توسیع ہے کیونکہ یہ موجودہ کوڈ میں ترمیم کیے بغیر سسٹم کے تمام حصوں کو چلاتا ہے۔
-
اپنی ہینڈ بک بنائیں: ان نمونوں کو مضبوط کرنے کا بہترین طریقہ انہیں سکھانا ہے۔ مختلف مسائل کے لیے مختلف ملٹی ایجنٹ سسٹم بنائیں اور جو کچھ آپ سیکھتے ہیں اسے دستاویز کریں۔ انفراسٹرکچر پیٹرن (ٹولز کے لیے MCP، ایجنٹ کوآرڈینیشن کے لیے A2A، آرکیسٹریشن کے لیے LangGraph، لچک کے لیے چوکیاں، LLM-جج برائے تشخیص) تمام ملٹی ایجنٹ کے مسائل پر لاگو ہوتے ہیں۔ کچھ ایجنٹ اور اوزار بدل جاتے ہیں۔
نتیجہ
آپ نے اس ہینڈ بک کا آغاز ایک سوال سے کیا۔ کیا آپ کا مسئلہ درحقیقت متعدد ایجنٹوں کی ضرورت ہے؟ اس سوال نے باقی انجینئرنگ کو ایماندار رکھا۔
لرننگ ایکسلریٹر میں ہر ایجنٹ موجود ہے کیونکہ وہ جو کام کرتا ہے وہ دوسرے ایجنٹوں سے بالکل مختلف ہوتے ہیں۔ مختلف ٹولز، مختلف LLM کال پیٹرن، مختلف درجہ حرارت، مختلف ناکامی کے طریقے۔
ہم نے ایک کثیر ایجنٹ فن تعمیر کا انتخاب نہیں کیا۔ میں نے اس کا انتخاب کیا کیونکہ مجھے مسئلہ فارم کی ضرورت ہے۔
اس فیصلے سے اوپر کی تمام تکنیکی تہوں نے انہی اصولوں پر عمل کیا۔
-
چونکہ پروڈکشن سسٹم کریش ہونے پر حالت نہیں کھو سکتے، لینگ گراف ریاست پر مبنی آرکیسٹریشن اور چیک پوائنٹ کی صلاحیتیں فراہم کرتا ہے۔
-
کیونکہ ایجنٹوں کو کسی خاص عمل سے منسلک نہیں کیا جانا چاہئے، MCP معیاری ٹول تک رسائی۔
-
A2A نے فریم ورک کے درمیان ہم آہنگی کو فعال کیا، کیونکہ حقیقی دنیا کا بنیادی ڈھانچہ بعض اوقات متعدد فریم ورک پر پھیلا ہوا ہے۔
-
چونکہ انفراسٹرکچر میٹرکس اکیلے یہ نہیں بتا سکتا کہ آیا کوئی ایجنٹ صحیح طریقے سے استدلال کر رہا ہے، لینگ فیوز نے فیصلے کی سطح کے نشانات حاصل کر لیے۔
-
DeepEval نے کوالٹی گیٹ لاگو کیا کیونکہ LLM نتائج کی جانچ کرنے کا واحد قابل اعتماد طریقہ واضح معیار کے خلاف فیصلہ کرنے والا ایک اور LLM ہے۔
-
Streamlit UI نے ظاہر کیا کہ LangGraph کوڈ I/O-agnostic ہے۔
-
یہی گراف ٹرمینل سیشنز اور ویب ایپس کو سپورٹ کرتا ہے۔
ان سب کے بنیادی انجینئرنگ اصول وہی ہیں جو آگے بڑھنے کے قابل ہیں۔ اچھی طرح سے ڈیزائن کردہ ملٹی ایجنٹ سسٹم میں تمام حدود پروٹوکول ہیں، ایسوسی ایشنز نہیں۔.
ایجنٹ TypedDict معاہدوں کے ذریعے ریاست کے ساتھ بات چیت کرتے ہیں۔ ایجنٹ MCP کے ذریعے ٹولز سے بات کرتے ہیں۔ ایجنٹ A2A کے ذریعے ایک دوسرے سے بات کرتے ہیں۔ ایجنٹ لینگ چین کال بیکس کے ذریعے مشاہدے کے بارے میں بات کرتے ہیں۔
ہر باؤنڈری کو بقیہ حدود کو چھوئے بغیر تبدیل، تبدیل یا بڑھایا جا سکتا ہے۔ یہ وہی ہے جو سسٹم کی پیداوار کا درجہ بناتا ہے۔ یہ استعمال شدہ مخصوص فریم ورک نہیں ہے، بلکہ ان فریم ورک کو واضح انٹرفیس کے پیچھے رکھنے کا نظم و ضبط ہے۔
جو بھی آپ آگے بناتے ہیں، اس اصول کو ذہن میں رکھیں۔ ماڈل بدل جاتا ہے۔ فریم ورک بدل جاتا ہے۔ ایجنٹ کے دور میں کچھ ٹولز اس شرح سے تیار ہوں گے جس کے ساتھ کوئی ہینڈ بک نہیں رکھ سکتی۔ اچھے تعمیراتی فیصلے ان سب کو پیچھے چھوڑ دیتے ہیں۔
اس ہینڈ بک کا مکمل کوڈ github.com/sandeepmb/freecodecamp-multi-agent-ai-system پر دستیاب ہے۔ کلون، دوڑنا، کانٹا، پیمانہ۔ اگر آپ ان نمونوں کی بنیاد پر کوئی دلچسپ چیز بناتے ہیں، تو میں واقعی اس کے بارے میں سننا چاہوں گا۔
اب جاؤ کچھ بنا لو۔
ضمیمہ A: فریم ورک کا موازنہ
اس ہینڈ بک میں شامل فریم ورک اور جہاں ہر فریم ورک مناسب ہے۔ یہ جدول 2026 کے اوائل تک ماحولیاتی نظام کی حالت کو ظاہر کرتا ہے۔ مخصوص خصوصیات تبدیل ہو جائیں گی۔ مقصدی قیاسات مستحکم رہتے ہیں۔
| کنکال | یہ کیا ہے | میں اسے کب استعمال کروں؟ | اگر آپ اسے چھوڑ دیتے ہیں۔ |
|---|---|---|---|
| لینگراف | چیک پوائنٹس، مشروط روٹنگ، اور بنیادی HITL کے ساتھ اسٹیٹفول ایجنٹ کا گراف | پروڈکشن ملٹی ایجنٹ ورک فلو جہاں ریاستی استقامت اور تعییناتی روٹنگ اہم ہیں۔ | سادہ واحد ایجنٹ کا کام جس میں کوئی ریاست نہیں ہے۔ |
| عملہ AI | اعلاناتی ٹیموں اور کاموں کے ساتھ کردار پر مبنی ملٹی ایجنٹ فریم ورک | کردار پر مبنی ایجنٹ تعاون کی تیز رفتار پروٹو ٹائپنگ۔ یہ ایک استعمال کا معاملہ ہے جو قدرتی طور پر فلائٹ اٹینڈنٹ استعارہ پر فٹ بیٹھتا ہے۔ | پیچیدہ برانچنگ منطق یا حسب ضرورت کنٹرول فلو۔ عملہ تجرید راستے میں ہو جاتا ہے. |
| خود کار طریقے سے پیدا | گروپ چیٹ کے نمونوں کے ساتھ مائیکروسافٹ کا مکالماتی ملٹی ایجنٹ فریم ورک | تحقیقی اور تحقیقی کام۔ بات چیت کے نمونوں پر مبنی ملٹی ایجنٹ منظرنامے۔ | پیداواری نظام جن کے لیے سخت کنٹرول کے بہاؤ اور واضح ریاستی انتظام کی ضرورت ہوتی ہے۔ |
| لاما انڈیکس | RAG - طاقتور ڈیٹا اکٹھا کرنے اور بازیافت کی صلاحیتوں کے ساتھ پہلا فریم ورک | سسٹم جہاں غیر ساختہ ڈیٹا کی بازیافت ایک اہم مسئلہ ہے۔ | خالص ایجنٹ آرکیسٹریشن۔ آپ شاید LangGraph یا اس سے ملتی جلتی کوئی چیز استعمال کریں گے۔ |
| لینگ چین | LLM ایپ کے بنیادی اصولوں کے لیے ایک وسیع ٹول کٹ۔ وہ بنیاد جس پر لینگ گراف بیٹھا ہے۔ | ایجنٹ کے اندر استعمال ہونے والے نچلے درجے کے بلڈنگ بلاکس (پرامپٹس، آؤٹ پٹ پارسر، چینز) | آرکیسٹریشن خود۔ گراف پر مبنی ملٹی ایجنٹ سسٹمز کے لیے لینگ گراف کا استعمال کریں۔ |
| ایم سی پی (کوڈ) | ماڈل سیاق و سباق پروٹوکول۔ معیاری ایجنٹ ٹول انٹرفیس | کوئی بھی ایسا نظام جہاں ٹول کا نفاذ لازمی طور پر قابل تبادلہ اور فریم ورک میں دوبارہ قابل استعمال ہو۔ | Python فنکشنز کے ساتھ ون آف انٹرنل ٹول ٹھیک سے کام کر رہا ہے۔ |
| A2A (کوڈ) | ایجنٹ سے ایجنٹ پروٹوکول۔ HTTP پر فریم ورک میں ایجنٹ کوآرڈینیشن | تمام ٹیموں یا فریم ورک میں ایجنٹوں کو مربوط کریں؛ ایجنٹوں کو آزادانہ طور پر تعینات کریں۔ | یہ مضبوطی سے جوڑے ہوئے ایجنٹ ہیں جو ہمیشہ ایک ساتھ تعینات ہوتے ہیں۔ براہ راست فنکشن کالز آسان ہیں۔ |
آرکیسٹریٹر کے انتخاب کے لیے انگوٹھے کا اصول ہے: لینگ گراف کی طاقتیں (چیک پوائنٹس، بریک/ریزیوم، واضح ریاستی معاہدے) پیداوار کے لیے ضروری ہیں۔ جب کردار پر مبنی استعارے ڈومینز کو واضح طور پر نقشہ بناتے ہیں تو CrewAI بہت اچھا ہوتا ہے۔ آٹوجین کا گروپ چیٹ پیٹرن تحقیق اور تلاش کے کاموں کے لیے سخت پروڈکشن کنٹرول فلو سے بہتر ہے۔
فریم ورک کی ترجیحات کو مسئلہ کی اقسام کو اوور رائیڈ کرنے کی اجازت نہ دیں۔ اگر آپ کا مسئلہ گراف ہے تو لینگ گراف استعمال کریں۔ اگر مسئلہ گفتگو کا ہے، تو AutoGen استعمال کریں۔
MCP اور A2A ان فریم ورک کا مقابلہ نہیں کرتے ہیں۔ وہ ذیل میں انضمام کی پرتیں ہیں۔ LangGraph میں ایک ایجنٹ بنائیں، اسے A2A سروس کے طور پر ظاہر کریں، اور MCP کو بطور ٹول استعمال کریں۔ آرکیسٹریشن فریم ورک سے قطع نظر آپ جو بھی انتخاب کرتے ہیں، آپ ان تینوں کو مکس اور میچ کر سکتے ہیں۔
ضمیمہ B: ماڈل سلیکشن گائیڈ
اس نظام میں تمام ایجنٹ مقامی تخمینہ کے لیے اولاما کا استعمال کرتے ہیں۔ ماڈل کا انتخاب اس بات کا تعین کرتا ہے کہ آیا ٹول کالز قابل اعتماد طریقے سے کام کرتی ہیں۔ 7B پیرامیٹرز والے ماڈل غلط JSON اور ٹول کے ناموں کو اس مقام تک پہنچاتے ہیں کہ ایجنٹ کا استعمال ناکام ہوجاتا ہے۔
VRAM سے سفارشات
| VRAM | ماڈل | پل کمانڈ | کے لیے بہترین موزوں ہے۔ |
|---|---|---|---|
| 8 جی بی | qwen2.5:7b |
ollama pull qwen2.5:7b |
یونیورسل اور قابل اعتماد ٹول کالنگ |
| 8 جی بی | qwen3:8b |
ollama pull qwen3:8b |
بہتر اندازہ، وہی VRAM کلاس |
| 24 جی بی | qwen2.5-coder:32b |
ollama pull qwen2.5-coder:32b |
اسے اس درجے کا بہترین ٹول کہیں۔ |
| 24 جی بی | qwen3:32b |
ollama pull qwen3:32b |
اس کلاس میں مجموعی طور پر بہترین |
| صرف سی پی یو | qwen2.5:7b (Q4_K_M) |
ollama pull qwen2.5:7b |
کام کرتا ہے، 5 سے 10 گنا سست |
macOS پر Apple Silicon مربوط میموری CPU اور GPU کے درمیان مشترک ہے۔ 16 جی بی مربوط میموری میک ماڈل تقریباً 8 جی بی کے ساتھ آتے ہیں۔ ایپل مینو کے ذریعے چیک کریں → اس میک کے بارے میں → چپ معلومات۔
پروڈکشن ایجنٹ کے استعمال کے لیے کم از کم قابل عمل درجے: 7B پیرامیٹرز۔ ذیلی 7B ماڈل چیٹ کو اچھی طرح سے ہینڈل کرتا ہے، لیکن قابل بھروسہ ٹول کالز کے لیے JSON فارمیٹ کی بہت ساری غلطیاں پیش کرتا ہے۔
کہ format="json" علامہ کی مجبوریاں مدد کرتی ہیں۔ درست JSON کے لیے انفرنس ٹائم کی ضمانت دیتا ہے۔ لیکن ماڈل ابھی بھی تیار کرنے کی ضرورت ہے۔ معنی خیز JSON قابل تجزیہ JSON نہیں ہے اور اس کے لیے پیرامیٹر کی گنتی 7 بلین سے زیادہ درکار ہے۔
اس نظام میں استعمال ہونے والی درجہ حرارت کی ترتیبات
یہ وہ ترتیبات ہیں جو ہر ایجنٹ پر لاگو ہوتی ہیں۔ اسے کبھی استعمال نہ کریں۔ temperature > 0.5 کسی بھی ایجنٹ کے لیے جو ساختی JSON آؤٹ پٹ تیار کرتا ہے۔ تجزیہ ناقابل اعتبار ہو جاتا ہے۔
# Structured output: Curriculum Planner, Quiz Generator grading
ChatOllama(temperature=0.1, format="json")
# Tool-calling loop: Explainer
ChatOllama(temperature=0.3)
# Creative generation: Quiz Generator questions, Progress Coach
ChatOllama(temperature=0.4, format="json")
# Deterministic evaluation: DeepEval OllamaJudge
ChatOllama(temperature=0.0)
مختلف درجہ حرارت کیوں اہم ہیں: ایک درجہ حرارت کی ترتیب کے ساتھ ایک واحد ایجنٹ ہر اس چیز سے سمجھوتہ کرے گا جو اسے سنبھالتا ہے۔ ساختی JSON منصوبوں کو مستقل مزاجی کے لیے 0.1 کی ضرورت ہوتی ہے۔ تخلیقی سوالات کی تخلیق تنوع کے لیے 0.4 کا فائدہ فراہم کرتی ہے۔ منصفانہ ہونے کے لیے، درجہ بندی کے لیے 0.1 کی ضرورت ہے۔
جب ایک ایجنٹ تینوں کام انجام دیتا ہے۔ temperature=0.25منصوبہ بندی تجزیہ کی غلطیاں پیدا کرتی ہے اور سوال پیدا کرنے سے بار بار سوالات پیدا ہوتے ہیں۔ مختلف درجہ حرارت کی ترتیب کے ساتھ اسے متعدد ایجنٹوں میں تقسیم کرنا اس نظام کے ملٹی ایجنٹ فن تعمیر کا ایک اہم جواز ہے۔
سوئچنگ ماڈل
تبدیلی OLLAMA_MODEL کو .env. کوڈ میں تبدیلی کی ضرورت نہیں ہے۔
# .env
OLLAMA_MODEL=qwen2.5-coder:32b
OLLAMA_BASE_URL=http://localhost:11434
پھر ماڈل درآمد کریں اگر آپ نے درج ذیل کام نہیں کیا ہے:
ollama pull qwen2.5-coder:32b
چاروں ایجنٹ خود بخود نئے ماڈل کو اپنی اگلی دوڑ میں استعمال کرتے ہیں۔
ماڈل کے لیے مخصوص جانچ کی حد
دہلیز tests/test_eval.py 7B ماڈل کے لیے 0.6 پر کیلیبریٹ کیا گیا۔ بڑے ماڈلز میں عام طور پر زیادہ اسکور ہوتے ہیں۔ اگر آپ اپ گریڈ کرنا چاہتے ہیں اور ایک سخت کوالٹی گیٹ رکھنا چاہتے ہیں، تو اضافہ کریں:
| ماڈل درجہ بندی | وفاداری | مطابقت | سوال کا معیار | میمو |
|---|---|---|---|---|
| 7-8B مقامی | 0.65-0.80 | 0.70-0.85 | 0.65-0.80 | 0.6 کی ڈیفالٹ حد |
| 32B مقامی | 0.80-0.90 | 0.85-0.95 | 0.80-0.90 | آپ حد کو 0.75 تک بڑھا سکتے ہیں۔ |
| GPT-4 / کلاڈ | 0.85-0.98 | 0.90-0.98 | 0.85-0.95 | آپ حد کو 0.85 تک بڑھا سکتے ہیں۔ |
اپنی حد کو اپنے عام سکور سے تقریباً 10% کم پر سیٹ کریں۔ اگر آپ اپنے عام اسکور کے بہت قریب پہنچ جاتے ہیں، تو آپ کا امتحان غیر مستحکم ہوگا۔ اگر آپ بہت دور جائیں گے تو آپ رجعت سے محروم ہوجائیں گے۔
ضمیمہ C: پیداوار بڑھانے کی چیک لسٹ
لکھا ہوا سسٹم ٹیوٹوریل لیول ہے۔ پیمانے پر تعینات کرنے سے پہلے اس چیک لسٹ کا جائزہ لیں۔ ہر آئٹم ایک حقیقی ناکامی کے موڈ پر نقشہ بناتا ہے جو پیداوار کی تعیناتی میں ظاہر ہوتا ہے۔
آرکیسٹریشن اور ریاستیں۔
-
[ ] SQLite کو PostgreSQL سے تبدیل کرنا چوکیوں کے لیے۔ SQLite ایک ہی عمل میں کام کرتا ہے۔ متعدد مثالوں کی تعیناتیوں کے لیے پوسٹگریس کی ضرورت ہوتی ہے۔
-
[ ] براہ کرم ورژن کی وضاحت کریں۔
AgentStateخاکہ پہلے سے طے شدہ اقدار کا استعمال کرتے ہوئے اختیاری کے طور پر ایک نیا فیلڈ شامل کریں۔ حذف کرنے سے پہلے، اپنے ریلیز سائیکل کے لیے ہٹائے گئے فیلڈز کا مزید استعمال نہ کریں۔ -
[ ] اسکیما منتقلی کی جانچ کریں۔ آپ کی تعیناتی پائپ لائن کے حصے کے طور پر۔ جاری ورک فلو کو رولنگ تعیناتیوں کے بعد بھی برقرار رکھا جانا چاہیے۔
-
[ ] ایک واضح ٹائم آؤٹ بجٹ سیٹ کریں۔ ہر ایجنٹ کال کرتا ہے۔ آرکیسٹریٹر سے تمام ڈاؤن اسٹریم سروسز تک ٹائم آؤٹ کا پرچار کرتا ہے۔
-
[ ] سرکٹ بریکر شامل کریں۔ تمام بیرونی سروس کالز (LLM API، A2A سروس، MCP سرور) کی وضاحت کرتا ہے۔ دوبارہ کوشش کریں طوفان پیداواری دباؤ کو بڑھا دیتے ہیں۔
تخمینہ اور لاگت
-
[ ] انفرنس گیٹ وے کے ذریعے روٹنگ (LiteLLM یا اس سے ملتی جلتی) میں شرح محدود، ماڈل متبادل، اور لاگت فی سیشن ٹریکنگ ہے۔
-
[ ] فی ایجنٹ ٹوکن بجٹ نافذ کریں۔ آرکیسٹریٹر کی سطح پر۔ یہ سخت پابندیاں ہیں، ہدایات نہیں۔
-
[ ] ٹوپی
max_iterationsہر ٹول کال لوپ میں۔ وضاحت کنندہ ہے۔max_iterations=8. یقینی بنائیں کہ ہر ایجنٹ کی حدود ایک جیسی ہیں۔ -
[ ] فی سیشن لاگت کی نگرانی کریں۔ جب سیشن بجٹ سے زیادہ ہوں تو اطلاعات بھیجیں۔ دوسری صورت میں، ایک الجھن ایجنٹ خود کو غیر معینہ مدت تک دوبارہ کر سکتا ہے.
مشاہدہ
-
[ ] لینگ فیوز کو منظم یا انتہائی دستیاب سیلف ہوسٹنگ میں منتقل کریں۔ مقامی لینگ فیوز پروڈکشن ٹریس والیوم تک توسیع نہیں کرتا ہے۔
-
[ ] سیشن لیول ٹریس کیپچر آپ سٹرکچرڈ ٹیگز (یوزر آئی ڈی، فیچر فلیگ، ماڈل ورژن) کا استعمال کرکے فلٹر اور موازنہ کرسکتے ہیں۔
-
[ ] اطلاع کی ترتیبات خرابی کی شرحوں میں اضافے، ٹوکن کی لاگت میں اضافے، اور تاخیر سے ریگریشنز کے بارے میں جانیں۔
-
[ ] نمونہ ٹریکنگ پیداوار میں. 100% سیمپلنگ مہنگا ہے۔ عام طور پر، غلطی کو مکمل طور پر پکڑنے کے لیے 10 سے 20 فیصد نمونے کافی ہوتے ہیں۔
-
[ ] ڈیٹا گودام میں ٹریس برآمد کریں۔ طویل مدتی تجزیہ اور ریگولیٹری آڈٹ کے لیے وقفے وقفے سے کارکردگی کا مظاہرہ کیا جاتا ہے۔
تشخیص اور معیار
-
[ ] CI پر ایک تشخیصی سوٹ چلانا ہر بار جب آپ تعینات کرتے ہیں۔ ایسی تعیناتیوں کو مسدود کریں جو معیار کی حد کو پورا نہیں کرتی ہیں۔
-
[ ] ریگریشن ٹیسٹ سیٹس کو برقرار رکھیں اچھے ان پٹ اور متوقع آؤٹ پٹس کو جانا جاتا ہے۔ کسی بھی ماڈل میں تبدیلی سے پہلے اسے چلائیں۔
-
[ ] وقت کے ساتھ معیار کے میٹرکس کو ٹریک کریں۔ بتدریج بڑھنے کو پکڑنا اچانک رجعت سے زیادہ مشکل ہے۔
-
[ ] انسانی جائزے کے نمونے لیں۔ زیادہ خطرے والے فیصلوں کے لیے۔ یہ تمام آؤٹ پٹ نہیں ہے، لیکن یہ شماریاتی لحاظ سے اہم نمونہ ہے۔
سیکورٹی
-
[ ] اپنی A2A سروس میں تصدیق شامل کریں۔ آپ کے ماحول کے لحاظ سے بیئرر ٹوکن، mTLS، یا OAuth۔
-
[ ] آڈٹ MCP ٹول پر عمل درآمد پاتھ نیویگیشن، انجیکشن، اور استحقاق میں اضافے کے لیے۔ کہ
read_study_fileاس نظام کا کام پیٹرن دکھاتا ہے۔ -
[ ] LLM ان پٹ کو حذف کریں۔ جو بھی ماڈل دیکھتا ہے وہ اس کے رویے کو متاثر کر سکتا ہے، بشمول بازیافت شدہ مواد سے بالواسطہ اشارے داخل کرنا۔
-
[ ] سٹرکچرڈ آؤٹ پٹ کی تصدیق پروڈکشن سسٹمز پر درخواست دینے سے پہلے اسکیما کی توثیق، پالیسی کے قواعد، اور حفاظتی فلٹرز۔
-
[ ] غیر تبدیل شدہ آڈٹ لاگز کو برقرار رکھیں تمام فیصلے جو پیداواری سرگرمیوں کا باعث بنتے ہیں۔ ریگولیٹڈ صنعتوں کے لیے ضروری ہے۔
-
[ ] ہیومن ان دی لوپ تھریشولڈز کو نافذ کرنا اعلی خطرے والے رویے کے لیے۔ کم خطرات کو خود کار بنائیں اور زیادہ خطرات کو بڑھا دیں۔
-
[ ] اسنادی گردش API کیز، ڈیٹا بیس کنکشنز، اور سروس ٹوکنز کے لیے۔
وشوسنییتا اور ناکامی کے طریقوں
-
[ ] متبادل راستے کا ڈیزائن تمام بیرونی انحصار کے لیے۔ اس سسٹم میں پروگریس کوچ کا A2A فال بیک پیٹرن ایک ایسا ماڈل ہے جو سروس کی کوشش کرتا ہے اور ناکامی پر خود بخود واپس آجاتا ہے۔
-
[ ] کولڈ ہینڈلنگ شروع کریں۔ ایجنٹ کنٹینرز کے لیے۔ گرم تالاب یا قابل قبول متبادل۔ کنٹینر شروع ہونے کے لیے صارفین کو 60 سیکنڈ انتظار کرنے سے گریز کریں۔
-
[ ] مواد کے فلٹرز کو لاگو کریں۔ ایجنٹ آؤٹ پٹ کے بارے میں۔ گراؤنڈ ان پٹ کے ساتھ ہیلوسینیشن بھی ہوتا ہے۔
-
[ ] صحت کی جانچ کی ترتیبات تمام خدمات کے لیے۔ A2A ایجنٹ کارڈ ہیلتھ اینڈ پوائنٹ کے طور پر کام کرتا ہے۔ کوئی بھی کلائنٹ کنیکٹیویٹی کی جانچ کے لیے اسے حاصل کر سکتا ہے۔
-
[ ] خوبصورت ہراس کی جانچ واضح طور پر۔ ایک وقت میں ایک ایک سروسز بند کریں اور چیک کریں کہ آیا ڈیفالٹ ایپ جواب دینا جاری رکھتی ہے۔
حکمرانی
-
[ ] ایجنٹ کی تمام ذمہ داریوں کو دستاویز کریں۔ کون سے ٹولز استعمال کیے جاتے ہیں، کن حالتوں کو پڑھا اور لکھا جاتا ہے، اور غلطی کے کون سے طریقوں کی توقع کی جاتی ہے۔
-
[ ] فوری ورژن رجسٹری کو برقرار رکھیں یہ گٹ کمٹ سے منسلک ہے۔ معلوم کریں کہ جب مسئلہ پیش آیا تو کون سے اشارے پروڈکشن میں تھے۔
-
[ ] ماڈل اپ گریڈ کا جائزہ لیں اور منظور کریں۔ ماڈل ورژن کو تبدیل کرنے سے آؤٹ پٹ رویے کو ان طریقوں سے تبدیل کیا جا سکتا ہے جو بہاو کے مفروضوں کو توڑ دیتے ہیں۔
-
[ ] رول بیک طریقہ کار قائم کریں۔ کوڈ اور ماڈل دونوں تبدیلیوں کے لیے۔ خراب تعیناتی کو واپس لانے میں منٹ لگتے ہیں، گھنٹے نہیں۔
یہ ایک مکمل فہرست نہیں ہے، لیکن اس میں ناکامی کے طریقوں کا احاطہ کیا گیا ہے جو ملٹی ایجنٹ سسٹم کی پیداواری تعیناتیوں میں حقیقت پسندانہ ہیں۔ اپنی پہلی عوامی ریلیز سے پہلے اس کا جائزہ لیں اور جیسے جیسے نظام تیار ہوتا ہے سہ ماہی میں اس کا دوبارہ جائزہ لیں۔