11import inspect
22import json
3+ from copy import deepcopy
34from collections import deque
45from typing import TYPE_CHECKING
56from sys import getsizeof
1314from sentry_sdk .utils import logger
1415
1516MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
17+ # Maximum characters when only a single message is left after bytes truncation
18+ MAX_SINGLE_MESSAGE_CONTENT_CHARS = 10_000
1619
1720
1821class GEN_AI_ALLOWED_MESSAGE_ROLES :
@@ -107,6 +110,23 @@ def get_start_span_function():
107110 return sentry_sdk .start_span if transaction_exists else sentry_sdk .start_transaction
108111
109112
113+ def _truncate_single_message_content_if_present (message , max_chars ):
114+ # type: (Dict[str, Any], int) -> Dict[str, Any]
115+ """
116+ Truncate a message's content to at most `max_chars` characters and append an
117+ ellipsis if truncation occurs.
118+ """
119+ if not isinstance (message , dict ) or "content" not in message :
120+ return message
121+ content = message ["content" ]
122+
123+ if not isinstance (content , str ) or len (content ) <= max_chars :
124+ return message
125+
126+ message ["content" ] = content [:max_chars ] + "..."
127+ return message
128+
129+
110130def _find_truncation_index (messages , max_bytes ):
111131 # type: (List[Dict[str, Any]], int) -> int
112132 """
@@ -124,16 +144,41 @@ def _find_truncation_index(messages, max_bytes):
124144 return 0
125145
126146
127- def truncate_messages_by_size (messages , max_bytes = MAX_GEN_AI_MESSAGE_BYTES ):
128- # type: (List[Dict[str, Any]], int) -> Tuple[List[Dict[str, Any]], int]
147+ def truncate_messages_by_size (
148+ messages ,
149+ max_bytes = MAX_GEN_AI_MESSAGE_BYTES ,
150+ max_single_message_chars = MAX_SINGLE_MESSAGE_CONTENT_CHARS ,
151+ ):
152+ # type: (List[Dict[str, Any]], int, int) -> Tuple[List[Dict[str, Any]], int]
153+ """
154+ Returns a truncated messages list, consisting of
155+ - the last message, with its content truncated to `max_single_message_chars` characters,
156+ if the last message's size exceeds `max_bytes` bytes; otherwise,
157+ - the maximum number of messages, starting from the end of the `messages` list, whose total
158+ serialized size does not exceed `max_bytes` bytes.
159+
160+ In the single message case, the serialized message size may exceed `max_bytes`, because
161+ truncation is based only on character count in that case.
162+ """
129163 serialized_json = json .dumps (messages , separators = ("," , ":" ))
130164 current_size = len (serialized_json .encode ("utf-8" ))
131165
132166 if current_size <= max_bytes :
133167 return messages , 0
134168
135169 truncation_index = _find_truncation_index (messages , max_bytes )
136- return messages [truncation_index :], truncation_index
170+ if truncation_index < len (messages ):
171+ truncated_messages = messages [truncation_index :]
172+ else :
173+ truncation_index = len (messages ) - 1
174+ truncated_messages = messages [- 1 :]
175+
176+ if len (truncated_messages ) == 1 :
177+ truncated_messages [0 ] = _truncate_single_message_content_if_present (
178+ deepcopy (truncated_messages [0 ]), max_chars = max_single_message_chars
179+ )
180+
181+ return truncated_messages , truncation_index
137182
138183
139184def truncate_and_annotate_messages (
0 commit comments