diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index 4cab69446e3..cbfa14ad21a 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -79,7 +79,6 @@ ANTHROPIC_DEFAULT_MAX_TOKENS: Final[int] = 1024 BETA_FLAGS: Final[list[str]] = ["mcp-client-2025-04-04", "code-execution-2025-08-25"] -STRUCTURED_OUTPUTS_BETA_FLAG: Final[str] = "structured-outputs-2025-11-13" ResponseModelT = TypeVar("ResponseModelT", bound=BaseModel | None, default=None) AnthropicAsyncClient = AsyncAnthropic | AsyncAnthropicBedrock | AsyncAnthropicFoundry | AsyncAnthropicVertex @@ -638,12 +637,17 @@ def _prepare_options( if tools_config := self._prepare_tools_for_anthropic(options): run_options.update(tools_config) - # response_format - use native output_format for structured outputs + # response_format - emit Anthropic's GA ``output_config.format`` shape. + # The deprecated ``output_format`` parameter (gated by the + # ``structured-outputs-2025-11-13`` beta flag) produced concatenated / + # malformed JSON when combined with tools — the GA path does not. + # Merge into any caller-supplied ``output_config`` so e.g. the + # adaptive-thinking ``effort`` setting survives the transformation. response_format = options.get("response_format") if response_format is not None: - run_options["output_format"] = self._prepare_response_format(response_format) - # Add the structured outputs beta flag - run_options["betas"].add(STRUCTURED_OUTPUTS_BETA_FLAG) + output_config = dict(run_options.get("output_config") or {}) + output_config["format"] = self._prepare_response_format(response_format) + run_options["output_config"] = output_config return run_options @@ -693,7 +697,7 @@ def _prepare_betas(self, options: Mapping[str, Any]) -> set[str]: } def _prepare_response_format(self, response_format: type[BaseModel] | dict[str, Any]) -> dict[str, Any]: - """Prepare the output_format parameter for structured output. + """Build the ``output_config.format`` payload for Anthropic structured outputs. Args: response_format: Either a Pydantic model class or a dict with the schema specification. @@ -701,7 +705,8 @@ def _prepare_response_format(self, response_format: type[BaseModel] | dict[str, or direct format with "schema" key, or the raw schema dict itself. Returns: - A dictionary representing the output_format for Anthropic's structured outputs. + A ``{"type": "json_schema", "schema": ...}`` dict — the value placed + under ``output_config["format"]`` on the GA structured-outputs path. """ if isinstance(response_format, dict): if "json_schema" in response_format: diff --git a/python/packages/anthropic/pyproject.toml b/python/packages/anthropic/pyproject.toml index 706acb1574e..b5eee7f78ce 100644 --- a/python/packages/anthropic/pyproject.toml +++ b/python/packages/anthropic/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.10.0,<2", - "anthropic>=0.80.0,<0.80.1", + "anthropic>=0.80.0,<0.117.0", ] [tool.uv] diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index d1dca21e9b4..6cb1907b0ca 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -1986,6 +1986,74 @@ class TestModel(BaseModel): assert "properties" in result["schema"] +async def test_prepare_options_uses_output_config_for_response_format( + mock_anthropic_client: MagicMock, +) -> None: + """``response_format`` is forwarded as GA ``output_config.format`` (not the deprecated ``output_format``). + + The deprecated ``output_format`` parameter, gated by the + ``structured-outputs-2025-11-13`` beta flag, produced concatenated / + malformed JSON when combined with tools. The GA ``output_config`` shape + works correctly with tools, so we emit that and no longer set the beta + flag. + """ + + class StructuredOut(BaseModel): + answer: str + + client = create_test_anthropic_client(mock_anthropic_client) + messages = [Message(role="user", contents=["Hello"])] + chat_options = ChatOptions[StructuredOut](max_tokens=100, response_format=StructuredOut) + + run_options = client._prepare_options(messages, chat_options) + + assert "output_format" not in run_options + assert "output_config" in run_options + fmt = run_options["output_config"]["format"] + assert fmt["type"] == "json_schema" + assert fmt["schema"]["additionalProperties"] is False + assert "answer" in fmt["schema"]["properties"] + # The deprecated structured-outputs beta flag is no longer needed on the + # GA path and must not leak into ``betas``. + assert "structured-outputs-2025-11-13" not in run_options["betas"] + + +async def test_prepare_options_preserves_caller_supplied_output_config_effort( + mock_anthropic_client: MagicMock, +) -> None: + """A caller-supplied ``output_config.effort`` (e.g. adaptive thinking) survives the format merge.""" + + class StructuredOut(BaseModel): + answer: str + + client = create_test_anthropic_client(mock_anthropic_client) + messages = [Message(role="user", contents=["Hello"])] + # ``output_config`` is provider-specific; pass it through additional kwargs + # the way a caller would when configuring adaptive thinking. + run_options = client._prepare_options( + messages, + ChatOptions[StructuredOut](max_tokens=100, response_format=StructuredOut), + output_config={"effort": "high"}, + ) + + output_config = run_options["output_config"] + assert output_config["effort"] == "high" + assert output_config["format"]["type"] == "json_schema" + assert "answer" in output_config["format"]["schema"]["properties"] + + +async def test_prepare_options_no_response_format_omits_output_config( + mock_anthropic_client: MagicMock, +) -> None: + """Without ``response_format``, no ``output_config`` is added implicitly.""" + client = create_test_anthropic_client(mock_anthropic_client) + messages = [Message(role="user", contents=["Hello"])] + run_options = client._prepare_options(messages, ChatOptions(max_tokens=100)) + + assert "output_config" not in run_options + assert "output_format" not in run_options + + # Message Preparation Tests diff --git a/python/uv.lock b/python/uv.lock index 1900787c191..30a88ffe097 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -230,7 +230,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, - { name = "anthropic", specifier = ">=0.80.0,<0.80.1" }, + { name = "anthropic", specifier = ">=0.80.0,<0.117.0" }, ] [[package]] @@ -1182,7 +1182,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.80.0" +version = "0.116.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1194,9 +1194,9 @@ dependencies = [ { name = "sniffio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/63/791e14ef5a8ecb485cef5b5d058c7ca3ad6c50a2f94cf4cea5231c6b7c16/anthropic-0.80.0.tar.gz", hash = "sha256:ef042586673fdcab2a6ffd381aa5f9a1bcce38ffe73c07fe70bd56d12b8124ba", size = 533291, upload-time = "2026-02-17T19:26:26.717Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/a2/d31f14e28d49bae983a3634e38dfb4b31c50110b5e403596c5c6a20b23f8/anthropic-0.116.0.tar.gz", hash = "sha256:5fc248fbb9fe03ef686f8a774f81586bca31a043260aab88b387ea3660f4a396", size = 949149, upload-time = "2026-07-02T19:08:10.534Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/4b/665f29338f51d0c2f9e04b276ea54cc1e957ae5c521a0ad868aa80abc608/anthropic-0.80.0-py3-none-any.whl", hash = "sha256:dad0e40ec371ee686e9ffb2e0cb461a0ed51447fa100927fb5d39b174c286d6f", size = 453667, upload-time = "2026-02-17T19:26:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/c7/dd/2a1e81cf1b163acc340afc4ec74ed1d86f5eed1a809fabdeed3e0997b346/anthropic-0.116.0-py3-none-any.whl", hash = "sha256:6c0a7698e8d652455da3499978279bb2588c7264d0a35be3666009a4258c8256", size = 956896, upload-time = "2026-07-02T19:08:08.756Z" }, ] [[package]]