Skip to content

Commit 8cfd9ba

Browse files
yeesiancopybara-github
authored andcommitted
fix: GenAI SDK client - parsing json body of HTTP response to yield JSON
PiperOrigin-RevId: 788922635
1 parent e92cca2 commit 8cfd9ba

File tree

2 files changed

+73
-4
lines changed

2 files changed

+73
-4
lines changed

tests/unit/vertexai/genai/test_agent_engines.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,46 @@ def test_scan_with_explicit_ignore_modules(self):
10231023
"pydantic": "1.11.1",
10241024
}
10251025

1026+
# pytest does not allow absl.testing.parameterized.named_parameters.
1027+
@pytest.mark.parametrize(
1028+
"obj, expected",
1029+
[
1030+
(
1031+
# "valid_json",
1032+
genai_types.HttpResponse(body='{"a": 1, "b": "hello"}'),
1033+
[{"a": 1, "b": "hello"}],
1034+
),
1035+
(
1036+
# "invalid_json",
1037+
genai_types.HttpResponse(body='{"a": 1, "b": "hello"'),
1038+
['{"a": 1, "b": "hello"'], # returns the unparsed string
1039+
),
1040+
(
1041+
# "nil_body",
1042+
genai_types.HttpResponse(body=None),
1043+
[None],
1044+
),
1045+
(
1046+
# "empty_data",
1047+
genai_types.HttpResponse(body=""),
1048+
[None],
1049+
),
1050+
(
1051+
# "nested_json",
1052+
genai_types.HttpResponse(body='{"a": {"b": 1}}'),
1053+
[{"a": {"b": 1}}],
1054+
),
1055+
(
1056+
# "multiline_json",
1057+
genai_types.HttpResponse(body='{"a": {"b": 1}}\n{"a": {"b": 2}}'),
1058+
[{"a": {"b": 1}}, {"a": {"b": 2}}],
1059+
),
1060+
],
1061+
)
1062+
def test_to_parsed_json(self, obj, expected):
1063+
for got, want in zip(_agent_engines_utils._yield_parsed_json(obj), expected):
1064+
assert got == want
1065+
10261066

10271067
@pytest.mark.usefixtures("google_auth_mock")
10281068
class TestAgentEngine:

vertexai/_genai/_agent_engines_utils.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import proto
4646

4747
from google.api_core import exceptions
48+
from google.genai import types as google_genai_types
4849
from google.protobuf import struct_pb2
4950
from google.protobuf import json_format
5051

@@ -1369,15 +1370,17 @@ def _method(self: genai_types.AgentEngine, **kwargs) -> Iterator[Any]: # type:
13691370
raise ValueError("api_client is not initialized.")
13701371
if not self.api_resource:
13711372
raise ValueError("api_resource is not initialized.")
1372-
for response in self.api_client._stream_query(
1373+
for http_response in self.api_client._stream_query(
13731374
name=self.api_resource.name,
13741375
config={
13751376
"class_method": method_name,
13761377
"input": kwargs,
13771378
"include_all_fields": True,
13781379
},
13791380
):
1380-
yield response
1381+
for line in _yield_parsed_json(http_response=http_response):
1382+
if line is not None:
1383+
yield line
13811384

13821385
return _method
13831386

@@ -1405,14 +1408,40 @@ async def _method(self: genai_types.AgentEngine, **kwargs) -> AsyncIterator[Any]
14051408
raise ValueError("api_client is not initialized.")
14061409
if not self.api_resource:
14071410
raise ValueError("api_resource is not initialized.")
1408-
for response in self.api_client._stream_query(
1411+
for http_response in self.api_client._stream_query(
14091412
name=self.api_resource.name,
14101413
config={
14111414
"class_method": method_name,
14121415
"input": kwargs,
14131416
"include_all_fields": True,
14141417
},
14151418
):
1416-
yield response
1419+
for line in _yield_parsed_json(http_response=http_response):
1420+
if line is not None:
1421+
yield line
14171422

14181423
return _method
1424+
1425+
1426+
def _yield_parsed_json(http_response: google_genai_types.HttpResponse) -> Iterator[Any]:
1427+
"""Converts the body of the HTTP Response message to JSON format.
1428+
1429+
Args:
1430+
http_response (google.genai.types.HttpResponse):
1431+
Required. The httpbody body to be converted to JSON object(s).
1432+
1433+
Yields:
1434+
Any: A JSON object or line of the original body or None.
1435+
"""
1436+
if not http_response.body:
1437+
yield None
1438+
return
1439+
1440+
# Handle the case of multiple dictionaries delimited by newlines.
1441+
for line in http_response.body.split("\n"):
1442+
if line:
1443+
try:
1444+
line = json.loads(line)
1445+
except Exception as e:
1446+
logger.warning(f"failed to parse json: {line}. Exception: {e}")
1447+
yield line

0 commit comments

Comments
 (0)