diff --git a/src/crewai/agents/crew_agent_executor.py b/src/crewai/agents/crew_agent_executor.py index 1397bf7e3..48099fad9 100644 --- a/src/crewai/agents/crew_agent_executor.py +++ b/src/crewai/agents/crew_agent_executor.py @@ -25,12 +25,16 @@ from crewai.utilities.agent_utils import ( has_reached_max_iterations, is_context_length_exceeded, process_llm_response, - show_agent_logs, ) from crewai.utilities.constants import MAX_LLM_RETRY, TRAINING_DATA_FILE from crewai.utilities.logger import Logger from crewai.utilities.tool_utils import execute_tool_and_check_finality 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): @@ -263,26 +267,32 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): """Show logs for the start of agent execution.""" if self.agent is None: raise ValueError("Agent cannot be None") - show_agent_logs( - printer=self._printer, - agent_role=self.agent.role, - task_description=( - getattr(self.task, "description") if self.task else "Not Found" + + crewai_event_bus.emit( + self.agent, + AgentLogsStartedEvent( + agent_role=self.agent.role, + task_description=( + getattr(self.task, "description") if self.task else "Not Found" + ), + verbose=self.agent.verbose + or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), ), - verbose=self.agent.verbose - or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), ) def _show_logs(self, formatted_answer: Union[AgentAction, AgentFinish]): """Show logs for the agent's execution.""" if self.agent is None: raise ValueError("Agent cannot be None") - show_agent_logs( - printer=self._printer, - agent_role=self.agent.role, - formatted_answer=formatted_answer, - verbose=self.agent.verbose - or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), + + crewai_event_bus.emit( + self.agent, + AgentLogsExecutionEvent( + agent_role=self.agent.role, + formatted_answer=formatted_answer, + verbose=self.agent.verbose + or (hasattr(self, "crew") and getattr(self.crew, "verbose", False)), + ), ) def _summarize_messages(self) -> None: diff --git a/src/crewai/utilities/events/agent_events.py b/src/crewai/utilities/events/agent_events.py index 51b8d2122..2699e346e 100644 --- a/src/crewai/utilities/events/agent_events.py +++ b/src/crewai/utilities/events/agent_events.py @@ -102,3 +102,24 @@ class LiteAgentExecutionErrorEvent(BaseEvent): agent_info: Dict[str, Any] error: str 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} diff --git a/src/crewai/utilities/events/event_listener.py b/src/crewai/utilities/events/event_listener.py index acc3ed667..76fbf4baf 100644 --- a/src/crewai/utilities/events/event_listener.py +++ b/src/crewai/utilities/events/event_listener.py @@ -27,6 +27,8 @@ from crewai.utilities.events.utils.console_formatter import ConsoleFormatter from .agent_events import ( AgentExecutionCompletedEvent, AgentExecutionStartedEvent, + AgentLogsStartedEvent, + AgentLogsExecutionEvent, LiteAgentExecutionCompletedEvent, LiteAgentExecutionErrorEvent, LiteAgentExecutionStartedEvent, @@ -291,8 +293,8 @@ class EventListener(BaseEventListener): self.formatter.handle_tool_usage_started( self.formatter.current_agent_branch, event.tool_name, - self.formatter.current_crew_tree, - ) + self.formatter.current_crew_tree, + ) @crewai_event_bus.on(ToolUsageFinishedEvent) def on_tool_usage_finished(source, event: ToolUsageFinishedEvent): @@ -320,7 +322,7 @@ class EventListener(BaseEventListener): event.tool_name, event.error, self.formatter.current_crew_tree, - ) + ) # ----------- LLM EVENTS ----------- @@ -466,5 +468,23 @@ class EventListener(BaseEventListener): 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() diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py index 380670c13..ff60a54ee 100644 --- a/src/crewai/utilities/events/utils/console_formatter.py +++ b/src/crewai/utilities/events/utils/console_formatter.py @@ -5,6 +5,7 @@ from rich.panel import Panel from rich.text import Text from rich.tree import Tree from rich.live import Live +from rich.syntax import Syntax class ConsoleFormatter: @@ -498,9 +499,7 @@ class ConsoleFormatter: # Parent for tool usage: LiteAgent > Agent > Task branch_to_use = ( - self.current_lite_agent_branch - or agent_branch - or self.current_task_branch + self.current_lite_agent_branch or agent_branch or self.current_task_branch ) # Render full crew tree when available for consistent live updates @@ -609,9 +608,7 @@ class ConsoleFormatter: # Parent for tool usage: LiteAgent > Agent > Task branch_to_use = ( - self.current_lite_agent_branch - or agent_branch - or self.current_task_branch + self.current_lite_agent_branch or agent_branch or self.current_task_branch ) # 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 # or if the current tool branch is not a thinking node - should_add_thinking = ( - self.current_tool_branch is None or - "Thinking" not in str(self.current_tool_branch.label) + should_add_thinking = self.current_tool_branch is None or "Thinking" not in str( + self.current_tool_branch.label ) if should_add_thinking: @@ -691,7 +687,10 @@ class ConsoleFormatter: tree_to_use, ] 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) removed = True break @@ -1186,9 +1185,7 @@ class ConsoleFormatter: # Prefer LiteAgent > Agent > Task branch as the parent for reasoning branch_to_use = ( - self.current_lite_agent_branch - or agent_branch - or self.current_task_branch + self.current_lite_agent_branch or agent_branch or self.current_task_branch ) # 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" - 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: self.update_tree_label(reasoning_branch, "✅", status_text, style) @@ -1292,3 +1291,149 @@ class ConsoleFormatter: # Clear stored branch after failure 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()