|
34 | 34 | from google.adk.agents.context import Context |
35 | 35 | from google.adk.agents.llm_agent import LlmAgent |
36 | 36 | from google.adk.apps.app import App |
| 37 | +from google.adk.apps.app import ResumabilityConfig |
37 | 38 | from google.adk.events.event import Event |
| 39 | +from google.adk.flows.llm_flows.functions import REQUEST_CONFIRMATION_FUNCTION_CALL_NAME |
| 40 | +from google.adk.tools.function_tool import FunctionTool |
| 41 | +from google.adk.tools.tool_context import ToolContext |
38 | 42 | from google.adk.workflow import node |
39 | 43 | from google.adk.workflow import START |
40 | 44 | from google.adk.workflow._base_node import BaseNode |
@@ -66,6 +70,11 @@ def _text_part(text: str) -> types.Part: |
66 | 70 | return types.Part.from_text(text=text) |
67 | 71 |
|
68 | 72 |
|
| 73 | +def _confirmed_task_step(tool_context: ToolContext) -> dict[str, bool]: |
| 74 | + """Return whether the resumable task step was confirmed.""" |
| 75 | + return {'confirmed': tool_context.tool_confirmation.confirmed} |
| 76 | + |
| 77 | + |
69 | 78 | def _make_task_agent( |
70 | 79 | name: str, |
71 | 80 | responses: list, |
@@ -450,7 +459,83 @@ async def test_chat_coordinator_resumes_unresolved_task_fc( |
450 | 459 |
|
451 | 460 |
|
452 | 461 | # --------------------------------------------------------------------------- |
453 | | -# 9. Strict isolation filtering: a stranger event with a foreign |
| 462 | +# 9. Resumable task delegation: a task sub-agent that pauses for tool |
| 463 | +# confirmation resumes without executing its parent's delegation FC. |
| 464 | +# --------------------------------------------------------------------------- |
| 465 | + |
| 466 | + |
| 467 | +@pytest.mark.asyncio |
| 468 | +async def test_task_sub_agent_resumes_without_parent_delegation_fc( |
| 469 | + request: pytest.FixtureRequest, |
| 470 | +): |
| 471 | + """A resumed task child does not execute its parent's delegation call.""" |
| 472 | + confirmation_tool = FunctionTool( |
| 473 | + func=_confirmed_task_step, |
| 474 | + require_confirmation=True, |
| 475 | + ) |
| 476 | + child = _make_task_agent( |
| 477 | + name='child', |
| 478 | + responses=[ |
| 479 | + types.Part.from_function_call( |
| 480 | + name=confirmation_tool.name, |
| 481 | + args={}, |
| 482 | + ), |
| 483 | + _finish_part({'result': 'confirmed'}), |
| 484 | + ], |
| 485 | + ) |
| 486 | + child.tools.append(confirmation_tool) |
| 487 | + |
| 488 | + root = LlmAgent( |
| 489 | + name='root', |
| 490 | + model=testing_utils.MockModel.create( |
| 491 | + responses=[ |
| 492 | + _delegate_part('child', 'perform a confirmed step'), |
| 493 | + 'Task confirmed.', |
| 494 | + ] |
| 495 | + ), |
| 496 | + sub_agents=[child], |
| 497 | + ) |
| 498 | + app = App( |
| 499 | + name=request.function.__name__, |
| 500 | + root_agent=root, |
| 501 | + resumability_config=ResumabilityConfig(is_resumable=True), |
| 502 | + ) |
| 503 | + runner = testing_utils.InMemoryRunner(app=app) |
| 504 | + |
| 505 | + first_events = await runner.run_async(testing_utils.get_user_content('start')) |
| 506 | + confirmation_fc = next( |
| 507 | + fc |
| 508 | + for event in first_events |
| 509 | + for fc in event.get_function_calls() |
| 510 | + if fc.name == REQUEST_CONFIRMATION_FUNCTION_CALL_NAME |
| 511 | + ) |
| 512 | + invocation_id = next( |
| 513 | + event.invocation_id |
| 514 | + for event in first_events |
| 515 | + if confirmation_fc in event.get_function_calls() |
| 516 | + ) |
| 517 | + |
| 518 | + resumed_events = await runner.run_async( |
| 519 | + testing_utils.UserContent( |
| 520 | + types.Part( |
| 521 | + function_response=types.FunctionResponse( |
| 522 | + id=confirmation_fc.id, |
| 523 | + name=REQUEST_CONFIRMATION_FUNCTION_CALL_NAME, |
| 524 | + response={'confirmed': True}, |
| 525 | + ) |
| 526 | + ) |
| 527 | + ), |
| 528 | + invocation_id=invocation_id, |
| 529 | + ) |
| 530 | + |
| 531 | + assert {'result': 'confirmed'} in _collect_finish_outputs(resumed_events) |
| 532 | + assert any( |
| 533 | + 'Task confirmed.' in text for text in _get_text_responses(resumed_events) |
| 534 | + ) |
| 535 | + |
| 536 | + |
| 537 | +# --------------------------------------------------------------------------- |
| 538 | +# 10. Strict isolation filtering: a stranger event with a foreign |
454 | 539 | # isolation_scope must NOT appear in the task agent's LLM context. |
455 | 540 | # --------------------------------------------------------------------------- |
456 | 541 |
|
|
0 commit comments