|
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