From f62b05b310c1dbf4c10c3861f76cacc3fd3b98db Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Wed, 28 Jan 2026 03:16:52 -0500 Subject: [PATCH] fix: ensure verbosity flag is applied --- .../base_agent_executor_mixin.py | 12 +- .../src/crewai/agents/crew_agent_executor.py | 102 ++++++----- .../src/crewai/experimental/agent_executor.py | 45 ++--- .../src/crewai/flow/persistence/decorators.py | 9 +- lib/crewai/src/crewai/flow/utils.py | 64 ++++--- lib/crewai/src/crewai/lite_agent.py | 37 ++-- lib/crewai/src/crewai/llms/base_llm.py | 35 ++-- .../memory/storage/ltm_sqlite_storage.py | 67 ++++---- .../providers/ibm/embedding_callable.py | 15 +- lib/crewai/src/crewai/task.py | 38 +++-- lib/crewai/src/crewai/tools/tool_usage.py | 18 +- .../src/crewai/utilities/agent_utils.py | 159 +++++++++++------- lib/crewai/src/crewai/utilities/converter.py | 27 +-- lib/crewai/tests/agents/test_lite_agent.py | 50 ++++++ 14 files changed, 424 insertions(+), 254 deletions(-) diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py index d4c0c0db4..c9dceaa84 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent_executor_mixin.py @@ -37,7 +37,8 @@ class CrewAgentExecutorMixin: self.crew and self.agent and self.task - and f"Action: {sanitize_tool_name('Delegate work to coworker')}" not in output.text + and f"Action: {sanitize_tool_name('Delegate work to coworker')}" + not in output.text ): try: if ( @@ -132,10 +133,11 @@ class CrewAgentExecutorMixin: and self.crew._long_term_memory and self.crew._entity_memory is None ): - self._printer.print( - content="Long term memory is enabled, but entity memory is not enabled. Please configure entity memory or set memory=True to automatically enable it.", - color="bold_yellow", - ) + if self.agent and self.agent.verbose: + self._printer.print( + content="Long term memory is enabled, but entity memory is not enabled. Please configure entity memory or set memory=True to automatically enable it.", + color="bold_yellow", + ) def _ask_human_input(self, final_answer: str) -> str: """Prompt human input with mode-appropriate messaging. diff --git a/lib/crewai/src/crewai/agents/crew_agent_executor.py b/lib/crewai/src/crewai/agents/crew_agent_executor.py index 150ae4126..d6e9c9bc3 100644 --- a/lib/crewai/src/crewai/agents/crew_agent_executor.py +++ b/lib/crewai/src/crewai/agents/crew_agent_executor.py @@ -206,13 +206,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): try: formatted_answer = self._invoke_loop() except AssertionError: - self._printer.print( - content="Agent failed to reach a final answer. This is likely a bug - please report it.", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content="Agent failed to reach a final answer. This is likely a bug - please report it.", + color="red", + ) raise except Exception as e: - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise if self.ask_for_human_input: @@ -327,6 +328,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): messages=self.messages, llm=self.llm, callbacks=self.callbacks, + verbose=self.agent.verbose, ) break @@ -341,6 +343,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) # breakpoint() if self.response_model is not None: @@ -399,6 +402,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): iterations=self.iterations, log_error_after=self.log_error_after, printer=self._printer, + verbose=self.agent.verbose, ) except Exception as e: @@ -413,9 +417,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, i18n=self._i18n, + verbose=self.agent.verbose, ) continue - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise e finally: self.iterations += 1 @@ -461,6 +466,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): messages=self.messages, llm=self.llm, callbacks=self.callbacks, + verbose=self.agent.verbose, ) self._show_logs(formatted_answer) return formatted_answer @@ -482,6 +488,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) # Check if the response is a list of tool calls @@ -535,9 +542,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, i18n=self._i18n, + verbose=self.agent.verbose, ) continue - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise e finally: self.iterations += 1 @@ -559,6 +567,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) formatted_answer = AgentFinish( @@ -755,10 +764,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): track_delegation_if_needed(func_name, args_dict, self.task) # Find the structured tool for hook context - structured_tool = None - for tool in self.tools or []: - if sanitize_tool_name(tool.name) == func_name: - structured_tool = tool + structured_tool: CrewStructuredTool | None = None + for structured in self.tools or []: + if sanitize_tool_name(structured.name) == func_name: + structured_tool = structured break # Execute before_tool_call hooks @@ -779,10 +788,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): hook_blocked = True break except Exception as hook_error: - self._printer.print( - content=f"Error in before_tool_call hook: {hook_error}", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content=f"Error in before_tool_call hook: {hook_error}", + color="red", + ) # If hook blocked execution, set result and skip tool execution if hook_blocked: @@ -848,15 +858,16 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): after_hooks = get_after_tool_call_hooks() try: for after_hook in after_hooks: - hook_result = after_hook(after_hook_context) - if hook_result is not None: - result = hook_result + after_hook_result = after_hook(after_hook_context) + if after_hook_result is not None: + result = after_hook_result after_hook_context.tool_result = result except Exception as hook_error: - self._printer.print( - content=f"Error in after_tool_call hook: {hook_error}", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content=f"Error in after_tool_call hook: {hook_error}", + color="red", + ) # Emit tool usage finished event crewai_event_bus.emit( @@ -942,13 +953,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): try: formatted_answer = await self._ainvoke_loop() except AssertionError: - self._printer.print( - content="Agent failed to reach a final answer. This is likely a bug - please report it.", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content="Agent failed to reach a final answer. This is likely a bug - please report it.", + color="red", + ) raise except Exception as e: - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise if self.ask_for_human_input: @@ -999,6 +1011,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): messages=self.messages, llm=self.llm, callbacks=self.callbacks, + verbose=self.agent.verbose, ) break @@ -1013,6 +1026,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) if self.response_model is not None: @@ -1070,6 +1084,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): iterations=self.iterations, log_error_after=self.log_error_after, printer=self._printer, + verbose=self.agent.verbose, ) except Exception as e: @@ -1083,9 +1098,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, i18n=self._i18n, + verbose=self.agent.verbose, ) continue - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise e finally: self.iterations += 1 @@ -1125,6 +1141,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): messages=self.messages, llm=self.llm, callbacks=self.callbacks, + verbose=self.agent.verbose, ) self._show_logs(formatted_answer) return formatted_answer @@ -1146,6 +1163,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) # Check if the response is a list of tool calls if ( @@ -1198,9 +1216,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, i18n=self._i18n, + verbose=self.agent.verbose, ) continue - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise e finally: self.iterations += 1 @@ -1222,6 +1241,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): from_agent=self.agent, response_model=self.response_model, executor_context=self, + verbose=self.agent.verbose, ) formatted_answer = AgentFinish( @@ -1339,10 +1359,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): ) if train_iteration is None or not isinstance(train_iteration, int): - self._printer.print( - content="Invalid or missing train iteration. Cannot save training data.", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content="Invalid or missing train iteration. Cannot save training data.", + color="red", + ) return training_handler = CrewTrainingHandler(TRAINING_DATA_FILE) @@ -1362,13 +1383,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin): if train_iteration in agent_training_data: agent_training_data[train_iteration]["improved_output"] = result.output else: - self._printer.print( - content=( - f"No existing training data for agent {agent_id} and iteration " - f"{train_iteration}. Cannot save improved output." - ), - color="red", - ) + if self.agent.verbose: + self._printer.print( + content=( + f"No existing training data for agent {agent_id} and iteration " + f"{train_iteration}. Cannot save improved output." + ), + color="red", + ) return # Update the training data and save diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 8b521f880..676780138 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -341,6 +341,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): messages=list(self.state.messages), llm=self.llm, callbacks=self.callbacks, + verbose=self.agent.verbose, ) self.state.current_answer = formatted_answer @@ -366,6 +367,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): from_agent=self.agent, response_model=None, executor_context=self, + verbose=self.agent.verbose, ) # Parse the LLM response @@ -401,7 +403,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): return "context_error" if e.__class__.__module__.startswith("litellm"): raise e - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise @listen("continue_reasoning_native") @@ -436,6 +438,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): from_agent=self.agent, response_model=None, executor_context=self, + verbose=self.agent.verbose, ) # Check if the response is a list of tool calls @@ -474,7 +477,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): return "context_error" if e.__class__.__module__.startswith("litellm"): raise e - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise @router(call_llm_and_parse) @@ -670,10 +673,10 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): track_delegation_if_needed(func_name, args_dict, self.task) - structured_tool = None - for tool in self.tools or []: - if sanitize_tool_name(tool.name) == func_name: - structured_tool = tool + structured_tool: CrewStructuredTool | None = None + for structured in self.tools or []: + if sanitize_tool_name(structured.name) == func_name: + structured_tool = structured break hook_blocked = False @@ -693,10 +696,11 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): hook_blocked = True break except Exception as hook_error: - self._printer.print( - content=f"Error in before_tool_call hook: {hook_error}", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content=f"Error in before_tool_call hook: {hook_error}", + color="red", + ) if hook_blocked: result = f"Tool execution blocked by hook. Tool: {func_name}" @@ -758,15 +762,16 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): after_hooks = get_after_tool_call_hooks() try: for after_hook in after_hooks: - hook_result = after_hook(after_hook_context) - if hook_result is not None: - result = hook_result + after_hook_result = after_hook(after_hook_context) + if after_hook_result is not None: + result = after_hook_result after_hook_context.tool_result = result except Exception as hook_error: - self._printer.print( - content=f"Error in after_tool_call hook: {hook_error}", - color="red", - ) + if self.agent.verbose: + self._printer.print( + content=f"Error in after_tool_call hook: {hook_error}", + color="red", + ) # Emit tool usage finished event crewai_event_bus.emit( @@ -911,6 +916,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): iterations=self.state.iterations, log_error_after=self.log_error_after, printer=self._printer, + verbose=self.agent.verbose, ) if formatted_answer: @@ -930,6 +936,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): llm=self.llm, callbacks=self.callbacks, i18n=self._i18n, + verbose=self.agent.verbose, ) self.state.iterations += 1 @@ -1021,7 +1028,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self._console.print(fail_text) raise except Exception as e: - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise finally: self._is_executing = False @@ -1106,7 +1113,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin): self._console.print(fail_text) raise except Exception as e: - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.agent.verbose) raise finally: self._is_executing = False diff --git a/lib/crewai/src/crewai/flow/persistence/decorators.py b/lib/crewai/src/crewai/flow/persistence/decorators.py index dbbeaa16f..20c860353 100644 --- a/lib/crewai/src/crewai/flow/persistence/decorators.py +++ b/lib/crewai/src/crewai/flow/persistence/decorators.py @@ -118,17 +118,20 @@ class PersistenceDecorator: ) except Exception as e: error_msg = LOG_MESSAGES["save_error"].format(method_name, str(e)) - cls._printer.print(error_msg, color="red") + if verbose: + cls._printer.print(error_msg, color="red") logger.error(error_msg) raise RuntimeError(f"State persistence failed: {e!s}") from e except AttributeError as e: error_msg = LOG_MESSAGES["state_missing"] - cls._printer.print(error_msg, color="red") + if verbose: + cls._printer.print(error_msg, color="red") logger.error(error_msg) raise ValueError(error_msg) from e except (TypeError, ValueError) as e: error_msg = LOG_MESSAGES["id_missing"] - cls._printer.print(error_msg, color="red") + if verbose: + cls._printer.print(error_msg, color="red") logger.error(error_msg) raise ValueError(error_msg) from e diff --git a/lib/crewai/src/crewai/flow/utils.py b/lib/crewai/src/crewai/flow/utils.py index 747a9c39e..5dc812fc3 100644 --- a/lib/crewai/src/crewai/flow/utils.py +++ b/lib/crewai/src/crewai/flow/utils.py @@ -151,7 +151,9 @@ def _unwrap_function(function: Any) -> Any: return function -def get_possible_return_constants(function: Any) -> list[str] | None: +def get_possible_return_constants( + function: Any, verbose: bool = True +) -> list[str] | None: """Extract possible string return values from a function using AST parsing. This function analyzes the source code of a router method to identify @@ -178,10 +180,11 @@ def get_possible_return_constants(function: Any) -> list[str] | None: # Can't get source code return None except Exception as e: - _printer.print( - f"Error retrieving source code for function {function.__name__}: {e}", - color="red", - ) + if verbose: + _printer.print( + f"Error retrieving source code for function {function.__name__}: {e}", + color="red", + ) return None try: @@ -190,25 +193,28 @@ def get_possible_return_constants(function: Any) -> list[str] | None: # Parse the source code into an AST code_ast = ast.parse(source) except IndentationError as e: - _printer.print( - f"IndentationError while parsing source code of {function.__name__}: {e}", - color="red", - ) - _printer.print(f"Source code:\n{source}", color="yellow") + if verbose: + _printer.print( + f"IndentationError while parsing source code of {function.__name__}: {e}", + color="red", + ) + _printer.print(f"Source code:\n{source}", color="yellow") return None except SyntaxError as e: - _printer.print( - f"SyntaxError while parsing source code of {function.__name__}: {e}", - color="red", - ) - _printer.print(f"Source code:\n{source}", color="yellow") + if verbose: + _printer.print( + f"SyntaxError while parsing source code of {function.__name__}: {e}", + color="red", + ) + _printer.print(f"Source code:\n{source}", color="yellow") return None except Exception as e: - _printer.print( - f"Unexpected error while parsing source code of {function.__name__}: {e}", - color="red", - ) - _printer.print(f"Source code:\n{source}", color="yellow") + if verbose: + _printer.print( + f"Unexpected error while parsing source code of {function.__name__}: {e}", + color="red", + ) + _printer.print(f"Source code:\n{source}", color="yellow") return None return_values: set[str] = set() @@ -388,15 +394,17 @@ def get_possible_return_constants(function: Any) -> list[str] | None: StateAttributeVisitor().visit(class_ast) except Exception as e: - _printer.print( - f"Could not analyze class context for {function.__name__}: {e}", - color="yellow", - ) + if verbose: + _printer.print( + f"Could not analyze class context for {function.__name__}: {e}", + color="yellow", + ) except Exception as e: - _printer.print( - f"Could not introspect class for {function.__name__}: {e}", - color="yellow", - ) + if verbose: + _printer.print( + f"Could not introspect class for {function.__name__}: {e}", + color="yellow", + ) VariableAssignmentVisitor().visit(code_ast) ReturnVisitor().visit(code_ast) diff --git a/lib/crewai/src/crewai/lite_agent.py b/lib/crewai/src/crewai/lite_agent.py index a0f2a90af..21c9089f3 100644 --- a/lib/crewai/src/crewai/lite_agent.py +++ b/lib/crewai/src/crewai/lite_agent.py @@ -72,13 +72,13 @@ from crewai.utilities.agent_utils import ( from crewai.utilities.converter import ( Converter, ConverterError, - generate_model_description, ) from crewai.utilities.guardrail import process_guardrail from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType from crewai.utilities.i18n import I18N, get_i18n from crewai.utilities.llm_utils import create_llm from crewai.utilities.printer import Printer +from crewai.utilities.pydantic_schema_utils import generate_model_description from crewai.utilities.token_counter_callback import TokenCalcHandler from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.types import LLMMessage @@ -344,11 +344,12 @@ class LiteAgent(FlowTrackable, BaseModel): ) except Exception as e: - self._printer.print( - content="Agent failed to reach a final answer. This is likely a bug - please report it.", - color="red", - ) - handle_unknown_error(self._printer, e) + if self.verbose: + self._printer.print( + content="Agent failed to reach a final answer. This is likely a bug - please report it.", + color="red", + ) + handle_unknown_error(self._printer, e, verbose=self.verbose) # Emit error event crewai_event_bus.emit( self, @@ -396,10 +397,11 @@ class LiteAgent(FlowTrackable, BaseModel): if isinstance(result, BaseModel): formatted_result = result except ConverterError as e: - self._printer.print( - content=f"Failed to parse output into response format after retries: {e.message}", - color="yellow", - ) + if self.verbose: + self._printer.print( + content=f"Failed to parse output into response format after retries: {e.message}", + color="yellow", + ) # Calculate token usage metrics if isinstance(self.llm, BaseLLM): @@ -605,6 +607,7 @@ class LiteAgent(FlowTrackable, BaseModel): messages=self._messages, llm=cast(LLM, self.llm), callbacks=self._callbacks, + verbose=self.verbose, ) enforce_rpm_limit(self.request_within_rpm_limit) @@ -617,6 +620,7 @@ class LiteAgent(FlowTrackable, BaseModel): printer=self._printer, from_agent=self, executor_context=self, + verbose=self.verbose, ) except Exception as e: @@ -646,16 +650,18 @@ class LiteAgent(FlowTrackable, BaseModel): self._append_message(formatted_answer.text, role="assistant") except OutputParserError as e: # noqa: PERF203 - self._printer.print( - content="Failed to parse LLM output. Retrying...", - color="yellow", - ) + if self.verbose: + self._printer.print( + content="Failed to parse LLM output. Retrying...", + color="yellow", + ) formatted_answer = handle_output_parser_exception( e=e, messages=self._messages, iterations=self._iterations, log_error_after=3, printer=self._printer, + verbose=self.verbose, ) except Exception as e: @@ -670,9 +676,10 @@ class LiteAgent(FlowTrackable, BaseModel): llm=cast(LLM, self.llm), callbacks=self._callbacks, i18n=self.i18n, + verbose=self.verbose, ) continue - handle_unknown_error(self._printer, e) + handle_unknown_error(self._printer, e, verbose=self.verbose) raise e finally: diff --git a/lib/crewai/src/crewai/llms/base_llm.py b/lib/crewai/src/crewai/llms/base_llm.py index 2a6def197..56e6dcb34 100644 --- a/lib/crewai/src/crewai/llms/base_llm.py +++ b/lib/crewai/src/crewai/llms/base_llm.py @@ -404,7 +404,7 @@ class BaseLLM(ABC): from_agent: Agent | None = None, tool_call: dict[str, Any] | None = None, call_type: LLMCallType | None = None, - response_id: str | None = None + response_id: str | None = None, ) -> None: """Emit stream chunk event. @@ -427,7 +427,7 @@ class BaseLLM(ABC): from_task=from_task, from_agent=from_agent, call_type=call_type, - response_id=response_id + response_id=response_id, ), ) @@ -497,7 +497,7 @@ class BaseLLM(ABC): from_agent=from_agent, ) - return result + return str(result) if not isinstance(result, str) else result except Exception as e: error_msg = f"Error executing function '{function_name}': {e!s}" @@ -737,22 +737,25 @@ class BaseLLM(ABC): task=None, crew=None, ) + verbose = getattr(from_agent, "verbose", True) if from_agent else True printer = Printer() try: for hook in before_hooks: result = hook(hook_context) if result is False: - printer.print( - content="LLM call blocked by before_llm_call hook", - color="yellow", - ) + if verbose: + printer.print( + content="LLM call blocked by before_llm_call hook", + color="yellow", + ) return False except Exception as e: - printer.print( - content=f"Error in before_llm_call hook: {e}", - color="yellow", - ) + if verbose: + printer.print( + content=f"Error in before_llm_call hook: {e}", + color="yellow", + ) return True @@ -805,6 +808,7 @@ class BaseLLM(ABC): crew=None, response=response, ) + verbose = getattr(from_agent, "verbose", True) if from_agent else True printer = Printer() modified_response = response @@ -815,9 +819,10 @@ class BaseLLM(ABC): modified_response = result hook_context.response = modified_response except Exception as e: - printer.print( - content=f"Error in after_llm_call hook: {e}", - color="yellow", - ) + if verbose: + printer.print( + content=f"Error in after_llm_call hook: {e}", + color="yellow", + ) return modified_response diff --git a/lib/crewai/src/crewai/memory/storage/ltm_sqlite_storage.py b/lib/crewai/src/crewai/memory/storage/ltm_sqlite_storage.py index bf4f6c738..2e64f416e 100644 --- a/lib/crewai/src/crewai/memory/storage/ltm_sqlite_storage.py +++ b/lib/crewai/src/crewai/memory/storage/ltm_sqlite_storage.py @@ -12,15 +12,17 @@ from crewai.utilities.paths import db_storage_path class LTMSQLiteStorage: """SQLite storage class for long-term memory data.""" - def __init__(self, db_path: str | None = None) -> None: + def __init__(self, db_path: str | None = None, verbose: bool = True) -> None: """Initialize the SQLite storage. Args: db_path: Optional path to the database file. + verbose: Whether to print error messages. """ if db_path is None: db_path = str(Path(db_storage_path()) / "long_term_memory_storage.db") self.db_path = db_path + self._verbose = verbose self._printer: Printer = Printer() Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) self._initialize_db() @@ -44,10 +46,11 @@ class LTMSQLiteStorage: conn.commit() except sqlite3.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred during database initialization: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred during database initialization: {e}", + color="red", + ) def save( self, @@ -69,10 +72,11 @@ class LTMSQLiteStorage: ) conn.commit() except sqlite3.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", + color="red", + ) def load(self, task_description: str, latest_n: int) -> list[dict[str, Any]] | None: """Queries the LTM table by task description with error handling.""" @@ -101,10 +105,11 @@ class LTMSQLiteStorage: ] except sqlite3.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", + color="red", + ) return None def reset(self) -> None: @@ -116,10 +121,11 @@ class LTMSQLiteStorage: conn.commit() except sqlite3.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", + color="red", + ) async def asave( self, @@ -147,10 +153,11 @@ class LTMSQLiteStorage: ) await conn.commit() except aiosqlite.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", + color="red", + ) async def aload( self, task_description: str, latest_n: int @@ -187,10 +194,11 @@ class LTMSQLiteStorage: for row in rows ] except aiosqlite.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", + color="red", + ) return None async def areset(self) -> None: @@ -200,7 +208,8 @@ class LTMSQLiteStorage: await conn.execute("DELETE FROM long_term_memories") await conn.commit() except aiosqlite.Error as e: - self._printer.print( - content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", - color="red", - ) + if self._verbose: + self._printer.print( + content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", + color="red", + ) diff --git a/lib/crewai/src/crewai/rag/embeddings/providers/ibm/embedding_callable.py b/lib/crewai/src/crewai/rag/embeddings/providers/ibm/embedding_callable.py index 26cc84dd0..7104c1705 100644 --- a/lib/crewai/src/crewai/rag/embeddings/providers/ibm/embedding_callable.py +++ b/lib/crewai/src/crewai/rag/embeddings/providers/ibm/embedding_callable.py @@ -1,6 +1,6 @@ """IBM WatsonX embedding function implementation.""" -from typing import cast +from typing import Any, cast from chromadb.api.types import Documents, EmbeddingFunction, Embeddings from typing_extensions import Unpack @@ -15,14 +15,18 @@ _printer = Printer() class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]): """Embedding function for IBM WatsonX models.""" - def __init__(self, **kwargs: Unpack[WatsonXProviderConfig]) -> None: + def __init__( + self, *, verbose: bool = True, **kwargs: Unpack[WatsonXProviderConfig] + ) -> None: """Initialize WatsonX embedding function. Args: + verbose: Whether to print error messages. **kwargs: Configuration parameters for WatsonX Embeddings and Credentials. """ super().__init__(**kwargs) self._config = kwargs + self._verbose = verbose @staticmethod def name() -> str: @@ -56,7 +60,7 @@ class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]): if isinstance(input, str): input = [input] - embeddings_config: dict = { + embeddings_config: dict[str, Any] = { "model_id": self._config["model_id"], } if "params" in self._config and self._config["params"] is not None: @@ -90,7 +94,7 @@ class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]): if "credentials" in self._config and self._config["credentials"] is not None: embeddings_config["credentials"] = self._config["credentials"] else: - cred_config: dict = {} + cred_config: dict[str, Any] = {} if "url" in self._config and self._config["url"] is not None: cred_config["url"] = self._config["url"] if "api_key" in self._config and self._config["api_key"] is not None: @@ -159,5 +163,6 @@ class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]): embeddings = embedding.embed_documents(input) return cast(Embeddings, embeddings) except Exception as e: - _printer.print(f"Error during WatsonX embedding: {e}", color="red") + if self._verbose: + _printer.print(f"Error during WatsonX embedding: {e}", color="red") raise diff --git a/lib/crewai/src/crewai/task.py b/lib/crewai/src/crewai/task.py index 0ccad6fc5..77056f0ca 100644 --- a/lib/crewai/src/crewai/task.py +++ b/lib/crewai/src/crewai/task.py @@ -767,10 +767,11 @@ class Task(BaseModel): if files: supported_types: list[str] = [] if self.agent.llm and self.agent.llm.supports_multimodal(): - provider = getattr(self.agent.llm, "provider", None) or getattr( - self.agent.llm, "model", "openai" + provider: str = str( + getattr(self.agent.llm, "provider", None) + or getattr(self.agent.llm, "model", "openai") ) - api = getattr(self.agent.llm, "api", None) + api: str | None = getattr(self.agent.llm, "api", None) supported_types = get_supported_content_types(provider, api) def is_auto_injected(content_type: str) -> bool: @@ -887,10 +888,11 @@ Follow these guidelines: try: crew_chat_messages = json.loads(crew_chat_messages_json) except json.JSONDecodeError as e: - _printer.print( - f"An error occurred while parsing crew chat messages: {e}", - color="red", - ) + if self.agent and self.agent.verbose: + _printer.print( + f"An error occurred while parsing crew chat messages: {e}", + color="red", + ) raise conversation_history = "\n".join( @@ -1132,11 +1134,12 @@ Follow these guidelines: guardrail_result_error=guardrail_result.error, task_output=task_output.raw, ) - printer = Printer() - printer.print( - content=f"Guardrail {guardrail_index if guardrail_index is not None else ''} blocked (attempt {attempt + 1}/{max_attempts}), retrying due to: {guardrail_result.error}\n", - color="yellow", - ) + if agent and agent.verbose: + printer = Printer() + printer.print( + content=f"Guardrail {guardrail_index if guardrail_index is not None else ''} blocked (attempt {attempt + 1}/{max_attempts}), retrying due to: {guardrail_result.error}\n", + color="yellow", + ) # Regenerate output from agent result = agent.execute_task( @@ -1229,11 +1232,12 @@ Follow these guidelines: guardrail_result_error=guardrail_result.error, task_output=task_output.raw, ) - printer = Printer() - printer.print( - content=f"Guardrail {guardrail_index if guardrail_index is not None else ''} blocked (attempt {attempt + 1}/{max_attempts}), retrying due to: {guardrail_result.error}\n", - color="yellow", - ) + if agent and agent.verbose: + printer = Printer() + printer.print( + content=f"Guardrail {guardrail_index if guardrail_index is not None else ''} blocked (attempt {attempt + 1}/{max_attempts}), retrying due to: {guardrail_result.error}\n", + color="yellow", + ) result = await agent.aexecute_task( task=self, diff --git a/lib/crewai/src/crewai/tools/tool_usage.py b/lib/crewai/src/crewai/tools/tool_usage.py index 30cc21666..e5a9e6154 100644 --- a/lib/crewai/src/crewai/tools/tool_usage.py +++ b/lib/crewai/src/crewai/tools/tool_usage.py @@ -384,6 +384,8 @@ class ToolUsage: if ( hasattr(available_tool, "max_usage_count") and available_tool.max_usage_count is not None + and self.agent + and self.agent.verbose ): self._printer.print( content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", @@ -396,6 +398,8 @@ class ToolUsage: if ( hasattr(available_tool, "max_usage_count") and available_tool.max_usage_count is not None + and self.agent + and self.agent.verbose ): self._printer.print( content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", @@ -610,6 +614,8 @@ class ToolUsage: if ( hasattr(available_tool, "max_usage_count") and available_tool.max_usage_count is not None + and self.agent + and self.agent.verbose ): self._printer.print( content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", @@ -622,6 +628,8 @@ class ToolUsage: if ( hasattr(available_tool, "max_usage_count") and available_tool.max_usage_count is not None + and self.agent + and self.agent.verbose ): self._printer.print( content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", @@ -884,15 +892,17 @@ class ToolUsage: # Attempt 4: Repair JSON try: repaired_input = str(repair_json(tool_input, skip_json_loads=True)) - self._printer.print( - content=f"Repaired JSON: {repaired_input}", color="blue" - ) + if self.agent and self.agent.verbose: + self._printer.print( + content=f"Repaired JSON: {repaired_input}", color="blue" + ) arguments = json.loads(repaired_input) if isinstance(arguments, dict): return arguments except Exception as e: error = f"Failed to repair JSON: {e}" - self._printer.print(content=error, color="red") + if self.agent and self.agent.verbose: + self._printer.print(content=error, color="red") error_message = ( "Tool input must be a valid dictionary in JSON or Python literal format" diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index af4f464d9..d759368d9 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -210,6 +210,7 @@ def handle_max_iterations_exceeded( messages: list[LLMMessage], llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], + verbose: bool = True, ) -> AgentFinish: """Handles the case when the maximum number of iterations is exceeded. Performs one more LLM call to get the final answer. @@ -220,14 +221,16 @@ def handle_max_iterations_exceeded( messages: List of messages to send to the LLM. llm: The LLM instance to call. callbacks: List of callbacks for the LLM call. + verbose: Whether to print output. Returns: AgentFinish with the final answer after exceeding max iterations. """ - printer.print( - content="Maximum iterations reached. Requesting final answer.", - color="yellow", - ) + if verbose: + printer.print( + content="Maximum iterations reached. Requesting final answer.", + color="yellow", + ) if formatted_answer and hasattr(formatted_answer, "text"): assistant_message = ( @@ -245,10 +248,11 @@ def handle_max_iterations_exceeded( ) if answer is None or answer == "": - printer.print( - content="Received None or empty response from LLM call.", - color="red", - ) + if verbose: + printer.print( + content="Received None or empty response from LLM call.", + color="red", + ) raise ValueError("Invalid response from LLM call - None or empty.") formatted = format_answer(answer=answer) @@ -322,6 +326,7 @@ def get_llm_response( from_agent: Agent | LiteAgent | None = None, response_model: type[BaseModel] | None = None, executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None = None, + verbose: bool = True, ) -> str | Any: """Call the LLM and return the response, handling any invalid responses. @@ -347,7 +352,7 @@ def get_llm_response( """ if executor_context is not None: - if not _setup_before_llm_call_hooks(executor_context, printer): + if not _setup_before_llm_call_hooks(executor_context, printer, verbose=verbose): raise ValueError("LLM call blocked by before_llm_call hook") messages = executor_context.messages @@ -364,13 +369,16 @@ def get_llm_response( except Exception as e: raise e if not answer: - printer.print( - content="Received None or empty response from LLM call.", - color="red", - ) + if verbose: + printer.print( + content="Received None or empty response from LLM call.", + color="red", + ) raise ValueError("Invalid response from LLM call - None or empty.") - return _setup_after_llm_call_hooks(executor_context, answer, printer) + return _setup_after_llm_call_hooks( + executor_context, answer, printer, verbose=verbose + ) async def aget_llm_response( @@ -384,6 +392,7 @@ async def aget_llm_response( from_agent: Agent | LiteAgent | None = None, response_model: type[BaseModel] | None = None, executor_context: CrewAgentExecutor | AgentExecutor | None = None, + verbose: bool = True, ) -> str | Any: """Call the LLM asynchronously and return the response. @@ -408,7 +417,7 @@ async def aget_llm_response( ValueError: If the response is None or empty. """ if executor_context is not None: - if not _setup_before_llm_call_hooks(executor_context, printer): + if not _setup_before_llm_call_hooks(executor_context, printer, verbose=verbose): raise ValueError("LLM call blocked by before_llm_call hook") messages = executor_context.messages @@ -425,13 +434,16 @@ async def aget_llm_response( except Exception as e: raise e if not answer: - printer.print( - content="Received None or empty response from LLM call.", - color="red", - ) + if verbose: + printer.print( + content="Received None or empty response from LLM call.", + color="red", + ) raise ValueError("Invalid response from LLM call - None or empty.") - return _setup_after_llm_call_hooks(executor_context, answer, printer) + return _setup_after_llm_call_hooks( + executor_context, answer, printer, verbose=verbose + ) def process_llm_response( @@ -498,13 +510,19 @@ def handle_agent_action_core( return formatted_answer -def handle_unknown_error(printer: Printer, exception: Exception) -> None: +def handle_unknown_error( + printer: Printer, exception: Exception, verbose: bool = True +) -> None: """Handle unknown errors by informing the user. Args: printer: Printer instance for output exception: The exception that occurred + verbose: Whether to print output. """ + if not verbose: + return + error_message = str(exception) if "litellm" in error_message: @@ -526,6 +544,7 @@ def handle_output_parser_exception( iterations: int, log_error_after: int = 3, printer: Printer | None = None, + verbose: bool = True, ) -> AgentAction: """Handle OutputParserError by updating messages and formatted_answer. @@ -548,7 +567,7 @@ def handle_output_parser_exception( thought="", ) - if iterations > log_error_after and printer: + if verbose and iterations > log_error_after and printer: printer.print( content=f"Error parsing LLM output, agent will retry: {e.error}", color="red", @@ -578,6 +597,7 @@ def handle_context_length( llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], i18n: I18N, + verbose: bool = True, ) -> None: """Handle context length exceeded by either summarizing or raising an error. @@ -593,16 +613,20 @@ def handle_context_length( SystemExit: If context length is exceeded and user opts not to summarize """ if respect_context_window: - printer.print( - content="Context length exceeded. Summarizing content to fit the model context window. Might take a while...", - color="yellow", + if verbose: + printer.print( + content="Context length exceeded. Summarizing content to fit the model context window. Might take a while...", + color="yellow", + ) + summarize_messages( + messages=messages, llm=llm, callbacks=callbacks, i18n=i18n, verbose=verbose ) - summarize_messages(messages=messages, llm=llm, callbacks=callbacks, i18n=i18n) else: - printer.print( - content="Context length exceeded. Consider using smaller text or RAG tools from crewai_tools.", - color="red", - ) + if verbose: + printer.print( + content="Context length exceeded. Consider using smaller text or RAG tools from crewai_tools.", + color="red", + ) raise SystemExit( "Context length exceeded and user opted not to summarize. Consider using smaller text or RAG tools from crewai_tools." ) @@ -613,6 +637,7 @@ def summarize_messages( llm: LLM | BaseLLM, callbacks: list[TokenCalcHandler], i18n: I18N, + verbose: bool = True, ) -> None: """Summarize messages to fit within context window. @@ -644,10 +669,11 @@ def summarize_messages( total_groups = len(messages_groups) for idx, group in enumerate(messages_groups, 1): - Printer().print( - content=f"Summarizing {idx}/{total_groups}...", - color="yellow", - ) + if verbose: + Printer().print( + content=f"Summarizing {idx}/{total_groups}...", + color="yellow", + ) summarization_messages = [ format_message_for_llm( @@ -905,12 +931,14 @@ def extract_tool_call_info( def _setup_before_llm_call_hooks( executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None, printer: Printer, + verbose: bool = True, ) -> bool: """Setup and invoke before_llm_call hooks for the executor context. Args: executor_context: The executor context to setup the hooks for. printer: Printer instance for error logging. + verbose: Whether to print output. Returns: True if LLM execution should proceed, False if blocked by a hook. @@ -925,26 +953,29 @@ def _setup_before_llm_call_hooks( for hook in executor_context.before_llm_call_hooks: result = hook(hook_context) if result is False: - printer.print( - content="LLM call blocked by before_llm_call hook", - color="yellow", - ) + if verbose: + printer.print( + content="LLM call blocked by before_llm_call hook", + color="yellow", + ) return False except Exception as e: - printer.print( - content=f"Error in before_llm_call hook: {e}", - color="yellow", - ) + if verbose: + printer.print( + content=f"Error in before_llm_call hook: {e}", + color="yellow", + ) if not isinstance(executor_context.messages, list): - printer.print( - content=( - "Warning: before_llm_call hook replaced messages with non-list. " - "Restoring original messages list. Hooks should modify messages in-place, " - "not replace the list (e.g., use context.messages.append() not context.messages = [])." - ), - color="yellow", - ) + if verbose: + printer.print( + content=( + "Warning: before_llm_call hook replaced messages with non-list. " + "Restoring original messages list. Hooks should modify messages in-place, " + "not replace the list (e.g., use context.messages.append() not context.messages = [])." + ), + color="yellow", + ) if isinstance(original_messages, list): executor_context.messages = original_messages else: @@ -957,6 +988,7 @@ def _setup_after_llm_call_hooks( executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None, answer: str, printer: Printer, + verbose: bool = True, ) -> str: """Setup and invoke after_llm_call hooks for the executor context. @@ -964,6 +996,7 @@ def _setup_after_llm_call_hooks( executor_context: The executor context to setup the hooks for. answer: The LLM response string. printer: Printer instance for error logging. + verbose: Whether to print output. Returns: The potentially modified response string. @@ -981,20 +1014,22 @@ def _setup_after_llm_call_hooks( answer = modified_response except Exception as e: - printer.print( - content=f"Error in after_llm_call hook: {e}", - color="yellow", - ) + if verbose: + printer.print( + content=f"Error in after_llm_call hook: {e}", + color="yellow", + ) if not isinstance(executor_context.messages, list): - printer.print( - content=( - "Warning: after_llm_call hook replaced messages with non-list. " - "Restoring original messages list. Hooks should modify messages in-place, " - "not replace the list (e.g., use context.messages.append() not context.messages = [])." - ), - color="yellow", - ) + if verbose: + printer.print( + content=( + "Warning: after_llm_call hook replaced messages with non-list. " + "Restoring original messages list. Hooks should modify messages in-place, " + "not replace the list (e.g., use context.messages.append() not context.messages = [])." + ), + color="yellow", + ) if isinstance(original_messages, list): executor_context.messages = original_messages else: diff --git a/lib/crewai/src/crewai/utilities/converter.py b/lib/crewai/src/crewai/utilities/converter.py index 742a1f6a0..09230ee12 100644 --- a/lib/crewai/src/crewai/utilities/converter.py +++ b/lib/crewai/src/crewai/utilities/converter.py @@ -205,10 +205,11 @@ def convert_to_model( ) except Exception as e: - Printer().print( - content=f"Unexpected error during model conversion: {type(e).__name__}: {e}. Returning original result.", - color="red", - ) + if agent and getattr(agent, "verbose", True): + Printer().print( + content=f"Unexpected error during model conversion: {type(e).__name__}: {e}. Returning original result.", + color="red", + ) return result @@ -262,10 +263,11 @@ def handle_partial_json( except ValidationError: raise except Exception as e: - Printer().print( - content=f"Unexpected error during partial JSON handling: {type(e).__name__}: {e}. Attempting alternative conversion method.", - color="red", - ) + if agent and getattr(agent, "verbose", True): + Printer().print( + content=f"Unexpected error during partial JSON handling: {type(e).__name__}: {e}. Attempting alternative conversion method.", + color="red", + ) return convert_with_instructions( result=result, @@ -323,10 +325,11 @@ def convert_with_instructions( ) if isinstance(exported_result, ConverterError): - Printer().print( - content=f"Failed to convert result to model: {exported_result}", - color="red", - ) + if agent and getattr(agent, "verbose", True): + Printer().print( + content=f"Failed to convert result to model: {exported_result}", + color="red", + ) return result return exported_result diff --git a/lib/crewai/tests/agents/test_lite_agent.py b/lib/crewai/tests/agents/test_lite_agent.py index 17d2ced42..153db39ca 100644 --- a/lib/crewai/tests/agents/test_lite_agent.py +++ b/lib/crewai/tests/agents/test_lite_agent.py @@ -1004,3 +1004,53 @@ def test_prepare_kickoff_param_files_override_message_files(): assert "files" in inputs assert inputs["files"]["same.png"] is param_file # param takes precedence + + +def test_lite_agent_verbose_false_suppresses_printer_output(): + """Test that setting verbose=False suppresses all printer output.""" + from crewai.agents.parser import AgentFinish + from crewai.types.usage_metrics import UsageMetrics + + mock_llm = Mock(spec=LLM) + mock_llm.call.return_value = "Final Answer: Hello!" + mock_llm.stop = [] + mock_llm.supports_stop_words.return_value = False + mock_llm.get_token_usage_summary.return_value = UsageMetrics( + total_tokens=100, + prompt_tokens=50, + completion_tokens=50, + cached_prompt_tokens=0, + successful_requests=1, + ) + + with pytest.warns(DeprecationWarning): + agent = LiteAgent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + llm=mock_llm, + verbose=False, + ) + + result = agent.kickoff("Say hello") + + assert result is not None + assert isinstance(result, LiteAgentOutput) + # Verify the printer was never called + agent._printer.print = Mock() + # For a clean verification, patch printer before execution + with pytest.warns(DeprecationWarning): + agent2 = LiteAgent( + role="Test Agent", + goal="Test goal", + backstory="Test backstory", + llm=mock_llm, + verbose=False, + ) + + mock_printer = Mock() + agent2._printer = mock_printer + + agent2.kickoff("Say hello") + + mock_printer.print.assert_not_called()