أمضت مايكروسوفت عام 2025 وهي تدير قصتين متوازيتين للوكلاء: Semantic Kernel، حزمة التطوير المؤسسية بما تحمله من قياس عن بعد ووسائط برمجية وأمان للأنواع، وAutoGen، المشروع البحثي الشهير بتجريداته الخفيفة لتعدد الوكلاء. في أواخر 2025 دمج الفريقان جهودهما في مكتبة واحدة — Microsoft Agent Framework — وفي مطلع 2026 وصلت إلى الإصدار 1.0 لكل من .NET وبايثون.
يأخذك هذا الدرس عبر حزمة بايثون من البداية إلى النهاية. ستبني نظام فرز تذاكر دعم العملاء: مجموعة من الوكلاء تصنّف التذكرة الواردة، وتصوغ ردًّا باستخدام أدوات حقيقية، وتحوّل الحالات الصعبة إلى إنسان، وتمرّر كل شيء عبر بوابة جودة قبل الإرسال. وفي الطريق ستتعلم واجهتي سير العمل اللتين يأتي بهما الإطار ومتى تستعمل كلًّا منهما.
ملاحظة حول واجهة 2026. بسّط الإصدار 1.0 الأنواع الأساسية: أصبح
ChatAgentهوAgent، وأصبحChatMessageهوMessage، واندمجrun_stream()فيrun(..., stream=True)، وصار المُزخرِف@ai_functionهو@tool. يستخدم هذا الدرس الأسماء الجديدة في كل مكان. إن كنت تقرأ مقالات أو مستودعات أمثلة أقدم، فتوقّع الأسماء الأطولChat*.
المتطلبات الأساسية
قبل البدء، تأكد من توفّر:
- Python 3.10+ مُثبَّتة (
python --version) - مفتاح OpenAI API (أو نقطة وصول Azure OpenAI / Microsoft Foundry)
- إلمام أساسي بـ async/await في بايثون
- محرر أكواد مثل VS Code
سنستخدم OpenAI مباشرة لأنه أسرع طريق إلى وكيل يعمل. كل ما في هذا الدرس ينطبق بنظافة على Azure OpenAI و Microsoft Foundry بمجرد تبديل صنف العميل.
ما الذي ستبنيه
سلسلة دعم متعددة المراحل مكوّنة من وكلاء صغار أحادي الغرض:
- وكيل فرز يصنّف التذكرة إلى فئة ودرجة إلحاح.
- وكيل دعم يصوغ ردًّا، مستدعيًا أدوات للاطلاع على حالة الطلب وسياسة الاسترداد.
- بوابة تدخّل بشري تتوقّف للموافقة عندما يكون الإلحاح مرتفعًا.
- وكيل ضمان جودة يراجع المسودة النهائية من حيث النبرة والدقة قبل الإرسال.
ستنفّذ هذا أولًا باستدعاءات وكلاء عادية، ثم بـواجهة سير العمل الوظيفية (@workflow / @step)، وأخيرًا بـواجهة سير العمل الرسومية (WorkflowBuilder + المنفّذون + الحواف) حتى تفهم المفاضلات.
الخطوة 1: تهيئة المشروع
أنشئ مجلد مشروع وبيئة افتراضية، ثم ثبّت الإطار.
mkdir support-agents && cd support-agents
python -m venv .venv
source .venv/bin/activate # على ويندوز: .venv\Scripts\activate
pip install agent-framework python-dotenvحزمة agent-framework الجامعة تجلب دعم OpenAI و Azure OpenAI افتراضيًّا. إن أردت تثبيتًا أخفّ يمكنك استخدام pip install agent-framework-core بدلًا منها.
أنشئ ملف .env لبيانات الاعتماد. الإطار لا يحمّل ملفات .env تلقائيًّا، لذا سنستدعي load_dotenv() صراحةً.
# .env
OPENAI_API_KEY=sk-...
OPENAI_CHAT_MODEL=gpt-4o-miniنصيحة: يقرأ الإطار
OPENAI_CHAT_MODEL(وOPENAI_MODEL) من البيئة. لاحظ توحيد 2026: المعامل الآنmodel، وليسmodel_idأبدًا.
الخطوة 2: أول وكيل لك
أنشئ hello.py. الوكيل هو الجمع بين عميل محادثة (اتصال النموذج) وتعليمات (موجّه النظام).
# hello.py
import asyncio
from dotenv import load_dotenv
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
load_dotenv()
async def main() -> None:
agent = Agent(
client=OpenAIChatClient(),
name="HelloAgent",
instructions="You are a friendly support assistant. Keep answers brief.",
)
result = await agent.run("A customer asks: where is my order?")
print(result)
if __name__ == "__main__":
asyncio.run(main())شغّله:
python hello.pyيُعيد استدعاء agent.run() كائن استجابة يكون تمثيله النصّي هو نصّ النموذج. ولاستقبال الرموز أثناء توليدها، مرّر stream=True:
print("Agent: ", end="", flush=True)
async for chunk in agent.run("Give me a one-sentence apology template.", stream=True):
if chunk.text:
print(chunk.text, end="", flush=True)
print()يعمل صنف Agent نفسه مع أي مزوّد. لاستهداف Azure OpenAI، بدّل الاستيراد إلى AzureOpenAIChatClient من agent_framework.azure؛ ولاستهداف Microsoft Foundry، استخدم FoundryChatClient. كود الوكيل أعلاه لا يتغيّر.
الخطوة 3: امنح الوكيل أدوات
تصبح الوكلاء مفيدة حين تستطيع التصرّف. في Agent Framework، الأداة ليست سوى دالة بايثون بسلسلة توثيق ومعاملات ذات أنواع — يولّد الإطار مخطط JSON ويدير حلقة الاستدعاء نيابةً عنك.
أنشئ tools.py:
# tools.py
from typing import Annotated
from pydantic import Field
def get_order_status(
order_id: Annotated[str, Field(description="The customer's order ID, e.g. 'A-1042'")]
) -> str:
"""Look up the shipping status of an order by its ID."""
# في الإنتاج يصل هذا إلى قاعدة بيانات الطلبات أو واجهة برمجية.
fake_db = {
"A-1042": "Shipped on 2026-06-14, arriving 2026-06-19.",
"A-1099": "Processing — not yet shipped.",
}
return fake_db.get(order_id, "No order found with that ID.")
def get_refund_policy(
region: Annotated[str, Field(description="Customer region: 'EU', 'US', or 'MENA'")]
) -> str:
"""Return the refund window and conditions for a region."""
policies = {
"EU": "14-day no-questions-asked returns under EU consumer law.",
"US": "30-day returns with receipt.",
"MENA": "14-day returns for unused items in original packaging.",
}
return policies.get(region, "Standard 14-day return policy applies.")الآن سجّل الأدوات بتمريرها إلى الوكيل. النموذج هو من يقرّر متى يستدعيها.
# support_agent.py
import asyncio
from dotenv import load_dotenv
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from tools import get_order_status, get_refund_policy
load_dotenv()
async def main() -> None:
agent = Agent(
client=OpenAIChatClient(),
name="SupportAgent",
instructions=(
"You are a support agent. Use the tools to look up real order "
"and policy data before answering. Never invent order details."
),
tools=[get_order_status, get_refund_policy],
)
result = await agent.run(
"Hi, where is order A-1042, and can I return it? I'm in the EU."
)
print(result)
if __name__ == "__main__":
asyncio.run(main())سيستدعي الوكيل get_order_status("A-1042") و get_refund_policy("EU")، ثم يدمج النتيجتين في ردّ واحد مؤسَّس على بيانات حقيقية. لم تكتب أي كود لمعالجة الاستدعاء — يشغّل الإطار حلقة الأداة تلقائيًّا.
بديل المُزخرِف. للأدوات التي تحتاج إلى إعدادات أو بيانات وصفية أغنى، زخرف الدالة بـ
@tool(المستوردة منagent_framework). الدالة العادية الممرَّرة في قائمةtoolsتُعامَل كأداة ضمنيًّا، فالمُزخرِف اختياري في الحالات البسيطة.
الخطوة 4: ذاكرة متعددة الأدوار عبر الجلسات
كل استدعاء agent.run() عديم الحالة افتراضيًّا. لإجراء محادثة، أنشئ جلسة (كانت تُسمّى سابقًا "thread") ومرّرها في كل دور.
# session_demo.py
import asyncio
from dotenv import load_dotenv
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from tools import get_order_status
load_dotenv()
async def main() -> None:
agent = Agent(
client=OpenAIChatClient(),
name="SupportAgent",
instructions="You are a concise support agent.",
tools=[get_order_status],
)
session = agent.create_session()
r1 = await agent.run("Where is order A-1042?", session=session)
print("Turn 1:", r1)
# السؤال التالي بلا رقم طلب — الجلسة تحمل السياق.
r2 = await agent.run("And will it arrive before the weekend?", session=session)
print("Turn 2:", r2)
if __name__ == "__main__":
asyncio.run(main())تسميتان تستحقّان التذكّر من إصدار 2026: أصبح get_new_thread() هو create_session()، وأصبح المعامل thread= هو session=. إن لم يُهيَّأ مزوّد سجل، يحقن الإطار تلقائيًّا InMemoryHistoryProvider، فتعمل المحادثات جاهزةً.
الخطوة 5: تنسيق عدة وكلاء
تستخدم الأنظمة الحقيقية عدة متخصّصين بدل وكيل واحد يفعل كل شيء. أبسط تنسيق هو التسلسلي: مخرَج الوكيل أ يغذّي الوكيل ب. يمكنك فعل ذلك يدويًّا — فالوكلاء كائنات بايثون قابلة للتركيب.
# pipeline_manual.py
import asyncio
from dotenv import load_dotenv
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from tools import get_order_status, get_refund_policy
load_dotenv()
def make_agent(name: str, instructions: str, tools=None) -> Agent:
return Agent(
client=OpenAIChatClient(),
name=name,
instructions=instructions,
tools=tools or [],
)
async def main() -> None:
triage = make_agent(
"Triage",
"Classify the ticket. Reply with exactly: 'CATEGORY: <billing|shipping|other> | "
"URGENCY: <low|high>'. Nothing else.",
)
support = make_agent(
"Support",
"Draft a warm, accurate reply. Use tools for real data.",
tools=[get_order_status, get_refund_policy],
)
qa = make_agent(
"QA",
"You review support drafts. If the tone is professional and the facts are "
"grounded, reply with the draft unchanged. Otherwise rewrite it.",
)
ticket = "URGENT: order A-1099 still not here and I leave the country tomorrow! EU customer."
label = await triage.run(ticket)
print("Triage:", label)
draft = await support.run(f"Ticket: {ticket}\nClassification: {label}")
print("Draft:", draft)
final = await qa.run(f"Review this draft:\n{draft}")
print("Final:", final)
if __name__ == "__main__":
asyncio.run(main())ينجح هذا، لكن تدفّق التحكّم ومعالجة الأخطاء وقابلية الرصد كلها على عاتقك. وهذا بالضبط ما تحلّه واجهات سير العمل.
الخطوة 6: واجهة سير العمل الوظيفية
تتيح لك الواجهة الوظيفية التعبير عن سير العمل كدالة async عادية مُزخرَفة بـ @workflow، حيث كل مرحلة @step. تبقى محافظًا على تدفّق تحكّم بايثون الأصلي — if/else والحلقات و asyncio.gather — مع نيلك أحداثًا لكل خطوة، وبثًّا، ونقاط حفظ، ودعمًا للتدخّل البشري.
# workflow_functional.py
import asyncio
from dotenv import load_dotenv
from agent_framework import Agent, workflow, step
from agent_framework.openai import OpenAIChatClient
from tools import get_order_status, get_refund_policy
load_dotenv()
client = OpenAIChatClient()
triage = Agent(client=client, name="Triage",
instructions="Reply 'URGENCY: high' or 'URGENCY: low' and one category word.")
support = Agent(client=client, name="Support",
instructions="Draft an accurate, friendly reply. Use tools for real data.",
tools=[get_order_status, get_refund_policy])
qa = Agent(client=client, name="QA",
instructions="Approve or rewrite the draft for tone and factual grounding.")
@step
async def classify(ticket: str) -> dict:
label = str(await triage.run(ticket))
return {"ticket": ticket, "label": label, "high": "high" in label.lower()}
@step
async def draft_reply(state: dict) -> dict:
draft = str(await support.run(f"Ticket: {state['ticket']}\nLabel: {state['label']}"))
state["draft"] = draft
return state
@step
async def review(state: dict) -> str:
return str(await qa.run(f"Review and finalize:\n{state['draft']}"))
@workflow
async def support_pipeline(ticket: str, ctx) -> str:
state = await classify(ticket)
# تفرّع بايثون الأصلي: تصعيد التذاكر العاجلة إلى إنسان.
if state["high"]:
decision = await ctx.request_info(
f"High-urgency ticket needs approval before auto-reply:\n{state['label']}"
)
if str(decision).strip().lower().startswith("no"):
return "Escalated to a human agent. No automated reply sent."
state = await draft_reply(state)
return await review(state)
async def main() -> None:
result = await support_pipeline.run(
"URGENT: order A-1099 not delivered, leaving tomorrow! EU."
)
print(result)
if __name__ == "__main__":
asyncio.run(main())أمور جديرة بالملاحظة:
ctx.request_info(...)هو بدائية التدخّل البشري. في التشغيل التفاعلي يوقف سير العمل ويُظهر حدث طلب يجيب عنه تطبيقك؛ وفي التشغيل المؤتمت توصِّل مجيبًا.- كل
@stepينتج حدثه الخاص، فتحصل على قابلية رصد دقيقة مجانًا. - ولأنه بايثون عادي، يمكنك استخدام
asyncio.gatherلتشغيل خطوات مستقلة بالتوازي — مثل التصنيف وجلب سجل الحساب في آنٍ واحد.
الواجهة الوظيفية هي نقطة البدء المُوصى بها. الجأ إلى الواجهة الرسومية حين تحتاج توجيه رسائل صارمًا ومتحقَّقًا من الأنواع بين منفّذين كثيرين.
الخطوة 7: واجهة سير العمل الرسومية
تنمذج الواجهة الرسومية سير العمل كرسم بياني موجَّه من منفّذين تصلهم حواف. تتألّق في الطوبولوجيات الثابتة، وتوازي التوزيع والتجميع، ونقاط الحفظ على حدود الخطوة الفائقة. تبنيها بـ WorkflowBuilder.
# workflow_graph.py
import asyncio
from dotenv import load_dotenv
from agent_framework import (
Agent, WorkflowBuilder, executor, WorkflowContext,
)
from agent_framework.openai import OpenAIChatClient
from tools import get_order_status, get_refund_policy
load_dotenv()
client = OpenAIChatClient()
support = Agent(client=client, name="Support",
instructions="Draft an accurate, friendly reply. Use tools for real data.",
tools=[get_order_status, get_refund_policy])
qa = Agent(client=client, name="QA",
instructions="Finalize the draft for tone and accuracy.")
@executor(id="classify")
async def classify(ticket: str, ctx: WorkflowContext[str]) -> None:
label = str(await Agent(
client=client, name="Triage",
instructions="Reply with one category word and 'high' or 'low' urgency.",
).run(ticket))
# خزّن التذكرة الأصلية في الحالة المشتركة للمنفّذين اللاحقين.
ctx.set_state("ticket", ticket)
await ctx.send_message(label)
@executor(id="draft")
async def draft(label: str, ctx: WorkflowContext[str]) -> None:
ticket = ctx.get_state("ticket")
reply = str(await support.run(f"Ticket: {ticket}\nLabel: {label}"))
await ctx.send_message(reply)
@executor(id="review")
async def review(reply: str, ctx: WorkflowContext) -> None:
final = str(await qa.run(f"Finalize:\n{reply}"))
await ctx.yield_output(final)
async def main() -> None:
workflow = (
WorkflowBuilder()
.set_start_executor(classify)
.add_edge(classify, draft)
.add_edge(draft, review)
.build()
)
async for event in workflow.run("Where is order A-1042? EU customer.", stream=True):
if event.type == "output":
print("FINAL:", event.data)
if __name__ == "__main__":
asyncio.run(main())المفاهيم الأساسية في الواجهة الرسومية:
- المنفّذ وحدة عمل — وكيل أو منطق مخصّص — يُعلَن بالمُزخرِف
@executorومعالج يستقبلWorkflowContext. - يمرّر
ctx.send_message(...)رسالة ذات نوع عبر الحواف الصادرة؛ ويصدرctx.yield_output(...)نتيجة سير عمل نهائية. - يقرأ ويكتب
ctx.set_state(...)/ctx.get_state(...)الحالة المشتركة. في إصدار 2026 أصبحا متزامنين (بلاawait)، وأُعيدت تسميةshared_stateإلىstate. - يربط
add_edge(a, b)المنفّذين معًا. يمكنك إرفاق دالة شرط بحافة للتوجيه حسب المحتوى، والتوزيع على عدة منفّذين لخطوات فائقة متوازية.
لاحظ نموذج الأحداث الموحَّد: بدل أصناف أحداث كثيرة، تفحص event.type — "output" و "request_info" وهكذا. للتدخّل البشري في الواجهة الرسومية، أضف عقدة RequestInfoExecutor.
الخطوة 8: قابلية الرصد
لأن Agent Framework يرث نَسَب Semantic Kernel المؤسسي، فإنه يصدر آثار ومقاييس OpenTelemetry جاهزةً. يصبح كل تشغيل وكيل وكل منفّذ سير عمل امتدادًا (span)، فترى استهلاك الرموز واستدعاءات الأدوات وزمن الاستجابة في أي واجهة خلفية متوافقة مع OTel.
from agent_framework.observability import setup_observability
# استدعِها مرة عند الإقلاع. تقرأ OTEL_EXPORTER_OTLP_ENDPOINT من البيئة.
setup_observability()وجّه OTEL_EXPORTER_OTLP_ENDPOINT إلى مجمِّع محلي (أو Aspire Dashboard / Jaeger) فتحصل على أثر كامل لأي وكيل استدعى أي أداة، مع التوقيت — لا يُقدَّر بثمن حين يسيء تشغيل متعدد الوكلاء التصرّف في الإنتاج.
اختبار تنفيذك
لا تريد ضرب نموذج مدفوع في اختبارات الوحدة. اختبر الأجزاء الحتمية — أدواتك — مباشرةً، وتحقّق من بنية سير العمل.
# test_tools.py
from tools import get_order_status, get_refund_policy
def test_known_order():
assert "Shipped" in get_order_status("A-1042")
def test_unknown_order():
assert "No order found" in get_order_status("ZZZ")
def test_region_policy():
assert "14-day" in get_refund_policy("EU")
assert "30-day" in get_refund_policy("US")شغّل بـ pytest. لاختبارات على مستوى الوكيل، يوفّر الإطار أدوات عميل اختبار تتيح لك تزييف ردود النموذج والتحقّق من استدعاء الأداة المتوقّعة، دون مكالمات شبكة.
استكشاف الأخطاء وإصلاحها
ImportError: cannot import name 'ChatAgent' — أنت على واجهة 1.0+. استخدم Agent لا ChatAgent. وينطبق ذلك على ChatMessage (صار Message).
الوكيل لا يستدعي أداتي أبدًا — تأكد أن للدالة سلسلة توثيق ومعاملات ذات أنواع وAnnotated. يبني الإطار مخطط الأداة من هذه؛ وصف مفقود لا يعطي النموذج ما يطابقه. وتحقّق أن تعليماتك تطلب صراحةً من الوكيل استخدام الأدوات.
قيم .env يُتجاهَل — Agent Framework لا يحمّل .env تلقائيًّا. استدعِ load_dotenv() قبل إنشاء أي عميل، أو صدّر المتغيّرات في صدفتك.
AttributeError على shared_state / await ctx.get_shared_state — في 2026 صارت ctx.state و ctx.get_state(...) و ctx.set_state(...)، وهي متزامنة. احذف await.
أخطاء مصادقة مع Azure — DefaultAzureCredential مريح محليًّا لكنه يفحص مصادر كثيرة. في الإنتاج فضّل بيانات اعتماد محدّدة مثل ManagedIdentityCredential لتجنّب زمن الاستجابة والارتدادات غير المتوقّعة.
الخطوات التالية
- أضف
RequestInfoExecutorإلى سير العمل الرسومي لتتوقّف التذاكر العاجلة لانتظار إنسان، تماشيًا مع المثال الوظيفي. - بدّل
OpenAIChatClientبـAzureOpenAIChatClientوانشره خلف نقطة وصول Foundry للحوكمة المؤسسية. - استكشف أنماط التنسيق المدمجة المتزامن والتسليم وMagentic لسلوك متعدد الوكلاء أكثر ديناميكية.
- غلّف سير عمل كاملًا كوكيل بـ
.as_agent()وعشّشه داخل نظام أكبر.
إن كنت تبني وكلاء بـ TypeScript بدلًا من ذلك، فإن دليلَينا حول OpenAI Agents SDK وMastra يغطّيان الأنماط نفسها في تلك المنظومة. وللبدائل التي تفضّل بايثون، طالع درسَينا Pydantic AI وAgno.
الخلاصة
يمنحك Microsoft Agent Framework مكتبة واحدة متماسكة تتدرّج من "وكيل مرحبًا" من ثلاثة أسطر إلى سير عمل متعدد الوكلاء متحقَّق من الأنواع وقابل للرصد ويدعم التدخّل البشري. النموذج الذهني صغير: الوكيل نموذج زائدًا تعليمات زائدًا أدوات؛ وسير العمل هو كيف تركّب الوكلاء بتدفّق تحكّم صريح. ابدأ باستدعاءات وكلاء عادية، وانتقل إلى الواجهة الوظيفية @workflow حين تحتاج تنسيقًا، وتبنّ الواجهة الرسومية WorkflowBuilder حين تحتاج توجيهًا صارمًا على نطاق واسع. ولأنه يحمل قياس Semantic Kernel وأناقة AutoGen، فهو من الأطر القليلة المبنية للإنتاج من اليوم الأول — خيار افتراضي قوي للفرق في منطقة الشرق الأوسط وشمال أفريقيا التي تعتمد بنية مايكروسوفت وأزور.