@@ -685,38 +685,61 @@ 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+ instrumentation_settings , usage , state .message_history , 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+ settings : InstrumentationSettings ,
699+ usage : _usage .RunUsage ,
700+ message_history : list [_messages .ModelMessage ],
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 (
700- [
701- InstrumentedModel .event_to_dict (e )
702- for e in settings .messages_to_otel_events (state .message_history )
703- ]
706+ [InstrumentedModel .event_to_dict (e ) for e in settings .messages_to_otel_events (message_history )]
704707 )
705708 }
706709 else :
707- attrs = {
708- 'pydantic_ai.all_messages' : json .dumps (settings .messages_to_otel_messages (list (state .message_history ))),
709- ** settings .system_instructions_attributes (literal_instructions ),
710+ # Store the last instructions here for convenience
711+ last_instructions = InstrumentedModel ._get_instructions (message_history ) # pyright: ignore[reportPrivateUsage]
712+ attrs : dict [str , Any ] = {
713+ 'pydantic_ai.all_messages' : json .dumps (settings .messages_to_otel_messages (list (message_history ))),
714+ ** settings .system_instructions_attributes (last_instructions ),
710715 }
711716
717+ # If this agent run was provided with existing history, store an attribute indicating the point at which the
718+ # new messages begin.
719+ if new_message_index > 0 :
720+ attrs ['pydantic_ai.new_message_index' ] = new_message_index
721+
722+ # If the instructions for this agent run were not always the same, store an attribute that indicates that.
723+ # This can signal to an observability UI that different steps in the agent run had different instructions.
724+ # Note: We purposely only look at "new" messages because they are the only ones produced by this agent run.
725+ if any (
726+ (
727+ isinstance (m , _messages .ModelRequest )
728+ and m .instructions is not None
729+ and m .instructions != last_instructions
730+ )
731+ for m in message_history [new_message_index :]
732+ ):
733+ attrs ['pydantic_ai.variable_instructions' ] = True
734+
712735 return {
713736 ** usage .opentelemetry_attributes (),
714737 ** attrs ,
715738 'logfire.json_schema' : json .dumps (
716739 {
717740 'type' : 'object' ,
718741 'properties' : {
719- ** {attr : {'type' : 'array' } for attr in attrs .keys ()},
742+ ** {k : {'type' : 'array' } if isinstance ( v , str ) else {} for k , v in attrs .items ()},
720743 'final_result' : {'type' : 'object' },
721744 },
722745 }
0 commit comments