Skip to content

Commit 49fdc26

Browse files
haranrkcopybara-github
authored andcommitted
feat(agents): forward ManagedAgent instruction as system_instruction
Resolve ManagedAgent.instruction (with {placeholder} state injection for strings; InstructionProvider callables bypass injection) and forward it to the Managed Agents API as the interaction's system_instruction on every turn, including chained turns. An empty instruction sends nothing. Co-authored-by: Haran Rajkumar <haranrk@google.com> PiperOrigin-RevId: 951220238
1 parent 720af3d commit 49fdc26

2 files changed

Lines changed: 131 additions & 1 deletion

File tree

src/google/adk/agents/_managed_agent.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from ..utils.content_utils import to_user_content
5252
from ..utils.context_utils import Aclosing
5353
from ..utils.env_utils import is_enterprise_mode_enabled
54+
from ..utils.instructions_utils import inject_session_state
5455
from ..utils.instructions_utils import InstructionProvider
5556
from .base_agent import BaseAgent
5657
from .context import Context
@@ -394,6 +395,15 @@ async def _run_async_impl(
394395
)
395396
interaction_tools = await self._resolve_backend_tools(ctx)
396397

398+
raw_si, bypass_state_injection = await self.canonical_instruction(
399+
ReadonlyContext(ctx)
400+
)
401+
system_instruction = raw_si
402+
if not bypass_state_injection:
403+
system_instruction = await inject_session_state(
404+
raw_si, ReadonlyContext(ctx)
405+
)
406+
397407
create_kwargs: dict[str, Any] = {
398408
'agent': self.agent_id,
399409
'input': input_steps,
@@ -411,6 +421,8 @@ async def _run_async_impl(
411421
create_kwargs['agent_config'] = self.agent_config
412422
if prev_interaction_id:
413423
create_kwargs['previous_interaction_id'] = prev_interaction_id
424+
if system_instruction:
425+
create_kwargs['system_instruction'] = system_instruction
414426

415427
# Request-time header merge, parity with google_llm.generate_content_async:
416428
# combine any RunConfig headers with ADK tracking headers, non-destructively.
@@ -436,7 +448,7 @@ async def _run_async_impl(
436448
build_interactions_request_log(
437449
model=self.agent_id,
438450
input_steps=input_steps,
439-
system_instruction=None,
451+
system_instruction=system_instruction or None,
440452
tools=interaction_tools if interaction_tools else None,
441453
generation_config=None,
442454
previous_interaction_id=prev_interaction_id,

tests/unittests/agents/test_managed_agent.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,3 +1089,121 @@ async def provider(ctx):
10891089

10901090
assert text == 'async provider'
10911091
assert bypass is True
1092+
1093+
1094+
def test_instruction_defaults_to_empty_and_is_omitted():
1095+
client = _RecordingClient([[]])
1096+
agent = ManagedAgent(name='mgr', agent_id='agents/a', api_client=client)
1097+
1098+
asyncio.run(_drain(agent._run_async_impl(_user_ctx('hi'))))
1099+
1100+
assert agent.instruction == ''
1101+
assert 'system_instruction' not in client.aio.interactions.calls[0]
1102+
1103+
1104+
def test_string_instruction_forwarded_as_system_instruction():
1105+
client = _RecordingClient([[]])
1106+
agent = ManagedAgent(
1107+
name='mgr',
1108+
agent_id='agents/a',
1109+
instruction='You are a terse assistant.',
1110+
api_client=client,
1111+
)
1112+
1113+
asyncio.run(_drain(agent._run_async_impl(_user_ctx('hi'))))
1114+
1115+
assert (
1116+
client.aio.interactions.calls[0]['system_instruction']
1117+
== 'You are a terse assistant.'
1118+
)
1119+
1120+
1121+
def test_sync_instruction_provider_forwarded_and_bypasses_injection():
1122+
client = _RecordingClient([[]])
1123+
1124+
# The '{name}' must be left literal: providers bypass state injection.
1125+
agent = ManagedAgent(
1126+
name='mgr',
1127+
agent_id='agents/a',
1128+
instruction=lambda ctx: 'Persona for {name}',
1129+
api_client=client,
1130+
)
1131+
1132+
asyncio.run(_drain(agent._run_async_impl(_user_ctx('hi'))))
1133+
1134+
assert (
1135+
client.aio.interactions.calls[0]['system_instruction']
1136+
== 'Persona for {name}'
1137+
)
1138+
1139+
1140+
def test_async_instruction_provider_forwarded():
1141+
client = _RecordingClient([[]])
1142+
1143+
async def provider(ctx):
1144+
return 'Async persona.'
1145+
1146+
agent = ManagedAgent(
1147+
name='mgr',
1148+
agent_id='agents/a',
1149+
instruction=provider,
1150+
api_client=client,
1151+
)
1152+
1153+
asyncio.run(_drain(agent._run_async_impl(_user_ctx('hi'))))
1154+
1155+
assert (
1156+
client.aio.interactions.calls[0]['system_instruction'] == 'Async persona.'
1157+
)
1158+
1159+
1160+
def test_instruction_sent_on_chained_turn():
1161+
prior = Event(
1162+
author='mgr', interaction_id='int_prev', environment_id='env_prev'
1163+
)
1164+
client = _RecordingClient([[]])
1165+
agent = ManagedAgent(
1166+
name='mgr',
1167+
agent_id='agents/a',
1168+
instruction='Stay in character.',
1169+
api_client=client,
1170+
)
1171+
ctx = _user_ctx('again', session_events=[prior])
1172+
1173+
asyncio.run(_drain(agent._run_async_impl(ctx)))
1174+
1175+
create_kwargs = client.aio.interactions.calls[0]
1176+
assert create_kwargs['previous_interaction_id'] == 'int_prev'
1177+
assert create_kwargs['system_instruction'] == 'Stay in character.'
1178+
1179+
1180+
def test_string_instruction_injects_session_state():
1181+
from google.adk.agents.invocation_context import InvocationContext
1182+
from google.adk.sessions.in_memory_session_service import InMemorySessionService
1183+
from google.adk.sessions.session import Session
1184+
1185+
client = _RecordingClient([[]])
1186+
agent = ManagedAgent(
1187+
name='mgr',
1188+
agent_id='agents/a',
1189+
instruction='Discuss {topic}.',
1190+
api_client=client,
1191+
)
1192+
session = Session(
1193+
app_name='test', user_id='user', id='s1', state={'topic': 'volcanoes'}
1194+
)
1195+
ctx = InvocationContext(
1196+
invocation_id='inv1',
1197+
session=session,
1198+
session_service=InMemorySessionService(),
1199+
user_content=genai_types.Content(
1200+
role='user', parts=[genai_types.Part(text='hi')]
1201+
),
1202+
)
1203+
1204+
asyncio.run(_drain(agent._run_async_impl(ctx)))
1205+
1206+
assert (
1207+
client.aio.interactions.calls[0]['system_instruction']
1208+
== 'Discuss volcanoes.'
1209+
)

0 commit comments

Comments
 (0)