2626if TYPE_CHECKING :
2727 from typing import Any , List , Dict
2828 from pydantic_ai .usage import RequestUsage # type: ignore
29+ from pydantic_ai .messages import ModelMessage , SystemPromptPart # type: ignore
30+ from sentry_sdk ._types import TextPart as SentryTextPart
2931
3032try :
31- from pydantic_ai .messages import ( # type: ignore
33+ from pydantic_ai .messages import (
3234 BaseToolCallPart ,
3335 BaseToolReturnPart ,
3436 SystemPromptPart ,
4850 BinaryContent = None
4951
5052
53+ def _transform_system_instructions (
54+ permanent_instructions : "list[SystemPromptPart]" ,
55+ current_instructions : "list[str]" ,
56+ ) -> "list[SentryTextPart]" :
57+ text_parts : "list[SentryTextPart]" = [
58+ {
59+ "type" : "text" ,
60+ "content" : instruction .content ,
61+ }
62+ for instruction in permanent_instructions
63+ ]
64+
65+ text_parts .extend (
66+ {
67+ "type" : "text" ,
68+ "content" : instruction ,
69+ }
70+ for instruction in current_instructions
71+ )
72+
73+ return text_parts
74+
75+
76+ def _get_system_instructions (
77+ messages : "list[ModelMessage]" ,
78+ ) -> "tuple[list[SystemPromptPart], list[str]]" :
79+ permanent_instructions = []
80+ current_instructions = []
81+
82+ for msg in messages :
83+ if hasattr (msg , "parts" ):
84+ for part in msg .parts :
85+ if SystemPromptPart and isinstance (part , SystemPromptPart ):
86+ permanent_instructions .append (part )
87+
88+ if hasattr (msg , "instructions" ) and msg .instructions is not None :
89+ current_instructions .append (msg .instructions )
90+
91+ return permanent_instructions , current_instructions
92+
93+
5194def _set_input_messages (span : "sentry_sdk.tracing.Span" , messages : "Any" ) -> None :
5295 """Set input messages data on a span."""
5396 if not _should_send_prompts ():
@@ -56,29 +99,27 @@ def _set_input_messages(span: "sentry_sdk.tracing.Span", messages: "Any") -> Non
5699 if not messages :
57100 return
58101
102+ permanent_instructions , current_instructions = _get_system_instructions (messages )
103+ if len (permanent_instructions ) > 0 or len (current_instructions ) > 0 :
104+ set_data_normalized (
105+ span ,
106+ SPANDATA .GEN_AI_SYSTEM_INSTRUCTIONS ,
107+ _transform_system_instructions (
108+ permanent_instructions , current_instructions
109+ ),
110+ unpack = False ,
111+ )
112+
59113 try :
60114 formatted_messages = []
61- system_prompt = None
62-
63- # Extract system prompt from any ModelRequest with instructions
64- for msg in messages :
65- if hasattr (msg , "instructions" ) and msg .instructions :
66- system_prompt = msg .instructions
67- break
68-
69- # Add system prompt as first message if present
70- if system_prompt :
71- formatted_messages .append (
72- {"role" : "system" , "content" : [{"type" : "text" , "text" : system_prompt }]}
73- )
74115
75116 for msg in messages :
76117 if hasattr (msg , "parts" ):
77118 for part in msg .parts :
78119 role = "user"
79120 # Use isinstance checks with proper base classes
80121 if SystemPromptPart and isinstance (part , SystemPromptPart ):
81- role = "system"
122+ continue
82123 elif (
83124 (TextPart and isinstance (part , TextPart ))
84125 or (ThinkingPart and isinstance (part , ThinkingPart ))
0 commit comments