Skip to content

Commit 2eb0fcb

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
fix: fix the async_stream_query method. Restore yield_parsed_json method lost during vertex -> agentplatform migration
PiperOrigin-RevId: 939654762
1 parent e135857 commit 2eb0fcb

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

agentplatform/_genai/_agent_engines_utils.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
from google.api_core import exceptions
5555
from google.genai import types as google_genai_types
56+
from google.api import httpbody_pb2
5657
from google.protobuf import struct_pb2
5758
from google.protobuf import json_format
5859

@@ -1915,6 +1916,50 @@ def _yield_parsed_json(http_response: google_genai_types.HttpResponse) -> Iterat
19151916
yield line
19161917

19171918

1919+
def _yield_parsed_json_from_httpbody(body: httpbody_pb2.HttpBody) -> Iterator[Any]:
1920+
"""Converts the contents of an `HttpBody` proto message to JSON format.
1921+
1922+
Unlike `_yield_parsed_json`, which parses a `google.genai.types.HttpResponse`
1923+
(with a `body` attribute), this helper parses the gRPC `httpbody_pb2.HttpBody`
1924+
protos yielded by `stream_query_reasoning_engine`, which expose
1925+
`content_type` and `data` instead.
1926+
1927+
Args:
1928+
body (httpbody_pb2.HttpBody):
1929+
Required. The httpbody proto to be converted to JSON object(s).
1930+
1931+
Yields:
1932+
Any: A JSON object, a line of the original body, or the original body if
1933+
it is not JSON, or None.
1934+
"""
1935+
content_type = getattr(body, "content_type", None)
1936+
data = getattr(body, "data", None)
1937+
1938+
if content_type is None or data is None or "application/json" not in content_type:
1939+
yield body
1940+
return
1941+
1942+
try:
1943+
utf8_data = data.decode("utf-8")
1944+
except Exception as e:
1945+
logger.warning(f"Failed to decode data: {data!r}. Exception: {e}")
1946+
yield body
1947+
return
1948+
1949+
if not utf8_data:
1950+
yield None
1951+
return
1952+
1953+
# Handle the case of multiple dictionaries delimited by newlines.
1954+
for line in utf8_data.split("\n"):
1955+
if line:
1956+
try:
1957+
line = json.loads(line)
1958+
except Exception as e:
1959+
logger.warning(f"failed to parse json: {line}. Exception: {e}")
1960+
yield line
1961+
1962+
19181963
def _validate_resource_limits_or_raise(resource_limits: dict[str, str]) -> None:
19191964
"""Validates the resource limits.
19201965

agentplatform/agent_engines/_agent_engines.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,9 @@ def _method(self, **kwargs) -> Iterable[Any]:
16351635
),
16361636
)
16371637
for chunk in response:
1638-
for parsed_json in _agent_engines_utils._yield_parsed_json(chunk):
1638+
for parsed_json in _agent_engines_utils._yield_parsed_json_from_httpbody(
1639+
chunk
1640+
):
16391641
if parsed_json is not None:
16401642
yield parsed_json
16411643

@@ -1669,7 +1671,9 @@ async def _method(self, **kwargs) -> AsyncIterable[Any]:
16691671
),
16701672
)
16711673
for chunk in response:
1672-
for parsed_json in _agent_engines_utils._yield_parsed_json(chunk):
1674+
for parsed_json in _agent_engines_utils._yield_parsed_json_from_httpbody(
1675+
chunk
1676+
):
16731677
if parsed_json is not None:
16741678
yield parsed_json
16751679

tests/unit/agentplatform/genai/test_agent_engines.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from agentplatform._genai import types as _genai_types
3838
from google.genai import client as genai_client
3939
from google.genai import types as genai_types
40+
from google.api import httpbody_pb2
4041
import pytest
4142

4243

@@ -1767,6 +1768,49 @@ def test_to_parsed_json(self, obj, expected):
17671768
for got, want in zip(_agent_engines_utils._yield_parsed_json(obj), expected):
17681769
assert got == want
17691770

1771+
# pytest does not allow absl.testing.parameterized.named_parameters.
1772+
@pytest.mark.parametrize(
1773+
"obj, expected",
1774+
[
1775+
(
1776+
# "valid_json",
1777+
httpbody_pb2.HttpBody(
1778+
content_type="application/json", data=b'{"a": 1, "b": "hello"}'
1779+
),
1780+
[{"a": 1, "b": "hello"}],
1781+
),
1782+
(
1783+
# "invalid_json",
1784+
httpbody_pb2.HttpBody(
1785+
content_type="application/json", data=b'{"a": 1, "b": "hello"'
1786+
),
1787+
['{"a": 1, "b": "hello"'], # returns the unparsed string
1788+
),
1789+
(
1790+
# "empty_data",
1791+
httpbody_pb2.HttpBody(content_type="application/json", data=b""),
1792+
[None],
1793+
),
1794+
(
1795+
# "multiline_json",
1796+
httpbody_pb2.HttpBody(
1797+
content_type="application/json",
1798+
data=b'{"a": {"b": 1}}\n{"a": {"b": 2}}',
1799+
),
1800+
[{"a": {"b": 1}}, {"a": {"b": 2}}],
1801+
),
1802+
],
1803+
)
1804+
def test_yield_parsed_json_from_httpbody(self, obj, expected):
1805+
got = list(_agent_engines_utils._yield_parsed_json_from_httpbody(obj))
1806+
assert got == expected
1807+
1808+
def test_yield_parsed_json_from_httpbody_non_json_content_type(self):
1809+
body = httpbody_pb2.HttpBody(content_type="text/plain", data=b"hello")
1810+
assert list(_agent_engines_utils._yield_parsed_json_from_httpbody(body)) == [
1811+
body
1812+
]
1813+
17701814
def test_create_base64_encoded_tarball(self):
17711815
with tempfile.TemporaryDirectory() as tmpdir:
17721816
test_file_path = os.path.join(tmpdir, "test_file.txt")

0 commit comments

Comments
 (0)