Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Condenser Interface and Defaults #5306

Merged
merged 109 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
d35a06e
refactor: Implement condenser configuration system
openhands-agent Nov 26, 2024
c6bc242
Fix condenser config and event creation issues
openhands-agent Nov 26, 2024
d0bc171
refactor: Move condenser config to core/config and improve env var ha…
openhands-agent Nov 26, 2024
8b9b52d
fixing up unit tests and type hints
Nov 27, 2024
10d5fc6
restoring utils file
Nov 27, 2024
6489787
splitting failing config unit tests
Nov 27, 2024
3f4af37
removing TOML-setting of condensers
Nov 27, 2024
39e6c3d
refactor: Implement condenser configuration system
openhands-agent Nov 26, 2024
1088537
Fix condenser config and event creation issues
openhands-agent Nov 26, 2024
4e63c7f
refactor: Move condenser config to core/config and improve env var ha…
openhands-agent Nov 26, 2024
b53f4b6
fixing up unit tests and type hints
Nov 27, 2024
190d6ae
restoring utils file
Nov 27, 2024
a07a615
splitting failing config unit tests
Nov 27, 2024
a550833
removing TOML-setting of condensers
Nov 27, 2024
14c8f1d
condenser config basemodel -> dataclass
Dec 2, 2024
5e05564
extending condenser output with support for metadata
Dec 4, 2024
1a3482c
condensation metadata utility functions
Dec 4, 2024
b8544ef
llm condenser metrics
Dec 4, 2024
3dc9245
merging
Dec 4, 2024
2a059c8
configuration and metrics for all condensers
Dec 5, 2024
fe6ed05
adding keep_first flag to reduce condenser
Dec 5, 2024
4343b8d
recent events condenser can keep arbitrary initial values
Dec 6, 2024
c0b1230
adding some evaluation scripts
Dec 9, 2024
4ce8594
uploading some exp data
Dec 9, 2024
1d97831
Add cactus plots for token usage and history length analysis
openhands-agent Dec 9, 2024
52193c4
feat(evaluation): Add graph generation script
openhands-agent Dec 9, 2024
2bff3bc
refactor(evaluation): Improve plot_graphs.py
openhands-agent Dec 9, 2024
83070df
feat(evaluation): Add browser renderer option
openhands-agent Dec 9, 2024
3b779a2
Merge branch 'main' into feature/condenser-refactor
Dec 13, 2024
402906f
Refactor Condenser to work directly with State objects
openhands-agent Dec 13, 2024
9ecf5aa
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 13, 2024
e49e84d
Add AmortizedForgettingCondenser framework
openhands-agent Dec 16, 2024
b6dff1f
Implement AmortizedForgettingCondenser
openhands-agent Dec 16, 2024
523dee2
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 16, 2024
8c87d70
Add keep_first parameter to AmortizedForgettingCondenser
openhands-agent Dec 16, 2024
4381307
Add event tracking to AmortizedForgettingCondenser
openhands-agent Dec 16, 2024
f2b9d07
feat: Add LLMAttentionCondenser with NotImplementedError placeholder
openhands-agent Dec 16, 2024
d64edf8
Implement LLMAttentionCondenser with state maintenance and LLM config…
openhands-agent Dec 16, 2024
e169d8a
Merge branch 'main' into feature/condenser-refactor
Dec 16, 2024
14873ad
fixing forgetting condensers and tests
Dec 17, 2024
18b8b17
fixing condenser tests
Dec 17, 2024
85885b9
minor
Dec 17, 2024
d74b5f6
cleaning up accidental experimental files
Dec 20, 2024
36dff26
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 20, 2024
d189f73
fixes to llm attention condenser
Dec 23, 2024
0dee6eb
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 23, 2024
b28d319
failing unit tests added
Dec 23, 2024
b6f6190
unit tests passing with event id added
Dec 23, 2024
2558fad
unit tests passing with event id added
Dec 23, 2024
bfa80cb
minor condenser entrypoint refactor
Dec 26, 2024
940d85e
cleaning up condenser metadata management
Dec 26, 2024
756bedb
doc string pass
Dec 26, 2024
6e263b8
observation-masking condenser
Dec 26, 2024
1daedff
fixing llm attention
Dec 27, 2024
d6f9906
fixing observation masking tests
Dec 27, 2024
909e847
fixing eval scripts
Dec 27, 2024
716f665
Merge branch 'main' into feature/condenser-refactor
Dec 27, 2024
5e29ebc
fixing lock
Dec 27, 2024
c20e811
fixing prompt caching tests
Dec 27, 2024
a96551c
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 27, 2024
68d1bff
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 27, 2024
219994d
safe dumping of llm-based condenser config
Dec 27, 2024
b69cd15
asdf
Dec 31, 2024
5c0704f
failing test for mismatched tool calls
Dec 31, 2024
006a5fd
mismatched tool call tests passing
Dec 31, 2024
32eccc9
refactoring test
Dec 31, 2024
8825d03
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 31, 2024
8dc834f
llm attention validation and test
Dec 31, 2024
5ec51e1
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 31, 2024
7aa7a24
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 31, 2024
e3bfd63
moving condenser observations
Dec 31, 2024
b587499
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 31, 2024
453ce82
condensation observation -> agent condensation observation
Dec 31, 2024
35bb405
Merge branch 'main' into feature/condenser-refactor
csmith49 Dec 31, 2024
de10aeb
fix broken imports
Dec 31, 2024
d3c9794
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Dec 31, 2024
1e98db6
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 2, 2025
b4d4680
fixing response format detection
Jan 2, 2025
66ae8a3
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Jan 2, 2025
0e6cea7
Resolve merge conflicts with main branch
openhands-agent Jan 3, 2025
c1c7881
Fix pyproject.toml formatting
openhands-agent Jan 3, 2025
06ef4cc
Update poetry.lock
openhands-agent Jan 3, 2025
1bd5310
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
c26f9dc
updating docs and field constraints
Jan 3, 2025
185421c
fixing bad llm config in condenser tests
Jan 3, 2025
124c327
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
8a65f96
get metrics functionality in eval utils
Jan 3, 2025
13feeb1
Merge branch 'feature/condenser-refactor' of github.com:csmith49/Open…
Jan 3, 2025
4565b54
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
ef03f98
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
c21f254
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
0a7646d
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 3, 2025
2a0f843
add condensation obs to be deserialized when restoring session
enyst Jan 5, 2025
f2bdd07
Merge branch 'main' of github.com:All-Hands-AI/OpenHands into feature…
enyst Jan 5, 2025
ce5dfc1
Merge branch 'main' into feature/condenser-refactor
enyst Jan 5, 2025
d6acd03
poetry lock
enyst Jan 5, 2025
c73e422
Merge branch 'main' into feature/condenser-refactor
enyst Jan 5, 2025
7171a31
Merge branch 'main' into feature/condenser-refactor
enyst Jan 5, 2025
f9ff522
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
cddd0e7
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
d80a68c
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
5beba03
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
c6acf01
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
1f88c06
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
cdb2c4b
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 6, 2025
956251f
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 7, 2025
47fb5e7
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 7, 2025
3261db3
updating lock file
Jan 7, 2025
01caf68
Merge branch 'main' into feature/condenser-refactor
csmith49 Jan 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion evaluation/benchmarks/swe_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from openhands.events.action import CmdRunAction, MessageAction
from openhands.events.observation import CmdOutputObservation, ErrorObservation
from openhands.events.serialization.event import event_to_dict
from openhands.memory.condenser import get_condensation_metadata
from openhands.runtime.base import Runtime
from openhands.utils.async_utils import call_async_from_sync
from openhands.utils.shutdown_listener import sleep_if_should_continue
Expand Down Expand Up @@ -148,6 +149,7 @@ def get_config(
codeact_enable_jupyter=False,
codeact_enable_browsing=RUN_WITH_BROWSING,
codeact_enable_llm_editor=False,
condenser=metadata.condenser_config,
)
config.set_agent_config(agent_config)
return config
Expand Down Expand Up @@ -439,7 +441,8 @@ def process_instance(

# NOTE: this is NO LONGER the event stream, but an agent history that includes delegate agent's events
histories = [event_to_dict(event) for event in state.history]
metrics = state.metrics.get() if state.metrics else None
metrics = state.metrics.get() if state.metrics else {}
metrics['condenser'] = get_condensation_metadata(state) if state else []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this line be moved inside state.metrics.get()? Then we wouldn't have to copy-paste it into every evaluation benchmark separately.

Copy link
Collaborator Author

@csmith49 csmith49 Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite so directly: the condenser metrics are stored on the state, but the .get() here is on the metrics object held by the state. We can clean it up with a state.get_metrics() function though.

EDIT: Never mind, that introduces a circular import between the condensers and the state that isn't easy to break. Maybe the get_metrics() should just be a function in evaluation/utils/shared.py.


# Save the output
output = EvalOutput(
Expand Down
19 changes: 19 additions & 0 deletions evaluation/utils/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

from openhands.controller.state.state import State
from openhands.core.config import LLMConfig
from openhands.core.config.condenser_config import (
CondenserConfig,
NoOpCondenserConfig,
)
from openhands.core.exceptions import (
AgentRuntimeBuildError,
AgentRuntimeDisconnectedError,
Expand Down Expand Up @@ -45,18 +49,29 @@ class EvalMetadata(BaseModel):
dataset: str | None = None
data_split: str | None = None
details: dict[str, Any] | None = None
condenser_config: CondenserConfig | None = None

def model_dump(self, *args, **kwargs):
dumped_dict = super().model_dump(*args, **kwargs)
# avoid leaking sensitive information
dumped_dict['llm_config'] = self.llm_config.to_safe_dict()
if hasattr(self.condenser_config, 'llm_config'):
dumped_dict['condenser_config']['llm_config'] = (
self.condenser_config.llm_config.to_safe_dict()
)

return dumped_dict

def model_dump_json(self, *args, **kwargs):
dumped = super().model_dump_json(*args, **kwargs)
dumped_dict = json.loads(dumped)
# avoid leaking sensitive information
dumped_dict['llm_config'] = self.llm_config.to_safe_dict()
if hasattr(self.condenser_config, 'llm_config'):
dumped_dict['condenser_config']['llm_config'] = (
self.condenser_config.llm_config.to_safe_dict()
)

logger.debug(f'Dumped metadata: {dumped_dict}')
return json.dumps(dumped_dict)

Expand Down Expand Up @@ -192,6 +207,7 @@ def make_metadata(
eval_output_dir: str,
data_split: str | None = None,
details: dict[str, Any] | None = None,
condenser_config: CondenserConfig | None = None,
) -> EvalMetadata:
model_name = llm_config.model.split('/')[-1]
model_path = model_name.replace(':', '_').replace('@', '-')
Expand Down Expand Up @@ -222,6 +238,9 @@ def make_metadata(
dataset=dataset_name,
data_split=data_split,
details=details,
condenser_config=condenser_config
if condenser_config
else NoOpCondenserConfig(),
Comment on lines +242 to +244
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this probably won't pass linting, so you should make sure that you have pre-commit config installed to auto-lint before committing.

)
metadata_json = metadata.model_dump_json()
logger.info(f'Metadata: {metadata_json}')
Expand Down
15 changes: 14 additions & 1 deletion openhands/agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
from openhands.events.observation.observation import Observation
from openhands.events.serialization.event import truncate_content
from openhands.llm.llm import LLM
from openhands.memory.condenser import (
CondensationObservation,
Condenser,
)
from openhands.runtime.plugins import (
AgentSkillsRequirement,
JupyterRequirement,
Expand Down Expand Up @@ -110,6 +114,9 @@ def __init__(
disabled_microagents=self.config.disabled_microagents,
)

self.condenser = Condenser.from_config(self.config.condenser)
logger.debug(f'Using condenser: {self.condenser}')

self.pending_actions: deque[Action] = deque()

def get_action_message(
Expand Down Expand Up @@ -317,6 +324,9 @@ def get_observation_message(
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += '\n[Last action has been rejected by the user]'
message = Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, CondensationObservation):
text = truncate_content(obs.content, max_message_chars)
message = Message(role='user', content=[TextContent(text=text)])
else:
# If an observation message is not returned, it will cause an error
# when the LLM tries to return the next message
Expand Down Expand Up @@ -436,7 +446,10 @@ def _get_messages(self, state: State) -> list[Message]:

pending_tool_call_action_messages: dict[str, Message] = {}
tool_call_id_to_message: dict[str, Message] = {}
events = list(state.history)

# Condense the events from the state.
events = self.condenser.condensed_history(state)

for event in events:
# create a regular message from an event
if isinstance(event, Action):
Expand Down
5 changes: 4 additions & 1 deletion openhands/core/config/agent_config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass, fields
from dataclasses import dataclass, field, fields

from openhands.core.config.condenser_config import CondenserConfig, NoOpCondenserConfig
from openhands.core.config.config_utils import get_field_info


Expand All @@ -18,6 +19,7 @@ class AgentConfig:
llm_config: The name of the llm config to use. If specified, this will override global llm config.
use_microagents: Whether to use microagents at all. Default is True.
disabled_microagents: A list of microagents to disable. Default is None.
condenser: Configuration for the memory condenser. Default is NoOpCondenserConfig.
"""

codeact_enable_browsing: bool = True
Expand All @@ -29,6 +31,7 @@ class AgentConfig:
llm_config: str | None = None
use_microagents: bool = True
disabled_microagents: list[str] | None = None
condenser: CondenserConfig = field(default_factory=NoOpCondenserConfig) # type: ignore

def defaults_to_dict(self) -> dict:
"""Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
Expand Down
88 changes: 88 additions & 0 deletions openhands/core/config/condenser_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from typing import Literal

from pydantic import BaseModel, Field

from openhands.core.config.llm_config import LLMConfig


class NoOpCondenserConfig(BaseModel):
"""Configuration for NoOpCondenser."""

type: Literal['noop'] = Field('noop')


class ObservationMaskingCondenserConfig(BaseModel):
"""Configuration for ObservationMaskingCondenser."""

type: Literal['observation_masking'] = Field('observation_masking')
attention_window: int = Field(
default=10,
description='The number of most-recent events where observations will not be masked.',
)


class RecentEventsCondenserConfig(BaseModel):
"""Configuration for RecentEventsCondenser."""

type: Literal['recent'] = Field('recent')
keep_first: int = Field(
default=0,
description='The number of initial events to condense.',
)
max_events: int = Field(
default=10, description='Maximum number of events to keep.', ge=1
)


class LLMSummarizingCondenserConfig(BaseModel):
"""Configuration for LLMCondenser."""

type: Literal['llm'] = Field('llm')
llm_config: LLMConfig = Field(
..., description='Configuration for the LLM to use for condensing.'
)


class AmortizedForgettingCondenserConfig(BaseModel):
"""Configuration for AmortizedForgettingCondenser."""

type: Literal['amortized'] = Field('amortized')
max_size: int = Field(
default=100,
description='Maximum size of the condensed history before triggering forgetting.',
ge=2,
)
keep_first: int = Field(
default=0,
description='Number of initial events to always keep in history.',
ge=0,
)


class LLMAttentionCondenserConfig(BaseModel):
"""Configuration for LLMAttentionCondenser."""

type: Literal['llm_attention'] = Field('llm_attention')
llm_config: LLMConfig = Field(
..., description='Configuration for the LLM to use for attention.'
)
max_size: int = Field(
default=100,
description='Maximum size of the condensed history before triggering forgetting.',
ge=2,
)
keep_first: int = Field(
default=0,
description='Number of initial events to always keep in history.',
ge=0,
)


CondenserConfig = (
NoOpCondenserConfig
| ObservationMaskingCondenserConfig
| RecentEventsCondenserConfig
| LLMSummarizingCondenserConfig
| AmortizedForgettingCondenserConfig
| LLMAttentionCondenserConfig
)
4 changes: 2 additions & 2 deletions openhands/memory/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from openhands.memory.condenser import MemoryCondenser
from openhands.memory.condenser import Condenser
from openhands.memory.memory import LongTermMemory

__all__ = ['LongTermMemory', 'MemoryCondenser']
__all__ = ['LongTermMemory', 'Condenser']
Loading
Loading