mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: fix(claude): preserve $defs in JSON schema for nested Pydantic models (#3655)
* fix(claude): preserve $defs in JSON schema for nested Pydantic models - Preserve $defs section from Pydantic JSON schema when converting FunctionTool to SDK MCP tool - This fixes tools with nested Pydantic models that use $ref references - Add test for nested type schema preservation Fixes #3654 * Adjust shared state import * Fix MCP tool kwargs serialization bug --------- Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
06d43ee130
commit
5c6cf4fc92
@@ -511,6 +511,9 @@ class ClaudeAgent(BaseAgent, Generic[TOptions]):
|
||||
"properties": schema.get("properties", {}),
|
||||
"required": schema.get("required", []),
|
||||
}
|
||||
# Preserve $defs for nested type references (Pydantic uses $defs for nested models)
|
||||
if "$defs" in schema:
|
||||
input_schema["$defs"] = schema["$defs"]
|
||||
|
||||
return SdkMcpTool(
|
||||
name=func_tool.name,
|
||||
|
||||
@@ -499,6 +499,33 @@ class TestClaudeAgentToolConversion:
|
||||
assert sdk_tool.input_schema is not None
|
||||
assert "properties" in sdk_tool.input_schema # type: ignore[operator]
|
||||
|
||||
def test_function_tool_to_sdk_mcp_tool_preserves_defs_for_nested_types(self) -> None:
|
||||
"""Test that $defs is preserved for tools with nested Pydantic models."""
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Address(BaseModel):
|
||||
street: str
|
||||
city: str
|
||||
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
address: Address
|
||||
|
||||
@tool
|
||||
def create_person(person: Person) -> str:
|
||||
"""Create a person with address."""
|
||||
return f"{person.name} lives at {person.address.street}, {person.address.city}"
|
||||
|
||||
agent = ClaudeAgent()
|
||||
sdk_tool = agent._function_tool_to_sdk_mcp_tool(create_person) # type: ignore[reportPrivateUsage]
|
||||
|
||||
# Verify $defs is preserved in the schema
|
||||
assert sdk_tool.input_schema is not None
|
||||
assert "$defs" in sdk_tool.input_schema # type: ignore[operator]
|
||||
assert "Address" in sdk_tool.input_schema["$defs"] # type: ignore[index]
|
||||
# Verify the nested reference exists in properties
|
||||
assert "person" in sdk_tool.input_schema["properties"] # type: ignore[index]
|
||||
|
||||
async def test_tool_handler_success(self) -> None:
|
||||
"""Test tool handler executes successfully."""
|
||||
|
||||
|
||||
@@ -796,11 +796,26 @@ class FunctionTool(BaseTool, Generic[ArgsT, ReturnT]):
|
||||
|
||||
attributes = get_function_span_attributes(self, tool_call_id=tool_call_id)
|
||||
if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined]
|
||||
# Filter out framework kwargs that are not JSON serializable
|
||||
serializable_kwargs = {
|
||||
k: v
|
||||
for k, v in kwargs.items()
|
||||
if k
|
||||
not in {
|
||||
"chat_options",
|
||||
"tools",
|
||||
"tool_choice",
|
||||
"thread",
|
||||
"conversation_id",
|
||||
"options",
|
||||
"response_format",
|
||||
}
|
||||
}
|
||||
attributes.update({
|
||||
OtelAttr.TOOL_ARGUMENTS: arguments.model_dump_json()
|
||||
if arguments
|
||||
else json.dumps(kwargs)
|
||||
if kwargs
|
||||
else json.dumps(serializable_kwargs, default=str)
|
||||
if serializable_kwargs
|
||||
else "None"
|
||||
})
|
||||
with get_function_span(attributes=attributes) as span:
|
||||
|
||||
@@ -10,7 +10,6 @@ from agent_framework import (
|
||||
Executor,
|
||||
InProcRunnerContext,
|
||||
Message,
|
||||
SharedState,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
@@ -24,6 +23,7 @@ from agent_framework._workflows._edge import (
|
||||
SwitchCaseEdgeGroupDefault,
|
||||
)
|
||||
from agent_framework._workflows._edge_runner import create_edge_runner
|
||||
from agent_framework._workflows._shared_state import SharedState
|
||||
from agent_framework.observability import EdgeGroupDeliveryStatus
|
||||
|
||||
# Add for test
|
||||
|
||||
@@ -8,7 +8,6 @@ from agent_framework import (
|
||||
ExecutorFailedEvent,
|
||||
InProcRunnerContext,
|
||||
RequestInfoEvent,
|
||||
SharedState,
|
||||
Workflow,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
@@ -20,6 +19,7 @@ from agent_framework import (
|
||||
WorkflowStatusEvent,
|
||||
handler,
|
||||
)
|
||||
from agent_framework._workflows._shared_state import SharedState
|
||||
|
||||
|
||||
class FailingExecutor(Executor):
|
||||
|
||||
+1
-1
@@ -32,9 +32,9 @@ from typing import Any, Literal, cast
|
||||
|
||||
from agent_framework._workflows import (
|
||||
Executor,
|
||||
SharedState,
|
||||
WorkflowContext,
|
||||
)
|
||||
from agent_framework._workflows._shared_state import SharedState
|
||||
from powerfx import Engine
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
|
||||
Reference in New Issue
Block a user