--- title: تدفقات المحادثة description: أنشئ تطبيقات دردشة متعددة الجولات مع kickoff لكل جولة وسجل الرسائل وتوجيه النية والتتبع وجسور WebSocket. icon: comments mode: "wide" --- ## نظرة عامة تعامل التطبيقات المحادثية مع كل سطر من المستخدم كـ **تشغيل flow جديد** بنفس **معرّف الجلسة**. توفر CrewAI مساعدات لسجل الرسائل وتصنيف النية الاختياري وتأجيل التتبع وجسور الواجهة، إضافة إلى REPL محلي `flow.chat()` للتدفقات المحادثية. | المفهوم | التنفيذ | |---------|---------| | معرّف الجلسة | `handle_turn(..., session_id=...)` → `kickoff(inputs={"id": ...})` → `state.id` | | سطر المستخدم | `handle_turn(message)` يضيف الرسالة إلى `state.messages` قبل تشغيل الرسم | | اكتمال الجولة | `FlowFinished` لهذا **التشغيل** فقط؛ تستمر المحادثة في `handle_turn` التالي | | تتبع الجلسة | `ConversationConfig(defer_trace_finalization=True)` + `finalize_session_traces()` | ## واجهات الجولات استخدم **`flow.handle_turn(message, session_id=...)`** لكل رسالة مستخدم من REST أو WebSocket أو الاختبارات أو الواجهات المخصصة. استخدم **`flow.chat()`** عندما تريد حلقة دردشة محلية في الطرفية لـ `Flow` محادثي. لا يقبل `Flow.kickoff()` الوسيطين `user_message=` أو `session_id=`. في التدفقات المحادثية، يخزن `handle_turn()` الرسالة المعلقة ويستدعي داخلياً `kickoff(inputs={"id": session_id})`. | API | الاستخدام | |-----|-----------| | `handle_turn(message, session_id=...)` | غلاف مريح لجولة واحدة في `Flow` محادثي | | `chat()` | REPL محلي في الطرفية لـ `Flow` محادثي | | `kickoff(inputs={...})` | تشغيل متقدم للـ flow بدون معالجة جولة محادثية | | `ask()` | مطالبة حاجزة **داخل** خطوة واحدة | | `@human_feedback` | الموافقة/الرفض على **مخرجات خطوة** — وليس السطر التالي | | `ChatSession.handle_turn(...)` | طبقة نقل فوق `handle_turn` | ## بداية سريعة ```python from uuid import uuid4 from crewai import Flow from crewai.flow import listen from crewai.experimental.conversational import ( ConversationConfig, ConversationState, ) @ConversationConfig(defer_trace_finalization=True) class SupportFlow(Flow[ConversationState]): conversational = True def route_turn(self, context): message = (self.state.current_user_message or "").lower() if "طلب" in message or "order" in message: return "order" if "وداع" in message or "goodbye" in message: return "goodbye" return "help" @listen("order") def handle_order(self): reply = "طلبك في الطريق." self.append_assistant_message(reply) return reply @listen("help") def handle_help(self): reply = "كيف يمكنني المساعدة؟" self.append_assistant_message(reply) return reply @listen("goodbye") def handle_goodbye(self): reply = "وداعاً!" self.append_assistant_message(reply) return reply session_id = str(uuid4()) flow = SupportFlow() try: flow.handle_turn("أين طلبي؟", session_id=session_id) flow.handle_turn("وماذا عن الإرجاع؟", session_id=session_id) finally: flow.finalize_session_traces() ``` ## دورة حياة الجولة كل `handle_turn` يشغّل: 1. **`_configure_conversational_kickoff`** — دمج `session_id` / `user_message` في `inputs` وتطبيق `ConversationalConfig`. 2. **استعادة الحالة** — عند وجود `inputs["id"]` و`@persist`. 3. **`FlowStarted`** — في أول جولة للجلسة المؤجلة فقط. 4. **`prepare_conversational_turn`** — إضافة رسالة المستخدم و`last_user_message` وتصنيف اختياري. 5. **تنفيذ الرسم** — `@start` → `@router` → معالجات `@listen`. 6. **نهاية التشغيل** — يُتخطى `flow_finished` والتتبع لكل جولة عند التأجيل؛ `Agent.kickoff()` / crews لا تغلق دفعة الأب. استدعِ **`append_assistant_message(reply)`** في المعالجات. سطر المستخدم محفوظ عبر `handle_turn` — لا تُضفه مرة أخرى. ## `ConversationalConfig` (افتراضيات على مستوى الصنف) عيّن على صنف `Flow` كـ `conversational_config: ClassVar[ConversationalConfig | None]`. | الحقل | الافتراضي | الغرض | |-------|-----------|--------| | `default_intents` | `None` | تسميات outcome للتصنيف التلقائي قبل kickoff | | `intent_llm` | `None` | نموذج التصنيف (مطلوب عند وجود intents) | | `interactive_prompt` | `"You: "` | مطالبة `kickoff(interactive=True)` | | `interactive_timeout` | `None` | مهلة لكل سطر في الوضع التفاعلي | | `exit_commands` | `exit`, `quit` | كلمات إنهاء الوضع التفاعلي | | `defer_trace_finalization` | `True` | إبقاء دفعة trace واحدة مفتوحة بين الجولات | يمكن التجاوز لكل kickoff عبر `intents=` و`intent_llm=`. ## `ChatState` (شكل الحالة الموصى به للحفظ) ```python from crewai.flow import ChatState class MyChatState(ChatState): # موروث: id, messages, last_user_message, last_intent, session_ready research_turn_count: int = 0 custom_flag: bool = False ``` | الحقل | الدور | |-------|------| | `id` | UUID الجلسة (مثل `session_id` / `inputs["id"]`) | | `messages` | قائمة `{role, content}` لسجل LLM | | `last_user_message` | آخر سطر مستخدم في هذه الجولة | | `last_intent` | تسمية المسار بعد التصنيف (إن وُجد) | | `session_ready` | علم bootstrap لمرة واحدة | `ConversationalInputs` هو `TypedDict` لـ `kickoff(inputs={...})`: `id`, `user_message`, `last_intent`. ## API المحادثة على `Flow` ### معاملات `kickoff` / `kickoff_async` | المعامل | الغرض | |---------|--------| | `user_message` | نص هذه الجولة (أو `{"role": "user", "content": "..."}`) | | `session_id` | UUID المحادثة → `inputs["id"]` / `state.id` | | `intents` | تسميات outcome لـ `classify_intent` قبل kickoff | | `intent_llm` | LLM للتصنيف (مطلوب مع `intents`) | | `interactive` | حلقة CLI عبر `ask()` (للعروض المحلية فقط) | | `interactive_prompt` | مطالبة الوضع التفاعلي | | `interactive_timeout` | مهلة `ask()` لكل سطر | | `exit_commands` | كلمات إنهاء الوضع التفاعلي | | `inputs` | حقول حالة إضافية | | `restore_from_state_id` | استنساخ من flow محفوظ آخر | ### سمات المثيل | السمة | الغرض | |-------|--------| | `conversational_config` | افتراضيات `ConversationalConfig` على مستوى الصنف | | `defer_trace_finalization` | علم المثيل؛ يُضبط تلقائياً من config عند kickoff | | `suppress_flow_events` | يخفي لوحات console؛ **التتبع يُسجّل** | | `stream` | بث؛ مع `ChatSession.handle_turn(..., stream=True)` | ### طرق وخصائص | الاسم | الوصف | |------|--------| | `append_message(role, content, **extra)` | إضافة إلى `state.messages` | | `conversation_messages` | سجل للقراءة فقط لاستدعاءات LLM | | `classify_intent(text, outcomes, *, llm, context=None)` | تعيين outcome | | `receive_user_message(text, *, outcomes=None, llm=None)` | إضافة رسالة مستخدم؛ `last_intent` اختياري | | `finalize_session_traces()` | إصدار `flow_finished` المؤجل وإنهاء دفعة trace | | `_should_defer_trace_finalization()` | هل يُؤجل إنهاء trace لكل جولة | | `input_history` | سجل تدقيق مطالبات وردود `ask()` | ### مساعدات الوحدة (`crewai.flow.conversation`) | الدالة | الوصف | |--------|--------| | `normalize_kickoff_inputs(...)` | دمج kwargs المحادثة في `inputs` | | `get_conversation_messages(flow)` | قراءة الرسائل من الحالة أو المخزن | | `append_message(flow, ...)` | مثل طريقة المثيل | | `prepare_conversational_turn(flow, ...)` | تهيئة الجولة (عادةً kickoff يستدعيها) | | `receive_user_message(flow, ...)` | مثل طريقة المثيل | | `set_state_field(flow, name, value)` | تعيين حقل dict أو Pydantic | | `get_conversational_config(flow)` | قراءة `conversational_config` | | `input_history_to_messages(entries)` | تحويل `input_history` لصيغة رسائل LLM | ## أنماط توجيه النية ### أ. تصنيف مسبق عبر `ConversationalConfig` (الأبسط) عيّن `default_intents` و`intent_llm`. كل kickoff يصنّف قبل `@router`؛ اقرأ `self.state.last_intent` في `route()`. ### ب. تصنيف داخل `@router` (مطالبات أغنى) عيّن `default_intents=None` ليضيف kickoff الرسالة فقط. في `route()` استدعِ `classify_intent`: ```python @router(bootstrap) def route(self): intent = self.classify_intent( self._routing_prompt(self.state.last_user_message), ("GREETING", "ORDER", "RESEARCH", "GOODBYE"), llm=self.conversational_config.intent_llm or "gpt-4o-mini", ) self.state.last_intent = intent return intent ``` للبحث على الويب أو أدوات متعددة الخطوات استخدم **`@listen("RESEARCH")`** مع `Agent.kickoff()` وأدوات — وليس `LLM.call()` فقط. ## عندما ينتهي الـ flow ويستمر المستخدم `FlowFinished` يعني أن **تنفيذ الرسم هذا** اكتمل. تستمر المحادثة بـ `kickoff` آخر ونفس `session_id`. `@persist` يستعيد `messages` والأعلام والسياق. **نمط الحفظ:** يُفضّل `@persist` على **خطوة نهائية واحدة** (مثل `finalize`) وليس على صنف `Flow` بالكامل. الحفظ على مستوى الصنف بعد كل method قد يفقد تحديثات المعالجات في نفس الجولة. لا تستخدم `@human_feedback` لأسطر المتابعة في الدردشة إلا عند الحاجة لموافقة بشرية على مخرجات خطوة محددة. ## `Flow` المحادثاتي (تجريبي) **ميزة تجريبية.** سطح `Flow` المحادثاتي (`conversational = True`، `handle_turn`، `ConversationConfig`، `RouterConfig`، `ConversationState`، الرسم البياني المدمج والمساعدات) يقع تحت `crewai.experimental` وقد يتغير شكله قبل التخرج. ثبّت إصدار CrewAI إذا كنت تعتمد على سلوك محدد، وراقب changelog للتحديثات الكاسرة. الملاحظات والمشاكل مرحب بها. فعّل الرسم المحادثاتي بتعيين `conversational = True` على صنف فرعي من `Flow`. عندئذٍ يُظهر `Flow` الأساسي رسم `@start` / `@router` / `converse_turn` / `end_conversation` مدمجاً، ويدير `state.messages`، ويُشغّل LLM التوجيه، ويبقي دفعة trace مفتوحة عبر الجولات. أنت تكتب **المسارات المخصصة** فقط؛ والإطار يتولى الباقي. استخدمه عندما تريد دردشة متعددة الجولات مع موجّه قائم على LLM ومعالجات لكل مسار دون توصيل دورة الحياة يدوياً. استخدم `Flow[ChatState]` (النمط الأدنى مستوى في الأعلى) عندما تحتاج تحكماً كاملاً. ### مثال سريع ```python from crewai import LLM, Flow from crewai.flow import listen from crewai.experimental.conversational import ( ConversationConfig, ConversationState, RouterConfig, ) ROUTER_LLM = LLM(model="gpt-4o-mini") @ConversationConfig( system_prompt="A multi-agent assistant for ordinary chat and tool-backed tasks.", llm=ROUTER_LLM, router=RouterConfig(), # المسارات + الأوصاف تُكتشف تلقائياً من معالجات @listen ) class SupportFlow(Flow[ConversationState]): conversational = True @listen("INTERNET_SEARCH") def handle_internet_search(self) -> str: """Fresh web research, current news, real-time lookups.""" ... self.append_assistant_message(reply) return reply @listen("CREWAI_DOCS") def handle_crewai_docs(self) -> str: """Look up the CrewAI documentation for framework/API questions.""" ... self.append_assistant_message(reply) return reply flow = SupportFlow() try: flow.handle_turn("ماذا يمكنك أن تفعل؟") # يوجَّه إلى converse (مدمج) flow.handle_turn("ابحث في الويب عن أخبار الذكاء الاصطناعي.") # يوجَّه إلى INTERNET_SEARCH flow.handle_turn("لخص النتيجة الأولى.") # يعود إلى converse finally: flow.finalize_session_traces() ``` للدردشة المحلية في الطرفية، استخدم `chat()`: ```python def kickoff() -> None: SupportFlow().chat() ``` يلف `chat()` استدعاءات `handle_turn()` داخل REPL، ويخرج عند `exit` / `quit`، ويتجاهل الأسطر الفارغة افتراضياً، ويستدعي `finalize_session_traces()` عند انتهاء الجلسة. ### `ConversationConfig` مزخرف صنف يُلحق افتراضيات الدردشة على مستوى الصنف. | الحقل | الافتراضي | الغرض | |-------|-----------|-------| | `system_prompt` | `slices.conversational_system_prompt` من i18n | رسالة system يستخدمها `converse_turn` المدمج. مرر `""` للتعطيل التام. | | `llm` | `None` | LLM المحادثة (يستخدمه `converse_turn` وكاحتياطي للموجّه). | | `router` | `None` | `RouterConfig` للتوجيه عبر LLM. بدونه، يسقط الـ flow دائماً إلى `converse`. | | `answer_from_history_prompt` | افتراضي الإطار | رسالة system للمسار الاختياري `answer_from_history`. | | `answer_from_history_llm` | `None` | يُفعّل الاختصار `answer_from_history` عند تعيينه. | | `intent_llm` | `None` | LLM لمسار التصنيف المسبق القديم `intents=`/`default_intents`. | | `default_intents` | `None` | تسميات النتائج للتصنيف المسبق القديم. | | `visible_agent_outputs` | `None` | `"all"` أو قائمة بأسماء الـ agents الذين تُرفع مخرجاتهم من `append_agent_result()` إلى رسائل عامة. | | `defer_trace_finalization` | `True` | يبقي دفعة trace واحدة مفتوحة عبر استدعاءات `handle_turn()`. | ### `RouterConfig` وفهرس المسارات المُولَّد تلقائياً ```python RouterConfig( prompt="تأطير اختياري للنطاق (سياسة، صوت، شخصية).", response_format=MyRoute, # اختياري؛ يُولَّد تلقائياً عند الإغفال llm=ROUTER_LLM, # يسقط إلى ConversationConfig.llm routes=["INTERNET_SEARCH", "CREWAI_DOCS"], # اختياري؛ يُستنتج من المستمعين route_descriptions={ "INTERNET_SEARCH": "تجاوز الـ docstring لهذا المسار فقط.", }, default_intent="converse", # يُستخدم عند فشل LLM أو غيابه fallback_intent="converse", # يُستخدم عندما يعيد LLM مساراً غير صالح intent_field="intent", ) ``` تُبنى رسالة الموجّه إلى LLM تلقائياً. لكل مسار يختار الإطار وصفاً بهذا الترتيب من الأولوية: 1. `RouterConfig.route_descriptions[label]` — تجاوز صريح. 2. `Flow.builtin_route_descriptions[label]` — نص جاهز من الإطار لـ `converse` و`end` و`answer_from_history` (مصاغ لـ LLM التوجيه). 3. أول سطر غير فارغ من docstring معالج `@listen(label)`. 4. فارغ (المسار يظهر في الفهرس بلا وصف). عملياً، **إضافة مسار جديد = `@listen("X")` + docstring من سطر واحد**: ```python @listen("INTERNET_SEARCH") def handle_internet_search(self) -> str: """Fresh web research, current news, real-time lookups.""" ... ``` …وسيرى LLM التوجيه: ``` Routes: - CREWAI_DOCS: Look up the CrewAI documentation for framework/API questions. - INTERNET_SEARCH: Fresh web research, current news, real-time lookups. - converse: Ordinary chat, follow-ups, summaries, clarifications… - end: User signals the conversation is finished (goodbye, exit, done). ``` `RouterConfig.prompt` مخصص لـ **تأطير النطاق** (شخصية المساعد، قواعد العمل، النبرة). فهرس المسارات يُبنى تلقائياً — لا تُدرج المسارات في `prompt`؛ سيختل التزامن لحظة إضافة معالج جديد. ### المسارات المدمجة | المسار | المعالج | الغرض | |--------|---------|-------| | `converse` | `converse_turn` | معالج الدردشة الافتراضي. يستدعي `ConversationConfig.llm` بـ system prompt + التاريخ القانوني للرسائل. | | `end` | `end_conversation` | يضبط `state.ended = True` ويُصدر رد إنهاء. | | `answer_from_history` | `answer_from_history_turn` | اختياري. يُوجَّه إليه عندما يكون `ConversationConfig.answer_from_history_llm` مُعيَّناً ويمكن الإجابة على الرسالة من التاريخ فقط. | يمكنك تجاوز أي من هذه بتعريف معالج بنفس الاسم في الصنف الفرعي. ### دلالات `handle_turn()` `flow.handle_turn(message)` يُشغّل جولة واحدة: 1. يعيد ضبط تعقّب التنفيذ لكل جولة (`_completed_methods`, `_method_outputs`) ليُعاد تشغيل الرسم — بدون ذلك، استدعاءات `kickoff` المتكررة على نفس النسخة ستُحدث دائرة قصر من الجولة الثانية لأن `Flow.kickoff_async` يعتبر `inputs={"id": ...}` استعادة من نقطة تفتيش. 2. يُلحق رسالة المستخدم بـ `state.messages` ويضبط `current_user_message` / `last_user_message`. يُحافَظ على `last_intent` **من الجولة السابقة** كي يستخدمها LLM التوجيه كإشارة. 3. يُشغّل `conversation_start` → `route_conversation` → معالج `@listen` المختار. 4. يخزّن الموجّه قراره في `state.last_intent` (يكون مرئياً لسياق التوجيه في الجولة التالية). 5. إذا أعاد معالجك سلسلة نصية ولم يستدعِ `append_assistant_message`، فإن `handle_turn` يُلحقها نيابةً عنك. استدعِ `handle_turn()` لرسائل الدردشة. استدعاء `kickoff(inputs={"id": ...})` مباشرةً يشغل الرسم بدون غلاف الجولة المحادثية. ### `chat()` للـ REPL المحلي `flow.chat()` هو غلاف الطرفية الجاهز فوق `handle_turn()`: ```python flow = SupportFlow() flow.chat() ``` يتولى الحلقة المحلية الشائعة: 1. يطلب رسالة من المستخدم. 2. يتوقف عند `exit` / `quit` أو `EOFError` أو `KeyboardInterrupt`. 3. يستدعي `handle_turn(message, session_id=...)`. 4. يطبع نتيجة المساعد. 5. ينهي traces الجلسة المؤجلة داخل كتلة `finally`. خصص سلوك الطرفية عبر I/O قابل للحقن: ```python flow.chat( session_id="demo-session", prompt="You: ", assistant_prefix="Assistant: ", exit_commands=("exit", "quit", "bye"), ) ``` لتطبيقات الويب والـ workers الخلفية والاختبارات ووسائط النقل المخصصة، استمر في استخدام `handle_turn()` مباشرةً. ### سلوك موجّه مخصص لتشغيل آثار جانبية (إعداد ناقل أحداث، قياس عن بُعد) في كل قرار توجيه، تجاوز `route_turn`: ```python class SupportFlow(Flow[ConversationState]): conversational = True def route_turn(self, context: dict[str, Any]) -> str | None: self.event_bus = MyBus(self) return super().route_turn(context) ``` لتجاوز موجّه LLM واختيار مسار برمجياً، أعد سلسلة نصية من `route_turn`؛ إعادة `None` تسقط إلى `_route_with_config(...)`. ### `append_assistant_message` و`append_agent_result` داخل معالج `@listen(label)`، اختر: - `self.append_assistant_message(text)` — يضيف جولة مساعد مرئية للمستخدم إلى `state.messages`. سيراها `converse_turn` في الجولة التالية. - `self.append_agent_result(agent_name, result, visibility="private")` — يسجّل حدثاً منظماً في `state.events` وموضوعاً في `state.agent_threads[agent_name]`. الرؤية العامة تستدعي `append_assistant_message` أيضاً. استخدم النتائج الخاصة للعمل الجانبي الذي يجب ألا يلوث التاريخ القانوني. يمكن لـ `ConversationConfig.visible_agent_outputs` رفع النتائج الخاصة لـ agents محددين إلى عامة عالمياً (`"all"` أو قائمة بالأسماء). ## التتبع عبر الجولات مع `defer_trace_finalization=True` (افتراضي في `ConversationalConfig`): - **دفعة trace واحدة** لجلسة الدردشة. - **`flow_started`** في الجولة الأولى فقط؛ **`flow_finished`** مرة في `finalize_session_traces()`. - **`kickoff` لكل جولة** لا يطبع "Trace batch finalized". - **العمل المتداخل** (`Agent.kickoff()`, crews, Exa) يُلحق بدفعة **الأب**؛ flow داخلي من `AgentExecutor` لا يغلق دفعة الجلسة مبكراً. ```python flow.chat(session_id=session_id) ``` `flow.chat()` يستدعي `finalize_session_traces()` نيابةً عنك. عندما تملك الحلقة عبر `handle_turn()` أو `kickoff(...)`، استدعِ `finalize_session_traces()` عند انتهاء الجلسة. `suppress_flow_events=True` يخفي لوحات Rich فقط؛ أحداث trace والـ methods تُصدر. ### دورة حياة trace لـ `Flow` المحادثاتي يستخدم [`Flow` المحادثاتي](#flow-المحادثاتي-تجريبي) التجريبي نفس دورة حياة tracing: `defer_trace_finalization` افتراضياً `True`، فيبقي كل `handle_turn()` أثر الجلسة مفتوحاً. أنهِ دوماً عند نهاية الجلسة — لُف حلقتك بـ `try/finally` واستدعِ `flow.finalize_session_traces()` عند الخروج. بدون ذلك، تبقى الدفعة مفتوحة وقد لا تُصدَّر آخر محادثة أبداً. ## البث اضبط `stream = True` على صنف `Flow`. عندئذٍ يُصدر `kickoff(...)` أحداث `assistant_delta` (وما يرتبط بها) عبر ناقل الأحداث القياسي. ## الاستيراد ```python from crewai.flow import ( ChatState, ConversationalConfig, ConversationalInputs, Flow, listen, persist, router, start, ) ``` ## مراجع - [إتقان إدارة حالة Flow](/ar/guides/flows/mastering-flow-state) - [أنشئ أول Flow](/ar/guides/flows/first-flow) - Demo: `lib/crewai/runner_conversational_flow_simple.py` — REPL بسيط مع `RESEARCH` ووكيل Exa