From df87bfaeefe30b7b37deb2c2d1f043a648f22d39 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Tue, 6 Oct 2026 17:22:37 +0200 Subject: [PATCH 1/2] Update Anthropic SDK to 1.11 --- .../agent_framework_anthropic/_chat_client.py | 6 +++--- python/packages/anthropic/pyproject.toml | 2 +- .../anthropic/tests/test_anthropic_client.py | 15 ++++++++++++--- python/uv.lock | 11 +++++------ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index 5cc91b1efed..e4a7d50a84e 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -95,7 +95,7 @@ logger = logging.getLogger("agent_framework.anthropic") ANTHROPIC_DEFAULT_MAX_TOKENS: Final[int] = 1024 -BETA_FLAGS: Final[list[str]] = ["mcp-client-2025-04-04", "code-execution-2025-08-25"] +BETA_FLAGS: Final[list[str]] = ["mcp-client-2025-04-04"] ResponseModelT = TypeVar("ResponseModelT", bound=BaseModel | None, default=None) AnthropicAsyncClient = AsyncAnthropic | AsyncAnthropicBedrock | AsyncAnthropicFoundry | AsyncAnthropicVertex @@ -332,7 +332,7 @@ def __init__( This can be used to further configure the client before passing it in. For instance if you need to set a different base_url for testing or private deployments. additional_beta_flags: Additional beta flags to enable on the client. - Default flags are: "mcp-client-2025-04-04", "code-execution-2025-08-25". + The default flag is "mcp-client-2025-04-04". additional_properties: Additional properties stored on the client instance. env_file_path: Path to environment file for loading settings. env_file_encoding: Encoding of the environment file. @@ -1774,7 +1774,7 @@ def __init__( This can be used to further configure the client before passing it in. For instance if you need to set a different base_url for testing or private deployments. additional_beta_flags: Additional beta flags to enable on the client. - Default flags are: "mcp-client-2025-04-04", "code-execution-2025-08-25". + The default flag is "mcp-client-2025-04-04". additional_properties: Additional properties stored on the client instance. middleware: Optional middleware to apply to the client. function_invocation_configuration: Optional function invocation configuration override. diff --git a/python/packages/anthropic/pyproject.toml b/python/packages/anthropic/pyproject.toml index 44353c95faa..030a61e2409 100644 --- a/python/packages/anthropic/pyproject.toml +++ b/python/packages/anthropic/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "agent-framework-core>=1.19.0,<2", - "anthropic>=0.80.0,<0.117.0", + "anthropic>=1.11.0,<2", ] [tool.uv] diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 0b330a469cb..94b86ca2f0a 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -7,7 +7,7 @@ from unittest.mock import MagicMock, patch import anthropic as anthropic_sdk -import httpx +import httpx2 import pytest from agent_framework import ( Agent, @@ -1505,6 +1505,15 @@ async def test_prepare_options_excludes_stream_option( assert "stream" not in run_options +def test_prepare_betas_uses_only_required_default_flags( + mock_anthropic_client: MagicMock, +) -> None: + """GA features must not add redundant beta headers.""" + client = create_test_anthropic_client(mock_anthropic_client) + + assert client._prepare_betas({}) == {"mcp-client-2025-04-04"} + + async def test_prepare_options_consumes_additional_beta_flags( mock_anthropic_client: MagicMock, ) -> None: @@ -2001,8 +2010,8 @@ async def mock_stream(): def _anthropic_status_error( error_cls: type[anthropic_sdk.APIStatusError], status_code: int, message: str ) -> anthropic_sdk.APIStatusError: - request = httpx.Request("POST", "https://api.anthropic.com/v1/messages") - response = httpx.Response(status_code, request=request, json={"error": {"message": message}}) + request = httpx2.Request("POST", "https://api.anthropic.com/v1/messages") + response = httpx2.Response(status_code, request=request, json={"error": {"message": message}}) return error_cls(message, response=response, body={"error": {"message": message}}) diff --git a/python/uv.lock b/python/uv.lock index 87ca7014a48..5a80dc0c084 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -253,7 +253,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "agent-framework-core", editable = "packages/core" }, - { name = "anthropic", specifier = ">=0.80.0,<0.117.0" }, + { name = "anthropic", specifier = ">=1.11.0,<2" }, ] [[package]] @@ -1254,21 +1254,20 @@ wheels = [ [[package]] name = "anthropic" -version = "0.116.0" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "distro", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "docstring-parser", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "httpx2", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "jiter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { 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/66/a2/d31f14e28d49bae983a3634e38dfb4b31c50110b5e403596c5c6a20b23f8/anthropic-0.116.0.tar.gz", hash = "sha256:5fc248fbb9fe03ef686f8a774f81586bca31a043260aab88b387ea3660f4a396", size = 949149, upload-time = "2026-07-02T19:08:10.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/12/9a6ffa397b172adb040008d934a1dfd85d0e4cefc77489416db294ffc880/anthropic-1.11.0.tar.gz", hash = "sha256:3906fabac7ad7b5b46c6186040398fc7826885c77ce34e4dd7849de16fc8d0f8", size = 1390390, upload-time = "2026-09-30T22:58:21.098Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/ab/6f/20edcd2956cac68042b1bcdd13ab3311447519f86b26929ff268dc74c650/anthropic-1.11.0-py3-none-any.whl", hash = "sha256:52f97b2c485cca7ac66058374f5073a3febb7e6849b16989c145d602a3efee21", size = 1681654, upload-time = "2026-09-30T22:58:22.925Z" }, ] [[package]] From 19032d2316c893cc702d36d50a6a7fd78085d667 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Tue, 6 Oct 2026 18:56:58 +0200 Subject: [PATCH 2/2] Fix Anthropic SDK 1.x compatibility --- .../_bedrock_client.py | 31 ++++++++++++- .../agent_framework_anthropic/_chat_client.py | 28 +++++++++--- .../anthropic/tests/test_anthropic_client.py | 21 ++++++--- .../tests/test_anthropic_provider_clients.py | 45 +++++++++++++++++++ .../providers/anthropic/anthropic_skills.py | 15 +++---- python/samples/02-agents/typed_options.py | 13 +++--- 6 files changed, 125 insertions(+), 28 deletions(-) diff --git a/python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py b/python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py index 215382b33a8..3af207e20be 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py @@ -2,8 +2,10 @@ from __future__ import annotations +import logging +import os from collections.abc import Sequence -from typing import Any, ClassVar, Generic, TypedDict +from typing import Any, ClassVar, Generic, TypedDict, cast from agent_framework import ( ChatAndFunctionMiddlewareTypes, @@ -18,6 +20,31 @@ from ._chat_client import AnthropicOptionsT, RawAnthropicClient +logger = logging.getLogger("agent_framework.anthropic") + +DEFAULT_AWS_REGION = "us-east-1" + + +def _resolve_aws_region(aws_region: str | None, aws_profile: str | None) -> str: + if aws_region: + return aws_region + + if env_region := os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION"): + return env_region + + try: + import boto3 + + session: Any = boto3.Session(profile_name=aws_profile) + profile_region = cast(str | None, session.region_name) + if profile_region: + return profile_region + except ImportError: + pass + + logger.warning("No AWS region specified, defaulting to %s", DEFAULT_AWS_REGION) + return DEFAULT_AWS_REGION + class AnthropicBedrockSettings(TypedDict, total=False): """Resolved settings for Anthropic Bedrock wrappers.""" @@ -90,7 +117,7 @@ def __init__( anthropic_client = AsyncAnthropicBedrock( aws_secret_key=secret_key_secret.get_secret_value() if secret_key_secret is not None else None, aws_access_key=access_key_secret.get_secret_value() if access_key_secret is not None else None, - aws_region=settings.get("aws_region"), + aws_region=_resolve_aws_region(settings.get("aws_region"), settings.get("aws_profile")), aws_profile=settings.get("aws_profile"), aws_session_token=session_token_secret.get_secret_value() if session_token_secret is not None else None, base_url=settings.get("anthropic_bedrock_base_url"), diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index e4a7d50a84e..146b9f6a998 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -96,6 +96,7 @@ ANTHROPIC_DEFAULT_MAX_TOKENS: Final[int] = 1024 BETA_FLAGS: Final[list[str]] = ["mcp-client-2025-04-04"] +UNSUPPORTED_SAMPLING_OPTIONS: Final[tuple[str, ...]] = ("temperature", "top_p", "top_k") ResponseModelT = TypeVar("ResponseModelT", bound=BaseModel | None, default=None) AnthropicAsyncClient = AsyncAnthropic | AsyncAnthropicBedrock | AsyncAnthropicFoundry | AsyncAnthropicVertex @@ -150,8 +151,8 @@ class AnthropicChatOptions(ChatOptions[ResponseModelT], Generic[ResponseModelT], a default of 1024 will be used. Keys: - temperature: Sampling temperature between 0 and 1. - top_p: Nucleus sampling parameter. + temperature: Unsupported by Anthropic SDK 1.x; ignored with a warning. + top_p: Unsupported by Anthropic SDK 1.x; ignored with a warning. max_tokens: Maximum number of tokens to generate (REQUIRED). stop: Stop sequences, translates to ``stop_sequences`` in Anthropic API. @@ -163,7 +164,7 @@ class AnthropicChatOptions(ChatOptions[ResponseModelT], Generic[ResponseModelT], instructions: System instructions for the model, translating to ``system`` in the Anthropic API. Use a string for generic instructions or structured Anthropic system blocks when you need prompt-cache ``cache_control``. - top_k: Number of top tokens to consider for sampling. + top_k: Unsupported by Anthropic SDK 1.x; ignored with a warning. service_tier: Service tier ("auto" or "standard_only"). thinking: Extended thinking configuration for Claude models. When enabled, responses include ``thinking`` content blocks showing Claude's @@ -174,8 +175,7 @@ class AnthropicChatOptions(ChatOptions[ResponseModelT], Generic[ResponseModelT], additional_beta_flags: Additional beta flags to enable on the request. """ - # Anthropic-specific generation parameters (supported by all models) - top_k: int + # Anthropic-specific generation parameters service_tier: Literal["auto", "standard_only"] # Extended thinking (Claude models) @@ -191,6 +191,9 @@ class AnthropicChatOptions(ChatOptions[ResponseModelT], Generic[ResponseModelT], additional_beta_flags: list[str] # Unsupported base options (override with None to indicate not supported) + temperature: None # type: ignore[misc] + top_p: None # type: ignore[misc] + top_k: None logit_bias: None # type: ignore[misc] seed: None # type: ignore[misc] frequency_penalty: None # type: ignore[misc] @@ -229,6 +232,20 @@ def _apply_option_translations(options: dict[str, Any]) -> None: options.setdefault(new_key, old_value) +def _drop_unsupported_sampling_options(options: dict[str, Any]) -> None: + unsupported_options = [name for name in UNSUPPORTED_SAMPLING_OPTIONS if name in options] + if not unsupported_options: + return + + for name in unsupported_options: + options.pop(name) + + logger.warning( + "Ignoring unsupported Anthropic sampling options: %s", + ", ".join(unsupported_options), + ) + + # region Role and Finish Reason Maps @@ -714,6 +731,7 @@ def _prepare_options( } _apply_option_translations(filtered_kwargs) run_options.update(filtered_kwargs) + _drop_unsupported_sampling_options(run_options) # system message - Anthropic expects system instructions as a separate request parameter instructions = options.get("instructions") diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 94b86ca2f0a..c91a37765ed 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio +import logging import os import re from pathlib import Path @@ -1129,13 +1130,12 @@ async def test_prepare_options_basic(mock_anthropic_client: MagicMock) -> None: client = create_test_anthropic_client(mock_anthropic_client) messages = [Message(role="user", contents=["Hello"])] - chat_options = ChatOptions(max_tokens=100, temperature=0.7) + chat_options = ChatOptions(max_tokens=100) run_options = client._prepare_options(messages, chat_options) assert run_options["model"] == client.model assert run_options["max_tokens"] == 100 - assert run_options["temperature"] == 0.7 assert "messages" in run_options @@ -1479,16 +1479,23 @@ async def test_prepare_options_with_stop_sequences( assert run_options["stop_sequences"] == ["STOP", "END"] -async def test_prepare_options_with_top_p(mock_anthropic_client: MagicMock) -> None: - """Test _prepare_options with top_p.""" +def test_prepare_options_ignores_unsupported_sampling_options( + mock_anthropic_client: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Unsupported sampling options are dropped before calling Anthropic SDK 1.x.""" client = create_test_anthropic_client(mock_anthropic_client) messages = [Message(role="user", contents=["Hello"])] - chat_options = ChatOptions(top_p=0.9) + chat_options = {"temperature": 0.7, "top_p": 0.9, "top_k": 40} - run_options = client._prepare_options(messages, chat_options) + with caplog.at_level(logging.WARNING, logger="agent_framework.anthropic"): + run_options = client._prepare_options(messages, chat_options) - assert run_options["top_p"] == 0.9 + assert "temperature" not in run_options + assert "top_p" not in run_options + assert "top_k" not in run_options + assert "Ignoring unsupported Anthropic sampling options: temperature, top_p, top_k" in caplog.text async def test_prepare_options_excludes_stream_option( diff --git a/python/packages/anthropic/tests/test_anthropic_provider_clients.py b/python/packages/anthropic/tests/test_anthropic_provider_clients.py index d637aedef96..182091c8c9b 100644 --- a/python/packages/anthropic/tests/test_anthropic_provider_clients.py +++ b/python/packages/anthropic/tests/test_anthropic_provider_clients.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import logging from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -211,6 +212,50 @@ def test_raw_anthropic_bedrock_client_creates_sdk_client_from_arguments( assert type(factory.call_args.kwargs[key]) is str +def test_raw_anthropic_bedrock_client_preserves_default_region( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + monkeypatch.delenv("AWS_REGION", raising=False) + monkeypatch.delenv("AWS_DEFAULT_REGION", raising=False) + mock_transport = _create_mock_transport("https://bedrock-runtime.us-east-1.amazonaws.com") + + with ( + patch("boto3.Session") as session_factory, + patch( + "agent_framework_anthropic._bedrock_client.AsyncAnthropicBedrock", + return_value=mock_transport, + ) as factory, + caplog.at_level(logging.WARNING, logger="agent_framework.anthropic"), + ): + session_factory.return_value.region_name = None + RawAnthropicBedrockClient(model="claude-bedrock-test") + + assert factory.call_args.kwargs["aws_region"] == "us-east-1" + assert "No AWS region specified, defaulting to us-east-1" in caplog.text + + +def test_raw_anthropic_bedrock_client_uses_profile_region( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("AWS_REGION", raising=False) + monkeypatch.delenv("AWS_DEFAULT_REGION", raising=False) + mock_transport = _create_mock_transport("https://bedrock-runtime.eu-west-1.amazonaws.com") + + with ( + patch("boto3.Session") as session_factory, + patch( + "agent_framework_anthropic._bedrock_client.AsyncAnthropicBedrock", + return_value=mock_transport, + ) as factory, + ): + session_factory.return_value.region_name = "eu-west-1" + RawAnthropicBedrockClient(model="claude-bedrock-test", aws_profile="test-profile") + + session_factory.assert_called_once_with(profile_name="test-profile") + assert factory.call_args.kwargs["aws_region"] == "eu-west-1" + + @pytest.mark.parametrize("access_token", ["access-token", SecretString("access-token")], ids=["str", "secret"]) def test_raw_anthropic_vertex_client_creates_sdk_client_from_arguments( access_token: str | SecretString, diff --git a/python/samples/02-agents/providers/anthropic/anthropic_skills.py b/python/samples/02-agents/providers/anthropic/anthropic_skills.py index f5945474bad..5724955c2be 100644 --- a/python/samples/02-agents/providers/anthropic/anthropic_skills.py +++ b/python/samples/02-agents/providers/anthropic/anthropic_skills.py @@ -3,9 +3,11 @@ import asyncio import logging from pathlib import Path +from typing import cast from agent_framework import Agent, Content from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient +from anthropic import AsyncAnthropic from dotenv import load_dotenv # Load environment variables from .env file @@ -17,8 +19,6 @@ This sample demonstrates using Anthropic with: - Listing and using Anthropic-managed Skills. -- One approach to add additional beta flags. - 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. @@ -30,12 +30,13 @@ async def main() -> None: """Example of streaming response (get results as they are generated).""" - client = AnthropicClient[AnthropicChatOptions](additional_beta_flags=["skills-2025-10-02"]) + client = AnthropicClient[AnthropicChatOptions]() + anthropic_client = cast(AsyncAnthropic, client.anthropic_client) # List Anthropic-managed Skills - skills = await client.anthropic_client.beta.skills.list(source="anthropic", betas=["skills-2025-10-02"]) # type: ignore + skills = await anthropic_client.skills.list(source="anthropic") for skill in skills.data: - print(f"{skill.source}: {skill.id} (version: {skill.latest_version})") + print(f"{skill.source}: {skill.id} (version: {skill.latest_version_id})") # Create a agent with the pptx skill enabled # Skills also need the code interpreter tool to function @@ -87,9 +88,7 @@ async def main() -> None: for idx, file in enumerate(files): if file.file_id is None: continue - file_content = await client.anthropic_client.beta.files.download( # type: ignore - file_id=file.file_id, betas=["files-api-2025-04-14"] - ) + file_content = await anthropic_client.files.download(file_id=file.file_id) with open(Path(__file__).parent / f"python_programming-{idx}.pptx", "wb") as f: await file_content.write_to_file(f.name) print(f"File {idx}: python_programming-{idx}.pptx saved to disk.") diff --git a/python/samples/02-agents/typed_options.py b/python/samples/02-agents/typed_options.py index b7c22a4151e..23f07948aba 100644 --- a/python/samples/02-agents/typed_options.py +++ b/python/samples/02-agents/typed_options.py @@ -40,15 +40,15 @@ async def demo_anthropic_chat_client() -> None: # Create Anthropic client client = AnthropicClient(model="claude-sonnet-4-5-20250929") - # Standard options work great: + # Supported standard and Anthropic-specific options are type checked: response = await client.get_response( [Message("user", contents=["What is the capital of France?"])], options={ - "temperature": 1, # Must be 1 when thinking is enabled "max_tokens": 2048, - # Anthropic-specific options: "thinking": {"type": "enabled", "budget_tokens": 1024}, - # "top_k": 40, # <-- Uncomment for Anthropic-specific option + # Sampling options removed from Anthropic SDK 1.x are caught by the type checker: + # "temperature": 1, + # "top_k": 40, }, ) @@ -68,9 +68,10 @@ async def demo_anthropic_agent() -> None: name="claude-assistant", instructions="You are a helpful assistant powered by Claude. Be concise.", default_options={ - "temperature": 0.5, "max_tokens": 200, - "top_k": 40, # Anthropic-specific option, uncomment to try + # Sampling options removed from Anthropic SDK 1.x are caught by the type checker: + # "temperature": 0.5, + # "top_k": 40, }, )