22
33import re
44from dataclasses import dataclass , replace
5- from typing import TypeVar
5+ from pathlib import Path
6+ from typing import Literal , TypeVar
67
8+ from pydantic_ai .agent import Agent
79import pytest
810from inline_snapshot import snapshot
911
1315from pydantic_ai .messages import ToolCallPart
1416from pydantic_ai .models .test import TestModel
1517from pydantic_ai .tools import ToolDefinition
18+ from pydantic_ai .toolsets .abstract import AbstractToolset
1619from pydantic_ai .toolsets .combined import CombinedToolset
20+ from pydantic_ai .toolsets .dynamic import DynamicToolset
1721from pydantic_ai .toolsets .filtered import FilteredToolset
1822from pydantic_ai .toolsets .function import FunctionToolset
1923from pydantic_ai .toolsets .prefixed import PrefixedToolset
@@ -469,3 +473,71 @@ async def test_context_manager():
469473 async with toolset :
470474 assert server1 .is_running
471475 assert server2 .is_running
476+
477+
478+ async def test_dynamic_toolset ():
479+ run_context = build_run_context (Path ())
480+
481+ def test_function (ctx : RunContext [Path ]) -> Literal ['nothing' ]:
482+ return 'nothing'
483+
484+ function_toolset = FunctionToolset [Path ]()
485+ function_toolset .add_function (test_function )
486+
487+ async def prepare_toolset (ctx : RunContext [Path ]) -> AbstractToolset [Path ]:
488+ return function_toolset
489+
490+ dynamic_toolset : DynamicToolset [Path ] = DynamicToolset [Path ](build_toolset_fn = prepare_toolset )
491+
492+ # The toolset is unique per context manager
493+ async with dynamic_toolset :
494+
495+ # The toolset starts empty
496+ assert dynamic_toolset ._dynamic_toolset .get ().toolsets == []
497+
498+ # The toolset is built dynamically on the first call to get_tools within the context
499+ _ = await dynamic_toolset .get_tools (run_context )
500+ assert dynamic_toolset ._dynamic_toolset .get ().toolsets == [function_toolset ]
501+
502+ # Any time the context is entered again, the toolsets are reset, to be generated again
503+ async with dynamic_toolset :
504+ assert dynamic_toolset ._dynamic_toolset .get ().toolsets == []
505+
506+ assert dynamic_toolset ._dynamic_toolset .get ().toolsets == [function_toolset ]
507+
508+ async with dynamic_toolset :
509+ assert dynamic_toolset ._dynamic_toolset .get ().toolsets == []
510+
511+ async def test_dynamic_toolset_with_agent ():
512+ run_context = build_run_context (Path ())
513+
514+ def test_function (ctx : RunContext [Path ]) -> Literal ['nothing' ]:
515+ return 'nothing'
516+
517+
518+ def test_function_two (ctx : RunContext [Path ]) -> Literal ['nothing' ]:
519+ return 'nothing'
520+
521+
522+ function_toolset = FunctionToolset [Path ]()
523+ function_toolset .add_function (test_function )
524+ function_toolset .add_function (test_function_two )
525+
526+ async def prepare_toolset (ctx : RunContext [Path ]) -> AbstractToolset [Path ]:
527+ return function_toolset
528+
529+ dynamic_toolset : DynamicToolset [Path ] = DynamicToolset [Path ](build_toolset_fn = prepare_toolset )
530+
531+ agent = Agent [Path , str ](
532+ model = TestModel (),
533+ toolsets = [dynamic_toolset ],
534+ deps_type = Path ,
535+ output_type = str ,
536+ )
537+
538+ async with agent :
539+ result = await agent .run (deps = Path ("." ), user_prompt = "Please call each tool you have access to and tell me what it returns" )
540+ print (result .output )
541+
542+ result = await agent .run (deps = Path ("./tomato" ), user_prompt = "Please call each tool you have access to and tell me what it returns." )
543+ print (result .output )
0 commit comments