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:
JamesTheGiblet 2026-01-06 22:04:37 +00:00
parent 743f9f311d
commit f9fd27d228
28 changed files with 2398 additions and 4077 deletions

0
tests/__init__.py Normal file
View file

29
tests/test_all.py Normal file
View 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)

View file

@ -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)

View file

@ -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)

View file

@ -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
View 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()