11import json
2+ from copy import deepcopy
23from collections import deque
34from typing import TYPE_CHECKING
45from sys import getsizeof
1213from sentry_sdk .utils import logger
1314
1415MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
16+ # Maximum characters when only a single message is left after bytes truncation
17+ MAX_SINGLE_MESSAGE_CONTENT_CHARS = 10_000
1518
1619
1720class GEN_AI_ALLOWED_MESSAGE_ROLES :
@@ -101,27 +104,20 @@ def get_start_span_function():
101104 return sentry_sdk .start_span if transaction_exists else sentry_sdk .start_transaction
102105
103106
104- def _truncate_single_message (message , max_bytes ):
107+ def _truncate_single_message_content_if_present (message , max_chars ):
105108 # type: (Dict[str, Any], int) -> Dict[str, Any]
106109 """
107- Truncate a single message to fit within max_bytes .
110+ Truncate a single message to fit within max_chars .
108111 If the message is too large, truncate the content field.
109112 """
110113 if not isinstance (message , dict ) or "content" not in message :
111114 return message
112- content = message . get ( "content" , "" )
115+ content = message [ "content" ]
113116
114- if not isinstance (content , str ) or len (content ) <= max_bytes :
117+ if not isinstance (content , str ) or len (content ) <= max_chars :
115118 return message
116119
117- overhead_message = message .copy ()
118- overhead_message ["content" ] = ""
119- overhead_size = len (
120- json .dumps (overhead_message , separators = ("," , ":" )).encode ("utf-8" )
121- )
122-
123- available_content_bytes = max_bytes - overhead_size - 20
124- message ["content" ] = content [:available_content_bytes ] + "..."
120+ message ["content" ] = content [:max_chars ] + "..."
125121 return message
126122
127123
@@ -142,22 +138,37 @@ def _find_truncation_index(messages, max_bytes):
142138 return 0
143139
144140
145- def truncate_messages_by_size (messages , max_bytes = MAX_GEN_AI_MESSAGE_BYTES ):
146- # type: (List[Dict[str, Any]], int) -> Tuple[List[Dict[str, Any]], int]
147- messages_with_truncated_content = [
148- _truncate_single_message (msg , max_bytes ) for msg in messages
149- ]
150-
151- serialized_json = json .dumps (messages_with_truncated_content , separators = ("," , ":" ))
141+ def truncate_messages_by_size (
142+ messages ,
143+ max_bytes = MAX_GEN_AI_MESSAGE_BYTES ,
144+ max_single_message_chars = MAX_SINGLE_MESSAGE_CONTENT_CHARS ,
145+ ):
146+ # type: (List[Dict[str, Any]], int, int) -> Tuple[List[Dict[str, Any]], int]
147+ """
148+ Returns a truncated messages array, consisting of
149+ - the last message, with the messages's content truncated to `max_single_message_chars` characters,
150+ if the last message's size exceeds `max_bytes`; otherwise,
151+ - the maximum number of messages, starting from the end of the `messages` array, whose total
152+ serialized size does not exceed `max_bytes` bytes.
153+ """
154+ serialized_json = json .dumps (messages , separators = ("," , ":" ))
152155 current_size = len (serialized_json .encode ("utf-8" ))
153156
154157 if current_size <= max_bytes :
155- return messages_with_truncated_content , 0
158+ return messages , 0
156159
157- truncation_index = _find_truncation_index (
158- messages_with_truncated_content , max_bytes
160+ truncation_index = _find_truncation_index (messages , max_bytes )
161+ truncated_messages = (
162+ messages [truncation_index :]
163+ if truncation_index < len (messages )
164+ else messages [- 1 :]
159165 )
160- return messages_with_truncated_content [truncation_index :], truncation_index
166+ if len (truncated_messages ) == 1 :
167+ truncated_messages [0 ] = _truncate_single_message_content_if_present (
168+ deepcopy (truncated_messages [0 ]), max_chars = max_single_message_chars
169+ )
170+
171+ return truncated_messages , truncation_index
161172
162173
163174def truncate_and_annotate_messages (
0 commit comments