Skip to content

Commit b8416a6

Browse files
feat(vertexai): route URL inputs to file_data (#1519)
1 parent 62a73bb commit b8416a6

5 files changed

Lines changed: 66 additions & 5 deletions

File tree

libs/vertexai/langchain_google_vertexai/_image_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import base64
4+
import mimetypes
45
import os
56
import re
67
from enum import Enum
@@ -112,7 +113,10 @@ def load_part(self, image_string: str) -> Part:
112113
bytes_ = self._bytes_from_b64(image_string)
113114

114115
if route == Route.URL:
115-
bytes_ = self._bytes_from_url(image_string)
116+
mime_type, _ = mimetypes.guess_type(image_string)
117+
if not mime_type:
118+
mime_type = "application/octet-stream"
119+
return Part.from_uri(uri=image_string, mime_type=mime_type)
116120

117121
if route == Route.LOCAL_FILE:
118122
msg = (

libs/vertexai/langchain_google_vertexai/chat_models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import base64
99
from functools import cached_property
1010
import json
11+
import mimetypes
1112
import logging
1213
import re
1314
from operator import itemgetter
@@ -302,6 +303,15 @@ def _convert_to_prompt(part: str | dict) -> Part | None:
302303
oai_content_block = convert_to_openai_image_block(part)
303304
url = oai_content_block["image_url"]["url"]
304305
return imageBytesLoader.load_gapic_part(url)
306+
if part.get("source_type") == "url" or "url" in part:
307+
url = part.get("url")
308+
if not url:
309+
msg = "Data content block must contain 'url'."
310+
raise ValueError(msg)
311+
mime_type = part.get("mime_type")
312+
if not mime_type:
313+
mime_type, _ = mimetypes.guess_type(url)
314+
return Part(file_data=FileData(file_uri=url, mime_type=mime_type))
305315
if "base64" in part or part.get("source_type") == "base64":
306316
key_name = "base64" if "base64" in part else "data"
307317
bytes_ = base64.b64decode(part[key_name])
@@ -652,6 +662,22 @@ def _append_to_content(
652662
raise TypeError(msg)
653663

654664

665+
def _collapse_text_content(content: list[Any]) -> str | list[Any]:
666+
"""Collapse list content into a string when it only contains plain text."""
667+
if not content:
668+
return ""
669+
if all(isinstance(item, str) for item in content):
670+
return "".join(content)
671+
if all(
672+
isinstance(item, dict)
673+
and item.get("type") == "text"
674+
and set(item.keys()).issubset({"type", "text"})
675+
for item in content
676+
):
677+
return "".join(item.get("text", "") for item in content)
678+
return content
679+
680+
655681
@overload
656682
def _parse_response_candidate(
657683
response_candidate: Candidate | VertexCandidate,
@@ -804,6 +830,8 @@ def _parse_response_candidate(
804830

805831
if content is None:
806832
content = ""
833+
if isinstance(content, list):
834+
content = _collapse_text_content(content)
807835

808836
if streaming:
809837
return AIMessageChunk(

libs/vertexai/tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Tests configuration to be executed before tests execution."""
22

3+
from collections.abc import Generator
4+
from typing import Any
5+
36
import pytest
47

58
_RELEASE_FLAG = "release"
@@ -55,3 +58,10 @@ def pytest_collection_modifyitems(
5558
if keywords and not any(config.getoption(f"--{kw}") for kw in keywords):
5659
skip = pytest.mark.skip(reason=f"need --{keywords[0]} option to run")
5760
item.add_marker(skip)
61+
62+
63+
@pytest.hookimpl(hookwrapper=True)
64+
def pytest_runtest_makereport(
65+
item: pytest.Item, call: pytest.CallInfo
66+
) -> Generator[None, Any, None]:
67+
yield

libs/vertexai/tests/integration_tests/test_chat_models.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,10 +1163,13 @@ class MyModel(TypedDict):
11631163
assert response == {"name": "Erick", "age": 28}
11641164

11651165
# Test stream
1166+
last_non_empty: dict[str, object] | None = None
11661167
for chunk in model.stream([message]):
11671168
assert isinstance(chunk, dict)
11681169
assert all(key in ["name", "age"] for key in chunk)
1169-
assert chunk == {"name": "Erick", "age": 28}
1170+
if chunk:
1171+
last_non_empty = chunk
1172+
assert last_non_empty == {"name": "Erick", "age": 28}
11701173

11711174

11721175
@pytest.mark.extended
@@ -1243,14 +1246,30 @@ def test_context_catching() -> None:
12431246
response = chat.invoke("What is the secret number?")
12441247

12451248
assert isinstance(response, AIMessage)
1246-
assert isinstance(response.content, str)
1249+
if isinstance(response.content, str):
1250+
content_text = response.content
1251+
else:
1252+
content_text = " ".join(
1253+
block.get("text", "")
1254+
for block in response.content
1255+
if isinstance(block, dict) and block.get("type") == "text"
1256+
)
1257+
assert isinstance(content_text, str)
12471258

12481259
# Using cached content in request
12491260
chat = ChatVertexAI(model_name=_DEFAULT_MODEL_NAME, rate_limiter=RATE_LIMITER)
12501261
response = chat.invoke("What is the secret number?", cached_content=cached_content)
12511262

12521263
assert isinstance(response, AIMessage)
1253-
assert isinstance(response.content, str)
1264+
if isinstance(response.content, str):
1265+
content_text = response.content
1266+
else:
1267+
content_text = " ".join(
1268+
block.get("text", "")
1269+
for block in response.content
1270+
if isinstance(block, dict) and block.get("type") == "text"
1271+
)
1272+
assert isinstance(content_text, str)
12541273

12551274

12561275
@pytest.mark.extended

libs/vertexai/tests/unit_tests/test_chat_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ def test_python_literal_inputs() -> None:
10321032
)
10331033
),
10341034
AIMessage(
1035-
content=["Mike age is 30", "Arthur age is 30"],
1035+
content="Mike age is 30Arthur age is 30",
10361036
additional_kwargs={},
10371037
),
10381038
),

0 commit comments

Comments
 (0)