mirror of
https://github.com/JamesTheGiblet/BuddAI.git
synced 2026-01-08 21:58:40 +00:00
Implement core skills: Code validation, model fine-tuning, and system diagnostics
- Added `ModelFineTuner` class for preparing training data and fine-tuning models based on user corrections. - Introduced `CodeValidator` class to validate generated code against various hardware and style rules, including safety checks and function naming conventions. - Developed skills for calculator operations, system information retrieval, weather fetching, and timer functionality. - Implemented a self-diagnostic skill to run unit tests and report results. - Created a dynamic skill loading mechanism to discover and register skills from the current directory. - Added unit tests for skills to ensure functionality and reliability.
This commit is contained in:
parent
743f9f311d
commit
f9fd27d228
28 changed files with 2398 additions and 4077 deletions
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
29
tests/test_all.py
Normal file
29
tests/test_all.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import unittest
|
||||
import sys
|
||||
import os
|
||||
|
||||
def run_suite():
|
||||
"""
|
||||
Discover and run all tests in the tests/ directory.
|
||||
"""
|
||||
# Get directories
|
||||
tests_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(tests_dir)
|
||||
|
||||
# Add project root to sys.path to allow imports of 'core', 'skills', etc.
|
||||
if project_root not in sys.path:
|
||||
sys.path.insert(0, project_root)
|
||||
|
||||
# Discover tests
|
||||
loader = unittest.TestLoader()
|
||||
suite = loader.discover(tests_dir, pattern="test_*.py", top_level_dir=project_root)
|
||||
|
||||
# Run tests
|
||||
runner = unittest.TextTestRunner(verbosity=2)
|
||||
result = runner.run(suite)
|
||||
|
||||
return result.wasSuccessful()
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_suite()
|
||||
sys.exit(0 if success else 1)
|
||||
|
|
@ -22,10 +22,10 @@ import http.client
|
|||
|
||||
# Dynamic import of buddai_v3.2.py
|
||||
REPO_ROOT = Path(__file__).parent.parent
|
||||
MODULE_PATH = REPO_ROOT / "buddai_v3.2.py"
|
||||
spec = importlib.util.spec_from_file_location("buddai_v3_2", MODULE_PATH)
|
||||
MODULE_PATH = REPO_ROOT / "buddai_executive.py"
|
||||
spec = importlib.util.spec_from_file_location("buddai_executive", MODULE_PATH)
|
||||
buddai_module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["buddai_v3_2"] = buddai_module
|
||||
sys.modules["buddai_executive"] = buddai_module
|
||||
spec.loader.exec_module(buddai_module)
|
||||
BuddAI = buddai_module.BuddAI
|
||||
|
||||
|
|
@ -564,12 +564,12 @@ def test_schedule_awareness():
|
|||
print_test("Schedule Awareness")
|
||||
|
||||
# Mock datetime to test different times
|
||||
with patch('buddai_v3_2.datetime') as mock_date:
|
||||
with patch('core.buddai_personality.datetime') as mock_date:
|
||||
# 1. Early Morning (Monday 6:00 AM)
|
||||
mock_date.now.return_value = datetime(2025, 12, 29, 6, 0, 0)
|
||||
|
||||
buddai = BuddAI(server_mode=False)
|
||||
status = buddai.get_user_status()
|
||||
status = buddai.personality_manager.get_user_status()
|
||||
|
||||
if "Early Morning" in status:
|
||||
print_pass(f"6:00 AM Mon -> {status}")
|
||||
|
|
@ -579,7 +579,7 @@ def test_schedule_awareness():
|
|||
|
||||
# 2. Work Hours (Monday 10:00 AM)
|
||||
mock_date.now.return_value = datetime(2025, 12, 29, 10, 0, 0)
|
||||
status = buddai.get_user_status()
|
||||
status = buddai.personality_manager.get_user_status()
|
||||
|
||||
if "Work Hours" in status:
|
||||
print_pass(f"10:00 AM Mon -> {status}")
|
||||
|
|
@ -619,7 +619,7 @@ def test_session_management():
|
|||
test_db = Path(test_db_path)
|
||||
|
||||
try:
|
||||
with patch('buddai_v3_2.DB_PATH', test_db):
|
||||
with patch('buddai_executive.DB_PATH', test_db):
|
||||
buddai = BuddAI(server_mode=False)
|
||||
|
||||
# 1. Create
|
||||
|
|
@ -665,8 +665,8 @@ def test_rapid_session_creation():
|
|||
# Mock datetime to return a fixed time, forcing ID collisions
|
||||
fixed_time = datetime(2025, 1, 1, 12, 0, 0)
|
||||
|
||||
with patch('buddai_v3_2.DB_PATH', test_db):
|
||||
with patch('buddai_v3_2.datetime') as mock_dt:
|
||||
with patch('buddai_executive.DB_PATH', test_db):
|
||||
with patch('buddai_executive.datetime') as mock_dt:
|
||||
mock_dt.now.return_value = fixed_time
|
||||
|
||||
buddai = BuddAI(server_mode=False)
|
||||
|
|
@ -711,7 +711,7 @@ def test_repo_isolation():
|
|||
(repo_path / "user1_secret.py").write_text("def user1_secret_function():\n pass")
|
||||
|
||||
try:
|
||||
with patch('buddai_v3_2.DB_PATH', test_db):
|
||||
with patch('buddai_executive.DB_PATH', test_db):
|
||||
# Suppress internal prints to keep test output clean
|
||||
with patch('builtins.print'):
|
||||
# User 1 indexes the repo
|
||||
|
|
@ -815,7 +815,7 @@ def test_websocket_logic():
|
|||
test_db = Path(test_db_path)
|
||||
|
||||
try:
|
||||
with patch('buddai_v3_2.DB_PATH', test_db):
|
||||
with patch('buddai_executive.DB_PATH', test_db):
|
||||
# Suppress prints during init
|
||||
with patch('builtins.print'):
|
||||
buddai = BuddAI(server_mode=False)
|
||||
|
|
@ -932,7 +932,7 @@ def test_feedback_system():
|
|||
test_db = Path(test_db_path)
|
||||
|
||||
try:
|
||||
with patch('buddai_v3_2.DB_PATH', test_db):
|
||||
with patch('buddai_executive.DB_PATH', test_db):
|
||||
# Suppress prints
|
||||
with patch('builtins.print'):
|
||||
buddai = BuddAI(server_mode=False)
|
||||
|
|
|
|||
|
|
@ -10,18 +10,19 @@ import importlib.util
|
|||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from unittest.mock import MagicMock, patch
|
||||
from core.buddai_prompt_engine import PromptEngine
|
||||
|
||||
# Load buddai_v3.2.py dynamically due to version number in filename
|
||||
REPO_ROOT = Path(__file__).parent.parent
|
||||
MODULE_PATH = REPO_ROOT / "buddai_v3.2.py"
|
||||
MODULE_PATH = REPO_ROOT / "buddai_executive.py"
|
||||
|
||||
if not MODULE_PATH.exists():
|
||||
print(f"Error: Could not find {MODULE_PATH}")
|
||||
sys.exit(1)
|
||||
|
||||
spec = importlib.util.spec_from_file_location("buddai_v3_2", MODULE_PATH)
|
||||
spec = importlib.util.spec_from_file_location("buddai_executive", MODULE_PATH)
|
||||
buddai_module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["buddai_v3_2"] = buddai_module
|
||||
sys.modules["buddai_executive"] = buddai_module
|
||||
spec.loader.exec_module(buddai_module)
|
||||
|
||||
BuddAI = buddai_module.BuddAI
|
||||
|
|
@ -52,20 +53,20 @@ class TestBuddAITypesAndLogic(unittest.TestCase):
|
|||
self.assertEqual(chat_hints['return'], str)
|
||||
|
||||
# is_complex
|
||||
self.assertEqual(BuddAI.is_complex.__annotations__['return'], bool)
|
||||
self.assertEqual(PromptEngine.is_complex.__annotations__['return'], bool)
|
||||
|
||||
# extract_modules
|
||||
self.assertEqual(BuddAI.extract_modules.__annotations__['return'], List[str])
|
||||
self.assertEqual(PromptEngine.extract_modules.__annotations__['return'], List[str])
|
||||
|
||||
# build_modular_plan
|
||||
self.assertEqual(BuddAI.build_modular_plan.__annotations__['return'], List[Dict[str, str]])
|
||||
self.assertEqual(PromptEngine.build_modular_plan.__annotations__['return'], List[Dict[str, str]])
|
||||
|
||||
def test_routing_simple_question(self):
|
||||
"""Test that simple questions route to the FAST model"""
|
||||
with patch.object(self.buddai, 'call_model', return_value="Fast response") as mock_call:
|
||||
response = self.buddai._route_request("What is a servo?", force_model=None, forge_mode="2")
|
||||
|
||||
mock_call.assert_called_with("fast", "What is a servo?")
|
||||
mock_call.assert_called_with("fast", "What is a servo?", system_task=True)
|
||||
self.assertEqual(response, "Fast response")
|
||||
|
||||
def test_routing_complex_request(self):
|
||||
|
|
@ -74,7 +75,7 @@ class TestBuddAITypesAndLogic(unittest.TestCase):
|
|||
|
||||
with patch.object(self.buddai, 'execute_modular_build', return_value="Modular code") as mock_build:
|
||||
# Mock is_complex to ensure it returns True for this test case
|
||||
with patch.object(self.buddai, 'is_complex', return_value=True):
|
||||
with patch.object(self.buddai.prompt_engine, 'is_complex', return_value=True):
|
||||
response = self.buddai._route_request(complex_msg, force_model=None, forge_mode="2")
|
||||
|
||||
mock_build.assert_called()
|
||||
|
|
@ -84,11 +85,11 @@ class TestBuddAITypesAndLogic(unittest.TestCase):
|
|||
"""Test that search queries route to repository search"""
|
||||
search_msg = "Show me functions using applyForge"
|
||||
|
||||
with patch.object(self.buddai, 'search_repositories', return_value="Search results") as mock_search:
|
||||
with patch.object(self.buddai.repo_manager, 'search_repositories', return_value="Search results") as mock_search:
|
||||
# Mock is_search_query to ensure True
|
||||
with patch.object(self.buddai, 'is_search_query', return_value=True):
|
||||
with patch.object(self.buddai.repo_manager, 'is_search_query', return_value=True):
|
||||
# Ensure is_complex is False so it doesn't preempt search
|
||||
with patch.object(self.buddai, 'is_complex', return_value=False):
|
||||
with patch.object(self.buddai.prompt_engine, 'is_complex', return_value=False):
|
||||
response = self.buddai._route_request(search_msg, force_model=None, forge_mode="2")
|
||||
|
||||
mock_search.assert_called_with(search_msg)
|
||||
|
|
@ -105,7 +106,7 @@ class TestBuddAITypesAndLogic(unittest.TestCase):
|
|||
def test_extract_modules(self):
|
||||
"""Verify module extraction logic"""
|
||||
msg = "I need a robot with bluetooth and a flipper weapon"
|
||||
modules = self.buddai.extract_modules(msg)
|
||||
modules = self.buddai.prompt_engine.extract_modules(msg)
|
||||
self.assertIn("ble", modules)
|
||||
self.assertIn("servo", modules)
|
||||
self.assertNotIn("motor", modules)
|
||||
|
|
|
|||
|
|
@ -18,18 +18,25 @@ import json
|
|||
|
||||
# Dynamic import of buddai_v3.2.py
|
||||
REPO_ROOT = Path(__file__).parent.parent
|
||||
MODULE_PATH = REPO_ROOT / "buddai_v3.2.py"
|
||||
spec = importlib.util.spec_from_file_location("buddai_v3_2", MODULE_PATH)
|
||||
MODULE_PATH = REPO_ROOT / "buddai_executive.py"
|
||||
spec = importlib.util.spec_from_file_location("buddai_executive", MODULE_PATH)
|
||||
buddai_module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["buddai_v3_2"] = buddai_module
|
||||
sys.modules["buddai_executive"] = buddai_module
|
||||
spec.loader.exec_module(buddai_module)
|
||||
|
||||
# Check for server dependencies
|
||||
SERVER_AVAILABLE = getattr(buddai_module, "SERVER_AVAILABLE", False)
|
||||
|
||||
if SERVER_AVAILABLE:
|
||||
# Load buddai_server.py dynamically to get 'app'
|
||||
SERVER_PATH = REPO_ROOT / "buddai_server.py"
|
||||
spec_server = importlib.util.spec_from_file_location("buddai_server", SERVER_PATH)
|
||||
server_module = importlib.util.module_from_spec(spec_server)
|
||||
sys.modules["buddai_server"] = server_module
|
||||
spec_server.loader.exec_module(server_module)
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
app = buddai_module.app
|
||||
app = server_module.app
|
||||
client = TestClient(app)
|
||||
else:
|
||||
print("⚠️ Server dependencies missing. Integration tests skipped.")
|
||||
|
|
@ -43,7 +50,7 @@ class TestBuddAIIntegration(unittest.TestCase):
|
|||
os.close(self.db_fd)
|
||||
|
||||
# Patch DB_PATH in the module
|
||||
self.db_patcher = patch("buddai_v3_2.DB_PATH", Path(self.db_path))
|
||||
self.db_patcher = patch("buddai_executive.DB_PATH", Path(self.db_path))
|
||||
self.mock_db_path = self.db_patcher.start()
|
||||
|
||||
# Reset the manager to ensure fresh BuddAI instances connected to temp DB
|
||||
|
|
@ -143,9 +150,9 @@ class TestBuddAIIntegration(unittest.TestCase):
|
|||
def test_upload_api(self):
|
||||
"""Test file upload endpoint"""
|
||||
with tempfile.TemporaryDirectory() as tmp_data_dir:
|
||||
with patch("buddai_v3_2.DATA_DIR", Path(tmp_data_dir)):
|
||||
with patch("buddai_executive.DATA_DIR", Path(tmp_data_dir)):
|
||||
# Mock indexing to avoid parsing logic
|
||||
with patch.object(buddai_module.BuddAI, 'index_local_repositories') as mock_index:
|
||||
with patch.object(buddai_module.RepoManager, 'index_local_repositories') as mock_index:
|
||||
|
||||
# Create dummy file
|
||||
files = {'file': ('test.py', b'print("hello")', 'text/x-python')}
|
||||
|
|
|
|||
56
tests/test_skills.py
Normal file
56
tests/test_skills.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path so we can import 'skills'
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from skills import load_registry
|
||||
|
||||
class TestSkills(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.registry = load_registry()
|
||||
|
||||
def test_registry_loading(self):
|
||||
"""Ensure skills are discovered and loaded"""
|
||||
self.assertGreater(len(self.registry), 0, "No skills loaded")
|
||||
# Check for core skills
|
||||
self.assertIn("calculator", self.registry)
|
||||
self.assertIn("weather", self.registry)
|
||||
self.assertIn("timer", self.registry)
|
||||
self.assertIn("system_info", self.registry)
|
||||
self.assertIn("test_all", self.registry)
|
||||
|
||||
def test_calculator_logic(self):
|
||||
"""Verify calculator skill math"""
|
||||
calc = self.registry["calculator"]["run"]
|
||||
self.assertIn("4", calc("Calculate 2 + 2"))
|
||||
self.assertIn("25", calc("5 * 5"))
|
||||
self.assertIsNone(calc("No math here"))
|
||||
|
||||
def test_timer_parsing(self):
|
||||
"""Verify timer parses duration correctly"""
|
||||
timer = self.registry["timer"]["run"]
|
||||
# We use 0 seconds to avoid waiting during tests
|
||||
response = timer("Set a timer for 0 seconds")
|
||||
self.assertIn("Timer started", response)
|
||||
|
||||
@patch('urllib.request.urlopen')
|
||||
def test_weather_mock(self, mock_urlopen):
|
||||
"""Verify weather skill with mocked network"""
|
||||
# Mock the API response so tests work offline
|
||||
mock_response = MagicMock()
|
||||
mock_response.status = 200
|
||||
mock_response.read.return_value = b"London: +15C"
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
mock_urlopen.return_value = mock_response
|
||||
|
||||
weather = self.registry["weather"]["run"]
|
||||
result = weather("Weather in London")
|
||||
|
||||
self.assertIn("London: +15C", result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue