Improve VLM client lifecycle, retries, metrics, and cache keys

- Adds abstract aclose() and shared load_prompt() helper to BaseVLMClient.

- OllamaClient now logs latency, tokens, and prompt hash per call.

- Retries cover HTTP 429 and 5xx in addition to network/timeout errors.

- Cache key now includes response_format and max_tokens.

- Warns when JSON schema generation falls back to plain 'json' format.

- Adds FakeVLMClient.aclose() and cache unit tests.
This commit is contained in:
2026-08-21 16:44:14 +02:00
parent 17e0496d2f
commit 081084816b
5 changed files with 132 additions and 12 deletions
+44
View File
@@ -0,0 +1,44 @@
"""Tests for the LLM response cache."""
from __future__ import annotations
import pytest
from odoo_ocr.config import Settings
from odoo_ocr.utils import cache
@pytest.fixture
def cache_settings(tmp_path) -> Settings:
return Settings(cache={"enabled": True, "dir": tmp_path / "llm_cache"})
def test_cache_roundtrip(cache_settings: Settings) -> None:
response = {"content": "hello", "model": "m", "prompt_tokens": 1}
cache.set_cached("sys", "user", [b"img"], "model", 0.1, 4096, response, cache_settings)
assert cache.get_cached("sys", "user", [b"img"], "model", 0.1, 4096, cache_settings) == response
def test_cache_miss(cache_settings: Settings) -> None:
assert cache.get_cached("a", "b", [], "m", 0.1, 4096, cache_settings) is None
def test_cache_disabled(tmp_path) -> None:
settings = Settings(cache={"enabled": False, "dir": tmp_path / "llm_cache"})
cache.set_cached("sys", "user", [], "m", 0.1, 4096, {"content": "x"}, settings)
assert cache.get_cached("sys", "user", [], "m", 0.1, 4096, settings) is None
assert not (tmp_path / "llm_cache").exists()
def test_cache_key_includes_temperature_format_and_max_tokens(cache_settings: Settings) -> None:
response = {"content": "x"}
cache.set_cached("sys", "user", [], "m", 0.1, 4096, response, cache_settings, format_key="text")
# Different temperature must not hit the same entry.
assert cache.get_cached("sys", "user", [], "m", 0.2, 4096, cache_settings) is None
# Different response_format must not hit the same entry.
assert (
cache.get_cached("sys", "user", [], "m", 0.1, 4096, cache_settings, format_key="ExtractedInvoice")
is None
)
# Different max_tokens must not hit the same entry.
assert cache.get_cached("sys", "user", [], "m", 0.1, 2048, cache_settings) is None