الوحدة 2 — حلقة الاستدلال والفعل
قلب أيّ وكيل حلقة تحكّم بسيطة: النموذج يفكّر، يقترح فعلًا، نُنفّذ الفعل، نُعيد إليه الملاحظة، ثمّ يقرّر: أَيَنتَهي أم يواصل. هذا ما اقترحته ورقة ReAct (Yao وآخرون، 2022) قبل أن يذوب النمط في نداء الأدوات الأصليّ. في هذه الوحدة نكتب هذه الحلقة يدًا، لأنّ فهمها يحميك من ألف خطأ.
النمط: فكرة، فعل، ملاحظة
يعمل ReAct بتناوب ثلاثيّ:
- فكرة (thought): النموذج يكتب استدلاله المختصر: «أحتاج أن أعرف تاريخ الإصدار قبل مقارنة النسخ.»
- فعل (action): يستدعي أداة بوسائط محدّدة:
recherche_web("release date Postgres 16"). - ملاحظة (observation): شيفرتك تنفّذ الأداة وتُعيد النتيجة إليه.
يُكرَّر هذا حتّى يُنتج النموذج جوابًا نهائيًّا بلا استدعاء أداة. الحلقة كلّها ستّون سطرًا أو أقلّ.
التنفيذ الأدنى ببايثون
نبدأ بأربع أدوات لوكيل البحث: recherche_web، lire_page، base_interne، noter_fait. ثمّ حلقة تحكم:
from openai import OpenAI
client = OpenAI()
outils = [
{"type": "function", "function": {
"name": "recherche_web",
"description": "يبحث في الويب ويُعيد قائمة عناوين URL مع مقتطف قصير.",
"parameters": {"type": "object", "properties": {"requete": {"type": "string"}}, "required": ["requete"]},
}},
# ... أدوات lire_page وbase_interne وnoter_fait بنفس الصيغة
]
def executer(nom, args):
if nom == "recherche_web": return recherche_web(args["requete"])
if nom == "lire_page": return lire_page(args["url"])
if nom == "base_interne": return base_interne(args["question"])
if nom == "noter_fait": return noter_fait(args["fait"], args["source"])
return {"erreur": f"أداة غير معروفة: {nom}"}
def boucle_agent(question, max_iterations=8):
messages = [
{"role": "system", "content": "أنت وكيل بحث. استعمل الأدوات المتاحة، لا تخترع مصادر."},
{"role": "user", "content": question},
]
for i in range(max_iterations):
reponse = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=outils)
msg = reponse.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content
for appel in msg.tool_calls:
resultat = executer(appel.function.name, json.loads(appel.function.arguments))
messages.append({"role": "tool", "tool_call_id": appel.id, "content": json.dumps(resultat)})
return "توقّفت الحلقة عند الحدّ الأقصى للتكرار."
هذه الشيفرة كاملة عمليًّا. لا LangChain ولا LangGraph. الفكرة والفعل يذوبان في tool_calls، والملاحظة تعود عبر رسالة دور tool. حلقة for تُشكّل حدّ التكرار.