> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# LangChain: متى يفيدك إطار العمل ومتى يكفيك الكود
- URL: https://arabroot.io/articles/ما-هي-langchain-ومتى-تحتاجها/
- Published: 2026-10-10T13:30:00.000Z
- Updated: 2026-10-09T14:39:53.000Z
- Description: ما الذي تحله LangChain فعلاً في تطبيقات نماذج اللغة، وكيف تجربها على سيرفرك مع Ollama، ومتى يكفيك استدعاء مباشر دونها.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الذكاء الاصطناعي والبيانات, الاستضافة الذاتية

في مقال [من RAG إلى Agents](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/) بنينا مساعد الموظفين بكود Python عادي يرسل طلبات HTTP إلى Ollama، وكان ذلك مقصوداً حتى ترى كل خطوة بعينك. ولكن عندما يكبر التطبيق سوف تجد أنك تكتب نفس الكود مرة بعد مرة: تحويل الرسائل إلى الشكل الذي يفهمه كل مزود Provider، ووصف الأدوات Tools بمخطط JSON Schema، وقراءة المخرج المنظم Structured Output والتحقق منه، وتنفيذ الأداة التي طلبها النموذج ثم إرجاع نتيجتها إليه، وتقطيع المستندات وتضمينها Embedding والبحث فيها، ثم تكتشف أن تغيير المزود من Ollama على سيرفرك إلى خدمة سحابية يعني تعديل نصف هذا الكود. وهذا الكود الذي يربط الأجزاء ببعضها Glue Code هو بالضبط ما تقدمه مكتبة LangChain جاهزاً.

والسؤال الذي يهمك ليس «هل LangChain جيدة؟» وإنما «هل تحتاجها في مشروعك؟»، لأن كل طبقة جاهزة تعطيك وقتاً اليوم وتأخذ منك شيئاً غداً: حزماً إضافية، وتغييرات في الإصدارات، وأخطاء تمر عبر طبقات لم تكتبها. لذلك سوف نشرح ما تحله فعلاً، ثم نجربها على سيرفرك مع نموذج صغير في Ollama، ثم نقارنها بالبدائل وبالكود العادي بصراحة.

وسوف نناقش في هذا المقال ما يلي:

- المشكلة التي جاءت LangChain لحلها، ولماذا لا يكفي الحل البسيط عندما يكبر التطبيق.
- مكونات LangChain اليوم: الحزم Packages والإصدار 1.x، وcreate\_agent والطبقات الوسيطة Middleware، وماذا حدث للغة LCEL والسلاسل Chains.
- ما هو مفتوح المصدر برخصة MIT، وما هو خدمة تجارية مثل LangSmith.
- تجربة حقيقية على Docker: نموذج واحد بواجهة موحدة، ومخرج منظم، وأداة داخل Agent، وRAG صغير على مستندات عربية.
- متى لا تحتاج LangChain، والبدائل: LlamaIndex وPydantic AI وMicrosoft Agent Framework لمطوري C#، والكود العادي.
- أخطاء شائعة، وقائمة تحقق، ثم الخلاصة.

وإذا لم تكن المصطلحات مثل Tool Calling وRAG وAgent واضحة لك بعد، فابدأ بالمقالات التالية، لأننا لن نعيد شرحها هنا:

[![](https://arabroot.io/content/images/2026/10/ai-terms-cover-cover.webp)دليلمصطلحات الذكاء الاصطناعي ونماذج اللغة للمبتدئينشرح قصير مع مثال لكل مصطلح ستسمعه في نقاشات الذكاء الاصطناعي ونماذج اللغة، حتى تفهم ما يعرضه عليك أي فريق أو مزود.](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-%D9%88%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/rag-cover-cover.webp)دليلRAG للمبتدئين: نموذج لغة يجيب من مستنداتك على سيرفرككيف تجعل نموذج اللغة يجيب من مستندات مؤسستك ويذكر مصدره، دون أن يخرج أي مستند من سيرفرك.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%D9%83-%D9%85%D8%B9-rag-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/mcp-cover-cover.webp)دليلبروتوكول MCP للمبتدئين: كيف تربط النماذج بأدواتك بأمانكيف يربط بروتوكول MCP نماذج اللغة بأنظمتك وبياناتك، ولماذا الأمان أهم ما فيه، مع سيرفر MCP صغير تبنيه بنفسك.](https://arabroot.io/articles/%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-mcp-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/rag-mcp-agents-cover-cover.webp)دليلمن RAG إلى Agents: أي طريقة يحتاجها مساعد شركتك؟ما الذي تحله كل طريقة لبناء مساعد ذكي للموظفين وما الذي لا تحله، ولماذا تبدأ بالأرخص ولا تنتقل إلى الأغلى إلا عند الحاجة.](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/)[![](https://arabroot.io/content/images/2026/10/local-llm-hardware-cover-cover.webp)دليلتشغيل نماذج اللغة على جهازك: كم ذاكرة وأي GPU تحتاج؟كيف تحسب الذاكرة التي يحتاجها النموذج، وماذا تتوقع من جهازك الحالي قبل أن تشتري بطاقة رسومية GPU.](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%84%D8%BA%D8%A9-%D8%B9%D9%84%D9%89-%D8%AC%D9%87%D8%A7%D8%B2%D9%83-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/langgraph-cover-cover.webp)دليلLangGraph: بناء Agent يحفظ حالته ويطلب الموافقةكيف تبني Agent بخطوات واضحة في LangGraph، يحفظ حالته في قاعدة بيانات ويكمل بعد التوقف، ولا ينفذ أي فعل قبل موافقتك.](https://arabroot.io/articles/langgraph-%D8%A8%D9%86%D8%A7%D8%A1-agent-%D9%8A%D8%AD%D9%81%D8%B8-%D8%AD%D8%A7%D9%84%D8%AA%D9%87/)

## المشكلة التي جاءت LangChain لحلها

الحل البسيط أن تستدعي مكتبة المزود مباشرة SDK، فتكتب لـ Ollama طلباً بشكل، ولـ OpenAI طلباً بشكل آخر، ولـ Anthropic شكلاً ثالثاً، وهذا يعمل تماماً في تطبيق صغير بنموذج واحد، بل هو الخيار الأفضل في أغلب الحالات كما سوف نرى في قسم [متى لا تحتاجها](#when-not). ولكنه يبدأ بالتعقيد عندما تجتمع ثلاثة أشياء:

- أكثر من مزود: نموذج محلي للبيانات الحساسة ونموذج سحابي للمهام العامة، أو مزود احتياطي Fallback إذا توقف الأساسي، فكل مزود يصف الأدوات ويرجع استدعاءاتها بشكل مختلف قليلاً.
- أكثر من أداة ومخرج منظم: فكل أداة تحتاج وصفاً بمخطط، وكل استدعاء يحتاج تنفيذاً وإرجاعاً للنتيجة، وكل مخرج JSON يحتاج تحققاً Validation.
- أجزاء متكررة حول النموذج: البحث في المستندات لـ RAG، وتلخيص المحادثة الطويلة، وحد لعدد الخطوات، وتسجيل كل استدعاء.

وهنا تقدم LangChain واجهات موحدة Standard Interfaces: واجهة واحدة لنماذج المحادثة Chat Models، وواجهة واحدة للأدوات، وواجهة واحدة للمخرج المنظم، وواجهة واحدة لنماذج التضمين Embeddings وقواعد البيانات الشعاعية Vector Stores والمسترجع Retriever، ثم مئات حزم الربط Integrations التي تطبق هذه الواجهات لكل مزود. والصورة التالية تبين الفكرة:

![على اليمين كود التطبيق يستدعي واجهة واحدة هي ChatModel بدوال invoke وstream وbind_tools وwith_structured_output، وتحتها ثلاث حزم مزودين: langchain-ollama تتصل بسيرفر Ollama على شبكتك، وlangchain-openai وlangchain-anthropic تتصلان بخدمات سحابية، وعلى اليسار الحل البسيط حيث يكتب التطبيق كود ربط مختلفاً لكل مزود](https://arabroot.io/content/images/2026/10/langchain-01-one-interface.webp)

الكود يكلم واجهة واحدة، وحزمة المزود تترجمها إلى طلبه الخاص

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

## مكونات LangChain اليوم: الحزم Packages والإصدارات

صدر الإصدار 1.0 من LangChain ومن LangGraph في [17 أكتوبر 2025](https://pypi.org/project/langchain/?ref=arabroot.io)، وكان تغييراً كبيراً في شكل المكتبة، فبحسب [صفحة ما الجديد في LangChain v1](https://docs.langchain.com/oss/python/releases/langchain-v1?ref=arabroot.io) أصبحت الدالة create\_agent هي الطريقة القياسية لبناء الوكيل Agent بدلاً من create\_react\_agent القديمة في LangGraph، وأصبح للرسائل خاصية موحدة اسمها content\_blocks، وتقلصت الحزمة langchain إلى ما يلزم لبناء الوكلاء، وانتقل كل ما هو قديم إلى حزمة منفصلة اسمها langchain-classic. والصورة التالية تبين الحزم وعلاقتها ببعضها كما هي اليوم:

![طبقات الحزم: في الأسفل langchain-core وفيها الواجهات Runnable والرسائل والأدوات والقوالب، وفوقها langgraph كبيئة تشغيل للوكيل، وفوقها langchain وفيها create_agent والطبقات الوسيطة وinit_chat_model، وبجانبها حزم المزودين مثل langchain-ollama وlangchain-openai، وعلى الجانب langchain-classic للسلاسل والمسترجعات القديمة، وكلها برخصة MIT، وفي صندوق منفصل بخط متقطع LangSmith كخدمة تجارية اختيارية للتتبع والتقييم والنشر](https://arabroot.io/content/images/2026/10/langchain-02-packages.webp)

كل الحزم مفتوحة المصدر برخصة MIT، وLangSmith خدمة منفصلة اختيارية

- الحزمة langchain-core: الأساس الذي تعتمد عليه كل الحزم الأخرى، وفيها الواجهات المجردة Abstractions للنماذج والرسائل والأدوات والقوالب Prompts والمستندات، وواجهة Runnable التي سوف نتكلم عنها في قسم [LCEL](#lcel)، وآخر إصدار عند كتابة المقال [1.6.9](https://pypi.org/project/langchain-core/?ref=arabroot.io).
- الحزمة langchain: وفيها create\_agent والطبقات الوسيطة Middleware والدالة init\_chat\_model التي تنشئ نموذجاً من نص مثل "ollama:qwen2.5:1.5b"، وآخر إصدار [1.4.4](https://pypi.org/project/langchain/?ref=arabroot.io)، وتعتمد على langgraph لأن الوكيل نفسه يعمل كرسم بياني Graph في LangGraph.
- حزم المزودين Provider Packages: مثل langchain-ollama وlangchain-openai وlangchain-anthropic، وكل منها حزمة مستقلة تثبتها وحدها كما في [صفحة التثبيت](https://docs.langchain.com/oss/python/langchain/install?ref=arabroot.io)، والسبب أن كل مزود يتغير بسرعته الخاصة.
- الحزمة langchain-classic: السلاسل Chains القديمة، والمسترجعات Retrievers من الوحدة langchain.retrievers القديمة، وواجهة الفهرسة Indexing API، فإذا وجدت مثالاً على الإنترنت يستورد from langchain.chains فهو مكتوب للإصدار 0.3 ويحتاج هذه الحزمة.
- الحزمة langchain-community: ربط مع مئات الأدوات التي يكتبها المجتمع Community، وجودتها تختلف من ربط إلى آخر.

أما سياسة الإصدارات فبحسب [صفحة Versioning](https://docs.langchain.com/oss/python/versioning?ref=arabroot.io) تتبع الحزم الإصدار الدلالي Semantic Versioning، فلا تغيير يكسر الكود Breaking Change إلا في إصدار رئيسي مثل 2.0، والإصدار 1.0 من LangChain وLangGraph مصنف دعماً طويلاً LTS يبقى في التطوير النشط حتى صدور 2.0، ثم يحصل على إصلاحات الأمان سنة على الأقل، أما الإصدار القديم 0.3 فهو في وضع الصيانة Maintenance حتى ديسمبر 2026\. وتذكر [سياسة الإصدار](https://docs.langchain.com/oss/python/release-policy?ref=arabroot.io) أن الإصدارات الصغيرة Patch قد تصدر عدة مرات في الأسبوع، وهذا ما رأيناه فعلاً، فقد صدرت langchain 1.4.4 وlangchain-core 1.6.9 قبل يوم واحد من كتابة هذا القسم، لذلك ثبت الإصدار في ملف المتطلبات دائماً.

## الدالة create\_agent والطبقات الوسيطة Middleware

الوكيل Agent في LangChain هو الحلقة التي شرحناها في [سلم الطرق](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#agent): يستدعي النموذج، فإذا طلب أداة نفذها ورجع بالنتيجة، ويكرر ذلك حتى لا يطلب النموذج أي أداة. وتصف [الصفحة الرئيسية للتوثيق](https://docs.langchain.com/oss/python/langchain/overview?ref=arabroot.io) المعادلة هكذا: الوكيل يساوي النموذج مع الحزام Harness، والحزام هو كل ما حول النموذج: التعليمات والأدوات والطبقات الوسيطة. والمثال التالي هو الشكل الأساسي:

```python
from langchain.agents import create_agent

agent = create_agent(
    model="ollama:qwen2.5:1.5b",
    tools=[get_ticket_status],
    system_prompt="أنت مساعد الدعم الفني.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "ما حالة التذكرة IT-2041؟"}]})
```

والطبقات الوسيطة Middleware هي أهم إضافة في الإصدار 1، وهي دوال تعمل في نقاط محددة من الحلقة، والصورة التالية تبين هذه النقاط:

![حلقة الوكيل: before_agent مرة في البداية، ثم before_model قبل كل استدعاء للنموذج، وwrap_model_call حول الاستدعاء، وafter_model بعده، ثم إذا طلب النموذج أداة تمر على wrap_tool_call حول تنفيذ الأداة وترجع النتيجة إلى النموذج، وإذا لم يطلب أداة تخرج الحلقة إلى after_agent، وبجانبها أمثلة للطبقات الجاهزة: حد عدد الاستدعاءات، وموافقة إنسان، وتلخيص المحادثة، وإخفاء البيانات الشخصية](https://arabroot.io/content/images/2026/10/langchain-03-middleware.webp)

كل طبقة وسيطة تعمل قبل خطوة أو بعدها أو حولها، دون أن تغير كود الحلقة نفسها

وتأتي المكتبة بطبقات جاهزة، منها كما في [قائمة الطبقات الجاهزة](https://docs.langchain.com/oss/python/langchain/middleware/built-in?ref=arabroot.io): حد عدد استدعاءات النموذج ModelCallLimitMiddleware وحد عدد استدعاءات الأدوات، وإعادة المحاولة Retry والنموذج البديل Fallback، وتلخيص المحادثة الطويلة SummarizationMiddleware، وكشف البيانات الشخصية PII وإخفاؤها، وموافقة الإنسان HumanInTheLoopMiddleware قبل أدوات معينة. وقد يتساءل البعض: لماذا لا أكتب هذا بنفسي داخل الحلقة؟ والإجابة أنك تستطيع ذلك في حلقة من عشرين سطراً، ولكن عندما تجتمع خمس حاجات مثل الحد والتلخيص والموافقة وإخفاء البيانات والتسجيل في حلقة واحدة تصبح الحلقة صعبة القراءة والاختبار، والطبقة الوسيطة تفصل كل حاجة في مكانها.

ولاحظ أن الوكيل الذي ترجعه create\_agent هو في الحقيقة رسم بياني مبني في LangGraph، فبحسب [توثيق الطبقات الوسيطة](https://docs.langchain.com/oss/python/langchain/middleware/overview?ref=arabroot.io) لا توجد بيئة تشغيل منفصلة للطبقات، وإنما تعمل داخل الرسم البياني الذي ترجعه create\_agent، لذلك تحصل منه على الحفظ Persistence والبث Streaming وموافقة الإنسان دون أن تكتب رسماً بيانياً بنفسك. وعندما تحتاج خطوات لا تناسب شكل الحلقة، مثل تصنيف الطلب ثم توجيهه إلى أحد وكيلين، أو خطوات ثابتة يقررها الكود بين خطوات النموذج، فهنا تنزل إلى LangGraph مباشرة، وهو موضوع مقال [LangGraph: بناء Agent يحفظ حالته ويطلب الموافقة](https://arabroot.io/articles/langgraph-%D8%A8%D9%86%D8%A7%D8%A1-agent-%D9%8A%D8%AD%D9%81%D8%B8-%D8%AD%D8%A7%D9%84%D8%AA%D9%87/).

## ماذا حدث للغة LCEL والسلاسل Chains؟

إذا قرأت عن LangChain قبل 2025 فقد رأيت السلاسل Chains مثل LLMChain وRetrievalQA، ثم رأيت لغة LCEL (اختصار LangChain Expression Language) التي تربط الأجزاء بالعلامة | مثل أوامر Linux: prompt | model | parser. والوضع اليوم كما يلي:

- السلاسل القديمة انتقلت إلى langchain-classic كما ذكرنا، ولا يبنى عليها كود جديد.
- واجهة Runnable التي تقوم عليها LCEL ما زالت في langchain-core، فكل نموذج وقالب ومسترجع هو Runnable له الدوال invoke وstream وbatch، وتجد في [توثيق النماذج](https://docs.langchain.com/oss/python/langchain/models?ref=arabroot.io) الإعداد RunnableConfig الذي يمرر لكل استدعاء، والعلامة | ما زالت تعمل كما سوف ترى في تجربة RAG.
- التوثيق الحالي لا يبني الأمثلة على LCEL، وإنما على create\_agent للوكلاء وعلى LangGraph للخطوات المخصصة، لذلك استخدم LCEL للسلاسل القصيرة الثابتة مثل «قالب ثم نموذج ثم تحويل المخرج إلى نص»، ولا تقم ببناء منطق فيه شروط وحلقات بالعلامة |، والسبب أن الرسم البياني في LangGraph يعبر عن ذلك بوضوح أكبر ويحفظ الحالة بين الخطوات.

## ما هو مفتوح المصدر وما هو خدمة تجارية؟

الشركة التي تطور LangChain اسمها LangChain Inc، وهي تبني نموذج عملها على المكتبات المفتوحة من جهة، وعلى منصة تجارية من جهة أخرى، لذلك من المهم أن تعرف أين ينتهي هذا وأين يبدأ ذاك:

| المكون                                                                                               | الرخصة      | ماذا يفعل                                                     | هل تحتاجه؟                                                      |
| ---------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------- | --------------------------------------------------------------- |
| [langchain وlangchain-core وحزم المزودين](https://github.com/langchain-ai/langchain?ref=arabroot.io) | MIT         | المكتبة نفسها                                                 | نعم إذا اخترت LangChain                                         |
| [langgraph](https://github.com/langchain-ai/langgraph?ref=arabroot.io)                               | MIT         | بيئة تشغيل الوكيل والرسوم البيانية                            | تثبت تلقائياً مع langchain                                      |
| [langsmith (حزمة الكلاينت)](https://github.com/langchain-ai/langsmith-sdk?ref=arabroot.io)           | MIT         | إرسال التتبع Tracing إلى منصة LangSmith                       | تثبت تلقائياً مع langchain-core، ولا ترسل شيئاً حتى تفعل التتبع |
| منصة LangSmith                                                                                       | خدمة تجارية | التتبع والتقييم Evaluation وإدارة التعليمات والنشر Deployment | اختيارية                                                        |

وفي [صفحة الأسعار](https://www.langchain.com/pricing?ref=arabroot.io) خطة مجانية محدودة وخطط مدفوعة، أما تشغيل LangSmith على سيرفرك فهو بحسب [توثيق Self-hosted LangSmith](https://docs.langchain.com/langsmith/self-hosted?ref=arabroot.io) إضافة على خطة Enterprise للشركات الكبيرة ويحتاج مفتاح ترخيص License Key. لذلك إذا كان سبب اختيارك لنموذج محلي ألا تخرج بيانات مؤسستك، فتذكر أن التتبع يرسل التعليمات والمخرجات كاملة إلى المنصة، ولا تقم بتفعيله إلا بعد أن تقرر أين تذهب هذه البيانات. والتتبع لا يعمل إلا إذا ضبطت المتغير LANGSMITH\_TRACING مع مفتاح API كما في [توثيق LangGraph](https://docs.langchain.com/oss/python/langgraph/overview?ref=arabroot.io)، وفي تجربتنا لم نضبطهما. وإذا احتجت التتبع على سيرفرك فهناك أدوات مفتوحة تتبع تطبيقات LangChain، ولكن هذا موضوع مقال قادم بإذن الله.

## تجربة حقيقية: LangChain مع Ollama على سيرفرك

سوف نشغل كل شيء داخل Docker: نموذج qwen2.5:1.5b في Ollama وهو نموذج حجمه قرابة 1 GB يدعم استدعاء الأدوات بحسب [صفحته في مكتبة Ollama](https://ollama.com/library/qwen2.5?ref=arabroot.io)، ونموذج التضمين qwen3-embedding:0.6b [متعدد اللغات](https://ollama.com/library/qwen3-embedding?ref=arabroot.io)، ثم Container لـ Python فيه إصدارات مثبتة من LangChain. والمخرجات التالية سلوك نموذج صغير على معالج دون بطاقة رسومية، والهدف أن ترى ما تقدمه المكتبة وما لا تقدمه، وليس أن نقيس جودة النموذج.

### تجهيز بيئة التجربة

نبدأ بشبكة Docker خاصة بالتجربة، وVolume لملفات النموذج، وContainer لـ Ollama دون أي منفذ منشور على جهازك:

```bash
docker network create lab-lc-net
docker volume create lab-lc-ollama
docker run -d --name lab-lc-ollama --network lab-lc-net \
  -v lab-lc-ollama:/root/.ollama ollama/ollama:0.40.2
docker exec lab-lc-ollama ollama pull qwen2.5:1.5b
docker exec lab-lc-ollama ollama pull qwen3-embedding:0.6b
```

لاحظ أننا لم نستخدم الخيار -p، والسبب أن واجهة Ollama لا تطلب أي مصادقة، فمن يصل إلى منفذها يستطيع استخدام النموذج، أما برامجنا فسوف تعمل على نفس الشبكة lab-lc-net وتصل إلى Ollama باسمه http://lab-lc-ollama:11434، كما شرحنا في [شبكات Docker](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/). ثم نبني Docker Image لـ Python بإصدارات مثبتة، في مجلد فيه الملف Dockerfile التالي:

```dockerfile
FROM python:3.13.16-slim
RUN pip install --no-cache-dir \
    langchain==1.4.4 \
    langchain-core==1.6.9 \
    langchain-ollama==1.1.0 \
    langgraph==1.2.14 \
    langgraph-checkpoint-sqlite==3.1.1 \
    numpy==2.5.3
ENV PYTHONIOENCODING=utf-8 PYTHONUNBUFFERED=1
WORKDIR /lab
```

```bash
docker build -t lab-lc-py:1 .
```

والحزمة numpy أضفناها بعد خطأ سوف تراه في قسم RAG. وكل البرامج التالية في مجلد واحد، وتشغلها بالأمر التالي مع تغيير اسم الملف:

```bash
docker run --rm --name lab-lc-run --network lab-lc-net -v "$PWD:/lab" lab-lc-py:1 python models.py
```

وعند تثبيت هذه الحزم الست ثبت pip معها إصدارات محددة من الحزم التي تعتمد عليها، وأهمها كما يلي:

```text
langchain==1.4.4
langchain-core==1.6.9
langchain-ollama==1.1.0
langchain-protocol==0.0.19
langgraph==1.2.14
langgraph-checkpoint==4.2.0
langgraph-checkpoint-sqlite==3.1.1
langgraph-prebuilt==1.1.0
langgraph-sdk==0.4.6
langsmith==0.14.5
ollama==0.6.3
pydantic==2.14.0
```

لاحظ أن langgraph وlangsmith ثبتتا رغم أننا لم نطلبهما، والسبب أن langchain تعتمد على langgraph، وlangchain-core تعتمد على langsmith. والملف common.py التالي تستخدمه كل البرامج:

```python
from langchain_ollama import ChatOllama, OllamaEmbeddings

OLLAMA = "http://lab-lc-ollama:11434"   # اسم Container على شبكة التجربة، دون أي منفذ على جهازك
model = ChatOllama(model="qwen2.5:1.5b", base_url=OLLAMA, temperature=0, seed=1)
embeddings = OllamaEmbeddings(model="qwen3-embedding:0.6b", base_url=OLLAMA)
```

### الخطوة 1: نموذج واحد بواجهة موحدة

البرنامج models.py ينشئ النموذج من نص فيه اسم المزود واسم النموذج، وهذه هي الواجهة الموحدة التي تكلمنا عنها:

```python
from langchain.chat_models import init_chat_model

# نفس السطر يعمل مع أي مزود: غير "ollama:" إلى "openai:" أو "anthropic:" بعد تثبيت حزمة المزود
model = init_chat_model("ollama:qwen2.5:1.5b", base_url="http://lab-lc-ollama:11434", temperature=0, seed=1)
print(type(model).__name__)

reply = model.invoke([
    ("system", "أجب في جملة واحدة بالعربية."),
    ("user", "ما الفرق بين Docker Image وContainer؟"),
])
print(reply.content)
print(reply.usage_metadata)
```

والمخرج سوف يكون كما يلي:

```text
ChatOllama
Docker Image هي الصورة التي تشكل Container، وهي تضمن المكونات الأساسية التي تجعل Container يعمل. Container هو الترجمة أو التكليف لImage، وهو يحتوي على المكونات الأساسية التي تجعله يعمل بشكل مستقل.
{'input_tokens': 33, 'output_tokens': 57, 'total_tokens': 90}
```

في المخرج أعلاه لاحظ التالي:

- الدالة init\_chat\_model قرأت البادئة ollama: فأنشأت ChatOllama من الحزمة langchain-ollama، ولو كتبت "openai:..." لأنشأت ChatOpenAI، وبقية الكود لا يتغير.
- الخاصية usage\_metadata جاءت بنفس الأسماء input\_tokens وoutput\_tokens التي تجدها مع أي مزود آخر، وهذا مفيد عندما تحسب كلفة كل طلب.
- الإجابة نفسها ضعيفة ولم تلتزم بجملة واحدة، وهذا من النموذج الصغير وليس من LangChain، فالمكتبة توحد الطريقة ولا تحسن الجودة.

### الخطوة 2: المخرج المنظم Structured Output بكلاس Pydantic

نريد أن نحول رسالة الموظف إلى تذكرة لها نوع وأولوية وملخص، والبرنامج structured.py يصف التذكرة بكلاس Pydantic، ويطلب من النموذج أن يملأها عبر with\_structured\_output، وهي في ChatOllama تستخدم [المخرج المنظم في Ollama](https://docs.ollama.com/capabilities/structured-outputs?ref=arabroot.io) افتراضياً:

```python
import sys
from typing import Literal
from pydantic import BaseModel, Field
from common import model

class Ticket(BaseModel):
    category: Literal["network", "hardware", "access", "other"] = Field(description="نوع المشكلة")
    priority: Literal["low", "medium", "high"] = Field(description="الأولوية")
    summary: str = Field(description="ملخص المشكلة في جملة قصيرة بالعربية")

extract = model.with_structured_output(Ticket)   # method="json_schema" هو الافتراضي في ChatOllama

RULES = ("صنف رسالة الموظف. category: network لمشكلات الشبكة وVPN، وaccess لطلبات الصلاحيات والمجلدات، "
         "وhardware للأجهزة. priority: high إذا كان العمل متوقفاً الآن، وإلا low أو medium. "
         "summary: جملة قصيرة باللغة العربية فقط.")
messages = [
    "الـ VPN لا يعمل من البيت منذ الصباح ولا أستطيع الدخول إلى نظام الرواتب، وعندي اجتماع مع الإدارة بعد ساعة",
    "أحتاج صلاحية على مجلد التصاميم المشترك لأن المشروع الجديد يبدأ الأسبوع القادم",
]
with_rules = len(sys.argv) > 1 and sys.argv[1] == "rules"
for text in messages:
    ticket = extract.invoke([("system", RULES), ("user", text)] if with_rules else text)
    print(type(ticket).__name__, ticket.model_dump())
```

نشغله مرة دون تعليمات، ومرة مع التعليمات RULES:

```bash
docker run --rm --name lab-lc-run --network lab-lc-net -v "$PWD:/lab" lab-lc-py:1 python structured.py
docker run --rm --name lab-lc-run --network lab-lc-net -v "$PWD:/lab" lab-lc-py:1 python structured.py rules
```

والمخرج سوف يكون كما يلي:

```text
Ticket {'category': 'network', 'priority': 'high', 'summary': 'VPN连接问题导致无法从家中访问系统，影响了工作进度。建议立即联系IT部门进行故障排查和修复。同时，尽快安排与部门经理的会议，以便及时解决VPN问题并避免进一步的延误。'}
Ticket {'category': 'network', 'priority': 'high', 'summary': '需要为共同设计的项目目录申请权限，以便在项目开始之前可以进行必要的准备工作。'}
Ticket {'category': 'network', 'priority': 'high', 'summary': 'VPN ونظام الرواتب غير يعملان الآن. يرجى الاتصال بالإدارة في غضون ساعة.'}
Ticket {'category': 'access', 'priority': 'high', 'summary': 'Need access to the shared design folder for the upcoming project that starts next week.'}
```

في المخرج أعلاه لاحظ التالي:

- في كل المرات رجع كائن من الكلاس Ticket وليس نصاً، والقيم category وpriority من القيم المسموحة فقط، لأن المخطط يرسل إلى Ollama فيقيد التوليد نفسه، وتتحقق Pydantic من النتيجة بعد ذلك.
- دون تعليمات كتب النموذج الملخص بالصينية في الرسالتين رغم أن وصف الحقل يقول «بالعربية»، وصنف طلب الصلاحية على أنه مشكلة شبكة network، أي أن المخطط يضمن الشكل ولا يضمن صحة القيم، وهذا ما ذكرناه في [درجة المخرج المنظم](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#structured).
- مع التعليمات صار التصنيف الثاني access وهو الصحيح، وصار الملخص الأول بالعربية، ولكن الملخص الثاني جاء بالإنجليزية، والأولوية الثانية high رغم أن المشروع يبدأ الأسبوع القادم. لذلك تحقق من القيم المهمة في الكود، أو استخدم نموذجاً أكبر لهذه المهمة.
- ولاحظ أننا شغلنا البرنامج عدة مرات بنفس الإعدادات temperature=0 وseed=1 فاختلفت صياغة الملخص بين مرة وأخرى، لذلك لا تفترض أن الإعدادين يجعلان المخرج ثابتاً تماماً، وقس دائماً على أكثر من تشغيل.

### الخطوة 3: أداة Tool داخل Agent

الأداة في LangChain دالة Python عادية عليها الكلمة @tool، والمكتبة تبني وصفها من اسم الدالة ونص التوثيق Docstring وأنواع المعاملات. والبرنامج tools.py يعرف أداة للقراءة فقط، ثم يستخدمها بطريقتين: مرة مع bind\_tools وحدها، ومرة داخل create\_agent مع طبقة وسيطة تحد عدد استدعاءات النموذج:

```python
import json
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from common import model

@tool
def get_ticket_status(ticket_id: str) -> str:
    """Return the current status of a helpdesk ticket, for example IT-2041."""
    tickets = {"IT-2041": {"status": "in_progress", "team": "network-team", "updated": "2026-10-09 14:10"}}
    return json.dumps(tickets.get(ticket_id, {"error": "ticket not found"}))

# 1) استدعاء الأداة وحده: النموذج يطلب الأداة، ولا ينفذها أحد
call = model.bind_tools([get_ticket_status]).invoke("What is the status of ticket IT-2041?")
print("tool_calls:", call.tool_calls)

# 2) نفس الأداة داخل create_agent: الحلقة تنفذ الأداة وترجع النتيجة للنموذج حتى ينتهي
agent = create_agent(
    model=model,
    tools=[get_ticket_status],
    system_prompt="أنت مساعد الدعم الفني. استخدم الأداة لمعرفة حالة التذكرة، ثم أجب بالعربية في جملة واحدة.",
    middleware=[ModelCallLimitMiddleware(run_limit=4, exit_behavior="end")],
)
for question in ["What is the status of ticket IT-2041?", "ما حالة التذكرة IT-2041؟"]:
    result = agent.invoke({"messages": [{"role": "user", "content": question}]})
    print("----", question)
    for m in result["messages"]:
        print(f"{m.type:>5}:", m.tool_calls if getattr(m, "tool_calls", None) else m.content)
```

والمخرج سوف يكون كما يلي:

```text
tool_calls: [{'name': 'get_ticket_status', 'args': {'ticket_id': 'IT-2041'}, 'id': '6bbcbeca-ae37-4ec1-be41-ae0bed096bcf', 'type': 'tool_call'}]
---- What is the status of ticket IT-2041?
human: What is the status of ticket IT-2041?
   ai: [{'name': 'get_ticket_status', 'args': {'ticket_id': 'IT-2041'}, 'id': 'ff882779-78a3-47e9-8ad2-5ffa47866ae5', 'type': 'tool_call'}]
 tool: {"status": "in_progress", "team": "network-team", "updated": "2026-10-09 14:10"}
   ai: The ticket IT-2041 is currently in progress and is being handled by the network-team. It was last updated on 2026-10-09 14:10.
---- ما حالة التذكرة IT-2041؟
human: ما حالة التذكرة IT-2041؟
   ai: سيقوم المساعد بتذليل التذكرة وتحديد حالة التذكرة.
```

في المخرج أعلاه لاحظ التالي:

- مع bind\_tools وحدها طلب النموذج الأداة بالمعامل الصحيح، ولكن لم ينفذها أحد، فهذا كل ما يفعله استدعاء الأدوات Tool Calling: النموذج يطلب، وكودك ينفذ.
- داخل create\_agent نفذت الحلقة الأداة، ووضعت نتيجتها في رسالة من نوع tool، ثم استدعت النموذج مرة ثانية فكتب الإجابة، أي أن الحلقة التي كتبناها بأيدينا في [تجربة الوكيل](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#lab-agent) أصبحت سطراً واحداً.
- عندما جاء السؤال بالعربية لم يطلب النموذج الأداة أصلاً، وكتب جملة لا معنى لها عن حالة التذكرة، وهذا نفس ما رأيناه مع نفس النموذج دون أي مكتبة، فالمشكلة في النموذج الصغير مع الأسئلة العربية وليست في LangChain. والحل نموذج أكبر، أو أن يقرر الكود استدعاء الأداة إذا وجد رقم تذكرة في السؤال، كما في [سير العمل Workflow](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#workflow-vs-agent).
- الطبقة ModelCallLimitMiddleware لم تتدخل هنا لأن الحلقة انتهت في استدعاءين أو أقل، ولكنها تضمن ألا يستمر الوكيل في حلقة مفتوحة إذا أخطأ النموذج.

### الخطوة 4: RAG صغير على مستندات عربية

البرنامج rag.py يضع أربع فقرات من سياسات شركة في قاعدة شعاعية في الذاكرة InMemoryVectorStore، ويبحث فيها بنموذج التضمين، ثم يبني سلسلة LCEL تجمع المسترجع Retriever مع القالب والنموذج. ولم نستخدم قاعدة بيانات حقيقية لأن ذلك موضوع [مقال RAG](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%D9%83-%D9%85%D8%B9-rag-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، والهدف هنا أن ترى قطع LangChain فقط:

```python
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from common import model, embeddings

DOCS = [
    Document("يحق للموظف العمل عن بعد يومين في الأسبوع بحد أقصى بعد إكمال ستة أشهر في الشركة، وبموافقة المدير المباشر.", metadata={"source": "HR-07"}),
    Document("لا يسمح بالاتصال بأنظمة الشركة من خارج المكتب إلا عبر VPN الشركة ومن جهاز الشركة.", metadata={"source": "IT-02"}),
    Document("تصرف بدلات السفر خلال عشرة أيام عمل من تقديم الفواتير في نظام المصروفات.", metadata={"source": "FIN-11"}),
    Document("تغير كلمة المرور كل تسعين يوماً، ويجب ألا تقل عن اثني عشر حرفاً.", metadata={"source": "IT-05"}),
]
store = InMemoryVectorStore.from_documents(DOCS, embeddings)

QUESTIONS = ["كم يوماً في الأسبوع أستطيع العمل من البيت؟", "كم يوماً من الإجازة المرضية يحق لي؟"]
for q in QUESTIONS:
    print("search:", q)
    for doc, score in store.similarity_search_with_score(q, k=2):
        print(f"  {score:.3f} [{doc.metadata['source']}] {doc.page_content[:45]}…")

retriever = store.as_retriever(search_kwargs={"k": 2})
def format_docs(docs):
    return "\n".join(f"[{d.metadata['source']}] {d.page_content}" for d in docs)

RULES = "أجب من المصادر فقط، واذكر رمز المصدر بين قوسين. وإذا لم تجد الإجابة فيها فقل: لا أعرف."
# الطريقة A: المصادر داخل رسالة النظام System Message
prompt_a = ChatPromptTemplate.from_messages([("system", RULES + "\nالمصادر:\n{context}"), ("user", "{question}")])
# الطريقة B: المصادر داخل رسالة المستخدم مع السؤال
prompt_b = ChatPromptTemplate.from_messages([("system", RULES), ("user", "المصادر:\n{context}\n\nالسؤال: {question}")])

for name, prompt in (("A", prompt_a), ("B", prompt_b)):
    # LCEL: كل جزء Runnable، والعلامة | تمرر مخرج كل جزء إلى الذي بعده
    chain = {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model | StrOutputParser()
    for q in QUESTIONS:
        print(f"answer {name}:", chain.invoke(q))
```

وفي أول تشغيل، قبل أن نضيف numpy إلى Dockerfile، توقف البرنامج عند أول بحث بالخطأ التالي:

```text
search: كم يوماً في الأسبوع أستطيع العمل من البيت؟
Traceback (most recent call last):
  File "/lab/rag.py", line 19, in <module>
    for doc, score in store.similarity_search_with_score(q, k=2):
  ...
  File "/usr/local/lib/python3.13/site-packages/langchain_core/vectorstores/utils.py", line 59, in _cosine_similarity
    raise ImportError(msg)
ImportError: cosine_similarity requires numpy to be installed. Please install numpy with `pip install numpy`.
```

والسبب أن InMemoryVectorStore في langchain-core تحسب التشابه Cosine Similarity بالمكتبة numpy، ولكن langchain-core لا تضعها في متطلباتها، فلا تظهر المشكلة عند التثبيت وإنما عند أول بحث، وهذا مثال صغير على ثمن الطبقات الجاهزة: الاعتماد موجود ولكنه مخفي. وبعد إضافة numpy==2.5.3 وإعادة البناء كان المخرج كما يلي:

```text
search: كم يوماً في الأسبوع أستطيع العمل من البيت؟
  0.572 [HR-07] يحق للموظف العمل عن بعد يومين في الأسبوع بحد …
  0.251 [IT-02] لا يسمح بالاتصال بأنظمة الشركة من خارج المكتب…
search: كم يوماً من الإجازة المرضية يحق لي؟
  0.410 [HR-07] يحق للموظف العمل عن بعد يومين في الأسبوع بحد …
  0.326 [FIN-11] تصرف بدلات السفر خلال عشرة أيام عمل من تقديم …
answer A: أنا لا أعرف.
answer A: لا أعرف.
answer B: كما ورد في المصادر، يمكن للموظف العمل عن بعد يومين في الأسبوع بحد أقصى بعد إكمال ستة أشهر في الشركة، وبموافقة المدير المباشر.
answer B: لا أعرف.
```

الصورة التالية تبين السلسلة التي بنيناها، والفرق بين الطريقتين A وB:

![سلسلة LCEL من اليمين: السؤال يذهب إلى المسترجع الذي يبحث في InMemoryVectorStore بنموذج qwen3-embedding فيرجع مقطعين مثل HR-07 بدرجة 0.572، ثم format_docs، ثم القالب، ثم النموذج qwen2.5:1.5b، ثم StrOutputParser، وتحتها طريقتان للقالب: A تضع المصادر في رسالة النظام فكانت الإجابة لا أعرف رغم وجود المصدر، وB تضع المصادر مع السؤال في رسالة المستخدم فجاءت الإجابة الصحيحة يومين في الأسبوع في كل التشغيلات](https://arabroot.io/content/images/2026/10/langchain-04-rag-chain.webp)

نفس المسترجع ونفس النموذج، والفرق مكان المصادر في الرسائل

في المخرج أعلاه لاحظ التالي:

- البحث نجح في السؤال الأول، فجاءت فقرة HR-07 أولاً بدرجة تشابه 0.572 رغم أن السؤال يقول «من البيت» والفقرة تقول «عن بعد»، وهذه فائدة البحث بالمعنى Semantic Search.
- في السؤال الثاني لا توجد إجابة في المستندات، ومع ذلك رجع البحث فقرتين لأن البحث يرجع أقرب k فقرات دائماً، ودرجة 0.410 أقل بوضوح من 0.572، وقد شرحنا في [مقال RAG](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%D9%83-%D9%85%D8%B9-rag-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#query) كيف تضع حداً أدنى للدرجة.
- الطريقة A وضعت المصادر في رسالة النظام، فأجاب النموذج «لا أعرف» رغم أن المصدر الصحيح أمامه، وعندما شغلنا البرنامج ست مرات كاملة أعطت الطريقة A ثلاث إجابات مختلفة للسؤال الأول: «لا أعرف»، والإجابة الصحيحة، وفقرة عامة تقول إن ذلك «يعتمد على العديد من العوامل».
- الطريقة B وضعت المصادر مع السؤال في رسالة المستخدم، فأعطت الإجابة الصحيحة في المرات الست، وقالت «لا أعرف» في السؤال الذي لا إجابة له. وهذا الفرق من النموذج الصغير وليس قاعدة عامة، والدرس أن تجرب مكان المصادر في القالب وتقيس، فالمكتبة لا تختار ذلك عنك.
- في الطريقتين لم يذكر النموذج رمز المصدر بين قوسين، لذلك أضف المصدر من الكود كما فعلنا في [تجربة المخرج المنظم](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#lab-format)، والكود يعرف المصادر لأنه هو الذي استرجعها.

### الخطوة 5: كم تكلف الطبقة؟

قبل أن نحكم على المكتبة لنقس ما أضافته إلى Docker Image، فالأوامر التالية تعد الحزم في Image الأساسية وفي Image التجربة، وتقيس حجم المكتبات وزمن استيراد langchain.agents:

```bash
docker run --rm --name lab-lc-run python:3.13.16-slim pip list 2>/dev/null | tail -n +3 | wc -l
docker run --rm --name lab-lc-run lab-lc-py:1 sh -c "pip list 2>/dev/null | tail -n +3 | wc -l; du -sh /usr/local/lib/python3.13/site-packages | cut -f1"
docker run --rm --name lab-lc-run lab-lc-py:1 python -c "import time; t=time.time(); import langchain.agents; print(round(time.time()-t, 2))"
```

والمخرج سوف يكون كما يلي:

```text
1
46
155M
1.23
```

أي أن ست حزم طلبناها أضافت 45 حزمة بحجم 155 MB مع numpy، وأصبح حجم Docker Image قرابة 380 MB بدلاً من 178 MB، واستغرق استيراد langchain.agents وحده بين 1.2 و2.5 ثانية في ثلاث مرات على جهاز مشترك مع تجارب أخرى، وهذه الأرقام توضيحية فقط. وهذا لا يهم في خدمة تعمل طوال اليوم، ولكنه يهم في أداة سطر أوامر CLI أو دالة تبدأ مع كل طلب.

### إزالة التجربة

```bash
docker rm -f lab-lc-ollama
docker volume rm lab-lc-ollama
docker network rm lab-lc-net
docker rmi lab-lc-py:1
```

وإذا كنت سوف تكمل بمقال [LangGraph](https://arabroot.io/articles/langgraph-%D8%A8%D9%86%D8%A7%D8%A1-agent-%D9%8A%D8%AD%D9%81%D8%B8-%D8%AD%D8%A7%D9%84%D8%AA%D9%87/) فلا تحذفها الآن، لأنه يستخدم نفس الشبكة ونفس Ollama ونفس Docker Image.

## LangChain بلغة JavaScript

للمكتبة نسخة رسمية بلغة TypeScript في المستودع [langchainjs](https://github.com/langchain-ai/langchainjs?ref=arabroot.io) برخصة MIT، وتتبع نفس التصميم، فالدالة اسمها createAgent كما في [توثيق JavaScript](https://docs.langchain.com/oss/javascript/langchain/overview?ref=arabroot.io)، وحزم المزودين بأسماء مثل @langchain/ollama، وتعمل على Node.js. ولاحظ أن الإصدارات لا تتطابق بين اللغتين، فالحزمة langchain على npm كانت عند كتابة المقال 1.5.16 بينما هي على PyPI 1.4.4، لذلك ارجع إلى توثيق اللغة التي تستخدمها وليس إلى أمثلة اللغة الأخرى.

## متى لا تحتاج LangChain؟

هذا أهم قسم في المقال، لأن أغلب المشروعات التي نراها لا تحتاج المكتبة أصلاً:

- استدعاء واحد لمزود واحد: إذا كان تطبيقك يرسل سؤالاً إلى نموذج واحد ويعرض الإجابة، أو يستخرج حقولاً بمخطط JSON، فمكتبة المزود أو طلب HTTP مباشر أقصر وأوضح، كما فعلنا في [تجربة RAG دون إطار عمل](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%D9%83-%D9%85%D8%B9-rag-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#lab) و[مضيف MCP الصغير](https://arabroot.io/articles/%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-mcp-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#lab-host).
- ثمن الطبقة Abstraction Cost: رأيت في التجربة 45 حزمة إضافية واعتماداً مخفياً على numpy، وعندما يحدث خطأ في الإنتاج فسوف تقرأ Traceback يمر بطبقات لم تكتبها، وإذا احتجت ميزة جديدة عند المزود فقد تنتظر حتى تدعمها حزمة الربط.
- تغير الإصدارات Version Churn: في أقل من سنة انتقلت create\_react\_agent إلى create\_agent، وانتقلت السلاسل والمسترجعات القديمة إلى langchain-classic، و[انتقل دعم MCP](https://docs.langchain.com/oss/python/migrate/langchain-mcp-adapters?ref=arabroot.io) من الحزمة المستقلة langchain-mcp-adapters إلى الوحدة langchain.mcp التي تحتاج الإضافة langchain\[mcp\]، وأصبحت الحزمة langgraph-supervisor [غير مطورة بنشاط](https://docs.langchain.com/oss/python/migrate/langgraph-supervisor?ref=arabroot.io). ووعد الإصدار الدلالي بعد 1.0 يخفف هذا، ولكن أغلب الأمثلة على الإنترنت ما زالت للإصدار 0.3.
- الحاجة إلى فهم كل خطوة: في نظام عليه تدقيق أو صلاحيات حساسة قد تفضل أن يكون كل طلب يخرج إلى النموذج مكتوباً في كودك، حتى تعرف بالضبط ماذا أرسلت.

ومتى تستحق إذن؟ عندما تحتاج أكثر من مزود فعلاً، أو مكونات RAG جاهزة لمصادر كثيرة، أو وكيلاً بطبقات وسيطة وحفظ للحالة وموافقة إنسان، أو عندما يعرف فريقك المكتبة أصلاً فيوفر عليك تعليمهم كوداً خاصاً بك.

## البدائل: مقارنة صادقة

لا توجد مكتبة أفضل في كل شيء، والجدول التالي يضع LangChain مع البدائل الأشهر عند كتابة المقال:

| الأداة                                                                                    | اللغة              | الرخصة      | تركيزها                                                  |
| ----------------------------------------------------------------------------------------- | ------------------ | ----------- | -------------------------------------------------------- |
| [LangChain](https://github.com/langchain-ai/langchain?ref=arabroot.io)                    | Python وTypeScript | MIT         | واجهات موحدة ومئات الروابط، وcreate\_agent فوق LangGraph |
| [LlamaIndex](https://github.com/run-llama/llama%5Findex?ref=arabroot.io)                  | Python             | MIT         | RAG وفهرسة المستندات                                     |
| [Pydantic AI](https://github.com/pydantic/pydantic-ai?ref=arabroot.io)                    | Python             | MIT         | Agent بأنواع Types واضحة على طريقة Pydantic              |
| [Microsoft Agent Framework](https://github.com/microsoft/agent-framework?ref=arabroot.io) | .NET وPython       | MIT         | Agents وWorkflows، ووريث Semantic Kernel وAutoGen        |
| مكتبة المزود أو HTTP مباشرة                                                               | أي لغة             | بحسب المزود | استدعاء النموذج فقط                                      |

### LlamaIndex

- نقاط القوة: قوية في RAG، أي تحميل المستندات وفهرستها والاسترجاع منها، وفيها أيضاً أدوات لبناء Agents.
- نقاط الضعف: يقول [ملف README في المستودع](https://github.com/run-llama/llama%5Findex?ref=arabroot.io) اليوم صراحة إن تركيز الشركة انتقل إلى خدمتها التجارية LlamaParse لتحليل المستندات، وإن المكتبة المفتوحة ما زالت متاحة للاستخدام، وأن النسخة بلغة TypeScript أصبحت [مهملة وغير مطورة](https://github.com/run-llama/LlamaIndexTS?ref=arabroot.io)، وهذا شيء يجب أن تأخذه في حسابك عند اختيار أساس لمشروع طويل.

### Pydantic AI

- نقاط القوة: من فريق Pydantic نفسه، فالمخرج المنظم والتحقق من الأنواع جزء أصيل فيها، وتغيير النموذج نص واحد كما في LangChain، والكود أقرب إلى Python العادي.
- نقاط الضعف: مشروع أحدث، فقد بدأ مستودعه في يونيو 2024، لذلك أمثلة المجتمع حوله أقل، وتركيزها على حلقة الوكيل وليس على RAG، فتوثيقها يقدم نماذج التضمين Embeddings ومثالاً لـ RAG تبني أجزاءه بنفسك، وأداة المراقبة المقترحة معها Logfire خدمة تجارية كما في [توثيقها](https://pydantic.dev/docs/ai/?ref=arabroot.io).

### Microsoft Agent Framework وSemantic Kernel لمطوري C#

- نقاط القوة: إذا كان فريقك يكتب C# فهذا هو الخيار الطبيعي، فبحسب [توثيق Microsoft](https://learn.microsoft.com/en-us/agent-framework/overview/?ref=arabroot.io) هو الوريث المباشر لـ Semantic Kernel وAutoGen من نفس الفريقين، ويجمع الوكلاء مع سير عمل Workflows على شكل رسم بياني، ويدعم Ollama ونماذج أخرى غير Azure. ويقول [مستودع Semantic Kernel](https://github.com/microsoft/semantic-kernel?ref=arabroot.io) نفسه إن Agent Framework هو وريثه وإنه وصل إلى الإصدار 1.0.
- نقاط الضعف: أمثلة التوثيق تبدأ بخدمات Azure، والانتقال من Semantic Kernel يحتاج تعديل كود، وأمثلة المجتمع أقل من LangChain. وإذا كنت قد قرأت سلسلة [ابن نموذج لغة بلغة C# من الصفر](https://arabroot.io/articles/%D8%A7%D8%A8%D9%86-%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%84%D8%BA%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-csharp-1-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%85/) فهذا هو المكان الذي تبني فيه التطبيق حول النموذج بنفس اللغة.

### الكود العادي مع مكتبة المزود

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

## كيف تختار؟

الصورة التالية تختصر القرار في أسئلة قليلة:

![خريطة قرار: هل تستدعي نموذجاً واحداً من مزود واحد دون أدوات كثيرة؟ نعم فالكود العادي مع مكتبة المزود، وإلا هل فريقك يكتب C#؟ نعم فMicrosoft Agent Framework، وإلا هل المشروع RAG على مستندات كثيرة بأشكال مختلفة؟ نعم فLangChain أو LlamaIndex للفهرسة، وإلا هل تحتاج أكثر من مزود ووكيلاً بطبقات وسيطة؟ نعم فLangChain مع create_agent، وإذا كانت الخطوات مخصصة مع حفظ الحالة وموافقة إنسان فLangGraph، وإذا لم تنطبق أي إجابة فابدأ بالكود العادي، وفي الجانب Pydantic AI كحلقة وكيل بأنواع صارمة](https://arabroot.io/content/images/2026/10/langchain-05-decision.webp)

ابدأ بالأبسط، وأضف المكتبة عندما يصبح كود الربط أكبر من كود عملك

## أخطاء شائعة

- نسخ أمثلة الإصدار 0.3: أغلب المقالات القديمة تستخدم LLMChain وfrom langchain.chains، فإذا ظهر لك خطأ ImportError فارجع إلى [صفحة الإصدار 1](https://docs.langchain.com/oss/python/releases/langchain-v1?ref=arabroot.io) قبل أن تثبت langchain-classic.
- تثبيت langchain دون رقم إصدار: الإصدارات الصغيرة تصدر عدة مرات في الأسبوع، وما يعمل اليوم قد يتغير سلوكه بعد شهر، لذلك ثبت كل الحزم كما في Dockerfile أعلاه.
- الظن أن المكتبة تحسن النموذج: رأيت أن نفس النموذج لم يطلب الأداة بالعربية ولم يذكر المصدر، فالمكتبة توحد الطريقة فقط، وجودة الإجابة من النموذج ومن التعليمات ومن الكود الذي يتحقق.
- تفعيل التتبع دون قرار: تفعيل LANGSMITH\_TRACING يرسل التعليمات والمخرجات إلى منصة خارجية، وهذا يلغي سبب تشغيل النموذج على سيرفرك.
- بناء منطق معقد بالعلامة |: الشروط والحلقات وحفظ الحالة مكانها LangGraph وليس سلسلة LCEL طويلة.
- Agent لمهمة ثابتة الخطوات: create\_agent يجعل بناء الوكيل سهلاً، وهذا لا يعني أنك تحتاجه، فاقرأ [سير العمل أم الوكيل](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/#workflow-vs-agent) قبل أن تبدأ.

## قائمة التحقق

- كتبت ما تحتاجه من المكتبة فعلاً: أكثر من مزود، أو أدوات كثيرة، أو مكونات RAG، أو وكيل بطبقات وسيطة.
- كل الحزم مثبتة بأرقام إصداراتها، ومعها الحزم التي تعتمد عليها مثل numpy.
- تعرف أي حزمة مزود تستخدم، وأي إصدار من Ollama أو الخدمة السحابية.
- التتبع إلى LangSmith معطل، أو قررت كتابياً أين تذهب بياناته.
- المخرج المنظم يتحقق منه الكود، والقيم المهمة لا تؤخذ من النموذج دون فحص.
- للوكيل حد لعدد الاستدعاءات، وأدوات الكتابة تمر على موافقة إنسان.
- لديك أسئلة تقييم ثابتة تشغلها أكثر من مرة بعد كل تغيير في النموذج أو القالب أو الإصدار.

## تدريب عملي

- عدل rag.py حتى يضيف الكود رمز المصدر بعد الإجابة من metadata الفقرة الأولى، ولا يضيفه إذا كانت الإجابة «لا أعرف».
- غير structured.py حتى يرجع خطأ إذا كانت الأولوية high والرسالة لا تحتوي كلمات مثل «الآن» أو «متوقف»، ولاحظ كم مرة يحتاج الكود أن يصحح النموذج.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- LangChain مكتبة مفتوحة برخصة MIT تقدم واجهات موحدة للنماذج والأدوات والمخرج المنظم ومكونات RAG، ومئات حزم الربط مع المزودين، وفوقها create\_agent والطبقات الوسيطة.
- الإصدار 1.x غير شكل المكتبة: create\_agent بدلاً من create\_react\_agent، والقديم في langchain-classic، وإصدار دلالي مع دعم طويل للإصدار 1.0، والوكيل نفسه يعمل فوق LangGraph.
- LCEL لم تختف، فواجهة Runnable ما زالت أساس langchain-core، ولكنها للسلاسل القصيرة الثابتة، والمنطق المعقد مكانه LangGraph.
- المكتبات مفتوحة، ومنصة LangSmith خدمة تجارية اختيارية، وتشغيلها على سيرفرك مرتبط بخطة Enterprise.
- التجربة بينت أن المكتبة توحد الطريقة ولا تحسن النموذج، وأن لها ثمناً من الحزم والاعتمادات المخفية والتغييرات.
- إذا كان تطبيقك استدعاء واحداً لمزود واحد فالكود العادي يكفيك، وإذا كان فريقك يكتب C# فابدأ بـ Microsoft Agent Framework.

وفي المقال التالي من [مرحلة الذكاء الاصطناعي على سيرفرك](https://arabroot.io/start/#stage-ai) ننزل طبقة إلى LangGraph، فنبني الوكيل كرسم بياني يحفظ حالته في SQLite ويكمل بعد إعادة التشغيل ويتوقف لينتظر موافقتك قبل أي فعل:

[![](https://arabroot.io/content/images/2026/10/langgraph-cover-cover.webp)دليلLangGraph: بناء Agent يحفظ حالته ويطلب الموافقةكيف تبني Agent بخطوات واضحة في LangGraph، يحفظ حالته في قاعدة بيانات ويكمل بعد التوقف، ولا ينفذ أي فعل قبل موافقتك.](https://arabroot.io/articles/langgraph-%D8%A8%D9%86%D8%A7%D8%A1-agent-%D9%8A%D8%AD%D9%81%D8%B8-%D8%AD%D8%A7%D9%84%D8%AA%D9%87/)[![](https://arabroot.io/content/images/2026/10/rag-mcp-agents-cover-cover.webp)دليلمن RAG إلى Agents: أي طريقة يحتاجها مساعد شركتك؟ما الذي تحله كل طريقة لبناء مساعد ذكي للموظفين وما الذي لا تحله، ولماذا تبدأ بالأرخص ولا تنتقل إلى الأغلى إلا عند الحاجة.](https://arabroot.io/articles/rag-%D8%A7%D9%85-fine-tuning-%D8%A7%D9%85-mcp-%D8%A7%D9%85-agents/)[![](https://arabroot.io/content/images/2026/10/rag-cover-cover.webp)دليلRAG للمبتدئين: نموذج لغة يجيب من مستنداتك على سيرفرككيف تجعل نموذج اللغة يجيب من مستندات مؤسستك ويذكر مصدره، دون أن يخرج أي مستند من سيرفرك.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA%D9%83-%D9%85%D8%B9-rag-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/mcp-cover-cover.webp)دليلبروتوكول MCP للمبتدئين: كيف تربط النماذج بأدواتك بأمانكيف يربط بروتوكول MCP نماذج اللغة بأنظمتك وبياناتك، ولماذا الأمان أهم ما فيه، مع سيرفر MCP صغير تبنيه بنفسك.](https://arabroot.io/articles/%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-mcp-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، واختبار التجربة على Docker Engine 29.8.0 (معالج فقط) مع Ollama 0.40.2 والنموذجين qwen2.5:1.5b وqwen3-embedding:0.6b، وPython 3.13.16 مع langchain 1.4.4 وlangchain-core 1.6.9 وlangchain-ollama 1.1.0 وlanggraph 1.2.14.