From 47d82911c07044bdfda4de406adb48b2f42d7672 Mon Sep 17 00:00:00 2001 From: chetantoshniwal Date: Thu, 2 Apr 2026 02:43:59 -0700 Subject: [PATCH] Python: Fix server_tool_use input_json_delta handling and improve Anthropic samples (#5050) * Fix server_tool_use input_json_delta handling and improve Anthropic samples - Fix: Skip input_json_delta for server_tool_use content blocks in AnthropicClient streaming. Server-managed tools (e.g., skills with code interpreter) were producing Content.from_function_call(name='') entries that caused Anthropic API 400 errors on subsequent turns. - Samples: Add dotenv loading and environment variable documentation to Anthropic Claude samples (MCP, permissions, session, shell, tools, URL, skills). * Add regression test for server_tool_use + input_json_delta skip behavior Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/7c68dcb2-b577-4e36-b423-664b8fe3ac1d Co-authored-by: chetantoshniwal <255221507+chetantoshniwal@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: chetantoshniwal <255221507+chetantoshniwal@users.noreply.github.com> --- .../agent_framework_anthropic/_chat_client.py | 4 +- .../anthropic/tests/test_anthropic_client.py | 47 +++++++++++++++++++ .../anthropic/anthropic_claude_with_mcp.py | 3 ++ ...hropic_claude_with_multiple_permissions.py | 7 +++ .../anthropic_claude_with_session.py | 7 +++ .../anthropic/anthropic_claude_with_shell.py | 7 +++ .../anthropic/anthropic_claude_with_tools.py | 7 +++ .../anthropic/anthropic_claude_with_url.py | 7 +++ .../providers/anthropic/anthropic_skills.py | 4 ++ 9 files changed, 91 insertions(+), 2 deletions(-) diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index ebe03411a3..6e5e5f14a6 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -1281,8 +1281,8 @@ class RawAnthropicClient( ) ) case "input_json_delta": - # Skip argument deltas for MCP tools — execution is handled server-side. - if self._last_call_content_type == "mcp_tool_use": + # Skip argument deltas for MCP and server tools — execution is handled server-side. + if self._last_call_content_type in ("mcp_tool_use", "server_tool_use"): pass else: call_id = self._last_call_id_name[0] if self._last_call_id_name else "" diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index d1102d56ad..c0567b6d87 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -1144,6 +1144,53 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name( assert result[0].arguments == '"San Francisco"}' +def test_parse_contents_server_tool_use_input_json_delta_ignored( + mock_anthropic_client: MagicMock, +) -> None: + """Regression test: input_json_delta events are ignored after a server_tool_use block. + + Server-managed tools have their execution handled server-side, so streaming + input_json_delta events must not produce Content.from_function_call(name='') + entries that would cause Anthropic API 400 errors on subsequent turns. + """ + client = create_test_anthropic_client(mock_anthropic_client) + + # Simulate a server_tool_use event that sets _last_call_content_type + server_tool_content = MagicMock() + server_tool_content.type = "server_tool_use" + server_tool_content.id = "srvtool_abc" + server_tool_content.name = "web_search" + server_tool_content.input = {} + + result = client._parse_contents_from_anthropic([server_tool_content]) + # server_tool_use falls through to function_call (not mcp_tool_use / code_execution) + assert len(result) == 1 + assert result[0].type == "function_call" + assert client._last_call_content_type == "server_tool_use" # type: ignore[attr-defined] + + # input_json_delta events after server_tool_use must be silently ignored + delta_content = MagicMock() + delta_content.type = "input_json_delta" + delta_content.partial_json = '{"query": "latest news"}' + + result = client._parse_contents_from_anthropic([delta_content]) + assert result == [], ( + "input_json_delta after server_tool_use should produce no content, " + "but got: %r" % result + ) + + # A second delta must also be ignored + delta_content_2 = MagicMock() + delta_content_2.type = "input_json_delta" + delta_content_2.partial_json = '{"extra": true}' + + result = client._parse_contents_from_anthropic([delta_content_2]) + assert result == [], ( + "subsequent input_json_delta after server_tool_use should also be ignored, " + "but got: %r" % result + ) + + # Stream Processing Tests diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_mcp.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_mcp.py index 991481487c..67f702860f 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_mcp.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_mcp.py @@ -12,6 +12,9 @@ Supported MCP server types: - "http": Remote HTTP server - "sse": Remote SSE (Server-Sent Events) server +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key + SECURITY NOTE: MCP servers can expose powerful capabilities. Only configure servers you trust. Use permission handlers to control what actions are allowed. """ diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_multiple_permissions.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_multiple_permissions.py index e4165089c0..12181b39eb 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_multiple_permissions.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_multiple_permissions.py @@ -15,6 +15,9 @@ Available built-in tools: - "Glob": Search for files by pattern - "Grep": Search file contents +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key + SECURITY NOTE: Only enable permissions that are necessary for your use case. More permissions mean more potential for unintended actions. """ @@ -24,6 +27,10 @@ from typing import Any from agent_framework.anthropic import ClaudeAgent from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() async def prompt_permission( diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_session.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_session.py index 8a022624b5..31bd1457d7 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_session.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_session.py @@ -6,6 +6,9 @@ Claude Agent with Session Management This sample demonstrates session management with ClaudeAgent, showing persistent conversation capabilities. Sessions are automatically persisted by the Claude Code CLI. + +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key """ import asyncio @@ -14,8 +17,12 @@ from typing import Annotated from agent_framework import tool from agent_framework.anthropic import ClaudeAgent +from dotenv import load_dotenv from pydantic import Field +# Load environment variables from .env file +load_dotenv() + @tool def get_weather( diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_shell.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_shell.py index eb8c0961fa..08562d7f1b 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_shell.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_shell.py @@ -7,6 +7,9 @@ This sample demonstrates how to enable shell command execution with ClaudeAgent. By providing a permission handler via `can_use_tool`, the agent can execute shell commands to perform tasks like listing files, running scripts, or executing system commands. +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key + SECURITY NOTE: Only enable shell permissions when you trust the agent's actions. Shell commands have full access to your system within the permissions of the running process. """ @@ -16,6 +19,10 @@ from typing import Any from agent_framework.anthropic import ClaudeAgent from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() async def prompt_permission( diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_tools.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_tools.py index 8ce13ef54e..29a99e013e 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_tools.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_tools.py @@ -13,11 +13,18 @@ Available built-in tools: - "Edit": Edit existing files - "Glob": Search for files by pattern - "Grep": Search file contents + +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key """ import asyncio from agent_framework.anthropic import ClaudeAgent +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() async def main() -> None: diff --git a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_url.py b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_url.py index bcb6b8b03e..5d206dd244 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_claude_with_url.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_claude_with_url.py @@ -10,6 +10,9 @@ Available web tools: - "WebFetch": Fetch content from URLs - "WebSearch": Search the web +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key + SECURITY NOTE: Only enable URL permissions when you trust the agent's actions. URL fetching allows the agent to access any URL accessible from your network. """ @@ -17,6 +20,10 @@ URL fetching allows the agent to access any URL accessible from your network. import asyncio from agent_framework.anthropic import ClaudeAgent +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() async def main() -> None: diff --git a/python/samples/02-agents/providers/anthropic/anthropic_skills.py b/python/samples/02-agents/providers/anthropic/anthropic_skills.py index 9d8b2df92b..5f4d1d40b9 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_skills.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_skills.py @@ -21,6 +21,10 @@ This sample demonstrates using Anthropic with: You can also set additonal_chat_options with "additional_beta_flags" per request. - Creating an agent with the Code Interpreter tool and a Skill. - Catching and downloading generated files from the agent. + +Environment variables: +- ANTHROPIC_API_KEY: Your Anthropic API key +- ANTHROPIC_CHAT_MODEL_ID: The Anthropic model to use, such as "claude-sonnet-4-5-20250929" """