refactor: Implement code review suggestions for emoji disable feature

- Move EMOJI_MAP to module level as DEFAULT_EMOJI_MAP for memory efficiency
- Add _parse_bool_env() helper function for cleaner boolean conversion
- Implement performance optimization with icon caching
- Improve error handling for unknown emojis with better fallback format
- Update tests to work with new module structure
- All functionality preserved and tests passing

Addresses feedback from PR review #3063

Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
Devin AI
2025-06-25 13:02:32 +00:00
parent 4e288e4cf7
commit a9823a4368
2 changed files with 50 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
import os import os
from typing import Any, Dict, Optional from typing import Any, Dict, Optional, Union
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
@@ -8,32 +8,7 @@ from rich.tree import Tree
from rich.live import Live from rich.live import Live
from rich.syntax import Syntax from rich.syntax import Syntax
DEFAULT_EMOJI_MAP = {
class ConsoleFormatter:
current_crew_tree: Optional[Tree] = None
current_task_branch: Optional[Tree] = None
current_agent_branch: Optional[Tree] = None
current_tool_branch: Optional[Tree] = None
current_flow_tree: Optional[Tree] = None
current_method_branch: Optional[Tree] = None
current_lite_agent_branch: Optional[Tree] = None
tool_usage_counts: Dict[str, int] = {}
current_reasoning_branch: Optional[Tree] = None # Track reasoning status
_live_paused: bool = False
current_llm_tool_tree: Optional[Tree] = None
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.
# Once any non-Tree renderable is printed we stop the Live session so the
# final Tree persists on the terminal.
self._live: Optional[Live] = None
EMOJI_MAP = {
"": "[DONE]", "": "[DONE]",
"": "[FAILED]", "": "[FAILED]",
"🚀": "[CREW]", "🚀": "[CREW]",
@@ -59,11 +34,53 @@ class ConsoleFormatter:
"💾": "[SAVE]", "💾": "[SAVE]",
"🔒": "[SECURE]", "🔒": "[SECURE]",
"🌟": "[STAR]", "🌟": "[STAR]",
} }
def _parse_bool_env(val: str) -> bool:
"""Parse environment variable value to boolean."""
return val.lower() in ("true", "1", "yes") if val else False
class ConsoleFormatter:
current_crew_tree: Optional[Tree] = None
current_task_branch: Optional[Tree] = None
current_agent_branch: Optional[Tree] = None
current_tool_branch: Optional[Tree] = None
current_flow_tree: Optional[Tree] = None
current_method_branch: Optional[Tree] = None
current_lite_agent_branch: Optional[Tree] = None
tool_usage_counts: Dict[str, int] = {}
current_reasoning_branch: Optional[Tree] = None # Track reasoning status
_live_paused: bool = False
current_llm_tool_tree: Optional[Tree] = None
def __init__(self, verbose: bool = False):
self.console = Console(width=None)
self.verbose = verbose
self.disable_emojis = _parse_bool_env(os.getenv("CREWAI_DISABLE_EMOJIS", ""))
self.emoji_map = DEFAULT_EMOJI_MAP.copy()
self.icon_cache: Dict[str, str] = {}
if self.disable_emojis:
self.icon_cache = {emoji: text for emoji, text in self.emoji_map.items()}
# 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.
# Once any non-Tree renderable is printed we stop the Live session so the
# final Tree persists on the terminal.
self._live: Optional[Live] = None
def _get_icon(self, emoji: str) -> str: def _get_icon(self, emoji: str) -> str:
"""Get emoji or text alternative based on disable_emojis setting."""
if self.disable_emojis: if self.disable_emojis:
return self.EMOJI_MAP.get(emoji, emoji.encode('ascii', 'ignore').decode('ascii')) if emoji in self.icon_cache:
return self.icon_cache[emoji]
ascii_fallback = emoji.encode('ascii', 'ignore').decode('ascii')
fallback = f"[ICON:{ascii_fallback or 'UNKNOWN'}]"
self.icon_cache[emoji] = fallback
return fallback
return emoji return emoji
def create_panel(self, content: Text, title: str, style: str = "blue") -> Panel: def create_panel(self, content: Text, title: str, style: str = "blue") -> Panel:

View File

@@ -96,11 +96,11 @@ class TestConsoleFormatterEmojiDisable:
assert formatter._get_icon(emoji) == expected_text assert formatter._get_icon(emoji) == expected_text
def test_unknown_emoji_fallback(self): def test_unknown_emoji_fallback(self):
"""Test that unknown emojis fall back to ASCII-only representation.""" """Test that unknown emojis fall back to proper representation."""
with patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}): with patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}):
formatter = ConsoleFormatter(verbose=True) formatter = ConsoleFormatter(verbose=True)
result = formatter._get_icon("🦄") result = formatter._get_icon("🦄")
assert result == "" assert result == "[ICON:UNKNOWN]"
@patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"}) @patch.dict(os.environ, {"CREWAI_DISABLE_EMOJIS": "true"})
def test_crew_tree_creation_without_emojis(self): def test_crew_tree_creation_without_emojis(self):