7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
204 lines
5.2 KiB
Python
204 lines
5.2 KiB
Python
"""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
|