Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,19 @@ def _convert_message_to_anthropic_format(self, message: ChatMessage) -> dict[str
if content.has_top_level_media_type("image"):
a_content.append({
"type": "image",
"source": {"data": content.uri, "media_type": content.media_type},
"source": {
"data": content.get_data_bytes_as_str(),
"media_type": content.media_type,
"type": "base64",
},
})
else:
logger.debug(f"Ignoring unsupported data content media type: {content.media_type} for now")
case "uri":
if content.has_top_level_media_type("image"):
a_content.append({"type": "image", "source": {"type": "url", "url": content.uri}})
else:
logger.debug(f"Ignoring unsupported data content media type: {content.media_type} for now")
case "function_call":
a_content.append({
"type": "tool_use",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions python/packages/anthropic/tests/test_anthropic_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
import os
from pathlib import Path
from typing import Annotated
from unittest.mock import MagicMock, patch

Expand All @@ -9,6 +10,7 @@
ChatMessage,
ChatOptions,
ChatResponseUpdate,
DataContent,
FinishReason,
FunctionCallContent,
FunctionResultContent,
Expand Down Expand Up @@ -775,3 +777,31 @@ async def test_anthropic_client_integration_ordering() -> None:

assert response is not None
assert response.messages[0].text is not None


@pytest.mark.flaky
@skip_if_anthropic_integration_tests_disabled
async def test_anthropic_client_integration_images() -> None:
"""Integration test with images."""
client = AnthropicClient()

# get a image from the assets folder
image_path = Path(__file__).parent / "assets" / "sample_image.jpg"
with open(image_path, "rb") as img_file: # noqa [ASYNC230]
image_bytes = img_file.read()

messages = [
ChatMessage(
role=Role.USER,
contents=[
TextContent(text="Describe this image"),
DataContent(media_type="image/jpeg", data=image_bytes),
],
),
]

response = await client.get_response(messages=messages)

assert response is not None
assert response.messages[0].text is not None
assert "house" in response.messages[0].text.lower()
29 changes: 26 additions & 3 deletions python/packages/core/agent_framework/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,9 @@ def __iadd__(self, other: "TextReasoningContent") -> Self:
return self


TDataContent = TypeVar("TDataContent", bound="DataContent")


class DataContent(BaseContent):
"""Represents binary data content with an associated media type (also known as a MIME type).

Expand Down Expand Up @@ -1079,8 +1082,8 @@ def detect_image_format_from_base64(image_base64: str) -> str:
except Exception:
return "png" # Fallback if decoding fails

@classmethod
def create_data_uri_from_base64(cls, image_base64: str) -> tuple[str, str]:
@staticmethod
def create_data_uri_from_base64(image_base64: str) -> tuple[str, str]:
"""Create a data URI and media type from base64 image data.

Args:
Expand All @@ -1089,11 +1092,31 @@ def create_data_uri_from_base64(cls, image_base64: str) -> tuple[str, str]:
Returns:
Tuple of (data_uri, media_type)
"""
format_type = cls.detect_image_format_from_base64(image_base64)
format_type = DataContent.detect_image_format_from_base64(image_base64)
uri = f"data:image/{format_type};base64,{image_base64}"
media_type = f"image/{format_type}"
return uri, media_type

def get_data_bytes_as_str(self) -> str:
"""Extracts and returns the base64-encoded data from the data URI.

Returns:
The binary data as str.
"""
match = URI_PATTERN.match(self.uri)
if not match:
raise ValueError(f"Invalid data URI format: {self.uri}")
return match.group("base64_data")

def get_data_bytes(self) -> bytes:
"""Extracts and returns the binary data from the data URI.

Returns:
The binary data as bytes.
"""
base64_data = self.get_data_bytes_as_str()
return base64.b64decode(base64_data)


class UriContent(BaseContent):
"""Represents a URI content.
Expand Down
2 changes: 1 addition & 1 deletion python/packages/lab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ math = [

[dependency-groups]
dev = [
"uv>=0.8.2,<0.9.0",
"uv",
"pre-commit >= 3.7",
"ruff>=0.11.8",
"pytest>=8.4.1",
Expand Down
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dependencies = [

[dependency-groups]
dev = [
"uv>=0.8.2,<0.10.0",
"uv>=0.9,<1.0.0",
"flit>=3.12.0",
"pre-commit >= 3.7",
"ruff>=0.11.8",
Expand Down
Loading