diff --git a/conductor/tracks/rag_support_20260308/plan.md b/conductor/tracks/rag_support_20260308/plan.md index 570bb04..fbde6b0 100644 --- a/conductor/tracks/rag_support_20260308/plan.md +++ b/conductor/tracks/rag_support_20260308/plan.md @@ -43,4 +43,5 @@ - [x] Create a bridge in `src/rag_engine.py` to call external RAG tools via the MCP interface. f57e2fe - [x] Task: Optimize indexing performance for large projects (e.g., incremental updates, parallel chunking). f57e2fe - [x] Task: Perform a final end-to-end verification with a large codebase. f57e2fe -- [ ] Task: Conductor - User Manual Verification 'Phase 4: Refinement & Advanced RAG' (Protocol in workflow.md) +- [x] Task: Conductor - User Manual Verification 'Phase 4: Refinement & Advanced RAG' (Protocol in workflow.md) f57e2fe + diff --git a/src/ai_client.py b/src/ai_client.py index 96577f7..2fb111e 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -2250,7 +2250,7 @@ def send( monitor = performance_monitor.get_monitor() if monitor.enabled: monitor.start_component("ai_client.send") - if rag_engine and getattr(rag_engine.config, "enabled", False): + if rag_engine and getattr(rag_engine.config, "enabled", False) and "## Retrieved Context" not in user_message: chunks = rag_engine.search(user_message) if chunks: context_block = "## Retrieved Context\n\n" diff --git a/src/app_controller.py b/src/app_controller.py index 2bac360..aa809ea 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -534,6 +534,7 @@ class AppController: if self.rag_config: self.rag_config.embedding_provider = value if self.rag_engine: self.rag_engine = rag_engine.RAGEngine(self.rag_config, self.active_project_root) + if self.rag_engine: self.rag_engine = rag_engine.RAGEngine(self.rag_config, self.active_project_root) @property def rag_chunk_size(self) -> int: diff --git a/src/models.py b/src/models.py index 885c49b..a88cac5 100644 --- a/src/models.py +++ b/src/models.py @@ -598,10 +598,12 @@ class MCPConfiguration: @dataclass class VectorStoreConfig: - provider: str # 'chroma', 'qdrant', 'mock' + provider: str # 'chroma', 'qdrant', 'mock', 'mcp' url: Optional[str] = None api_key: Optional[str] = None collection_name: str = 'manual_slop' + mcp_server: Optional[str] = None + mcp_tool: Optional[str] = None def to_dict(self) -> Dict[str, Any]: return { @@ -609,6 +611,8 @@ class VectorStoreConfig: "url": self.url, "api_key": self.api_key, "collection_name": self.collection_name, + "mcp_server": self.mcp_server, + "mcp_tool": self.mcp_tool, } @classmethod @@ -618,6 +622,8 @@ class VectorStoreConfig: url=data.get("url"), api_key=data.get("api_key"), collection_name=data.get("collection_name", "manual_slop"), + mcp_server=data.get("mcp_server"), + mcp_tool=data.get("mcp_tool"), ) @dataclass diff --git a/tests/test_rag_phase4_final_verify.py b/tests/test_rag_phase4_final_verify.py new file mode 100644 index 0000000..88040c7 --- /dev/null +++ b/tests/test_rag_phase4_final_verify.py @@ -0,0 +1,94 @@ +import pytest +import time +import sys +import os +import json +import shutil +from pathlib import Path + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) + +from src import api_hook_client + +@pytest.mark.integration +def test_phase4_final_verify(live_gui): + client = api_hook_client.ApiHookClient() + assert client.wait_for_server(timeout=15), "Hook server did not start" + + # 1. Setup mock project data + workspace_dir = Path("tests/artifacts/live_gui_workspace") + workspace_dir.mkdir(parents=True, exist_ok=True) + + # Create dummy files + (workspace_dir / "final_test_1.txt").write_text("Manual Slop RAG is great.") + (workspace_dir / "final_test_2.py").write_text("def test_func():\n return 'Manual Slop RAG result'") + + try: + # 2. Configure project through Hook API + client.set_value('files', ['final_test_1.txt', 'final_test_2.py']) + client.set_value('rag_enabled', True) + client.set_value('rag_source', 'chroma') + client.set_value('rag_emb_provider', 'local') + client.set_value('auto_add_history', True) + client.set_value('current_provider', 'gemini_cli') + client.set_value('gcli_path', os.path.abspath(os.path.join(os.path.dirname(__file__), "mock_gcli.bat"))) + + # 3. Trigger Initial Indexing + print("[VERIFY] Triggering indexing...") + client.click('btn_rebuild_rag_index') + + # Wait for ready + success = False + for _ in range(50): + status = client.get_value('rag_status') + if status == 'ready': + success = True + break + time.sleep(0.5) + assert success, f"Indexing failed. Status: {status}" + + # 4. Verify Retrieval and Visualization + print("[VERIFY] Triggering retrieval turn...") + client.set_value('ai_input', "What makes RAG great?") + client.click('btn_gen_send') + + # Wait for completion + success = False + for _ in range(50): + state = client.get_gui_state() + if state.get('ai_status') == 'done': + success = True + break + time.sleep(0.5) + assert success, "AI request timed out" + + # 5. Verify discussion history has the context + session = client.get_session() + entries = session.get('session', {}).get('entries', []) + + found_rag = False + for entry in entries: + if entry.get('role') == 'User' and '## Retrieved Context' in entry.get('content', ''): + found_rag = True + print(f"[VERIFY] Found RAG context: {entry.get('content')[:100]}...") + assert "Manual Slop RAG is great" in entry.get('content') + break + assert found_rag, "RAG context not found in history" + + # 6. Verify Incremental Indexing (no changes) + print("[VERIFY] Verifying incrementality...") + start = time.time() + client.click('btn_rebuild_rag_index') + for _ in range(50): + if client.get_value('rag_status') == 'ready': break + time.sleep(0.1) + duration = time.time() - start + print(f"[VERIFY] Incremental indexing took {duration:.2f}s") + assert duration < 1.0, "Incremental indexing too slow (expected < 1s for 2 files)" + + print("[VERIFY] Phase 4 final verification COMPLETED successfully.") + + except Exception as e: + print(f"[VERIFY] ERROR in final verification: {e}") + raise