2
2
3
3
import re
4
4
from dataclasses import dataclass , replace
5
- from typing import TypeVar
5
+ from pathlib import Path
6
+ from typing import Literal , TypeVar
6
7
8
+ from pydantic_ai .agent import Agent
7
9
import pytest
8
10
from inline_snapshot import snapshot
9
11
13
15
from pydantic_ai .messages import ToolCallPart
14
16
from pydantic_ai .models .test import TestModel
15
17
from pydantic_ai .tools import ToolDefinition
18
+ from pydantic_ai .toolsets .abstract import AbstractToolset
16
19
from pydantic_ai .toolsets .combined import CombinedToolset
20
+ from pydantic_ai .toolsets .dynamic import DynamicToolset
17
21
from pydantic_ai .toolsets .filtered import FilteredToolset
18
22
from pydantic_ai .toolsets .function import FunctionToolset
19
23
from pydantic_ai .toolsets .prefixed import PrefixedToolset
@@ -469,3 +473,71 @@ async def test_context_manager():
469
473
async with toolset :
470
474
assert server1 .is_running
471
475
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