test(providers): Mistral batch retry + retry-log detail + comment (#893 r4)

Round-4 review on PR #893 (no blockers, minor items):
- Document why Mistral's _is_transient is SDK-level only (429/5xx): a bare
  connection drop the SDK surfaces as httpx/ConnectionError isn't an SDKError
  and isn't retried here by design — the pod-rollover target is the gateway
  (OpenAI-compatible) path, which does cover connection errors.
- Include the last error (%r) in the retry helper's "not resolved after N
  attempts" error log.
- Add test_mistral_embed_batch_retries_on_5xx (batch path parity with embed()).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-11 06:45:56 +02:00
co-authored by Claude Opus 4.8
parent c4b6d4a017
commit 81f7403b12
3 changed files with 31 additions and 2 deletions
+20
View File
@@ -353,3 +353,23 @@ async def test_mistral_embed_retries_on_5xx(mock_mistral_client, monkeypatch):
embedding = await provider.embed("hello")
assert embedding == [0.1, 0.2, 0.3]
assert mock_mistral_client.embeddings.create_async.await_count == 2
@pytest.mark.unit
async def test_mistral_embed_batch_retries_on_5xx(mock_mistral_client, monkeypatch):
"""The batch path (_embed_batch_request) shares the transient retry too."""
from nextcloud_mcp_server.providers import _retry
monkeypatch.setattr(_retry.anyio, "sleep", AsyncMock(return_value=None))
err = SDKError.__new__(SDKError)
err.status_code = 503
mock_mistral_client.embeddings.create_async = AsyncMock(
side_effect=[err, _make_response([[0.1, 0.2], [0.3, 0.4]])]
)
provider = MistralProvider(api_key="test-key", embedding_model="mistral-embed")
embeddings = await provider.embed_batch(["a", "b"])
assert embeddings == [[0.1, 0.2], [0.3, 0.4]]
assert mock_mistral_client.embeddings.create_async.await_count == 2