|
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 |
|
| 15 | +import asyncio |
15 | 16 | from typing import Any |
16 | 17 | from typing import Optional |
17 | 18 |
|
|
30 | 31 | from google.adk.models.llm_response import LlmResponse |
31 | 32 | from google.adk.plugins.base_plugin import BasePlugin |
32 | 33 | from google.adk.plugins.plugin_manager import PluginManager |
| 34 | +from google.adk.runners import Runner |
33 | 35 | from google.adk.sessions.in_memory_session_service import InMemorySessionService |
34 | 36 | from google.adk.tools.agent_tool import AgentTool |
35 | 37 | from google.adk.tools.tool_context import ToolContext |
@@ -727,6 +729,61 @@ async def before_agent_callback(self, **kwargs): |
727 | 729 | assert tracking_plugin.before_agent_calls == 1 |
728 | 730 |
|
729 | 731 |
|
| 732 | +@pytest.mark.asyncio |
| 733 | +async def test_include_plugins_true_sub_runner_does_not_close_parent_plugins(): |
| 734 | + """Sub-Runner must not close plugins owned by the parent runner.""" |
| 735 | + |
| 736 | + class SlowClosePlugin(BasePlugin): |
| 737 | + |
| 738 | + def __init__(self, name: str): |
| 739 | + super().__init__(name) |
| 740 | + self.close_calls = 0 |
| 741 | + |
| 742 | + async def close(self): |
| 743 | + self.close_calls += 1 |
| 744 | + # Would otherwise blow past the sub-Runner's plugin_close_timeout. |
| 745 | + await asyncio.sleep(10) |
| 746 | + |
| 747 | + parent_plugin = SlowClosePlugin(name='parent_plugin') |
| 748 | + |
| 749 | + mock_model = testing_utils.MockModel.create( |
| 750 | + responses=[function_call_no_schema, 'response1', 'response2'] |
| 751 | + ) |
| 752 | + |
| 753 | + tool_agent = Agent(name='tool_agent', model=mock_model) |
| 754 | + root_agent = Agent( |
| 755 | + name='root_agent', |
| 756 | + model=mock_model, |
| 757 | + tools=[AgentTool(agent=tool_agent, include_plugins=True)], |
| 758 | + ) |
| 759 | + |
| 760 | + runner = Runner( |
| 761 | + app_name='test_app', |
| 762 | + agent=root_agent, |
| 763 | + artifact_service=InMemoryArtifactService(), |
| 764 | + session_service=InMemorySessionService(), |
| 765 | + memory_service=InMemoryMemoryService(), |
| 766 | + plugins=[parent_plugin], |
| 767 | + # Tight timeout amplifies the bug if it regresses; with the fix, the |
| 768 | + # sub-Runner's close skips the parent's plugins entirely. |
| 769 | + plugin_close_timeout=0.01, |
| 770 | + ) |
| 771 | + session = await runner.session_service.create_session( |
| 772 | + app_name='test_app', user_id='test_user' |
| 773 | + ) |
| 774 | + # Must not raise RuntimeError("Failed to close plugins: ...") from the |
| 775 | + # sub-Runner closing the parent's slow-to-close plugin. |
| 776 | + async for _ in runner.run_async( |
| 777 | + user_id=session.user_id, |
| 778 | + session_id=session.id, |
| 779 | + new_message=testing_utils.get_user_content('test1'), |
| 780 | + ): |
| 781 | + pass |
| 782 | + |
| 783 | + # The sub-Runner must not have closed the parent's plugin. |
| 784 | + assert parent_plugin.close_calls == 0 |
| 785 | + |
| 786 | + |
730 | 787 | def test_agent_tool_description_with_input_schema(): |
731 | 788 | """Test that agent description is propagated when using input_schema.""" |
732 | 789 |
|
|
0 commit comments