From 4e288e4cf73524a49d50c5654ae540e688e3f961 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:57:26 +0000 Subject: [PATCH] feat: Add CREWAI_DISABLE_EMOJIS environment variable to disable emojis in logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add environment variable CREWAI_DISABLE_EMOJIS to control emoji usage - Create comprehensive emoji-to-text mapping for runtime compatibility - Implement _get_icon() helper method in ConsoleFormatter - Replace all hardcoded emojis with dynamic icon selection - Add comprehensive test suite covering all emoji disable scenarios - Maintain backward compatibility (emojis enabled by default) - Fixes issue #3062 for Azure Functions and similar runtime environments Co-Authored-By: Joรฃo --- .../events/utils/console_formatter.py | 153 ++++++---- tests/utilities/events/utils/__init__.py | 1 + .../test_console_formatter_emoji_disable.py | 272 ++++++++++++++++++ 3 files changed, 367 insertions(+), 59 deletions(-) create mode 100644 tests/utilities/events/utils/__init__.py create mode 100644 tests/utilities/events/utils/test_console_formatter_emoji_disable.py diff --git a/src/crewai/utilities/events/utils/console_formatter.py b/src/crewai/utilities/events/utils/console_formatter.py index ccf1837cc..5cb894b66 100644 --- a/src/crewai/utilities/events/utils/console_formatter.py +++ b/src/crewai/utilities/events/utils/console_formatter.py @@ -1,3 +1,4 @@ +import os from typing import Any, Dict, Optional from rich.console import Console @@ -24,6 +25,7 @@ class ConsoleFormatter: def __init__(self, verbose: bool = False): self.console = Console(width=None) self.verbose = verbose + self.disable_emojis = os.getenv("CREWAI_DISABLE_EMOJIS", "").lower() in ("true", "1", "yes") # Live instance to dynamically update a Tree renderable (e.g. the Crew tree) # When multiple Tree objects are printed sequentially we reuse this Live # instance so the previous render is replaced instead of writing a new one. @@ -31,6 +33,39 @@ class ConsoleFormatter: # final Tree persists on the terminal. self._live: Optional[Live] = None + EMOJI_MAP = { + "โœ…": "[DONE]", + "โŒ": "[FAILED]", + "๐Ÿš€": "[CREW]", + "๐Ÿ”„": "[RUNNING]", + "๐Ÿ“‹": "[TASK]", + "๐Ÿ”ง": "[TOOL]", + "๐Ÿง ": "[THINKING]", + "๐ŸŒŠ": "[FLOW]", + "โœจ": "[CREATED]", + "๐Ÿงช": "[TEST]", + "๐Ÿ“š": "[KNOWLEDGE]", + "๐Ÿ”": "[SEARCH]", + "๐Ÿ”Ž": "[QUERY]", + "๐Ÿค–": "[AGENT]", + "๐Ÿ“Š": "[METRICS]", + "โšก": "[QUICK]", + "๐ŸŽฏ": "[TARGET]", + "๐Ÿ”—": "[LINK]", + "๐Ÿ’ก": "[IDEA]", + "โš ๏ธ": "[WARNING]", + "๐ŸŽ‰": "[SUCCESS]", + "๐Ÿ”ฅ": "[HOT]", + "๐Ÿ’พ": "[SAVE]", + "๐Ÿ”’": "[SECURE]", + "๐ŸŒŸ": "[STAR]", + } + + def _get_icon(self, emoji: str) -> str: + if self.disable_emojis: + return self.EMOJI_MAP.get(emoji, emoji.encode('ascii', 'ignore').decode('ascii')) + return emoji + def create_panel(self, content: Text, title: str, style: str = "blue") -> Panel: """Create a standardized panel with consistent styling.""" return Panel( @@ -167,15 +202,15 @@ class ConsoleFormatter: return if status == "completed": - prefix, style = "โœ… Crew:", "green" + prefix, style = f"{self._get_icon('โœ…')} Crew:", "green" title = "Crew Completion" content_title = "Crew Execution Completed" elif status == "failed": - prefix, style = "โŒ Crew:", "red" + prefix, style = f"{self._get_icon('โŒ')} Crew:", "red" title = "Crew Failure" content_title = "Crew Execution Failed" else: - prefix, style = "๐Ÿš€ Crew:", "cyan" + prefix, style = f"{self._get_icon('๐Ÿš€')} Crew:", "cyan" title = "Crew Execution" content_title = "Crew Execution Started" @@ -202,7 +237,7 @@ class ConsoleFormatter: return None tree = Tree( - Text("๐Ÿš€ Crew: ", style="cyan bold") + Text(crew_name, style="cyan") + Text(f"{self._get_icon('๐Ÿš€')} Crew: ", style="cyan bold") + Text(crew_name, style="cyan") ) content = self.create_status_content( @@ -227,7 +262,7 @@ class ConsoleFormatter: return None task_content = Text() - task_content.append(f"๐Ÿ“‹ Task: {task_id}", style="yellow bold") + task_content.append(f"{self._get_icon('๐Ÿ“‹')} Task: {task_id}", style="yellow bold") task_content.append("\nStatus: ", style="white") task_content.append("Executing Task...", style="yellow dim") @@ -258,11 +293,11 @@ class ConsoleFormatter: if status == "completed": style = "green" - status_text = "โœ… Completed" + status_text = f"{self._get_icon('โœ…')} Completed" panel_title = "Task Completion" else: style = "red" - status_text = "โŒ Failed" + status_text = f"{self._get_icon('โŒ')} Failed" panel_title = "Task Failure" # Update tree label @@ -271,7 +306,7 @@ class ConsoleFormatter: # Build label without introducing stray blank lines task_content = Text() # First line: Task ID - task_content.append(f"๐Ÿ“‹ Task: {task_id}", style=f"{style} bold") + task_content.append(f"{self._get_icon('๐Ÿ“‹')} Task: {task_id}", style=f"{style} bold") # Second line: Assigned to task_content.append("\nAssigned to: ", style="white") @@ -330,14 +365,14 @@ class ConsoleFormatter: # Create initial tree with flow ID flow_label = Text() - flow_label.append("๐ŸŒŠ Flow: ", style="blue bold") + flow_label.append(f"{self._get_icon('๐ŸŒŠ')} Flow: ", style="blue bold") flow_label.append(flow_name, style="blue") flow_label.append("\nID: ", style="white") flow_label.append(flow_id, style="blue") flow_tree = Tree(flow_label) - self.add_tree_node(flow_tree, "โœจ Created", "blue") - self.add_tree_node(flow_tree, "โœ… Initialization Complete", "green") + self.add_tree_node(flow_tree, f"{self._get_icon('โœจ')} Created", "blue") + self.add_tree_node(flow_tree, f"{self._get_icon('โœ…')} Initialization Complete", "green") return flow_tree @@ -345,13 +380,13 @@ class ConsoleFormatter: """Initialize a flow execution tree.""" flow_tree = Tree("") flow_label = Text() - flow_label.append("๐ŸŒŠ Flow: ", style="blue bold") + flow_label.append(f"{self._get_icon('๐ŸŒŠ')} Flow: ", style="blue bold") flow_label.append(flow_name, style="blue") flow_label.append("\nID: ", style="white") flow_label.append(flow_id, style="blue") flow_tree.label = flow_label - self.add_tree_node(flow_tree, "๐Ÿง  Starting Flow...", "yellow") + self.add_tree_node(flow_tree, f"{self._get_icon('๐Ÿง ')} Starting Flow...", "yellow") self.print(flow_tree) self.print() @@ -373,7 +408,7 @@ class ConsoleFormatter: # Update main flow label self.update_tree_label( flow_tree, - "โœ… Flow Finished:" if status == "completed" else "โŒ Flow Failed:", + f"{self._get_icon('โœ…')} Flow Finished:" if status == "completed" else f"{self._get_icon('โŒ')} Flow Failed:", flow_name, "green" if status == "completed" else "red", ) @@ -383,9 +418,9 @@ class ConsoleFormatter: if "Starting Flow" in str(child.label): child.label = Text( ( - "โœ… Flow Completed" + f"{self._get_icon('โœ…')} Flow Completed" if status == "completed" - else "โŒ Flow Failed" + else f"{self._get_icon('โŒ')} Flow Failed" ), style="green" if status == "completed" else "red", ) @@ -418,20 +453,20 @@ class ConsoleFormatter: return None if status == "running": - prefix, style = "๐Ÿ”„ Running:", "yellow" + prefix, style = f"{self._get_icon('๐Ÿ”„')} Running:", "yellow" elif status == "completed": - prefix, style = "โœ… Completed:", "green" + prefix, style = f"{self._get_icon('โœ…')} Completed:", "green" # Update initialization node when a method completes successfully for child in flow_tree.children: if "Starting Flow" in str(child.label): child.label = Text("Flow Method Step", style="white") break else: - prefix, style = "โŒ Failed:", "red" + prefix, style = f"{self._get_icon('โŒ')} Failed:", "red" # Update initialization node on failure for child in flow_tree.children: if "Starting Flow" in str(child.label): - child.label = Text("โŒ Flow Step Failed", style="red") + child.label = Text(f"{self._get_icon('โŒ')} Flow Step Failed", style="red") break if not method_branch: @@ -453,7 +488,7 @@ class ConsoleFormatter: def get_llm_tree(self, tool_name: str): text = Text() - text.append(f"๐Ÿ”ง Using {tool_name} from LLM available_function", style="yellow") + text.append(f"{self._get_icon('๐Ÿ”ง')} Using {tool_name} from LLM available_function", style="yellow") tree = self.current_flow_tree or self.current_crew_tree @@ -484,7 +519,7 @@ class ConsoleFormatter: tool_name: str, ): tree = self.get_llm_tree(tool_name) - self.add_tree_node(tree, "โœ… Tool Usage Completed", "green") + self.add_tree_node(tree, f"{self._get_icon('โœ…')} Tool Usage Completed", "green") self.print(tree) self.print() @@ -494,7 +529,7 @@ class ConsoleFormatter: error: str, ): tree = self.get_llm_tree(tool_name) - self.add_tree_node(tree, "โŒ Tool Usage Failed", "red") + self.add_tree_node(tree, f"{self._get_icon('โŒ')} Tool Usage Failed", "red") self.print(tree) self.print() @@ -541,7 +576,7 @@ class ConsoleFormatter: # Update label with current count self.update_tree_label( tool_branch, - "๐Ÿ”ง", + self._get_icon("๐Ÿ”ง"), f"Using {tool_name} ({self.tool_usage_counts[tool_name]})", "yellow", ) @@ -570,7 +605,7 @@ class ConsoleFormatter: # Update the existing tool node's label self.update_tree_label( tool_branch, - "๐Ÿ”ง", + self._get_icon("๐Ÿ”ง"), f"Used {tool_name} ({self.tool_usage_counts[tool_name]})", "green", ) @@ -600,7 +635,7 @@ class ConsoleFormatter: if tool_branch: self.update_tree_label( tool_branch, - "๐Ÿ”ง Failed", + f"{self._get_icon('๐Ÿ”ง')} Failed", f"{tool_name} ({self.tool_usage_counts[tool_name]})", "red", ) @@ -646,7 +681,7 @@ class ConsoleFormatter: if should_add_thinking: tool_branch = branch_to_use.add("") - self.update_tree_label(tool_branch, "๐Ÿง ", "Thinking...", "blue") + self.update_tree_label(tool_branch, self._get_icon("๐Ÿง "), "Thinking...", "blue") self.current_tool_branch = tool_branch self.print(tree_to_use) self.print() @@ -756,7 +791,7 @@ class ConsoleFormatter: # Update the thinking branch to show failure if thinking_branch_to_update: - thinking_branch_to_update.label = Text("โŒ LLM Failed", style="red bold") + thinking_branch_to_update.label = Text(f"{self._get_icon('โŒ')} LLM Failed", style="red bold") # Clear the current_tool_branch reference if self.current_tool_branch is thinking_branch_to_update: self.current_tool_branch = None @@ -766,7 +801,7 @@ class ConsoleFormatter: # Show error panel error_content = Text() - error_content.append("โŒ LLM Call Failed\n", style="red bold") + error_content.append(f"{self._get_icon('โŒ')} LLM Call Failed\n", style="red bold") error_content.append("Error: ", style="white") error_content.append(str(error), style="red") @@ -781,7 +816,7 @@ class ConsoleFormatter: # Create initial panel content = Text() - content.append("๐Ÿงช Starting Crew Test\n\n", style="blue bold") + content.append(f"{self._get_icon('๐Ÿงช')} Starting Crew Test\n\n", style="blue bold") content.append("Crew: ", style="white") content.append(f"{crew_name}\n", style="blue") content.append("ID: ", style="white") @@ -795,13 +830,13 @@ class ConsoleFormatter: # Create and display the test tree test_label = Text() - test_label.append("๐Ÿงช Test: ", style="blue bold") + test_label.append(f"{self._get_icon('๐Ÿงช')} Test: ", style="blue bold") test_label.append(crew_name or "Crew", style="blue") test_label.append("\nStatus: ", style="white") test_label.append("In Progress", style="yellow") test_tree = Tree(test_label) - self.add_tree_node(test_tree, "๐Ÿ”„ Running tests...", "yellow") + self.add_tree_node(test_tree, f"{self._get_icon('๐Ÿ”„')} Running tests...", "yellow") self.print(test_tree) self.print() @@ -817,7 +852,7 @@ class ConsoleFormatter: if flow_tree: # Update test tree label to show completion test_label = Text() - test_label.append("โœ… Test: ", style="green bold") + test_label.append(f"{self._get_icon('โœ…')} Test: ", style="green bold") test_label.append(crew_name or "Crew", style="green") test_label.append("\nStatus: ", style="white") test_label.append("Completed", style="green bold") @@ -826,7 +861,7 @@ class ConsoleFormatter: # Update the running tests node for child in flow_tree.children: if "Running tests" in str(child.label): - child.label = Text("โœ… Tests completed successfully", style="green") + child.label = Text(f"{self._get_icon('โœ…')} Tests completed successfully", style="green") break self.print(flow_tree) @@ -848,7 +883,7 @@ class ConsoleFormatter: return content = Text() - content.append("๐Ÿ“‹ Crew Training Started\n", style="blue bold") + content.append(f"{self._get_icon('๐Ÿ“‹')} Crew Training Started\n", style="blue bold") content.append("Crew: ", style="white") content.append(f"{crew_name}\n", style="blue") content.append("Time: ", style="white") @@ -863,7 +898,7 @@ class ConsoleFormatter: return content = Text() - content.append("โœ… Crew Training Completed\n", style="green bold") + content.append(f"{self._get_icon('โœ…')} Crew Training Completed\n", style="green bold") content.append("Crew: ", style="white") content.append(f"{crew_name}\n", style="green") content.append("Time: ", style="white") @@ -878,7 +913,7 @@ class ConsoleFormatter: return failure_content = Text() - failure_content.append("โŒ Crew Training Failed\n", style="red bold") + failure_content.append(f"{self._get_icon('โŒ')} Crew Training Failed\n", style="red bold") failure_content.append("Crew: ", style="white") failure_content.append(crew_name or "Crew", style="red") @@ -891,7 +926,7 @@ class ConsoleFormatter: return failure_content = Text() - failure_content.append("โŒ Crew Test Failed\n", style="red bold") + failure_content.append(f"{self._get_icon('โŒ')} Crew Test Failed\n", style="red bold") failure_content.append("Crew: ", style="white") failure_content.append(crew_name or "Crew", style="red") @@ -906,7 +941,7 @@ class ConsoleFormatter: # Create initial tree for LiteAgent if it doesn't exist if not self.current_lite_agent_branch: lite_agent_label = Text() - lite_agent_label.append("๐Ÿค– LiteAgent: ", style="cyan bold") + lite_agent_label.append(f"{self._get_icon('๐Ÿค–')} LiteAgent: ", style="cyan bold") lite_agent_label.append(lite_agent_role, style="cyan") lite_agent_label.append("\nStatus: ", style="white") lite_agent_label.append("In Progress", style="yellow") @@ -931,15 +966,15 @@ class ConsoleFormatter: # Determine style based on status if status == "completed": - prefix, style = "โœ… LiteAgent:", "green" + prefix, style = f"{self._get_icon('โœ…')} LiteAgent:", "green" status_text = "Completed" title = "LiteAgent Completion" elif status == "failed": - prefix, style = "โŒ LiteAgent:", "red" + prefix, style = f"{self._get_icon('โŒ')} LiteAgent:", "red" status_text = "Failed" title = "LiteAgent Error" else: - prefix, style = "๐Ÿค– LiteAgent:", "yellow" + prefix, style = f"{self._get_icon('๐Ÿค–')} LiteAgent:", "yellow" status_text = "In Progress" title = "LiteAgent Status" @@ -1010,7 +1045,7 @@ class ConsoleFormatter: knowledge_branch = branch_to_use.add("") self.update_tree_label( - knowledge_branch, "๐Ÿ”", "Knowledge Retrieval Started", "blue" + knowledge_branch, self._get_icon("๐Ÿ”"), "Knowledge Retrieval Started", "blue" ) self.print(tree_to_use) @@ -1041,7 +1076,7 @@ class ConsoleFormatter: knowledge_panel = Panel( Text(knowledge_text, style="white"), - title="๐Ÿ“š Retrieved Knowledge", + title=f"{self._get_icon('๐Ÿ“š')} Retrieved Knowledge", border_style="green", padding=(1, 2), ) @@ -1053,7 +1088,7 @@ class ConsoleFormatter: for child in branch_to_use.children: if "Knowledge Retrieval Started" in str(child.label): self.update_tree_label( - child, "โœ…", "Knowledge Retrieval Completed", "green" + child, self._get_icon("โœ…"), "Knowledge Retrieval Completed", "green" ) knowledge_branch_found = True break @@ -1066,7 +1101,7 @@ class ConsoleFormatter: and "Completed" not in str(child.label) ): self.update_tree_label( - child, "โœ…", "Knowledge Retrieval Completed", "green" + child, self._get_icon("โœ…"), "Knowledge Retrieval Completed", "green" ) knowledge_branch_found = True break @@ -1074,7 +1109,7 @@ class ConsoleFormatter: if not knowledge_branch_found: knowledge_branch = branch_to_use.add("") self.update_tree_label( - knowledge_branch, "โœ…", "Knowledge Retrieval Completed", "green" + knowledge_branch, self._get_icon("โœ…"), "Knowledge Retrieval Completed", "green" ) self.print(tree_to_use) @@ -1086,7 +1121,7 @@ class ConsoleFormatter: knowledge_panel = Panel( Text(knowledge_text, style="white"), - title="๐Ÿ“š Retrieved Knowledge", + title=f"{self._get_icon('๐Ÿ“š')} Retrieved Knowledge", border_style="green", padding=(1, 2), ) @@ -1111,7 +1146,7 @@ class ConsoleFormatter: query_branch = branch_to_use.add("") self.update_tree_label( - query_branch, "๐Ÿ”Ž", f"Query: {task_prompt[:50]}...", "yellow" + query_branch, self._get_icon("๐Ÿ”Ž"), f"Query: {task_prompt[:50]}...", "yellow" ) self.print(tree_to_use) @@ -1132,7 +1167,7 @@ class ConsoleFormatter: if branch_to_use and tree_to_use: query_branch = branch_to_use.add("") - self.update_tree_label(query_branch, "โŒ", "Knowledge Query Failed", "red") + self.update_tree_label(query_branch, self._get_icon("โŒ"), "Knowledge Query Failed", "red") self.print(tree_to_use) self.print() @@ -1158,7 +1193,7 @@ class ConsoleFormatter: return None query_branch = branch_to_use.add("") - self.update_tree_label(query_branch, "โœ…", "Knowledge Query Completed", "green") + self.update_tree_label(query_branch, self._get_icon("โœ…"), "Knowledge Query Completed", "green") self.print(tree_to_use) self.print() @@ -1178,7 +1213,7 @@ class ConsoleFormatter: if branch_to_use and tree_to_use: query_branch = branch_to_use.add("") - self.update_tree_label(query_branch, "โŒ", "Knowledge Search Failed", "red") + self.update_tree_label(query_branch, self._get_icon("โŒ"), "Knowledge Search Failed", "red") self.print(tree_to_use) self.print() @@ -1223,7 +1258,7 @@ class ConsoleFormatter: status_text = ( f"Reasoning (Attempt {attempt})" if attempt > 1 else "Reasoning..." ) - self.update_tree_label(reasoning_branch, "๐Ÿง ", status_text, "blue") + self.update_tree_label(reasoning_branch, self._get_icon("๐Ÿง "), status_text, "blue") self.print(tree_to_use) self.print() @@ -1254,7 +1289,7 @@ class ConsoleFormatter: ) if reasoning_branch is not None: - self.update_tree_label(reasoning_branch, "โœ…", status_text, style) + self.update_tree_label(reasoning_branch, self._get_icon("โœ…"), status_text, style) if tree_to_use is not None: self.print(tree_to_use) @@ -1263,7 +1298,7 @@ class ConsoleFormatter: if plan: plan_panel = Panel( Text(plan, style="white"), - title="๐Ÿง  Reasoning Plan", + title=f"{self._get_icon('๐Ÿง ')} Reasoning Plan", border_style=style, padding=(1, 2), ) @@ -1292,7 +1327,7 @@ class ConsoleFormatter: ) if reasoning_branch is not None: - self.update_tree_label(reasoning_branch, "โŒ", "Reasoning Failed", "red") + self.update_tree_label(reasoning_branch, self._get_icon("โŒ"), "Reasoning Failed", "red") if tree_to_use is not None: self.print(tree_to_use) @@ -1335,7 +1370,7 @@ class ConsoleFormatter: # Create and display the panel agent_panel = Panel( content, - title="๐Ÿค– Agent Started", + title=f"{self._get_icon('๐Ÿค–')} Agent Started", border_style="magenta", padding=(1, 2), ) @@ -1406,7 +1441,7 @@ class ConsoleFormatter: # Create the main action panel action_panel = Panel( main_content, - title="๐Ÿ”ง Agent Tool Execution", + title=f"{self._get_icon('๐Ÿ”ง')} Agent Tool Execution", border_style="magenta", padding=(1, 2), ) @@ -1448,7 +1483,7 @@ class ConsoleFormatter: # Create and display the finish panel finish_panel = Panel( content, - title="โœ… Agent Final Answer", + title=f"{self._get_icon('โœ…')} Agent Final Answer", border_style="green", padding=(1, 2), ) diff --git a/tests/utilities/events/utils/__init__.py b/tests/utilities/events/utils/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/utilities/events/utils/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/utilities/events/utils/test_console_formatter_emoji_disable.py b/tests/utilities/events/utils/test_console_formatter_emoji_disable.py new file mode 100644 index 000000000..8f71eddd6 --- /dev/null +++ b/tests/utilities/events/utils/test_console_formatter_emoji_disable.py @@ -0,0 +1,272 @@ +import os +import pytest +from unittest.mock import patch +from rich.text import Text +from rich.tree import Tree + +from crewai.utilities.events.utils.console_formatter import ConsoleFormatter + + +class TestConsoleFormatterEmojiDisable: + """Test emoji disable functionality in ConsoleFormatter.""" + + def test_emoji_enabled_by_default(self): + """Test that emojis are enabled by default.""" + formatter = ConsoleFormatter(verbose=True) + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + assert formatter._get_icon("โŒ") == "โŒ" + assert formatter._get_icon("๐Ÿš€") == "๐Ÿš€" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_emoji_disabled_with_true(self): + """Test that emojis are disabled when CREWAI_DISABLE_EMOJIS=true.""" + formatter = ConsoleFormatter(verbose=True) + assert formatter.disable_emojis + assert formatter._get_icon("โœ…") == "[DONE]" + assert formatter._get_icon("โŒ") == "[FAILED]" + assert formatter._get_icon("๐Ÿš€") == "[CREW]" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "1"}) + def test_emoji_disabled_with_one(self): + """Test that emojis are disabled when CREWAI_DISABLE_EMOJIS=1.""" + formatter = ConsoleFormatter(verbose=True) + assert formatter.disable_emojis + assert formatter._get_icon("โœ…") == "[DONE]" + assert formatter._get_icon("โŒ") == "[FAILED]" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "yes"}) + def test_emoji_disabled_with_yes(self): + """Test that emojis are disabled when CREWAI_DISABLE_EMOJIS=yes.""" + formatter = ConsoleFormatter(verbose=True) + assert formatter.disable_emojis + assert formatter._get_icon("โœ…") == "[DONE]" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "false"}) + def test_emoji_enabled_with_false(self): + """Test that emojis are enabled when CREWAI_DISABLE_EMOJIS=false.""" + formatter = ConsoleFormatter(verbose=True) + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "0"}) + def test_emoji_enabled_with_zero(self): + """Test that emojis are enabled when CREWAI_DISABLE_EMOJIS=0.""" + formatter = ConsoleFormatter(verbose=True) + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": ""}) + def test_emoji_enabled_with_empty_string(self): + """Test that emojis are enabled when CREWAI_DISABLE_EMOJIS is empty.""" + formatter = ConsoleFormatter(verbose=True) + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + + def test_emoji_enabled_when_env_var_not_set(self): + """Test that emojis are enabled when CREWAI_DISABLE_EMOJIS is not set.""" + with patch.dict(os.environ, {}, clear=True): + formatter = ConsoleFormatter(verbose=True) + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + + def test_all_emoji_mappings(self): + """Test that all emojis in EMOJI_MAP have proper text alternatives.""" + with patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}): + formatter = ConsoleFormatter(verbose=True) + + expected_mappings = { + "โœ…": "[DONE]", + "โŒ": "[FAILED]", + "๐Ÿš€": "[CREW]", + "๐Ÿ”„": "[RUNNING]", + "๐Ÿ“‹": "[TASK]", + "๐Ÿ”ง": "[TOOL]", + "๐Ÿง ": "[THINKING]", + "๐ŸŒŠ": "[FLOW]", + "โœจ": "[CREATED]", + "๐Ÿงช": "[TEST]", + "๐Ÿ“š": "[KNOWLEDGE]", + "๐Ÿ”": "[SEARCH]", + "๐Ÿ”Ž": "[QUERY]", + "๐Ÿค–": "[AGENT]", + } + + for emoji, expected_text in expected_mappings.items(): + assert formatter._get_icon(emoji) == expected_text + + def test_unknown_emoji_fallback(self): + """Test that unknown emojis fall back to ASCII-only representation.""" + with patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}): + formatter = ConsoleFormatter(verbose=True) + result = formatter._get_icon("๐Ÿฆ„") + assert result == "" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_crew_tree_creation_without_emojis(self): + """Test that crew tree creation works without emojis.""" + formatter = ConsoleFormatter(verbose=True) + tree = formatter.create_crew_tree("Test Crew", "test-id") + + assert tree is not None + tree_label_str = str(tree.label) + assert "[CREW]" in tree_label_str + assert "๐Ÿš€" not in tree_label_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_task_branch_creation_without_emojis(self): + """Test that task branch creation works without emojis.""" + formatter = ConsoleFormatter(verbose=True) + crew_tree = Tree("Test Crew") + task_branch = formatter.create_task_branch(crew_tree, "test-task-id") + + assert task_branch is not None + task_label_str = str(task_branch.label) + assert "[TASK]" in task_label_str + assert "๐Ÿ“‹" not in task_label_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_update_crew_tree_completed_without_emojis(self): + """Test that crew tree completion updates work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + tree = Tree("Test") + + formatter.update_crew_tree(tree, "Test Crew", "test-id", "completed", "Test output") + + tree_str = str(tree.label) + assert "[DONE]" in tree_str + assert "โœ…" not in tree_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_tool_usage_without_emojis(self): + """Test that tool usage events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + crew_tree = Tree("Test Crew") + agent_branch = crew_tree.add("Test Agent") + + formatter.handle_tool_usage_started(agent_branch, "test_tool", crew_tree) + + found_tool_branch = False + for child in agent_branch.children: + child_str = str(child.label) + if "test_tool" in child_str: + assert "[TOOL]" in child_str + assert "๐Ÿ”ง" not in child_str + found_tool_branch = True + break + + assert found_tool_branch, "Tool branch should have been created" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_llm_call_without_emojis(self): + """Test that LLM call events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + crew_tree = Tree("Test Crew") + agent_branch = crew_tree.add("Test Agent") + + thinking_branch = formatter.handle_llm_call_started(agent_branch, crew_tree) + + if thinking_branch: + thinking_str = str(thinking_branch.label) + assert "[THINKING]" in thinking_str + assert "๐Ÿง " not in thinking_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_knowledge_retrieval_without_emojis(self): + """Test that knowledge retrieval events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + crew_tree = Tree("Test Crew") + agent_branch = crew_tree.add("Test Agent") + + knowledge_branch = formatter.handle_knowledge_retrieval_started(agent_branch, crew_tree) + + if knowledge_branch: + knowledge_str = str(knowledge_branch.label) + assert "[SEARCH]" in knowledge_str + assert "๐Ÿ”" not in knowledge_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_reasoning_without_emojis(self): + """Test that reasoning events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + crew_tree = Tree("Test Crew") + agent_branch = crew_tree.add("Test Agent") + + reasoning_branch = formatter.handle_reasoning_started(agent_branch, 1, crew_tree) + + if reasoning_branch: + reasoning_str = str(reasoning_branch.label) + assert "[THINKING]" in reasoning_str + assert "๐Ÿง " not in reasoning_str + + def test_case_insensitive_environment_variable(self): + """Test that environment variable parsing is case insensitive.""" + test_cases = [ + ("TRUE", True), + ("True", True), + ("true", True), + ("1", True), + ("YES", True), + ("yes", True), + ("Yes", True), + ("FALSE", False), + ("False", False), + ("false", False), + ("0", False), + ("NO", False), + ("no", False), + ("No", False), + ("", False), + ("random", False), + ] + + for env_value, expected_disabled in test_cases: + with patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": env_value}): + formatter = ConsoleFormatter(verbose=True) + assert formatter.disable_emojis == expected_disabled, f"Failed for env_value: {env_value}" + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_flow_events_without_emojis(self): + """Test that flow events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + + flow_tree = formatter.create_flow_tree("Test Flow", "test-flow-id") + + if flow_tree: + flow_str = str(flow_tree.label) + assert "[FLOW]" in flow_str + assert "๐ŸŒŠ" not in flow_str + + for child in flow_tree.children: + child_str = str(child.label) + if "Created" in child_str: + assert "[CREATED]" in child_str + assert "โœจ" not in child_str + + @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) + def test_lite_agent_without_emojis(self): + """Test that lite agent events work without emojis.""" + formatter = ConsoleFormatter(verbose=True) + + formatter.handle_lite_agent_execution("Test Agent", "started") + + if formatter.current_lite_agent_branch: + agent_str = str(formatter.current_lite_agent_branch.label) + assert "[AGENT]" in agent_str + assert "๐Ÿค–" not in agent_str + + def test_backward_compatibility(self): + """Test that the default behavior (emojis enabled) is preserved.""" + with patch.dict(os.environ, {}, clear=True): + formatter = ConsoleFormatter(verbose=True) + + assert not formatter.disable_emojis + assert formatter._get_icon("โœ…") == "โœ…" + assert formatter._get_icon("โŒ") == "โŒ" + assert formatter._get_icon("๐Ÿš€") == "๐Ÿš€" + + tree = formatter.create_crew_tree("Test Crew", "test-id") + if tree: + tree_str = str(tree.label) + assert "๐Ÿš€" in tree_str + assert "[CREW]" not in tree_str