Skip to content

Commit 2631d9b

Browse files
authored
Record instructions on the agent run span even when they are dynamic (#3131)
1 parent f3f40fe commit 2631d9b

File tree

2 files changed

+807
-13
lines changed

2 files changed

+807
-13
lines changed

pydantic_ai_slim/pydantic_ai/agent/__init__.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)