تستيقظ صباح يوم الاثنين على خمسة تقارير عن أخطاء برمجية (bugs) حرجة. لقد أدت أداة مراقبة المراجعات وظيفتها؛ فقد رصدت كل تقرير عن تعطل، وكل تقييم غاضب بنجمة واحدة، وكل ملاحظة تقول "يتجمد التطبيق عندما أضغط على حفظ". أنت تعرف بالضبط ما هو المعطل، لكن ما لا تعرفه هو أين تبحث.
كان هذا هو الجدار الذي اصطدمت به بعد بناء أول خط معالجة (pipeline) لي. كان يراقب مراجعات التطبيق وسجلات التعطل الواردة دون مشاكل، ويصنف كل قطعة من الملاحظات في فئات مرتبة: أخطاء، أو تعطلات، أو طلبات ميزات. كانت لوحة التحكم تبدو سليمة، لكن عملية تصحيح الأخطاء (debugging) الفعلية لم تكن كذلك.
إن معرفة وجود خطأ برمجي ليست سوى الخطوة الأولى في رحلة طويلة. كان لا يزال يتعين عليّ فتح الـ IDE، واستخدام grep عبر الوحدات البرمجية (modules)، ومقارنة تتبعات المكدس (stack traces) مع قاعدة الكود الحالية، وإعادة بناء مسار الفشل في ذهني. عندما تتراكم التذاكر (tickets) ويكون القهوة لا يزال ساخناً، فإن هذا البحث اليدوي المضني يستهلك وقتاً لا تملكه. كنت بحاجة إلى أن يفعل خط المعالجة أكثر من مجرد تحديد المشكلات؛ كنت بحاجة إليه أن يحقق فيها.
لذا أعدت بناء النظام حول هدف واحد: أخذ تقرير خطأ خام وإرجاع تشخيص موثق. ليس فقرة من تأملات نماذج اللغة الكبيرة (LLM)، بل نتيجة مهيكلة تحدد الملف، وتشير إلى السطر، وتقدر المخاطر، وتقترح حلاً. إليك كيف تم ذلك.
لماذا تتفوق الهيكلة على سجل الدردشة
قمت ببناء وكيل التحقيق (investigating agent) باستخدام PydanticAI. كان السبب بسيطاً؛ عندما تطلب من نموذج لغوي الاستنتاج حول الكود، فإن مخرجاته الافتراضية هي تدفق ودي من النصوص. قد يساعد ذلك القارئ البشري، لكنه عديم الفائدة لأي سكربت لاحق. كنت بحاجة إلى عقد قابل للقراءة آلياً.
يعيد الوكيل نموذج بيانات موثقاً يحتوي على أربعة حقول محددة: السبب الجذري، الملفات المتأثرة، التغييرات المقترحة، وتقييم للتعقيد والمخاطر. إذا كان النموذج يفتقد حقلاً ما أو "يهلوس" بمسار ملف غير موجود، تفشل عملية التحقق وأكتشف ذلك فوراً. هذه الصرامة تحافظ على دقة خط المعالجة.
للقيام بالعمل الاستقصائي الفعلي، يحصل الوكيل على أربع أدوات للقراءة فقط ولا شيء غير ذلك. يمكنه البحث في الكود عبر grep، وقراءة نطاقات أسطر محددة من ملف، وسرد محتويات الدليل، وتحديد الرموز (symbols) مثل الفئات (classes) أو الدوال (functions). الجزء المهم هو "القراءة فقط"؛ لم أكن أريد وكيلاً يمتلك صلاحيات الكتابة يتجول في مستودعي البرمجي في الثانية صباحاً. افهم أولاً، ثم عدّل.
خريطة المستودع (Repo Map): السياق قبل الأدوات
كانت النسخة الأولى من الوكيل دقيقة ولكنها مكلفة للغاية من حيث استهلاك الـ tokens. كان يستهلك الـ tokens مثل سائح يمشي في دوائر. كان النموذج يستدعي list-dir ثم grep ثم يقرأ ملفاً، ثم يستدعي list-dir مرة أخرى، محاولاً ببطء بناء نموذج ذهني لهيكل المشروع، توكن تلو الآخر وبكلفة عالية.
كان الحل هو إنشاء خريطة مستودع (repo map) مدمجة قبل أن يبدأ الوكيل عمله. هذه الخريطة هي نظرة عامة مركزة للمستودع: الملفات الرئيسية، وظائفها أو فئاتها الأساسية، وكيفية اتصال الوحدات البرمجية الكبرى ببعضها. فكر في الأمر كأنك تعطي الوكيل جهاز GPS بدلاً من مطالبته باكتشاف الطرق عن طريق التجربة والخطأ.
مع وجود تلك الخريطة في نافذة السياق (context window) الخاصة به، لن يضيع الوكيل استدعاءاته في اكتشاف أن src/utils/parser.ts موجود؛ فهو يعرف التضاريس بالفعل، ويتوجه مباشرة إلى القمة حيث يتصاعد الدخان. هذا التغيير الوحيد ألغى مرحلة التخبط تماماً.
قمع الأدوات: فرض الوصول إلى نتيجة
حتى مع وجود خريطة، كان بإمكان الوكيل التردد. قد يجد ملفاً مشبوهاً، ثم يشك في نفسه، ثم يبحث مرة أخرى، ثم يقرأ ملفاً آخر، عالقاً في حلقة مفرغة من "فقط فحص واحد إضافي". كنت بحاجة إلى طريقة لفرض الزخم.
لقد قمت بتنفيذ "قمع أدوات" مكون من ثلاث مراحل يقيد ما يمكن للوكيل القيام به مع تقدمه.
المرحلة الأولى هي الاستكشاف. يمتلك الوكيل وصولاً كاملاً إلى جميع الأدوات الأربع. يمكنه البحث والتصفح وقراءة كل ما يحتاجه لمحاكاة الخطأ في استنتاجه.
المرحلة الثانية هي التعمق. بمجرد أن يحدد الوكيل خطوط الخلل المحتملة، يفقد أدوات الاستكشاف. يمكنه فقط قراءة الملفات. لا مزيد من grep ولا مزيد من سرد محتويات الأدلة. في هذه المرحلة، يجب عليه دراسة الكود الذي وجده بالفعل وبناء سلسلة الأدلة الخاصة به.
المرحلة الثالثة هي المخرجات. يتم إغلاق جميع الأدوات. لا يمكن للوكيل الاستعلام عن قاعدة الكود بعد الآن؛ عليه أن يجلس ويكتب التقرير. هذا يمنع دوامة "دعني أتحقق من شيء واحد آخر" التي لا تنتهي.
أدى هذا القمع إلى خفض متوسط عدد استدعاءات الأدوات من أكثر من أربعين استدعاءً لكل تحليل إلى حوالي عشرة استدعاءات فقط. أصبح الوكيل أسرع، وأرخص، وبشكل مفارق، أصبح أكثر ثقة لأنه كان مضطراً للالتزام بنتيجة معينة.
الحفاظ على إمكانية استبدال الواجهة الخلفية (Backend)
لم أرغب في ربط النظام برمجياً بمزود نموذج واحد. أنا أستخدم محركات مختلفة بناءً على المهمة؛ أحيانًا Claude Code، وأحيانًا Grok Build، وأحيانًا أي شيء آخر يكون الأرخص في تلك اللحظة. وللحفاظ على استقلالية المنطق الأساسي عن المزود، قمت بتقسيم العمل إلى مرحلتين.
المرحلة الأولى هي الاستكشاف. يقوم وكيل البرمجة (coding agent)، والذي يمكن أن يكون أي نموذج قادر، بقراءة خريطة المستودع (repo map)، واستخدام الأدوات، وإنتاج تقرير markdown خام. هذا هو الجزء المكلف من حيث التفكير.
المرحلة الثانية هي الهيكلة. يأخذ نموذج لغوي كبير (LLM) رخيص وسريع ذلك الـ markdown ويعيد تنسيقه في نموذج Pydantic صارم. لا تتطلب هذه المرحلة أي قدر من التفكير المنطقي تقريبًا؛ فهي مجرد عملية استخراج وتنسيق، لذا يمكن تشغيلها على أجهزة خفيفة.
بما أن الحد الفاصل واضح، يمكنني تبديل الواجهة الخلفية (backend) دون المساس بمنطق التحقق (validation logic). يعمل تقرير markdown كمحول عالمي (universal adapter) بين العقل الاستكشافي والمخرجات المهيكلة التي أستخدمها فعليًا.
ما نجح بالفعل
لقد غير هذا الإعداد طريقة تعاملي مع المشكلات الواردة. لا تزال طبقة التصنيف تفرق بين الأخطاء (bugs) وطلبات الميزات (feature requests)، ولكن الآن تبدأ طبقة التحليل العمل فورًا بعدها. وبحلول الوقت الذي أفتح فيه المحرر الخاص بي، يكون مسار الملف، ونطاق الأسطر، والتغيير المقترح في انتظاري. لا أزال أراجع كل شيء يدويًا. هذه مساعدة وليست قيادة آلية (autopilot). لكن عملية جمع السياق التي كانت
