mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-02-12 17:08:14 +00:00
Compare commits
3 Commits
codex/nl2s
...
devin/1769
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876513a5cf | ||
|
|
22ae9ef78e | ||
|
|
f24857c56b |
@@ -193,7 +193,13 @@ class Crew(FlowTrackable, BaseModel):
|
||||
tasks: list[Task] = Field(default_factory=list)
|
||||
agents: list[BaseAgent] = Field(default_factory=list)
|
||||
process: Process = Field(default=Process.sequential)
|
||||
verbose: bool = Field(default=False)
|
||||
verbose: bool | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Whether to enable verbose logging output. True=always enable, "
|
||||
"False=always disable, None=check CREWAI_VERBOSE env var (defaults to True if not set)."
|
||||
),
|
||||
)
|
||||
memory: bool = Field(
|
||||
default=False,
|
||||
description="If crew should use memory to store memories of it's execution",
|
||||
@@ -350,6 +356,8 @@ class Crew(FlowTrackable, BaseModel):
|
||||
@model_validator(mode="after")
|
||||
def set_private_attrs(self) -> Crew:
|
||||
"""set private attributes."""
|
||||
from crewai.utilities.logger_utils import should_enable_verbose
|
||||
|
||||
self._cache_handler = CacheHandler()
|
||||
event_listener = EventListener()
|
||||
|
||||
@@ -357,6 +365,11 @@ class Crew(FlowTrackable, BaseModel):
|
||||
tracing_enabled = should_enable_tracing(override=self.tracing)
|
||||
set_tracing_enabled(tracing_enabled)
|
||||
|
||||
# Determine verbose setting (respects CREWAI_VERBOSE env var)
|
||||
# Update self.verbose to the resolved boolean value so it can be used
|
||||
# consistently throughout the class (e.g., in _create_manager_agent)
|
||||
self.verbose = should_enable_verbose(override=self.verbose)
|
||||
|
||||
# Always setup trace listener - actual execution control is via contextvar
|
||||
trace_listener = TraceCollectionListener()
|
||||
trace_listener.setup_listeners(crewai_event_bus)
|
||||
|
||||
@@ -119,14 +119,11 @@ To enable tracing, do any one of these:
|
||||
self, content: Text, title: str, style: str = "blue", is_flow: bool = False
|
||||
) -> None:
|
||||
"""Print a panel with consistent formatting if verbose is enabled."""
|
||||
if not self.verbose:
|
||||
return
|
||||
panel = self.create_panel(content, title, style)
|
||||
if is_flow:
|
||||
self.print(panel)
|
||||
self.print()
|
||||
else:
|
||||
if self.verbose:
|
||||
self.print(panel)
|
||||
self.print()
|
||||
self.print(panel)
|
||||
self.print()
|
||||
|
||||
def handle_crew_status(
|
||||
self,
|
||||
|
||||
@@ -559,6 +559,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
name: str | None = None
|
||||
tracing: bool | None = None
|
||||
stream: bool = False
|
||||
verbose: bool = True
|
||||
|
||||
def __class_getitem__(cls: type[Flow[T]], item: type[T]) -> type[Flow[T]]:
|
||||
class _FlowGeneric(cls): # type: ignore
|
||||
@@ -572,6 +573,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
persistence: FlowPersistence | None = None,
|
||||
tracing: bool | None = None,
|
||||
suppress_flow_events: bool = False,
|
||||
verbose: bool | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a new Flow instance.
|
||||
@@ -580,8 +582,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
persistence: Optional persistence backend for storing flow states
|
||||
tracing: Whether to enable tracing. True=always enable, False=always disable, None=check environment/user settings
|
||||
suppress_flow_events: Whether to suppress flow event emissions (internal use)
|
||||
verbose: Whether to enable verbose logging output. True=always enable, False=always disable,
|
||||
None=check CREWAI_VERBOSE environment variable (defaults to True if not set).
|
||||
**kwargs: Additional state values to initialize or override
|
||||
"""
|
||||
from crewai.events.event_listener import EventListener
|
||||
|
||||
# Initialize basic instance attributes
|
||||
self._methods: dict[FlowMethodName, FlowMethod[Any, Any]] = {}
|
||||
self._method_execution_counts: dict[FlowMethodName, int] = {}
|
||||
@@ -605,6 +611,14 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
self._pending_feedback_context: PendingFeedbackContext | None = None
|
||||
self.suppress_flow_events: bool = suppress_flow_events
|
||||
|
||||
# Set verbose and configure event listener
|
||||
from crewai.utilities.logger_utils import should_enable_verbose
|
||||
|
||||
self.verbose = should_enable_verbose(override=verbose)
|
||||
event_listener = EventListener()
|
||||
event_listener.verbose = self.verbose
|
||||
event_listener.formatter.verbose = self.verbose
|
||||
|
||||
# Initialize state with initial values
|
||||
self._state = self._create_initial_state()
|
||||
self.tracing = tracing
|
||||
|
||||
@@ -4,6 +4,7 @@ from collections.abc import Generator
|
||||
import contextlib
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
|
||||
|
||||
@@ -56,3 +57,39 @@ def suppress_warnings() -> Generator[None, None, None]:
|
||||
"ignore", message="open_text is deprecated*", category=DeprecationWarning
|
||||
)
|
||||
yield
|
||||
|
||||
|
||||
def should_enable_verbose(*, override: bool | None = None) -> bool:
|
||||
"""Determine if verbose logging should be enabled.
|
||||
|
||||
This is the single source of truth for verbose logging enablement.
|
||||
Priority order:
|
||||
1. Explicit override (e.g., Crew.verbose=True/False or Flow.verbose=True/False)
|
||||
2. Environment variable CREWAI_VERBOSE
|
||||
|
||||
Args:
|
||||
override: Explicit override for verbose (True=always enable, False=always disable,
|
||||
None=check environment variable, defaults to True if not set)
|
||||
|
||||
Returns:
|
||||
True if verbose logging should be enabled, False otherwise.
|
||||
|
||||
Example:
|
||||
# Disable verbose logging globally via environment variable
|
||||
export CREWAI_VERBOSE=false
|
||||
|
||||
# Or in code
|
||||
flow = Flow(verbose=False)
|
||||
crew = Crew(verbose=False)
|
||||
"""
|
||||
if override is not None:
|
||||
return override
|
||||
|
||||
env_value = os.getenv("CREWAI_VERBOSE", "").lower()
|
||||
if env_value in ("false", "0"):
|
||||
return False
|
||||
if env_value in ("true", "1"):
|
||||
return True
|
||||
|
||||
# Default to True if not set
|
||||
return True
|
||||
|
||||
258
lib/crewai/tests/test_verbose_control.py
Normal file
258
lib/crewai/tests/test_verbose_control.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""Test verbose control for Flow and Crew."""
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from crewai.events.event_listener import EventListener
|
||||
from crewai.flow.flow import Flow, start, listen
|
||||
from crewai.utilities.logger_utils import should_enable_verbose
|
||||
|
||||
|
||||
class TestShouldEnableVerbose:
|
||||
"""Test the should_enable_verbose utility function."""
|
||||
|
||||
def test_override_true_returns_true(self):
|
||||
"""Test that explicit override=True always returns True."""
|
||||
assert should_enable_verbose(override=True) is True
|
||||
|
||||
def test_override_false_returns_false(self):
|
||||
"""Test that explicit override=False always returns False."""
|
||||
assert should_enable_verbose(override=False) is False
|
||||
|
||||
def test_env_var_false_disables_verbose(self):
|
||||
"""Test that CREWAI_VERBOSE=false disables verbose."""
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "false"}):
|
||||
assert should_enable_verbose() is False
|
||||
|
||||
def test_env_var_0_disables_verbose(self):
|
||||
"""Test that CREWAI_VERBOSE=0 disables verbose."""
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "0"}):
|
||||
assert should_enable_verbose() is False
|
||||
|
||||
def test_env_var_true_enables_verbose(self):
|
||||
"""Test that CREWAI_VERBOSE=true enables verbose."""
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "true"}):
|
||||
assert should_enable_verbose() is True
|
||||
|
||||
def test_env_var_1_enables_verbose(self):
|
||||
"""Test that CREWAI_VERBOSE=1 enables verbose."""
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "1"}):
|
||||
assert should_enable_verbose() is True
|
||||
|
||||
def test_no_env_var_defaults_to_true(self):
|
||||
"""Test that no CREWAI_VERBOSE env var defaults to True."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
# Remove CREWAI_VERBOSE if it exists
|
||||
os.environ.pop("CREWAI_VERBOSE", None)
|
||||
assert should_enable_verbose() is True
|
||||
|
||||
def test_override_takes_precedence_over_env_var(self):
|
||||
"""Test that explicit override takes precedence over env var."""
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "false"}):
|
||||
assert should_enable_verbose(override=True) is True
|
||||
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "true"}):
|
||||
assert should_enable_verbose(override=False) is False
|
||||
|
||||
|
||||
class TestFlowVerboseControl:
|
||||
"""Test verbose control in Flow class."""
|
||||
|
||||
def test_flow_verbose_default_is_true(self):
|
||||
"""Test that Flow verbose defaults to True when no env var is set."""
|
||||
# Remove CREWAI_VERBOSE if it exists
|
||||
os.environ.pop("CREWAI_VERBOSE", None)
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
flow = SimpleFlow()
|
||||
assert flow.verbose is True
|
||||
|
||||
def test_flow_verbose_false_disables_logging(self):
|
||||
"""Test that Flow with verbose=False disables logging."""
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
flow = SimpleFlow(verbose=False)
|
||||
assert flow.verbose is False
|
||||
|
||||
# Verify EventListener is also set to verbose=False
|
||||
event_listener = EventListener()
|
||||
assert event_listener.verbose is False
|
||||
assert event_listener.formatter.verbose is False
|
||||
|
||||
def test_flow_verbose_true_enables_logging(self):
|
||||
"""Test that Flow with verbose=True enables logging."""
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
flow = SimpleFlow(verbose=True)
|
||||
assert flow.verbose is True
|
||||
|
||||
# Verify EventListener is also set to verbose=True
|
||||
event_listener = EventListener()
|
||||
assert event_listener.verbose is True
|
||||
assert event_listener.formatter.verbose is True
|
||||
|
||||
def test_flow_respects_env_var_false(self):
|
||||
"""Test that Flow respects CREWAI_VERBOSE=false env var."""
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "false"}, clear=False):
|
||||
flow = SimpleFlow()
|
||||
assert flow.verbose is False
|
||||
|
||||
def test_flow_respects_env_var_true(self):
|
||||
"""Test that Flow respects CREWAI_VERBOSE=true env var."""
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "true"}, clear=False):
|
||||
flow = SimpleFlow()
|
||||
assert flow.verbose is True
|
||||
|
||||
def test_flow_explicit_verbose_overrides_env_var(self):
|
||||
"""Test that explicit verbose parameter overrides env var."""
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
return "done"
|
||||
|
||||
# Explicit verbose=True overrides CREWAI_VERBOSE=false
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "false"}, clear=False):
|
||||
flow = SimpleFlow(verbose=True)
|
||||
assert flow.verbose is True
|
||||
|
||||
# Explicit verbose=False overrides CREWAI_VERBOSE=true
|
||||
with patch.dict(os.environ, {"CREWAI_VERBOSE": "true"}, clear=False):
|
||||
flow = SimpleFlow(verbose=False)
|
||||
assert flow.verbose is False
|
||||
|
||||
|
||||
class TestFlowVerboseExecution:
|
||||
"""Test that verbose setting actually suppresses output during Flow execution."""
|
||||
|
||||
def test_flow_verbose_false_suppresses_console_output(self):
|
||||
"""Test that Flow with verbose=False suppresses console output."""
|
||||
execution_order = []
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
execution_order.append("step_1")
|
||||
return "step_1_done"
|
||||
|
||||
@listen(step_1)
|
||||
def step_2(self):
|
||||
execution_order.append("step_2")
|
||||
return "step_2_done"
|
||||
|
||||
# Create flow with verbose=False
|
||||
flow = SimpleFlow(verbose=False)
|
||||
|
||||
# Verify the formatter's verbose is False
|
||||
event_listener = EventListener()
|
||||
assert event_listener.formatter.verbose is False
|
||||
|
||||
# Execute the flow
|
||||
result = flow.kickoff()
|
||||
|
||||
# Flow should still execute correctly
|
||||
assert execution_order == ["step_1", "step_2"]
|
||||
assert result == "step_2_done"
|
||||
|
||||
def test_flow_verbose_true_allows_console_output(self):
|
||||
"""Test that Flow with verbose=True allows console output."""
|
||||
execution_order = []
|
||||
|
||||
class SimpleFlow(Flow):
|
||||
@start()
|
||||
def step_1(self):
|
||||
execution_order.append("step_1")
|
||||
return "step_1_done"
|
||||
|
||||
@listen(step_1)
|
||||
def step_2(self):
|
||||
execution_order.append("step_2")
|
||||
return "step_2_done"
|
||||
|
||||
# Create flow with verbose=True
|
||||
flow = SimpleFlow(verbose=True)
|
||||
|
||||
# Verify the formatter's verbose is True
|
||||
event_listener = EventListener()
|
||||
assert event_listener.formatter.verbose is True
|
||||
|
||||
# Execute the flow
|
||||
result = flow.kickoff()
|
||||
|
||||
# Flow should execute correctly
|
||||
assert execution_order == ["step_1", "step_2"]
|
||||
assert result == "step_2_done"
|
||||
|
||||
|
||||
class TestConsoleFormatterVerbose:
|
||||
"""Test that ConsoleFormatter respects verbose setting."""
|
||||
|
||||
def test_console_formatter_print_panel_respects_verbose_false(self):
|
||||
"""Test that print_panel does not print when verbose=False."""
|
||||
from rich.text import Text
|
||||
from crewai.events.utils.console_formatter import ConsoleFormatter
|
||||
|
||||
formatter = ConsoleFormatter(verbose=False)
|
||||
|
||||
# Create a mock to capture print calls
|
||||
with patch.object(formatter, "print") as mock_print:
|
||||
content = Text("Test content")
|
||||
formatter.print_panel(content, "Test Title", "blue", is_flow=True)
|
||||
|
||||
# print should not be called when verbose=False
|
||||
mock_print.assert_not_called()
|
||||
|
||||
def test_console_formatter_print_panel_respects_verbose_true(self):
|
||||
"""Test that print_panel prints when verbose=True."""
|
||||
from rich.text import Text
|
||||
from crewai.events.utils.console_formatter import ConsoleFormatter
|
||||
|
||||
formatter = ConsoleFormatter(verbose=True)
|
||||
|
||||
# Create a mock to capture print calls
|
||||
with patch.object(formatter, "print") as mock_print:
|
||||
content = Text("Test content")
|
||||
formatter.print_panel(content, "Test Title", "blue", is_flow=True)
|
||||
|
||||
# print should be called when verbose=True
|
||||
assert mock_print.call_count >= 1
|
||||
|
||||
def test_console_formatter_flow_events_respect_verbose_false(self):
|
||||
"""Test that flow events are suppressed when verbose=False."""
|
||||
from rich.text import Text
|
||||
from crewai.events.utils.console_formatter import ConsoleFormatter
|
||||
|
||||
formatter = ConsoleFormatter(verbose=False)
|
||||
|
||||
# Create a mock to capture print calls
|
||||
with patch.object(formatter, "print") as mock_print:
|
||||
content = Text("Flow event content")
|
||||
# is_flow=True should still respect verbose=False
|
||||
formatter.print_panel(content, "Flow Event", "blue", is_flow=True)
|
||||
|
||||
# print should not be called even for flow events when verbose=False
|
||||
mock_print.assert_not_called()
|
||||
Reference in New Issue
Block a user