Skip to content

Commit 9ea6700

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Forward per-call http_options from AgentEngine.query family of methods.
PiperOrigin-RevId: 919896831
1 parent a99f340 commit 9ea6700

2 files changed

Lines changed: 126 additions & 14 deletions

File tree

agentplatform/_genai/_agent_engines_utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,18 @@ def _wrap_query_operation(*, method_name: str) -> Callable[..., Any]:
16301630
Agent Engine method using the `query` API. It handles the creation of
16311631
the API request and the processing of the API response.
16321632
1633+
The reserved keyword argument `http_options` is consumed by this
1634+
wrapper (rather than being forwarded to the deployed agent as part of
1635+
`input`) and is propagated to the underlying HTTP call. Use it to set
1636+
per-call HTTP options such as custom headers. For example:
1637+
1638+
from google.genai.types import HttpOptions
1639+
1640+
agent_engine.query(
1641+
input="hello",
1642+
http_options=HttpOptions(headers={"x-my-header": "value"}),
1643+
)
1644+
16331645
Args:
16341646
method_name: The name of the Agent Engine method to call.
16351647
doc: Documentation string for the method.
@@ -1644,12 +1656,14 @@ def _method(self: genai_types.AgentEngine, **kwargs) -> Any: # type: ignore[no-
16441656
raise ValueError("api_client is not initialized.")
16451657
if not self.api_resource:
16461658
raise ValueError("api_resource is not initialized.")
1659+
http_options = kwargs.pop("http_options", None)
16471660
response = self.api_client._query(
16481661
name=self.api_resource.name,
16491662
config={
16501663
"class_method": method_name,
16511664
"input": kwargs,
16521665
"include_all_fields": True,
1666+
"http_options": http_options,
16531667
},
16541668
)
16551669
return response.output
@@ -1666,6 +1680,10 @@ def _wrap_async_query_operation(
16661680
Agent Engine method asynchronously using the `query` API. It handles the
16671681
creation of the API request and the processing of the API response.
16681682
1683+
The reserved keyword argument `http_options` is consumed by this
1684+
wrapper (rather than being forwarded to the deployed agent as part of
1685+
`input`) and is propagated to the underlying HTTP call.
1686+
16691687
Args:
16701688
method_name: The name of the Agent Engine method to call.
16711689
doc: Documentation string for the method.
@@ -1682,12 +1700,14 @@ async def _method(
16821700
raise ValueError("api_async_client is not initialized.")
16831701
if not self.api_resource:
16841702
raise ValueError("api_resource is not initialized.")
1703+
http_options = kwargs.pop("http_options", None)
16851704
response = await self.api_async_client._query(
16861705
name=self.api_resource.name,
16871706
config={
16881707
"class_method": method_name,
16891708
"input": kwargs,
16901709
"include_all_fields": True,
1710+
"http_options": http_options,
16911711
},
16921712
)
16931713
return response.output
@@ -1702,6 +1722,10 @@ def _wrap_stream_query_operation(*, method_name: str) -> Callable[..., Iterator[
17021722
Agent Engine method using the `stream_query` API. It handles the
17031723
creation of the API request and the processing of the API response.
17041724
1725+
The reserved keyword argument `http_options` is consumed by this
1726+
wrapper (rather than being forwarded to the deployed agent as part of
1727+
`input`) and is propagated to the underlying HTTP call.
1728+
17051729
Args:
17061730
method_name: The name of the Agent Engine method to call.
17071731
doc: Documentation string for the method.
@@ -1716,12 +1740,14 @@ def _method(self: genai_types.AgentEngine, **kwargs) -> Iterator[Any]: # type:
17161740
raise ValueError("api_client is not initialized.")
17171741
if not self.api_resource:
17181742
raise ValueError("api_resource is not initialized.")
1743+
http_options = kwargs.pop("http_options", None)
17191744
for http_response in self.api_client._stream_query(
17201745
name=self.api_resource.name,
17211746
config={
17221747
"class_method": method_name,
17231748
"input": kwargs,
17241749
"include_all_fields": True,
1750+
"http_options": http_options,
17251751
},
17261752
):
17271753
for line in _yield_parsed_json(http_response=http_response):
@@ -1740,6 +1766,10 @@ def _wrap_async_stream_query_operation(
17401766
Agent Engine method using the `stream_query` API. It handles the
17411767
creation of the API request and the processing of the API response.
17421768
1769+
The reserved keyword argument `http_options` is consumed by this
1770+
wrapper (rather than being forwarded to the deployed agent as part of
1771+
`input`) and is propagated to the underlying HTTP call.
1772+
17431773
Args:
17441774
method_name: The name of the Agent Engine method to call.
17451775
doc: Documentation string for the method.
@@ -1754,12 +1784,14 @@ async def _method(self: genai_types.AgentEngine, **kwargs) -> AsyncIterator[Any]
17541784
raise ValueError("api_client is not initialized.")
17551785
if not self.api_resource:
17561786
raise ValueError("api_resource is not initialized.")
1787+
http_options = kwargs.pop("http_options", None)
17571788
async for http_response in self.api_client._async_stream_query(
17581789
name=self.api_resource.name,
17591790
config={
17601791
"class_method": method_name,
17611792
"input": kwargs,
17621793
"include_all_fields": True,
1794+
"http_options": http_options,
17631795
},
17641796
):
17651797
for line in _yield_parsed_json(http_response=http_response):

tests/unit/agentplatform/genai/test_agent_engines.py

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def register_operations(self) -> Dict[str, List[str]]:
427427
"""
428428
_TEST_METHOD_TO_BE_UNREGISTERED_NAME = "method_to_be_unregistered"
429429
_TEST_QUERY_PROMPT = "Find the first fibonacci number greater than 999"
430+
_UNSET_HTTP_OPTIONS = object()
430431
_TEST_AGENT_ENGINE_ENV_KEY = "GOOGLE_CLOUD_AGENT_ENGINE_ENV"
431432
_TEST_AGENT_ENGINE_ENV_VALUE = "test_env_value"
432433
_TEST_AGENT_ENGINE_GCS_URI = "{}/{}/{}".format(
@@ -3068,7 +3069,26 @@ def test_delete_agent_engine_force(self):
30683069
None,
30693070
)
30703071

3071-
def test_query_agent_engine(self):
3072+
@pytest.mark.parametrize(
3073+
"http_options_arg, expected_http_options",
3074+
[
3075+
(_UNSET_HTTP_OPTIONS, None),
3076+
(
3077+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3078+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3079+
),
3080+
(
3081+
{"headers": {"x-my-header": "my-value"}},
3082+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3083+
),
3084+
],
3085+
ids=["default", "http_options_instance", "http_options_dict"],
3086+
)
3087+
def test_query_agent_engine(self, http_options_arg, expected_http_options):
3088+
"""Sync query: forwards http_options to the HTTP layer (or None by default)."""
3089+
kwargs = {"query": _TEST_QUERY_PROMPT}
3090+
if http_options_arg is not _UNSET_HTTP_OPTIONS:
3091+
kwargs["http_options"] = http_options_arg
30723092
with mock.patch.object(
30733093
self.client.agent_engines._api_client, "request"
30743094
) as request_mock:
@@ -3086,7 +3106,7 @@ def test_query_agent_engine(self):
30863106
),
30873107
)
30883108
)
3089-
agent.query(query=_TEST_QUERY_PROMPT)
3109+
agent.query(**kwargs)
30903110
request_mock.assert_called_with(
30913111
"post",
30923112
f"{_TEST_AGENT_ENGINE_RESOURCE_NAME}:query",
@@ -3095,7 +3115,7 @@ def test_query_agent_engine(self):
30953115
"classMethod": "query",
30963116
"input": {"query": _TEST_QUERY_PROMPT},
30973117
},
3098-
None,
3118+
expected_http_options,
30993119
)
31003120

31013121
@mock.patch("google.cloud.storage.Client")
@@ -3316,7 +3336,26 @@ def test_run_query_job_agent_engine_directory_no_slash(
33163336
output_gcs_uri="gs://my-input-bucket/path/b92b9b89-4585-4146-8ee5-22fe99802a8e_output.json",
33173337
)
33183338

3319-
def test_query_agent_engine_async(self):
3339+
@pytest.mark.parametrize(
3340+
"http_options_arg, expected_http_options",
3341+
[
3342+
(_UNSET_HTTP_OPTIONS, None),
3343+
(
3344+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3345+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3346+
),
3347+
(
3348+
{"headers": {"x-my-header": "my-value"}},
3349+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3350+
),
3351+
],
3352+
ids=["default", "http_options_instance", "http_options_dict"],
3353+
)
3354+
def test_query_agent_engine_async(self, http_options_arg, expected_http_options):
3355+
"""Async query: forwards http_options to the HTTP layer (or None by default)."""
3356+
kwargs = {"query": _TEST_QUERY_PROMPT}
3357+
if http_options_arg is not _UNSET_HTTP_OPTIONS:
3358+
kwargs["http_options"] = http_options_arg
33203359
agent = self.client.agent_engines._register_api_methods(
33213360
agent_engine=_genai_types.AgentEngine(
33223361
api_async_client=agent_engines.AsyncAgentEngines(
@@ -3336,7 +3375,7 @@ def test_query_agent_engine_async(self):
33363375
self.client.agent_engines._api_client, "async_request"
33373376
) as request_mock:
33383377
request_mock.return_value = genai_types.HttpResponse(body="")
3339-
asyncio.run(agent.async_query(query=_TEST_QUERY_PROMPT))
3378+
asyncio.run(agent.async_query(**kwargs))
33403379
request_mock.assert_called_with(
33413380
"post",
33423381
f"{_TEST_AGENT_ENGINE_RESOURCE_NAME}:query",
@@ -3345,7 +3384,7 @@ def test_query_agent_engine_async(self):
33453384
"classMethod": "async_query",
33463385
"input": {"query": _TEST_QUERY_PROMPT},
33473386
},
3348-
None,
3387+
expected_http_options,
33493388
)
33503389

33513390
def test_cancel_query_job_agent_engine(self):
@@ -3507,7 +3546,26 @@ def test_check_query_job_agent_engine_blob_not_exists(self):
35073546
config={"retrieve_result": True},
35083547
)
35093548

3510-
def test_query_agent_engine_stream(self):
3549+
@pytest.mark.parametrize(
3550+
"http_options_arg, expected_http_options",
3551+
[
3552+
(_UNSET_HTTP_OPTIONS, None),
3553+
(
3554+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3555+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3556+
),
3557+
(
3558+
{"headers": {"x-my-header": "my-value"}},
3559+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3560+
),
3561+
],
3562+
ids=["default", "http_options_instance", "http_options_dict"],
3563+
)
3564+
def test_query_agent_engine_stream(self, http_options_arg, expected_http_options):
3565+
"""Streaming query: forwards http_options to the HTTP layer (or None by default)."""
3566+
kwargs = {"query": _TEST_QUERY_PROMPT}
3567+
if http_options_arg is not _UNSET_HTTP_OPTIONS:
3568+
kwargs["http_options"] = http_options_arg
35113569
with mock.patch.object(
35123570
self.client.agent_engines._api_client, "request_streamed"
35133571
) as request_mock:
@@ -3524,7 +3582,7 @@ def test_query_agent_engine_stream(self):
35243582
),
35253583
)
35263584
)
3527-
list(agent.stream_query(query=_TEST_QUERY_PROMPT))
3585+
list(agent.stream_query(**kwargs))
35283586
request_mock.assert_called_with(
35293587
"post",
35303588
f"{_TEST_AGENT_ENGINE_RESOURCE_NAME}:streamQuery?alt=sse",
@@ -3533,11 +3591,34 @@ def test_query_agent_engine_stream(self):
35333591
"classMethod": "stream_query",
35343592
"input": {"query": _TEST_QUERY_PROMPT},
35353593
},
3536-
None,
3594+
expected_http_options,
35373595
)
35383596

3539-
def test_query_agent_engine_async_stream(self):
3597+
@pytest.mark.parametrize(
3598+
"http_options_arg, expected_http_options",
3599+
[
3600+
(_UNSET_HTTP_OPTIONS, None),
3601+
(
3602+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3603+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3604+
),
3605+
(
3606+
{"headers": {"x-my-header": "my-value"}},
3607+
genai_types.HttpOptions(headers={"x-my-header": "my-value"}),
3608+
),
3609+
],
3610+
ids=["default", "http_options_instance", "http_options_dict"],
3611+
)
3612+
def test_query_agent_engine_async_stream(
3613+
self, http_options_arg, expected_http_options
3614+
):
3615+
"""Async streaming query: forwards http_options to the HTTP layer (or None)."""
3616+
kwargs = {"query": _TEST_QUERY_PROMPT}
3617+
if http_options_arg is not _UNSET_HTTP_OPTIONS:
3618+
kwargs["http_options"] = http_options_arg
3619+
35403620
async def mock_async_generator():
3621+
"""Fake async-streamed HTTP response generator."""
35413622
yield genai_types.HttpResponse(body=b"")
35423623

35433624
with mock.patch.object(
@@ -3559,9 +3640,8 @@ async def mock_async_generator():
35593640
)
35603641

35613642
async def consume():
3562-
async for response in agent.async_stream_query(
3563-
query=_TEST_QUERY_PROMPT
3564-
):
3643+
"""Drain the async iterator."""
3644+
async for response in agent.async_stream_query(**kwargs):
35653645
print(response)
35663646

35673647
asyncio.run(consume())
@@ -3573,7 +3653,7 @@ async def consume():
35733653
"classMethod": "async_stream_query",
35743654
"input": {"query": _TEST_QUERY_PROMPT},
35753655
},
3576-
None,
3656+
expected_http_options,
35773657
)
35783658

35793659
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)