Navegador

navegador / tests / test_llm.py
Source Blame History 489 lines
13d6a44… lmata 1 """
13d6a44… lmata 2 Tests for navegador/llm.py — LLM backend abstraction.
13d6a44… lmata 3
13d6a44… lmata 4 All tests are fully offline. SDK imports are patched to avoid requiring
13d6a44… lmata 5 any LLM SDK to be installed in the test environment.
13d6a44… lmata 6 """
13d6a44… lmata 7
13d6a44… lmata 8 from __future__ import annotations
13d6a44… lmata 9
13d6a44… lmata 10 import sys
13d6a44… lmata 11 from unittest.mock import MagicMock, patch
13d6a44… lmata 12
13d6a44… lmata 13 import pytest
13d6a44… lmata 14
13d6a44… lmata 15
13d6a44… lmata 16 # ── Helpers ───────────────────────────────────────────────────────────────────
13d6a44… lmata 17
13d6a44… lmata 18
13d6a44… lmata 19 def _block_import(name: str):
13d6a44… lmata 20 """
13d6a44… lmata 21 Context manager that makes ``import <name>`` raise ImportError for the
13d6a44… lmata 22 duration of the block, even if the package is installed.
13d6a44… lmata 23 """
13d6a44… lmata 24
13d6a44… lmata 25 class _Blocker:
13d6a44… lmata 26 def __enter__(self):
13d6a44… lmata 27 self._original = sys.modules.get(name, None)
13d6a44… lmata 28 sys.modules[name] = None # type: ignore[assignment]
13d6a44… lmata 29 return self
13d6a44… lmata 30
13d6a44… lmata 31 def __exit__(self, *_):
13d6a44… lmata 32 if self._original is None:
13d6a44… lmata 33 sys.modules.pop(name, None)
13d6a44… lmata 34 else:
13d6a44… lmata 35 sys.modules[name] = self._original
13d6a44… lmata 36
13d6a44… lmata 37 return _Blocker()
13d6a44… lmata 38
13d6a44… lmata 39
13d6a44… lmata 40 def _fake_anthropic_module():
13d6a44… lmata 41 """Return a minimal mock that satisfies AnthropicProvider's usage."""
13d6a44… lmata 42 mod = MagicMock()
13d6a44… lmata 43 client = MagicMock()
13d6a44… lmata 44 message = MagicMock()
13d6a44… lmata 45 message.content = [MagicMock(text="hello from anthropic")]
13d6a44… lmata 46 client.messages.create.return_value = message
13d6a44… lmata 47 mod.Anthropic.return_value = client
13d6a44… lmata 48 return mod, client
13d6a44… lmata 49
13d6a44… lmata 50
13d6a44… lmata 51 def _fake_openai_module():
13d6a44… lmata 52 """Return a minimal mock that satisfies OpenAIProvider's usage."""
13d6a44… lmata 53 mod = MagicMock()
13d6a44… lmata 54 client = MagicMock()
13d6a44… lmata 55 choice = MagicMock()
13d6a44… lmata 56 choice.message.content = "hello from openai"
13d6a44… lmata 57 response = MagicMock()
13d6a44… lmata 58 response.choices = [choice]
13d6a44… lmata 59 client.chat.completions.create.return_value = response
13d6a44… lmata 60 embed_data = MagicMock()
13d6a44… lmata 61 embed_data.embedding = [0.1, 0.2, 0.3]
13d6a44… lmata 62 embed_response = MagicMock()
13d6a44… lmata 63 embed_response.data = [embed_data]
13d6a44… lmata 64 client.embeddings.create.return_value = embed_response
13d6a44… lmata 65 mod.OpenAI.return_value = client
13d6a44… lmata 66 return mod, client
13d6a44… lmata 67
13d6a44… lmata 68
13d6a44… lmata 69 def _fake_ollama_module():
13d6a44… lmata 70 """Return a minimal mock that satisfies OllamaProvider's usage."""
13d6a44… lmata 71 mod = MagicMock()
13d6a44… lmata 72 client = MagicMock()
13d6a44… lmata 73 client.chat.return_value = {"message": {"content": "hello from ollama"}}
13d6a44… lmata 74 client.embeddings.return_value = {"embedding": [0.4, 0.5, 0.6]}
13d6a44… lmata 75 mod.Client.return_value = client
13d6a44… lmata 76 return mod, client
13d6a44… lmata 77
13d6a44… lmata 78
13d6a44… lmata 79 # ── AnthropicProvider ─────────────────────────────────────────────────────────
13d6a44… lmata 80
13d6a44… lmata 81
13d6a44… lmata 82 class TestAnthropicProvider:
13d6a44… lmata 83 def test_raises_import_error_when_sdk_missing(self):
13d6a44… lmata 84 with _block_import("anthropic"):
13d6a44… lmata 85 # Remove cached module from navegador.llm so the guard re-runs
13d6a44… lmata 86 import importlib
13d6a44… lmata 87
13d6a44… lmata 88 import navegador.llm as llm_mod
13d6a44… lmata 89
13d6a44… lmata 90 importlib.reload(llm_mod)
13d6a44… lmata 91 with pytest.raises(ImportError, match="pip install anthropic"):
13d6a44… lmata 92 llm_mod.AnthropicProvider()
13d6a44… lmata 93
13d6a44… lmata 94 def test_name_is_anthropic(self):
13d6a44… lmata 95 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 96 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 97 import importlib
13d6a44… lmata 98
13d6a44… lmata 99 import navegador.llm as llm_mod
13d6a44… lmata 100
13d6a44… lmata 101 importlib.reload(llm_mod)
13d6a44… lmata 102 p = llm_mod.AnthropicProvider()
13d6a44… lmata 103 assert p.name == "anthropic"
13d6a44… lmata 104
13d6a44… lmata 105 def test_default_model(self):
13d6a44… lmata 106 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 107 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 108 import importlib
13d6a44… lmata 109
13d6a44… lmata 110 import navegador.llm as llm_mod
13d6a44… lmata 111
13d6a44… lmata 112 importlib.reload(llm_mod)
13d6a44… lmata 113 p = llm_mod.AnthropicProvider()
13d6a44… lmata 114 assert p.model == "claude-3-5-haiku-20241022"
13d6a44… lmata 115
13d6a44… lmata 116 def test_custom_model(self):
13d6a44… lmata 117 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 118 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 119 import importlib
13d6a44… lmata 120
13d6a44… lmata 121 import navegador.llm as llm_mod
13d6a44… lmata 122
13d6a44… lmata 123 importlib.reload(llm_mod)
13d6a44… lmata 124 p = llm_mod.AnthropicProvider(model="claude-opus-4")
13d6a44… lmata 125 assert p.model == "claude-opus-4"
13d6a44… lmata 126
13d6a44… lmata 127 def test_complete_returns_text(self):
13d6a44… lmata 128 fake_mod, client = _fake_anthropic_module()
13d6a44… lmata 129 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 130 import importlib
13d6a44… lmata 131
13d6a44… lmata 132 import navegador.llm as llm_mod
13d6a44… lmata 133
13d6a44… lmata 134 importlib.reload(llm_mod)
13d6a44… lmata 135 p = llm_mod.AnthropicProvider()
13d6a44… lmata 136 result = p.complete("say hello")
13d6a44… lmata 137 assert result == "hello from anthropic"
13d6a44… lmata 138 client.messages.create.assert_called_once()
13d6a44… lmata 139
13d6a44… lmata 140 def test_complete_passes_max_tokens(self):
13d6a44… lmata 141 fake_mod, client = _fake_anthropic_module()
13d6a44… lmata 142 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 143 import importlib
13d6a44… lmata 144
13d6a44… lmata 145 import navegador.llm as llm_mod
13d6a44… lmata 146
13d6a44… lmata 147 importlib.reload(llm_mod)
13d6a44… lmata 148 p = llm_mod.AnthropicProvider()
13d6a44… lmata 149 p.complete("hi", max_tokens=512)
13d6a44… lmata 150 _, kwargs = client.messages.create.call_args
13d6a44… lmata 151 assert kwargs["max_tokens"] == 512
13d6a44… lmata 152
13d6a44… lmata 153 def test_embed_raises_not_implemented(self):
13d6a44… lmata 154 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 155 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 156 import importlib
13d6a44… lmata 157
13d6a44… lmata 158 import navegador.llm as llm_mod
13d6a44… lmata 159
13d6a44… lmata 160 importlib.reload(llm_mod)
13d6a44… lmata 161 p = llm_mod.AnthropicProvider()
13d6a44… lmata 162 with pytest.raises(NotImplementedError):
13d6a44… lmata 163 p.embed("text")
13d6a44… lmata 164
13d6a44… lmata 165
13d6a44… lmata 166 # ── OpenAIProvider ────────────────────────────────────────────────────────────
13d6a44… lmata 167
13d6a44… lmata 168
13d6a44… lmata 169 class TestOpenAIProvider:
13d6a44… lmata 170 def test_raises_import_error_when_sdk_missing(self):
13d6a44… lmata 171 with _block_import("openai"):
13d6a44… lmata 172 import importlib
13d6a44… lmata 173
13d6a44… lmata 174 import navegador.llm as llm_mod
13d6a44… lmata 175
13d6a44… lmata 176 importlib.reload(llm_mod)
13d6a44… lmata 177 with pytest.raises(ImportError, match="pip install openai"):
13d6a44… lmata 178 llm_mod.OpenAIProvider()
13d6a44… lmata 179
13d6a44… lmata 180 def test_name_is_openai(self):
13d6a44… lmata 181 fake_mod, _ = _fake_openai_module()
13d6a44… lmata 182 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 183 import importlib
13d6a44… lmata 184
13d6a44… lmata 185 import navegador.llm as llm_mod
13d6a44… lmata 186
13d6a44… lmata 187 importlib.reload(llm_mod)
13d6a44… lmata 188 p = llm_mod.OpenAIProvider()
13d6a44… lmata 189 assert p.name == "openai"
13d6a44… lmata 190
13d6a44… lmata 191 def test_default_model(self):
13d6a44… lmata 192 fake_mod, _ = _fake_openai_module()
13d6a44… lmata 193 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 194 import importlib
13d6a44… lmata 195
13d6a44… lmata 196 import navegador.llm as llm_mod
13d6a44… lmata 197
13d6a44… lmata 198 importlib.reload(llm_mod)
13d6a44… lmata 199 p = llm_mod.OpenAIProvider()
13d6a44… lmata 200 assert p.model == "gpt-4o-mini"
13d6a44… lmata 201
13d6a44… lmata 202 def test_custom_model(self):
13d6a44… lmata 203 fake_mod, _ = _fake_openai_module()
13d6a44… lmata 204 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 205 import importlib
13d6a44… lmata 206
13d6a44… lmata 207 import navegador.llm as llm_mod
13d6a44… lmata 208
13d6a44… lmata 209 importlib.reload(llm_mod)
13d6a44… lmata 210 p = llm_mod.OpenAIProvider(model="gpt-4o")
13d6a44… lmata 211 assert p.model == "gpt-4o"
13d6a44… lmata 212
13d6a44… lmata 213 def test_complete_returns_text(self):
13d6a44… lmata 214 fake_mod, client = _fake_openai_module()
13d6a44… lmata 215 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 216 import importlib
13d6a44… lmata 217
13d6a44… lmata 218 import navegador.llm as llm_mod
13d6a44… lmata 219
13d6a44… lmata 220 importlib.reload(llm_mod)
13d6a44… lmata 221 p = llm_mod.OpenAIProvider()
13d6a44… lmata 222 result = p.complete("say hello")
13d6a44… lmata 223 assert result == "hello from openai"
13d6a44… lmata 224 client.chat.completions.create.assert_called_once()
13d6a44… lmata 225
13d6a44… lmata 226 def test_embed_returns_list_of_floats(self):
13d6a44… lmata 227 fake_mod, client = _fake_openai_module()
13d6a44… lmata 228 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 229 import importlib
13d6a44… lmata 230
13d6a44… lmata 231 import navegador.llm as llm_mod
13d6a44… lmata 232
13d6a44… lmata 233 importlib.reload(llm_mod)
13d6a44… lmata 234 p = llm_mod.OpenAIProvider()
13d6a44… lmata 235 result = p.embed("hello world")
13d6a44… lmata 236 assert result == [0.1, 0.2, 0.3]
13d6a44… lmata 237 client.embeddings.create.assert_called_once()
13d6a44… lmata 238
13d6a44… lmata 239
13d6a44… lmata 240 # ── OllamaProvider ────────────────────────────────────────────────────────────
13d6a44… lmata 241
13d6a44… lmata 242
13d6a44… lmata 243 class TestOllamaProvider:
13d6a44… lmata 244 def test_raises_import_error_when_sdk_missing(self):
13d6a44… lmata 245 with _block_import("ollama"):
13d6a44… lmata 246 import importlib
13d6a44… lmata 247
13d6a44… lmata 248 import navegador.llm as llm_mod
13d6a44… lmata 249
13d6a44… lmata 250 importlib.reload(llm_mod)
13d6a44… lmata 251 with pytest.raises(ImportError, match="pip install ollama"):
13d6a44… lmata 252 llm_mod.OllamaProvider()
13d6a44… lmata 253
13d6a44… lmata 254 def test_name_is_ollama(self):
13d6a44… lmata 255 fake_mod, _ = _fake_ollama_module()
13d6a44… lmata 256 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 257 import importlib
13d6a44… lmata 258
13d6a44… lmata 259 import navegador.llm as llm_mod
13d6a44… lmata 260
13d6a44… lmata 261 importlib.reload(llm_mod)
13d6a44… lmata 262 p = llm_mod.OllamaProvider()
13d6a44… lmata 263 assert p.name == "ollama"
13d6a44… lmata 264
13d6a44… lmata 265 def test_default_model(self):
13d6a44… lmata 266 fake_mod, _ = _fake_ollama_module()
13d6a44… lmata 267 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 268 import importlib
13d6a44… lmata 269
13d6a44… lmata 270 import navegador.llm as llm_mod
13d6a44… lmata 271
13d6a44… lmata 272 importlib.reload(llm_mod)
13d6a44… lmata 273 p = llm_mod.OllamaProvider()
13d6a44… lmata 274 assert p.model == "llama3.2"
13d6a44… lmata 275
13d6a44… lmata 276 def test_custom_model(self):
13d6a44… lmata 277 fake_mod, _ = _fake_ollama_module()
13d6a44… lmata 278 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 279 import importlib
13d6a44… lmata 280
13d6a44… lmata 281 import navegador.llm as llm_mod
13d6a44… lmata 282
13d6a44… lmata 283 importlib.reload(llm_mod)
13d6a44… lmata 284 p = llm_mod.OllamaProvider(model="mistral")
13d6a44… lmata 285 assert p.model == "mistral"
13d6a44… lmata 286
13d6a44… lmata 287 def test_complete_returns_text(self):
13d6a44… lmata 288 fake_mod, client = _fake_ollama_module()
13d6a44… lmata 289 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 290 import importlib
13d6a44… lmata 291
13d6a44… lmata 292 import navegador.llm as llm_mod
13d6a44… lmata 293
13d6a44… lmata 294 importlib.reload(llm_mod)
13d6a44… lmata 295 p = llm_mod.OllamaProvider()
13d6a44… lmata 296 result = p.complete("say hello")
13d6a44… lmata 297 assert result == "hello from ollama"
13d6a44… lmata 298 client.chat.assert_called_once()
13d6a44… lmata 299
13d6a44… lmata 300 def test_embed_returns_list_of_floats(self):
13d6a44… lmata 301 fake_mod, client = _fake_ollama_module()
13d6a44… lmata 302 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 303 import importlib
13d6a44… lmata 304
13d6a44… lmata 305 import navegador.llm as llm_mod
13d6a44… lmata 306
13d6a44… lmata 307 importlib.reload(llm_mod)
13d6a44… lmata 308 p = llm_mod.OllamaProvider()
13d6a44… lmata 309 result = p.embed("hello world")
13d6a44… lmata 310 assert result == [0.4, 0.5, 0.6]
13d6a44… lmata 311 client.embeddings.assert_called_once()
13d6a44… lmata 312
13d6a44… lmata 313
13d6a44… lmata 314 # ── discover_providers ────────────────────────────────────────────────────────
13d6a44… lmata 315
13d6a44… lmata 316
13d6a44… lmata 317 class TestDiscoverProviders:
13d6a44… lmata 318 def _reload(self, modules: dict):
13d6a44… lmata 319 import importlib
13d6a44… lmata 320
13d6a44… lmata 321 import navegador.llm as llm_mod
13d6a44… lmata 322
13d6a44… lmata 323 importlib.reload(llm_mod)
13d6a44… lmata 324 return llm_mod
13d6a44… lmata 325
13d6a44… lmata 326 def test_all_available(self):
13d6a44… lmata 327 fake_a, _ = _fake_anthropic_module()
13d6a44… lmata 328 fake_o, _ = _fake_openai_module()
13d6a44… lmata 329 fake_ol, _ = _fake_ollama_module()
13d6a44… lmata 330 with patch.dict(
13d6a44… lmata 331 sys.modules,
13d6a44… lmata 332 {"anthropic": fake_a, "openai": fake_o, "ollama": fake_ol},
13d6a44… lmata 333 ):
13d6a44… lmata 334 llm_mod = self._reload({})
13d6a44… lmata 335 result = llm_mod.discover_providers()
13d6a44… lmata 336 assert result == ["anthropic", "openai", "ollama"]
13d6a44… lmata 337
13d6a44… lmata 338 def test_only_openai_available(self):
13d6a44… lmata 339 fake_o, _ = _fake_openai_module()
13d6a44… lmata 340 with (
13d6a44… lmata 341 _block_import("anthropic"),
13d6a44… lmata 342 patch.dict(sys.modules, {"openai": fake_o}),
13d6a44… lmata 343 _block_import("ollama"),
13d6a44… lmata 344 ):
13d6a44… lmata 345 llm_mod = self._reload({})
13d6a44… lmata 346 result = llm_mod.discover_providers()
13d6a44… lmata 347 assert result == ["openai"]
13d6a44… lmata 348
13d6a44… lmata 349 def test_none_available(self):
13d6a44… lmata 350 with _block_import("anthropic"), _block_import("openai"), _block_import("ollama"):
13d6a44… lmata 351 llm_mod = self._reload({})
13d6a44… lmata 352 result = llm_mod.discover_providers()
13d6a44… lmata 353 assert result == []
13d6a44… lmata 354
13d6a44… lmata 355 def test_preserves_priority_order(self):
13d6a44… lmata 356 fake_a, _ = _fake_anthropic_module()
13d6a44… lmata 357 fake_ol, _ = _fake_ollama_module()
13d6a44… lmata 358 with (
13d6a44… lmata 359 patch.dict(sys.modules, {"anthropic": fake_a, "ollama": fake_ol}),
13d6a44… lmata 360 _block_import("openai"),
13d6a44… lmata 361 ):
13d6a44… lmata 362 llm_mod = self._reload({})
13d6a44… lmata 363 result = llm_mod.discover_providers()
13d6a44… lmata 364 assert result == ["anthropic", "ollama"]
13d6a44… lmata 365
13d6a44… lmata 366
13d6a44… lmata 367 # ── get_provider ──────────────────────────────────────────────────────────────
13d6a44… lmata 368
13d6a44… lmata 369
13d6a44… lmata 370 class TestGetProvider:
13d6a44… lmata 371 def _reload(self):
13d6a44… lmata 372 import importlib
13d6a44… lmata 373
13d6a44… lmata 374 import navegador.llm as llm_mod
13d6a44… lmata 375
13d6a44… lmata 376 importlib.reload(llm_mod)
13d6a44… lmata 377 return llm_mod
13d6a44… lmata 378
13d6a44… lmata 379 def test_returns_anthropic_provider(self):
13d6a44… lmata 380 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 381 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 382 llm_mod = self._reload()
13d6a44… lmata 383 p = llm_mod.get_provider("anthropic")
13d6a44… lmata 384 assert p.name == "anthropic"
13d6a44… lmata 385
13d6a44… lmata 386 def test_returns_openai_provider(self):
13d6a44… lmata 387 fake_mod, _ = _fake_openai_module()
13d6a44… lmata 388 with patch.dict(sys.modules, {"openai": fake_mod}):
13d6a44… lmata 389 llm_mod = self._reload()
13d6a44… lmata 390 p = llm_mod.get_provider("openai")
13d6a44… lmata 391 assert p.name == "openai"
13d6a44… lmata 392
13d6a44… lmata 393 def test_returns_ollama_provider(self):
13d6a44… lmata 394 fake_mod, _ = _fake_ollama_module()
13d6a44… lmata 395 with patch.dict(sys.modules, {"ollama": fake_mod}):
13d6a44… lmata 396 llm_mod = self._reload()
13d6a44… lmata 397 p = llm_mod.get_provider("ollama")
13d6a44… lmata 398 assert p.name == "ollama"
13d6a44… lmata 399
13d6a44… lmata 400 def test_passes_model_argument(self):
13d6a44… lmata 401 fake_mod, _ = _fake_anthropic_module()
13d6a44… lmata 402 with patch.dict(sys.modules, {"anthropic": fake_mod}):
13d6a44… lmata 403 llm_mod = self._reload()
13d6a44… lmata 404 p = llm_mod.get_provider("anthropic", model="claude-opus-4")
13d6a44… lmata 405 assert p.model == "claude-opus-4"
13d6a44… lmata 406
13d6a44… lmata 407 def test_unknown_provider_raises_value_error(self):
13d6a44… lmata 408 import importlib
13d6a44… lmata 409
13d6a44… lmata 410 import navegador.llm as llm_mod
13d6a44… lmata 411
13d6a44… lmata 412 importlib.reload(llm_mod)
13d6a44… lmata 413 with pytest.raises(ValueError, match="Unknown LLM provider"):
13d6a44… lmata 414 llm_mod.get_provider("grok")
13d6a44… lmata 415
13d6a44… lmata 416 def test_unknown_provider_message_includes_valid_options(self):
13d6a44… lmata 417 import importlib
13d6a44… lmata 418
13d6a44… lmata 419 import navegador.llm as llm_mod
13d6a44… lmata 420
13d6a44… lmata 421 importlib.reload(llm_mod)
13d6a44… lmata 422 with pytest.raises(ValueError, match="anthropic"):
13d6a44… lmata 423 llm_mod.get_provider("nonexistent")
13d6a44… lmata 424
13d6a44… lmata 425
13d6a44… lmata 426 # ── auto_provider ─────────────────────────────────────────────────────────────
13d6a44… lmata 427
13d6a44… lmata 428
13d6a44… lmata 429 class TestAutoProvider:
13d6a44… lmata 430 def _reload(self):
13d6a44… lmata 431 import importlib
13d6a44… lmata 432
13d6a44… lmata 433 import navegador.llm as llm_mod
13d6a44… lmata 434
13d6a44… lmata 435 importlib.reload(llm_mod)
13d6a44… lmata 436 return llm_mod
13d6a44… lmata 437
13d6a44… lmata 438 def test_prefers_anthropic_when_all_available(self):
13d6a44… lmata 439 fake_a, _ = _fake_anthropic_module()
13d6a44… lmata 440 fake_o, _ = _fake_openai_module()
13d6a44… lmata 441 fake_ol, _ = _fake_ollama_module()
13d6a44… lmata 442 with patch.dict(
13d6a44… lmata 443 sys.modules,
13d6a44… lmata 444 {"anthropic": fake_a, "openai": fake_o, "ollama": fake_ol},
13d6a44… lmata 445 ):
13d6a44… lmata 446 llm_mod = self._reload()
13d6a44… lmata 447 p = llm_mod.auto_provider()
13d6a44… lmata 448 assert p.name == "anthropic"
13d6a44… lmata 449
13d6a44… lmata 450 def test_falls_back_to_openai_when_anthropic_missing(self):
13d6a44… lmata 451 fake_o, _ = _fake_openai_module()
13d6a44… lmata 452 fake_ol, _ = _fake_ollama_module()
13d6a44… lmata 453 with (
13d6a44… lmata 454 _block_import("anthropic"),
13d6a44… lmata 455 patch.dict(sys.modules, {"openai": fake_o, "ollama": fake_ol}),
13d6a44… lmata 456 ):
13d6a44… lmata 457 llm_mod = self._reload()
13d6a44… lmata 458 p = llm_mod.auto_provider()
13d6a44… lmata 459 assert p.name == "openai"
13d6a44… lmata 460
13d6a44… lmata 461 def test_falls_back_to_ollama_when_anthropic_and_openai_missing(self):
13d6a44… lmata 462 fake_ol, _ = _fake_ollama_module()
13d6a44… lmata 463 with (
13d6a44… lmata 464 _block_import("anthropic"),
13d6a44… lmata 465 _block_import("openai"),
13d6a44… lmata 466 patch.dict(sys.modules, {"ollama": fake_ol}),
13d6a44… lmata 467 ):
13d6a44… lmata 468 llm_mod = self._reload()
13d6a44… lmata 469 p = llm_mod.auto_provider()
13d6a44… lmata 470 assert p.name == "ollama"
13d6a44… lmata 471
13d6a44… lmata 472 def test_raises_runtime_error_when_no_sdk_available(self):
13d6a44… lmata 473 with _block_import("anthropic"), _block_import("openai"), _block_import("ollama"):
13d6a44… lmata 474 llm_mod = self._reload()
13d6a44… lmata 475 with pytest.raises(RuntimeError, match="No LLM SDK is installed"):
13d6a44… lmata 476 llm_mod.auto_provider()
13d6a44… lmata 477
13d6a44… lmata 478 def test_runtime_error_message_includes_install_hints(self):
13d6a44… lmata 479 with _block_import("anthropic"), _block_import("openai"), _block_import("ollama"):
13d6a44… lmata 480 llm_mod = self._reload()
13d6a44… lmata 481 with pytest.raises(RuntimeError, match="pip install"):
13d6a44… lmata 482 llm_mod.auto_provider()
13d6a44… lmata 483
13d6a44… lmata 484 def test_passes_model_to_provider(self):
13d6a44… lmata 485 fake_a, _ = _fake_anthropic_module()
13d6a44… lmata 486 with patch.dict(sys.modules, {"anthropic": fake_a}):
13d6a44… lmata 487 llm_mod = self._reload()
13d6a44… lmata 488 p = llm_mod.auto_provider(model="claude-opus-4")
13d6a44… lmata 489 assert p.model == "claude-opus-4"

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button