1212from pathlib import Path
1313
1414from astrbot .core import logger
15+ from astrbot .core .agent .context .config import (
16+ DEFAULT_FALLBACK_MAX_CONTEXT_TOKENS ,
17+ resolve_compression_threshold ,
18+ )
1519from astrbot .core .agent .handoff import HandoffTool
1620from astrbot .core .agent .mcp_client import MCPTool
1721from astrbot .core .agent .message import TextPart
97101 get_astrbot_system_tmp_path ,
98102 get_astrbot_workspaces_path ,
99103)
104+ from astrbot .core .utils .config_number import coerce_int_config
100105from astrbot .core .utils .file_extract import extract_file_moonshotai
101106from astrbot .core .utils .llm_metadata import LLM_METADATAS
102107from astrbot .core .utils .media_utils import (
@@ -178,6 +183,12 @@ class MainAgentBuildConfig:
178183 """The API key for Moonshot AI file extraction provider."""
179184 context_limit_reached_strategy : str = "truncate_by_turns"
180185 """The strategy to handle context length limit reached."""
186+ compression_threshold_mode : str = "percentage"
187+ """How percentage and output-reserve compression thresholds are combined."""
188+ compression_threshold_percentage : float = 0.82
189+ """User-defined context usage threshold."""
190+ compression_max_output_tokens : int = 0
191+ """Maximum output budget override. Zero uses model metadata."""
181192 llm_compress_instruction : str = ""
182193 """The instruction for compression in llm_compress strategy."""
183194 llm_compress_keep_recent_ratio : float = 0.15
@@ -189,7 +200,7 @@ class MainAgentBuildConfig:
189200 This enforce max turns before compression"""
190201 dequeue_context_length : int = 10
191202 """The number of oldest turns to remove when context length limit is reached."""
192- fallback_max_context_tokens : int = 128000
203+ fallback_max_context_tokens : int = DEFAULT_FALLBACK_MAX_CONTEXT_TOKENS
193204 """Fallback max context tokens. When max_context_tokens is 0 and the model is not in LLM_METADATAS, use this value."""
194205 llm_safety_mode : bool = True
195206 """This will inject healthy and safe system prompt into the main agent,
@@ -1593,17 +1604,59 @@ async def build_main_agent(
15931604 req .model = None
15941605 fallback_providers = [p for p in fallback_providers if p is not provider ]
15951606
1596- if provider .provider_config .get ("max_context_tokens" , 0 ) <= 0 :
1597- model = provider .get_model ()
1598- if model_info := LLM_METADATAS .get (model ):
1599- provider .provider_config ["max_context_tokens" ] = model_info ["limit" ][
1600- "context"
1601- ]
1602- else :
1603- # fallback: default to configured fallback value
1604- provider .provider_config ["max_context_tokens" ] = (
1605- config .fallback_max_context_tokens
1606- )
1607+ configured_context_tokens = coerce_int_config (
1608+ provider .provider_config .get ("max_context_tokens" , 0 ),
1609+ default = 0 ,
1610+ min_value = 0 ,
1611+ field_name = "max_context_tokens" ,
1612+ source = "provider config" ,
1613+ )
1614+ model_info = LLM_METADATAS .get (provider .get_model ())
1615+ metadata_limit = (model_info .get ("limit" ) or {}) if model_info else {}
1616+ metadata_context_tokens = coerce_int_config (
1617+ metadata_limit .get ("context" , 0 ),
1618+ default = 0 ,
1619+ min_value = 0 ,
1620+ field_name = "model context limit" ,
1621+ source = "model metadata" ,
1622+ )
1623+ fallback_context_tokens = coerce_int_config (
1624+ config .fallback_max_context_tokens ,
1625+ default = DEFAULT_FALLBACK_MAX_CONTEXT_TOKENS ,
1626+ min_value = 1 ,
1627+ field_name = "fallback_max_context_tokens" ,
1628+ )
1629+ provider .provider_config ["max_context_tokens" ] = (
1630+ configured_context_tokens or metadata_context_tokens or fallback_context_tokens
1631+ )
1632+
1633+ metadata_output_tokens = coerce_int_config (
1634+ metadata_limit .get ("output" , 0 ),
1635+ default = 0 ,
1636+ min_value = 0 ,
1637+ field_name = "model output limit" ,
1638+ source = "model metadata" ,
1639+ )
1640+ configured_output_tokens = coerce_int_config (
1641+ config .compression_max_output_tokens ,
1642+ default = 0 ,
1643+ min_value = 0 ,
1644+ field_name = "compression_max_output_tokens" ,
1645+ )
1646+ threshold_result = resolve_compression_threshold (
1647+ mode = config .compression_threshold_mode ,
1648+ percentage = config .compression_threshold_percentage ,
1649+ max_context_tokens = provider .provider_config ["max_context_tokens" ],
1650+ max_output_tokens = configured_output_tokens or metadata_output_tokens ,
1651+ )
1652+ if threshold_result ["fallback_reason" ]:
1653+ logger .warning (
1654+ "Compression threshold mode %s requires max output tokens, but no "
1655+ "override or model metadata is available for %s; using %.0f%%." ,
1656+ threshold_result ["mode" ],
1657+ provider .get_model (),
1658+ threshold_result ["effective_threshold" ] * 100 ,
1659+ )
16071660
16081661 if event .get_platform_name () == "webchat" :
16091662 asyncio .create_task (_handle_webchat (event , req , provider ))
@@ -1645,6 +1698,7 @@ async def build_main_agent(
16451698 tool_executor = FunctionToolExecutor (),
16461699 agent_hooks = MAIN_AGENT_HOOKS ,
16471700 streaming = config .streaming_response ,
1701+ compression_threshold = threshold_result ["effective_threshold" ],
16481702 llm_compress_instruction = config .llm_compress_instruction ,
16491703 llm_compress_keep_recent_ratio = config .llm_compress_keep_recent_ratio ,
16501704 llm_compress_provider = _get_compress_provider (config , plugin_context , event ),
0 commit comments