Skip to content

Commit 5720b13

Browse files
authored
fixed Anthropic url support (#839)
1 parent 7bdf41f commit 5720b13

File tree

4 files changed

+115
-46
lines changed

4 files changed

+115
-46
lines changed

libs/vertexai/langchain_google_vertexai/_anthropic_utils.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import re
23
import warnings
34
from typing import (
@@ -31,7 +32,9 @@
3132
from langchain_core.utils.function_calling import convert_to_openai_tool
3233
from pydantic import BaseModel
3334

34-
from langchain_google_vertexai._image_utils import image_bytes_to_b64_string
35+
from langchain_google_vertexai._image_utils import (
36+
ImageBytesLoader,
37+
)
3538
from langchain_google_vertexai._utils import load_image_from_gcs
3639

3740
if TYPE_CHECKING:
@@ -49,29 +52,43 @@
4952

5053
def _format_image(image_url: str, project: Optional[str]) -> Dict:
5154
"""Formats a message image to a dict for anthropic api."""
52-
regex = r"^data:(?P<media_type>image/.+);base64,(?P<data>.+)$"
55+
regex = r"^data:(?P<media_type>(?:image|application)/.+);base64,(?P<data>.+)$"
5356
match = re.match(regex, image_url)
54-
5557
if match:
5658
return {
5759
"type": "base64",
5860
"media_type": match.group("media_type"),
5961
"data": match.group("data"),
6062
}
6163
elif validators.url(image_url):
64+
loader = ImageBytesLoader(project=project)
65+
image_bytes = loader.load_bytes(image_url)
66+
raw_mime_type = image_url.split(".")[-1].lower()
67+
doc_type = "application" if raw_mime_type == "pdf" else "image"
68+
mime_type = (
69+
f"{doc_type}/jpeg"
70+
if raw_mime_type == "jpg"
71+
else f"{doc_type}/{raw_mime_type}"
72+
)
6273
return {
63-
"type": "url",
64-
"url": image_url,
74+
"type": "base64",
75+
"media_type": mime_type,
76+
"data": base64.b64encode(image_bytes).decode("ascii"),
6577
}
6678
elif image_url.startswith("gs://"):
6779
# Gets image and encodes to base64.
68-
image = load_image_from_gcs(image_url, project)
80+
loader = ImageBytesLoader(project=project)
81+
part = loader.load_part(image_url)
82+
if part.file_data.mime_type:
83+
mime_type = part.file_data.mime_type
84+
image_data = load_image_from_gcs(image_url, project=project).data
85+
else:
86+
mime_type = part.inline_data.mime_type
87+
image_data = part.inline_data.data
6988
return {
7089
"type": "base64",
71-
"media_type": image._mime_type(),
72-
"data": image_bytes_to_b64_string(
73-
image.data(), "ascii", image._mime_type().split("/")[-1]
74-
),
90+
"media_type": mime_type,
91+
"data": base64.b64encode(image_data).decode("ascii"),
7592
}
7693
else:
7794
raise ValueError(
@@ -168,7 +185,11 @@ def _format_message_anthropic(
168185
if block["type"] == "image_url":
169186
# convert format
170187
source = _format_image(block["image_url"]["url"], project)
171-
content.append({"type": "image", "source": source})
188+
if source["media_type"] == "application/pdf":
189+
doc_type = "document"
190+
else:
191+
doc_type = "image"
192+
content.append({"type": doc_type, "source": source})
172193
continue
173194

174195
if block["type"] == "tool_use":

libs/vertexai/langchain_google_vertexai/_image_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,12 @@ def image_bytes_to_b64_string(
252252
Returns:
253253
B64 image encoded string.
254254
"""
255+
if image_format == "pdf":
256+
image_type = "application"
257+
else:
258+
image_type = "image"
255259
encoded_bytes = base64.b64encode(image_bytes).decode(encoding)
256-
return f"data:image/{image_format};base64,{encoded_bytes}"
260+
return f"data:{image_type}/{image_format};base64,{encoded_bytes}"
257261

258262

259263
def create_text_content_part(message_str: str) -> Dict:
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Test ChatGoogleVertexAI chat model."""
2+
import os
3+
4+
from langchain_google_vertexai._image_utils import image_bytes_to_b64_string
5+
from langchain_google_vertexai._utils import load_image_from_gcs
6+
from langchain_google_vertexai.model_garden import ChatAnthropicVertex
7+
8+
9+
def test_pdf_gcs_uri():
10+
gcs_uri = "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf"
11+
llm = ChatAnthropicVertex(
12+
model="claude-3-5-sonnet-v2@20241022",
13+
location="us-east5",
14+
temperature=0.8,
15+
project=os.environ["PROJECT_ID"],
16+
)
17+
18+
res = llm.invoke(
19+
[
20+
{
21+
"role": "user",
22+
"content": [
23+
"Parse this pdf.",
24+
{"type": "image_url", "image_url": {"url": gcs_uri}},
25+
],
26+
}
27+
]
28+
)
29+
assert len(res.content) > 100
30+
31+
32+
def test_pdf_byts():
33+
gcs_uri = "gs://cloud-samples-data/generative-ai/pdf/2403.05530.pdf"
34+
llm = ChatAnthropicVertex(
35+
model="claude-3-5-sonnet-v2@20241022",
36+
location="us-east5",
37+
temperature=0.8,
38+
project=os.environ["PROJECT_ID"],
39+
)
40+
image = load_image_from_gcs(gcs_uri, "kuligin-sandbox1")
41+
image_data = image_bytes_to_b64_string(image.data, "ascii", "pdf")
42+
43+
res = llm.invoke(
44+
[
45+
{
46+
"role": "user",
47+
"content": [
48+
"Parse this pdf.",
49+
{"type": "image_url", "image_url": {"url": image_data}},
50+
],
51+
}
52+
]
53+
)
54+
assert len(res.content) > 100
55+
56+
57+
def test_https_image():
58+
uri = "https://picsum.photos/seed/picsum/200/300.jpg"
59+
60+
llm = ChatAnthropicVertex(
61+
model="claude-3-5-sonnet-v2@20241022",
62+
location="us-east5",
63+
temperature=0.8,
64+
project=os.environ["PROJECT_ID"],
65+
)
66+
67+
res = llm.invoke(
68+
[
69+
{
70+
"role": "user",
71+
"content": [
72+
"Parse this pdf.",
73+
{"type": "image_url", "image_url": {"url": uri}},
74+
],
75+
}
76+
]
77+
)
78+
assert len(res.content) > 10

libs/vertexai/tests/unit_tests/test_anthropic_utils.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -173,40 +173,6 @@ def test_format_message_anthropic_with_chain_of_thoughts():
173173
]
174174

175175

176-
def test_format_message_anthropic_with_image_content():
177-
"""Test formatting a system message with chain of thoughts."""
178-
message = SystemMessage(
179-
content=[
180-
{
181-
"type": "image_url",
182-
"image_url": {"url": "data:image/png;base64,/9j/4AAQSk"},
183-
},
184-
{
185-
"type": "image_url",
186-
"image_url": {"url": "https://your-valid-image-url.png"},
187-
},
188-
]
189-
)
190-
result = _format_message_anthropic(message, project="test-project")
191-
assert result == [
192-
{
193-
"type": "image",
194-
"source": {
195-
"type": "base64",
196-
"media_type": "image/png",
197-
"data": "/9j/4AAQSk",
198-
},
199-
},
200-
{
201-
"type": "image",
202-
"source": {
203-
"type": "url",
204-
"url": "https://your-valid-image-url.png",
205-
},
206-
},
207-
]
208-
209-
210176
def test_format_messages_anthropic_with_system_string():
211177
"""Test formatting messages with system message as string."""
212178
messages = [

0 commit comments

Comments
 (0)