Python: Introducing the Anthropic Client (#1819)

* initial version of anthropic connector

* updated implementation and added tests

* fix type and readme

* mypy fix and int tests enabled

* add integration test setup

* updated based on comments

* improved function result handling

* added extra unordered test

* updated from review

* fix tool choice handling

* same fix for chat client
This commit is contained in:
Eduard van Valkenburg
2025-11-03 20:32:28 +01:00
committed by GitHub
Unverified
parent a766a81243
commit 12d17acdc0
23 changed files with 1826 additions and 63 deletions
@@ -690,12 +690,12 @@ class BaseChatClient(SerializationMixin, ABC):
chat_tool_mode = chat_options.tool_choice
if chat_tool_mode is None or chat_tool_mode == ToolMode.NONE or chat_tool_mode == "none":
chat_options.tools = None
chat_options.tool_choice = ToolMode.NONE.mode
chat_options.tool_choice = ToolMode.NONE
return
if not chat_options.tools:
chat_options.tool_choice = ToolMode.NONE.mode
chat_options.tool_choice = ToolMode.NONE
else:
chat_options.tool_choice = chat_tool_mode.mode if isinstance(chat_tool_mode, ToolMode) else chat_tool_mode
chat_options.tool_choice = chat_tool_mode
def service_url(self) -> str:
"""Get the URL of the service.
+15 -15
View File
@@ -561,7 +561,7 @@ class BaseContent(SerializationMixin):
def __init__(
self,
*,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -651,7 +651,7 @@ class TextContent(BaseContent):
*,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
**kwargs: Any,
):
"""Initializes a TextContent instance.
@@ -793,7 +793,7 @@ class TextReasoningContent(BaseContent):
*,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
**kwargs: Any,
):
"""Initializes a TextReasoningContent instance.
@@ -936,7 +936,7 @@ class DataContent(BaseContent):
self,
*,
uri: str,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -962,7 +962,7 @@ class DataContent(BaseContent):
*,
data: bytes,
media_type: str,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -989,7 +989,7 @@ class DataContent(BaseContent):
uri: str | None = None,
data: bytes | None = None,
media_type: str | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1093,7 +1093,7 @@ class UriContent(BaseContent):
uri: str,
media_type: str,
*,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1187,7 +1187,7 @@ class ErrorContent(BaseContent):
message: str | None = None,
error_code: str | None = None,
details: str | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1271,7 +1271,7 @@ class FunctionCallContent(BaseContent):
name: str,
arguments: str | dict[str, Any | None] | None = None,
exception: Exception | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1380,7 +1380,7 @@ class FunctionResultContent(BaseContent):
call_id: str,
result: Any | None = None,
exception: Exception | None = None,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1438,7 +1438,7 @@ class UsageContent(BaseContent):
self,
details: UsageDetails | MutableMapping[str, Any],
*,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1556,7 +1556,7 @@ class BaseUserInputRequest(BaseContent):
self,
*,
id: str,
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1610,7 +1610,7 @@ class FunctionApprovalResponseContent(BaseContent):
*,
id: str,
function_call: FunctionCallContent | MutableMapping[str, Any],
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -1674,7 +1674,7 @@ class FunctionApprovalRequestContent(BaseContent):
*,
id: str,
function_call: FunctionCallContent | MutableMapping[str, Any],
annotations: list[Annotations | MutableMapping[str, Any]] | None = None,
annotations: Sequence[Annotations | MutableMapping[str, Any]] | None = None,
additional_properties: dict[str, Any] | None = None,
raw_representation: Any | None = None,
**kwargs: Any,
@@ -3146,7 +3146,7 @@ class ChatOptions(SerializationMixin):
@classmethod
def _validate_tool_mode(
cls, tool_choice: ToolMode | Literal["auto", "required", "none"] | Mapping[str, Any] | None
) -> ToolMode | str | None:
) -> ToolMode | None:
"""Validates the tool_choice field to ensure it is a valid ToolMode."""
if not tool_choice:
return None
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft. All rights reserved.
import importlib
from typing import Any
PACKAGE_NAME = "agent_framework_anthropic"
PACKAGE_EXTRA = "anthropic"
_IMPORTS = ["__version__", "AnthropicClient"]
def __getattr__(name: str) -> Any:
if name in _IMPORTS:
try:
return getattr(importlib.import_module(PACKAGE_NAME), name)
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
f"The '{PACKAGE_EXTRA}' extra is not installed, please do `pip install agent-framework-{PACKAGE_EXTRA}`"
) from exc
raise AttributeError(f"Module {PACKAGE_NAME} has no attribute {name}.")
def __dir__() -> list[str]:
return _IMPORTS
@@ -0,0 +1,5 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework_anthropic import AnthropicClient, __version__
__all__ = ["AnthropicClient", "__version__"]
@@ -408,7 +408,7 @@ class OpenAIAssistantsClient(OpenAIConfigMixin, BaseChatClient):
run_options["tools"] = tool_definitions
if chat_options.tool_choice == "none" or chat_options.tool_choice == "auto":
run_options["tool_choice"] = chat_options.tool_choice
run_options["tool_choice"] = chat_options.tool_choice.mode
elif (
isinstance(chat_options.tool_choice, ToolMode)
and chat_options.tool_choice == "required"
@@ -191,6 +191,8 @@ class OpenAIBaseChatClient(OpenAIBase, BaseChatClient):
for key, value in additional_properties.items():
if value is not None:
options_dict[key] = value
if (tool_choice := options_dict.get("tool_choice")) and len(tool_choice.keys()) == 1:
options_dict["tool_choice"] = tool_choice["mode"]
return options_dict
def _create_chat_response(self, response: ChatCompletion, chat_options: ChatOptions) -> "ChatResponse":
@@ -345,6 +345,8 @@ class OpenAIBaseResponsesClient(OpenAIBase, BaseChatClient):
options_dict[key] = value
if "store" not in options_dict:
options_dict["store"] = False
if (tool_choice := options_dict.get("tool_choice")) and len(tool_choice.keys()) == 1:
options_dict["tool_choice"] = tool_choice["mode"]
return options_dict
def _prepare_chat_messages_for_request(self, chat_messages: Sequence[ChatMessage]) -> list[dict[str, Any]]:
+2
View File
@@ -45,6 +45,8 @@ all = [
"agent-framework-mem0",
"agent-framework-redis",
"agent-framework-devui",
"agent-framework-purview",
"agent-framework-anthropic",
]
[tool.uv]