شرحexplainer · 2026-10-07

create_agent وطبقة الوساطة في لانغ تشين: كيف تبني وكيلاً قابلاً للتخصيص خطوة بخطوة

create_agent هي الدالة الوحيدة لبناء وكيل في لانغ تشين 1.0 بعد أن حلّت محل create_react_agent، وسبب قوتها الحقيقي هو طبقة الوساطة البرمجية middleware التي تُدخل ستّة خطّافات في دورة الوكيل. يشرح المقال الخطّافات الستة في جدول، ويغطّي الحالة AgentState والمخرجات المهيكلة عبر response_format، وكتابة وسيطتك الخاصة التي تختار النموذج حسب خبرة المستخدم.

نُشر 7 أكتوبر 2026 · قبل 59 دقيقةconfidence 0.921 مصدرrecheck 2027-01-05
مخطط يوضّح مواضع الخطّافات الستة حول حلقة النموذج، مع указа اللحظة التي يعمل فيها كل خطّاف داخل الدورة.

create_agent هي الدالة الوحيدة التي تحتاجها لبناء وكيل في لانغ تشين (LangChain) 1.0، وسبب قوتها ليس أنها تصنع وكيلاً، بل ما يحيط بحلقة النموذج: طبقة الوساطة البرمجية (middleware) التي تُدخل خطّافات جاهزة في الدورة لتلتقط نداءات النموذج والأدوات وتغيّر سلوكها. في هذا المقال تبدأ من أبسط وكيل ممكن، وتنتهي بوسيطة تكتبها بنفسك وتختار النموذج حسب خبرة المستخدم.

المصطلحات التي ستقابلها في هذا المقال:

  • وكيل (agent): نموذج يستدعي أدوات في حلقة حتى تكتمل المهمة.
  • بنية تشغيل الوكيل (harness): كل ما يحيط بحلقة النموذج — الموجّه النظامي والأدوات والوساط.
  • طبقة وسيطة (middleware): قطعة تلتقط سلوك الوكيل عند نقطة محددة من الدورة وتعدّله.
  • خطّاف (hook): الدالة التي تعمل عند تلك النقطة، وتعيد نتيجة أو تعديلاً.
  • الحالة (state) وAgentState: بيانات الوكيل التي تُقرأ وتُحدَّث كل دورة، وحقلها المدمج messages.
  • سياق النداء (context): بيانات ترسلها مع الاستدعاء ولا تُحفظ بعد انتهائه.
  • محادثة (thread): سلسلة استدعاءات متتابعة يميّزها thread_id.
  • مخرجات مهيكلة (structured output): بيانات مُتحقَّق منها في مُخطَّط (schema) محدّد بدل نصّ حر.
  • تدخّل بشري في الحلقة (HITL): إيقاف التنفيذ وانتظار موافقة إنسان قبل أداة حسّاسة.
  • حواجز حماية (guardrails): قيود تفرضها على ما يمرّ عبر الوكيل قبل النموذج وبعده.

ما هي create_agent ولماذا حلّت محل create_react_agent؟

في لانغ تشين 1.0، create_agent صارت المعيار الوحيد لبناء الوكلاء، وقد حلّت محل langgraph.prebuilt.create_react_agent. أي أن كل مقال أو درس قديم مبنيّ على create_react_agent يتحدّث عن واجهة تصمّمها الدالة نيابةً عنك، لا عن بنية تركّبها أنت.

السبب أعمق من تغيير اسم: create_agent لم تعد تغلّف حلقة فيصممها لك، بل تسلّمك بنية تشغيل الوكيل (harness) كمجموعة قطع تمرّرها أنت. والوثائق نفسها تصف الوساطة بأنها السمة المميزة لهذه الدالة، لأنها «مدخل قابل للتخصيص بدرجة عالية، يرفع سقف ما تستطيع بنائه».

عملياً: مع create_agent تحدّد model= وtools= وsystem_prompt=، ثم تبني قدرةً طبقةً طبقة عبر middleware= بدل إعادة كتابة رسم بياني كامل في لانغ غراف (LangGraph).

إن كنت تتبع درساً قديماً، احذف LLMChain وConversationChain من ذهنك: النطاق langchain نُظِّف، وما تقدّمه الآن هو لبنات بناء الوكلاء. تخصيص أعمق للوكيل نفسه — تحكّم بالخطوة التالية، تقسيم فرعي، تحكّم في التنفيذ — يبقى عمل لانغ غراف.

ما الحد الأدنى الذي يعطيك وكيلاً يعمل؟

ثلاثة معاملات تكفي: model= وtools= وsystem_prompt=. لا رسم بياني، ولا تجميع، ولا إعداد بيئة تشغيل.

from langchain.agents import create_agent


def check_weather(location: str) -> str:
    """Return the weather forecast for the specified location."""
    return f"It's always sunny in {location}"


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[check_weather],
    system_prompt="You are a helpful assistant. Be concise and accurate.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "what is the weather in sf"}]}
)
print(result["messages"][-1].content)

صيغة معرّف النموذج "المزوّد:الموديل" تعني أنك تبدّل المزوّد بسطر واحد: openai:gpt-5.5 أو anthropic:claude-sonnet-5 أو google_genai:gemini-3.6-flash، والبنية نفسها تعمل.

ملاحظة صدق عن الوثائق: معرّفات الموديلات لا تتّسق بين صفحات لانغ تشين نفسها — فمثلاً صفحة الوكلاء تعرض anthropic:claude-sonnet-5 بينما أمثلة صفحات أخرى تعرض claude-sonnet-4-6. أعرض هنا ما ورد حرفياً في أمثلة الوثائق التي أقرأ منها الكود، لكن لا تعتمد على أي معرّف معرّف زمنياً؛ تحقّق من صفحة المزوّد قبل النشر.

الدالة تُعيد CompiledStateGraph مجمّعاً، جاهزاً للاستدعاء. ما تفعله بـthread_id وcheckpointer موضوع المقالة القادمة عن الذاكرة، أما هنا فندخل في الإنتاجية خطوة بخطوة.

ما هي AgentState وكيف تبني حالةً مخصّصة فوقها؟

كل وكيل يدير سياق تنفيذه عبر AgentState — وهو TypedDict، أي مُخطَّط مفاتيحه نصوص، يحمل سجلّ المحادثة وأي حقول تحتاجها أدواتك ووساطك. الحقل المدمج الوحيد هو messages: قائمة BaseMessage تحمل كامل سجلّ المحادثة للحالة الحالية، وهو append-only: الرسائل الجديدة تُضاف ولا تستبدل أبداً.

هذا يعني أنك لا تحتاج أن تعرف كيف تُحدَّث رسالة واحدة، ولا تكتب دالة دمج (reducer) خاصة بسجلّ الرسائل. كل ما تحتاجه هو وراثة AgentState وإضافة حقولك، ثم تمريرها بـstate_schema=:

from langchain.agents import AgentState, create_agent


class MyState(AgentState):
    user_id: str
    call_count: int


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    state_schema=MyState,
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "من أنا؟"}],
    "user_id": "user-123",
    "call_count": 0,
})

معلمة state_schema تقبل «مُخطَّط TypedDict يوسّع AgentState». استخدمه للحقول التي تحتاجها أداتك أو خطوطك على نطاق الوكيل كله.

لكن هناك خيار أنظف في كثير من الحالات: لو كانت حقولك تخصّ خطّافات بعينها، فاكتبها داخل state_schema الخاص بتلك الوساطة نفسها بدل توسيع حالة الوكيل كلها. الوثائق توصي بذلك صراحةً: إبقاء التوسيع في نطاق الخطّاف الذي يحتاجه يحفظ المشهد.

تحذير عملي: create_agent لا يدعم مخطّطات Pydantic للحالة

هذا من أكثر الأسئلة التي تصلني، وهو شائع جداً لأن منهج بناء الرسم البياني في لانغ غراف يقود الناس إليه. حين تبني رسماً بيانياً موجّهاً بنفسك في لانغ غراف، تُعرَّف الحالة عادةً بنموذج Pydantic لأجل التحقّق. create_agent لا تدعم ذلك: توثيق لانغ غراف نفسه ينصّ صراحةً أن «المصنع الأعلى مستوى create_agent في langchain لا يدعم مخطّطات Pydantic للحالة».

ما يعني عملياً: إذا كان قالبك الحالي يمرّر class State(BaseModel) إلى create_agent، فلن يعمل. استخدم TypedDict مثل MyState أعلاه. إن كنت تشتغل على رسم في لانغ غراف وتريد التحقّق من الحقول، فذاك إطار آخر وموضوع آخر — لا تحاول المزج بين المنهجيين.

كيف تحصل على مخرجات مهيكلة عبر response_format؟

response_format= تخبر الوكيل أن يُعيد بيانات ضمن مُخطَّط تتحقّق منه المكتبة نيابةً عنك، وتضعها في مفتاح structured_response داخل الحالة النهائية — بدل أن تحلّل نصاً حراً بنفسك.

المعامل يقبل أربعة أشياء:

  • ToolStrategy: يحقّق المُخطَّط عبر استدعاء الأدوات، ويعمل مع كل نموذج يدعم استدعاء الأدوات.
  • ProviderStrategy: يستعمل المخرجات المهيكلة الأصلية في مزوّد النموذج، وهي الأدقّ حين تكون متاحة.
  • صنف المُخطَّط مباشرة (نموذج Pydantic أو dataclass أو TypedDict): تختار لانغ تشين الاستراتيجية الأنسب تلقائياً حسب قدرات النموذج — ProviderStrategy لمزوّدات مثل OpenAI وAnthropic (Claude) وxAI (Grok)، وToolStrategy لغيرها.
  • None: لا مخرجات مهيكلة.

مثال عملي مع ToolStrategy:

from typing import Literal

from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy


class ProductReview(BaseModel):
    """Analysis of a product review."""
    rating: int | None = Field(description="The rating of the product", ge=1, le=5)
    sentiment: Literal["positive", "negative"] = Field(description="The sentiment of the review")
    key_points: list[str] = Field(
        description="The key points of the review. Lowercase, 1-3 words each."
    )


agent = create_agent(
    model="gpt-5.5",
    tools=[],
    response_format=ToolStrategy(ProductReview),
)

result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'",
    }]
})
print(result["structured_response"])
# ProductReview(rating=5, sentiment='positive', key_points=['fast shipping', 'expensive'])

فائدة التكلفة هنا حقيقية لا نظرية. المنهج القديم كان يطلب من النموذج صياغة الجواب، ثم يستدعي النموذج مرة ثانية للتحويل إلى JSON — نداء إضافي كامل في كل مرة. في 1.0، المخرجات المهيكلة تُولَّد داخل الحلقة الرئيسية نفسها بدل نداء إضافي للّغة، «ما يلغي التكلفة الإضافية من نداءات LLM» كما تقول الوثائق.

عملياً: كل استخراج حقل متكرر — تصنيف تذاكر، تلخيص ملاحظات عملاء، تحويل نصّ حر إلى مُخطَّط — يوفّر عندك نداءً كاملاً في كل طلب. على وكيل يخدم آلاف الطلبات يومياً، هذا فرق في الفاتورة لا في الكود فقط.

تحذيران عمليان من الوثائق: إذا مرّرت قاموس JSON Schema مباشرة إلى response_format فلن يُكتشف تلقائياً — لازم تغلّفه صراحةً في ProviderStrategy أو ToolStrategy. وثانياً: عند أخطاء التحقّق يعيد الوكيل محاولةً تلقائياً افتراضياً، وتحكم في ذلك بـhandle_errors (نصّ ثابت، أو نوع استثناء، أو دالة تُرجع الرسالة).

لماذا الطبقة الوسيطة هي محور create_agent؟

الوساطة ليست تزييناً ولا طبقة HTTP — هي الأداة الأساسية للتخصيص عند لانغ تشين. الفكرة كلها: «كل قطعة تعني اهتماماً واحداً، وتتداخل في الحلقة في التوقيت الصحيح، وتنسجم بحرية مع أي أخرى». خذ فقط ما يحتاجه حالتك، واترك الباقي.

ما تعطيك إياه عملياً:

  • تتبّع سلوك الوكيل عبر التسجيل والتحليلات والتنقيح.
  • تحويل الموجّهات وأدوات الاختيار وتنسيق المخرجات قبل وصولها للنموذج.
  • إضافة إعادة المحاولة والبدائل الاحتياطية ومنطق الإنهاء المبكر.
  • تطبيق حدود المعدّل وحواجز الحماية وكشف بيانات التعريف الشخصية.

وهي ليست بيئة تشغيل منفصلة: الخطّافات تعمل داخل الرسم المُجمَّع الذي تعيده create_agent. يمكنك وضع الوكيل كله — بوساطه — داخل رسم أكبر في لانغ غراف كعقدة أو رسم فرعي، وستستمر كل خطّافاته بالعمل.

الترتيب الذي تمرّر به الوسائط يمرّره لانغ تشين باحترام:

  • خطّافات before_*: من الأول إلى الأخير.
  • خطّافات after_*: من الأخير إلى الأول (معكوسة).
  • خطّافات wrap_*: متداخلة — الوسيط الأول في القائمة يلتفّ حول البقية كلّها.

ما الخطّافات الستة ومتى يعمل كل خطّاف؟

هذه أهم جدول في المقال. من يفهم هذه اللحظات الستّ يعرف أين يضع كل سطر تخصيص يكتبه.

الخطّاف متى يعمل داخل الدورة مثال استخدام
before_agent مرة واحدة قبل أن يبدأ الوكيل (لكل نداء) تهيئة تعليمات من ملفّ تعريف المستخدم قبل أول خطوة؛ رفض الطلب مبكراً إن كان غير مسموحاً
before_model قبل كل نداء للنموذج في الحلقة تصفية سجلّ الرسائل قبل الإرسال؛ حقن سياق محدّث؛ فحص سقف عدد الرسائل
wrap_model_call يلتفّ حول كل نداء نموذج — أنت تقرّر إن نُادي handler مرة، أو صفراً (اختصار)، أو أكثر من مرة (إعادة محاولة) اختيار النموذج حسب المستخدم أو عبء الطلب؛ بديل احتياطي عند فشل المزوّد؛ إعادة محاولة تلقائية؛ موجّه ديناميكي؛ تخزين مؤقّت
wrap_tool_call يلتفّ حول كل نداء أداة، بنفس منطق التفاف تسجيل تنفيذ الأداة ومخرجاتها؛ التقاط الاستثناءات وتحويلها إلى رسالة خطأ يفهمها النموذج؛ إعادة محاولة الأداة
after_model بعد كل ردّ نموذج (قبل تنفيذ أدواته) تسجيل الردّ؛ فحص محتوى محظور؛ قياس التكلفة؛ إيقاف مبكر بـjump_to
after_agent مرة واحدة بعد انتهاء الوكيل (لكل نداء) تدقيق نهائي؛ تسجيل مقاييس النداء؛ تنظيف موارد أو إرسال إشعار «اكتمل»

القاعدة الفاصلة بين المجموعتين: الخطّافات بأسلوب عقدة (before_agent، before_model، after_model، after_agent) تعمل بشكل تسلسلي عند نقطة محدّدة، وتُحدّث الحالة بإرجاع dict تُدمج عبر دوالّ الدمج (reducers) في الرسم. أما الخطّافات بأسلوب الالتفاف (wrap_model_call، wrap_tool_call) فتحيط بالنداء نفسه وتعطيك تحكّماً في التدفّق: هل تُنادي التنفيذ أم لا، وكم مرّة.

استعمل أسلوب العقدة للأمور التسلسلية — تسجيل، تحقّق، حساب. واستعمل أسلوب الالتفاف للتحكّم بالتدفّق — إعادة محاولة، بديل احتياطي، تخزين مؤقّت، اختصار.

متى تقفز خارج الدورة؟

كل خطّاف عقدة يستطيع إرجاع jump_to لقفزة مبكرة: 'end' يقفز إلى نهاية تنفيذ الوكيل أو إلى أول خطّاف after_agent، و'tools' يقفز إلى عقدة الأدوات، و'model' يقفز إلى عقدة النموذج أو أول خطّاف before_model. هكذا توقف حلقة لا تتوقّف دون أن تلمس رسمك البياني.

مثال سقف رسائل — لاحظ أنه يُعيد قفزة end قبل أن يدفع النموذج فاتورة زائدة:

from typing import Any

from langchain.agents.middleware import before_model, AgentState, hook_config
from langchain.messages import AIMessage
from langgraph.runtime import Runtime


@before_model(can_jump_to=["end"])
def check_message_limit(
    state: AgentState, runtime: Runtime
) -> dict[str, Any] | None:
    if len(state["messages"]) >= 50:
        return {
            "messages": [AIMessage("Conversation limit reached.")],
            "jump_to": "end",
        }
    return None

ما الوسيطات الجاهزة التي تستحق أن تبدأ بها؟

لا تبدأ من صفر. لانغ تشين يوفّر طبقة كاملة من الوساط الجاهزة للإنتاج، موزّعة على ست فئات — بيئة التنفيذ، وإدارة السياق، والتخطيط والتكليف، والتحمّل عند الأعطال، وحواجز الحماية، والتوجيه البشري. وأشهر ما فيها هو التالي:

تلخيص المحادثة (SummarizationMiddleware) — يضغط سجلّ المحادثة قبل أن تملأ نافذة السياق. تحدّد متى يلخّص (trigger) وكم يحتفظ (keep):

SummarizationMiddleware(
    model="gpt-5.4-mini",
    trigger=("tokens", 4000),
    keep=("messages", 20),
),

يمكنك أيضاً الضبط بـfraction (نسبة من حجم السياق)، أو بـmessages، أو بقائمة شروط (أيّها يتحقّق = OR) أو قاموس شروط (كلّها تتحقّق = AND).

تدخّل بشري في الحلقة (HumanInTheLoopMiddleware) — يوقف التنفيذ قبل أداة حسّسة وينتظر موافقة أو تعديلاً أو رفضاً. الشرط مهم: هذه الوساطة تتطلب checkpointer للحفاظ على الحالة عبر الانقطاع، فأنشئ الوكيل بـcheckpointer=InMemorySaver() إن جرّبت محلياً.

HumanInTheLoopMiddleware(
    interrupt_on={
        "your_send_email_tool": {
            "allowed_decisions": ["approve", "edit", "reject"],
        },
        "your_read_email_tool": False,
    }
),

كشف بيانات التعريف الشخصية (PIIMiddleware) — يكتشف بيانات التعريف في الرسائل ويطبّق استراتيجية: redact (استبدال بـ[REDACTED_…])، أو mask (إخفاء جزئي)، أو hash (بصمة ثابتة)، أو block (رفع استثناء). تتحكم أين يفحص: apply_to_input لرسائل المستخدم، وapply_to_output لردّ النموذج، وapply_to_tool_results لنتائج الأدوات.

وحدّ الاستدعاءات — لتفادي انفجار في الفاتورة: ModelCallLimitMiddleware وToolCallLimitMiddleware يأخذان وسيطين لكل مستوى: thread_limit يحدّ إجمالي النداءات على المحادثة كلها، وrun_limit يحدّها داخل النداء الواحد. مثال موثّق من الوثائق: thread_limit=10 مع run_limit=5 لعداد استدعاءات النموذج، وthread_limit=20 مع run_limit=10 لعداد استدعاءات الأدوات.. الحدّ على مستوى المحادثة يحتاج checkpointer.

التحمّل عند الأعطال — ModelRetryMiddleware وToolRetryMiddleware بإعادة محاولة تلقائية، وModelFallbackMiddleware ينتقل إلى نموذج بديل حين يفشل الأساسي، وToolErrorMiddleware يحوّل استثناء الأداة إلى رسالة خطأ يفهمها النموذج بدل أن يفشل النداء كله.

تنبيه إصدارات: بعض هذه الوساط مؤهَّلة لإصدارات حديثة من langchain. مثال حرفي من الوثائق: ToolErrorMiddleware يتطلب langchain>=1.3.14. ثبّت أحدث نسخة ولا تفترض أن اسم الوسيط موجود في إصدارك.

كيف تكتب وسيطتك الخاصة؟

الآن الجزء الأمتع. الوسيط إمّا دالة واحدة يعلوها مُزخرِف (decorator) مثل @wrap_model_call حين يكون الخطّاف واحداً، وإمّا صنف يورث AgentMiddleware إن أردت عدة خطّافات أو إعدادات وقت الإنشاء.

المثال الكامل هنا يختار نموذجاً حسب خبرة المستخدم، ويقرأ الخبرة من runtime.context — أي من سياق هذا النداء، لا من الحالة المحفوظة:

from collections.abc import Callable
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.agents.middleware import (
    AgentMiddleware,
    ModelRequest,
    ModelResponse,
)
from langchain.chat_models import init_chat_model


@dataclass
class RequestContext:
    experience_level: str  # "beginner" | "advanced"


ADVANCED_MODEL = init_chat_model("gpt-5.5")
BEGINNER_MODEL = init_chat_model("gpt-5-nano")


class ExperienceRoutingMiddleware(AgentMiddleware):
    """يختار نموذج أثقل للمتمرّسين وأخفّ للمبتدئين، داخل نفس الوكيل."""

    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        level = getattr(request.runtime.context, "experience_level", "advanced")
        model = BEGINNER_MODEL if level == "beginner" else ADVANCED_MODEL
        return handler(request.override(model=model))


agent = create_agent(
    model="gpt-5.5",
    tools=[check_weather],
    context_schema=RequestContext,
    middleware=[ExperienceRoutingMiddleware()],
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "ما هو thread_id؟"}]},
    context=RequestContext(experience_level="beginner"),
)

لماذا wrap_model_call لهذا بالذات؟ لأنه خطّاف الالتفاف: أنت تستقبل الطلب، تعدّله، ثم تنادي handler(request.override(model=model)) لتتابع الدورة. لو استخدمت before_model لتغيير النموذج لما استطعت — ذلك الخطّاف يعدّل الحالة والموجّه، لا الطلب المُمرَّر إلى النموذج نفسه. وبما أن الالتفافات متداخلة، يمكنك أن تحيط بهذا الوسيط وسيط إعادة محاولة أو بديل احتياطي آخر في القائمة نفسها، وينسجمان معاً بالترتيب الذي كتبته.

الأعراف عملية: اجعل كل وسيط مسؤولاً عن اهتمام واحد، تعامل مع الأخطاء بلطف حتى لا تُسقط الوكيل، اكتب تعريف حقول الحالة المخصّصة بوضوح، وثّق أي سند لسلوك الوسيط عند الحاجة.

ما الفرق بين runtime وcontext؟ ومتى يخطئ المبتدئ؟

الخلط بينهما خطأ شائع جداً وله أعراض صامتة: الوسيط يقرأ بيانات النداء الأخير في حين كان يقصد بيانات المحادثة، فلا تعرف من أين جاء الخلل.

thread_id context
ما هو مُعرّف يختار محادثة بعينها كائن بيانات ترسله مع الاستدعاء
أين يمرّ داخل config في configurable معامل مستقل إلى جانب config
عمره يبقى عبر الاستدعاءات ما دام checkpointer موجوداً هذا النداء فقط، ثم يُرمى
ما يصل منه نقاط الحفظ وسجلّ الرسائل الأدوات والوساط عبر runtime.context
غرضه استمرارية المحادثة بيانات هذا الطلب: معرّف المستخدم، مفاتيح واجهة برمجية، أعلام ميزة

بصياغة الوثائق: «thread_id يحدّد المحادثة (سجلّ الرسائل، نقاط الحفظ)، بينما context يحمل بيانات لكل نداء تقرأها أدواتك ووساطك وقت الاستدعاء. ويُمرَّر الاثنان معاً عادةً».

المعنى العملي: ضع في context ما يخصّ هذا الطلب فقط (من المستخدم؟ ما اللغة المفضّلة؟ ما المفتاح؟)، وفي الحالة ما يجب أن يتذكّره الوكيل بين النداءات. والوسيط يقرأ الأول عبر runtime.context، والثاني من state.

ولاحظ أن runtime هو ما يجمع الاثنين: العقدة تستقبل state وruntime، وruntime.context هو مدخلك إلى بيانات النداء، بينما الحالة هي ما يُحفظ.

ماذا ستبني في هذا المقال خطوة بخطوة؟

اجمع ما سبق في وكيل واحد حقيقي. ابدأ بالحد الأدنى، ثم أضف القدرات واحدة واحدة، كل واحدة وسط مستقل:

from langchain.agents import create_agent
from langchain.agents.middleware import (
    PIIMiddleware,
    SummarizationMiddleware,
    HumanInTheLoopMiddleware,
)
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.utils.uuid import uuid7


agent = create_agent(
    model="gpt-5.5",
    tools=[check_weather, read_email, send_email],
    checkpointer=InMemorySaver(),
    context_schema=RequestContext,
    middleware=[
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        SummarizationMiddleware(
            model="gpt-5.4-mini",
            trigger=("tokens", 4000),
            keep=("messages", 20),
        ),
        HumanInTheLoopMiddleware(interrupt_on={"send_email": True}),
        ExperienceRoutingMiddleware(),
    ],
)

config = {"configurable": {"thread_id": str(uuid7())}}

result = agent.invoke(
    {"messages": [{"role": "user", "content": "راسل فريق الدعم عن حالة طلبي"}]},
    config=config,
    context=RequestContext(experience_level="beginner"),
)

قائمة تحقق قبل أن تسلّم هذا الوكيل:

  1. الوكيل يُنشأ بـcreate_agent لا بـcreate_react_agent، وأي استيراد قديم محذوف.
  2. حالة الوكيل مبنية على AgentState بـTypedDict، لا على نموذج Pydantic.
  3. لكل خطّاف مهم سبب واضح للاختيار بين أسلوب العقدة وأسلوب الالتفاف.
  4. بيانات النداء في context، وبيانات المحادثة في الحالة عبر thread_id.
  5. response_format يعطي structured_response يتحقّق منه مُخطَّط، لا نصّاً تحلّله يدوياً.
  6. حدّ استدعاءات يحمي الفاتورة، ووساطة التدخّل البشري تحمي الأدوات الحسّاسة.

الخطوة التالية: أضف state_schema= مخصّصاً لكل وسيطة تحتاج عدّادات، أو انتقل إلى مقالة الذاكرة لتعرف كيف يبني checkpointer محادثة تصمد بعد إغلاق البرنامج.


تحتاج وكيلاً يعمل لفريقك؟

نصمّم ونبني أنظمة وكلاء للشركات والأفراد فوق لانغ تشين (LangChain) ولانغ غراف (LangGraph) — من إثبات المفهوم في أسبوع إلى نظام إنتاجي يُشغَّل يومياً. ابدأ بطلبك من صفحة التواصل.

المصادر

كل ادعاء في المقال مرتبط بمصدره. الروابط تفتح في نافذة جديدة.

  1. 01
    Prebuilt middleware — LangChain ↗

    docs.langchain.com

    ToolErrorMiddleware يتطلب langchain>=1.3.14

  2. 02
    Structured output — LangChain ↗

    docs.langchain.com

    ToolStrategy هي الاستراتيجية المختارة تلقائياً للنماذج التي لا تدعم المخرجات المهيكلة أصلاً في مزوّدها

  3. 03
    LangChain v1 release notes ↗

    docs.langchain.com

    create_agent هي الطريقة القياسية لبناء الوكلاء في LangChain 1.0 وقد حلت محل langgraph.prebuilt.create_react_agent

  4. 04
    Structured output — LangChain ↗

    docs.langchain.com

    response_format يقبل ToolStrategy أو ProviderStrategy أو صنف مُخطط مباشرة، وتوضع النتيجة في structured_response

  5. 05
    Agents — LangChain ↗

    docs.langchain.com

    thread_id ينسّق المحادثة بينما context يحمل بيانات لكل نداء يقرأها الوكلاء ووساطهم وقت الاستدعاء ويُمرّران معاً عادة

  6. 06
    Prebuilt middleware — LangChain ↗

    docs.langchain.com

    استراتيجيات كشف بيانات التعريف الشخصية هي block وredact وmask وhash مع تحكم في apply_to_input وapply_to_output

  7. 07
    Agents — LangChain ↗

    docs.langchain.com

    الحقل messages في AgentState مدمج وسجلّه append-only: تُضاف الرسائل ولا تستبدل

  8. 08
    LangChain v1 release notes ↗

    docs.langchain.com

    الخطّافات الستة هي before_agent وbefore_model وwrap_model_call وwrap_tool_call وafter_model وafter_agent

  9. 09
    LangChain v1 release notes ↗

    docs.langchain.com

    الوساطة هي السمة المميزة لـ create_agent لأنها تمنح مدخلاً قابلاً للتخصيص بدرجة عالية

  10. 10
    Agents — LangChain ↗

    docs.langchain.com

    الوكيل نموذج يستدعي أدوات في حلقة حتى تكتمل المهمة، وبنية تشغيل الوكيل هي كل ما يحيط بحلقة النموذج

  11. 11
    Custom middleware — LangChain ↗

    docs.langchain.com

    ترتيب التنفيذ يحترم قبل_* من الأول إلى الأخير وafter_* بالعكس وwrap_* متداخلة بحيث يلتفّ أول وسيط على البقية

  12. 12
    Custom middleware — LangChain ↗

    docs.langchain.com

    خطّافات العقدة تعمل تسلسلياً عند نقاط محدّدة وتُحدّث الحالة بإرجاع dict، بينما خطّافات الالتفاف تعطي تحكما في التدفق بعدد استدعاءات handler

  13. 13
    LangChain v1 release notes ↗

    docs.langchain.com

    في v1 تُولَّد المخرجات المهيكلة داخل الحلقة الرئيسية بدل نداء إضافي للغة، ما يلغي التكلفة الإضافية

  14. 14
    Structured output — LangChain ↗

    docs.langchain.com

    قاموسات JSON Schema يجب تغليفها صراحة في ProviderStrategy أو ToolStrategy فلا تُكتشف تلقائياً

  15. 15
    Custom middleware — LangChain ↗

    docs.langchain.com

    قفزات jump_to المتاحة هي end وtools وmodel للخروج المبكر من الحلقة

  16. 16
    LangGraph — Graph API (state, reducers, nodes) ↗

    docs.langchain.com

    مصنع create_agent الأعلى مستوى في langchain لا يدعم مخططات Pydantic للحالة

  17. 17
    LangChain v1 release notes ↗

    docs.langchain.com

    نطاق langchain نُظِّف ليُركّز على لبنات بناء الوكلاء الأساسية ونُقلت وظائفه القديمة إلى langchain-classic

  18. 18
    Prebuilt middleware — LangChain ↗

    docs.langchain.com

    وساطة تدخّل بشري في الحلقة تتطلب checkpointer للحفاظ على الحالة عبر الانقطاع

  19. 19
    Agents — LangChain ↗

    docs.langchain.com

    يمكن توسيع حالة الوكيل بوراثة AgentState وتمريرها إلى create_agent عبر state_schema

شروح أخرى

الكل ←