@@ -685,15 +685,21 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
685685 finally :
686686 try :
687687 if instrumentation_settings and run_span .is_recording ():
688- run_span .set_attributes (self ._run_span_end_attributes (state , usage , instrumentation_settings ))
688+ run_span .set_attributes (
689+ self ._run_span_end_attributes (
690+ state , usage , instrumentation_settings , graph_deps .new_message_index
691+ )
692+ )
689693 finally :
690694 run_span .end ()
691695
692696 def _run_span_end_attributes (
693- self , state : _agent_graph .GraphAgentState , usage : _usage .RunUsage , settings : InstrumentationSettings
697+ self ,
698+ state : _agent_graph .GraphAgentState ,
699+ usage : _usage .RunUsage ,
700+ settings : InstrumentationSettings ,
701+ new_message_index : int ,
694702 ):
695- literal_instructions , _ = self ._get_instructions ()
696-
697703 if settings .version == 1 :
698704 attrs = {
699705 'all_messages_events' : json .dumps (
@@ -704,19 +710,32 @@ def _run_span_end_attributes(
704710 )
705711 }
706712 else :
707- attrs = {
713+ # Store the last instructions here for convenience
714+ last_instructions = InstrumentedModel ._get_instructions (state .message_history ) # pyright: ignore[reportPrivateUsage]
715+ attrs : dict [str , Any ] = {
708716 'pydantic_ai.all_messages' : json .dumps (settings .messages_to_otel_messages (list (state .message_history ))),
709- ** settings .system_instructions_attributes (literal_instructions ),
717+ ** settings .system_instructions_attributes (last_instructions ),
710718 }
711719
720+ # Store an attribute that indicates that the instructions from this agent run were not always the same
721+ # This can signal to an observability UI that different steps in the agent run had different instructions
722+ # Note: We purposely only look at "new" messages because they are the only ones produced by this agent run.
723+ for m in state .message_history [new_message_index :]:
724+ if (
725+ isinstance (m , _messages .ModelRequest )
726+ and m .instructions is not None
727+ and m .instructions != last_instructions
728+ ):
729+ attrs ['pydantic_ai.variable_instructions' ] = True
730+
712731 return {
713732 ** usage .opentelemetry_attributes (),
714733 ** attrs ,
715734 'logfire.json_schema' : json .dumps (
716735 {
717736 'type' : 'object' ,
718737 'properties' : {
719- ** {attr : {'type' : 'array' } for attr in attrs .keys ()},
738+ ** {k : {'type' : 'array' } if isinstance ( v , str ) else {} for k , v in attrs .items ()},
720739 'final_result' : {'type' : 'object' },
721740 },
722741 }
0 commit comments