Set default condenser to ConversationWindowCondenser (#10031)

Co-authored-by: Calvin Smith <calvin@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Calvin Smith 2025-08-01 10:35:40 -06:00 committed by GitHub
parent d0a8c896c2
commit 39fff41dd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 64 additions and 16 deletions

View File

@ -23,7 +23,10 @@ from openhands.cli.utils import (
)
from openhands.controller.agent import Agent
from openhands.core.config import OpenHandsConfig
from openhands.core.config.condenser_config import NoOpCondenserConfig
from openhands.core.config.condenser_config import (
CondenserPipelineConfig,
ConversationWindowCondenserConfig,
)
from openhands.core.config.utils import OH_DEFAULT_AGENT
from openhands.memory.condenser.impl.llm_summarizing_condenser import (
LLMSummarizingCondenserConfig,
@ -472,12 +475,21 @@ async def modify_llm_settings_advanced(
agent_config = config.get_agent_config(config.default_agent)
if enable_memory_condensation:
agent_config.condenser = LLMSummarizingCondenserConfig(
llm_config=llm_config,
type='llm',
agent_config.condenser = CondenserPipelineConfig(
type='pipeline',
condensers=[
ConversationWindowCondenserConfig(type='conversation_window'),
# Use LLMSummarizingCondenserConfig with the custom llm_config
LLMSummarizingCondenserConfig(
llm_config=llm_config, type='llm', keep_first=4, max_size=120
),
],
)
else:
agent_config.condenser = NoOpCondenserConfig(type='noop')
agent_config.condenser = ConversationWindowCondenserConfig(
type='conversation_window'
)
config.set_agent_config(agent_config)
settings = await settings_store.load()

View File

@ -2,7 +2,10 @@ from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from openhands.core.config.condenser_config import CondenserConfig, NoOpCondenserConfig
from openhands.core.config.condenser_config import (
CondenserConfig,
ConversationWindowCondenserConfig,
)
from openhands.core.config.extended_config import ExtendedConfig
from openhands.core.logger import openhands_logger as logger
from openhands.utils.import_utils import get_impl
@ -44,7 +47,11 @@ class AgentConfig(BaseModel):
enable_som_visual_browsing: bool = Field(default=True)
"""Whether to enable SoM (Set of Marks) visual browsing."""
condenser: CondenserConfig = Field(
default_factory=lambda: NoOpCondenserConfig(type='noop')
# The default condenser is set to the conversation window condenser -- if
# we use NoOp and the conversation hits the LLM context length limit,
# the agent will generate a condensation request which will never be
# handled.
default_factory=lambda: ConversationWindowCondenserConfig()
)
extended: ExtendedConfig = Field(default_factory=lambda: ExtendedConfig({}))
"""Extended configuration for the agent."""

View File

@ -249,6 +249,7 @@ def test_step_with_no_pending_actions(mock_state: State):
llm.completion = Mock(return_value=mock_response)
llm.is_function_calling_active = Mock(return_value=True) # Enable function calling
llm.is_caching_prompt_active = Mock(return_value=False)
llm.format_messages_for_llm = Mock(return_value=[]) # Mock message formatting
# Create agent with mocked LLM
config = AgentConfig()
@ -269,6 +270,10 @@ def test_step_with_no_pending_actions(mock_state: State):
initial_user_message._source = EventSource.USER
mock_state.history = [initial_user_message]
# Mock the view returned by condenser
mock_view = View(events=mock_state.history)
mock_state.view = mock_view
action = agent.step(mock_state)
assert isinstance(action, MessageAction)
assert action.content == 'Task completed'

View File

@ -19,16 +19,24 @@ from openhands.storage.settings.file_settings_store import FileSettingsStore
# Mock classes for condensers
class MockLLMSummarizingCondenserConfig:
def __init__(self, llm_config, type):
def __init__(self, llm_config, type, keep_first=4, max_size=120):
self.llm_config = llm_config
self.type = type
self.keep_first = keep_first
self.max_size = max_size
class MockNoOpCondenserConfig:
class MockConversationWindowCondenserConfig:
def __init__(self, type):
self.type = type
class MockCondenserPipelineConfig:
def __init__(self, type, condensers):
self.type = type
self.condensers = condensers
class TestDisplaySettings:
@pytest.fixture
def app_config(self):
@ -467,7 +475,13 @@ class TestModifyLLMSettingsAdvanced:
'openhands.cli.settings.LLMSummarizingCondenserConfig',
MockLLMSummarizingCondenserConfig,
)
@patch('openhands.cli.settings.NoOpCondenserConfig', MockNoOpCondenserConfig)
@patch(
'openhands.cli.settings.ConversationWindowCondenserConfig',
MockConversationWindowCondenserConfig,
)
@patch(
'openhands.cli.settings.CondenserPipelineConfig', MockCondenserPipelineConfig
)
async def test_modify_llm_settings_advanced_success(
self, mock_confirm, mock_session, mock_list_agents, app_config, settings_store
):
@ -521,7 +535,10 @@ class TestModifyLLMSettingsAdvanced:
'openhands.cli.settings.LLMSummarizingCondenserConfig',
MockLLMSummarizingCondenserConfig,
)
@patch('openhands.cli.settings.NoOpCondenserConfig', MockNoOpCondenserConfig)
@patch(
'openhands.cli.settings.ConversationWindowCondenserConfig',
MockConversationWindowCondenserConfig,
)
async def test_modify_llm_settings_advanced_user_cancels(
self, mock_confirm, mock_session, mock_list_agents, app_config, settings_store
):
@ -548,7 +565,10 @@ class TestModifyLLMSettingsAdvanced:
'openhands.cli.settings.LLMSummarizingCondenserConfig',
MockLLMSummarizingCondenserConfig,
)
@patch('openhands.cli.settings.NoOpCondenserConfig', MockNoOpCondenserConfig)
@patch(
'openhands.cli.settings.ConversationWindowCondenserConfig',
MockConversationWindowCondenserConfig,
)
async def test_modify_llm_settings_advanced_invalid_agent(
self,
mock_print,
@ -599,7 +619,10 @@ class TestModifyLLMSettingsAdvanced:
'openhands.cli.settings.LLMSummarizingCondenserConfig',
MockLLMSummarizingCondenserConfig,
)
@patch('openhands.cli.settings.NoOpCondenserConfig', MockNoOpCondenserConfig)
@patch(
'openhands.cli.settings.ConversationWindowCondenserConfig',
MockConversationWindowCondenserConfig,
)
async def test_modify_llm_settings_advanced_user_rejects_save(
self, mock_confirm, mock_session, mock_list_agents, app_config, settings_store
):

View File

@ -16,6 +16,7 @@ from openhands.core.config import (
load_openhands_config,
)
from openhands.core.config.condenser_config import (
ConversationWindowCondenserConfig,
LLMSummarizingCondenserConfig,
NoOpCondenserConfig,
RecentEventsCondenserConfig,
@ -680,7 +681,7 @@ def test_agent_config_condenser_with_no_enabled():
"""Test default agent condenser with enable_default_condenser=False."""
config = OpenHandsConfig(enable_default_condenser=False)
agent_config = config.get_agent_config()
assert isinstance(agent_config.condenser, NoOpCondenserConfig)
assert isinstance(agent_config.condenser, ConversationWindowCondenserConfig)
def test_sandbox_volumes_toml(default_config, temp_toml_file):
@ -907,9 +908,9 @@ def test_default_condenser_behavior_disabled(default_config, temp_toml_file):
default_config.enable_default_condenser = False
load_from_toml(default_config, temp_toml_file)
# Verify the agent config uses NoOpCondenserConfig
# Verify the agent config uses ConversationWindowCondenserConfig
agent_config = default_config.get_agent_config()
assert isinstance(agent_config.condenser, NoOpCondenserConfig)
assert isinstance(agent_config.condenser, ConversationWindowCondenserConfig)
def test_default_condenser_explicit_toml_override(default_config, temp_toml_file):