Skip to content

Commit 41660e5

Browse files
committed
fix: support instructions provider for agents
1 parent 97996a2 commit 41660e5

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed

typescript-sdk/integrations/adk-middleware/src/adk_middleware/adk_agent.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -701,18 +701,27 @@ async def _start_background_execution(
701701
if input.messages and isinstance(input.messages[0], SystemMessage):
702702
system_content = input.messages[0].content
703703
if system_content:
704-
# Get existing instruction (may be None or empty)
705704
current_instruction = getattr(adk_agent, 'instruction', '') or ''
706-
707-
# Append SystemMessage content to existing instructions
708-
if current_instruction:
709-
new_instruction = f"{current_instruction}\n\n{system_content}"
705+
706+
if callable(current_instruction):
707+
# Handle instructions provider
708+
async def instruction_provider_wrapper(*args, **kwargs):
709+
original_instructions = await current_instruction(*args, **kwargs)
710+
return f"{original_instructions}\n\n{system_content}"
711+
712+
new_instruction = instruction_provider_wrapper
713+
logger.debug(
714+
f"Will wrap callable InstructionProvider and append SystemMessage: '{system_content[:100]}...'")
710715
else:
711-
new_instruction = system_content
712-
716+
# Handle string instructions
717+
if current_instruction:
718+
new_instruction = f"{current_instruction}\n\n{system_content}"
719+
else:
720+
new_instruction = system_content
721+
logger.debug(f"Will append SystemMessage to string instructions: '{system_content[:100]}...'")
722+
713723
agent_updates['instruction'] = new_instruction
714-
logger.debug(f"Will append SystemMessage to agent instructions: '{system_content[:100]}...'")
715-
724+
716725
# Create dynamic toolset if tools provided and prepare tool updates
717726
toolset = None
718727
if input.tools:

typescript-sdk/integrations/adk-middleware/tests/test_adk_agent.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,67 @@ async def mock_run_background(input, adk_agent, user_id, app_name, event_queue):
239239
expected_instruction = "You are a helpful assistant.\n\nBe very concise in responses."
240240
assert captured_agent.instruction == expected_instruction
241241

242+
@pytest.mark.asyncio
243+
async def test_system_message_appended_to_instruction_provider(self):
244+
"""Test that SystemMessage as first message gets appended to agent instructions
245+
when they are set via instruction provider."""
246+
# Create an agent with initial instructions
247+
received_context = None
248+
249+
async def instruction_provider(context) -> str:
250+
nonlocal received_context
251+
received_context = context
252+
return "You are a helpful assistant."
253+
254+
mock_agent = Agent(
255+
name="test_agent",
256+
instruction=instruction_provider
257+
)
258+
259+
adk_agent = ADKAgent(adk_agent=mock_agent, app_name="test_app", user_id="test_user")
260+
261+
# Create input with SystemMessage as first message
262+
system_input = RunAgentInput(
263+
thread_id="test_thread",
264+
run_id="test_run",
265+
messages=[
266+
SystemMessage(id="sys_1", role="system", content="Be very concise in responses."),
267+
UserMessage(id="msg_1", role="user", content="Hello")
268+
],
269+
context=[],
270+
state={},
271+
tools=[],
272+
forwarded_props={}
273+
)
274+
275+
# Mock the background execution to capture the modified agent
276+
captured_agent = None
277+
original_run_background = adk_agent._run_adk_in_background
278+
279+
async def mock_run_background(input, adk_agent, user_id, app_name, event_queue):
280+
nonlocal captured_agent
281+
captured_agent = adk_agent
282+
# Just put a completion event in the queue and return
283+
await event_queue.put(None)
284+
285+
with patch.object(adk_agent, '_run_adk_in_background', side_effect=mock_run_background):
286+
# Start execution to trigger agent modification
287+
execution = await adk_agent._start_background_execution(system_input)
288+
289+
# Wait briefly for the background task to start
290+
await asyncio.sleep(0.01)
291+
292+
# Verify the agent's instruction was wrapped correctly
293+
assert captured_agent is not None
294+
assert callable(captured_agent.instruction) is True
295+
296+
# Test that the context object received in instruction provider is the same
297+
test_context = {"test": "value"}
298+
expected_instruction = "You are a helpful assistant.\n\nBe very concise in responses."
299+
agent_instruction = await captured_agent.instruction(test_context)
300+
assert agent_instruction == expected_instruction
301+
assert received_context is test_context
302+
242303
@pytest.mark.asyncio
243304
async def test_system_message_not_first_ignored(self):
244305
"""Test that SystemMessage not as first message is ignored."""

0 commit comments

Comments
 (0)