allow emitting facts in batches
This commit is contained in:
parent
6fe7905a82
commit
3ac2c3c66d
|
|
@ -4,3 +4,4 @@ __pycache__/
|
||||||
.aider*
|
.aider*
|
||||||
.env
|
.env
|
||||||
/work
|
/work
|
||||||
|
/dist
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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]:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 == [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue