localize delete_key_snippets with gc agent
This commit is contained in:
parent
332cbec826
commit
d4353b1824
|
|
@ -17,13 +17,66 @@ from ra_aid.agent_utils import create_agent, run_agent_with_retry
|
||||||
from ra_aid.database.repositories.key_snippet_repository import KeySnippetRepository
|
from ra_aid.database.repositories.key_snippet_repository import KeySnippetRepository
|
||||||
from ra_aid.llm import initialize_llm
|
from ra_aid.llm import initialize_llm
|
||||||
from ra_aid.prompts.key_snippets_gc_prompts import KEY_SNIPPETS_GC_PROMPT
|
from ra_aid.prompts.key_snippets_gc_prompts import KEY_SNIPPETS_GC_PROMPT
|
||||||
from ra_aid.tools.memory import delete_key_snippets, log_work_event, _global_memory
|
from ra_aid.tools.memory import log_work_event, _global_memory
|
||||||
|
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
key_snippet_repository = KeySnippetRepository()
|
key_snippet_repository = KeySnippetRepository()
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def delete_key_snippets(snippet_ids: List[int]) -> str:
|
||||||
|
"""Delete multiple key snippets from the database by their IDs.
|
||||||
|
Silently skips any IDs that don't exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
snippet_ids: List of snippet IDs to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Success or failure message
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
not_found_snippets = []
|
||||||
|
failed_snippets = []
|
||||||
|
|
||||||
|
for snippet_id in snippet_ids:
|
||||||
|
# Get the snippet first to capture filepath for the message
|
||||||
|
snippet = key_snippet_repository.get(snippet_id)
|
||||||
|
if snippet:
|
||||||
|
filepath = snippet.filepath
|
||||||
|
# Delete from database
|
||||||
|
success = key_snippet_repository.delete(snippet_id)
|
||||||
|
if success:
|
||||||
|
success_msg = f"Successfully deleted snippet #{snippet_id} from {filepath}"
|
||||||
|
console.print(
|
||||||
|
Panel(
|
||||||
|
Markdown(success_msg), title="Snippet Deleted", border_style="green"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
results.append((snippet_id, filepath))
|
||||||
|
log_work_event(f"Deleted snippet {snippet_id}.")
|
||||||
|
else:
|
||||||
|
failed_snippets.append(snippet_id)
|
||||||
|
else:
|
||||||
|
not_found_snippets.append(snippet_id)
|
||||||
|
|
||||||
|
# Prepare result message
|
||||||
|
result_parts = []
|
||||||
|
if results:
|
||||||
|
deleted_msg = "Successfully deleted snippets:\n" + "\n".join([f"- #{snippet_id}: {filepath}" for snippet_id, filepath in results])
|
||||||
|
result_parts.append(deleted_msg)
|
||||||
|
|
||||||
|
if not_found_snippets:
|
||||||
|
not_found_msg = f"Snippets not found: {', '.join([f'#{snippet_id}' for snippet_id in not_found_snippets])}"
|
||||||
|
result_parts.append(not_found_msg)
|
||||||
|
|
||||||
|
if failed_snippets:
|
||||||
|
failed_msg = f"Failed to delete snippets: {', '.join([f'#{snippet_id}' for snippet_id in failed_snippets])}"
|
||||||
|
result_parts.append(failed_msg)
|
||||||
|
|
||||||
|
return "Snippets deleted."
|
||||||
|
|
||||||
|
|
||||||
def run_key_snippets_gc_agent() -> None:
|
def run_key_snippets_gc_agent() -> None:
|
||||||
"""Run the key snippets gc agent to maintain a reasonable number of key snippets.
|
"""Run the key snippets gc agent to maintain a reasonable number of key snippets.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ from .fuzzy_find import fuzzy_find_project_files
|
||||||
from .human import ask_human
|
from .human import ask_human
|
||||||
from .list_directory import list_directory_tree
|
from .list_directory import list_directory_tree
|
||||||
from .memory import (
|
from .memory import (
|
||||||
delete_key_snippets,
|
|
||||||
delete_tasks,
|
delete_tasks,
|
||||||
deregister_related_files,
|
deregister_related_files,
|
||||||
emit_key_facts,
|
emit_key_facts,
|
||||||
|
|
@ -29,7 +28,6 @@ from .write_file import put_complete_file_contents
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ask_expert",
|
"ask_expert",
|
||||||
"delete_key_snippets",
|
|
||||||
"web_search_tavily",
|
"web_search_tavily",
|
||||||
"deregister_related_files",
|
"deregister_related_files",
|
||||||
"emit_expert_context",
|
"emit_expert_context",
|
||||||
|
|
|
||||||
|
|
@ -256,33 +256,6 @@ def emit_key_snippet(snippet_info: SnippetInfo) -> str:
|
||||||
return f"Snippet #{snippet_id} stored."
|
return f"Snippet #{snippet_id} stored."
|
||||||
|
|
||||||
|
|
||||||
@tool("delete_key_snippets")
|
|
||||||
def delete_key_snippets(snippet_ids: List[int]) -> str:
|
|
||||||
"""Delete multiple key snippets from the database by their IDs.
|
|
||||||
Silently skips any IDs that don't exist.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
snippet_ids: List of snippet IDs to delete
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for snippet_id in snippet_ids:
|
|
||||||
# Get the snippet first to capture filepath for the message
|
|
||||||
snippet = key_snippet_repository.get(snippet_id)
|
|
||||||
if snippet:
|
|
||||||
filepath = snippet.filepath
|
|
||||||
# Delete from database
|
|
||||||
success = key_snippet_repository.delete(snippet_id)
|
|
||||||
if success:
|
|
||||||
success_msg = f"Successfully deleted snippet #{snippet_id} from {filepath}"
|
|
||||||
console.print(
|
|
||||||
Panel(
|
|
||||||
Markdown(success_msg), title="Snippet Deleted", border_style="green"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
results.append(success_msg)
|
|
||||||
|
|
||||||
log_work_event(f"Deleted snippets {snippet_ids}.")
|
|
||||||
return "Snippets deleted."
|
|
||||||
|
|
||||||
|
|
||||||
@tool("swap_task_order")
|
@tool("swap_task_order")
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import importlib
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from ra_aid.agents.key_snippets_gc_agent import delete_key_snippets
|
||||||
from ra_aid.tools.memory import (
|
from ra_aid.tools.memory import (
|
||||||
_global_memory,
|
_global_memory,
|
||||||
delete_key_snippets,
|
|
||||||
delete_tasks,
|
delete_tasks,
|
||||||
deregister_related_files,
|
deregister_related_files,
|
||||||
emit_key_facts,
|
emit_key_facts,
|
||||||
|
|
@ -113,61 +113,66 @@ def mock_repository():
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_key_snippet_repository():
|
def mock_key_snippet_repository():
|
||||||
"""Mock the KeySnippetRepository to avoid database operations during tests"""
|
"""Mock the KeySnippetRepository to avoid database operations during tests"""
|
||||||
with patch('ra_aid.tools.memory.key_snippet_repository') as mock_repo:
|
snippets = {} # Local in-memory storage
|
||||||
# Setup the mock repository to behave like the original, but using memory
|
snippet_id_counter = 0
|
||||||
snippets = {} # Local in-memory storage
|
|
||||||
snippet_id_counter = 0
|
# Mock KeySnippet objects
|
||||||
|
class MockKeySnippet:
|
||||||
# Mock KeySnippet objects
|
def __init__(self, id, filepath, line_number, snippet, description=None):
|
||||||
class MockKeySnippet:
|
self.id = id
|
||||||
def __init__(self, id, filepath, line_number, snippet, description=None):
|
self.filepath = filepath
|
||||||
self.id = id
|
self.line_number = line_number
|
||||||
self.filepath = filepath
|
self.snippet = snippet
|
||||||
self.line_number = line_number
|
self.description = description
|
||||||
self.snippet = snippet
|
|
||||||
self.description = description
|
|
||||||
|
|
||||||
# Mock create method
|
# Mock create method
|
||||||
def mock_create(filepath, line_number, snippet, description=None):
|
def mock_create(filepath, line_number, snippet, description=None):
|
||||||
nonlocal snippet_id_counter
|
nonlocal snippet_id_counter
|
||||||
key_snippet = MockKeySnippet(snippet_id_counter, filepath, line_number, snippet, description)
|
key_snippet = MockKeySnippet(snippet_id_counter, filepath, line_number, snippet, description)
|
||||||
snippets[snippet_id_counter] = key_snippet
|
snippets[snippet_id_counter] = key_snippet
|
||||||
snippet_id_counter += 1
|
snippet_id_counter += 1
|
||||||
return key_snippet
|
return key_snippet
|
||||||
mock_repo.create.side_effect = mock_create
|
|
||||||
|
# Mock get method
|
||||||
# Mock get method
|
def mock_get(snippet_id):
|
||||||
def mock_get(snippet_id):
|
return snippets.get(snippet_id)
|
||||||
return snippets.get(snippet_id)
|
|
||||||
mock_repo.get.side_effect = mock_get
|
# Mock delete method
|
||||||
|
def mock_delete(snippet_id):
|
||||||
# Mock delete method
|
if snippet_id in snippets:
|
||||||
def mock_delete(snippet_id):
|
del snippets[snippet_id]
|
||||||
if snippet_id in snippets:
|
return True
|
||||||
del snippets[snippet_id]
|
return False
|
||||||
return True
|
|
||||||
return False
|
# Mock get_snippets_dict method
|
||||||
mock_repo.delete.side_effect = mock_delete
|
def mock_get_snippets_dict():
|
||||||
|
return {
|
||||||
# Mock get_snippets_dict method
|
snippet_id: {
|
||||||
def mock_get_snippets_dict():
|
"filepath": snippet.filepath,
|
||||||
return {
|
"line_number": snippet.line_number,
|
||||||
snippet_id: {
|
"snippet": snippet.snippet,
|
||||||
"filepath": snippet.filepath,
|
"description": snippet.description
|
||||||
"line_number": snippet.line_number,
|
|
||||||
"snippet": snippet.snippet,
|
|
||||||
"description": snippet.description
|
|
||||||
}
|
|
||||||
for snippet_id, snippet in snippets.items()
|
|
||||||
}
|
}
|
||||||
mock_repo.get_snippets_dict.side_effect = mock_get_snippets_dict
|
for snippet_id, snippet in snippets.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock get_all method
|
||||||
|
def mock_get_all():
|
||||||
|
return list(snippets.values())
|
||||||
|
|
||||||
|
# Create the actual mocks for both memory.py and key_snippets_gc_agent.py
|
||||||
|
with patch('ra_aid.tools.memory.key_snippet_repository') as memory_mock_repo, \
|
||||||
|
patch('ra_aid.agents.key_snippets_gc_agent.key_snippet_repository') as agent_mock_repo:
|
||||||
|
|
||||||
# Mock get_all method
|
# Setup both mocks with the same implementation
|
||||||
def mock_get_all():
|
for mock_repo in [memory_mock_repo, agent_mock_repo]:
|
||||||
return list(snippets.values())
|
mock_repo.create.side_effect = mock_create
|
||||||
mock_repo.get_all.side_effect = mock_get_all
|
mock_repo.get.side_effect = mock_get
|
||||||
|
mock_repo.delete.side_effect = mock_delete
|
||||||
|
mock_repo.get_snippets_dict.side_effect = mock_get_snippets_dict
|
||||||
|
mock_repo.get_all.side_effect = mock_get_all
|
||||||
|
|
||||||
yield mock_repo
|
yield memory_mock_repo
|
||||||
|
|
||||||
|
|
||||||
def test_emit_key_facts_single_fact(reset_memory, mock_repository):
|
def test_emit_key_facts_single_fact(reset_memory, mock_repository):
|
||||||
|
|
@ -342,7 +347,8 @@ def test_emit_key_snippet(reset_memory, mock_key_snippet_repository):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_delete_key_snippets(reset_memory, mock_key_snippet_repository):
|
@patch('ra_aid.agents.key_snippets_gc_agent.log_work_event')
|
||||||
|
def test_delete_key_snippets(mock_log_work_event, reset_memory, mock_key_snippet_repository):
|
||||||
"""Test deleting multiple code snippets"""
|
"""Test deleting multiple code snippets"""
|
||||||
# Mock snippets
|
# Mock snippets
|
||||||
snippets = [
|
snippets = [
|
||||||
|
|
@ -373,24 +379,26 @@ def test_delete_key_snippets(reset_memory, mock_key_snippet_repository):
|
||||||
mock_key_snippet_repository.reset_mock()
|
mock_key_snippet_repository.reset_mock()
|
||||||
|
|
||||||
# Test deleting mix of valid and invalid IDs
|
# Test deleting mix of valid and invalid IDs
|
||||||
result = delete_key_snippets.invoke({"snippet_ids": [0, 1, 999]})
|
with patch('ra_aid.agents.key_snippets_gc_agent.key_snippet_repository', mock_key_snippet_repository):
|
||||||
|
result = delete_key_snippets.invoke({"snippet_ids": [0, 1, 999]})
|
||||||
|
|
||||||
# Verify success message
|
# Verify success message
|
||||||
assert result == "Snippets deleted."
|
assert result == "Snippets deleted."
|
||||||
|
|
||||||
# Verify repository delete was called with correct IDs
|
# Verify repository delete was called with correct IDs
|
||||||
mock_key_snippet_repository.get.assert_any_call(0)
|
mock_key_snippet_repository.get.assert_any_call(0)
|
||||||
mock_key_snippet_repository.get.assert_any_call(1)
|
mock_key_snippet_repository.get.assert_any_call(1)
|
||||||
mock_key_snippet_repository.get.assert_any_call(999)
|
mock_key_snippet_repository.get.assert_any_call(999)
|
||||||
|
|
||||||
mock_key_snippet_repository.delete.assert_any_call(0)
|
mock_key_snippet_repository.delete.assert_any_call(0)
|
||||||
mock_key_snippet_repository.delete.assert_any_call(1)
|
mock_key_snippet_repository.delete.assert_any_call(1)
|
||||||
|
|
||||||
# Make sure delete wasn't called for ID 999
|
# Make sure delete wasn't called for ID 999
|
||||||
assert mock_key_snippet_repository.delete.call_count == 2
|
assert mock_key_snippet_repository.delete.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
def test_delete_key_snippets_empty(reset_memory, mock_key_snippet_repository):
|
@patch('ra_aid.agents.key_snippets_gc_agent.log_work_event')
|
||||||
|
def test_delete_key_snippets_empty(mock_log_work_event, reset_memory, mock_key_snippet_repository):
|
||||||
"""Test deleting snippets with empty ID list"""
|
"""Test deleting snippets with empty ID list"""
|
||||||
# Add a test snippet
|
# Add a test snippet
|
||||||
snippet = {
|
snippet = {
|
||||||
|
|
@ -405,11 +413,12 @@ def test_delete_key_snippets_empty(reset_memory, mock_key_snippet_repository):
|
||||||
mock_key_snippet_repository.reset_mock()
|
mock_key_snippet_repository.reset_mock()
|
||||||
|
|
||||||
# Test with empty list
|
# Test with empty list
|
||||||
result = delete_key_snippets.invoke({"snippet_ids": []})
|
with patch('ra_aid.agents.key_snippets_gc_agent.key_snippet_repository', mock_key_snippet_repository):
|
||||||
assert result == "Snippets deleted."
|
result = delete_key_snippets.invoke({"snippet_ids": []})
|
||||||
|
assert result == "Snippets deleted."
|
||||||
|
|
||||||
# Verify no call to delete method
|
# Verify no call to delete method
|
||||||
mock_key_snippet_repository.delete.assert_not_called()
|
mock_key_snippet_repository.delete.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_emit_related_files_basic(reset_memory, tmp_path):
|
def test_emit_related_files_basic(reset_memory, tmp_path):
|
||||||
|
|
@ -613,7 +622,8 @@ def test_emit_related_files_path_normalization(reset_memory, tmp_path):
|
||||||
os.chdir(original_dir)
|
os.chdir(original_dir)
|
||||||
|
|
||||||
|
|
||||||
def test_key_snippets_integration(reset_memory, tmp_path, mock_key_snippet_repository):
|
@patch('ra_aid.agents.key_snippets_gc_agent.log_work_event')
|
||||||
|
def test_key_snippets_integration(mock_log_work_event, reset_memory, tmp_path, mock_key_snippet_repository):
|
||||||
"""Integration test for key snippets functionality"""
|
"""Integration test for key snippets functionality"""
|
||||||
# Create test files
|
# Create test files
|
||||||
file1 = tmp_path / "file1.py"
|
file1 = tmp_path / "file1.py"
|
||||||
|
|
@ -665,13 +675,14 @@ def test_key_snippets_integration(reset_memory, tmp_path, mock_key_snippet_repos
|
||||||
mock_key_snippet_repository.reset_mock()
|
mock_key_snippet_repository.reset_mock()
|
||||||
|
|
||||||
# Delete some but not all snippets (0 and 2)
|
# Delete some but not all snippets (0 and 2)
|
||||||
result = delete_key_snippets.invoke({"snippet_ids": [0, 2]})
|
with patch('ra_aid.agents.key_snippets_gc_agent.key_snippet_repository', mock_key_snippet_repository):
|
||||||
assert result == "Snippets deleted."
|
result = delete_key_snippets.invoke({"snippet_ids": [0, 2]})
|
||||||
|
assert result == "Snippets deleted."
|
||||||
|
|
||||||
# Verify delete was called for the correct IDs
|
# Verify delete was called for the correct IDs
|
||||||
mock_key_snippet_repository.delete.assert_any_call(0)
|
mock_key_snippet_repository.delete.assert_any_call(0)
|
||||||
mock_key_snippet_repository.delete.assert_any_call(2)
|
mock_key_snippet_repository.delete.assert_any_call(2)
|
||||||
assert mock_key_snippet_repository.delete.call_count == 2
|
assert mock_key_snippet_repository.delete.call_count == 2
|
||||||
|
|
||||||
# Reset mock again
|
# Reset mock again
|
||||||
mock_key_snippet_repository.reset_mock()
|
mock_key_snippet_repository.reset_mock()
|
||||||
|
|
@ -705,13 +716,14 @@ def test_key_snippets_integration(reset_memory, tmp_path, mock_key_snippet_repos
|
||||||
mock_key_snippet_repository.reset_mock()
|
mock_key_snippet_repository.reset_mock()
|
||||||
|
|
||||||
# Delete remaining snippets
|
# Delete remaining snippets
|
||||||
result = delete_key_snippets.invoke({"snippet_ids": [1, 3]})
|
with patch('ra_aid.agents.key_snippets_gc_agent.key_snippet_repository', mock_key_snippet_repository):
|
||||||
assert result == "Snippets deleted."
|
result = delete_key_snippets.invoke({"snippet_ids": [1, 3]})
|
||||||
|
assert result == "Snippets deleted."
|
||||||
|
|
||||||
# Verify delete was called for the correct IDs
|
# Verify delete was called for the correct IDs
|
||||||
mock_key_snippet_repository.delete.assert_any_call(1)
|
mock_key_snippet_repository.delete.assert_any_call(1)
|
||||||
mock_key_snippet_repository.delete.assert_any_call(3)
|
mock_key_snippet_repository.delete.assert_any_call(3)
|
||||||
assert mock_key_snippet_repository.delete.call_count == 2
|
assert mock_key_snippet_repository.delete.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
def test_emit_task_with_id(reset_memory):
|
def test_emit_task_with_id(reset_memory):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue