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

350 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Deep coverage tests for llm_config.py targeting uncovered branches.
Focuses on:
- get_llm() with registered custom LLMs (callable factory and BaseChatModel instance)
- get_llm() with invalid provider
- ProcessingLLMWrapper.close()
- _get_context_window_for_provider() with None window_size from settings
"""
from unittest.mock import MagicMock, patch
import pytest
from langchain_core.language_models import BaseChatModel
MODULE = "local_deep_research.config.llm_config"
# Context-window resolution delegates to the _helpers twin, which reads
# settings via thread_settings.get_setting_from_snapshot. Patch this module
# (not MODULE) for context-window tests.
THREAD_SETTINGS = "local_deep_research.config.thread_settings"
def _make_settings_snapshot(provider="openai", model="gpt-4", **extra):
snap = {
"llm.provider": provider,
"llm.model": model,
"llm.temperature": 0.7,
"llm.max_tokens": 4096,
"llm.supports_max_tokens": True,
"llm.context_window_unrestricted": True,
"rate_limiting.llm_enabled": False,
}
snap.update(extra)
return snap
# ---------------------------------------------------------------------------
# get_llm with registered custom LLM callable factory
# ---------------------------------------------------------------------------
class TestGetLlmRegisteredFactory:
def test_callable_factory_invoked(self):
mock_instance = MagicMock(spec=BaseChatModel)
mock_factory = MagicMock(return_value=mock_instance)
# Make isinstance(mock_factory, BaseChatModel) return False
# mock_factory is already a MagicMock (not BaseChatModel)
with (
patch(f"{MODULE}.is_llm_registered", return_value=True),
patch(f"{MODULE}.get_llm_from_registry", return_value=mock_factory),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
patch(f"{MODULE}.wrap_llm_without_think_tags") as mock_wrap,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "my_custom",
"rate_limiting.llm_enabled": False,
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
get_llm(
provider="my_custom",
settings_snapshot={"search.tool": "searxng"},
)
mock_factory.assert_called_once()
mock_wrap.assert_called_once()
def test_factory_returning_non_basechatmodel_raises(self):
mock_factory = MagicMock(return_value="not_a_model")
with (
patch(f"{MODULE}.is_llm_registered", return_value=True),
patch(f"{MODULE}.get_llm_from_registry", return_value=mock_factory),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "my_custom",
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
with pytest.raises(ValueError, match="must return a BaseChatModel"):
get_llm(
provider="my_custom",
settings_snapshot={"search.tool": "searxng"},
)
def test_factory_with_bad_signature_raises_type_error(self):
def bad_factory():
return MagicMock(spec=BaseChatModel)
with (
patch(f"{MODULE}.is_llm_registered", return_value=True),
patch(f"{MODULE}.get_llm_from_registry", return_value=bad_factory),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "bad_factory",
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
with pytest.raises(TypeError, match="invalid signature"):
get_llm(
provider="bad_factory",
settings_snapshot={"search.tool": "searxng"},
)
def test_registered_basechatmodel_instance_used_directly(self):
mock_instance = MagicMock(spec=BaseChatModel)
with (
patch(f"{MODULE}.is_llm_registered", return_value=True),
patch(
f"{MODULE}.get_llm_from_registry", return_value=mock_instance
),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
patch(f"{MODULE}.wrap_llm_without_think_tags") as mock_wrap,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "my_instance",
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
get_llm(
provider="my_instance",
settings_snapshot={"search.tool": "searxng"},
)
# The factory path is skipped for BaseChatModel instances
mock_wrap.assert_called_once()
def test_registered_invalid_type_raises_value_error(self):
"""Registered object that is not BaseChatModel and not callable raises ValueError."""
class WeirdThing:
pass
with (
patch(f"{MODULE}.is_llm_registered", return_value=True),
patch(f"{MODULE}.get_llm_from_registry", return_value=WeirdThing()),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "weird",
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
with pytest.raises(
ValueError, match="must be either a BaseChatModel"
):
get_llm(
provider="weird",
settings_snapshot={"search.tool": "searxng"},
)
# ---------------------------------------------------------------------------
# get_llm with invalid provider
# ---------------------------------------------------------------------------
class TestGetLlmInvalidProvider:
def test_invalid_provider_raises_value_error(self):
with (
patch(f"{MODULE}.is_llm_registered", return_value=False),
patch(f"{MODULE}.get_setting_from_snapshot") as mock_setting,
):
mock_setting.side_effect = (
lambda key, default=None, settings_snapshot=None: {
"llm.model": "gpt-4",
"llm.temperature": 0.7,
"llm.provider": "bogus_provider",
"llm.supports_max_tokens": False,
"llm.context_window_unrestricted": True,
"rate_limiting.llm_enabled": False,
}.get(key, default)
)
from local_deep_research.config.llm_config import get_llm
with pytest.raises(ValueError, match="Invalid provider"):
get_llm(
provider="bogus_provider",
settings_snapshot={"search.tool": "searxng"},
)
# ---------------------------------------------------------------------------
# _get_context_window_for_provider edge cases
# ---------------------------------------------------------------------------
class TestContextWindowEdgeCases:
def test_none_window_size_for_local_provider_defaults_to_8192(self):
"""If local_context_window_size returns None, defaults to 8192."""
with patch(
f"{THREAD_SETTINGS}.get_setting_from_snapshot",
return_value=None,
):
from local_deep_research.config.llm_config import (
_get_context_window_for_provider,
)
result = _get_context_window_for_provider("ollama")
assert result == 8192
def test_none_window_size_for_cloud_restricted_defaults_to_128000(self):
"""Restricted cloud: if context_window_size returns None, defaults to 128000."""
call_num = [0]
def fake_setting(key, default=None, settings_snapshot=None):
call_num[0] += 1
if key == "llm.context_window_unrestricted":
return False
if key == "llm.context_window_size":
return None
return default
with patch(
f"{THREAD_SETTINGS}.get_setting_from_snapshot",
side_effect=fake_setting,
):
from local_deep_research.config.llm_config import (
_get_context_window_for_provider,
)
result = _get_context_window_for_provider("openai")
assert result == 128000
def test_llamacpp_is_local_provider(self):
"""llamacpp is in the local provider list."""
with patch(
f"{THREAD_SETTINGS}.get_setting_from_snapshot",
return_value=8192,
):
from local_deep_research.config.llm_config import (
_get_context_window_for_provider,
)
result = _get_context_window_for_provider("llamacpp")
assert result == 8192
# ---------------------------------------------------------------------------
# ProcessingLLMWrapper.close()
# ---------------------------------------------------------------------------
class TestProcessingLLMWrapperClose:
def _make_wrapper(self, mock_llm):
with patch(f"{MODULE}.get_setting_from_snapshot", return_value=False):
from local_deep_research.config.llm_config import (
wrap_llm_without_think_tags,
)
return wrap_llm_without_think_tags(mock_llm)
def test_close_called_without_error(self):
mock_llm = MagicMock()
wrapper = self._make_wrapper(mock_llm)
with patch(
"local_deep_research.utilities.llm_utils._close_base_llm"
) as mock_close:
wrapper.close()
mock_close.assert_called_once_with(mock_llm)
def test_close_swallows_exceptions(self):
mock_llm = MagicMock()
wrapper = self._make_wrapper(mock_llm)
with patch(
"local_deep_research.utilities.llm_utils._close_base_llm",
side_effect=RuntimeError("close failed"),
):
# Should not raise
wrapper.close()
# ---------------------------------------------------------------------------
# get_llm model/provider name cleaning
# ---------------------------------------------------------------------------
class TestGetLlmNameCleaning:
def test_model_name_stripped_and_unquoted(self):
"""Model name with surrounding quotes/whitespace is cleaned."""
captured = {}
def fake_setting(key, default=None, settings_snapshot=None):
return {
"llm.model": ' "gpt-4" ',
"llm.temperature": 0.7,
"llm.provider": "openai",
"llm.openai.api_key": "sk-test",
"llm.supports_max_tokens": False,
"llm.context_window_unrestricted": True,
"rate_limiting.llm_enabled": False,
}.get(key, default)
with (
patch(f"{MODULE}.is_llm_registered", return_value=False),
patch(
f"{MODULE}.get_setting_from_snapshot", side_effect=fake_setting
),
patch(
"langchain_openai.ChatOpenAI.__init__", return_value=None
) as mock_init,
patch(
f"{MODULE}.wrap_llm_without_think_tags",
return_value=MagicMock(),
),
):
try:
from local_deep_research.config.llm_config import get_llm
get_llm(settings_snapshot={"search.tool": "searxng"})
if mock_init.call_args:
captured["model"] = mock_init.call_args.kwargs.get(
"model",
mock_init.call_args.args[0]
if mock_init.call_args.args
else None,
)
except Exception:
pass # ChatOpenAI init may fail, we just care about name cleaning