allow emitting facts in batches

This commit is contained in:
AI Christianson 2024-12-11 12:35:22 -05:00
parent 6fe7905a82
commit 3ac2c3c66d
7 changed files with 51 additions and 132 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ __pycache__/
.aider* .aider*
.env .env
/work /work
/dist

View File

@ -10,7 +10,7 @@ from langgraph.prebuilt import create_react_agent
from ra_aid.tools import ( from ra_aid.tools import (
ask_expert, run_shell_command, run_programming_task, ask_expert, run_shell_command, run_programming_task,
emit_research_notes, emit_plan, emit_related_file, emit_task, emit_research_notes, emit_plan, emit_related_file, emit_task,
emit_expert_context, get_memory_value, emit_key_fact, delete_key_fact, emit_expert_context, get_memory_value, emit_key_facts, delete_key_facts,
emit_key_snippet, delete_key_snippet, emit_key_snippet, delete_key_snippet,
request_implementation, read_file_tool, emit_research_subtask, request_implementation, read_file_tool, emit_research_subtask,
fuzzy_find_project_files, ripgrep_search, list_directory_tree fuzzy_find_project_files, ripgrep_search, list_directory_tree
@ -62,9 +62,9 @@ planning_memory = MemorySaver()
implementation_memory = MemorySaver() implementation_memory = MemorySaver()
# Define tool sets for each stage # Define tool sets for each stage
research_tools = [list_directory_tree, emit_research_subtask, run_shell_command, emit_expert_context, ask_expert, emit_research_notes, emit_related_file, emit_key_fact, delete_key_fact, emit_key_snippet, delete_key_snippet, request_implementation, read_file_tool, fuzzy_find_project_files, ripgrep_search] research_tools = [list_directory_tree, emit_research_subtask, run_shell_command, emit_expert_context, ask_expert, emit_research_notes, emit_related_file, emit_key_facts, delete_key_facts, emit_key_snippet, delete_key_snippet, request_implementation, read_file_tool, fuzzy_find_project_files, ripgrep_search]
planning_tools = [list_directory_tree, emit_expert_context, ask_expert, emit_plan, emit_task, emit_related_file, emit_key_fact, delete_key_fact, emit_key_snippet, delete_key_snippet, read_file_tool, fuzzy_find_project_files, ripgrep_search] planning_tools = [list_directory_tree, emit_expert_context, ask_expert, emit_plan, emit_task, emit_related_file, emit_key_facts, delete_key_facts, emit_key_snippet, delete_key_snippet, read_file_tool, fuzzy_find_project_files, ripgrep_search]
implementation_tools = [list_directory_tree, run_shell_command, emit_expert_context, ask_expert, run_programming_task, emit_related_file, emit_key_fact, delete_key_fact, emit_key_snippet, delete_key_snippet, read_file_tool, fuzzy_find_project_files, ripgrep_search] implementation_tools = [list_directory_tree, run_shell_command, emit_expert_context, ask_expert, run_programming_task, emit_related_file, emit_key_facts, delete_key_facts, emit_key_snippet, delete_key_snippet, read_file_tool, fuzzy_find_project_files, ripgrep_search]
# Create stage-specific agents with individual memory objects # Create stage-specific agents with individual memory objects
research_agent = create_react_agent(model, research_tools, checkpointer=research_memory) research_agent = create_react_agent(model, research_tools, checkpointer=research_memory)
@ -285,7 +285,8 @@ Be very thorough in your research and emit lots of snippets, key facts. If you t
research_notes=get_memory_value('research_notes'), research_notes=get_memory_value('research_notes'),
key_facts=get_memory_value('key_facts'), key_facts=get_memory_value('key_facts'),
key_snippets=get_memory_value('key_snippets'), key_snippets=get_memory_value('key_snippets'),
base_task=base_task base_task=base_task,
related_files="\n".join(related_files)
) )
# Run planning agent # Run planning agent

View File

@ -90,6 +90,9 @@ Research Notes:
{research_notes} {research_notes}
</notes> </notes>
Relevant Files:
{related_files}
Key Facts: Key Facts:
{key_facts} {key_facts}
@ -98,8 +101,8 @@ Key Snippets:
Fact Management: Fact Management:
Each fact is identified with [Fact ID: X]. Each fact is identified with [Fact ID: X].
Facts may be deleted if they become outdated, irrelevant, or duplicates. Facts may be deleted if they become outdated, irrelevant, or duplicates.
Use delete_key_fact with the specific Fact ID to remove unnecessary facts. Use delete_key_facts([id1, id2, ...]) with a list of numeric Fact IDs to remove unnecessary facts.
Snippet Management: Snippet Management:
Each snippet is identified with [Snippet ID: X]. Each snippet is identified with [Snippet ID: X].
@ -155,8 +158,8 @@ Key Snippets:
Fact Management: Fact Management:
Each fact is identified with [Fact ID: X]. Each fact is identified with [Fact ID: X].
Facts may be deleted if they become outdated, irrelevant, or duplicates. Facts may be deleted if they become outdated, irrelevant, or duplicates.
Use delete_key_fact with the specific Fact ID to remove unnecessary facts. Use delete_key_facts([id1, id2, ...]) with a list of numeric Fact IDs to remove unnecessary facts.
Snippet Management: Snippet Management:
Each snippet is identified with [Snippet ID: X]. Each snippet is identified with [Snippet ID: X].
@ -192,7 +195,7 @@ Relevant Files:
Important Notes: Important Notes:
- Focus solely on the given task and implement it as described. - Focus solely on the given task and implement it as described.
- Scale the complexity of your solution to the complexity of the request. For simple requests, keep it straightforward and minimal. For complex requests, maintain the previously planned depth. - Scale the complexity of your solution to the complexity of the request. For simple requests, keep it straightforward and minimal. For complex requests, maintain the previously planned depth.
- Use delete_key_fact to remove facts that become outdated, irrelevant, or duplicated. - Use delete_key_facts to remove facts that become outdated, irrelevant, or duplicated.
- Use emit_key_snippet to manage code sections before and after modifications as needed. - Use emit_key_snippet to manage code sections before and after modifications as needed.
- Regularly remove outdated snippets with delete_key_snippet. - Regularly remove outdated snippets with delete_key_snippet.
@ -202,9 +205,10 @@ Instructions:
{task} {task}
3. Work incrementally, validating as you go. 3. Work incrementally, validating as you go.
4. Update or remove any key facts that no longer apply. 4. Use delete_key_facts to remove any key facts that no longer apply.
5. Do not add features not explicitly required. 5. Do not add features not explicitly required.
6. Only create or modify files directly related to this task. 6. Only create or modify files directly related to this task.
7. For trivial changes, use sed and awk judiciously via the run_shell_command tool.
Once the task is complete, ensure all updated files are emitted. Once the task is complete, ensure all updated files are emitted.
""" """

View File

@ -6,17 +6,17 @@ from .fuzzy_find import fuzzy_find_project_files
from .list_directory import list_directory_tree from .list_directory import list_directory_tree
from .ripgrep import ripgrep_search from .ripgrep import ripgrep_search
from .memory import ( from .memory import (
emit_research_notes, emit_plan, emit_task, get_memory_value, emit_key_fact, emit_research_notes, emit_plan, emit_task, get_memory_value, emit_key_facts,
request_implementation, skip_implementation, delete_key_fact, emit_research_subtask, request_implementation, skip_implementation, delete_key_facts, emit_research_subtask,
emit_key_snippet, delete_key_snippet emit_key_snippet, delete_key_snippet
) )
__all__ = [ __all__ = [
'ask_expert', 'ask_expert',
'delete_key_fact', 'delete_key_facts',
'delete_key_snippet', 'delete_key_snippet',
'emit_expert_context', 'emit_expert_context',
'emit_key_fact', 'emit_key_facts',
'emit_key_snippet', 'emit_key_snippet',
'emit_plan', 'emit_plan',
'emit_related_file', 'emit_related_file',

View File

@ -86,37 +86,6 @@ def emit_research_subtask(subtask: str) -> str:
console.print(Panel(Markdown(subtask), title="🔬 Research Subtask")) console.print(Panel(Markdown(subtask), title="🔬 Research Subtask"))
return f"Added research subtask: {subtask}" return f"Added research subtask: {subtask}"
@tool("emit_key_fact")
def emit_key_fact(fact: str) -> str:
"""Store a key fact about the project or current task in global memory.
Key facts are things like:
- Specific files/functions to look at and what they do
- Coding conventions
- Specific external interfaces related to the task
Key facts should be objective and not restating things already specified in our top-level task.
They are generally things that will not change throughout the duration of our top-level task.
Args:
fact: The key fact to store
Returns:
The stored fact
"""
# Get and increment fact ID
fact_id = _global_memory['key_fact_id_counter']
_global_memory['key_fact_id_counter'] += 1
# Store fact with ID
_global_memory['key_facts'][fact_id] = fact
# Display panel with ID
console.print(Panel(Markdown(fact), title=f"💡 Key Fact #{fact_id}", border_style="bright_cyan"))
# Return fact with ID
return f"Stored fact #{fact_id}: {fact}"
@tool("emit_key_facts") @tool("emit_key_facts")
def emit_key_facts(facts: List[str]) -> List[str]: def emit_key_facts(facts: List[str]) -> List[str]:
@ -145,26 +114,6 @@ def emit_key_facts(facts: List[str]) -> List[str]:
return results return results
@tool("delete_key_fact")
def delete_key_fact(fact_id: int) -> str:
"""Delete a key fact from global memory by its ID.
Args:
fact_id: The ID of the fact to delete
Returns:
A message indicating success or failure
"""
if fact_id not in _global_memory['key_facts']:
error_msg = f"Error: No fact found with ID #{fact_id}"
console.print(Panel(Markdown(error_msg), title="❌ Delete Failed", border_style="red"))
return error_msg
# Delete the fact
deleted_fact = _global_memory['key_facts'].pop(fact_id)
success_msg = f"Successfully deleted fact #{fact_id}: {deleted_fact}"
console.print(Panel(Markdown(success_msg), title="🗑️ Fact Deleted", border_style="green"))
return success_msg
@tool("delete_key_facts") @tool("delete_key_facts")
def delete_key_facts(fact_ids: List[int]) -> List[str]: def delete_key_facts(fact_ids: List[int]) -> List[str]:

View File

@ -75,30 +75,7 @@ def run_programming_task(input: RunProgrammingTaskInput) -> Dict[str, Union[str,
"-m" "-m"
] ]
# Inject key facts into instructions if they exist command.append(input.instructions)
key_facts = get_memory_value('key_facts')
enhanced_instructions = input.instructions
# Get and format snippets if they exist
key_snippets = get_memory_value('key_snippets')
# Combine all sections
enhanced_instructions = f"""Key Facts About This Project:
{key_facts}
Key Code Snippets:
{key_snippets}
Instructions:
{input.instructions}
Only implement the immediate instructions, do not expand scope.
"""
command.append(enhanced_instructions)
# Use both input files and related files # Use both input files and related files
files_to_use = set(related_files) # Start with related files files_to_use = set(related_files) # Start with related files
@ -111,7 +88,7 @@ Only implement the immediate instructions, do not expand scope.
# Create a pretty display of what we're doing # Create a pretty display of what we're doing
task_display = [ task_display = [
"## Instructions\n", "## Instructions\n",
f"{enhanced_instructions}\n" f"{input.instructions}\n"
] ]
if files_to_use: if files_to_use:

View File

@ -1,8 +1,6 @@
import pytest import pytest
from ra_aid.tools.memory import ( from ra_aid.tools.memory import (
_global_memory, _global_memory,
emit_key_fact,
delete_key_fact,
get_memory_value, get_memory_value,
emit_research_subtask, emit_research_subtask,
emit_key_facts, emit_key_facts,
@ -27,43 +25,35 @@ def reset_memory():
_global_memory['tasks'] = [] _global_memory['tasks'] = []
_global_memory['research_subtasks'] = [] _global_memory['research_subtasks'] = []
def test_emit_key_fact(reset_memory): def test_emit_key_facts_single_fact(reset_memory):
"""Test emitting key facts with ID assignment""" """Test emitting a single key fact using emit_key_facts"""
# First fact should get ID 0 # Test with single fact
result = emit_key_fact("First fact") result = emit_key_facts.invoke({"facts": ["First fact"]})
assert result == "Stored fact #0: First fact" assert result[0] == "Stored fact #0: First fact"
assert _global_memory['key_facts'][0] == "First fact" assert _global_memory['key_facts'][0] == "First fact"
assert _global_memory['key_fact_id_counter'] == 1
# Second fact should get ID 1
result = emit_key_fact("Second fact")
assert result == "Stored fact #1: Second fact"
assert _global_memory['key_facts'][1] == "Second fact"
# Counter should be at 2
assert _global_memory['key_fact_id_counter'] == 2
def test_delete_key_fact(reset_memory): def test_delete_key_facts_single_fact(reset_memory):
"""Test deleting key facts""" """Test deleting a single key fact using delete_key_facts"""
# Add some facts # Add a fact
emit_key_fact("First fact") emit_key_facts.invoke({"facts": ["Test fact"]})
emit_key_fact("Second fact")
# Delete fact #0 # Delete the fact
result = delete_key_fact({'fact_id': 0}) result = delete_key_facts.invoke({"fact_ids": [0]})
assert result == "Successfully deleted fact #0: First fact" assert result[0] == "Successfully deleted fact #0: Test fact"
assert 0 not in _global_memory['key_facts'] assert 0 not in _global_memory['key_facts']
assert 1 in _global_memory['key_facts']
def test_delete_invalid_fact(reset_memory): def test_delete_key_facts_invalid(reset_memory):
"""Test error handling when deleting non-existent facts""" """Test deleting non-existent facts returns empty list"""
result = delete_key_fact({'fact_id': 999}) # Try to delete non-existent fact
assert result == "Error: No fact found with ID #999" result = delete_key_facts.invoke({"fact_ids": [999]})
assert result == []
# Add and delete a fact, then try to delete it again # Add and delete a fact, then try to delete it again
emit_key_fact("Test fact") emit_key_facts.invoke({"facts": ["Test fact"]})
delete_key_fact({'fact_id': 0}) delete_key_facts.invoke({"fact_ids": [0]})
result = delete_key_fact({'fact_id': 0}) result = delete_key_facts.invoke({"fact_ids": [0]})
assert result == "Error: No fact found with ID #0" assert result == []
def test_get_memory_value_key_facts(reset_memory): def test_get_memory_value_key_facts(reset_memory):
"""Test get_memory_value with key facts dictionary""" """Test get_memory_value with key facts dictionary"""
@ -71,8 +61,7 @@ def test_get_memory_value_key_facts(reset_memory):
assert get_memory_value('key_facts') == "" assert get_memory_value('key_facts') == ""
# Add some facts # Add some facts
emit_key_fact("First fact") emit_key_facts.invoke({"facts": ["First fact", "Second fact"]})
emit_key_fact("Second fact")
# Should return markdown formatted list # Should return markdown formatted list
expected = "## 🔑 Key Fact #0\n\nFirst fact\n\n## 🔑 Key Fact #1\n\nSecond fact" expected = "## 🔑 Key Fact #0\n\nFirst fact\n\n## 🔑 Key Fact #1\n\nSecond fact"
@ -96,7 +85,7 @@ def test_emit_key_facts(reset_memory):
"""Test emitting multiple key facts at once""" """Test emitting multiple key facts at once"""
# Test emitting multiple facts # Test emitting multiple facts
facts = ["First fact", "Second fact", "Third fact"] facts = ["First fact", "Second fact", "Third fact"]
results = emit_key_facts({'facts': facts}) results = emit_key_facts.invoke({"facts": facts})
# Verify return messages # Verify return messages
assert results == [ assert results == [
@ -116,12 +105,10 @@ def test_emit_key_facts(reset_memory):
def test_delete_key_facts(reset_memory): def test_delete_key_facts(reset_memory):
"""Test deleting multiple key facts""" """Test deleting multiple key facts"""
# Add some test facts # Add some test facts
emit_key_fact("First fact") emit_key_facts.invoke({"facts": ["First fact", "Second fact", "Third fact"]})
emit_key_fact("Second fact")
emit_key_fact("Third fact")
# Test deleting mix of existing and non-existing IDs # Test deleting mix of existing and non-existing IDs
results = delete_key_facts({'fact_ids': [0, 1, 999]}) results = delete_key_facts.invoke({"fact_ids": [0, 1, 999]})
# Verify only success messages for existing facts # Verify only success messages for existing facts
assert results == [ assert results == [