Compare commits

...

3 Commits

Author SHA1 Message Date
Devin AI
876513a5cf fix: Update self.verbose to resolved value in Crew class
This ensures that self.verbose is always a boolean after initialization,
which is needed when passing verbose to Agent in _create_manager_agent.

Co-Authored-By: João <joao@crewai.com>
2026-01-30 20:22:19 +00:00
Devin AI
22ae9ef78e fix: Address PR review comments
- Change Crew.verbose field type from bool to bool | None to allow env var check
- Remove unused imports (StringIO, pytest) from test file

Co-Authored-By: João <joao@crewai.com>
2026-01-30 20:14:03 +00:00
Devin AI
f24857c56b feat: Add verbose control for Flow and global CREWAI_VERBOSE env var
This commit addresses issue #4314 by adding the ability to disable verbose
logging for Flow events.

Changes:
- Add verbose parameter to Flow.__init__() with support for True/False/None
- Add should_enable_verbose() utility function in logger_utils.py
- Support CREWAI_VERBOSE environment variable to globally control verbose output
- Fix ConsoleFormatter.print_panel() to respect verbose setting for all events
  (previously flow events bypassed the verbose check)
- Update Crew class to use should_enable_verbose() for consistency
- Add comprehensive tests for the new verbose control feature

Usage:
- Set CREWAI_VERBOSE=false to disable all verbose logging globally
- Or pass verbose=False to Flow() or Crew() to disable for specific instances
- Explicit verbose parameter takes precedence over environment variable

Fixes #4314

Co-Authored-By: João <joao@crewai.com>
2026-01-30 20:05:31 +00:00
5 changed files with 327 additions and 8 deletions

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View 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()