Compare commits

...

3 Commits

Author SHA1 Message Date
Greyson LaLonde
9ad2a9271f Merge branch 'main' into gl/fix/google-response-schema 2026-01-28 12:20:24 -05:00
Greyson LaLonde
1e27cf3f0f fix: ensure verbosity flag is applied
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Notify Downstream / notify-downstream (push) Waiting to run
2026-01-28 11:52:47 -05:00
Greyson LaLonde
cba6d2eb9b fix: use response_json_schema 2026-01-28 06:28:49 -05:00
16 changed files with 428 additions and 257 deletions

View File

@@ -37,7 +37,8 @@ class CrewAgentExecutorMixin:
self.crew self.crew
and self.agent and self.agent
and self.task 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: try:
if ( if (
@@ -132,10 +133,11 @@ class CrewAgentExecutorMixin:
and self.crew._long_term_memory and self.crew._long_term_memory
and self.crew._entity_memory is None and self.crew._entity_memory is None
): ):
self._printer.print( if self.agent and self.agent.verbose:
content="Long term memory is enabled, but entity memory is not enabled. Please configure entity memory or set memory=True to automatically enable it.", self._printer.print(
color="bold_yellow", 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: def _ask_human_input(self, final_answer: str) -> str:
"""Prompt human input with mode-appropriate messaging. """Prompt human input with mode-appropriate messaging.

View File

@@ -206,13 +206,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
try: try:
formatted_answer = self._invoke_loop() formatted_answer = self._invoke_loop()
except AssertionError: except AssertionError:
self._printer.print( if self.agent.verbose:
content="Agent failed to reach a final answer. This is likely a bug - please report it.", self._printer.print(
color="red", content="Agent failed to reach a final answer. This is likely a bug - please report it.",
) color="red",
)
raise raise
except Exception as e: except Exception as e:
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
if self.ask_for_human_input: if self.ask_for_human_input:
@@ -327,6 +328,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
messages=self.messages, messages=self.messages,
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
verbose=self.agent.verbose,
) )
break break
@@ -341,6 +343,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
# breakpoint() # breakpoint()
if self.response_model is not None: if self.response_model is not None:
@@ -399,6 +402,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
iterations=self.iterations, iterations=self.iterations,
log_error_after=self.log_error_after, log_error_after=self.log_error_after,
printer=self._printer, printer=self._printer,
verbose=self.agent.verbose,
) )
except Exception as e: except Exception as e:
@@ -413,9 +417,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
i18n=self._i18n, i18n=self._i18n,
verbose=self.agent.verbose,
) )
continue continue
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise e raise e
finally: finally:
self.iterations += 1 self.iterations += 1
@@ -461,6 +466,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
messages=self.messages, messages=self.messages,
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
verbose=self.agent.verbose,
) )
self._show_logs(formatted_answer) self._show_logs(formatted_answer)
return formatted_answer return formatted_answer
@@ -482,6 +488,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
# Check if the response is a list of tool calls # Check if the response is a list of tool calls
@@ -535,9 +542,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
i18n=self._i18n, i18n=self._i18n,
verbose=self.agent.verbose,
) )
continue continue
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise e raise e
finally: finally:
self.iterations += 1 self.iterations += 1
@@ -559,6 +567,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
formatted_answer = AgentFinish( formatted_answer = AgentFinish(
@@ -755,10 +764,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
track_delegation_if_needed(func_name, args_dict, self.task) track_delegation_if_needed(func_name, args_dict, self.task)
# Find the structured tool for hook context # Find the structured tool for hook context
structured_tool = None structured_tool: CrewStructuredTool | None = None
for tool in self.tools or []: for structured in self.tools or []:
if sanitize_tool_name(tool.name) == func_name: if sanitize_tool_name(structured.name) == func_name:
structured_tool = tool structured_tool = structured
break break
# Execute before_tool_call hooks # Execute before_tool_call hooks
@@ -779,10 +788,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
hook_blocked = True hook_blocked = True
break break
except Exception as hook_error: except Exception as hook_error:
self._printer.print( if self.agent.verbose:
content=f"Error in before_tool_call hook: {hook_error}", self._printer.print(
color="red", 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 execution, set result and skip tool execution
if hook_blocked: if hook_blocked:
@@ -848,15 +858,16 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
after_hooks = get_after_tool_call_hooks() after_hooks = get_after_tool_call_hooks()
try: try:
for after_hook in after_hooks: for after_hook in after_hooks:
hook_result = after_hook(after_hook_context) after_hook_result = after_hook(after_hook_context)
if hook_result is not None: if after_hook_result is not None:
result = hook_result result = after_hook_result
after_hook_context.tool_result = result after_hook_context.tool_result = result
except Exception as hook_error: except Exception as hook_error:
self._printer.print( if self.agent.verbose:
content=f"Error in after_tool_call hook: {hook_error}", self._printer.print(
color="red", content=f"Error in after_tool_call hook: {hook_error}",
) color="red",
)
# Emit tool usage finished event # Emit tool usage finished event
crewai_event_bus.emit( crewai_event_bus.emit(
@@ -942,13 +953,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
try: try:
formatted_answer = await self._ainvoke_loop() formatted_answer = await self._ainvoke_loop()
except AssertionError: except AssertionError:
self._printer.print( if self.agent.verbose:
content="Agent failed to reach a final answer. This is likely a bug - please report it.", self._printer.print(
color="red", content="Agent failed to reach a final answer. This is likely a bug - please report it.",
) color="red",
)
raise raise
except Exception as e: except Exception as e:
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
if self.ask_for_human_input: if self.ask_for_human_input:
@@ -999,6 +1011,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
messages=self.messages, messages=self.messages,
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
verbose=self.agent.verbose,
) )
break break
@@ -1013,6 +1026,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
if self.response_model is not None: if self.response_model is not None:
@@ -1070,6 +1084,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
iterations=self.iterations, iterations=self.iterations,
log_error_after=self.log_error_after, log_error_after=self.log_error_after,
printer=self._printer, printer=self._printer,
verbose=self.agent.verbose,
) )
except Exception as e: except Exception as e:
@@ -1083,9 +1098,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
i18n=self._i18n, i18n=self._i18n,
verbose=self.agent.verbose,
) )
continue continue
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise e raise e
finally: finally:
self.iterations += 1 self.iterations += 1
@@ -1125,6 +1141,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
messages=self.messages, messages=self.messages,
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
verbose=self.agent.verbose,
) )
self._show_logs(formatted_answer) self._show_logs(formatted_answer)
return formatted_answer return formatted_answer
@@ -1146,6 +1163,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
# Check if the response is a list of tool calls # Check if the response is a list of tool calls
if ( if (
@@ -1198,9 +1216,10 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
i18n=self._i18n, i18n=self._i18n,
verbose=self.agent.verbose,
) )
continue continue
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise e raise e
finally: finally:
self.iterations += 1 self.iterations += 1
@@ -1222,6 +1241,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=self.response_model, response_model=self.response_model,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
formatted_answer = AgentFinish( formatted_answer = AgentFinish(
@@ -1339,10 +1359,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
) )
if train_iteration is None or not isinstance(train_iteration, int): if train_iteration is None or not isinstance(train_iteration, int):
self._printer.print( if self.agent.verbose:
content="Invalid or missing train iteration. Cannot save training data.", self._printer.print(
color="red", content="Invalid or missing train iteration. Cannot save training data.",
) color="red",
)
return return
training_handler = CrewTrainingHandler(TRAINING_DATA_FILE) training_handler = CrewTrainingHandler(TRAINING_DATA_FILE)
@@ -1362,13 +1383,14 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
if train_iteration in agent_training_data: if train_iteration in agent_training_data:
agent_training_data[train_iteration]["improved_output"] = result.output agent_training_data[train_iteration]["improved_output"] = result.output
else: else:
self._printer.print( if self.agent.verbose:
content=( self._printer.print(
f"No existing training data for agent {agent_id} and iteration " content=(
f"{train_iteration}. Cannot save improved output." f"No existing training data for agent {agent_id} and iteration "
), f"{train_iteration}. Cannot save improved output."
color="red", ),
) color="red",
)
return return
# Update the training data and save # Update the training data and save

View File

@@ -341,6 +341,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
messages=list(self.state.messages), messages=list(self.state.messages),
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
verbose=self.agent.verbose,
) )
self.state.current_answer = formatted_answer self.state.current_answer = formatted_answer
@@ -366,6 +367,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=None, response_model=None,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
# Parse the LLM response # Parse the LLM response
@@ -401,7 +403,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
return "context_error" return "context_error"
if e.__class__.__module__.startswith("litellm"): if e.__class__.__module__.startswith("litellm"):
raise e raise e
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
@listen("continue_reasoning_native") @listen("continue_reasoning_native")
@@ -436,6 +438,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
from_agent=self.agent, from_agent=self.agent,
response_model=None, response_model=None,
executor_context=self, executor_context=self,
verbose=self.agent.verbose,
) )
# Check if the response is a list of tool calls # Check if the response is a list of tool calls
@@ -474,7 +477,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
return "context_error" return "context_error"
if e.__class__.__module__.startswith("litellm"): if e.__class__.__module__.startswith("litellm"):
raise e raise e
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
@router(call_llm_and_parse) @router(call_llm_and_parse)
@@ -670,10 +673,10 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
track_delegation_if_needed(func_name, args_dict, self.task) track_delegation_if_needed(func_name, args_dict, self.task)
structured_tool = None structured_tool: CrewStructuredTool | None = None
for tool in self.tools or []: for structured in self.tools or []:
if sanitize_tool_name(tool.name) == func_name: if sanitize_tool_name(structured.name) == func_name:
structured_tool = tool structured_tool = structured
break break
hook_blocked = False hook_blocked = False
@@ -693,10 +696,11 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
hook_blocked = True hook_blocked = True
break break
except Exception as hook_error: except Exception as hook_error:
self._printer.print( if self.agent.verbose:
content=f"Error in before_tool_call hook: {hook_error}", self._printer.print(
color="red", content=f"Error in before_tool_call hook: {hook_error}",
) color="red",
)
if hook_blocked: if hook_blocked:
result = f"Tool execution blocked by hook. Tool: {func_name}" 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() after_hooks = get_after_tool_call_hooks()
try: try:
for after_hook in after_hooks: for after_hook in after_hooks:
hook_result = after_hook(after_hook_context) after_hook_result = after_hook(after_hook_context)
if hook_result is not None: if after_hook_result is not None:
result = hook_result result = after_hook_result
after_hook_context.tool_result = result after_hook_context.tool_result = result
except Exception as hook_error: except Exception as hook_error:
self._printer.print( if self.agent.verbose:
content=f"Error in after_tool_call hook: {hook_error}", self._printer.print(
color="red", content=f"Error in after_tool_call hook: {hook_error}",
) color="red",
)
# Emit tool usage finished event # Emit tool usage finished event
crewai_event_bus.emit( crewai_event_bus.emit(
@@ -911,6 +916,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
iterations=self.state.iterations, iterations=self.state.iterations,
log_error_after=self.log_error_after, log_error_after=self.log_error_after,
printer=self._printer, printer=self._printer,
verbose=self.agent.verbose,
) )
if formatted_answer: if formatted_answer:
@@ -930,6 +936,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
llm=self.llm, llm=self.llm,
callbacks=self.callbacks, callbacks=self.callbacks,
i18n=self._i18n, i18n=self._i18n,
verbose=self.agent.verbose,
) )
self.state.iterations += 1 self.state.iterations += 1
@@ -1021,7 +1028,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
self._console.print(fail_text) self._console.print(fail_text)
raise raise
except Exception as e: except Exception as e:
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
finally: finally:
self._is_executing = False self._is_executing = False
@@ -1106,7 +1113,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
self._console.print(fail_text) self._console.print(fail_text)
raise raise
except Exception as e: except Exception as e:
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
raise raise
finally: finally:
self._is_executing = False self._is_executing = False

View File

@@ -118,17 +118,20 @@ class PersistenceDecorator:
) )
except Exception as e: except Exception as e:
error_msg = LOG_MESSAGES["save_error"].format(method_name, str(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) logger.error(error_msg)
raise RuntimeError(f"State persistence failed: {e!s}") from e raise RuntimeError(f"State persistence failed: {e!s}") from e
except AttributeError as e: except AttributeError as e:
error_msg = LOG_MESSAGES["state_missing"] 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) logger.error(error_msg)
raise ValueError(error_msg) from e raise ValueError(error_msg) from e
except (TypeError, ValueError) as e: except (TypeError, ValueError) as e:
error_msg = LOG_MESSAGES["id_missing"] 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) logger.error(error_msg)
raise ValueError(error_msg) from e raise ValueError(error_msg) from e

View File

@@ -151,7 +151,9 @@ def _unwrap_function(function: Any) -> Any:
return function 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. """Extract possible string return values from a function using AST parsing.
This function analyzes the source code of a router method to identify 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 # Can't get source code
return None return None
except Exception as e: except Exception as e:
_printer.print( if verbose:
f"Error retrieving source code for function {function.__name__}: {e}", _printer.print(
color="red", f"Error retrieving source code for function {function.__name__}: {e}",
) color="red",
)
return None return None
try: try:
@@ -190,25 +193,28 @@ def get_possible_return_constants(function: Any) -> list[str] | None:
# Parse the source code into an AST # Parse the source code into an AST
code_ast = ast.parse(source) code_ast = ast.parse(source)
except IndentationError as e: except IndentationError as e:
_printer.print( if verbose:
f"IndentationError while parsing source code of {function.__name__}: {e}", _printer.print(
color="red", f"IndentationError while parsing source code of {function.__name__}: {e}",
) color="red",
_printer.print(f"Source code:\n{source}", color="yellow") )
_printer.print(f"Source code:\n{source}", color="yellow")
return None return None
except SyntaxError as e: except SyntaxError as e:
_printer.print( if verbose:
f"SyntaxError while parsing source code of {function.__name__}: {e}", _printer.print(
color="red", f"SyntaxError while parsing source code of {function.__name__}: {e}",
) color="red",
_printer.print(f"Source code:\n{source}", color="yellow") )
_printer.print(f"Source code:\n{source}", color="yellow")
return None return None
except Exception as e: except Exception as e:
_printer.print( if verbose:
f"Unexpected error while parsing source code of {function.__name__}: {e}", _printer.print(
color="red", f"Unexpected error while parsing source code of {function.__name__}: {e}",
) color="red",
_printer.print(f"Source code:\n{source}", color="yellow") )
_printer.print(f"Source code:\n{source}", color="yellow")
return None return None
return_values: set[str] = set() return_values: set[str] = set()
@@ -388,15 +394,17 @@ def get_possible_return_constants(function: Any) -> list[str] | None:
StateAttributeVisitor().visit(class_ast) StateAttributeVisitor().visit(class_ast)
except Exception as e: except Exception as e:
_printer.print( if verbose:
f"Could not analyze class context for {function.__name__}: {e}", _printer.print(
color="yellow", f"Could not analyze class context for {function.__name__}: {e}",
) color="yellow",
)
except Exception as e: except Exception as e:
_printer.print( if verbose:
f"Could not introspect class for {function.__name__}: {e}", _printer.print(
color="yellow", f"Could not introspect class for {function.__name__}: {e}",
) color="yellow",
)
VariableAssignmentVisitor().visit(code_ast) VariableAssignmentVisitor().visit(code_ast)
ReturnVisitor().visit(code_ast) ReturnVisitor().visit(code_ast)

View File

@@ -72,13 +72,13 @@ from crewai.utilities.agent_utils import (
from crewai.utilities.converter import ( from crewai.utilities.converter import (
Converter, Converter,
ConverterError, ConverterError,
generate_model_description,
) )
from crewai.utilities.guardrail import process_guardrail from crewai.utilities.guardrail import process_guardrail
from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType from crewai.utilities.guardrail_types import GuardrailCallable, GuardrailType
from crewai.utilities.i18n import I18N, get_i18n from crewai.utilities.i18n import I18N, get_i18n
from crewai.utilities.llm_utils import create_llm from crewai.utilities.llm_utils import create_llm
from crewai.utilities.printer import Printer 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.token_counter_callback import TokenCalcHandler
from crewai.utilities.tool_utils import execute_tool_and_check_finality from crewai.utilities.tool_utils import execute_tool_and_check_finality
from crewai.utilities.types import LLMMessage from crewai.utilities.types import LLMMessage
@@ -344,11 +344,12 @@ class LiteAgent(FlowTrackable, BaseModel):
) )
except Exception as e: except Exception as e:
self._printer.print( if self.verbose:
content="Agent failed to reach a final answer. This is likely a bug - please report it.", self._printer.print(
color="red", content="Agent failed to reach a final answer. This is likely a bug - please report it.",
) color="red",
handle_unknown_error(self._printer, e) )
handle_unknown_error(self._printer, e, verbose=self.verbose)
# Emit error event # Emit error event
crewai_event_bus.emit( crewai_event_bus.emit(
self, self,
@@ -396,10 +397,11 @@ class LiteAgent(FlowTrackable, BaseModel):
if isinstance(result, BaseModel): if isinstance(result, BaseModel):
formatted_result = result formatted_result = result
except ConverterError as e: except ConverterError as e:
self._printer.print( if self.verbose:
content=f"Failed to parse output into response format after retries: {e.message}", self._printer.print(
color="yellow", content=f"Failed to parse output into response format after retries: {e.message}",
) color="yellow",
)
# Calculate token usage metrics # Calculate token usage metrics
if isinstance(self.llm, BaseLLM): if isinstance(self.llm, BaseLLM):
@@ -605,6 +607,7 @@ class LiteAgent(FlowTrackable, BaseModel):
messages=self._messages, messages=self._messages,
llm=cast(LLM, self.llm), llm=cast(LLM, self.llm),
callbacks=self._callbacks, callbacks=self._callbacks,
verbose=self.verbose,
) )
enforce_rpm_limit(self.request_within_rpm_limit) enforce_rpm_limit(self.request_within_rpm_limit)
@@ -617,6 +620,7 @@ class LiteAgent(FlowTrackable, BaseModel):
printer=self._printer, printer=self._printer,
from_agent=self, from_agent=self,
executor_context=self, executor_context=self,
verbose=self.verbose,
) )
except Exception as e: except Exception as e:
@@ -646,16 +650,18 @@ class LiteAgent(FlowTrackable, BaseModel):
self._append_message(formatted_answer.text, role="assistant") self._append_message(formatted_answer.text, role="assistant")
except OutputParserError as e: # noqa: PERF203 except OutputParserError as e: # noqa: PERF203
self._printer.print( if self.verbose:
content="Failed to parse LLM output. Retrying...", self._printer.print(
color="yellow", content="Failed to parse LLM output. Retrying...",
) color="yellow",
)
formatted_answer = handle_output_parser_exception( formatted_answer = handle_output_parser_exception(
e=e, e=e,
messages=self._messages, messages=self._messages,
iterations=self._iterations, iterations=self._iterations,
log_error_after=3, log_error_after=3,
printer=self._printer, printer=self._printer,
verbose=self.verbose,
) )
except Exception as e: except Exception as e:
@@ -670,9 +676,10 @@ class LiteAgent(FlowTrackable, BaseModel):
llm=cast(LLM, self.llm), llm=cast(LLM, self.llm),
callbacks=self._callbacks, callbacks=self._callbacks,
i18n=self.i18n, i18n=self.i18n,
verbose=self.verbose,
) )
continue continue
handle_unknown_error(self._printer, e) handle_unknown_error(self._printer, e, verbose=self.verbose)
raise e raise e
finally: finally:

View File

@@ -404,7 +404,7 @@ class BaseLLM(ABC):
from_agent: Agent | None = None, from_agent: Agent | None = None,
tool_call: dict[str, Any] | None = None, tool_call: dict[str, Any] | None = None,
call_type: LLMCallType | None = None, call_type: LLMCallType | None = None,
response_id: str | None = None response_id: str | None = None,
) -> None: ) -> None:
"""Emit stream chunk event. """Emit stream chunk event.
@@ -427,7 +427,7 @@ class BaseLLM(ABC):
from_task=from_task, from_task=from_task,
from_agent=from_agent, from_agent=from_agent,
call_type=call_type, call_type=call_type,
response_id=response_id response_id=response_id,
), ),
) )
@@ -497,7 +497,7 @@ class BaseLLM(ABC):
from_agent=from_agent, from_agent=from_agent,
) )
return result return str(result) if not isinstance(result, str) else result
except Exception as e: except Exception as e:
error_msg = f"Error executing function '{function_name}': {e!s}" error_msg = f"Error executing function '{function_name}': {e!s}"
@@ -737,22 +737,25 @@ class BaseLLM(ABC):
task=None, task=None,
crew=None, crew=None,
) )
verbose = getattr(from_agent, "verbose", True) if from_agent else True
printer = Printer() printer = Printer()
try: try:
for hook in before_hooks: for hook in before_hooks:
result = hook(hook_context) result = hook(hook_context)
if result is False: if result is False:
printer.print( if verbose:
content="LLM call blocked by before_llm_call hook", printer.print(
color="yellow", content="LLM call blocked by before_llm_call hook",
) color="yellow",
)
return False return False
except Exception as e: except Exception as e:
printer.print( if verbose:
content=f"Error in before_llm_call hook: {e}", printer.print(
color="yellow", content=f"Error in before_llm_call hook: {e}",
) color="yellow",
)
return True return True
@@ -805,6 +808,7 @@ class BaseLLM(ABC):
crew=None, crew=None,
response=response, response=response,
) )
verbose = getattr(from_agent, "verbose", True) if from_agent else True
printer = Printer() printer = Printer()
modified_response = response modified_response = response
@@ -815,9 +819,10 @@ class BaseLLM(ABC):
modified_response = result modified_response = result
hook_context.response = modified_response hook_context.response = modified_response
except Exception as e: except Exception as e:
printer.print( if verbose:
content=f"Error in after_llm_call hook: {e}", printer.print(
color="yellow", content=f"Error in after_llm_call hook: {e}",
) color="yellow",
)
return modified_response return modified_response

View File

@@ -466,9 +466,9 @@ class GeminiCompletion(BaseLLM):
if response_model: if response_model:
config_params["response_mime_type"] = "application/json" config_params["response_mime_type"] = "application/json"
schema_output = generate_model_description(response_model) schema_output = generate_model_description(response_model)
config_params["response_schema"] = schema_output.get("json_schema", {}).get( config_params["response_json_schema"] = schema_output.get(
"schema", {} "json_schema", {}
) ).get("schema", {})
# Handle tools for supported models # Handle tools for supported models
if tools and self.supports_tools: if tools and self.supports_tools:

View File

@@ -12,15 +12,17 @@ from crewai.utilities.paths import db_storage_path
class LTMSQLiteStorage: class LTMSQLiteStorage:
"""SQLite storage class for long-term memory data.""" """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. """Initialize the SQLite storage.
Args: Args:
db_path: Optional path to the database file. db_path: Optional path to the database file.
verbose: Whether to print error messages.
""" """
if db_path is None: if db_path is None:
db_path = str(Path(db_storage_path()) / "long_term_memory_storage.db") db_path = str(Path(db_storage_path()) / "long_term_memory_storage.db")
self.db_path = db_path self.db_path = db_path
self._verbose = verbose
self._printer: Printer = Printer() self._printer: Printer = Printer()
Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
self._initialize_db() self._initialize_db()
@@ -44,10 +46,11 @@ class LTMSQLiteStorage:
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred during database initialization: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred during database initialization: {e}",
) color="red",
)
def save( def save(
self, self,
@@ -69,10 +72,11 @@ class LTMSQLiteStorage:
) )
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", self._printer.print(
color="red", 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: def load(self, task_description: str, latest_n: int) -> list[dict[str, Any]] | None:
"""Queries the LTM table by task description with error handling.""" """Queries the LTM table by task description with error handling."""
@@ -101,10 +105,11 @@ class LTMSQLiteStorage:
] ]
except sqlite3.Error as e: except sqlite3.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred while querying LTM: {e}",
) color="red",
)
return None return None
def reset(self) -> None: def reset(self) -> None:
@@ -116,10 +121,11 @@ class LTMSQLiteStorage:
conn.commit() conn.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}",
) color="red",
)
async def asave( async def asave(
self, self,
@@ -147,10 +153,11 @@ class LTMSQLiteStorage:
) )
await conn.commit() await conn.commit()
except aiosqlite.Error as e: except aiosqlite.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred while saving to LTM: {e}",
) color="red",
)
async def aload( async def aload(
self, task_description: str, latest_n: int self, task_description: str, latest_n: int
@@ -187,10 +194,11 @@ class LTMSQLiteStorage:
for row in rows for row in rows
] ]
except aiosqlite.Error as e: except aiosqlite.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while querying LTM: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred while querying LTM: {e}",
) color="red",
)
return None return None
async def areset(self) -> None: async def areset(self) -> None:
@@ -200,7 +208,8 @@ class LTMSQLiteStorage:
await conn.execute("DELETE FROM long_term_memories") await conn.execute("DELETE FROM long_term_memories")
await conn.commit() await conn.commit()
except aiosqlite.Error as e: except aiosqlite.Error as e:
self._printer.print( if self._verbose:
content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}", self._printer.print(
color="red", content=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {e}",
) color="red",
)

View File

@@ -1,6 +1,6 @@
"""IBM WatsonX embedding function implementation.""" """IBM WatsonX embedding function implementation."""
from typing import cast from typing import Any, cast
from chromadb.api.types import Documents, EmbeddingFunction, Embeddings from chromadb.api.types import Documents, EmbeddingFunction, Embeddings
from typing_extensions import Unpack from typing_extensions import Unpack
@@ -15,14 +15,18 @@ _printer = Printer()
class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]): class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]):
"""Embedding function for IBM WatsonX models.""" """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. """Initialize WatsonX embedding function.
Args: Args:
verbose: Whether to print error messages.
**kwargs: Configuration parameters for WatsonX Embeddings and Credentials. **kwargs: Configuration parameters for WatsonX Embeddings and Credentials.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self._config = kwargs self._config = kwargs
self._verbose = verbose
@staticmethod @staticmethod
def name() -> str: def name() -> str:
@@ -56,7 +60,7 @@ class WatsonXEmbeddingFunction(EmbeddingFunction[Documents]):
if isinstance(input, str): if isinstance(input, str):
input = [input] input = [input]
embeddings_config: dict = { embeddings_config: dict[str, Any] = {
"model_id": self._config["model_id"], "model_id": self._config["model_id"],
} }
if "params" in self._config and self._config["params"] is not None: 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: if "credentials" in self._config and self._config["credentials"] is not None:
embeddings_config["credentials"] = self._config["credentials"] embeddings_config["credentials"] = self._config["credentials"]
else: else:
cred_config: dict = {} cred_config: dict[str, Any] = {}
if "url" in self._config and self._config["url"] is not None: if "url" in self._config and self._config["url"] is not None:
cred_config["url"] = self._config["url"] cred_config["url"] = self._config["url"]
if "api_key" in self._config and self._config["api_key"] is not None: 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) embeddings = embedding.embed_documents(input)
return cast(Embeddings, embeddings) return cast(Embeddings, embeddings)
except Exception as e: 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 raise

View File

@@ -767,10 +767,11 @@ class Task(BaseModel):
if files: if files:
supported_types: list[str] = [] supported_types: list[str] = []
if self.agent.llm and self.agent.llm.supports_multimodal(): if self.agent.llm and self.agent.llm.supports_multimodal():
provider = getattr(self.agent.llm, "provider", None) or getattr( provider: str = str(
self.agent.llm, "model", "openai" 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) supported_types = get_supported_content_types(provider, api)
def is_auto_injected(content_type: str) -> bool: def is_auto_injected(content_type: str) -> bool:
@@ -887,10 +888,11 @@ Follow these guidelines:
try: try:
crew_chat_messages = json.loads(crew_chat_messages_json) crew_chat_messages = json.loads(crew_chat_messages_json)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
_printer.print( if self.agent and self.agent.verbose:
f"An error occurred while parsing crew chat messages: {e}", _printer.print(
color="red", f"An error occurred while parsing crew chat messages: {e}",
) color="red",
)
raise raise
conversation_history = "\n".join( conversation_history = "\n".join(
@@ -1132,11 +1134,12 @@ Follow these guidelines:
guardrail_result_error=guardrail_result.error, guardrail_result_error=guardrail_result.error,
task_output=task_output.raw, task_output=task_output.raw,
) )
printer = Printer() if agent and agent.verbose:
printer.print( printer = Printer()
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", printer.print(
color="yellow", 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 # Regenerate output from agent
result = agent.execute_task( result = agent.execute_task(
@@ -1229,11 +1232,12 @@ Follow these guidelines:
guardrail_result_error=guardrail_result.error, guardrail_result_error=guardrail_result.error,
task_output=task_output.raw, task_output=task_output.raw,
) )
printer = Printer() if agent and agent.verbose:
printer.print( printer = Printer()
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", printer.print(
color="yellow", 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( result = await agent.aexecute_task(
task=self, task=self,

View File

@@ -384,6 +384,8 @@ class ToolUsage:
if ( if (
hasattr(available_tool, "max_usage_count") hasattr(available_tool, "max_usage_count")
and available_tool.max_usage_count is not None and available_tool.max_usage_count is not None
and self.agent
and self.agent.verbose
): ):
self._printer.print( self._printer.print(
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", 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 ( if (
hasattr(available_tool, "max_usage_count") hasattr(available_tool, "max_usage_count")
and available_tool.max_usage_count is not None and available_tool.max_usage_count is not None
and self.agent
and self.agent.verbose
): ):
self._printer.print( self._printer.print(
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", 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 ( if (
hasattr(available_tool, "max_usage_count") hasattr(available_tool, "max_usage_count")
and available_tool.max_usage_count is not None and available_tool.max_usage_count is not None
and self.agent
and self.agent.verbose
): ):
self._printer.print( self._printer.print(
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", 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 ( if (
hasattr(available_tool, "max_usage_count") hasattr(available_tool, "max_usage_count")
and available_tool.max_usage_count is not None and available_tool.max_usage_count is not None
and self.agent
and self.agent.verbose
): ):
self._printer.print( self._printer.print(
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}", 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 # Attempt 4: Repair JSON
try: try:
repaired_input = str(repair_json(tool_input, skip_json_loads=True)) repaired_input = str(repair_json(tool_input, skip_json_loads=True))
self._printer.print( if self.agent and self.agent.verbose:
content=f"Repaired JSON: {repaired_input}", color="blue" self._printer.print(
) content=f"Repaired JSON: {repaired_input}", color="blue"
)
arguments = json.loads(repaired_input) arguments = json.loads(repaired_input)
if isinstance(arguments, dict): if isinstance(arguments, dict):
return arguments return arguments
except Exception as e: except Exception as e:
error = f"Failed to repair JSON: {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 = ( error_message = (
"Tool input must be a valid dictionary in JSON or Python literal format" "Tool input must be a valid dictionary in JSON or Python literal format"

View File

@@ -210,6 +210,7 @@ def handle_max_iterations_exceeded(
messages: list[LLMMessage], messages: list[LLMMessage],
llm: LLM | BaseLLM, llm: LLM | BaseLLM,
callbacks: list[TokenCalcHandler], callbacks: list[TokenCalcHandler],
verbose: bool = True,
) -> AgentFinish: ) -> AgentFinish:
"""Handles the case when the maximum number of iterations is exceeded. Performs one more LLM call to get the final answer. """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. messages: List of messages to send to the LLM.
llm: The LLM instance to call. llm: The LLM instance to call.
callbacks: List of callbacks for the LLM call. callbacks: List of callbacks for the LLM call.
verbose: Whether to print output.
Returns: Returns:
AgentFinish with the final answer after exceeding max iterations. AgentFinish with the final answer after exceeding max iterations.
""" """
printer.print( if verbose:
content="Maximum iterations reached. Requesting final answer.", printer.print(
color="yellow", content="Maximum iterations reached. Requesting final answer.",
) color="yellow",
)
if formatted_answer and hasattr(formatted_answer, "text"): if formatted_answer and hasattr(formatted_answer, "text"):
assistant_message = ( assistant_message = (
@@ -245,10 +248,11 @@ def handle_max_iterations_exceeded(
) )
if answer is None or answer == "": if answer is None or answer == "":
printer.print( if verbose:
content="Received None or empty response from LLM call.", printer.print(
color="red", content="Received None or empty response from LLM call.",
) color="red",
)
raise ValueError("Invalid response from LLM call - None or empty.") raise ValueError("Invalid response from LLM call - None or empty.")
formatted = format_answer(answer=answer) formatted = format_answer(answer=answer)
@@ -322,6 +326,7 @@ def get_llm_response(
from_agent: Agent | LiteAgent | None = None, from_agent: Agent | LiteAgent | None = None,
response_model: type[BaseModel] | None = None, response_model: type[BaseModel] | None = None,
executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None = None, executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None = None,
verbose: bool = True,
) -> str | Any: ) -> str | Any:
"""Call the LLM and return the response, handling any invalid responses. """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 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") raise ValueError("LLM call blocked by before_llm_call hook")
messages = executor_context.messages messages = executor_context.messages
@@ -364,13 +369,16 @@ def get_llm_response(
except Exception as e: except Exception as e:
raise e raise e
if not answer: if not answer:
printer.print( if verbose:
content="Received None or empty response from LLM call.", printer.print(
color="red", content="Received None or empty response from LLM call.",
) color="red",
)
raise ValueError("Invalid response from LLM call - None or empty.") 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( async def aget_llm_response(
@@ -384,6 +392,7 @@ async def aget_llm_response(
from_agent: Agent | LiteAgent | None = None, from_agent: Agent | LiteAgent | None = None,
response_model: type[BaseModel] | None = None, response_model: type[BaseModel] | None = None,
executor_context: CrewAgentExecutor | AgentExecutor | None = None, executor_context: CrewAgentExecutor | AgentExecutor | None = None,
verbose: bool = True,
) -> str | Any: ) -> str | Any:
"""Call the LLM asynchronously and return the response. """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. ValueError: If the response is None or empty.
""" """
if executor_context is not None: 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") raise ValueError("LLM call blocked by before_llm_call hook")
messages = executor_context.messages messages = executor_context.messages
@@ -425,13 +434,16 @@ async def aget_llm_response(
except Exception as e: except Exception as e:
raise e raise e
if not answer: if not answer:
printer.print( if verbose:
content="Received None or empty response from LLM call.", printer.print(
color="red", content="Received None or empty response from LLM call.",
) color="red",
)
raise ValueError("Invalid response from LLM call - None or empty.") 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( def process_llm_response(
@@ -498,13 +510,19 @@ def handle_agent_action_core(
return formatted_answer 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. """Handle unknown errors by informing the user.
Args: Args:
printer: Printer instance for output printer: Printer instance for output
exception: The exception that occurred exception: The exception that occurred
verbose: Whether to print output.
""" """
if not verbose:
return
error_message = str(exception) error_message = str(exception)
if "litellm" in error_message: if "litellm" in error_message:
@@ -526,6 +544,7 @@ def handle_output_parser_exception(
iterations: int, iterations: int,
log_error_after: int = 3, log_error_after: int = 3,
printer: Printer | None = None, printer: Printer | None = None,
verbose: bool = True,
) -> AgentAction: ) -> AgentAction:
"""Handle OutputParserError by updating messages and formatted_answer. """Handle OutputParserError by updating messages and formatted_answer.
@@ -548,7 +567,7 @@ def handle_output_parser_exception(
thought="", thought="",
) )
if iterations > log_error_after and printer: if verbose and iterations > log_error_after and printer:
printer.print( printer.print(
content=f"Error parsing LLM output, agent will retry: {e.error}", content=f"Error parsing LLM output, agent will retry: {e.error}",
color="red", color="red",
@@ -578,6 +597,7 @@ def handle_context_length(
llm: LLM | BaseLLM, llm: LLM | BaseLLM,
callbacks: list[TokenCalcHandler], callbacks: list[TokenCalcHandler],
i18n: I18N, i18n: I18N,
verbose: bool = True,
) -> None: ) -> None:
"""Handle context length exceeded by either summarizing or raising an error. """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 SystemExit: If context length is exceeded and user opts not to summarize
""" """
if respect_context_window: if respect_context_window:
printer.print( if verbose:
content="Context length exceeded. Summarizing content to fit the model context window. Might take a while...", printer.print(
color="yellow", 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: else:
printer.print( if verbose:
content="Context length exceeded. Consider using smaller text or RAG tools from crewai_tools.", printer.print(
color="red", content="Context length exceeded. Consider using smaller text or RAG tools from crewai_tools.",
) color="red",
)
raise SystemExit( raise SystemExit(
"Context length exceeded and user opted not to summarize. Consider using smaller text or RAG tools from crewai_tools." "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, llm: LLM | BaseLLM,
callbacks: list[TokenCalcHandler], callbacks: list[TokenCalcHandler],
i18n: I18N, i18n: I18N,
verbose: bool = True,
) -> None: ) -> None:
"""Summarize messages to fit within context window. """Summarize messages to fit within context window.
@@ -644,10 +669,11 @@ def summarize_messages(
total_groups = len(messages_groups) total_groups = len(messages_groups)
for idx, group in enumerate(messages_groups, 1): for idx, group in enumerate(messages_groups, 1):
Printer().print( if verbose:
content=f"Summarizing {idx}/{total_groups}...", Printer().print(
color="yellow", content=f"Summarizing {idx}/{total_groups}...",
) color="yellow",
)
summarization_messages = [ summarization_messages = [
format_message_for_llm( format_message_for_llm(
@@ -905,12 +931,14 @@ def extract_tool_call_info(
def _setup_before_llm_call_hooks( def _setup_before_llm_call_hooks(
executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None, executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None,
printer: Printer, printer: Printer,
verbose: bool = True,
) -> bool: ) -> bool:
"""Setup and invoke before_llm_call hooks for the executor context. """Setup and invoke before_llm_call hooks for the executor context.
Args: Args:
executor_context: The executor context to setup the hooks for. executor_context: The executor context to setup the hooks for.
printer: Printer instance for error logging. printer: Printer instance for error logging.
verbose: Whether to print output.
Returns: Returns:
True if LLM execution should proceed, False if blocked by a hook. 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: for hook in executor_context.before_llm_call_hooks:
result = hook(hook_context) result = hook(hook_context)
if result is False: if result is False:
printer.print( if verbose:
content="LLM call blocked by before_llm_call hook", printer.print(
color="yellow", content="LLM call blocked by before_llm_call hook",
) color="yellow",
)
return False return False
except Exception as e: except Exception as e:
printer.print( if verbose:
content=f"Error in before_llm_call hook: {e}", printer.print(
color="yellow", content=f"Error in before_llm_call hook: {e}",
) color="yellow",
)
if not isinstance(executor_context.messages, list): if not isinstance(executor_context.messages, list):
printer.print( if verbose:
content=( printer.print(
"Warning: before_llm_call hook replaced messages with non-list. " content=(
"Restoring original messages list. Hooks should modify messages in-place, " "Warning: before_llm_call hook replaced messages with non-list. "
"not replace the list (e.g., use context.messages.append() not context.messages = [])." "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", ),
) color="yellow",
)
if isinstance(original_messages, list): if isinstance(original_messages, list):
executor_context.messages = original_messages executor_context.messages = original_messages
else: else:
@@ -957,6 +988,7 @@ def _setup_after_llm_call_hooks(
executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None, executor_context: CrewAgentExecutor | AgentExecutor | LiteAgent | None,
answer: str, answer: str,
printer: Printer, printer: Printer,
verbose: bool = True,
) -> str: ) -> str:
"""Setup and invoke after_llm_call hooks for the executor context. """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. executor_context: The executor context to setup the hooks for.
answer: The LLM response string. answer: The LLM response string.
printer: Printer instance for error logging. printer: Printer instance for error logging.
verbose: Whether to print output.
Returns: Returns:
The potentially modified response string. The potentially modified response string.
@@ -981,20 +1014,22 @@ def _setup_after_llm_call_hooks(
answer = modified_response answer = modified_response
except Exception as e: except Exception as e:
printer.print( if verbose:
content=f"Error in after_llm_call hook: {e}", printer.print(
color="yellow", content=f"Error in after_llm_call hook: {e}",
) color="yellow",
)
if not isinstance(executor_context.messages, list): if not isinstance(executor_context.messages, list):
printer.print( if verbose:
content=( printer.print(
"Warning: after_llm_call hook replaced messages with non-list. " content=(
"Restoring original messages list. Hooks should modify messages in-place, " "Warning: after_llm_call hook replaced messages with non-list. "
"not replace the list (e.g., use context.messages.append() not context.messages = [])." "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", ),
) color="yellow",
)
if isinstance(original_messages, list): if isinstance(original_messages, list):
executor_context.messages = original_messages executor_context.messages = original_messages
else: else:

View File

@@ -205,10 +205,11 @@ def convert_to_model(
) )
except Exception as e: except Exception as e:
Printer().print( if agent and getattr(agent, "verbose", True):
content=f"Unexpected error during model conversion: {type(e).__name__}: {e}. Returning original result.", Printer().print(
color="red", content=f"Unexpected error during model conversion: {type(e).__name__}: {e}. Returning original result.",
) color="red",
)
return result return result
@@ -262,10 +263,11 @@ def handle_partial_json(
except ValidationError: except ValidationError:
raise raise
except Exception as e: except Exception as e:
Printer().print( if agent and getattr(agent, "verbose", True):
content=f"Unexpected error during partial JSON handling: {type(e).__name__}: {e}. Attempting alternative conversion method.", Printer().print(
color="red", content=f"Unexpected error during partial JSON handling: {type(e).__name__}: {e}. Attempting alternative conversion method.",
) color="red",
)
return convert_with_instructions( return convert_with_instructions(
result=result, result=result,
@@ -323,10 +325,11 @@ def convert_with_instructions(
) )
if isinstance(exported_result, ConverterError): if isinstance(exported_result, ConverterError):
Printer().print( if agent and getattr(agent, "verbose", True):
content=f"Failed to convert result to model: {exported_result}", Printer().print(
color="red", content=f"Failed to convert result to model: {exported_result}",
) color="red",
)
return result return result
return exported_result return exported_result

View File

@@ -1004,3 +1004,53 @@ def test_prepare_kickoff_param_files_override_message_files():
assert "files" in inputs assert "files" in inputs
assert inputs["files"]["same.png"] is param_file # param takes precedence 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()

View File

@@ -2585,6 +2585,7 @@ def test_warning_long_term_memory_without_entity_memory():
goal="You research about math.", goal="You research about math.",
backstory="You're an expert in research and you love to learn new things.", backstory="You're an expert in research and you love to learn new things.",
allow_delegation=False, allow_delegation=False,
verbose=True,
) )
task1 = Task( task1 = Task(