fix: Tweak prompting behavior of LLMSummarizingCondenser (#7695)

Co-authored-by: Calvin Smith <calvin@all-hands.dev>
This commit is contained in:
Calvin Smith 2025-04-03 16:06:10 -06:00 committed by GitHub
parent 8bceee9e42
commit cc1aadaba5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 15 deletions

View File

@ -75,6 +75,10 @@ class LLMSummarizingCondenserConfig(BaseModel):
description='Maximum size of the condensed history before triggering forgetting.',
ge=2,
)
max_event_length: int = Field(
default=10_000,
description='Maximum length of the event representations to be passed to the LLM.',
)
model_config = {'extra': 'forbid'}

View File

@ -4,6 +4,7 @@ from openhands.core.config.condenser_config import LLMSummarizingCondenserConfig
from openhands.core.message import Message, TextContent
from openhands.events.action.agent import CondensationAction
from openhands.events.observation.agent import AgentCondensationObservation
from openhands.events.serialization.event import truncate_content
from openhands.llm import LLM
from openhands.memory.condenser.condenser import (
Condensation,
@ -20,7 +21,13 @@ class LLMSummarizingCondenser(RollingCondenser):
and newly forgotten events.
"""
def __init__(self, llm: LLM, max_size: int = 100, keep_first: int = 1):
def __init__(
self,
llm: LLM,
max_size: int = 100,
keep_first: int = 1,
max_event_length: int = 10_000,
):
if keep_first >= max_size // 2:
raise ValueError(
f'keep_first ({keep_first}) must be less than half of max_size ({max_size})'
@ -32,10 +39,15 @@ class LLMSummarizingCondenser(RollingCondenser):
self.max_size = max_size
self.keep_first = keep_first
self.max_event_length = max_event_length
self.llm = llm
super().__init__()
def _truncate(self, content: str) -> str:
"""Truncate the content to fit within the specified maximum event length."""
return truncate_content(content, max_chars=self.max_event_length)
def get_condensation(self, view: View) -> Condensation:
head = view[: self.keep_first]
target_size = self.max_size // 2
@ -56,40 +68,66 @@ class LLMSummarizingCondenser(RollingCondenser):
forgotten_events.append(event)
# Construct prompt for summarization
prompt = """You are maintaining state history for an LLM-based code agent. Track:
prompt = """You are maintaining a context-aware state summary for an interactive agent. You will be given a list of events corresponding to actions taken by the agent, and the most recent previous summary if one exists. Track:
USER_CONTEXT: (Preserve essential user requirements, problem descriptions, and clarifications in concise form)
USER_CONTEXT: (Preserve essential user requirements, goals, and clarifications in concise form)
STATE: {File paths, function signatures, data structures}
COMPLETED: (Tasks completed so far, with brief results)
PENDING: (Tasks that still need to be done)
CURRENT_STATE: (Current variables, data structures, or relevant state)
For code-specific tasks, also include:
CODE_STATE: {File paths, function signatures, data structures}
TESTS: {Failing cases, error messages, outputs}
CHANGES: {Code edits, variable updates}
DEPS: {Dependencies, imports, external calls}
INTENT: {Why changes were made, acceptance criteria}
VERSION_CONTROL_STATUS: {Repository state, current branch, PR status, commit history}
PRIORITIZE:
1. Capture key user requirements and constraints
2. Maintain critical problem context
3. Keep all sections concise
1. Adapt tracking format to match the actual task type
2. Capture key user requirements and goals
3. Distinguish between completed and pending tasks
4. Keep all sections concise and relevant
SKIP: {Git clones, build logs, file listings}
SKIP: Tracking irrelevant details for the current task type
Example history format:
USER_CONTEXT: Fix FITS card float representation - "0.009125" becomes "0.009124999999999999" causing comment truncation. Use Python's str() when possible while maintaining FITS compliance.
Example formats:
STATE: mod_float() in card.py updated
For code tasks:
USER_CONTEXT: Fix FITS card float representation issue
COMPLETED: Modified mod_float() in card.py, all tests passing
PENDING: Create PR, update documentation
CODE_STATE: mod_float() in card.py updated
TESTS: test_format() passed
CHANGES: str(val) replaces f"{val:.16G}"
DEPS: None modified
INTENT: Fix precision while maintaining FITS compliance"""
VERSION_CONTROL_STATUS: Branch: fix-float-precision, Latest commit: a1b2c3d
For other tasks:
USER_CONTEXT: Write 20 haikus based on coin flip results
COMPLETED: 15 haikus written for results [T,H,T,H,T,H,T,T,H,T,H,T,H,T,H]
PENDING: 5 more haikus needed
CURRENT_STATE: Last flip: Heads, Haiku count: 15/20"""
prompt += '\n\n'
prompt += ('\n' + summary_event.message + '\n') if summary_event.message else ''
# Add the previous summary if it exists. We'll always have a summary
# event, but the types aren't precise enought to guarantee that it has a
# message attribute.
summary_event_content = self._truncate(
summary_event.message if summary_event.message else ''
)
prompt += f'<PREVIOUS SUMMARY>\n{summary_event_content}\n</PREVIOUS SUMMARY>\n'
prompt += '\n\n'
# Add all events that are being forgotten. We use the string
# representation defined by the event, and truncate it if necessary.
for forgotten_event in forgotten_events:
prompt += str(forgotten_event) + '\n\n'
event_content = self._truncate(str(forgotten_event))
prompt += f'<EVENT id={forgotten_event.id}>\n{event_content}\n</EVENT>\n'
prompt += 'Now summarize the events using the rules above.'
messages = [Message(role='user', content=[TextContent(text=prompt)])]
@ -121,6 +159,7 @@ INTENT: Fix precision while maintaining FITS compliance"""
llm=LLM(config=config.llm_config),
max_size=config.max_size,
keep_first=config.keep_first,
max_event_length=config.max_event_length,
)