3232 HistoryProcessor ,
3333 ModelRequestNode ,
3434 UserPromptNode ,
35+ build_run_context ,
3536 capture_run_messages ,
3637)
3738from .._output import OutputToolset
8990S = TypeVar ('S' )
9091NoneType = type (None )
9192
93+ AgentMetadataValue = str | dict [str , str ] | Callable [[RunContext [AgentDepsT ]], str | dict [str , str ]]
94+
9295
9396@dataclasses .dataclass (init = False )
9497class Agent (AbstractAgent [AgentDepsT , OutputDataT ]):
@@ -130,6 +133,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
130133 """Options to automatically instrument with OpenTelemetry."""
131134
132135 _instrument_default : ClassVar [InstrumentationSettings | bool ] = False
136+ _metadata : AgentMetadataValue [AgentDepsT ] | None = dataclasses .field (repr = False )
133137
134138 _deps_type : type [AgentDepsT ] = dataclasses .field (repr = False )
135139 _output_schema : _output .OutputSchema [OutputDataT ] = dataclasses .field (repr = False )
@@ -175,6 +179,7 @@ def __init__(
175179 defer_model_check : bool = False ,
176180 end_strategy : EndStrategy = 'early' ,
177181 instrument : InstrumentationSettings | bool | None = None ,
182+ metadata : AgentMetadataValue [AgentDepsT ] | None = None ,
178183 history_processors : Sequence [HistoryProcessor [AgentDepsT ]] | None = None ,
179184 event_stream_handler : EventStreamHandler [AgentDepsT ] | None = None ,
180185 ) -> None : ...
@@ -201,6 +206,7 @@ def __init__(
201206 defer_model_check : bool = False ,
202207 end_strategy : EndStrategy = 'early' ,
203208 instrument : InstrumentationSettings | bool | None = None ,
209+ metadata : AgentMetadataValue [AgentDepsT ] | None = None ,
204210 history_processors : Sequence [HistoryProcessor [AgentDepsT ]] | None = None ,
205211 event_stream_handler : EventStreamHandler [AgentDepsT ] | None = None ,
206212 ) -> None : ...
@@ -225,6 +231,7 @@ def __init__(
225231 defer_model_check : bool = False ,
226232 end_strategy : EndStrategy = 'early' ,
227233 instrument : InstrumentationSettings | bool | None = None ,
234+ metadata : AgentMetadataValue [AgentDepsT ] | None = None ,
228235 history_processors : Sequence [HistoryProcessor [AgentDepsT ]] | None = None ,
229236 event_stream_handler : EventStreamHandler [AgentDepsT ] | None = None ,
230237 ** _deprecated_kwargs : Any ,
@@ -276,6 +283,10 @@ def __init__(
276283 [`Agent.instrument_all()`][pydantic_ai.Agent.instrument_all]
277284 will be used, which defaults to False.
278285 See the [Debugging and Monitoring guide](https://ai.pydantic.dev/logfire/) for more info.
286+ metadata: Optional metadata to attach to telemetry for this agent.
287+ Provide a string literal, a dict of string keys and values, or a callable returning one of those values
288+ computed from the [`RunContext`][pydantic_ai.tools.RunContext] on each run.
289+ Metadata is only recorded when instrumentation is enabled.
279290 history_processors: Optional list of callables to process the message history before sending it to the model.
280291 Each processor takes a list of messages and returns a modified list of messages.
281292 Processors can be sync or async and are applied in sequence.
@@ -292,6 +303,7 @@ def __init__(
292303
293304 self ._output_type = output_type
294305 self .instrument = instrument
306+ self ._metadata = metadata
295307 self ._deps_type = deps_type
296308
297309 if mcp_servers := _deprecated_kwargs .pop ('mcp_servers' , None ):
@@ -349,6 +361,9 @@ def __init__(
349361 self ._override_instructions : ContextVar [
350362 _utils .Option [list [str | _system_prompt .SystemPromptFunc [AgentDepsT ]]]
351363 ] = ContextVar ('_override_instructions' , default = None )
364+ self ._override_metadata : ContextVar [_utils .Option [AgentMetadataValue [AgentDepsT ]]] = ContextVar (
365+ '_override_metadata' , default = None
366+ )
352367
353368 self ._enter_lock = Lock ()
354369 self ._entered_count = 0
@@ -645,6 +660,7 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
645660 },
646661 )
647662
663+ run_metadata : str | dict [str , str ] | None = None
648664 try :
649665 async with graph .iter (
650666 inputs = user_prompt_node ,
@@ -656,8 +672,9 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
656672 async with toolset :
657673 agent_run = AgentRun (graph_run )
658674 yield agent_run
659- if (final_result := agent_run .result ) is not None and run_span .is_recording ():
660- if instrumentation_settings and instrumentation_settings .include_content :
675+ if instrumentation_settings and run_span .is_recording ():
676+ run_metadata = self ._compute_agent_metadata (build_run_context (agent_run .ctx ))
677+ if instrumentation_settings .include_content and (final_result := agent_run .result ) is not None :
661678 run_span .set_attribute (
662679 'final_result' ,
663680 (
@@ -671,18 +688,32 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
671688 if instrumentation_settings and run_span .is_recording ():
672689 run_span .set_attributes (
673690 self ._run_span_end_attributes (
674- instrumentation_settings , usage , state .message_history , graph_deps .new_message_index
691+ instrumentation_settings ,
692+ usage ,
693+ state .message_history ,
694+ graph_deps .new_message_index ,
695+ run_metadata ,
675696 )
676697 )
677698 finally :
678699 run_span .end ()
679700
701+ def _compute_agent_metadata (self , ctx : RunContext [AgentDepsT ]) -> str | dict [str , str ] | None :
702+ metadata_override = self ._override_metadata .get ()
703+ metadata_config = metadata_override .value if metadata_override is not None else self ._metadata
704+ if metadata_config is None :
705+ return None
706+
707+ metadata = metadata_config (ctx ) if callable (metadata_config ) else metadata_config
708+ return metadata
709+
680710 def _run_span_end_attributes (
681711 self ,
682712 settings : InstrumentationSettings ,
683713 usage : _usage .RunUsage ,
684714 message_history : list [_messages .ModelMessage ],
685715 new_message_index : int ,
716+ metadata : str | dict [str , str ] | None = None ,
686717 ):
687718 if settings .version == 1 :
688719 attrs = {
@@ -716,6 +747,12 @@ def _run_span_end_attributes(
716747 ):
717748 attrs ['pydantic_ai.variable_instructions' ] = True
718749
750+ if metadata is not None :
751+ if isinstance (metadata , dict ):
752+ attrs ['logfire.agent.metadata' ] = json .dumps (metadata )
753+ else :
754+ attrs ['logfire.agent.metadata' ] = metadata
755+
719756 return {
720757 ** usage .opentelemetry_attributes (),
721758 ** attrs ,
@@ -740,6 +777,7 @@ def override(
740777 toolsets : Sequence [AbstractToolset [AgentDepsT ]] | _utils .Unset = _utils .UNSET ,
741778 tools : Sequence [Tool [AgentDepsT ] | ToolFuncEither [AgentDepsT , ...]] | _utils .Unset = _utils .UNSET ,
742779 instructions : Instructions [AgentDepsT ] | _utils .Unset = _utils .UNSET ,
780+ metadata : AgentMetadataValue [AgentDepsT ] | _utils .Unset = _utils .UNSET ,
743781 ) -> Iterator [None ]:
744782 """Context manager to temporarily override agent name, dependencies, model, toolsets, tools, or instructions.
745783
@@ -753,6 +791,7 @@ def override(
753791 toolsets: The toolsets to use instead of the toolsets passed to the agent constructor and agent run.
754792 tools: The tools to use instead of the tools registered with the agent.
755793 instructions: The instructions to use instead of the instructions registered with the agent.
794+ metadata: The metadata to use instead of the metadata passed to the agent constructor.
756795 """
757796 if _utils .is_set (name ):
758797 name_token = self ._override_name .set (_utils .Some (name ))
@@ -785,6 +824,11 @@ def override(
785824 else :
786825 instructions_token = None
787826
827+ if _utils .is_set (metadata ):
828+ metadata_token = self ._override_metadata .set (_utils .Some (metadata ))
829+ else :
830+ metadata_token = None
831+
788832 try :
789833 yield
790834 finally :
@@ -800,6 +844,8 @@ def override(
800844 self ._override_tools .reset (tools_token )
801845 if instructions_token is not None :
802846 self ._override_instructions .reset (instructions_token )
847+ if metadata_token is not None :
848+ self ._override_metadata .reset (metadata_token )
803849
804850 @overload
805851 def instructions (
0 commit comments