diff --git a/docs/ar/guides/flows/conversational-flows.mdx b/docs/ar/guides/flows/conversational-flows.mdx new file mode 100644 index 000000000..9251a09fe --- /dev/null +++ b/docs/ar/guides/flows/conversational-flows.mdx @@ -0,0 +1,339 @@ +--- +title: تدفقات المحادثة +description: أنشئ تطبيقات دردشة متعددة الجولات مع kickoff لكل جولة وسجل الرسائل وتوجيه النية والتتبع وجسور WebSocket. +icon: comments +mode: "wide" +--- + +## نظرة عامة + +تعامل التطبيقات المحادثية مع كل سطر من المستخدم كـ **تشغيل flow جديد** بنفس **معرّف الجلسة**. توفر CrewAI مساعدات لسجل الرسائل وتصنيف النية الاختياري وتأجيل التتبع وجسور الواجهة — دون API منفصل `chat()` على `Flow`. + +| المفهوم | التنفيذ | +|---------|---------| +| معرّف الجلسة | `kickoff(session_id=...)` → `inputs["id"]` → `state.id` | +| سطر المستخدم | `kickoff(user_message=...)` يُضاف إلى `state.messages` قبل تشغيل الرسم | +| اكتمال الجولة | `FlowFinished` لهذا **التشغيل** فقط؛ تستمر المحادثة في `kickoff` التالي | +| تتبع الجلسة | `ConversationalConfig(defer_trace_finalization=True)` + `finalize_session_traces()` | + +## نقطة دخول واحدة: `kickoff` + +استخدم **`flow.kickoff(user_message=..., session_id=...)`** لكل رسالة مستخدم (REST أو WebSocket أو CLI). لا تنشئ غلاف `chat()` مخصصاً على `Flow`. + +| API | الاستخدام | +|-----|-----------| +| `kickoff(user_message=..., session_id=...)` | كل رسالة مستخدم | +| `kickoff_async(...)` | نفس المعاملات؛ دخول async أصلي | +| `ask()` | مطالبة حاجزة **داخل** خطوة واحدة | +| `@human_feedback` | الموافقة/الرفض على **مخرجات خطوة** — وليس السطر التالي | +| `ChatSession.handle_turn(...)` | طبقة نقل فوق `kickoff` | + +## بداية سريعة + +```python +from uuid import uuid4 + +from crewai.flow import ( + ChatState, + ConversationalConfig, + Flow, + listen, + or_, + persist, + router, + start, +) +from crewai.flow.persistence import SQLiteFlowPersistence + + +class SupportFlow(Flow[ChatState]): + conversational_config = ConversationalConfig( + default_intents=["order", "help", "goodbye"], + intent_llm="gpt-4o-mini", + defer_trace_finalization=True, + ) + + @start() + def bootstrap(self): + if not self.state.session_ready: + self.state.session_ready = True + return "ready" + + @router(bootstrap) + def route(self): + return self.state.last_intent or "help" + + @listen("order") + def handle_order(self): + reply = "طلبك في الطريق." + self.append_message("assistant", reply) + return reply + + @listen("help") + def handle_help(self): + reply = "كيف يمكنني المساعدة؟" + self.append_message("assistant", reply) + return reply + + @listen("goodbye") + def handle_goodbye(self): + reply = "وداعاً!" + self.append_message("assistant", reply) + return reply + + @persist(SQLiteFlowPersistence("support.db")) + @listen(or_(handle_order, handle_help, handle_goodbye)) + def finalize(self): + return self.state.model_dump() + + +session_id = str(uuid4()) +flow = SupportFlow() + +flow.kickoff(user_message="أين طلبي؟", session_id=session_id) +flow.kickoff(user_message="وماذا عن الإرجاع؟", session_id=session_id) +flow.finalize_session_traces() +``` + +## دورة حياة الجولة + +كل `kickoff` مع `user_message` يشغّل: + +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_message("assistant", reply)`** في المعالجات. سطر المستخدم محفوظ عند kickoff — لا تُضفه مرة أخرى. + +## `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` لأسطر المتابعة في الدردشة إلا عند الحاجة لموافقة بشرية على مخرجات خطوة محددة. + +## التتبع عبر الجولات + +مع `defer_trace_finalization=True` (افتراضي في `ConversationalConfig`): + +- **دفعة trace واحدة** لجلسة الدردشة. +- **`flow_started`** في الجولة الأولى فقط؛ **`flow_finished`** مرة في `finalize_session_traces()`. +- **`kickoff` لكل جولة** لا يطبع "Trace batch finalized". +- **العمل المتداخل** (`Agent.kickoff()`, crews, Exa) يُلحق بدفعة **الأب**؛ flow داخلي من `AgentExecutor` لا يغلق دفعة الجلسة مبكراً. + +```python +try: + while True: + line = input("You: ").strip() + if not line: + break + flow.kickoff(user_message=line, session_id=session_id) +finally: + flow.finalize_session_traces() +``` + +`ChatSession.close()` يستدعي `finalize_session_traces()` عند التأجيل. + +`suppress_flow_events=True` يخفي لوحات Rich فقط؛ أحداث trace والـ methods تُصدر. + +## `ChatSession` (WebSocket / SSE) + +غلاف `kickoff` وجسر أحداث اختياري للواجهات. + +```python +from crewai.flow import ChatMessage, ChatSession + + +def on_event(msg: ChatMessage): + print(msg.type, msg.payload) + + +session = ChatSession( + flow, + session_id="channel-1", + intents=["order", "help"], + intent_llm="gpt-4o-mini", + on_event=on_event, +) + +turn = session.handle_turn("مرحباً") +print(turn.output, turn.intent, len(turn.messages)) + +for msg in session.iter_turn_stream("أخبرني المزيد"): + print(msg.type, msg.payload) + +session.close() # finalize_session_traces عند التأجيل +``` + +| النوع | الغرض | +|------|--------| +| `ChatSession` | جلسة؛ `handle_turn`, `iter_turn_stream`, `close` | +| `TurnResult` | `session_id`, `output`, `intent`, `messages`, `streaming` اختياري | +| `ChatMessage` | صيغة wire: `type`, `session_id`, `payload`, `seq` | +| `ConversationEventBridge` | bus → `ChatMessage` | + +قيم `ChatMessage.type`: `user_message`, `assistant_delta`, `assistant_done`, `turn_started`, `turn_finished`, `error`, `tool_started`, `tool_finished`. + +`stamp_conversation_fingerprint(event, session_id)` — يضبط `conversation_id` للموزّعات الخارجية. + +## `QueueInputProvider` (`ask` حاجز عبر WebSocket) + +عندما يستدعي flow `ask()` داخل method وتصل الرسائل عبر socket: + +```python +from crewai.flow import Flow, QueueInputProvider + +provider = QueueInputProvider() +flow = MyFlow(input_provider=provider) + +provider.push(session_id, user_text) + +reply = flow.ask("You: ", metadata={"session_id": session_id}) +provider.close_session(session_id) +``` + +## البث + +عيّن `stream = True` على صنف `Flow`. استخدم `kickoff(...)` أو `ChatSession.handle_turn(..., stream=True)` لأحداث مثل `assistant_delta`. + +## الاستيراد + +```python +from crewai.flow import ( + ChatMessage, + ChatSession, + ChatState, + ConversationalConfig, + ConversationalInputs, + ConversationEventBridge, + Flow, + QueueInputProvider, + TurnResult, + 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 diff --git a/docs/ar/guides/flows/first-flow.mdx b/docs/ar/guides/flows/first-flow.mdx index d4ae8f898..ffeb30c2d 100644 --- a/docs/ar/guides/flows/first-flow.mdx +++ b/docs/ar/guides/flows/first-flow.mdx @@ -272,6 +272,7 @@ crewai flow plot 3. استكشف دوال `and_` و`or_` لتنفيذ متوازٍ أكثر تعقيدًا 4. اربط Flow بواجهات API خارجية وقواعد بيانات وواجهات مستخدم 5. ادمج عدة Crews متخصصة في Flow واحد +6. أنشئ تطبيقات دردشة متعددة الجولات مع [تدفقات المحادثة](/ar/guides/flows/conversational-flows) (`kickoff` لكل رسالة، `ChatSession`، تأجيل التتبع) تهانينا! لقد بنيت بنجاح أول CrewAI Flow يجمع بين الكود العادي واستدعاءات LLM المباشرة ومعالجة Crew لإنشاء دليل شامل. هذه المهارات الأساسية تمكّنك من إنشاء تطبيقات AI متطورة بشكل متزايد. diff --git a/docs/ar/guides/flows/mastering-flow-state.mdx b/docs/ar/guides/flows/mastering-flow-state.mdx index 09e56c3df..2311a4462 100644 --- a/docs/ar/guides/flows/mastering-flow-state.mdx +++ b/docs/ar/guides/flows/mastering-flow-state.mdx @@ -20,6 +20,8 @@ mode: "wide" 5. **توسيع تطبيقاتك** - دعم سير العمل المعقدة بتنظيم بيانات مناسب 6. **تمكين التطبيقات الحوارية** - تخزين والوصول إلى سجل المحادثات للتفاعلات الواعية بالسياق +للدردشة متعددة الجولات (`kickoff` لكل سطر مستخدم، `ChatState`، توجيه النية، تأجيل التتبع، و`ChatSession`)، راجع [تدفقات المحادثة](/ar/guides/flows/conversational-flows). + ## أساسيات إدارة الحالة ### نهجان لإدارة الحالة diff --git a/docs/docs.json b/docs/docs.json index dbc53562c..9e6c8c9a6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -115,6 +115,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -599,6 +600,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -1083,6 +1085,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -1566,6 +1569,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -2048,6 +2052,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -2532,6 +2537,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -3016,6 +3022,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -3500,6 +3507,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -3984,6 +3992,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -4466,6 +4475,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -4948,6 +4958,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -5431,6 +5442,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -5915,6 +5927,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -6397,6 +6410,7 @@ "pages": [ "en/guides/flows/first-flow", "en/guides/flows/mastering-flow-state", + "en/guides/flows/conversational-flows", "en/guides/flows/inputs-id-deprecation" ] }, @@ -6912,6 +6926,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -7373,6 +7388,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -7834,6 +7850,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -8295,6 +8312,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -8755,6 +8773,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -9216,6 +9235,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -9677,6 +9697,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -10138,6 +10159,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -10599,6 +10621,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -11059,6 +11082,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -11519,6 +11543,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -11979,6 +12004,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -12438,6 +12464,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -12897,6 +12924,7 @@ "pages": [ "pt-BR/guides/flows/first-flow", "pt-BR/guides/flows/mastering-flow-state", + "pt-BR/guides/flows/conversational-flows", "pt-BR/guides/flows/inputs-id-deprecation" ] }, @@ -13387,6 +13415,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -13860,6 +13889,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -14333,6 +14363,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -14806,6 +14837,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -15279,6 +15311,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -15753,6 +15786,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -16227,6 +16261,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -16701,6 +16736,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -17175,6 +17211,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -17648,6 +17685,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -18121,6 +18159,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -18594,6 +18633,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -19066,6 +19106,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -19538,6 +19579,7 @@ "pages": [ "ko/guides/flows/first-flow", "ko/guides/flows/mastering-flow-state", + "ko/guides/flows/conversational-flows", "ko/guides/flows/inputs-id-deprecation" ] }, @@ -20041,6 +20083,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -20514,6 +20557,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -20987,6 +21031,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -21460,6 +21505,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -21933,6 +21979,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -22407,6 +22454,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -22881,6 +22929,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -23355,6 +23404,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -23829,6 +23879,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -24302,6 +24353,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -24775,6 +24827,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -25248,6 +25301,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -25720,6 +25774,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, @@ -26192,6 +26247,7 @@ "pages": [ "ar/guides/flows/first-flow", "ar/guides/flows/mastering-flow-state", + "ar/guides/flows/conversational-flows", "ar/guides/flows/inputs-id-deprecation" ] }, diff --git a/docs/en/guides/flows/conversational-flows.mdx b/docs/en/guides/flows/conversational-flows.mdx index f06e1e6b9..0b36dfce0 100644 --- a/docs/en/guides/flows/conversational-flows.mdx +++ b/docs/en/guides/flows/conversational-flows.mdx @@ -1,51 +1,68 @@ --- title: Conversational Flows -description: Build multi-turn chat apps with kickoff per turn, message history, and intent routing. +description: Build multi-turn chat apps with kickoff per turn, message history, intent routing, tracing, and WebSocket bridges. icon: comments mode: "wide" --- +## Overview + +Conversational apps treat each user line as a **new flow run** with the **same session id**. CrewAI adds helpers for message history, optional intent classification, deferred tracing, and UI bridges — without a separate `chat()` API on `Flow`. + +| Concept | Implementation | +|---------|----------------| +| Session id | `kickoff(session_id=...)` → `inputs["id"]` → `state.id` | +| User line | `kickoff(user_message=...)` appends to `state.messages` before the graph runs | +| Turn complete | `FlowFinished` for **this run** only; chat continues on the next `kickoff` | +| Full-session trace | `ConversationalConfig(defer_trace_finalization=True)` + `finalize_session_traces()` | + ## One entry point: `kickoff` -Chat apps should use **`flow.kickoff(user_message=..., session_id=...)`** for each user line. Do not add a separate `chat()` method — session identity is `state.id` (same as `inputs["id"]` with `@persist`). +Use **`flow.kickoff(user_message=..., session_id=...)`** for every user message (REST, WebSocket, CLI). Do not add a custom `chat()` wrapper on `Flow`. | API | Use for | |-----|---------| -| `kickoff(user_message=..., session_id=...)` | Each new user message (API, WebSocket turn, CLI loop) | +| `kickoff(user_message=..., session_id=...)` | Each user message | +| `kickoff_async(...)` | Same parameters; native async entry | | `ask()` | Blocking prompt **inside** one step (wizard, clarification) | -| `@human_feedback` | Approve/reject **a step output** before continuing — not the next chat line | +| `@human_feedback` | Approve/reject **a step output** — not the next chat line | +| `ChatSession.handle_turn(...)` | Transport layer over `kickoff` (SSE / WebSocket) | ## Quick start ```python from uuid import uuid4 -from pydantic import Field - -from crewai.flow import ChatState, Flow, listen, persist, router, start +from crewai.flow import ( + ChatState, + ConversationalConfig, + Flow, + listen, + or_, + persist, + router, + start, +) from crewai.flow.persistence import SQLiteFlowPersistence -@persist(SQLiteFlowPersistence()) class SupportFlow(Flow[ChatState]): + conversational_config = ConversationalConfig( + default_intents=["order", "help", "goodbye"], + intent_llm="gpt-4o-mini", + defer_trace_finalization=True, + ) + @start() def bootstrap(self): - if self.state.session_ready: - return "ready" - # load permissions once per session - self.state.session_ready = True + if not self.state.session_ready: + self.state.session_ready = True return "ready" @router(bootstrap) def route(self): - if self.state.last_intent: - return self.state.last_intent - return self.classify_intent( - self.state.last_user_message, - outcomes=["order", "help", "goodbye"], - llm="gpt-4o-mini", - context=self.conversation_messages, - ) + # last_intent set in prepare_conversational_turn when default_intents is set + return self.state.last_intent or "help" @listen("order") def handle_order(self): @@ -59,92 +76,269 @@ class SupportFlow(Flow[ChatState]): self.append_message("assistant", reply) return reply + @listen("goodbye") + def handle_goodbye(self): + reply = "Goodbye!" + self.append_message("assistant", reply) + return reply + + @persist(SQLiteFlowPersistence("support.db")) + @listen(or_(handle_order, handle_help, handle_goodbye)) + def finalize(self): + return self.state.model_dump() + session_id = str(uuid4()) flow = SupportFlow() -# Turn 1 flow.kickoff(user_message="Where is my order?", session_id=session_id) - -# Turn 2 — same session, flow finished after turn 1 is normal flow.kickoff(user_message="What about returns?", session_id=session_id) +flow.finalize_session_traces() # one trace link for the whole chat ``` -## When the flow finishes but the user keeps chatting +## Turn lifecycle -`FlowFinished` means **this graph run** completed. The conversation continues with **another `kickoff`** and the same `session_id`. `@persist` restores `messages`, permissions flags, and context. +Each `kickoff` with `user_message` runs this pipeline: -For multi-turn chat, prefer **`@persist` on a single terminal step** (for example `finalize`) rather than on the whole `Flow` class. Class-level persist saves after every method; `load_state` uses the latest row, which is often a mid-run snapshot (for example right after `bootstrap`) and can omit handler updates from the same turn. +1. **`_configure_conversational_kickoff`** — merges `session_id` / `user_message` into `inputs`, applies `ConversationalConfig`, enables deferred tracing when configured. +2. **State restore** — if `inputs["id"]` exists and `@persist` is configured, loads the latest snapshot. +3. **`FlowStarted`** — emitted on the first deferred session turn only. +4. **`prepare_conversational_turn`** — appends the user message to `state.messages`, sets `last_user_message`, clears `last_intent`, optionally classifies when `intents` / `default_intents` + `intent_llm` are set. +5. **Graph execution** — `@start` → `@router` → `@listen` handlers. +6. **End of run** — per-turn `flow_finished` and trace finalization are **skipped** when deferral is enabled; nested `Agent.kickoff()` / crews do not close the parent batch either. -Do **not** use `@human_feedback` for follow-up questions unless a human must approve a specific payload before it is shown. +Handlers should call **`append_message("assistant", reply)`** so the next turn’s `conversation_messages` includes assistant text. The user line is already stored at kickoff — do not append it again in handlers. -## Tracing across turns +## `ConversationalConfig` (class-level defaults) -By default, `ConversationalConfig(defer_trace_finalization=True)` keeps **one trace batch** for the whole chat session instead of finalizing after every `kickoff()`. Call `flow.finalize_session_traces()` when the user leaves (or use `ChatSession.close()`, which does this automatically). +Set on your `Flow` subclass as `conversational_config: ClassVar[ConversationalConfig | None]`. + +| Field | Default | Purpose | +|-------|---------|---------| +| `default_intents` | `None` | Outcome labels for automatic pre-kickoff classification | +| `intent_llm` | `None` | Model for classification (required when intents are used) | +| `interactive_prompt` | `"You: "` | Prompt for `kickoff(interactive=True)` | +| `interactive_timeout` | `None` | Per-line timeout in interactive mode | +| `exit_commands` | `exit`, `quit` | Words that end interactive mode | +| `defer_trace_finalization` | `True` | Keep one trace batch open across turns | + +Override per kickoff with `intents=` and `intent_llm=` keyword arguments. + +## `ChatState` (recommended persisted shape) ```python -flow.kickoff(user_message="Hello", session_id=session_id) -flow.kickoff(user_message="Track my order", session_id=session_id) -flow.finalize_session_traces() # one link for the full conversation +from crewai.flow import ChatState + + +class MyChatState(ChatState): + # Inherited: id, messages, last_user_message, last_intent, session_ready + research_turn_count: int = 0 + custom_flag: bool = False ``` -Crews kicked off **inside** a flow method (for example a research step) append their events to the **parent flow batch**. `CrewKickoffCompleted` does not finalize that batch (even when crew worker threads lose `current_flow_id` context); finalization happens in `finalize_session_traces()`. +| Field | Role | +|-------|------| +| `id` | Session UUID (same as `session_id` / `inputs["id"]`) | +| `messages` | `list` of `{role, content}` for LLM history | +| `last_user_message` | Latest user line for this turn | +| `last_intent` | Route label after classification (if used) | +| `session_ready` | One-time bootstrap flag (permissions, caches, etc.) | -Per-turn `flow_finished` is also deferred: only `flow_started` opens the session scope on the first turn, and `flow_finished` runs once in `finalize_session_traces()` so the event bus does not warn about a missing `flow_started`. +`ConversationalInputs` is a `TypedDict` for conventional `kickoff(inputs={...})` keys: `id`, `user_message`, `last_intent`. -## Kickoff parameters +## `Flow` conversational API + +### `kickoff` / `kickoff_async` parameters | Parameter | Purpose | |-----------|---------| -| `user_message` | This turn's user text (also `inputs["user_message"]`) | +| `user_message` | This turn’s text (or `{"role": "user", "content": "..."}`) | | `session_id` | Conversation UUID → `inputs["id"]` / `state.id` | -| `intents` | Optional labels for pre-kickoff `classify_intent` | +| `intents` | Outcome labels for pre-kickoff `classify_intent` | | `intent_llm` | LLM for classification (required with `intents`) | -| `interactive=True` | CLI demo loop via `ask()` (not for production APIs) | +| `interactive` | CLI loop via `ask()` (local demos only) | +| `interactive_prompt` | Override prompt in interactive mode | +| `interactive_timeout` | Per-line `ask()` timeout | +| `exit_commands` | Words that end interactive mode | +| `inputs` | Additional state fields (merged with conversational keys) | +| `restore_from_state_id` | Fork hydration from another persisted flow | -Class-level defaults: +### Instance attributes + +| Attribute | Purpose | +|-----------|---------| +| `conversational_config` | Class-level `ConversationalConfig` defaults | +| `defer_trace_finalization` | Instance flag; set automatically from config on kickoff | +| `suppress_flow_events` | Hides console flow panels; **tracing still records** method/flow events | +| `stream` | Enable streaming; use with `ChatSession.handle_turn(..., stream=True)` | + +### Methods and properties + +| Name | Description | +|------|-------------| +| `append_message(role, content, **extra)` | Append to `state.messages` (roles: `user`, `assistant`, `system`, `tool`) | +| `conversation_messages` | Read-only history for LLM calls | +| `classify_intent(text, outcomes, *, llm, context=None)` | Map text to one outcome (same collapse logic as `@human_feedback`) | +| `receive_user_message(text, *, outcomes=None, llm=None)` | Append user message; optionally set `last_intent` | +| `finalize_session_traces()` | Emit deferred `flow_finished` and finalize the session trace batch | +| `_should_defer_trace_finalization()` | Whether this flow defers per-turn trace finalization | +| `input_history` | Audit trail of `ask()` prompts and responses | + +### Module helpers (`crewai.flow.conversation`) + +Importable for tests or custom orchestration: + +| Function | Description | +|----------|-------------| +| `normalize_kickoff_inputs(inputs, user_message=..., session_id=...)` | Merge conversational kwargs into `inputs` | +| `get_conversation_messages(flow)` | Read messages from state or internal buffer | +| `append_message(flow, role, content, **extra)` | Same as instance method | +| `prepare_conversational_turn(flow, user_message=..., intents=..., intent_llm=..., config=...)` | Turn hydration (usually called by kickoff) | +| `receive_user_message(flow, text, ...)` | Same as instance method | +| `set_state_field(flow, name, value)` | Set a field on dict or Pydantic state | +| `get_conversational_config(flow)` | Read class `conversational_config` | +| `input_history_to_messages(entries)` | Convert `input_history` to LLM message format | + +## Intent routing patterns + +### A. Pre-classify via `ConversationalConfig` (simplest) + +Set `default_intents` and `intent_llm`. Each kickoff runs classification before your `@router`; read `self.state.last_intent` in `route()`. + +### B. Classify inside `@router` (richer prompts) + +Set `default_intents=None` so kickoff only appends the user message. In `route()`, call `classify_intent` with a custom prompt or descriptions: ```python -from crewai.flow import ConversationalConfig, Flow - - -class MyFlow(Flow[ChatState]): - conversational_config = ConversationalConfig( - default_intents=["order", "help"], - intent_llm="gpt-4o-mini", +@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 ``` -## Helpers on `Flow` +Use **`@listen("RESEARCH")`** (or similar) for steps that run `Agent.kickoff()` with tools — not bare `LLM.call()` — when you need web research or multi-step tool use. -- `append_message(role, content)` — update `state.messages` -- `conversation_messages` — history for LLM calls -- `classify_intent(text, outcomes, llm=..., context=...)` — route labels (same logic as `@human_feedback` collapse) -- `receive_user_message(text, ...)` — append user line + optional classify -- `input_history` — audit trail from `ask()` +## When the flow finishes but the user keeps chatting -Recommended state shape: `ChatState` (`id`, `messages`, `last_user_message`, `last_intent`, `session_ready`). +`FlowFinished` means **this graph run** completed. The conversation continues with another `kickoff` and the same `session_id`. `@persist` restores `messages`, flags, and context. -## ChatSession (WebSocket / SSE bridge) +**Persist pattern:** prefer `@persist` on a **single terminal step** (for example `finalize`) rather than on the whole `Flow` class. Class-level persist saves after every method; `load_state` uses the latest row, which may be a mid-run snapshot (for example right after `bootstrap`) and miss handler updates from the same turn. -For UIs, use `ChatSession` to wrap kickoff and map events to `ChatMessage`: +Do **not** use `@human_feedback` for follow-up chat lines unless a human must approve a specific step output before it is shown. + +## Tracing across turns + +With `defer_trace_finalization=True` (default in `ConversationalConfig`): + +- **One trace batch** for the whole chat session. +- **`flow_started`** on the first turn only; **`flow_finished`** once in `finalize_session_traces()`. +- **Per-turn** `kickoff` does not print “Trace batch finalized”. +- **Nested work** (`Agent.kickoff()`, crews, Exa tools) appends to the **parent** batch; inner `AgentExecutor` flows do not close the session batch early. ```python -from crewai.flow import ChatSession +try: + while True: + line = input("You: ").strip() + if not line: + break + flow.kickoff(user_message=line, session_id=session_id) +finally: + flow.finalize_session_traces() +``` + +`ChatSession.close()` calls `finalize_session_traces()` when deferral is enabled. + +`suppress_flow_events=True` only hides Rich console panels; trace and method events still emit for observability. + +## `ChatSession` (WebSocket / SSE) + +Wraps `kickoff` and optional event bridging for UIs. + +```python +from crewai.flow import ChatMessage, ChatSession -def on_event(msg): +def on_event(msg: ChatMessage): print(msg.type, msg.payload) -session = ChatSession(flow, session_id="channel-1", on_event=on_event) +session = ChatSession( + flow, + session_id="channel-1", + intents=["order", "help"], + intent_llm="gpt-4o-mini", + on_event=on_event, +) + turn = session.handle_turn("Hello") -print(turn.output, turn.intent) -session.close() +print(turn.output, turn.intent, len(turn.messages)) + +for msg in session.iter_turn_stream("Tell me more"): + print(msg.type, msg.payload) + +session.close() # finalize_session_traces when deferred ``` -`QueueInputProvider` supports blocking `ask()` fed by a WebSocket handler (`provider.push(session_id, text)`). +| Type | Purpose | +|------|---------| +| `ChatSession` | One session; `handle_turn`, `iter_turn_stream`, `close` | +| `TurnResult` | `session_id`, `output`, `intent`, `messages`, optional `streaming` | +| `ChatMessage` | Wire format: `type`, `session_id`, `payload`, `seq` | +| `ConversationEventBridge` | Maps bus events → `ChatMessage` (`assistant_delta`, `tool_*`, …) | + +`ChatMessage.type` values: `user_message`, `assistant_delta`, `assistant_done`, `turn_started`, `turn_finished`, `error`, `tool_started`, `tool_finished`. + +`stamp_conversation_fingerprint(event, session_id)` — set `conversation_id` on events for external dispatchers. + +## `QueueInputProvider` (blocking `ask` from WebSocket) + +For flows that call `ask()` inside a method while messages arrive on a socket: + +```python +from crewai.flow import Flow, QueueInputProvider + +provider = QueueInputProvider() +flow = MyFlow(input_provider=provider) + +# WebSocket handler: +provider.push(session_id, user_text) + +# Inside a flow method: +reply = flow.ask("You: ", metadata={"session_id": session_id}) +provider.close_session(session_id) # unblock with None +``` ## Streaming -Enable `stream = True` on the Flow class and use `kickoff` / `ChatSession.handle_turn(..., stream=True)` to emit `assistant_delta` events through `ConversationEventBridge`. +Set `stream = True` on the `Flow` class. Use `kickoff(...)` or `ChatSession.handle_turn(..., stream=True)` so `ConversationEventBridge` emits `assistant_delta` (and related) events. + +## Imports + +```python +from crewai.flow import ( + ChatMessage, + ChatSession, + ChatState, + ConversationalConfig, + ConversationalInputs, + ConversationEventBridge, + Flow, + QueueInputProvider, + TurnResult, + listen, + persist, + router, + start, +) +``` + +## See also + +- [Mastering Flow State Management](/en/guides/flows/mastering-flow-state) — persistence, Pydantic state, `@persist` +- [Build Your First Flow](/en/guides/flows/first-flow) — flow basics +- Demo: `lib/crewai/runner_conversational_flow_simple.py` — minimal REPL with `RESEARCH` + Exa agent diff --git a/docs/en/guides/flows/first-flow.mdx b/docs/en/guides/flows/first-flow.mdx index a5b8c347c..f536976d2 100644 --- a/docs/en/guides/flows/first-flow.mdx +++ b/docs/en/guides/flows/first-flow.mdx @@ -617,6 +617,7 @@ Now that you've built your first flow, you can: 3. Explore the `and_` and `or_` functions for more complex parallel execution 4. Connect your flow to external APIs, databases, or user interfaces 5. Combine multiple specialized crews in a single flow +6. Build multi-turn chat apps with [Conversational Flows](/en/guides/flows/conversational-flows) (`kickoff` per message, `ChatSession`, deferred tracing) Congratulations! You've successfully built your first CrewAI Flow that combines regular code, direct LLM calls, and crew-based processing to create a comprehensive guide. These foundational skills enable you to create increasingly sophisticated AI applications that can tackle complex, multi-stage problems through a combination of procedural control and collaborative intelligence. diff --git a/docs/en/guides/flows/mastering-flow-state.mdx b/docs/en/guides/flows/mastering-flow-state.mdx index 68a821246..648a82dbd 100644 --- a/docs/en/guides/flows/mastering-flow-state.mdx +++ b/docs/en/guides/flows/mastering-flow-state.mdx @@ -22,6 +22,8 @@ Effective state management enables you to: 5. **Scale your applications** - Support complex workflows with proper data organization 6. **Enable conversational applications** - Store and access conversation history for context-aware AI interactions +For multi-turn chat (`kickoff` per user line, `ChatState`, intent routing, deferred tracing, and `ChatSession`), see [Conversational Flows](/en/guides/flows/conversational-flows). + Let's explore how to leverage these capabilities effectively. ## State Management Fundamentals diff --git a/docs/ko/guides/flows/conversational-flows.mdx b/docs/ko/guides/flows/conversational-flows.mdx new file mode 100644 index 000000000..344fa5f9b --- /dev/null +++ b/docs/ko/guides/flows/conversational-flows.mdx @@ -0,0 +1,342 @@ +--- +title: 대화형 Flow +description: 턴마다 kickoff, 메시지 기록, 의도 라우팅, 트레이싱, WebSocket 브리지로 멀티턴 채팅 앱을 만듭니다. +icon: comments +mode: "wide" +--- + +## 개요 + +대화형 앱은 각 사용자 입력을 **동일한 세션 id**로 **새 flow 실행**으로 처리합니다. CrewAI는 메시지 기록, 선택적 의도 분류, 지연 트레이싱, UI 브리지를 제공하며, `Flow`에 별도 `chat()` API는 없습니다. + +| 개념 | 구현 | +|------|------| +| 세션 id | `kickoff(session_id=...)` → `inputs["id"]` → `state.id` | +| 사용자 입력 | `kickoff(user_message=...)`가 그래프 실행 전 `state.messages`에 추가 | +| 턴 완료 | `FlowFinished`는 **이번 실행**만 의미; 다음 `kickoff`로 대화 계속 | +| 세션 전체 트레이스 | `ConversationalConfig(defer_trace_finalization=True)` + `finalize_session_traces()` | + +## 단일 진입점: `kickoff` + +모든 사용자 메시지에 **`flow.kickoff(user_message=..., session_id=...)`**를 사용하세요 (REST, WebSocket, CLI). `Flow`에 커스텀 `chat()` 래퍼를 만들지 마세요. + +| API | 용도 | +|-----|------| +| `kickoff(user_message=..., session_id=...)` | 각 사용자 메시지 | +| `kickoff_async(...)` | 동일 파라미터; 네이티브 async 진입 | +| `ask()` | 한 스텝 **내부** 블로킹 프롬프트 (마법사, 확인) | +| `@human_feedback` | **스텝 출력** 승인/거부 — 다음 채팅 줄이 아님 | +| `ChatSession.handle_turn(...)` | `kickoff` 위의 전송 계층 (SSE / WebSocket) | + +## 빠른 시작 + +```python +from uuid import uuid4 + +from crewai.flow import ( + ChatState, + ConversationalConfig, + Flow, + listen, + or_, + persist, + router, + start, +) +from crewai.flow.persistence import SQLiteFlowPersistence + + +class SupportFlow(Flow[ChatState]): + conversational_config = ConversationalConfig( + default_intents=["order", "help", "goodbye"], + intent_llm="gpt-4o-mini", + defer_trace_finalization=True, + ) + + @start() + def bootstrap(self): + if not self.state.session_ready: + self.state.session_ready = True + return "ready" + + @router(bootstrap) + def route(self): + # default_intents 설정 시 prepare_conversational_turn에서 last_intent 설정 + return self.state.last_intent or "help" + + @listen("order") + def handle_order(self): + reply = "주문이 배송 중입니다." + self.append_message("assistant", reply) + return reply + + @listen("help") + def handle_help(self): + reply = "무엇을 도와드릴까요?" + self.append_message("assistant", reply) + return reply + + @listen("goodbye") + def handle_goodbye(self): + reply = "안녕히 가세요!" + self.append_message("assistant", reply) + return reply + + @persist(SQLiteFlowPersistence("support.db")) + @listen(or_(handle_order, handle_help, handle_goodbye)) + def finalize(self): + return self.state.model_dump() + + +session_id = str(uuid4()) +flow = SupportFlow() + +flow.kickoff(user_message="주문 어디까지 왔나요?", session_id=session_id) +flow.kickoff(user_message="반품은 어떻게 하나요?", session_id=session_id) +flow.finalize_session_traces() # 전체 대화에 대한 단일 trace 링크 +``` + +## 턴 생명주기 + +`user_message`가 있는 각 `kickoff`는 다음 파이프라인을 실행합니다: + +1. **`_configure_conversational_kickoff`** — `session_id` / `user_message`를 `inputs`에 병합, `ConversationalConfig` 적용, 설정 시 지연 트레이싱 활성화. +2. **상태 복원** — `inputs["id"]`가 있고 `@persist`가 설정되면 최신 스냅샷 로드. +3. **`FlowStarted`** — 지연 세션의 첫 턴에서만 발생. +4. **`prepare_conversational_turn`** — 사용자 메시지를 `state.messages`에 추가, `last_user_message` 설정, `last_intent` 초기화, `intents` / `default_intents` + `intent_llm` 설정 시 분류. +5. **그래프 실행** — `@start` → `@router` → `@listen` 핸들러. +6. **실행 종료** — 지연 활성화 시 턴별 `flow_finished` 및 trace 종료 **건너뜀**; 중첩 `Agent.kickoff()` / crew도 부모 batch를 닫지 않음. + +핸들러는 **`append_message("assistant", reply)`**를 호출해 다음 턴의 `conversation_messages`에 어시스턴트 응답이 포함되게 하세요. 사용자 입력은 kickoff 시 이미 저장됩니다 — 핸들러에서 다시 추가하지 마세요. + +## `ConversationalConfig` (클래스 수준 기본값) + +`Flow` 서브클래스에 `conversational_config: ClassVar[ConversationalConfig | None]`로 설정합니다. + +| 필드 | 기본값 | 목적 | +|------|--------|------| +| `default_intents` | `None` | kickoff 전 자동 분류용 outcome 라벨 | +| `intent_llm` | `None` | 분류용 모델 (intent 사용 시 필수) | +| `interactive_prompt` | `"You: "` | `kickoff(interactive=True)` 프롬프트 | +| `interactive_timeout` | `None` | 대화형 모드 줄 단위 타임아웃 | +| `exit_commands` | `exit`, `quit` | 대화형 모드 종료 단어 | +| `defer_trace_finalization` | `True` | 턴 간 하나의 trace batch 유지 | + +`intents=` 및 `intent_llm=` 키워드로 kickoff마다 재정의할 수 있습니다. + +## `ChatState` (권장 persist 형태) + +```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` | LLM 기록용 `{role, content}` 리스트 | +| `last_user_message` | 이번 턴의 최신 사용자 입력 | +| `last_intent` | 분류 후 라우트 라벨 (사용 시) | +| `session_ready` | 일회성 bootstrap 플래그 | + +`ConversationalInputs`는 `kickoff(inputs={...})`용 `TypedDict`: `id`, `user_message`, `last_intent`. + +## `Flow` 대화 API + +### `kickoff` / `kickoff_async` 파라미터 + +| 파라미터 | 목적 | +|----------|------| +| `user_message` | 이번 턴 텍스트 (또는 `{"role": "user", "content": "..."}`) | +| `session_id` | 대화 UUID → `inputs["id"]` / `state.id` | +| `intents` | kickoff 전 `classify_intent`용 outcome 라벨 | +| `intent_llm` | 분류 LLM (`intents`와 함께 필수) | +| `interactive` | `ask()` CLI 루프 (로컬 데모 전용) | +| `interactive_prompt` | 대화형 모드 프롬프트 | +| `interactive_timeout` | 줄 단위 `ask()` 타임아웃 | +| `exit_commands` | 대화형 모드 종료 단어 | +| `inputs` | 추가 상태 필드 | +| `restore_from_state_id` | 다른 persist flow에서 fork 복원 | + +### 인스턴스 속성 + +| 속성 | 목적 | +|------|------| +| `conversational_config` | 클래스 수준 `ConversationalConfig` | +| `defer_trace_finalization` | 인스턴스 플래그; kickoff 시 config에서 자동 설정 | +| `suppress_flow_events` | 콘솔 flow 패널 숨김; **트레이싱은 계속 기록** | +| `stream` | 스트리밍; `ChatSession.handle_turn(..., stream=True)`와 함께 | + +### 메서드 및 프로퍼티 + +| 이름 | 설명 | +|------|------| +| `append_message(role, content, **extra)` | `state.messages`에 추가 | +| `conversation_messages` | LLM 호출용 읽기 전용 기록 | +| `classify_intent(text, outcomes, *, llm, context=None)` | outcome 매핑 (`@human_feedback`와 동일 collapse) | +| `receive_user_message(text, *, outcomes=None, llm=None)` | 사용자 메시지 추가; 선택적 `last_intent` | +| `finalize_session_traces()` | 지연 `flow_finished` 발생 및 세션 trace batch 종료 | +| `_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 메시지 형식으로 | + +## 의도 라우팅 패턴 + +### A. `ConversationalConfig`로 사전 분류 (가장 단순) + +`default_intents`와 `intent_llm` 설정. 각 kickoff가 `@router` 전에 분류; `route()`에서 `self.state.last_intent` 읽기. + +### B. `@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 +``` + +웹 리서치나 다단계 tool이 필요하면 **`@listen("RESEARCH")`** 등에서 `Agent.kickoff()`와 tool 사용 — 단순 `LLM.call()` 대신. + +## flow가 끝났지만 사용자는 계속 대화할 때 + +`FlowFinished`는 **이번 그래프 실행**이 완료됨을 의미합니다. 같은 `session_id`로 또 다른 `kickoff`로 대화가 이어집니다. `@persist`가 `messages`, 플래그, 컨텍스트를 복원합니다. + +**Persist 패턴:** 전체 `Flow` 클래스보다 **단일 종료 스텝**(예: `finalize`)에 `@persist`를 두는 것이 좋습니다. 클래스 수준 persist는 매 메서드 후 저장하며, `load_state`는 최신 행을 사용해 같은 턴의 핸들러 업데이트를 놓칠 수 있습니다. + +후속 채팅 줄에 `@human_feedback`를 쓰지 마세요. 특정 스텝 출력을 사람이 승인해야 할 때만 사용하세요. + +## 턴 간 트레이싱 + +`defer_trace_finalization=True` (`ConversationalConfig` 기본값): + +- 채팅 세션 전체에 **하나의 trace batch**. +- 첫 턴에만 **`flow_started`**; `finalize_session_traces()`에서 **`flow_finished`** 한 번. +- 턴별 `kickoff`는 “Trace batch finalized”를 출력하지 않음. +- **중첩 작업** (`Agent.kickoff()`, crew, Exa tool)은 **부모** batch에 추가; 내부 `AgentExecutor` flow가 세션 batch를 조기 종료하지 않음. + +```python +try: + while True: + line = input("You: ").strip() + if not line: + break + flow.kickoff(user_message=line, session_id=session_id) +finally: + flow.finalize_session_traces() +``` + +지연 활성화 시 `ChatSession.close()`가 `finalize_session_traces()`를 호출합니다. + +`suppress_flow_events=True`는 Rich 콘솔 패널만 숨깁니다. trace 및 method 이벤트는 계속 발생합니다. + +## `ChatSession` (WebSocket / SSE) + +UI용 `kickoff` 래퍼 및 선택적 이벤트 브리지. + +```python +from crewai.flow import ChatMessage, ChatSession + + +def on_event(msg: ChatMessage): + print(msg.type, msg.payload) + + +session = ChatSession( + flow, + session_id="channel-1", + intents=["order", "help"], + intent_llm="gpt-4o-mini", + on_event=on_event, +) + +turn = session.handle_turn("안녕하세요") +print(turn.output, turn.intent, len(turn.messages)) + +for msg in session.iter_turn_stream("더 알려주세요"): + print(msg.type, msg.payload) + +session.close() # 지연 시 finalize_session_traces +``` + +| 타입 | 목적 | +|------|------| +| `ChatSession` | 세션; `handle_turn`, `iter_turn_stream`, `close` | +| `TurnResult` | `session_id`, `output`, `intent`, `messages`, 선택적 `streaming` | +| `ChatMessage` | wire 형식: `type`, `session_id`, `payload`, `seq` | +| `ConversationEventBridge` | bus 이벤트 → `ChatMessage` | + +`ChatMessage.type`: `user_message`, `assistant_delta`, `assistant_done`, `turn_started`, `turn_finished`, `error`, `tool_started`, `tool_finished`. + +`stamp_conversation_fingerprint(event, session_id)` — 외부 디스패처용 `conversation_id` 설정. + +## `QueueInputProvider` (WebSocket에서 블로킹 `ask`) + +메서드 내부에서 `ask()`를 호출하고 소켓으로 메시지를 받을 때: + +```python +from crewai.flow import Flow, QueueInputProvider + +provider = QueueInputProvider() +flow = MyFlow(input_provider=provider) + +provider.push(session_id, user_text) + +reply = flow.ask("You: ", metadata={"session_id": session_id}) +provider.close_session(session_id) +``` + +## 스트리밍 + +`Flow` 클래스에 `stream = True`. `kickoff(...)` 또는 `ChatSession.handle_turn(..., stream=True)`로 `assistant_delta` 등 이벤트 발생. + +## import + +```python +from crewai.flow import ( + ChatMessage, + ChatSession, + ChatState, + ConversationalConfig, + ConversationalInputs, + ConversationEventBridge, + Flow, + QueueInputProvider, + TurnResult, + listen, + persist, + router, + start, +) +``` + +## 참고 + +- [Flow 상태 관리 마스터하기](/ko/guides/flows/mastering-flow-state) +- [첫 Flow 만들기](/ko/guides/flows/first-flow) +- 데모: `lib/crewai/runner_conversational_flow_simple.py` diff --git a/docs/ko/guides/flows/first-flow.mdx b/docs/ko/guides/flows/first-flow.mdx index 72ed9866a..8c222f69f 100644 --- a/docs/ko/guides/flows/first-flow.mdx +++ b/docs/ko/guides/flows/first-flow.mdx @@ -607,6 +607,7 @@ result = ContentCrew().crew().kickoff(inputs={ 3. 더 복잡한 병렬 실행을 위해 `and_` 및 `or_` 함수를 탐색해 보세요. 4. flow를 외부 API, 데이터베이스 또는 사용자 인터페이스에 연결해 보세요. 5. 여러 전문화된 crew를 하나의 flow에서 결합해 보세요. +6. [대화형 Flow](/ko/guides/flows/conversational-flows)로 멀티턴 채팅 앱 구축 (`kickoff` per message, `ChatSession`, 지연 트레이싱) 축하합니다! 정규 코드, 직접적인 LLM 호출, crew 기반 처리를 결합하여 포괄적인 가이드를 생성하는 첫 번째 CrewAI Flow를 성공적으로 구축하셨습니다. 이러한 기초적인 역량을 바탕으로 절차적 제어와 협업적 인텔리전스를 결합하여 복잡하고 다단계의 문제를 해결할 수 있는 점점 더 정교한 AI 애플리케이션을 만들 수 있습니다. diff --git a/docs/ko/guides/flows/mastering-flow-state.mdx b/docs/ko/guides/flows/mastering-flow-state.mdx index eafd24b29..5e7727cb1 100644 --- a/docs/ko/guides/flows/mastering-flow-state.mdx +++ b/docs/ko/guides/flows/mastering-flow-state.mdx @@ -22,6 +22,8 @@ State 관리는 모든 고급 AI 워크플로우의 중추입니다. CrewAI Flow 5. **애플리케이션 확장** - 적절한 데이터 조직을 통해 복잡한 워크플로를 지원할 수 있습니다. 6. **대화형 애플리케이션 활성화** - 컨텍스트 기반 AI 상호작용을 위해 대화 내역을 저장하고 접근할 수 있습니다. +멀티턴 채팅(`kickoff` per user line, `ChatState`, 의도 라우팅, 지연 트레이싱, `ChatSession`)은 [대화형 Flow](/ko/guides/flows/conversational-flows)를 참고하세요. + 이러한 기능을 효과적으로 활용하는 방법을 살펴보겠습니다. ## 상태 관리 기본 사항 diff --git a/docs/pt-BR/guides/flows/conversational-flows.mdx b/docs/pt-BR/guides/flows/conversational-flows.mdx new file mode 100644 index 000000000..27089662d --- /dev/null +++ b/docs/pt-BR/guides/flows/conversational-flows.mdx @@ -0,0 +1,344 @@ +--- +title: Flows Conversacionais +description: Crie apps de chat multi-turno com kickoff por turno, histórico de mensagens, roteamento de intenção, tracing e pontes WebSocket. +icon: comments +mode: "wide" +--- + +## Visão geral + +Apps conversacionais tratam cada linha do usuário como uma **nova execução do flow** com o **mesmo id de sessão**. A CrewAI oferece helpers para histórico de mensagens, classificação opcional de intenção, tracing adiado e pontes para UI — sem uma API `chat()` separada em `Flow`. + +| Conceito | Implementação | +|---------|----------------| +| Id de sessão | `kickoff(session_id=...)` → `inputs["id"]` → `state.id` | +| Linha do usuário | `kickoff(user_message=...)` acrescenta em `state.messages` antes do grafo rodar | +| Fim do turno | `FlowFinished` só para **esta execução**; o chat segue no próximo `kickoff` | +| Trace da sessão | `ConversationalConfig(defer_trace_finalization=True)` + `finalize_session_traces()` | + +## Um ponto de entrada: `kickoff` + +Use **`flow.kickoff(user_message=..., session_id=...)`** para cada mensagem (REST, WebSocket, CLI). Não crie um wrapper `chat()` customizado em `Flow`. + +| API | Uso | +|-----|-----| +| `kickoff(user_message=..., session_id=...)` | Cada mensagem do usuário | +| `kickoff_async(...)` | Mesmos parâmetros; entrada async nativa | +| `ask()` | Prompt bloqueante **dentro** de um passo (wizard, esclarecimento) | +| `@human_feedback` | Aprovar/rejeitar **saída de um passo** — não a próxima linha do chat | +| `ChatSession.handle_turn(...)` | Camada de transporte sobre `kickoff` (SSE / WebSocket) | + +## Início rápido + +```python +from uuid import uuid4 + +from crewai.flow import ( + ChatState, + ConversationalConfig, + Flow, + listen, + or_, + persist, + router, + start, +) +from crewai.flow.persistence import SQLiteFlowPersistence + + +class SupportFlow(Flow[ChatState]): + conversational_config = ConversationalConfig( + default_intents=["order", "help", "goodbye"], + intent_llm="gpt-4o-mini", + defer_trace_finalization=True, + ) + + @start() + def bootstrap(self): + if not self.state.session_ready: + self.state.session_ready = True + return "ready" + + @router(bootstrap) + def route(self): + # last_intent definido em prepare_conversational_turn quando default_intents está setado + return self.state.last_intent or "help" + + @listen("order") + def handle_order(self): + reply = "Seu pedido está a caminho." + self.append_message("assistant", reply) + return reply + + @listen("help") + def handle_help(self): + reply = "Como posso ajudar?" + self.append_message("assistant", reply) + return reply + + @listen("goodbye") + def handle_goodbye(self): + reply = "Até logo!" + self.append_message("assistant", reply) + return reply + + @persist(SQLiteFlowPersistence("support.db")) + @listen(or_(handle_order, handle_help, handle_goodbye)) + def finalize(self): + return self.state.model_dump() + + +session_id = str(uuid4()) +flow = SupportFlow() + +flow.kickoff(user_message="Onde está meu pedido?", session_id=session_id) +flow.kickoff(user_message="E as devoluções?", session_id=session_id) +flow.finalize_session_traces() # um link de trace para o chat inteiro +``` + +## Ciclo de vida do turno + +Cada `kickoff` com `user_message` executa este pipeline: + +1. **`_configure_conversational_kickoff`** — mescla `session_id` / `user_message` em `inputs`, aplica `ConversationalConfig`, habilita tracing adiado quando configurado. +2. **Restauração de estado** — se `inputs["id"]` existe e `@persist` está configurado, carrega o snapshot mais recente. +3. **`FlowStarted`** — emitido apenas no primeiro turno da sessão adiada. +4. **`prepare_conversational_turn`** — acrescenta a mensagem do usuário em `state.messages`, define `last_user_message`, limpa `last_intent`, classifica opcionalmente quando `intents` / `default_intents` + `intent_llm` estão definidos. +5. **Execução do grafo** — `@start` → `@router` → handlers `@listen`. +6. **Fim da execução** — `flow_finished` por turno e finalização de trace são **ignorados** com adiamento; `Agent.kickoff()` / crews aninhados também não fecham o batch pai. + +Os handlers devem chamar **`append_message("assistant", reply)`** para que o próximo turno inclua a resposta do assistente. A linha do usuário já é salva no kickoff — não acrescente de novo nos handlers. + +## `ConversationalConfig` (padrões em nível de classe) + +Defina na subclasse de `Flow` como `conversational_config: ClassVar[ConversationalConfig | None]`. + +| Campo | Padrão | Propósito | +|-------|---------|-----------| +| `default_intents` | `None` | Rótulos de outcome para classificação automática antes do kickoff | +| `intent_llm` | `None` | Modelo para classificação (obrigatório quando há intents) | +| `interactive_prompt` | `"You: "` | Prompt para `kickoff(interactive=True)` | +| `interactive_timeout` | `None` | Timeout por linha no modo interativo | +| `exit_commands` | `exit`, `quit` | Palavras que encerram o modo interativo | +| `defer_trace_finalization` | `True` | Manter um batch de trace aberto entre turnos | + +Sobrescreva por kickoff com `intents=` e `intent_llm=`. + +## `ChatState` (formato persistido recomendado) + +```python +from crewai.flow import ChatState + + +class MyChatState(ChatState): + # Herdados: id, messages, last_user_message, last_intent, session_ready + research_turn_count: int = 0 + custom_flag: bool = False +``` + +| Campo | Função | +|-------|--------| +| `id` | UUID da sessão (igual a `session_id` / `inputs["id"]`) | +| `messages` | `list` de `{role, content}` para histórico de LLM | +| `last_user_message` | Última linha do usuário neste turno | +| `last_intent` | Rótulo de rota após classificação (se usado) | +| `session_ready` | Flag de bootstrap único (permissões, caches, etc.) | + +`ConversationalInputs` é um `TypedDict` para `kickoff(inputs={...})`: `id`, `user_message`, `last_intent`. + +## API conversacional em `Flow` + +### Parâmetros de `kickoff` / `kickoff_async` + +| Parâmetro | Propósito | +|-----------|-----------| +| `user_message` | Texto deste turno (ou `{"role": "user", "content": "..."}`) | +| `session_id` | UUID da conversa → `inputs["id"]` / `state.id` | +| `intents` | Rótulos de outcome para `classify_intent` antes do kickoff | +| `intent_llm` | LLM para classificação (obrigatório com `intents`) | +| `interactive` | Loop CLI via `ask()` (só demos locais) | +| `interactive_prompt` | Prompt no modo interativo | +| `interactive_timeout` | Timeout de `ask()` por linha | +| `exit_commands` | Palavras que encerram o modo interativo | +| `inputs` | Campos extras de estado (mesclados com chaves conversacionais) | +| `restore_from_state_id` | Hidratação fork de outro flow persistido | + +### Atributos de instância + +| Atributo | Propósito | +|-----------|-----------| +| `conversational_config` | Padrões `ConversationalConfig` em nível de classe | +| `defer_trace_finalization` | Flag de instância; definida automaticamente a partir do config no kickoff | +| `suppress_flow_events` | Oculta painéis Rich no console; **tracing ainda registra** eventos | +| `stream` | Habilita streaming; use com `ChatSession.handle_turn(..., stream=True)` | + +### Métodos e propriedades + +| Nome | Descrição | +|------|-------------| +| `append_message(role, content, **extra)` | Acrescenta em `state.messages` (roles: `user`, `assistant`, `system`, `tool`) | +| `conversation_messages` | Histórico somente leitura para chamadas LLM | +| `classify_intent(text, outcomes, *, llm, context=None)` | Mapeia texto a um outcome (mesma lógica de `@human_feedback`) | +| `receive_user_message(text, *, outcomes=None, llm=None)` | Acrescenta mensagem do usuário; opcionalmente define `last_intent` | +| `finalize_session_traces()` | Emite `flow_finished` adiado e finaliza o batch de trace da sessão | +| `_should_defer_trace_finalization()` | Se este flow adia finalização de trace por turno | +| `input_history` | Trilha de auditoria de prompts e respostas de `ask()` | + +### Helpers do módulo (`crewai.flow.conversation`) + +Importáveis para testes ou orquestração customizada: + +| Função | Descrição | +|----------|-------------| +| `normalize_kickoff_inputs(inputs, user_message=..., session_id=...)` | Mescla kwargs conversacionais em `inputs` | +| `get_conversation_messages(flow)` | Lê mensagens do estado ou buffer interno | +| `append_message(flow, role, content, **extra)` | Igual ao método de instância | +| `prepare_conversational_turn(flow, ...)` | Hidratação do turno (geralmente chamado pelo kickoff) | +| `receive_user_message(flow, text, ...)` | Igual ao método de instância | +| `set_state_field(flow, name, value)` | Define campo em estado dict ou Pydantic | +| `get_conversational_config(flow)` | Lê `conversational_config` da classe | +| `input_history_to_messages(entries)` | Converte `input_history` para formato de mensagens LLM | + +## Padrões de roteamento de intenção + +### A. Pré-classificar via `ConversationalConfig` (mais simples) + +Defina `default_intents` e `intent_llm`. Cada kickoff classifica antes do `@router`; leia `self.state.last_intent` em `route()`. + +### B. Classificar dentro do `@router` (prompts mais ricos) + +Defina `default_intents=None` para o kickoff só acrescentar a mensagem. Em `route()`, chame `classify_intent` com prompt ou descrições customizadas: + +```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 +``` + +Use **`@listen("RESEARCH")`** (ou similar) para passos com `Agent.kickoff()` e ferramentas — não `LLM.call()` puro — quando precisar de pesquisa web ou uso multi-etapa de tools. + +## Quando o flow termina mas o usuário continua conversando + +`FlowFinished` significa que **esta execução do grafo** terminou. A conversa segue com outro `kickoff` e o mesmo `session_id`. `@persist` restaura `messages`, flags e contexto. + +**Padrão de persistência:** prefira `@persist` em um **único passo terminal** (por exemplo `finalize`) em vez de na classe `Flow` inteira. Persist em nível de classe salva após cada método; `load_state` usa a linha mais recente, que pode ser snapshot no meio da execução e perder atualizações dos handlers no mesmo turno. + +Não use `@human_feedback` para linhas de chat de follow-up, a menos que um humano precise aprovar uma saída específica antes de exibi-la. + +## Tracing entre turnos + +Com `defer_trace_finalization=True` (padrão em `ConversationalConfig`): + +- **Um batch de trace** para toda a sessão de chat. +- **`flow_started`** só no primeiro turno; **`flow_finished`** uma vez em `finalize_session_traces()`. +- **`kickoff` por turno** não exibe “Trace batch finalized”. +- **Trabalho aninhado** (`Agent.kickoff()`, crews, tools Exa) acrescenta ao batch **pai**; flows internos de `AgentExecutor` não fecham o batch da sessão cedo. + +```python +try: + while True: + line = input("You: ").strip() + if not line: + break + flow.kickoff(user_message=line, session_id=session_id) +finally: + flow.finalize_session_traces() +``` + +`ChatSession.close()` chama `finalize_session_traces()` quando o adiamento está habilitado. + +`suppress_flow_events=True` só oculta painéis do console; eventos de trace e método ainda são emitidos. + +## `ChatSession` (WebSocket / SSE) + +Envolve `kickoff` e ponte opcional de eventos para UIs. + +```python +from crewai.flow import ChatMessage, ChatSession + + +def on_event(msg: ChatMessage): + print(msg.type, msg.payload) + + +session = ChatSession( + flow, + session_id="channel-1", + intents=["order", "help"], + intent_llm="gpt-4o-mini", + on_event=on_event, +) + +turn = session.handle_turn("Olá") +print(turn.output, turn.intent, len(turn.messages)) + +for msg in session.iter_turn_stream("Conte mais"): + print(msg.type, msg.payload) + +session.close() # finalize_session_traces quando adiado +``` + +| Tipo | Propósito | +|------|-----------| +| `ChatSession` | Uma sessão; `handle_turn`, `iter_turn_stream`, `close` | +| `TurnResult` | `session_id`, `output`, `intent`, `messages`, `streaming` opcional | +| `ChatMessage` | Formato wire: `type`, `session_id`, `payload`, `seq` | +| `ConversationEventBridge` | Mapeia eventos do bus → `ChatMessage` (`assistant_delta`, `tool_*`, …) | + +Valores de `ChatMessage.type`: `user_message`, `assistant_delta`, `assistant_done`, `turn_started`, `turn_finished`, `error`, `tool_started`, `tool_finished`. + +`stamp_conversation_fingerprint(event, session_id)` — define `conversation_id` em eventos para dispatchers externos. + +## `QueueInputProvider` (`ask` bloqueante via WebSocket) + +Para flows que chamam `ask()` dentro de um método enquanto mensagens chegam por socket: + +```python +from crewai.flow import Flow, QueueInputProvider + +provider = QueueInputProvider() +flow = MyFlow(input_provider=provider) + +# Handler WebSocket: +provider.push(session_id, user_text) + +# Dentro de um método do flow: +reply = flow.ask("You: ", metadata={"session_id": session_id}) +provider.close_session(session_id) # desbloqueia com None +``` + +## Streaming + +Defina `stream = True` na classe `Flow`. Use `kickoff(...)` ou `ChatSession.handle_turn(..., stream=True)` para `ConversationEventBridge` emitir `assistant_delta` (e eventos relacionados). + +## Imports + +```python +from crewai.flow import ( + ChatMessage, + ChatSession, + ChatState, + ConversationalConfig, + ConversationalInputs, + ConversationEventBridge, + Flow, + QueueInputProvider, + TurnResult, + listen, + persist, + router, + start, +) +``` + +## Veja também + +- [Dominando o Gerenciamento de Estado em Flows](/pt-BR/guides/flows/mastering-flow-state) — persistência, estado Pydantic, `@persist` +- [Construa Seu Primeiro Flow](/pt-BR/guides/flows/first-flow) — fundamentos de flow +- Demo: `lib/crewai/runner_conversational_flow_simple.py` — REPL mínimo com `RESEARCH` + agente Exa diff --git a/docs/pt-BR/guides/flows/first-flow.mdx b/docs/pt-BR/guides/flows/first-flow.mdx index 07ed7ae93..cb2330a90 100644 --- a/docs/pt-BR/guides/flows/first-flow.mdx +++ b/docs/pt-BR/guides/flows/first-flow.mdx @@ -614,6 +614,7 @@ Agora que você construiu seu primeiro flow, pode: 3. Explorar as funções `and_` e `or_` para execuções paralelas e mais complexas 4. Conectar seu flow a APIs externas, bancos de dados ou interfaces de usuário 5. Combinar múltiplos crews especializados em um único flow +6. Criar apps de chat multi-turn com [Flows conversacionais](/pt-BR/guides/flows/conversational-flows) (`kickoff` por mensagem, `ChatSession`, tracing adiado) Parabéns! Você construiu seu primeiro CrewAI Flow que combina código regular, chamadas diretas a LLM e processamento baseado em crews para criar um guia abrangente. Essas habilidades fundamentais permitem criar aplicações de IA cada vez mais sofisticadas, capazes de resolver problemas complexos de múltiplas etapas por meio de controle procedural e inteligência colaborativa. diff --git a/docs/pt-BR/guides/flows/mastering-flow-state.mdx b/docs/pt-BR/guides/flows/mastering-flow-state.mdx index 6589b51ad..1d3e6ee42 100644 --- a/docs/pt-BR/guides/flows/mastering-flow-state.mdx +++ b/docs/pt-BR/guides/flows/mastering-flow-state.mdx @@ -22,6 +22,8 @@ Um gerenciamento de estado efetivo possibilita que você: 5. **Escalone suas aplicações** – Ofereça suporte a workflows complexos com organização apropriada dos dados 6. **Habilite aplicações conversacionais** – Armazene e acesse o histórico da conversa para interações de IA com contexto +Para chat multi-turn (`kickoff` por linha do usuário, `ChatState`, roteamento por intenção, tracing adiado e `ChatSession`), veja [Flows conversacionais](/pt-BR/guides/flows/conversational-flows). + Vamos explorar como aproveitar essas capacidades de forma eficiente. ## Fundamentos do Gerenciamento de Estado