BL / tests /test_client.py
youssefboutaleb's picture
Support Mistral vision chat completions models and update default config
8394687
Raw History Blame Contribute Delete
4.18 kB
from __future__ import annotations
import json
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from bl_ocr.core.config import Settings
from bl_ocr.core.errors import OcrProviderError
from bl_ocr.ocr.client import MistralOcrClient
from bl_ocr.ocr.prompt import PromptAssets
def make_settings(
url: str = "https://api.mistral.ai/v1/chat/completions",
model: str = "ministral-8b-2512",
api_key: str | None = "fake-key",
) -> Settings:
return Settings(
service_name="test",
api_version="v1",
mistral_api_key=api_key,
mistral_ocr_url=url,
mistral_model=model,
mistral_strict_schema=True,
request_timeout_seconds=5.0,
max_retries=1,
max_upload_mb=1,
schema_path=Path("schema.json"),
example_path=Path("example.json"),
cors_allow_origins=("*",),
)
def test_client_requires_api_key():
settings = make_settings(api_key=None)
assets = PromptAssets.load(settings)
client = MistralOcrClient(settings=settings, assets=assets)
with pytest.raises(OcrProviderError, match="MISTRAL_API_KEY is not configured"):
client.extract(b"dummy-image", "image/jpeg")
def test_client_formats_chat_completions_payload():
settings = make_settings(
url="https://api.mistral.ai/v1/chat/completions",
model="ministral-8b-2512",
)
assets = PromptAssets.load(settings)
mock_session = MagicMock()
mock_resp = MagicMock()
mock_resp.status_code = 200
expected_content = json.dumps({"supplier": {"name": {"value": "PHARMA"}}})
mock_resp.json.return_value = {
"choices": [{"message": {"content": expected_content}}],
"id": "chat-123",
}
mock_session.post.return_value = mock_resp
client = MistralOcrClient(settings=settings, assets=assets, session=mock_session)
result = client.extract(b"image-bytes", "image/jpeg")
assert result["document_annotation"] == expected_content
assert result["raw_chat_response"]["id"] == "chat-123"
mock_session.post.assert_called_once()
called_url = mock_session.post.call_args[0][0]
called_kwargs = mock_session.post.call_args[1]
assert called_url == "https://api.mistral.ai/v1/chat/completions"
assert called_kwargs["headers"]["Authorization"] == "Bearer fake-key"
payload = called_kwargs["json"]
assert payload["model"] == "ministral-8b-2512"
assert "messages" in payload
assert len(payload["messages"]) == 1
assert payload["messages"][0]["role"] == "user"
assert payload["response_format"]["type"] == "json_schema"
assert payload["response_format"]["json_schema"]["strict"] is True
def test_client_formats_dedicated_ocr_payload():
settings = make_settings(
url="https://api.mistral.ai/v1/ocr",
model="mistral-ocr-latest",
)
assets = PromptAssets.load(settings)
mock_session = MagicMock()
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.json.return_value = {
"pages": [],
"document_annotation": '{"supplier": {}}',
}
mock_session.post.return_value = mock_resp
client = MistralOcrClient(settings=settings, assets=assets, session=mock_session)
result = client.extract(b"image-bytes", "image/jpeg")
assert result["document_annotation"] == '{"supplier": {}}'
called_kwargs = mock_session.post.call_args[1]
payload = called_kwargs["json"]
assert payload["model"] == "mistral-ocr-latest"
assert "document" in payload
assert payload["document"]["type"] == "image_url"
assert "document_annotation_format" in payload
def test_client_handles_http_errors():
settings = make_settings()
assets = PromptAssets.load(settings)
mock_session = MagicMock()
mock_resp = MagicMock()
mock_resp.status_code = 429
mock_resp.text = '{"error": "rate limit"}'
mock_session.post.return_value = mock_resp
client = MistralOcrClient(settings=settings, assets=assets, session=mock_session)
with pytest.raises(OcrProviderError) as exc_info:
client.extract(b"image-bytes", "image/jpeg")
assert exc_info.value.status_code == 429