mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(forge): improve tool call error feedback for LLM self-correction
When tool calls fail validation, the error messages now include:
- What arguments were actually provided
- The expected parameter schema with types and required/optional indicators
This helps LLMs understand and fix their mistakes when retrying,
rather than just being told a parameter is missing.
Example improved error:
Invalid function call for write_file: 'contents' is a required property
You provided: {"filename": 'story.txt'}
Expected parameters: {"filename": string (required), "contents": string (required)}
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@ from .schema import (
|
||||
_ModelName,
|
||||
_ModelProviderSettings,
|
||||
)
|
||||
from .utils import validate_tool_calls
|
||||
from .utils import InvalidFunctionCallError, validate_tool_calls
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_P = ParamSpec("_P")
|
||||
@@ -233,8 +233,8 @@ class BaseOpenAIChatProvider(
|
||||
self._logger.debug(
|
||||
f"Parsing failed on response: '''{_assistant_msg}'''"
|
||||
)
|
||||
parse_errors_fmt = "\n\n".join(
|
||||
f"{e.__class__.__name__}: {e}" for e in parse_errors
|
||||
parse_errors_fmt = self._format_parse_errors(
|
||||
parse_errors, tool_calls, functions
|
||||
)
|
||||
self._logger.warning(
|
||||
f"Parsing attempt #{attempts} failed: {parse_errors_fmt}"
|
||||
@@ -384,6 +384,61 @@ class BaseOpenAIChatProvider(
|
||||
)
|
||||
return completion, cost, prompt_tokens_used, completion_tokens_used
|
||||
|
||||
def _format_parse_errors(
|
||||
self,
|
||||
errors: list[Exception],
|
||||
tool_calls: Optional[list[AssistantToolCall]],
|
||||
functions: Optional[list[CompletionModelFunction]],
|
||||
) -> str:
|
||||
"""Format parse errors with helpful context about what was provided vs expected.
|
||||
|
||||
Args:
|
||||
errors: List of parsing/validation errors.
|
||||
tool_calls: The tool calls that were parsed (may have validation errors).
|
||||
functions: List of available functions for schema lookup.
|
||||
|
||||
Returns:
|
||||
Formatted error string with context.
|
||||
"""
|
||||
formatted_errors = []
|
||||
|
||||
for error in errors:
|
||||
if isinstance(error, InvalidFunctionCallError):
|
||||
# Build informative error message with context
|
||||
error_parts = [str(error)]
|
||||
|
||||
# Show what arguments were provided
|
||||
if error.arguments:
|
||||
args_str = ", ".join(
|
||||
f'"{k}": {repr(v)}' for k, v in error.arguments.items()
|
||||
)
|
||||
error_parts.append(f"\nYou provided: {{{args_str}}}")
|
||||
else:
|
||||
error_parts.append("\nYou provided: (no arguments)")
|
||||
|
||||
# Show expected schema if we have the function definition
|
||||
if functions:
|
||||
func = next(
|
||||
(f for f in functions if f.name == error.name),
|
||||
None,
|
||||
)
|
||||
if func and func.parameters:
|
||||
params_info = []
|
||||
for name, param in func.parameters.items():
|
||||
req = "required" if param.required else "optional"
|
||||
type_str = param.type.value if param.type else "any"
|
||||
params_info.append(f'"{name}": {type_str} ({req})')
|
||||
error_parts.append(
|
||||
f"\nExpected parameters: {{{', '.join(params_info)}}}"
|
||||
)
|
||||
|
||||
formatted_errors.append("".join(error_parts))
|
||||
else:
|
||||
# For non-tool-call errors, just use the standard format
|
||||
formatted_errors.append(f"{error.__class__.__name__}: {error}")
|
||||
|
||||
return "\n\n".join(formatted_errors)
|
||||
|
||||
def _parse_assistant_tool_calls(
|
||||
self, assistant_message: ChatCompletionMessage, **kwargs
|
||||
) -> tuple[list[AssistantToolCall], list[Exception]]:
|
||||
|
||||
@@ -325,7 +325,9 @@ class AnthropicProvider(BaseChatModelProvider[AnthropicModelName, AnthropicSetti
|
||||
# (required if last assistant message had tool_use blocks)
|
||||
tool_results = []
|
||||
for tc in assistant_msg.tool_calls or []:
|
||||
error_msg = self._get_tool_error_message(tc, tool_call_errors)
|
||||
error_msg = self._get_tool_error_message(
|
||||
tc, tool_call_errors, functions
|
||||
)
|
||||
tool_results.append(
|
||||
{
|
||||
"type": "tool_result",
|
||||
@@ -520,12 +522,14 @@ class AnthropicProvider(BaseChatModelProvider[AnthropicModelName, AnthropicSetti
|
||||
self,
|
||||
tool_call: AssistantToolCall,
|
||||
tool_call_errors: list,
|
||||
functions: Optional[list[CompletionModelFunction]] = None,
|
||||
) -> str:
|
||||
"""Get the error message for a failed tool call.
|
||||
|
||||
Args:
|
||||
tool_call: The tool call that failed.
|
||||
tool_call_errors: List of validation errors for tool calls.
|
||||
functions: List of available functions for schema lookup.
|
||||
|
||||
Returns:
|
||||
An appropriate error message for the tool result.
|
||||
@@ -538,9 +542,37 @@ class AnthropicProvider(BaseChatModelProvider[AnthropicModelName, AnthropicSetti
|
||||
(err for err in tool_call_errors if err.name == tool_call.function.name),
|
||||
None,
|
||||
)
|
||||
if matching_error:
|
||||
return str(matching_error)
|
||||
return "Not executed: validation failed"
|
||||
if not matching_error:
|
||||
return "Not executed: validation failed"
|
||||
|
||||
# Build informative error message with context
|
||||
error_parts = [str(matching_error)]
|
||||
|
||||
# Show what arguments were provided
|
||||
provided_args = tool_call.function.arguments
|
||||
if provided_args:
|
||||
args_str = ", ".join(f'"{k}": {repr(v)}' for k, v in provided_args.items())
|
||||
error_parts.append(f"\nYou provided: {{{args_str}}}")
|
||||
else:
|
||||
error_parts.append("\nYou provided: (no arguments)")
|
||||
|
||||
# Show expected schema if we have the function definition
|
||||
if functions:
|
||||
func = next(
|
||||
(f for f in functions if f.name == tool_call.function.name),
|
||||
None,
|
||||
)
|
||||
if func and func.parameters:
|
||||
params_info = []
|
||||
for name, param in func.parameters.items():
|
||||
req = "required" if param.required else "optional"
|
||||
type_str = param.type.value if param.type else "any"
|
||||
params_info.append(f'"{name}": {type_str} ({req})')
|
||||
error_parts.append(
|
||||
f"\nExpected parameters: {{{', '.join(params_info)}}}"
|
||||
)
|
||||
|
||||
return "".join(error_parts)
|
||||
|
||||
def _parse_assistant_tool_calls(
|
||||
self, assistant_message: Message
|
||||
|
||||
Reference in New Issue
Block a user