diff --git a/python/packages/claude/agent_framework_claude/_agent.py b/python/packages/claude/agent_framework_claude/_agent.py index 8335d2f149..f8f3796656 100644 --- a/python/packages/claude/agent_framework_claude/_agent.py +++ b/python/packages/claude/agent_framework_claude/_agent.py @@ -511,6 +511,9 @@ async def handler(args: dict[str, Any]) -> dict[str, Any]: "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, diff --git a/python/packages/claude/tests/test_claude_agent.py b/python/packages/claude/tests/test_claude_agent.py index 4402b611e4..15fc0b8090 100644 --- a/python/packages/claude/tests/test_claude_agent.py +++ b/python/packages/claude/tests/test_claude_agent.py @@ -499,6 +499,33 @@ def greet(name: str) -> str: 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.""" diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index 2ebd7b9015..d88fa4b54c 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -796,11 +796,26 @@ async def invoke( 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: diff --git a/python/packages/core/tests/workflow/test_edge.py b/python/packages/core/tests/workflow/test_edge.py index 42e3893a73..95dc71219d 100644 --- a/python/packages/core/tests/workflow/test_edge.py +++ b/python/packages/core/tests/workflow/test_edge.py @@ -10,7 +10,6 @@ Executor, InProcRunnerContext, Message, - SharedState, WorkflowContext, handler, ) @@ -24,6 +23,7 @@ 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 diff --git a/python/packages/core/tests/workflow/test_workflow_states.py b/python/packages/core/tests/workflow/test_workflow_states.py index 53baf86383..4aec349d15 100644 --- a/python/packages/core/tests/workflow/test_workflow_states.py +++ b/python/packages/core/tests/workflow/test_workflow_states.py @@ -8,7 +8,6 @@ ExecutorFailedEvent, InProcRunnerContext, RequestInfoEvent, - SharedState, Workflow, WorkflowBuilder, WorkflowContext, @@ -20,6 +19,7 @@ WorkflowStatusEvent, handler, ) +from agent_framework._workflows._shared_state import SharedState class FailingExecutor(Executor): diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py index 309a71a4b7..5fc34e1d7a 100644 --- a/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_declarative_base.py @@ -32,9 +32,9 @@ 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):