""" Tests for missing coverage gaps in local_deep_research/config/llm_config.py. Targets specific uncovered code paths: - get_llm env var logic (LDR_USE_FALLBACK_LLM, LDR_TESTING_WITH_MOCKS, provider_has_config skipping, lmstudio/llamacpp local checks) - get_llm ollama model-not-found, creation exception (now raises), enable_thinking true/false - get_llm llamacpp no model path, invalid extension, directory with gguf suggestion - _get_context_window_for_provider cloud unrestricted=False, local None window - wrap_llm_without_think_tags context_limit injection, no-overwrite, model_name vs model """ import os from unittest.mock import MagicMock, patch import pytest 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 reads; wrap_llm tests that also read # rate_limiting settings must patch BOTH. THREAD_SETTINGS = "local_deep_research.config.thread_settings" def _settings_dict(overrides=None): """Base settings dict with sensible defaults for most tests.""" base = { "search.tool": "searxng", "llm.model": "test-model", "llm.temperature": 0.5, "llm.provider": "ollama", "llm.local_context_window_size": 4096, "llm.context_window_unrestricted": True, "llm.supports_max_tokens": True, "llm.max_tokens": 4096, "rate_limiting.llm_enabled": False, } if overrides: base.update(overrides) return base def _mock_get_setting(settings): """Return a side_effect callable that reads from the given dict.""" return lambda key, default=None, **kw: settings.get(key, default) class TestGetLlmFallbackEnvVar: """Tests for LDR_USE_FALLBACK_LLM / LDR_TESTING_WITH_MOCKS env var guard.""" def test_fallback_env_var_skipped_when_testing_with_mocks(self): """When LDR_TESTING_WITH_MOCKS is set, fallback env var is ignored; now raises ValueError since fallback model was removed.""" settings = _settings_dict({"llm.provider": "none"}) with ( patch.dict( os.environ, {"LDR_USE_FALLBACK_LLM": "1", "LDR_TESTING_WITH_MOCKS": "1"}, ), patch(f"{MODULE}.is_llm_registered", return_value=False), patch( f"{MODULE}.get_setting_from_snapshot", side_effect=_mock_get_setting(settings), ), ): from local_deep_research.config.llm_config import get_llm with pytest.raises(ValueError): get_llm(provider="none", settings_snapshot=settings) def test_fallback_env_var_no_config_raises_error(self): """When provider has NO config, ValueError is raised (fallback model removed).""" settings = _settings_dict( {"llm.provider": "openai", "llm.openai.api_key": None} ) with ( patch.dict(os.environ, {"LDR_USE_FALLBACK_LLM": "1"}, clear=False), patch.dict(os.environ, {"LDR_TESTING_WITH_MOCKS": ""}, clear=False), patch(f"{MODULE}.is_llm_registered", return_value=False), patch( f"{MODULE}.get_setting_from_snapshot", side_effect=_mock_get_setting(settings), ), ): from local_deep_research.config.llm_config import get_llm with pytest.raises(ValueError): get_llm(provider="openai", settings_snapshot=settings) class TestGetLlmOllamaEdgeCases: """Tests for ollama-specific edge cases in get_llm.""" def _ollama_settings(self, overrides=None): s = _settings_dict( { "llm.provider": "ollama", "llm.ollama.url": "http://localhost:11434", "llm.ollama.enable_thinking": True, } ) if overrides: s.update(overrides) return s class TestGetContextWindowForProviderMissing: """Additional coverage for _get_context_window_for_provider edge cases.""" def test_cloud_unrestricted_false_returns_configured_size(self): """Cloud provider with unrestricted=False returns configured size.""" def fake_setting(key, default, settings_snapshot=None): if key == "llm.context_window_unrestricted": return False if key == "llm.context_window_size": return 64000 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("anthropic") assert result == 64000 assert isinstance(result, int) def test_local_provider_none_window_defaults_to_8192(self): """When local context window setting returns None, fallback 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_cloud_restricted_none_window_defaults_to_128000(self): """When cloud restricted but context_window_size is None, fallback to 128000.""" def fake_setting(key, default, settings_snapshot=None): 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 class TestWrapLlmContextLimitInjection: """Tests for context_limit injection in wrap_llm_without_think_tags.""" def test_injects_context_limit_for_local_provider(self): """wrap_llm sets context_limit for local provider (llamacpp).""" llm = MagicMock() research_ctx = {} def fake_setting(key, default=None, settings_snapshot=None): if key == "rate_limiting.llm_enabled": return False if key == "llm.local_context_window_size": return 8192 return default with ( patch( f"{MODULE}.get_setting_from_snapshot", side_effect=fake_setting ), patch( f"{THREAD_SETTINGS}.get_setting_from_snapshot", side_effect=fake_setting, ), ): from local_deep_research.config.llm_config import ( wrap_llm_without_think_tags, ) wrap_llm_without_think_tags( llm, provider="llamacpp", research_context=research_ctx ) assert research_ctx["context_limit"] == 8192 def test_does_not_overwrite_existing_context_limit(self): """If research_context already has context_limit, it is NOT overwritten.""" llm = MagicMock() research_ctx = {"context_limit": 32000} def fake_setting(key, default=None, settings_snapshot=None): if key == "rate_limiting.llm_enabled": return False if key == "llm.local_context_window_size": return 4096 return default with patch( f"{MODULE}.get_setting_from_snapshot", side_effect=fake_setting ): from local_deep_research.config.llm_config import ( wrap_llm_without_think_tags, ) wrap_llm_without_think_tags( llm, provider="ollama", research_context=research_ctx ) assert research_ctx["context_limit"] == 32000 def test_model_name_attribute_used_for_token_callback(self): """When llm has model_name, it is used for preset_model on token callback.""" llm = MagicMock() llm.model_name = "gpt-4o" llm.callbacks = None mock_counter = MagicMock() mock_callback = MagicMock() mock_counter.create_callback.return_value = mock_callback with ( patch(f"{MODULE}.get_setting_from_snapshot", return_value=False), patch( "local_deep_research.metrics.TokenCounter", return_value=mock_counter, ), ): from local_deep_research.config.llm_config import ( wrap_llm_without_think_tags, ) wrap_llm_without_think_tags(llm, research_id=1, provider="openai") assert mock_callback.preset_model == "gpt-4o" def test_model_attribute_fallback_for_token_callback(self): """When llm has .model but NOT .model_name, .model is used.""" llm = MagicMock(spec=["invoke", "callbacks", "model"]) llm.model = "claude-3-haiku" llm.callbacks = None mock_counter = MagicMock() mock_callback = MagicMock() mock_counter.create_callback.return_value = mock_callback with ( patch(f"{MODULE}.get_setting_from_snapshot", return_value=False), patch( "local_deep_research.metrics.TokenCounter", return_value=mock_counter, ), ): from local_deep_research.config.llm_config import ( wrap_llm_without_think_tags, ) wrap_llm_without_think_tags( llm, research_id=2, provider="anthropic" ) assert mock_callback.preset_model == "claude-3-haiku" def test_cloud_unrestricted_does_not_inject_context_limit(self): """For cloud provider with unrestricted context, context_limit is not added.""" llm = MagicMock() research_ctx = {} def fake_setting(key, default=None, settings_snapshot=None): if key == "rate_limiting.llm_enabled": return False if key == "llm.context_window_unrestricted": return True return default with ( patch( f"{MODULE}.get_setting_from_snapshot", side_effect=fake_setting ), patch( f"{THREAD_SETTINGS}.get_setting_from_snapshot", side_effect=fake_setting, ), ): from local_deep_research.config.llm_config import ( wrap_llm_without_think_tags, ) wrap_llm_without_think_tags( llm, provider="openai", research_context=research_ctx ) assert "context_limit" not in research_ctx