mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-22 22:58:13 +00:00
Compare commits
3 Commits
devin/1768
...
devin/1757
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a20f1092b | ||
|
|
0d115111d6 | ||
|
|
9228fae4ed |
@@ -1,25 +1,25 @@
|
|||||||
from typing import Any, Dict, Optional
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.live import Live
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
from rich.syntax import Syntax
|
||||||
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.syntax import Syntax
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleFormatter:
|
class ConsoleFormatter:
|
||||||
current_crew_tree: Optional[Tree] = None
|
current_crew_tree: Tree | None = None
|
||||||
current_task_branch: Optional[Tree] = None
|
current_task_branch: Tree | None = None
|
||||||
current_agent_branch: Optional[Tree] = None
|
current_agent_branch: Tree | None = None
|
||||||
current_tool_branch: Optional[Tree] = None
|
current_tool_branch: Tree | None = None
|
||||||
current_flow_tree: Optional[Tree] = None
|
current_flow_tree: Tree | None = None
|
||||||
current_method_branch: Optional[Tree] = None
|
current_method_branch: Tree | None = None
|
||||||
current_lite_agent_branch: Optional[Tree] = None
|
current_lite_agent_branch: Tree | None = None
|
||||||
tool_usage_counts: Dict[str, int] = {}
|
tool_usage_counts: ClassVar[dict[str, int]] = {}
|
||||||
current_reasoning_branch: Optional[Tree] = None # Track reasoning status
|
current_reasoning_branch: Tree | None = None # Track reasoning status
|
||||||
_live_paused: bool = False
|
_live_paused: bool = False
|
||||||
current_llm_tool_tree: Optional[Tree] = None
|
current_llm_tool_tree: Tree | None = None
|
||||||
|
|
||||||
def __init__(self, verbose: bool = False):
|
def __init__(self, verbose: bool = False):
|
||||||
self.console = Console(width=None)
|
self.console = Console(width=None)
|
||||||
@@ -29,7 +29,7 @@ class ConsoleFormatter:
|
|||||||
# instance so the previous render is replaced instead of writing a new one.
|
# instance so the previous render is replaced instead of writing a new one.
|
||||||
# Once any non-Tree renderable is printed we stop the Live session so the
|
# Once any non-Tree renderable is printed we stop the Live session so the
|
||||||
# final Tree persists on the terminal.
|
# final Tree persists on the terminal.
|
||||||
self._live: Optional[Live] = None
|
self._live: Live | None = None
|
||||||
|
|
||||||
def create_panel(self, content: Text, title: str, style: str = "blue") -> Panel:
|
def create_panel(self, content: Text, title: str, style: str = "blue") -> Panel:
|
||||||
"""Create a standardized panel with consistent styling."""
|
"""Create a standardized panel with consistent styling."""
|
||||||
@@ -45,7 +45,7 @@ class ConsoleFormatter:
|
|||||||
title: str,
|
title: str,
|
||||||
name: str,
|
name: str,
|
||||||
status_style: str = "blue",
|
status_style: str = "blue",
|
||||||
tool_args: Dict[str, Any] | str = "",
|
tool_args: dict[str, Any] | str = "",
|
||||||
**fields,
|
**fields,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
"""Create standardized status content with consistent formatting."""
|
"""Create standardized status content with consistent formatting."""
|
||||||
@@ -70,7 +70,7 @@ class ConsoleFormatter:
|
|||||||
prefix: str,
|
prefix: str,
|
||||||
name: str,
|
name: str,
|
||||||
style: str = "blue",
|
style: str = "blue",
|
||||||
status: Optional[str] = None,
|
status: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update tree label with consistent formatting."""
|
"""Update tree label with consistent formatting."""
|
||||||
label = Text()
|
label = Text()
|
||||||
@@ -115,7 +115,7 @@ class ConsoleFormatter:
|
|||||||
self._live.update(tree, refresh=True)
|
self._live.update(tree, refresh=True)
|
||||||
return # Nothing else to do
|
return # Nothing else to do
|
||||||
|
|
||||||
# Case 2: blank line while a live session is running – ignore so we
|
# Case 2: blank line while a live session is running - ignore so we
|
||||||
# don't break the in-place rendering behaviour
|
# don't break the in-place rendering behaviour
|
||||||
if len(args) == 0 and self._live:
|
if len(args) == 0 and self._live:
|
||||||
return
|
return
|
||||||
@@ -156,7 +156,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_crew_tree(
|
def update_crew_tree(
|
||||||
self,
|
self,
|
||||||
tree: Optional[Tree],
|
tree: Tree | None,
|
||||||
crew_name: str,
|
crew_name: str,
|
||||||
source_id: str,
|
source_id: str,
|
||||||
status: str = "completed",
|
status: str = "completed",
|
||||||
@@ -196,7 +196,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
self.print_panel(content, title, style)
|
self.print_panel(content, title, style)
|
||||||
|
|
||||||
def create_crew_tree(self, crew_name: str, source_id: str) -> Optional[Tree]:
|
def create_crew_tree(self, crew_name: str, source_id: str) -> Tree | None:
|
||||||
"""Create and initialize a new crew tree with initial status."""
|
"""Create and initialize a new crew tree with initial status."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -220,8 +220,8 @@ class ConsoleFormatter:
|
|||||||
return tree
|
return tree
|
||||||
|
|
||||||
def create_task_branch(
|
def create_task_branch(
|
||||||
self, crew_tree: Optional[Tree], task_id: str, task_name: Optional[str] = None
|
self, crew_tree: Tree | None, task_id: str, task_name: str | None = None
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Create and initialize a task branch."""
|
"""Create and initialize a task branch."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -255,11 +255,11 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_task_status(
|
def update_task_status(
|
||||||
self,
|
self,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
task_id: str,
|
task_id: str,
|
||||||
agent_role: str,
|
agent_role: str,
|
||||||
status: str = "completed",
|
status: str = "completed",
|
||||||
task_name: Optional[str] = None,
|
task_name: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update task status in the tree."""
|
"""Update task status in the tree."""
|
||||||
if not self.verbose or crew_tree is None:
|
if not self.verbose or crew_tree is None:
|
||||||
@@ -306,8 +306,8 @@ class ConsoleFormatter:
|
|||||||
self.print_panel(content, panel_title, style)
|
self.print_panel(content, panel_title, style)
|
||||||
|
|
||||||
def create_agent_branch(
|
def create_agent_branch(
|
||||||
self, task_branch: Optional[Tree], agent_role: str, crew_tree: Optional[Tree]
|
self, task_branch: Tree | None, agent_role: str, crew_tree: Tree | None
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Create and initialize an agent branch."""
|
"""Create and initialize an agent branch."""
|
||||||
if not self.verbose or not task_branch or not crew_tree:
|
if not self.verbose or not task_branch or not crew_tree:
|
||||||
return None
|
return None
|
||||||
@@ -325,9 +325,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_agent_status(
|
def update_agent_status(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
agent_role: str,
|
agent_role: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
status: str = "completed",
|
status: str = "completed",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update agent status in the tree."""
|
"""Update agent status in the tree."""
|
||||||
@@ -336,7 +336,7 @@ class ConsoleFormatter:
|
|||||||
# altering the tree. Keeping it a no-op avoids duplicate status lines.
|
# altering the tree. Keeping it a no-op avoids duplicate status lines.
|
||||||
return
|
return
|
||||||
|
|
||||||
def create_flow_tree(self, flow_name: str, flow_id: str) -> Optional[Tree]:
|
def create_flow_tree(self, flow_name: str, flow_id: str) -> Tree | None:
|
||||||
"""Create and initialize a flow tree."""
|
"""Create and initialize a flow tree."""
|
||||||
content = self.create_status_content(
|
content = self.create_status_content(
|
||||||
"Starting Flow Execution", flow_name, "blue", ID=flow_id
|
"Starting Flow Execution", flow_name, "blue", ID=flow_id
|
||||||
@@ -356,7 +356,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
return flow_tree
|
return flow_tree
|
||||||
|
|
||||||
def start_flow(self, flow_name: str, flow_id: str) -> Optional[Tree]:
|
def start_flow(self, flow_name: str, flow_id: str) -> Tree | None:
|
||||||
"""Initialize a flow execution tree."""
|
"""Initialize a flow execution tree."""
|
||||||
flow_tree = Tree("")
|
flow_tree = Tree("")
|
||||||
flow_label = Text()
|
flow_label = Text()
|
||||||
@@ -376,7 +376,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_flow_status(
|
def update_flow_status(
|
||||||
self,
|
self,
|
||||||
flow_tree: Optional[Tree],
|
flow_tree: Tree | None,
|
||||||
flow_name: str,
|
flow_name: str,
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
status: str = "completed",
|
status: str = "completed",
|
||||||
@@ -423,11 +423,11 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_method_status(
|
def update_method_status(
|
||||||
self,
|
self,
|
||||||
method_branch: Optional[Tree],
|
method_branch: Tree | None,
|
||||||
flow_tree: Optional[Tree],
|
flow_tree: Tree | None,
|
||||||
method_name: str,
|
method_name: str,
|
||||||
status: str = "running",
|
status: str = "running",
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Update method status in the flow tree."""
|
"""Update method status in the flow tree."""
|
||||||
if not flow_tree:
|
if not flow_tree:
|
||||||
return None
|
return None
|
||||||
@@ -480,7 +480,7 @@ class ConsoleFormatter:
|
|||||||
def handle_llm_tool_usage_started(
|
def handle_llm_tool_usage_started(
|
||||||
self,
|
self,
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
tool_args: Dict[str, Any] | str,
|
tool_args: dict[str, Any] | str,
|
||||||
):
|
):
|
||||||
# Create status content for the tool usage
|
# Create status content for the tool usage
|
||||||
content = self.create_status_content(
|
content = self.create_status_content(
|
||||||
@@ -520,11 +520,11 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_tool_usage_started(
|
def handle_tool_usage_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
tool_args: Dict[str, Any] | str = "",
|
tool_args: dict[str, Any] | str = "",
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Handle tool usage started event."""
|
"""Handle tool usage started event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -569,9 +569,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_tool_usage_finished(
|
def handle_tool_usage_finished(
|
||||||
self,
|
self,
|
||||||
tool_branch: Optional[Tree],
|
tool_branch: Tree | None,
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle tool usage finished event."""
|
"""Handle tool usage finished event."""
|
||||||
if not self.verbose or tool_branch is None:
|
if not self.verbose or tool_branch is None:
|
||||||
@@ -600,10 +600,10 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_tool_usage_error(
|
def handle_tool_usage_error(
|
||||||
self,
|
self,
|
||||||
tool_branch: Optional[Tree],
|
tool_branch: Tree | None,
|
||||||
tool_name: str,
|
tool_name: str,
|
||||||
error: str,
|
error: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle tool usage error event."""
|
"""Handle tool usage error event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -631,9 +631,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_llm_call_started(
|
def handle_llm_call_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Handle LLM call started event."""
|
"""Handle LLM call started event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -672,9 +672,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_llm_call_completed(
|
def handle_llm_call_completed(
|
||||||
self,
|
self,
|
||||||
tool_branch: Optional[Tree],
|
tool_branch: Tree | None,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle LLM call completed event."""
|
"""Handle LLM call completed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -693,7 +693,7 @@ class ConsoleFormatter:
|
|||||||
if tool_branch is not None and "Thinking" in str(tool_branch.label):
|
if tool_branch is not None and "Thinking" in str(tool_branch.label):
|
||||||
thinking_branch_to_remove = tool_branch
|
thinking_branch_to_remove = tool_branch
|
||||||
|
|
||||||
# Method 2: Fallback - search for any thinking node if tool_branch is None or not thinking
|
# Method 2: Fallback - search for any thinking node if tool_branch is None
|
||||||
if thinking_branch_to_remove is None:
|
if thinking_branch_to_remove is None:
|
||||||
parents = [
|
parents = [
|
||||||
self.current_lite_agent_branch,
|
self.current_lite_agent_branch,
|
||||||
@@ -736,7 +736,7 @@ class ConsoleFormatter:
|
|||||||
self.print()
|
self.print()
|
||||||
|
|
||||||
def handle_llm_call_failed(
|
def handle_llm_call_failed(
|
||||||
self, tool_branch: Optional[Tree], error: str, crew_tree: Optional[Tree]
|
self, tool_branch: Tree | None, error: str, crew_tree: Tree | None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle LLM call failed event."""
|
"""Handle LLM call failed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -752,7 +752,7 @@ class ConsoleFormatter:
|
|||||||
if tool_branch is not None and "Thinking" in str(tool_branch.label):
|
if tool_branch is not None and "Thinking" in str(tool_branch.label):
|
||||||
thinking_branch_to_update = tool_branch
|
thinking_branch_to_update = tool_branch
|
||||||
|
|
||||||
# Method 2: Fallback - search for any thinking node if tool_branch is None or not thinking
|
# Method 2: Fallback - search for any thinking node if tool_branch is None
|
||||||
if thinking_branch_to_update is None:
|
if thinking_branch_to_update is None:
|
||||||
parents = [
|
parents = [
|
||||||
self.current_lite_agent_branch,
|
self.current_lite_agent_branch,
|
||||||
@@ -789,7 +789,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_crew_test_started(
|
def handle_crew_test_started(
|
||||||
self, crew_name: str, source_id: str, n_iterations: int
|
self, crew_name: str, source_id: str, n_iterations: int
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Handle crew test started event."""
|
"""Handle crew test started event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -823,7 +823,7 @@ class ConsoleFormatter:
|
|||||||
return test_tree
|
return test_tree
|
||||||
|
|
||||||
def handle_crew_test_completed(
|
def handle_crew_test_completed(
|
||||||
self, flow_tree: Optional[Tree], crew_name: str
|
self, flow_tree: Tree | None, crew_name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle crew test completed event."""
|
"""Handle crew test completed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -913,7 +913,7 @@ class ConsoleFormatter:
|
|||||||
self.print_panel(failure_content, "Test Failure", "red")
|
self.print_panel(failure_content, "Test Failure", "red")
|
||||||
self.print()
|
self.print()
|
||||||
|
|
||||||
def create_lite_agent_branch(self, lite_agent_role: str) -> Optional[Tree]:
|
def create_lite_agent_branch(self, lite_agent_role: str) -> Tree | None:
|
||||||
"""Create and initialize a lite agent branch."""
|
"""Create and initialize a lite agent branch."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -935,10 +935,10 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def update_lite_agent_status(
|
def update_lite_agent_status(
|
||||||
self,
|
self,
|
||||||
lite_agent_branch: Optional[Tree],
|
lite_agent_branch: Tree | None,
|
||||||
lite_agent_role: str,
|
lite_agent_role: str,
|
||||||
status: str = "completed",
|
status: str = "completed",
|
||||||
**fields: Dict[str, Any],
|
**fields: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update lite agent status in the tree."""
|
"""Update lite agent status in the tree."""
|
||||||
if not self.verbose or lite_agent_branch is None:
|
if not self.verbose or lite_agent_branch is None:
|
||||||
@@ -981,7 +981,7 @@ class ConsoleFormatter:
|
|||||||
lite_agent_role: str,
|
lite_agent_role: str,
|
||||||
status: str = "started",
|
status: str = "started",
|
||||||
error: Any = None,
|
error: Any = None,
|
||||||
**fields: Dict[str, Any],
|
**fields: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle lite agent execution events with consistent formatting."""
|
"""Handle lite agent execution events with consistent formatting."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -1006,9 +1006,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_retrieval_started(
|
def handle_knowledge_retrieval_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Handle knowledge retrieval started event."""
|
"""Handle knowledge retrieval started event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -1034,13 +1034,13 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_retrieval_completed(
|
def handle_knowledge_retrieval_completed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
retrieved_knowledge: Any,
|
retrieved_knowledge: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle knowledge retrieval completed event."""
|
"""Handle knowledge retrieval completed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
@@ -1062,7 +1062,7 @@ class ConsoleFormatter:
|
|||||||
)
|
)
|
||||||
self.print(knowledge_panel)
|
self.print(knowledge_panel)
|
||||||
self.print()
|
self.print()
|
||||||
return None
|
return
|
||||||
|
|
||||||
knowledge_branch_found = False
|
knowledge_branch_found = False
|
||||||
for child in branch_to_use.children:
|
for child in branch_to_use.children:
|
||||||
@@ -1111,18 +1111,18 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_query_started(
|
def handle_knowledge_query_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
task_prompt: str,
|
task_prompt: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle knowledge query generated event."""
|
"""Handle knowledge query generated event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
if branch_to_use is None or tree_to_use is None:
|
if branch_to_use is None or tree_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
query_branch = branch_to_use.add("")
|
query_branch = branch_to_use.add("")
|
||||||
self.update_tree_label(
|
self.update_tree_label(
|
||||||
@@ -1134,9 +1134,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_query_failed(
|
def handle_knowledge_query_failed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
error: str,
|
error: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle knowledge query failed event."""
|
"""Handle knowledge query failed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -1159,18 +1159,18 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_query_completed(
|
def handle_knowledge_query_completed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle knowledge query completed event."""
|
"""Handle knowledge query completed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
|
|
||||||
if branch_to_use is None or tree_to_use is None:
|
if branch_to_use is None or tree_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
query_branch = branch_to_use.add("")
|
query_branch = branch_to_use.add("")
|
||||||
self.update_tree_label(query_branch, "✅", "Knowledge Query Completed", "green")
|
self.update_tree_label(query_branch, "✅", "Knowledge Query Completed", "green")
|
||||||
@@ -1180,9 +1180,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_knowledge_search_query_failed(
|
def handle_knowledge_search_query_failed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
error: str,
|
error: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle knowledge search query failed event."""
|
"""Handle knowledge search query failed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -1207,10 +1207,10 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_reasoning_started(
|
def handle_reasoning_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
attempt: int,
|
attempt: int,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
"""Handle agent reasoning started (or refinement) event."""
|
"""Handle agent reasoning started (or refinement) event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
@@ -1249,7 +1249,7 @@ class ConsoleFormatter:
|
|||||||
self,
|
self,
|
||||||
plan: str,
|
plan: str,
|
||||||
ready: bool,
|
ready: bool,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle agent reasoning completed event."""
|
"""Handle agent reasoning completed event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -1292,7 +1292,7 @@ class ConsoleFormatter:
|
|||||||
def handle_reasoning_failed(
|
def handle_reasoning_failed(
|
||||||
self,
|
self,
|
||||||
error: str,
|
error: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle agent reasoning failure event."""
|
"""Handle agent reasoning failure event."""
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
@@ -1329,7 +1329,7 @@ class ConsoleFormatter:
|
|||||||
def handle_agent_logs_started(
|
def handle_agent_logs_started(
|
||||||
self,
|
self,
|
||||||
agent_role: str,
|
agent_role: str,
|
||||||
task_description: Optional[str] = None,
|
task_description: str | None = None,
|
||||||
verbose: bool = False,
|
verbose: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle agent logs started event."""
|
"""Handle agent logs started event."""
|
||||||
@@ -1367,19 +1367,29 @@ class ConsoleFormatter:
|
|||||||
if not verbose:
|
if not verbose:
|
||||||
return
|
return
|
||||||
|
|
||||||
from crewai.agents.parser import AgentAction, AgentFinish
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from crewai.agents.parser import AgentAction, AgentFinish
|
||||||
|
|
||||||
agent_role = agent_role.partition("\n")[0]
|
agent_role = agent_role.partition("\n")[0]
|
||||||
|
|
||||||
if isinstance(formatted_answer, AgentAction):
|
if isinstance(formatted_answer, AgentAction):
|
||||||
thought = re.sub(r"\n+", "\n", formatted_answer.thought)
|
thought = re.sub(r"\n+", "\n", formatted_answer.thought)
|
||||||
formatted_json = json.dumps(
|
|
||||||
formatted_answer.tool_input,
|
try:
|
||||||
indent=2,
|
parsed_input = json.loads(formatted_answer.tool_input)
|
||||||
ensure_ascii=False,
|
formatted_json = json.dumps(
|
||||||
)
|
parsed_input,
|
||||||
|
indent=2,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
formatted_json = json.dumps(
|
||||||
|
formatted_answer.tool_input,
|
||||||
|
indent=2,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
# Create content for the action panel
|
# Create content for the action panel
|
||||||
content = Text()
|
content = Text()
|
||||||
@@ -1473,9 +1483,9 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_retrieval_started(
|
def handle_memory_retrieval_started(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> Optional[Tree]:
|
) -> Tree | None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1497,13 +1507,13 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_retrieval_completed(
|
def handle_memory_retrieval_completed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
memory_content: str,
|
memory_content: str,
|
||||||
retrieval_time_ms: float,
|
retrieval_time_ms: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
@@ -1528,7 +1538,7 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
if branch_to_use is None or tree_to_use is None:
|
if branch_to_use is None or tree_to_use is None:
|
||||||
add_panel()
|
add_panel()
|
||||||
return None
|
return
|
||||||
|
|
||||||
memory_branch_found = False
|
memory_branch_found = False
|
||||||
for child in branch_to_use.children:
|
for child in branch_to_use.children:
|
||||||
@@ -1565,13 +1575,13 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_query_completed(
|
def handle_memory_query_completed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
source_type: str,
|
source_type: str,
|
||||||
query_time_ms: float,
|
query_time_ms: float,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
@@ -1580,15 +1590,15 @@ class ConsoleFormatter:
|
|||||||
branch_to_use = tree_to_use
|
branch_to_use = tree_to_use
|
||||||
|
|
||||||
if branch_to_use is None:
|
if branch_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
memory_type = source_type.replace("_", " ").title()
|
memory_type = source_type.replace("_", " ").title()
|
||||||
|
|
||||||
for child in branch_to_use.children:
|
for child in branch_to_use.children:
|
||||||
if "Memory Retrieval" in str(child.label):
|
if "Memory Retrieval" in str(child.label):
|
||||||
for child in child.children:
|
for inner_child in child.children:
|
||||||
sources_branch = child
|
sources_branch = inner_child
|
||||||
if "Sources Used" in str(child.label):
|
if "Sources Used" in str(inner_child.label):
|
||||||
sources_branch.add(f"✅ {memory_type} ({query_time_ms:.2f}ms)")
|
sources_branch.add(f"✅ {memory_type} ({query_time_ms:.2f}ms)")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -1598,13 +1608,13 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_query_failed(
|
def handle_memory_query_failed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
error: str,
|
error: str,
|
||||||
source_type: str,
|
source_type: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = self.current_lite_agent_branch or agent_branch
|
branch_to_use = self.current_lite_agent_branch or agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
@@ -1613,15 +1623,15 @@ class ConsoleFormatter:
|
|||||||
branch_to_use = tree_to_use
|
branch_to_use = tree_to_use
|
||||||
|
|
||||||
if branch_to_use is None:
|
if branch_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
memory_type = source_type.replace("_", " ").title()
|
memory_type = source_type.replace("_", " ").title()
|
||||||
|
|
||||||
for child in branch_to_use.children:
|
for child in branch_to_use.children:
|
||||||
if "Memory Retrieval" in str(child.label):
|
if "Memory Retrieval" in str(child.label):
|
||||||
for child in child.children:
|
for inner_child in child.children:
|
||||||
sources_branch = child
|
sources_branch = inner_child
|
||||||
if "Sources Used" in str(child.label):
|
if "Sources Used" in str(inner_child.label):
|
||||||
sources_branch.add(f"❌ {memory_type} - Error: {error}")
|
sources_branch.add(f"❌ {memory_type} - Error: {error}")
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -1630,16 +1640,16 @@ class ConsoleFormatter:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def handle_memory_save_started(
|
def handle_memory_save_started(
|
||||||
self, agent_branch: Optional[Tree], crew_tree: Optional[Tree]
|
self, agent_branch: Tree | None, crew_tree: Tree | None
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = agent_branch or self.current_lite_agent_branch
|
branch_to_use = agent_branch or self.current_lite_agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
|
|
||||||
if tree_to_use is None:
|
if tree_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
for child in tree_to_use.children:
|
for child in tree_to_use.children:
|
||||||
if "Memory Update" in str(child.label):
|
if "Memory Update" in str(child.label):
|
||||||
@@ -1655,19 +1665,19 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_save_completed(
|
def handle_memory_save_completed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
save_time_ms: float,
|
save_time_ms: float,
|
||||||
source_type: str,
|
source_type: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = agent_branch or self.current_lite_agent_branch
|
branch_to_use = agent_branch or self.current_lite_agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
|
|
||||||
if tree_to_use is None:
|
if tree_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
memory_type = source_type.replace("_", " ").title()
|
memory_type = source_type.replace("_", " ").title()
|
||||||
content = f"✅ {memory_type} Memory Saved ({save_time_ms:.2f}ms)"
|
content = f"✅ {memory_type} Memory Saved ({save_time_ms:.2f}ms)"
|
||||||
@@ -1685,19 +1695,19 @@ class ConsoleFormatter:
|
|||||||
|
|
||||||
def handle_memory_save_failed(
|
def handle_memory_save_failed(
|
||||||
self,
|
self,
|
||||||
agent_branch: Optional[Tree],
|
agent_branch: Tree | None,
|
||||||
error: str,
|
error: str,
|
||||||
source_type: str,
|
source_type: str,
|
||||||
crew_tree: Optional[Tree],
|
crew_tree: Tree | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self.verbose:
|
if not self.verbose:
|
||||||
return None
|
return
|
||||||
|
|
||||||
branch_to_use = agent_branch or self.current_lite_agent_branch
|
branch_to_use = agent_branch or self.current_lite_agent_branch
|
||||||
tree_to_use = branch_to_use or crew_tree
|
tree_to_use = branch_to_use or crew_tree
|
||||||
|
|
||||||
if branch_to_use is None or tree_to_use is None:
|
if branch_to_use is None or tree_to_use is None:
|
||||||
return None
|
return
|
||||||
|
|
||||||
memory_type = source_type.replace("_", " ").title()
|
memory_type = source_type.replace("_", " ").title()
|
||||||
content = f"❌ {memory_type} Memory Save Failed"
|
content = f"❌ {memory_type} Memory Save Failed"
|
||||||
@@ -1738,7 +1748,7 @@ class ConsoleFormatter:
|
|||||||
def handle_guardrail_completed(
|
def handle_guardrail_completed(
|
||||||
self,
|
self,
|
||||||
success: bool,
|
success: bool,
|
||||||
error: Optional[str],
|
error: str | None,
|
||||||
retry_count: int,
|
retry_count: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Display guardrail evaluation result.
|
"""Display guardrail evaluation result.
|
||||||
|
|||||||
174
tests/utilities/test_console_formatter_json.py
Normal file
174
tests/utilities/test_console_formatter_json.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from crewai.agents.parser import AgentAction, AgentFinish
|
||||||
|
from crewai.events.utils.console_formatter import ConsoleFormatter
|
||||||
|
|
||||||
|
|
||||||
|
class TestConsoleFormatterJSON:
|
||||||
|
"""Test ConsoleFormatter JSON formatting functionality."""
|
||||||
|
|
||||||
|
def test_handle_agent_logs_execution_with_json_tool_input(self):
|
||||||
|
"""Test that JSON tool inputs are properly formatted."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
json_input = (
|
||||||
|
'{"task": "Research AI", "context": "Machine Learning", '
|
||||||
|
'"priority": "high"}'
|
||||||
|
)
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="I need to research this topic",
|
||||||
|
tool="research_tool",
|
||||||
|
tool_input=json_input,
|
||||||
|
text="Full agent text",
|
||||||
|
result="Research completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_handle_agent_logs_execution_with_malformed_json(self):
|
||||||
|
"""Test that malformed JSON falls back to string formatting."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
malformed_json = (
|
||||||
|
'{"task": "Research AI", "context": "Machine Learning"'
|
||||||
|
)
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="I need to research this topic",
|
||||||
|
tool="research_tool",
|
||||||
|
tool_input=malformed_json,
|
||||||
|
text="Full agent text",
|
||||||
|
result="Research completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_handle_agent_logs_execution_with_non_json_string(self):
|
||||||
|
"""Test that non-JSON strings are handled properly."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
plain_string = "search for weather in San Francisco"
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="I need to search for weather",
|
||||||
|
tool="search_tool",
|
||||||
|
tool_input=plain_string,
|
||||||
|
text="Full agent text",
|
||||||
|
result="Weather found"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_handle_agent_logs_execution_with_complex_json(self):
|
||||||
|
"""Test with complex nested JSON structures."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
complex_json = json.dumps({
|
||||||
|
"query": {
|
||||||
|
"type": "research",
|
||||||
|
"parameters": {
|
||||||
|
"topic": "AI in healthcare",
|
||||||
|
"depth": "comprehensive",
|
||||||
|
"sources": ["academic", "industry", "news"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filters": ["recent", "peer-reviewed"],
|
||||||
|
"limit": 50
|
||||||
|
})
|
||||||
|
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="Complex research query",
|
||||||
|
tool="advanced_search",
|
||||||
|
tool_input=complex_json,
|
||||||
|
text="Full agent text",
|
||||||
|
result="Complex search completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_handle_agent_logs_execution_with_agent_finish(self):
|
||||||
|
"""Test that AgentFinish objects are handled correctly."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
agent_finish = AgentFinish(
|
||||||
|
thought="Task completed",
|
||||||
|
output="Final result of the task",
|
||||||
|
text="Full agent text"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_finish, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_json_parsing_preserves_structure(self):
|
||||||
|
"""Test that JSON parsing preserves the original structure."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
original_data = {
|
||||||
|
"nested": {
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"string": "test",
|
||||||
|
"boolean": True,
|
||||||
|
"null": None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_string = json.dumps(original_data)
|
||||||
|
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="Testing structure preservation",
|
||||||
|
tool="test_tool",
|
||||||
|
tool_input=json_string,
|
||||||
|
text="Full agent text",
|
||||||
|
result="Test completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_empty_tool_input_handling(self):
|
||||||
|
"""Test handling of empty tool input."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="Empty input test",
|
||||||
|
tool="test_tool",
|
||||||
|
tool_input="",
|
||||||
|
text="Full agent text",
|
||||||
|
result="Test completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
|
|
||||||
|
def test_numeric_tool_input_handling(self):
|
||||||
|
"""Test handling of numeric tool input."""
|
||||||
|
formatter = ConsoleFormatter()
|
||||||
|
|
||||||
|
agent_action = AgentAction(
|
||||||
|
thought="Numeric input test",
|
||||||
|
tool="test_tool",
|
||||||
|
tool_input="42",
|
||||||
|
text="Full agent text",
|
||||||
|
result="Test completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(formatter, 'print') as mock_print:
|
||||||
|
formatter.handle_agent_logs_execution(agent_action, "Test Agent")
|
||||||
|
|
||||||
|
mock_print.assert_called()
|
||||||
Reference in New Issue
Block a user