mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-01 21:28:10 +00:00
improve one less route
This commit is contained in:
@@ -46,7 +46,9 @@ from crewai.flow.conversation import (
|
||||
get_conversation_messages,
|
||||
receive_user_message as _receive_user_message,
|
||||
)
|
||||
from crewai.flow.dsl import listen, router, start
|
||||
from crewai.flow.dsl import listen, start
|
||||
from crewai.flow.dsl._utils import _set_flow_method_definition
|
||||
from crewai.flow.flow_definition import FlowMethodDefinition
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
@@ -72,6 +74,15 @@ def _iter_condition_labels(condition: Any) -> set[str]:
|
||||
return set()
|
||||
|
||||
|
||||
def _conversation_start_router(func: Callable[..., Any]) -> Any:
|
||||
wrapper = start()(func)
|
||||
_set_flow_method_definition(
|
||||
wrapper,
|
||||
FlowMethodDefinition(start=True, router=True),
|
||||
)
|
||||
return wrapper
|
||||
|
||||
|
||||
class _ConversationalMixin:
|
||||
"""Experimental conversational graph for ``Flow``.
|
||||
|
||||
@@ -85,10 +96,7 @@ class _ConversationalMixin:
|
||||
conversational: ClassVar[bool] = False
|
||||
conversational_config: ClassVar[ConversationConfig | None] = None
|
||||
builtin_routes: ClassVar[tuple[str, ...]] = ("converse", "end")
|
||||
internal_routes: ClassVar[tuple[str, ...]] = (
|
||||
"answer_from_history",
|
||||
"conversation_start",
|
||||
)
|
||||
internal_routes: ClassVar[tuple[str, ...]] = ("answer_from_history",)
|
||||
builtin_route_descriptions: ClassVar[dict[str, str]] = {
|
||||
"converse": (
|
||||
"Ordinary chat, follow-ups, summaries, clarifications, and "
|
||||
@@ -138,23 +146,21 @@ class _ConversationalMixin:
|
||||
def kickoff(self, *args: Any, **kwargs: Any) -> Any:
|
||||
pass
|
||||
|
||||
@start()
|
||||
@_conversational_only
|
||||
def conversation_start(self) -> str | None:
|
||||
"""Internal Flow entrypoint that hands the user message to the router.
|
||||
"""Return the current user message for conversational route selection.
|
||||
|
||||
In conversational mode, ``Flow.kickoff_async`` runs all ``@start``
|
||||
methods sequentially and this one is registered last, so any user
|
||||
``@start`` methods (e.g. permission loading) have already finished
|
||||
before the returned value triggers ``route_conversation``.
|
||||
This remains as a plain overridable helper for compatibility. It is not
|
||||
registered as a Flow method; ``route_conversation`` is the synthetic
|
||||
built-in start/router that begins a conversational turn.
|
||||
"""
|
||||
state = cast(ConversationState, self.state)
|
||||
return state.current_user_message
|
||||
|
||||
@router(conversation_start)
|
||||
@_conversation_start_router
|
||||
@_conversational_only
|
||||
def route_conversation(self) -> str:
|
||||
"""Route the current turn to a listener label."""
|
||||
self.conversation_start()
|
||||
state = cast(ConversationState, self.state)
|
||||
context = self.build_router_context()
|
||||
previous_intent = state.last_intent
|
||||
@@ -651,16 +657,16 @@ class _ConversationalMixin:
|
||||
if not type(self)._is_conversational():
|
||||
return start_methods, False
|
||||
|
||||
conversation_start = "conversation_start"
|
||||
if conversation_start not in {str(method) for method in start_methods}:
|
||||
route_conversation = "route_conversation"
|
||||
if route_conversation not in {str(method) for method in start_methods}:
|
||||
return start_methods, False
|
||||
|
||||
ordered_starts = [
|
||||
method for method in start_methods if str(method) != conversation_start
|
||||
method for method in start_methods if str(method) != route_conversation
|
||||
]
|
||||
ordered_starts.append(
|
||||
next(
|
||||
method for method in start_methods if str(method) == conversation_start
|
||||
method for method in start_methods if str(method) == route_conversation
|
||||
)
|
||||
)
|
||||
return ordered_starts, True
|
||||
|
||||
@@ -39,9 +39,7 @@ class FlowConversationalDefinition(BaseModel):
|
||||
visible_agent_outputs: list[str] | Literal["all"] | None = None
|
||||
defer_trace_finalization: bool = True
|
||||
builtin_routes: list[str] = Field(default_factory=lambda: ["converse", "end"])
|
||||
internal_routes: list[str] = Field(
|
||||
default_factory=lambda: ["answer_from_history", "conversation_start"]
|
||||
)
|
||||
internal_routes: list[str] = Field(default_factory=lambda: ["answer_from_history"])
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -313,7 +313,7 @@ def _build_conversational_definition(
|
||||
internal_routes = getattr(
|
||||
flow_class,
|
||||
"internal_routes",
|
||||
("answer_from_history", "conversation_start"),
|
||||
("answer_from_history",),
|
||||
)
|
||||
if config is None:
|
||||
return FlowConversationalDefinition(
|
||||
|
||||
@@ -598,9 +598,9 @@ class TestConversationalFlow:
|
||||
"""Conversational flows: user ``@start`` methods finish before router fires.
|
||||
|
||||
Non-chat flows run ``@start`` methods in parallel via ``asyncio.gather``,
|
||||
which would race with ``conversation_start`` and let the router fire
|
||||
which would race with ``route_conversation`` and let the router fire
|
||||
before user setup finished. In conversational mode the framework runs
|
||||
them sequentially, with ``conversation_start`` last.
|
||||
them sequentially, with ``route_conversation`` last.
|
||||
"""
|
||||
order: list[str] = []
|
||||
|
||||
@@ -643,15 +643,10 @@ class TestConversationalFlow:
|
||||
assert "attach_bus" in order # still fires every turn
|
||||
assert "route_turn" in order
|
||||
|
||||
def test_subclass_can_override_conversation_start_without_redecorating(
|
||||
def test_subclass_can_override_conversation_start_helper(
|
||||
self,
|
||||
) -> None:
|
||||
"""Overriding an inherited ``@start`` method must not unregister it.
|
||||
|
||||
Before the metaclass fix, subclasses had to re-apply ``@start()`` on
|
||||
every override or the parent's ``conversation_start`` would silently
|
||||
drop out of the start registry — leaving the flow with nothing to fire.
|
||||
"""
|
||||
"""The compatibility helper remains overridable without adding a Flow node."""
|
||||
|
||||
bootstrap_calls: list[str] = []
|
||||
|
||||
@@ -673,6 +668,10 @@ class TestConversationalFlow:
|
||||
flow.handle_turn("hi")
|
||||
|
||||
assert bootstrap_calls == ["ran"]
|
||||
assert "conversation_start" not in BootstrapFlow.flow_definition().methods
|
||||
route_definition = BootstrapFlow.flow_definition().methods["route_conversation"]
|
||||
assert route_definition.start is True
|
||||
assert route_definition.router is True
|
||||
assert flow.state.messages[-1].content == "worked"
|
||||
|
||||
@conversational_graph_broken
|
||||
|
||||
@@ -223,10 +223,11 @@ def test_flow_definition_includes_conversational_builtins_when_enabled():
|
||||
assert definition.conversational.enabled is True
|
||||
assert definition.conversational.defer_trace_finalization is True
|
||||
assert definition.conversational.builtin_routes == ["converse", "end"]
|
||||
assert "conversation_start" in methods
|
||||
assert "conversation_start" not in methods
|
||||
assert "route_conversation" in methods
|
||||
assert "converse_turn" in methods
|
||||
assert methods["conversation_start"].start is True
|
||||
assert methods["route_conversation"].start is True
|
||||
assert methods["route_conversation"].router is True
|
||||
|
||||
|
||||
def test_flow_definition_serializes_conversational_config():
|
||||
@@ -260,7 +261,7 @@ def test_flow_definition_serializes_conversational_config():
|
||||
assert conversational.router.fallback_intent == "end"
|
||||
|
||||
|
||||
def test_flow_definition_preserves_undecorated_conversational_override():
|
||||
def test_flow_definition_uses_collapsed_conversational_router_start():
|
||||
class ChatFlow(Flow):
|
||||
conversational = True
|
||||
|
||||
@@ -269,8 +270,10 @@ def test_flow_definition_preserves_undecorated_conversational_override():
|
||||
|
||||
methods = ChatFlow.flow_definition().methods
|
||||
|
||||
assert methods["conversation_start"].start is True
|
||||
assert "conversation_start" not in methods
|
||||
assert "route_conversation" in methods
|
||||
assert methods["route_conversation"].start is True
|
||||
assert methods["route_conversation"].router is True
|
||||
|
||||
|
||||
def test_flow_definition_serializes_human_feedback_metadata():
|
||||
|
||||
Reference in New Issue
Block a user