@@ -107,6 +107,9 @@ class Agent(Generic[AgentDeps, ResultData]):
107
107
_function_tools : dict [str , Tool [AgentDeps ]] = dataclasses .field (repr = False )
108
108
_default_retries : int = dataclasses .field (repr = False )
109
109
_system_prompt_functions : list [_system_prompt .SystemPromptRunner [AgentDeps ]] = dataclasses .field (repr = False )
110
+ _system_prompt_dynamic_functions : dict [str , _system_prompt .SystemPromptRunner [AgentDeps ]] = dataclasses .field (
111
+ repr = False
112
+ )
110
113
_deps_type : type [AgentDeps ] = dataclasses .field (repr = False )
111
114
_max_result_retries : int = dataclasses .field (repr = False )
112
115
_override_deps : _utils .Option [AgentDeps ] = dataclasses .field (default = None , repr = False )
@@ -182,6 +185,7 @@ def __init__(
182
185
self ._register_tool (Tool (tool ))
183
186
self ._deps_type = deps_type
184
187
self ._system_prompt_functions = []
188
+ self ._system_prompt_dynamic_functions = {}
185
189
self ._max_result_retries = result_retries if result_retries is not None else retries
186
190
self ._result_validators = []
187
191
@@ -535,17 +539,37 @@ def system_prompt(self, func: Callable[[], str], /) -> Callable[[], str]: ...
535
539
@overload
536
540
def system_prompt (self , func : Callable [[], Awaitable [str ]], / ) -> Callable [[], Awaitable [str ]]: ...
537
541
542
+ @overload
543
+ def system_prompt (
544
+ self , / , * , dynamic : bool = False
545
+ ) -> Callable [[_system_prompt .SystemPromptFunc [AgentDeps ]], _system_prompt .SystemPromptFunc [AgentDeps ]]: ...
546
+
538
547
def system_prompt (
539
- self , func : _system_prompt .SystemPromptFunc [AgentDeps ], /
540
- ) -> _system_prompt .SystemPromptFunc [AgentDeps ]:
548
+ self ,
549
+ func : _system_prompt .SystemPromptFunc [AgentDeps ] | None = None ,
550
+ / ,
551
+ * ,
552
+ dynamic : bool = False ,
553
+ ) -> (
554
+ Callable [[_system_prompt .SystemPromptFunc [AgentDeps ]], _system_prompt .SystemPromptFunc [AgentDeps ]]
555
+ | _system_prompt .SystemPromptFunc [AgentDeps ]
556
+ ):
541
557
"""Decorator to register a system prompt function.
542
558
543
559
Optionally takes [`RunContext`][pydantic_ai.tools.RunContext] as its only argument.
544
560
Can decorate a sync or async functions.
545
561
562
+ The decorator can be used either bare (`agent.system_prompt`) or as a function call
563
+ (`agent.system_prompt(...)`), see the examples below.
564
+
546
565
Overloads for every possible signature of `system_prompt` are included so the decorator doesn't obscure
547
566
the type of the function, see `tests/typed_agent.py` for tests.
548
567
568
+ Args:
569
+ func: The function to decorate
570
+ dynamic: If True, the system prompt will be reevaluated even when `messages_history` is provided,
571
+ see [`SystemPromptPart.dynamic_ref`][pydantic_ai.messages.SystemPromptPart.dynamic_ref]
572
+
549
573
Example:
550
574
```python
551
575
from pydantic_ai import Agent, RunContext
@@ -556,17 +580,27 @@ def system_prompt(
556
580
def simple_system_prompt() -> str:
557
581
return 'foobar'
558
582
559
- @agent.system_prompt
583
+ @agent.system_prompt(dynamic=True)
560
584
async def async_system_prompt(ctx: RunContext[str]) -> str:
561
585
return f'{ctx.deps} is the best'
562
-
563
- result = agent.run_sync('foobar', deps='spam')
564
- print(result.data)
565
- #> success (no tool calls)
566
586
```
567
587
"""
568
- self ._system_prompt_functions .append (_system_prompt .SystemPromptRunner (func ))
569
- return func
588
+ if func is None :
589
+
590
+ def decorator (
591
+ func_ : _system_prompt .SystemPromptFunc [AgentDeps ],
592
+ ) -> _system_prompt .SystemPromptFunc [AgentDeps ]:
593
+ runner = _system_prompt .SystemPromptRunner (func_ , dynamic = dynamic )
594
+ self ._system_prompt_functions .append (runner )
595
+ if dynamic :
596
+ self ._system_prompt_dynamic_functions [func_ .__qualname__ ] = runner
597
+ return func_
598
+
599
+ return decorator
600
+ else :
601
+ assert not dynamic , "dynamic can't be True in this case"
602
+ self ._system_prompt_functions .append (_system_prompt .SystemPromptRunner (func , dynamic = dynamic ))
603
+ return func
570
604
571
605
@overload
572
606
def result_validator (
@@ -835,6 +869,23 @@ async def add_tool(tool: Tool[AgentDeps]) -> None:
835
869
result_tools = self ._result_schema .tool_defs () if self ._result_schema is not None else [],
836
870
)
837
871
872
+ async def _reevaluate_dynamic_prompts (
873
+ self , messages : list [_messages .ModelMessage ], run_context : RunContext [AgentDeps ]
874
+ ) -> None :
875
+ """Reevaluate any `SystemPromptPart` with dynamic_ref in the provided messages by running the associated runner function."""
876
+ # Only proceed if there's at least one dynamic runner.
877
+ if self ._system_prompt_dynamic_functions :
878
+ for msg in messages :
879
+ if isinstance (msg , _messages .ModelRequest ):
880
+ for i , part in enumerate (msg .parts ):
881
+ if isinstance (part , _messages .SystemPromptPart ) and part .dynamic_ref :
882
+ # Look up the runner by its ref
883
+ if runner := self ._system_prompt_dynamic_functions .get (part .dynamic_ref ):
884
+ updated_part_content = await runner .run (run_context )
885
+ msg .parts [i ] = _messages .SystemPromptPart (
886
+ updated_part_content , dynamic_ref = part .dynamic_ref
887
+ )
888
+
838
889
async def _prepare_messages (
839
890
self , user_prompt : str , message_history : list [_messages .ModelMessage ] | None , run_context : RunContext [AgentDeps ]
840
891
) -> list [_messages .ModelMessage ]:
@@ -850,8 +901,10 @@ async def _prepare_messages(
850
901
ctx_messages .used = True
851
902
852
903
if message_history :
853
- # shallow copy messages
904
+ # Shallow copy messages
854
905
messages .extend (message_history )
906
+ # Reevaluate any dynamic system prompt parts
907
+ await self ._reevaluate_dynamic_prompts (messages , run_context )
855
908
messages .append (_messages .ModelRequest ([_messages .UserPromptPart (user_prompt )]))
856
909
else :
857
910
parts = await self ._sys_parts (run_context )
@@ -1088,7 +1141,10 @@ async def _sys_parts(self, run_context: RunContext[AgentDeps]) -> list[_messages
1088
1141
messages : list [_messages .ModelRequestPart ] = [_messages .SystemPromptPart (p ) for p in self ._system_prompts ]
1089
1142
for sys_prompt_runner in self ._system_prompt_functions :
1090
1143
prompt = await sys_prompt_runner .run (run_context )
1091
- messages .append (_messages .SystemPromptPart (prompt ))
1144
+ if sys_prompt_runner .dynamic :
1145
+ messages .append (_messages .SystemPromptPart (prompt , dynamic_ref = sys_prompt_runner .function .__qualname__ ))
1146
+ else :
1147
+ messages .append (_messages .SystemPromptPart (prompt ))
1092
1148
return messages
1093
1149
1094
1150
def _unknown_tool (self , tool_name : str , run_context : RunContext [AgentDeps ]) -> _messages .RetryPromptPart :
0 commit comments