Skip to content

Commit 172c69d

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: Forward per-request labels to RunConfig in streaming_agent_run_with_events
PiperOrigin-RevId: 953446344
1 parent f93c455 commit 172c69d

4 files changed

Lines changed: 139 additions & 0 deletions

File tree

agentplatform/agent_engines/templates/adk.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ def __init__(self, **kwargs):
236236
)
237237
# The session ID.
238238

239+
self.labels: Optional[Dict[str, str]] = kwargs.get("labels")
240+
# Per-request user labels (e.g. for billing/attribution) to attach to
241+
# the RunConfig for this invocation, so they are propagated onto the
242+
# downstream Vertex API requests.
243+
239244

240245
class _StreamingRunResponse:
241246
"""Response object for `streaming_agent_run_with_events` method.
@@ -1414,12 +1419,20 @@ async def streaming_agent_run_with_events(self, request_json: str):
14141419

14151420
# Run the agent
14161421
message_for_agent = types.Content(**request.message)
1422+
# Propagate per-request user labels (e.g. billing/attribution) onto a
1423+
# RunConfig so the ADK flow forwards them to downstream Vertex requests.
1424+
run_config = None
1425+
if request.labels:
1426+
from google.adk.agents.run_config import RunConfig
1427+
1428+
run_config = RunConfig(labels=request.labels)
14171429
try:
14181430
async for event in runner.run_async(
14191431
user_id=request.user_id,
14201432
session_id=session.id,
14211433
new_message=message_for_agent,
14221434
state_delta=state_delta,
1435+
run_config=run_config,
14231436
):
14241437
converted_event = await self._convert_response_events(
14251438
user_id=request.user_id,

tests/unit/agentplatform/frameworks/test_frameworks_adk.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,62 @@ async def test_streaming_agent_run_with_events(
655655
events.append(event)
656656
assert len(events) == 1
657657

658+
@pytest.mark.asyncio
659+
async def test_streaming_agent_run_with_events_propagates_labels(
660+
self,
661+
default_instrumentor_builder_mock: mock.Mock,
662+
get_project_id_mock: mock.Mock,
663+
):
664+
app = adk_template.AdkApp(agent=_TEST_AGENT)
665+
app.set_up()
666+
667+
# Pre-create a session in the real in-memory session service.
668+
await app.async_create_session(
669+
user_id=_TEST_USER_ID, session_id="test_session_id"
670+
)
671+
672+
runner_mock = mock.Mock()
673+
674+
async def mock_run_async(*args, **kwargs):
675+
from google.adk.events import event
676+
677+
yield event.Event(
678+
**{
679+
"author": "currency_exchange_agent",
680+
"content": {"parts": [{"text": "Sweden"}], "role": "model"},
681+
"id": "9aaItGK9",
682+
"invocation_id": "e-6543c213-6417-484b-9551-b67915d1d5f7",
683+
}
684+
)
685+
686+
spy = mock.MagicMock(side_effect=mock_run_async)
687+
runner_mock.run_async = spy
688+
app._tmpl_attrs["runner"] = runner_mock
689+
690+
labels = {"goog-originating-logical-product-id": "prod1"}
691+
request_json = json.dumps(
692+
{
693+
"user_id": _TEST_USER_ID,
694+
"session_id": "test_session_id",
695+
"message": {
696+
"parts": [{"text": "What is the exchange rate from USD to SEK?"}],
697+
"role": "user",
698+
},
699+
"labels": labels,
700+
}
701+
)
702+
703+
async for _ in app.streaming_agent_run_with_events(
704+
request_json=request_json,
705+
):
706+
pass
707+
708+
# The labels are surfaced to run_async via a RunConfig.
709+
spy.assert_called_once()
710+
run_config = spy.call_args.kwargs["run_config"]
711+
assert run_config is not None
712+
assert run_config.labels == labels
713+
658714
@pytest.mark.asyncio
659715
@mock.patch.dict(
660716
os.environ,

tests/unit/vertex_adk/test_agent_engine_templates_adk.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,65 @@ async def mock_run_async(*args, **kwargs):
572572
session_id="test_session_id",
573573
new_message=mock.ANY,
574574
state_delta={"test_user_id1": "test_access_token"},
575+
run_config=None,
575576
)
576577

578+
@pytest.mark.asyncio
579+
async def test_streaming_agent_run_with_events_propagates_labels(
580+
self,
581+
default_instrumentor_builder_mock: mock.Mock,
582+
get_project_id_mock: mock.Mock,
583+
):
584+
app = agent_engines.AdkApp(agent=_TEST_AGENT)
585+
app.set_up()
586+
587+
# Pre-create a session in the real in-memory session service.
588+
await app.async_create_session(
589+
user_id=_TEST_USER_ID, session_id="test_session_id"
590+
)
591+
592+
runner_mock = mock.Mock()
593+
594+
async def mock_run_async(*args, **kwargs):
595+
from google.adk.events import event
596+
597+
yield event.Event(
598+
**{
599+
"author": "currency_exchange_agent",
600+
"content": {"parts": [{"text": "Sweden"}], "role": "model"},
601+
"id": "9aaItGK9",
602+
"invocation_id": "e-6543c213-6417-484b-9551-b67915d1d5f7",
603+
}
604+
)
605+
606+
spy = mock.MagicMock(side_effect=mock_run_async)
607+
runner_mock.run_async = spy
608+
app._tmpl_attrs["runner"] = runner_mock
609+
610+
labels = {"goog-originating-logical-product-id": "prod1"}
611+
request_json = json.dumps(
612+
{
613+
"user_id": _TEST_USER_ID,
614+
"session_id": "test_session_id",
615+
"message": {
616+
"parts": [{"text": "What is the exchange rate from USD to SEK?"}],
617+
"role": "user",
618+
},
619+
"labels": labels,
620+
}
621+
)
622+
623+
async for _ in app.streaming_agent_run_with_events(
624+
request_json=request_json,
625+
):
626+
pass
627+
628+
# The labels are surfaced to run_async via a RunConfig.
629+
spy.assert_called_once()
630+
run_config = spy.call_args.kwargs["run_config"]
631+
assert run_config is not None
632+
assert run_config.labels == labels
633+
577634
@pytest.mark.asyncio
578635
@mock.patch.dict(
579636
os.environ,

vertexai/agent_engines/templates/adk.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ def __init__(self, **kwargs):
220220
)
221221
# The session ID.
222222

223+
self.labels: Optional[Dict[str, str]] = kwargs.get("labels")
224+
# Per-request user labels (e.g. for billing/attribution) to attach to
225+
# the RunConfig for this invocation, so they are propagated onto the
226+
# downstream Vertex API requests.
227+
223228

224229
class _StreamingRunResponse:
225230
"""Response object for `streaming_agent_run_with_events` method.
@@ -1374,12 +1379,20 @@ async def streaming_agent_run_with_events(self, request_json: str):
13741379

13751380
# Run the agent
13761381
message_for_agent = types.Content(**request.message)
1382+
# Propagate per-request user labels (e.g. billing/attribution) onto a
1383+
# RunConfig so the ADK flow forwards them to downstream Vertex requests.
1384+
run_config = None
1385+
if request.labels:
1386+
from google.adk.agents.run_config import RunConfig
1387+
1388+
run_config = RunConfig(labels=request.labels)
13771389
try:
13781390
async for event in runner.run_async(
13791391
user_id=request.user_id,
13801392
session_id=session.id,
13811393
new_message=message_for_agent,
13821394
state_delta=state_delta,
1395+
run_config=run_config,
13831396
):
13841397
converted_event = await self._convert_response_events(
13851398
user_id=request.user_id,

0 commit comments

Comments
 (0)