Python: [BREAKING] Remove FunctionTool[Any] compatibility shim for schema passthrough (#3600) (#3907)

* Fix #3600: Pass JSON schemas through without Pydantic conversion

This change optimizes FunctionTool and MCP flows by passing JSON schemas
directly to providers without converting them to Pydantic models first.

Key changes:
- Store JSON schema as-is when supplied to FunctionTool
- Skip Pydantic model_validate for schema-supplied tools in invoke()
- Return MCP tool schemas directly without conversion
- Add comprehensive tests for schema passthrough behavior

Performance benefits:
- Eliminates expensive Pydantic model creation for supplied schemas
- Preserves exact schema structure (additionalProperties, custom fields, etc.)
- Reduces memory overhead and initialization time

Maintains backward compatibility:
- Function signature inference still uses Pydantic models
- Explicit Pydantic models passed as input_model work as before
- All existing tests pass

* Fix schema passthrough validation and remove helper

* Simplify FunctionTool without generic model dependency

* Fix FunctionTool typing fallout in 3600

* Remove FunctionTool[Any] compatibility shim

* Use serializable kwargs in OTEL tool args
This commit is contained in:
Eduard van Valkenburg
2026-02-14 11:12:21 +01:00
committed by GitHub
Unverified
parent cd1e3110aa
commit fc9c81b0b1
16 changed files with 645 additions and 482 deletions
@@ -324,7 +324,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]] = []
new_tools: list[FunctionTool] = []
for target in targets:
handoff_tool = self._create_handoff_tool(target.target_id, target.description)
if handoff_tool.name in existing_names:
@@ -340,7 +340,7 @@ class HandoffAgentExecutor(AgentExecutor):
else:
default_options["tools"] = existing_tools
def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool[Any]:
def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool:
"""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."