feat: add max files and file open window to system prompt (#3515)

This commit is contained in:
Kevin Lin
2025-07-23 17:05:53 -07:00
committed by GitHub
parent 0516e6e5d9
commit 99c479cd78
7 changed files with 49 additions and 7 deletions

View File

@@ -122,7 +122,9 @@ class BaseAgent(ABC):
curr_dynamic_section = extract_dynamic_section(curr_system_message_text)
# generate just the memory string with current state for comparison
curr_memory_str = agent_state.memory.compile(tool_usage_rules=tool_constraint_block, sources=agent_state.sources)
curr_memory_str = agent_state.memory.compile(
tool_usage_rules=tool_constraint_block, sources=agent_state.sources, max_files_open=agent_state.max_files_open
)
new_dynamic_section = extract_dynamic_section(curr_memory_str)
# compare just the dynamic sections (memory blocks, tool rules, directories)
@@ -149,6 +151,7 @@ class BaseAgent(ABC):
archival_memory_size=num_archival_memories,
tool_rules_solver=tool_rules_solver,
sources=agent_state.sources,
max_files_open=agent_state.max_files_open,
)
diff = united_diff(curr_system_message_text, new_system_message_str)

View File

@@ -153,6 +153,7 @@ class VoiceAgent(BaseAgent):
previous_message_count=self.num_messages,
archival_memory_size=self.num_archival_memories,
sources=agent_state.sources,
max_files_open=agent_state.max_files_open,
)
letta_message_db_queue = create_input_messages(
input_messages=input_messages, agent_id=agent_state.id, timezone=agent_state.timezone, actor=self.actor

View File

@@ -360,6 +360,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
return (
"{% if sources %}"
"<directories>\n"
"{% if max_files_open %}"
"<file_limits>\n"
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
"- max_files_open={{ max_files_open }}\n"
"</file_limits>\n"
"{% endif %}"
"{% for source in sources %}"
f'<directory name="{{{{ source.name }}}}">\n'
"{% if source.description %}"
@@ -427,6 +433,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
"{% endif %}"
"\n\n{% if sources %}"
"<directories>\n"
"{% if max_files_open %}"
"<file_limits>\n"
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
"- max_files_open={{ max_files_open }}\n"
"</file_limits>\n"
"{% endif %}"
"{% for source in sources %}"
f'<directory name="{{{{ source.name }}}}">\n'
"{% if source.description %}"
@@ -493,6 +505,12 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
"{% endif %}"
"\n\n{% if sources %}"
"<directories>\n"
"{% if max_files_open %}"
"<file_limits>\n"
"- current_files_open={{ file_blocks|selectattr('value')|list|length }}\n"
"- max_files_open={{ max_files_open }}\n"
"</file_limits>\n"
"{% endif %}"
"{% for source in sources %}"
f'<directory name="{{{{ source.name }}}}">\n'
"{% if source.description %}"

View File

@@ -124,7 +124,7 @@ class Memory(BaseModel, validate_assignment=True):
Template(prompt_template)
# Validate compatibility with current memory structure
Template(prompt_template).render(blocks=self.blocks, file_blocks=self.file_blocks, sources=[])
Template(prompt_template).render(blocks=self.blocks, file_blocks=self.file_blocks, sources=[], max_files_open=None)
# If we get here, the template is valid and compatible
self.prompt_template = prompt_template
@@ -133,11 +133,17 @@ class Memory(BaseModel, validate_assignment=True):
except Exception as e:
raise ValueError(f"Prompt template is not compatible with current memory structure: {str(e)}")
def compile(self, tool_usage_rules=None, sources=None) -> str:
def compile(self, tool_usage_rules=None, sources=None, max_files_open=None) -> str:
"""Generate a string representation of the memory in-context using the Jinja2 template"""
try:
template = Template(self.prompt_template)
return template.render(blocks=self.blocks, file_blocks=self.file_blocks, tool_usage_rules=tool_usage_rules, sources=sources)
return template.render(
blocks=self.blocks,
file_blocks=self.file_blocks,
tool_usage_rules=tool_usage_rules,
sources=sources,
max_files_open=max_files_open,
)
except TemplateSyntaxError as e:
raise ValueError(f"Invalid Jinja2 template syntax: {str(e)}")
except Exception as e:

View File

@@ -1587,6 +1587,7 @@ class AgentManager:
previous_message_count=num_messages - len(agent_state.message_ids),
archival_memory_size=num_archival_memories,
sources=agent_state.sources,
max_files_open=agent_state.max_files_open,
)
diff = united_diff(curr_system_message_openai["content"], new_system_message_str)
@@ -1644,7 +1645,9 @@ class AgentManager:
# note: we only update the system prompt if the core memory is changed
# this means that the archival/recall memory statistics may be someout out of date
curr_memory_str = agent_state.memory.compile(
sources=agent_state.sources, tool_usage_rules=tool_rules_solver.compile_tool_rule_prompts()
sources=agent_state.sources,
tool_usage_rules=tool_rules_solver.compile_tool_rule_prompts(),
max_files_open=agent_state.max_files_open,
)
if curr_memory_str in curr_system_message_openai["content"] and not force:
# NOTE: could this cause issues if a block is removed? (substring match would still work)
@@ -1672,6 +1675,7 @@ class AgentManager:
archival_memory_size=num_archival_memories,
tool_rules_solver=tool_rules_solver,
sources=agent_state.sources,
max_files_open=agent_state.max_files_open,
)
diff = united_diff(curr_system_message_openai["content"], new_system_message_str)
@@ -1831,7 +1835,11 @@ class AgentManager:
system_message = await self.message_manager.get_message_by_id_async(message_id=agent_state.message_ids[0], actor=actor)
temp_tool_rules_solver = ToolRulesSolver(agent_state.tool_rules)
if (
new_memory.compile(sources=agent_state.sources, tool_usage_rules=temp_tool_rules_solver.compile_tool_rule_prompts())
new_memory.compile(
sources=agent_state.sources,
tool_usage_rules=temp_tool_rules_solver.compile_tool_rule_prompts(),
max_files_open=agent_state.max_files_open,
)
not in system_message.content[0].text
):
# update the blocks (LRW) in the DB

View File

@@ -259,6 +259,7 @@ def compile_system_message(
archival_memory_size: int = 0,
tool_rules_solver: Optional[ToolRulesSolver] = None,
sources: Optional[List] = None,
max_files_open: Optional[int] = None,
) -> str:
"""Prepare the final/full system message that will be fed into the LLM API
@@ -291,7 +292,9 @@ def compile_system_message(
timezone=timezone,
)
memory_with_sources = in_context_memory.compile(tool_usage_rules=tool_constraint_block, sources=sources)
memory_with_sources = in_context_memory.compile(
tool_usage_rules=tool_constraint_block, sources=sources, max_files_open=max_files_open
)
full_memory_string = memory_with_sources + "\n\n" + memory_metadata_string
# Add to the variables list to inject
@@ -343,6 +346,7 @@ def initialize_message_sequence(
previous_message_count=previous_message_count,
archival_memory_size=archival_memory_size,
sources=agent_state.sources,
max_files_open=agent_state.max_files_open,
)
first_user_message = get_login_event(agent_state.timezone) # event letting Letta know the user just logged in

View File

@@ -240,6 +240,8 @@ def validate_context_window_overview(
assert attached_file.visible_content in overview.core_memory, "File must be attached in core memory"
assert '<file status="open"' in overview.core_memory
assert "</file>" in overview.core_memory
assert "max_files_open" in overview.core_memory, "Max files should be set in core memory"
assert "current_files_open" in overview.core_memory, "Current files should be set in core memory"
# Check for tools
assert overview.num_tokens_functions_definitions > 0