-
Notifications
You must be signed in to change notification settings - Fork 7.1k
fix: result_as_answer should not apply to tool errors #5170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -899,6 +899,7 @@ def _execute_single_native_tool_call( | |
| func_args, func_name, call_id, original_tool | ||
| ) | ||
| if parse_error is not None: | ||
| parse_error["is_error"] = True | ||
| return parse_error | ||
|
|
||
| if original_tool is None: | ||
|
|
@@ -920,6 +921,7 @@ def _execute_single_native_tool_call( | |
| max_usage_reached = True | ||
|
|
||
| from_cache = False | ||
| is_error = True # "Tool not found" is the default error state | ||
| result: str = "Tool not found" | ||
| input_str = json.dumps(args_dict) if args_dict else "" | ||
| if self.tools_handler and self.tools_handler.cache: | ||
|
|
@@ -933,6 +935,7 @@ def _execute_single_native_tool_call( | |
| else cached_result | ||
| ) | ||
| from_cache = True | ||
| is_error = False | ||
|
|
||
| agent_key = getattr(self.agent, "key", "unknown") if self.agent else "unknown" | ||
| started_at = datetime.now() | ||
|
|
@@ -987,8 +990,10 @@ def _execute_single_native_tool_call( | |
|
|
||
| if hook_blocked: | ||
| result = f"Tool execution blocked by hook. Tool: {func_name}" | ||
| is_error = True | ||
| elif max_usage_reached and original_tool: | ||
| result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore." | ||
| is_error = True | ||
| elif not from_cache and func_name in available_functions: | ||
| try: | ||
| raw_result = available_functions[func_name](**(args_dict or {})) | ||
|
|
@@ -1011,6 +1016,7 @@ def _execute_single_native_tool_call( | |
| result = ( | ||
| str(raw_result) if not isinstance(raw_result, str) else raw_result | ||
| ) | ||
| is_error = False | ||
| except Exception as e: | ||
| result = f"Error executing tool: {e}" | ||
| if self.task: | ||
|
|
@@ -1072,6 +1078,7 @@ def _execute_single_native_tool_call( | |
| "result": result, | ||
| "from_cache": from_cache, | ||
| "original_tool": original_tool, | ||
| "is_error": is_error, | ||
| } | ||
|
|
||
| def _append_tool_result_and_check_finality( | ||
|
|
@@ -1082,6 +1089,7 @@ def _append_tool_result_and_check_finality( | |
| result = cast(str, execution_result["result"]) | ||
| from_cache = cast(bool, execution_result["from_cache"]) | ||
| original_tool = execution_result["original_tool"] | ||
| is_error = cast(bool, execution_result.get("is_error", False)) | ||
|
|
||
| tool_message: LLMMessage = { | ||
| "role": "tool", | ||
|
|
@@ -1098,8 +1106,15 @@ def _append_tool_result_and_check_finality( | |
| color="green", | ||
| ) | ||
|
|
||
| # result_as_answer only applies to successful tool outputs. | ||
| # If the tool errored, let the agent reflect on the error instead. | ||
| # Two checks: (1) is_error flag from exception/parse failures, | ||
| # (2) string-based detection for tools that return error strings | ||
| # without raising exceptions (e.g., "Error performing search: ..."). | ||
| if ( | ||
| original_tool | ||
| not is_error | ||
| and not self._looks_like_tool_error(result) | ||
| and original_tool | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error prefix misclassifies valid tool outputsMedium Severity The new Additional Locations (1) |
||
| and hasattr(original_tool, "result_as_answer") | ||
| and original_tool.result_as_answer | ||
| ): | ||
|
|
@@ -1110,6 +1125,20 @@ def _append_tool_result_and_check_finality( | |
| ) | ||
| return None | ||
|
|
||
| @staticmethod | ||
| def _looks_like_tool_error(result: str) -> bool: | ||
| """Check if a tool result string looks like an error. | ||
|
|
||
| Many built-in crewAI tools return error strings (e.g., 'Error | ||
| performing search: ...') instead of raising exceptions. This | ||
| heuristic catches those cases so result_as_answer doesn't treat | ||
| them as final agent output. | ||
| """ | ||
| if not result: | ||
| return False | ||
| stripped = result.strip() | ||
| return stripped.startswith("Error ") or stripped.startswith("I encountered an error") | ||
|
|
||
| async def ainvoke(self, inputs: dict[str, Any]) -> dict[str, Any]: | ||
| """Execute the agent asynchronously with given inputs. | ||
|
|
||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.