Python: Centralize tool result parsing in FunctionTool.invoke() (#3854)

* Centralize tool result parsing in FunctionTool.invoke()

- Add parse_result static method to FunctionTool that converts raw
  function return values to strings at invocation time
- Add result_parser parameter to FunctionTool and @tool decorator
  for custom parsing
- Remove prepare_function_call_results from all 9 consumer files
  and from the public API
- Update MCPTool to parse MCP types directly to strings via
  _parse_tool_result_from_mcp and _parse_prompt_result_from_mcp
- Change MCPTool parse_tool_results/parse_prompt_results type from
  Literal[True] | Callable | None to Callable | None
- Remove ReturnT type parameter from FunctionTool (now single
  generic ArgsT since invoke() always returns str)
- Update all subclass signatures and docstrings

Fixes #1147

* Fix test_mcp_tool_call_tool_with_meta_integration for string results

The test was still accessing result[0].additional_properties but
invoke() now returns a string, not a list of Content objects.

* Fix SIM108 lint: use binary operator for output assignment

* Fix bedrock: use FunctionTool.parse_result instead of str() fallback

str(result) turns None into literal 'None' and dicts into Python reprs
with single quotes, breaking JSON parsing. Use the shared parse_result
which handles None as '' and serializes via json.dumps.

* updated lock

* updates from feedback
This commit is contained in:
Eduard van Valkenburg
2026-02-12 13:49:42 +00:00
committed by GitHub
parent 6000b737e9
commit 8ed50009c6
31 changed files with 486 additions and 452 deletions
@@ -325,7 +325,7 @@ class HandoffAgentExecutor(AgentExecutor):
existing_tools = list(default_options.get("tools") or [])
existing_names = {getattr(tool, "name", "") for tool in existing_tools if hasattr(tool, "name")}
new_tools: list[FunctionTool[Any, Any]] = []
new_tools: list[FunctionTool[Any]] = []
for target in targets:
handoff_tool = self._create_handoff_tool(target.target_id, target.description)
if handoff_tool.name in existing_names:
@@ -341,7 +341,7 @@ class HandoffAgentExecutor(AgentExecutor):
else:
default_options["tools"] = existing_tools
def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool[Any, Any]:
def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool[Any]:
"""Construct the synthetic handoff tool that signals routing to `target_id`."""
tool_name = get_handoff_tool_name(target_id)
doc = description or f"Handoff to the {target_id} agent."