reasoning logs

This commit is contained in:
João Moura
2025-05-20 14:21:21 -07:00
parent 8d2928e49a
commit 50b8f83428
5 changed files with 271 additions and 5 deletions

View File

@@ -56,6 +56,11 @@ from .tool_usage_events import (
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from .reasoning_events import (
AgentReasoningStartedEvent,
AgentReasoningCompletedEvent,
AgentReasoningFailedEvent,
)
class EventListener(BaseEventListener):
@@ -406,5 +411,30 @@ class EventListener(BaseEventListener):
self.formatter.current_crew_tree,
)
# ----------- REASONING EVENTS -----------
@crewai_event_bus.on(AgentReasoningStartedEvent)
def on_agent_reasoning_started(source, event: AgentReasoningStartedEvent):
self.formatter.handle_reasoning_started(
self.formatter.current_agent_branch,
event.attempt,
self.formatter.current_crew_tree,
)
@crewai_event_bus.on(AgentReasoningCompletedEvent)
def on_agent_reasoning_completed(source, event: AgentReasoningCompletedEvent):
self.formatter.handle_reasoning_completed(
event.plan,
event.ready,
self.formatter.current_crew_tree,
)
@crewai_event_bus.on(AgentReasoningFailedEvent)
def on_agent_reasoning_failed(source, event: AgentReasoningFailedEvent):
self.formatter.handle_reasoning_failed(
event.error,
self.formatter.current_crew_tree,
)
event_listener = EventListener()

View File

@@ -43,6 +43,11 @@ from .tool_usage_events import (
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from .reasoning_events import (
AgentReasoningStartedEvent,
AgentReasoningCompletedEvent,
AgentReasoningFailedEvent,
)
EventTypes = Union[
CrewKickoffStartedEvent,
@@ -74,4 +79,7 @@ EventTypes = Union[
LLMStreamChunkEvent,
LLMGuardrailStartedEvent,
LLMGuardrailCompletedEvent,
AgentReasoningStartedEvent,
AgentReasoningCompletedEvent,
AgentReasoningFailedEvent,
]

View File

@@ -0,0 +1,31 @@
from crewai.utilities.events.base_events import BaseEvent
class AgentReasoningStartedEvent(BaseEvent):
"""Event emitted when an agent starts reasoning about a task."""
type: str = "agent_reasoning_started"
agent_role: str
task_id: str
attempt: int = 1 # The current reasoning/refinement attempt
class AgentReasoningCompletedEvent(BaseEvent):
"""Event emitted when an agent finishes its reasoning process."""
type: str = "agent_reasoning_completed"
agent_role: str
task_id: str
plan: str
ready: bool
attempt: int = 1
class AgentReasoningFailedEvent(BaseEvent):
"""Event emitted when the reasoning process fails."""
type: str = "agent_reasoning_failed"
agent_role: str
task_id: str
error: str
attempt: int = 1

View File

@@ -15,6 +15,7 @@ class ConsoleFormatter:
current_method_branch: Optional[Tree] = None
current_lite_agent_branch: Optional[Tree] = None
tool_usage_counts: Dict[str, int] = {}
current_reasoning_branch: Optional[Tree] = None # Track reasoning status
def __init__(self, verbose: bool = False):
self.console = Console(width=None)
@@ -399,7 +400,11 @@ class ConsoleFormatter:
tree_to_use = branch_to_use or crew_tree
if branch_to_use is None or tree_to_use is None:
return None
# If we don't have a valid branch, default to crew_tree if provided
if crew_tree is not None:
branch_to_use = tree_to_use = crew_tree
else:
return None
# Update tool usage count
self.tool_usage_counts[tool_name] = self.tool_usage_counts.get(tool_name, 0) + 1
@@ -501,7 +506,11 @@ class ConsoleFormatter:
tree_to_use = branch_to_use or crew_tree
if branch_to_use is None or tree_to_use is None:
return None
# If we don't have a valid branch, default to crew_tree if provided
if crew_tree is not None:
branch_to_use = tree_to_use = crew_tree
else:
return None
# Only add thinking status if we don't have a current tool branch
if self.current_tool_branch is None:
@@ -797,7 +806,7 @@ class ConsoleFormatter:
tree_to_use = branch_to_use or crew_tree
if branch_to_use is None or tree_to_use is None:
# If we don't have a valid branch, use crew_tree as the branch if available
# If we don't have a valid branch, default to crew_tree if provided
if crew_tree is not None:
branch_to_use = tree_to_use = crew_tree
else:
@@ -982,3 +991,118 @@ class ConsoleFormatter:
"Knowledge Search Failed", "Search Error", "red", Error=error
)
self.print_panel(error_content, "Search Error", "red")
# ----------- AGENT REASONING EVENTS -----------
def handle_reasoning_started(
self,
agent_branch: Optional[Tree],
attempt: int,
crew_tree: Optional[Tree],
) -> Optional[Tree]:
"""Handle agent reasoning started (or refinement) event."""
if not self.verbose:
return None
# Prefer LiteAgent branch if we are within a LiteAgent context
branch_to_use = self.current_lite_agent_branch or agent_branch
tree_to_use = branch_to_use or crew_tree
if branch_to_use is None or tree_to_use is None:
# If we don't have a valid branch, default to crew_tree if provided
if crew_tree is not None:
branch_to_use = tree_to_use = crew_tree
else:
return None
# Reuse existing reasoning branch if present
reasoning_branch = self.current_reasoning_branch
if reasoning_branch is None:
reasoning_branch = branch_to_use.add("")
self.current_reasoning_branch = reasoning_branch
# Build label text depending on attempt
status_text = (
f"Reasoning (Attempt {attempt})" if attempt > 1 else "Reasoning..."
)
self.update_tree_label(reasoning_branch, "🧠", status_text, "blue")
self.print(tree_to_use)
self.print()
return reasoning_branch
def handle_reasoning_completed(
self,
plan: str,
ready: bool,
crew_tree: Optional[Tree],
) -> None:
"""Handle agent reasoning completed event."""
if not self.verbose:
return
reasoning_branch = self.current_reasoning_branch
tree_to_use = (
self.current_lite_agent_branch
or self.current_agent_branch
or crew_tree
)
style = "green" if ready else "yellow"
status_text = "Reasoning Completed" if ready else "Reasoning Completed (Not Ready)"
if reasoning_branch is not None:
self.update_tree_label(reasoning_branch, "", status_text, style)
if tree_to_use is not None:
self.print(tree_to_use)
# Show plan in a panel (trim very long plans)
if plan:
plan_panel = Panel(
Text(plan, style="white"),
title="🧠 Reasoning Plan",
border_style=style,
padding=(1, 2),
)
self.print(plan_panel)
self.print()
# Clear stored branch after completion
self.current_reasoning_branch = None
def handle_reasoning_failed(
self,
error: str,
crew_tree: Optional[Tree],
) -> None:
"""Handle agent reasoning failure event."""
if not self.verbose:
return
reasoning_branch = self.current_reasoning_branch
tree_to_use = (
self.current_lite_agent_branch
or self.current_agent_branch
or crew_tree
)
if reasoning_branch is not None:
self.update_tree_label(reasoning_branch, "", "Reasoning Failed", "red")
if tree_to_use is not None:
self.print(tree_to_use)
# Error panel
error_content = self.create_status_content(
"Reasoning Failed",
"Error",
"red",
Error=error,
)
self.print_panel(error_content, "Reasoning Error", "red")
# Clear stored branch after failure
self.current_reasoning_branch = None

View File

@@ -8,6 +8,12 @@ from crewai.agent import Agent
from crewai.task import Task
from crewai.utilities import I18N
from crewai.llm import LLM
from crewai.utilities.events.crewai_event_bus import crewai_event_bus
from crewai.utilities.events.reasoning_events import (
AgentReasoningStartedEvent,
AgentReasoningCompletedEvent,
AgentReasoningFailedEvent,
)
class ReasoningPlan(BaseModel):
@@ -49,7 +55,55 @@ class AgentReasoning:
Returns:
AgentReasoningOutput: The output of the agent reasoning process.
"""
return self.__handle_agent_reasoning()
# Emit a reasoning started event (attempt 1)
try:
crewai_event_bus.emit(
self.agent,
AgentReasoningStartedEvent(
agent_role=self.agent.role,
task_id=str(self.task.id),
attempt=1,
),
)
except Exception:
# Ignore event bus errors to avoid breaking execution
pass
try:
output = self.__handle_agent_reasoning()
# Emit reasoning completed event
try:
crewai_event_bus.emit(
self.agent,
AgentReasoningCompletedEvent(
agent_role=self.agent.role,
task_id=str(self.task.id),
plan=output.plan.plan,
ready=output.plan.ready,
attempt=1,
),
)
except Exception:
pass
return output
except Exception as e:
# Emit reasoning failed event
try:
crewai_event_bus.emit(
self.agent,
AgentReasoningFailedEvent(
agent_role=self.agent.role,
task_id=str(self.task.id),
error=str(e),
attempt=1,
),
)
except Exception:
pass
raise
def __handle_agent_reasoning(self) -> AgentReasoningOutput:
"""
@@ -108,6 +162,19 @@ class AgentReasoning:
max_attempts = self.agent.max_reasoning_attempts
while not ready and (max_attempts is None or attempt < max_attempts):
# Emit event for each refinement attempt
try:
crewai_event_bus.emit(
self.agent,
AgentReasoningStartedEvent(
agent_role=self.agent.role,
task_id=str(self.task.id),
attempt=attempt + 1,
),
)
except Exception:
pass
refine_prompt = self.__create_refine_prompt(plan)
if self.llm.supports_function_calling():
@@ -179,12 +246,18 @@ class AgentReasoning:
backstory=self.__get_agent_backstory()
)
# Prepare a simple callable that just returns the tool arguments as JSON
def _create_reasoning_plan(plan: str, ready: bool): # noqa: N802
"""Return the reasoning plan result in JSON string form."""
return json.dumps({"plan": plan, "ready": ready})
response = self.llm.call(
[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
],
tools=[function_schema]
tools=[function_schema],
available_functions={"create_reasoning_plan": _create_reasoning_plan},
)
self.logger.debug(f"Function calling response: {response[:100]}...")