@@ -265,6 +265,80 @@ async def _iter_llm_responses_with_fallback(
265265 return
266266 except Exception as exc : # noqa: BLE001
267267 last_exception = exc
268+ # Auto-compress context when model_max_prompt_tokens_exceeded
269+ _exc_str = str (exc ).lower ()
270+ if (
271+ "model_max_prompt_tokens_exceeded" in _exc_str
272+ or "prompt token count" in _exc_str
273+ or "tokens_exceeded" in _exc_str
274+ or "context_length_exceeded" in _exc_str
275+ or ("token" in _exc_str and "exceed" in _exc_str )
276+ ):
277+ logger .warning (
278+ "Chat Model %s: token limit exceeded, forcing context compression and retrying..." ,
279+ candidate_id ,
280+ )
281+ try :
282+ from astrbot .core .agent .context .truncator import ContextTruncator
283+ _truncator = ContextTruncator ()
284+ _before_total = len (self .run_context .messages )
285+ # Aggressively halve until small enough (up to 5 rounds)
286+ _compression_success = False
287+ for _halve_round in range (5 ):
288+ _before = len (self .run_context .messages )
289+ self .run_context .messages = _truncator .truncate_by_halving (
290+ self .run_context .messages
291+ )
292+ _after = len (self .run_context .messages )
293+ logger .info (
294+ "Forced context truncation round %d: %d -> %d messages." ,
295+ _halve_round + 1 , _before , _after ,
296+ )
297+ if _after <= 4 :
298+ break # Can't shrink further
299+ # Retry same candidate
300+ try :
301+ async for resp in self ._iter_llm_responses (include_model = idx == 0 ):
302+ if resp .is_chunk :
303+ has_stream_output = True
304+ yield resp
305+ continue
306+ yield resp
307+ logger .info (
308+ "Context truncation succeeded after %d round(s): %d -> %d messages." ,
309+ _halve_round + 1 , _before_total , _after ,
310+ )
311+ _compression_success = True
312+ return
313+ if has_stream_output :
314+ _compression_success = True
315+ return
316+ _compression_success = True
317+ break # succeeded without stream output
318+ except Exception as retry_exc :
319+ _exc_str2 = str (retry_exc ).lower ()
320+ if not (
321+ "model_max_prompt_tokens_exceeded" in _exc_str2
322+ or "prompt token count" in _exc_str2
323+ or "tokens_exceeded" in _exc_str2
324+ or "context_length_exceeded" in _exc_str2
325+ or ("token" in _exc_str2 and "exceed" in _exc_str2 )
326+ ):
327+ last_exception = retry_exc
328+ logger .warning (
329+ "Chat Model %s retry after compression failed: %s" ,
330+ candidate_id , retry_exc ,
331+ )
332+ break
333+ last_exception = retry_exc
334+ logger .warning (
335+ "Chat Model %s still token-exceeded after round %d, halving again..." ,
336+ candidate_id , _halve_round + 1 ,
337+ )
338+ continue
339+ except Exception as compress_exc :
340+ logger .error ("Failed to compress context: %s" , compress_exc )
341+ continue
268342 logger .warning (
269343 "Chat Model %s request error: %s" ,
270344 candidate_id ,
@@ -657,6 +731,14 @@ async def _handle_function_tools(
657731 logger .info (f"Agent 使用工具: { llm_response .tools_call_name } " )
658732
659733 def _append_tool_call_result (tool_call_id : str , content : str ) -> None :
734+ # Truncate oversized tool results to prevent context bloat
735+ MAX_TOOL_RESULT_CHARS = 12000
736+ if len (content ) > MAX_TOOL_RESULT_CHARS :
737+ truncated_len = len (content ) - MAX_TOOL_RESULT_CHARS
738+ content = (
739+ content [:MAX_TOOL_RESULT_CHARS ]
740+ + f"\n ... [TRUNCATED { truncated_len } chars to prevent context overflow]"
741+ )
660742 tool_call_result_blocks .append (
661743 ToolCallMessageSegment (
662744 role = "tool" ,
0 commit comments