feat: add agent logging events for execution tracking

- Introduced AgentLogsStartedEvent and AgentLogsExecutionEvent to enhance logging capabilities during agent execution.
- Updated CrewAgentExecutor to emit these events at the start and during execution, respectively.
- Modified EventListener to handle the new logging events and format output accordingly in the console.
- Enhanced ConsoleFormatter to display agent logs in a structured format, improving visibility of agent actions and outputs.
This commit is contained in:
lorenzejay
2025-06-12 10:07:56 -07:00
parent 554ea8fcee
commit b031d56578
4 changed files with 227 additions and 31 deletions

View File

@@ -25,12 +25,16 @@ from crewai.utilities.agent_utils import (
has_reached_max_iterations, has_reached_max_iterations,
is_context_length_exceeded, is_context_length_exceeded,
process_llm_response, process_llm_response,
show_agent_logs,
) )
from crewai.utilities.constants import MAX_LLM_RETRY, TRAINING_DATA_FILE from crewai.utilities.constants import MAX_LLM_RETRY, TRAINING_DATA_FILE
from crewai.utilities.logger import Logger from crewai.utilities.logger import Logger
from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.tool_utils import execute_tool_and_check_finality
from crewai.utilities.training_handler import CrewTrainingHandler from crewai.utilities.training_handler import CrewTrainingHandler
from crewai.utilities.events.agent_events import (
AgentLogsStartedEvent,
AgentLogsExecutionEvent,
)
from crewai.utilities.events.crewai_event_bus import crewai_event_bus
class CrewAgentExecutor(CrewAgentExecutorMixin): class CrewAgentExecutor(CrewAgentExecutorMixin):
@@ -263,26 +267,32 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
"""Show logs for the start of agent execution.""" """Show logs for the start of agent execution."""
if self.agent is None: if self.agent is None:
raise ValueError("Agent cannot be None") raise ValueError("Agent cannot be None")
show_agent_logs(
printer=self._printer, crewai_event_bus.emit(
self.agent,
AgentLogsStartedEvent(
agent_role=self.agent.role, agent_role=self.agent.role,
task_description=( task_description=(
getattr(self.task, "description") if self.task else "Not Found" getattr(self.task, "description") if self.task else "Not Found"
), ),
verbose=self.agent.verbose verbose=self.agent.verbose
or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)),
),
) )
def _show_logs(self, formatted_answer: Union[AgentAction, AgentFinish]): def _show_logs(self, formatted_answer: Union[AgentAction, AgentFinish]):
"""Show logs for the agent's execution.""" """Show logs for the agent's execution."""
if self.agent is None: if self.agent is None:
raise ValueError("Agent cannot be None") raise ValueError("Agent cannot be None")
show_agent_logs(
printer=self._printer, crewai_event_bus.emit(
self.agent,
AgentLogsExecutionEvent(
agent_role=self.agent.role, agent_role=self.agent.role,
formatted_answer=formatted_answer, formatted_answer=formatted_answer,
verbose=self.agent.verbose verbose=self.agent.verbose
or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)),
),
) )
def _summarize_messages(self) -> None: def _summarize_messages(self) -> None:

View File

@@ -102,3 +102,24 @@ class LiteAgentExecutionErrorEvent(BaseEvent):
agent_info: Dict[str, Any] agent_info: Dict[str, Any]
error: str error: str
type: str = "lite_agent_execution_error" type: str = "lite_agent_execution_error"
# New logging events
class AgentLogsStartedEvent(BaseEvent):
"""Event emitted when agent logs should be shown at start"""
agent_role: str
task_description: Optional[str] = None
verbose: bool = False
type: str = "agent_logs_started"
class AgentLogsExecutionEvent(BaseEvent):
"""Event emitted when agent logs should be shown during execution"""
agent_role: str
formatted_answer: Any
verbose: bool = False
type: str = "agent_logs_execution"
model_config = {"arbitrary_types_allowed": True}

View File

@@ -27,6 +27,8 @@ from crewai.utilities.events.utils.console_formatter import ConsoleFormatter
from .agent_events import ( from .agent_events import (
AgentExecutionCompletedEvent, AgentExecutionCompletedEvent,
AgentExecutionStartedEvent, AgentExecutionStartedEvent,
AgentLogsStartedEvent,
AgentLogsExecutionEvent,
LiteAgentExecutionCompletedEvent, LiteAgentExecutionCompletedEvent,
LiteAgentExecutionErrorEvent, LiteAgentExecutionErrorEvent,
LiteAgentExecutionStartedEvent, LiteAgentExecutionStartedEvent,
@@ -466,5 +468,23 @@ class EventListener(BaseEventListener):
self.formatter.current_crew_tree, self.formatter.current_crew_tree,
) )
# ----------- AGENT LOGGING EVENTS -----------
@crewai_event_bus.on(AgentLogsStartedEvent)
def on_agent_logs_started(source, event: AgentLogsStartedEvent):
self.formatter.handle_agent_logs_started(
event.agent_role,
event.task_description,
event.verbose,
)
@crewai_event_bus.on(AgentLogsExecutionEvent)
def on_agent_logs_execution(source, event: AgentLogsExecutionEvent):
self.formatter.handle_agent_logs_execution(
event.agent_role,
event.formatted_answer,
event.verbose,
)
event_listener = EventListener() event_listener = EventListener()

View File

@@ -5,6 +5,7 @@ from rich.panel import Panel
from rich.text import Text from rich.text import Text
from rich.tree import Tree from rich.tree import Tree
from rich.live import Live from rich.live import Live
from rich.syntax import Syntax
class ConsoleFormatter: class ConsoleFormatter:
@@ -498,9 +499,7 @@ class ConsoleFormatter:
# Parent for tool usage: LiteAgent > Agent > Task # Parent for tool usage: LiteAgent > Agent > Task
branch_to_use = ( branch_to_use = (
self.current_lite_agent_branch self.current_lite_agent_branch or agent_branch or self.current_task_branch
or agent_branch
or self.current_task_branch
) )
# Render full crew tree when available for consistent live updates # Render full crew tree when available for consistent live updates
@@ -609,9 +608,7 @@ class ConsoleFormatter:
# Parent for tool usage: LiteAgent > Agent > Task # Parent for tool usage: LiteAgent > Agent > Task
branch_to_use = ( branch_to_use = (
self.current_lite_agent_branch self.current_lite_agent_branch or agent_branch or self.current_task_branch
or agent_branch
or self.current_task_branch
) )
# Render full crew tree when available for consistent live updates # Render full crew tree when available for consistent live updates
@@ -626,9 +623,8 @@ class ConsoleFormatter:
# Only add thinking status if we don't have a current tool branch # Only add thinking status if we don't have a current tool branch
# or if the current tool branch is not a thinking node # or if the current tool branch is not a thinking node
should_add_thinking = ( should_add_thinking = self.current_tool_branch is None or "Thinking" not in str(
self.current_tool_branch is None or self.current_tool_branch.label
"Thinking" not in str(self.current_tool_branch.label)
) )
if should_add_thinking: if should_add_thinking:
@@ -691,7 +687,10 @@ class ConsoleFormatter:
tree_to_use, tree_to_use,
] ]
for parent in parents: for parent in parents:
if isinstance(parent, Tree) and thinking_branch_to_remove in parent.children: if (
isinstance(parent, Tree)
and thinking_branch_to_remove in parent.children
):
parent.children.remove(thinking_branch_to_remove) parent.children.remove(thinking_branch_to_remove)
removed = True removed = True
break break
@@ -1186,9 +1185,7 @@ class ConsoleFormatter:
# Prefer LiteAgent > Agent > Task branch as the parent for reasoning # Prefer LiteAgent > Agent > Task branch as the parent for reasoning
branch_to_use = ( branch_to_use = (
self.current_lite_agent_branch self.current_lite_agent_branch or agent_branch or self.current_task_branch
or agent_branch
or self.current_task_branch
) )
# We always want to render the full crew tree when possible so the # We always want to render the full crew tree when possible so the
@@ -1235,7 +1232,9 @@ class ConsoleFormatter:
) )
style = "green" if ready else "yellow" style = "green" if ready else "yellow"
status_text = "Reasoning Completed" if ready else "Reasoning Completed (Not Ready)" status_text = (
"Reasoning Completed" if ready else "Reasoning Completed (Not Ready)"
)
if reasoning_branch is not None: if reasoning_branch is not None:
self.update_tree_label(reasoning_branch, "", status_text, style) self.update_tree_label(reasoning_branch, "", status_text, style)
@@ -1292,3 +1291,149 @@ class ConsoleFormatter:
# Clear stored branch after failure # Clear stored branch after failure
self.current_reasoning_branch = None self.current_reasoning_branch = None
# ----------- AGENT LOGGING EVENTS -----------
def handle_agent_logs_started(
self,
agent_role: str,
task_description: Optional[str] = None,
verbose: bool = False,
) -> None:
"""Handle agent logs started event."""
if not verbose:
return
agent_role = agent_role.split("\n")[0]
# Create panel content
content = Text()
content.append("Agent: ", style="white")
content.append(f"{agent_role}", style="bright_green bold")
if task_description:
content.append("\n\nTask: ", style="white")
content.append(f"{task_description}", style="bright_green")
# Create and display the panel
agent_panel = Panel(
content,
title="🤖 Agent Started",
border_style="magenta",
padding=(1, 2),
)
self.print(agent_panel)
self.print()
def handle_agent_logs_execution(
self,
agent_role: str,
formatted_answer: Any,
verbose: bool = False,
) -> None:
"""Handle agent logs execution event."""
if not verbose:
return
from crewai.agents.parser import AgentAction, AgentFinish
import json
import re
agent_role = agent_role.split("\n")[0]
if isinstance(formatted_answer, AgentAction):
thought = re.sub(r"\n+", "\n", formatted_answer.thought)
formatted_json = json.dumps(
formatted_answer.tool_input,
indent=2,
ensure_ascii=False,
)
# Create content for the action panel
content = Text()
content.append("Agent: ", style="white")
content.append(f"{agent_role}\n\n", style="bright_green bold")
if thought and thought != "":
content.append("Thought: ", style="white")
content.append(f"{thought}\n\n", style="bright_green")
content.append("Using Tool: ", style="white")
content.append(f"{formatted_answer.tool}\n\n", style="bright_green bold")
content.append("Tool Input:\n", style="white")
# Create a syntax-highlighted JSON code block
json_syntax = Syntax(
formatted_json,
"json",
theme="monokai",
line_numbers=False,
background_color="default",
)
content.append("\n")
# Create separate panels for better organization
main_content = Text()
main_content.append("Agent: ", style="white")
main_content.append(f"{agent_role}\n\n", style="bright_green bold")
if thought and thought != "":
main_content.append("Thought: ", style="white")
main_content.append(f"{thought}\n\n", style="bright_green")
main_content.append("Using Tool: ", style="white")
main_content.append(f"{formatted_answer.tool}", style="bright_green bold")
# Create the main action panel
action_panel = Panel(
main_content,
title="🔧 Agent Tool Execution",
border_style="magenta",
padding=(1, 2),
)
# Create the JSON input panel
input_panel = Panel(
json_syntax,
title="📥 Tool Input",
border_style="blue",
padding=(1, 2),
)
# Create tool output content with better formatting
output_text = str(formatted_answer.result)
if len(output_text) > 1000:
output_text = output_text[:997] + "..."
output_panel = Panel(
Text(output_text, style="bright_green"),
title="📤 Tool Output",
border_style="green",
padding=(1, 2),
)
# Print all panels
self.print(action_panel)
self.print(input_panel)
self.print(output_panel)
self.print()
elif isinstance(formatted_answer, AgentFinish):
# Create content for the finish panel
content = Text()
content.append("Agent: ", style="white")
content.append(f"{agent_role}\n\n", style="bright_green bold")
content.append("Final Answer:\n", style="white")
content.append(f"{formatted_answer.output}", style="bright_green")
# Create and display the finish panel
finish_panel = Panel(
content,
title="✅ Agent Final Answer",
border_style="green",
padding=(1, 2),
)
self.print(finish_panel)
self.print()