Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

315 lines
9.7 KiB
Python

"""Tests for custom LLM integration with API functions."""
from typing import List
from unittest.mock import MagicMock, patch
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatResult
from pydantic import Field
from local_deep_research.api import (
detailed_research,
generate_report,
quick_summary,
)
from local_deep_research.llm import clear_llm_registry, is_llm_registered
class CustomTestLLM(BaseChatModel):
"""Custom LLM for API testing."""
identifier: str = Field(default="custom")
messages_received: List[List[BaseMessage]] = Field(default_factory=list)
def _generate(self, messages, **kwargs):
"""Generate response and track messages."""
self.messages_received.append(messages)
response = f"Response from {self.identifier}: {len(messages)} messages"
message = AIMessage(content=response)
generation = ChatGeneration(message=message)
return ChatResult(generations=[generation])
@property
def _llm_type(self):
return f"custom_{self.identifier}"
@pytest.fixture(autouse=True)
def clear_registry():
"""Clear the registry before and after each test."""
clear_llm_registry()
yield
clear_llm_registry()
@pytest.fixture
def mock_search_system():
"""Create a mock search system."""
system = MagicMock()
system.analyze_topic.return_value = {
"current_knowledge": "Mock research summary",
"findings": ["Finding 1", "Finding 2"],
"iterations": 2,
"questions": {"iteration_1": ["Q1", "Q2"]},
"formatted_findings": "Formatted findings",
"all_links_of_system": ["http://example.com"],
}
system.model = MagicMock()
return system
def test_quick_summary_with_custom_llm(mock_search_system):
"""Test quick_summary with a custom LLM."""
llm = CustomTestLLM(identifier="quick")
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_init.return_value = mock_search_system
result = quick_summary(
query="Test query",
llms={"my_llm": llm},
provider="my_llm",
temperature=0.5,
)
# Verify LLM was registered
assert is_llm_registered("my_llm")
# Verify result structure
assert "summary" in result
assert result["summary"] == "Mock research summary"
assert len(result["findings"]) == 2
# Verify init was called
assert mock_init.called
# The parameters are passed through kwargs
# Just verify the LLM was registered and used
def test_multiple_llms_registration(mock_search_system):
"""Test registering multiple LLMs at once."""
llm1 = CustomTestLLM(identifier="llm1")
llm2 = CustomTestLLM(identifier="llm2")
llm3 = CustomTestLLM(identifier="llm3")
llms = {"model1": llm1, "model2": llm2, "model3": llm3}
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_init.return_value = mock_search_system
quick_summary(
query="Test multiple LLMs",
llms=llms,
provider="model2", # Use the second one
)
# Verify all were registered
assert is_llm_registered("model1")
assert is_llm_registered("model2")
assert is_llm_registered("model3")
# Verify init was called
assert mock_init.called
# The LLMs were registered and that's what matters
def test_detailed_research_with_custom_llm(mock_search_system):
"""Test detailed_research with custom LLM."""
llm = CustomTestLLM(identifier="detailed")
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_init.return_value = mock_search_system
result = detailed_research(
query="Detailed test query",
llms={"detail_llm": llm},
provider="detail_llm",
iterations=3,
research_id="test-123",
)
# Verify result
assert result["summary"] == "Mock research summary"
assert "findings" in result
# Verify research context was set - patch before calling the function
with (
patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init,
patch(
"local_deep_research.api.research_functions.set_search_context"
) as mock_context,
):
mock_init.return_value = mock_search_system
detailed_research(
query="Context test",
llms={"ctx_llm": llm},
provider="ctx_llm",
research_id="ctx-123",
)
# Check context was set with correct research_id
assert mock_context.called
context_call = mock_context.call_args[0][0]
assert context_call["research_id"] == "ctx-123"
assert context_call["research_mode"] == "detailed"
def test_generate_report_with_custom_llm():
"""Test generate_report with custom LLM."""
llm = CustomTestLLM(identifier="report")
# Patch the entire flow to avoid real execution
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
# Set up the mock system with a properly mocked model
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "Initial findings",
"findings": ["Finding 1", "Finding 2"],
}
# Mock the model's invoke method properly
mock_model = MagicMock()
mock_response = MagicMock()
mock_response.content = "Report structure"
mock_model.invoke.return_value = mock_response
mock_system.model = mock_model
mock_init.return_value = mock_system
# Call the function
result = generate_report(
query="Report test query",
llms={"report_llm": llm},
provider="report_llm",
searches_per_section=3,
)
# Verify custom LLM was registered
assert is_llm_registered("report_llm")
# Verify init was called
assert mock_init.called
# Verify we got a report back
assert "content" in result
assert isinstance(result["content"], str)
def test_llm_factory_in_api():
"""Test using a factory function with API."""
factory_calls = []
def create_custom_llm(model_name=None, temperature=0.7, **kwargs):
factory_calls.append(
{
"model_name": model_name,
"temperature": temperature,
"extra": kwargs,
}
)
return CustomTestLLM(identifier=f"factory-{model_name}")
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "Factory test",
"findings": [],
"iterations": 1,
"questions": {},
"formatted_findings": "",
"all_links_of_system": [],
}
mock_init.return_value = mock_system
# Call quick_summary with factory
quick_summary(
query="Factory test",
llms={"factory_llm": create_custom_llm},
provider="factory_llm",
model_name="test-v1",
temperature=0.2,
)
# Verify the factory was registered
assert is_llm_registered("factory_llm")
# Verify init was called
assert mock_init.called
# The factory was registered and that's what matters
def test_combining_custom_llms_and_retrievers():
"""Test using both custom LLMs and retrievers."""
llm = CustomTestLLM(identifier="combined")
mock_retriever = MagicMock()
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
with patch(
"local_deep_research.web_search_engines.retriever_registry.retriever_registry"
) as mock_reg:
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "Combined test"
}
mock_init.return_value = mock_system
quick_summary(
query="Combined test",
llms={"custom_llm": llm},
retrievers={"custom_retriever": mock_retriever},
provider="custom_llm",
search_tool="custom_retriever",
)
# Verify both were registered
assert is_llm_registered("custom_llm")
mock_reg.register_multiple.assert_called_once()
# Verify init was called
assert mock_init.called
# The LLMs and retrievers were registered and that's what matters
def test_api_without_custom_llms():
"""Test that API still works without custom LLMs."""
with patch(
"local_deep_research.api.research_functions._init_search_system"
) as mock_init:
mock_system = MagicMock()
mock_system.analyze_topic.return_value = {
"current_knowledge": "No custom LLM"
}
mock_init.return_value = mock_system
# Call without llms parameter
result = quick_summary(
query="No custom LLM test",
provider="ollama", # Use built-in provider
model_name="llama2",
)
# Should work normally
assert "summary" in result
# Verify init was called
assert mock_init.called
# Since no custom LLMs were provided, none should be registered