"""Tests for the LLM registry module.""" import pytest from langchain_core.language_models import BaseChatModel from langchain_core.messages import AIMessage from langchain_core.outputs import ChatGeneration, ChatResult from local_deep_research.llm import ( clear_llm_registry, get_llm_from_registry, is_llm_registered, list_registered_llms, register_llm, unregister_llm, ) class MockLLM(BaseChatModel): """Mock LLM for testing.""" name: str = "mock" def _generate(self, messages, **kwargs): """Generate mock response.""" message = AIMessage(content=f"Mock response from {self.name}") generation = ChatGeneration(message=message) return ChatResult(generations=[generation]) @property def _llm_type(self): return "mock" @pytest.fixture(autouse=True) def clear_registry(): """Clear the registry before and after each test.""" clear_llm_registry() yield clear_llm_registry() def test_register_llm_instance(): """Test registering an LLM instance.""" llm = MockLLM(name="test-llm") # Register the LLM register_llm("test_model", llm) # Check it's registered assert is_llm_registered("test_model") assert "test_model" in list_registered_llms() # Retrieve it retrieved = get_llm_from_registry("test_model") assert retrieved is llm def test_register_llm_factory(): """Test registering an LLM factory function.""" def create_llm(**kwargs): return MockLLM(name="factory-llm", **kwargs) # Register the factory register_llm("factory_model", create_llm) # Check it's registered assert is_llm_registered("factory_model") # Retrieve the factory factory = get_llm_from_registry("factory_model") assert callable(factory) # Create an LLM from the factory llm = factory() assert isinstance(llm, MockLLM) assert llm.name == "factory-llm" def test_unregister_llm(): """Test unregistering an LLM.""" llm = MockLLM() register_llm("temp_model", llm) # Verify it's registered assert is_llm_registered("temp_model") # Unregister it unregister_llm("temp_model") # Verify it's gone assert not is_llm_registered("temp_model") assert "temp_model" not in list_registered_llms() assert get_llm_from_registry("temp_model") is None def test_multiple_llms(): """Test registering multiple LLMs.""" llm1 = MockLLM(name="llm1") llm2 = MockLLM(name="llm2") register_llm("model1", llm1) register_llm("model2", llm2) # Check both are registered registered = list_registered_llms() assert len(registered) == 2 assert "model1" in registered assert "model2" in registered # Retrieve them assert get_llm_from_registry("model1") is llm1 assert get_llm_from_registry("model2") is llm2 def test_overwrite_existing(): """Test overwriting an existing LLM.""" llm1 = MockLLM(name="original") llm2 = MockLLM(name="replacement") # Register first LLM register_llm("model", llm1) assert get_llm_from_registry("model") is llm1 # Overwrite with second LLM register_llm("model", llm2) assert get_llm_from_registry("model") is llm2 def test_clear_registry(): """Test clearing all registered LLMs.""" # Register multiple LLMs register_llm("model1", MockLLM()) register_llm("model2", MockLLM()) register_llm("model3", MockLLM()) assert len(list_registered_llms()) == 3 # Clear the registry clear_llm_registry() # Verify all are gone assert len(list_registered_llms()) == 0 assert not is_llm_registered("model1") assert not is_llm_registered("model2") assert not is_llm_registered("model3") def test_thread_safety(): """Test that registry operations are thread-safe.""" import threading import time results = [] errors = [] def register_many(): """Register many LLMs in a thread.""" try: for i in range(100): register_llm(f"thread_model_{i}", MockLLM(name=f"thread-{i}")) results.append("register_complete") except Exception as e: errors.append(e) def read_many(): """Read from registry in a thread.""" try: for _ in range(100): _ = list_registered_llms() time.sleep(0.001) # Small delay to increase contention results.append("read_complete") except Exception as e: errors.append(e) # Start multiple threads threads = [] for _ in range(3): threads.append(threading.Thread(target=register_many)) threads.append(threading.Thread(target=read_many)) for t in threads: t.start() for t in threads: t.join() # Check no errors occurred assert len(errors) == 0 assert len(results) == 6 # 3 register + 3 read threads def test_get_nonexistent(): """Test getting a non-existent LLM.""" assert get_llm_from_registry("does_not_exist") is None assert not is_llm_registered("does_not_exist") def test_empty_registry(): """Test operations on empty registry.""" assert list_registered_llms() == [] assert not is_llm_registered("any_model") assert get_llm_from_registry("any_model") is None