You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7239de5a19 | |||
| 8176f30c73 |
@@ -90,6 +90,14 @@ def _is_image_part(part: types.Part) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _is_document_part(part: types.Part) -> bool:
|
||||
return (
|
||||
part.inline_data
|
||||
and part.inline_data.mime_type
|
||||
and part.inline_data.mime_type.startswith("application/pdf")
|
||||
)
|
||||
|
||||
|
||||
def part_to_message_block(
|
||||
part: types.Part,
|
||||
) -> Union[
|
||||
@@ -151,6 +159,14 @@ def part_to_message_block(
|
||||
type="base64", media_type=part.inline_data.mime_type, data=data
|
||||
),
|
||||
)
|
||||
elif _is_document_part(part):
|
||||
data = base64.b64encode(part.inline_data.data).decode()
|
||||
return anthropic_types.DocumentBlockParam(
|
||||
type="document",
|
||||
source=dict(
|
||||
type="base64", media_type=part.inline_data.mime_type, data=data
|
||||
),
|
||||
)
|
||||
elif part.executable_code:
|
||||
return anthropic_types.TextBlockParam(
|
||||
type="text",
|
||||
@@ -179,6 +195,13 @@ def content_to_message_param(
|
||||
)
|
||||
continue
|
||||
|
||||
# Document data is not supported in Claude for assistant turns.
|
||||
if content.role != "user" and _is_document_part(part):
|
||||
logger.warning(
|
||||
"Document data is not supported in Claude for assistant turns."
|
||||
)
|
||||
continue
|
||||
|
||||
message_block.append(part_to_message_block(part))
|
||||
|
||||
return {
|
||||
@@ -233,15 +256,18 @@ def _update_type_string(value_dict: dict[str, Any]):
|
||||
if "type" in value_dict:
|
||||
value_dict["type"] = value_dict["type"].lower()
|
||||
|
||||
if "properties" in value_dict:
|
||||
for _, value in value_dict["properties"].items():
|
||||
_update_type_string(value)
|
||||
|
||||
if "items" in value_dict:
|
||||
# 'type' field could exist for items as well, this would be the case if
|
||||
# items represent primitive types.
|
||||
_update_type_string(value_dict["items"])
|
||||
|
||||
if "properties" in value_dict["items"]:
|
||||
# There could be properties as well on the items, especially if the items
|
||||
# are complex object themselves. We recursively traverse each individual
|
||||
# property as well and fix the "type" value.
|
||||
for _, value in value_dict["items"]["properties"].items():
|
||||
_update_type_string(value)
|
||||
|
||||
|
||||
def function_declaration_to_tool_param(
|
||||
function_declaration: types.FunctionDeclaration,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -279,69 +280,6 @@ function_declaration_test_cases = [
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"function_with_nested_object_parameter",
|
||||
types.FunctionDeclaration(
|
||||
name="update_profile",
|
||||
description="Updates a user profile.",
|
||||
parameters=types.Schema(
|
||||
type=types.Type.OBJECT,
|
||||
properties={
|
||||
"profile": types.Schema(
|
||||
type=types.Type.OBJECT,
|
||||
description="The profile data",
|
||||
properties={
|
||||
"name": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Full name",
|
||||
),
|
||||
"address": types.Schema(
|
||||
type=types.Type.OBJECT,
|
||||
description="Mailing address",
|
||||
properties={
|
||||
"city": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
),
|
||||
"state": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
required=["profile"],
|
||||
),
|
||||
),
|
||||
anthropic_types.ToolParam(
|
||||
name="update_profile",
|
||||
description="Updates a user profile.",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"description": "The profile data",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Full name",
|
||||
},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"description": "Mailing address",
|
||||
"properties": {
|
||||
"city": {"type": "string"},
|
||||
"state": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["profile"],
|
||||
},
|
||||
),
|
||||
),
|
||||
(
|
||||
"function_with_parameters_json_schema",
|
||||
types.FunctionDeclaration(
|
||||
@@ -589,6 +527,22 @@ def test_part_to_message_block_with_multiple_content_items():
|
||||
assert result["content"] == "First part\nSecond part"
|
||||
|
||||
|
||||
def test_part_to_message_block_with_pdf_document():
|
||||
"""Test that part_to_message_block handles PDF document parts."""
|
||||
pdf_data = b"%PDF-1.4 fake pdf content"
|
||||
part = Part(
|
||||
inline_data=types.Blob(mime_type="application/pdf", data=pdf_data)
|
||||
)
|
||||
|
||||
result = part_to_message_block(part)
|
||||
|
||||
assert isinstance(result, dict)
|
||||
assert result["type"] == "document"
|
||||
assert result["source"]["type"] == "base64"
|
||||
assert result["source"]["media_type"] == "application/pdf"
|
||||
assert result["source"]["data"] == base64.b64encode(pdf_data).decode()
|
||||
|
||||
|
||||
content_to_message_param_test_cases = [
|
||||
(
|
||||
"user_role_with_text_and_image",
|
||||
@@ -605,7 +559,7 @@ content_to_message_param_test_cases = [
|
||||
),
|
||||
"user",
|
||||
2, # Expected content length
|
||||
False, # Should not log warning
|
||||
None, # No warning expected
|
||||
),
|
||||
(
|
||||
"model_role_with_text_and_image",
|
||||
@@ -622,7 +576,7 @@ content_to_message_param_test_cases = [
|
||||
),
|
||||
"assistant",
|
||||
1, # Image filtered out, only text remains
|
||||
True, # Should log warning
|
||||
"Image data is not supported in Claude for assistant turns.",
|
||||
),
|
||||
(
|
||||
"assistant_role_with_text_and_image",
|
||||
@@ -639,30 +593,62 @@ content_to_message_param_test_cases = [
|
||||
),
|
||||
"assistant",
|
||||
1, # Image filtered out, only text remains
|
||||
True, # Should log warning
|
||||
"Image data is not supported in Claude for assistant turns.",
|
||||
),
|
||||
(
|
||||
"user_role_with_text_and_document",
|
||||
Content(
|
||||
role="user",
|
||||
parts=[
|
||||
Part.from_text(text="Summarize this document."),
|
||||
Part(
|
||||
inline_data=types.Blob(
|
||||
mime_type="application/pdf", data=b"fake_pdf_data"
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
"user",
|
||||
2, # Both text and document included
|
||||
None, # No warning expected
|
||||
),
|
||||
(
|
||||
"model_role_with_text_and_document",
|
||||
Content(
|
||||
role="model",
|
||||
parts=[
|
||||
Part.from_text(text="Here is the summary."),
|
||||
Part(
|
||||
inline_data=types.Blob(
|
||||
mime_type="application/pdf", data=b"fake_pdf_data"
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
"assistant",
|
||||
1, # Document filtered out, only text remains
|
||||
"Document data is not supported in Claude for assistant turns.",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"_, content, expected_role, expected_content_length, should_log_warning",
|
||||
"_, content, expected_role, expected_content_length, expected_warning",
|
||||
content_to_message_param_test_cases,
|
||||
ids=[case[0] for case in content_to_message_param_test_cases],
|
||||
)
|
||||
def test_content_to_message_param_with_images(
|
||||
_, content, expected_role, expected_content_length, should_log_warning
|
||||
def test_content_to_message_param(
|
||||
_, content, expected_role, expected_content_length, expected_warning
|
||||
):
|
||||
"""Test content_to_message_param handles images correctly based on role."""
|
||||
"""Test content_to_message_param handles images and documents based on role."""
|
||||
with mock.patch("google.adk.models.anthropic_llm.logger") as mock_logger:
|
||||
result = content_to_message_param(content)
|
||||
|
||||
assert result["role"] == expected_role
|
||||
assert len(result["content"]) == expected_content_length
|
||||
|
||||
if should_log_warning:
|
||||
mock_logger.warning.assert_called_once_with(
|
||||
"Image data is not supported in Claude for assistant turns."
|
||||
)
|
||||
if expected_warning:
|
||||
mock_logger.warning.assert_called_once_with(expected_warning)
|
||||
else:
|
||||
mock_logger.warning.assert_not_called()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user