|
4 | 4 | if TYPE_CHECKING:
|
5 | 5 | from typing import Any, Dict, List, Optional
|
6 | 6 |
|
7 |
| -try: |
8 |
| - from sentry_sdk.serializer import serialize |
9 |
| -except ImportError: |
10 |
| - # Fallback for cases where sentry_sdk isn't fully importable |
11 |
| - def serialize(obj, **kwargs): |
12 |
| - # type: (Any, **Any) -> Any |
13 |
| - return obj |
| 7 | +from sentry_sdk.serializer import serialize |
| 8 | +from sentry_sdk._types import AnnotatedValue |
14 | 9 |
|
15 |
| - |
16 |
| -# Custom limit for gen_ai message serialization - 50% of MAX_EVENT_BYTES |
17 |
| -# to leave room for other event data while still being generous for messages |
18 | 10 | MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
|
19 | 11 |
|
20 | 12 |
|
@@ -50,21 +42,26 @@ def truncate_messages_by_size(messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES):
|
50 | 42 |
|
51 | 43 |
|
52 | 44 | def serialize_gen_ai_messages(messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES):
|
53 |
| - # type: (Optional[List[Dict[str, Any]]], int) -> Optional[str] |
| 45 | + # type: (Optional[Any], int) -> Optional[str] |
54 | 46 | """
|
55 | 47 | Serialize and truncate gen_ai messages for storage in spans.
|
56 | 48 |
|
57 | 49 | This function handles the complete workflow of:
|
58 |
| - 1. Truncating messages to fit within size limits |
59 |
| - 2. Serializing them using Sentry's serializer |
| 50 | + 1. Truncating messages to fit within size limits (if not already done) |
| 51 | + 2. Serializing them using Sentry's serializer (which processes AnnotatedValue for _meta) |
60 | 52 | 3. Converting to JSON string for storage
|
61 | 53 |
|
62 |
| - :param messages: List of message objects or None |
| 54 | + :param messages: List of message objects, AnnotatedValue, or None |
63 | 55 | :param max_bytes: Maximum allowed size in bytes for the serialized messages
|
64 | 56 | :returns: JSON string of serialized messages or None if input was None/empty
|
65 | 57 | """
|
66 | 58 | if not messages:
|
67 | 59 | return None
|
| 60 | + |
| 61 | + if isinstance(messages, AnnotatedValue): |
| 62 | + serialized_messages = serialize(messages, is_vars=False) |
| 63 | + return json.dumps(serialized_messages, separators=(",", ":")) |
| 64 | + |
68 | 65 | truncated_messages = truncate_messages_by_size(messages, max_bytes)
|
69 | 66 | if not truncated_messages:
|
70 | 67 | return None
|
@@ -96,44 +93,31 @@ def get_messages_metadata(original_messages, truncated_messages):
|
96 | 93 |
|
97 | 94 |
|
98 | 95 | def truncate_and_serialize_messages(messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES):
|
99 |
| - # type: (Optional[List[Dict[str, Any]]], int) -> Dict[str, Any] |
| 96 | + # type: (Optional[List[Dict[str, Any]]], int) -> Any |
100 | 97 | """
|
101 |
| - One-stop function for gen_ai integrations to truncate and serialize messages. |
| 98 | + Truncate messages and return AnnotatedValue for automatic _meta creation. |
102 | 99 |
|
103 |
| - This is the main function that gen_ai integrations should use. It handles the |
104 |
| - complete workflow and returns both the serialized data and metadata. |
105 |
| -
|
106 |
| - Example usage: |
107 |
| - from sentry_sdk.ai.message_utils import truncate_and_serialize_messages |
108 |
| -
|
109 |
| - result = truncate_and_serialize_messages(messages) |
110 |
| - if result['serialized_data']: |
111 |
| - span.set_data('gen_ai.request.messages', result['serialized_data']) |
112 |
| - if result['metadata']['was_truncated']: |
113 |
| - # Log warning about truncation if desired |
114 |
| - pass |
| 100 | + This function handles truncation and returns the truncated messages wrapped in an |
| 101 | + AnnotatedValue (when truncation occurs) so that Sentry's serializer can automatically |
| 102 | + create the appropriate _meta structure. |
115 | 103 |
|
116 | 104 | :param messages: List of message objects or None
|
117 | 105 | :param max_bytes: Maximum allowed size in bytes for the serialized messages
|
118 |
| - :returns: Dictionary containing 'serialized_data', 'metadata', and 'original_size' |
| 106 | + :returns: List of messages, AnnotatedValue (if truncated), or None |
119 | 107 | """
|
120 | 108 | if not messages:
|
121 |
| - return { |
122 |
| - "serialized_data": None, |
123 |
| - "metadata": get_messages_metadata([], []), |
124 |
| - "original_size": 0, |
125 |
| - } |
126 |
| - |
127 |
| - original_serialized = serialize(messages, is_vars=False) |
128 |
| - original_json = json.dumps(original_serialized, separators=(",", ":")) |
129 |
| - original_size = len(original_json.encode("utf-8")) |
| 109 | + return None |
130 | 110 |
|
131 | 111 | truncated_messages = truncate_messages_by_size(messages, max_bytes)
|
132 |
| - serialized_data = serialize_gen_ai_messages(truncated_messages, max_bytes) |
133 |
| - metadata = get_messages_metadata(messages, truncated_messages) |
| 112 | + if not truncated_messages: |
| 113 | + return None |
134 | 114 |
|
135 |
| - return { |
136 |
| - "serialized_data": serialized_data, |
137 |
| - "metadata": metadata, |
138 |
| - "original_size": original_size, |
139 |
| - } |
| 115 | + original_count = len(messages) |
| 116 | + truncated_count = len(truncated_messages) |
| 117 | + |
| 118 | + if original_count != truncated_count: |
| 119 | + return AnnotatedValue( |
| 120 | + value=serialize_gen_ai_messages(truncated_messages), |
| 121 | + metadata={"len": original_count}, |
| 122 | + ) |
| 123 | + return truncated_messages |
0 commit comments