test(embedding): guard the remote-service never-loads-local invariant (D4a) - #317
Conversation
…allback Regression test for design item D4a / test-matrix row 8: a deployment configured for the remote embedding service (REFLEXIO_EMBEDDING_DAEMON_HOST set, authoritative local_service mode, no probe) must never silently load an in-process NomicEmbedder/LocalEmbedder as a hidden fallback when the remote service fails. A failing remote must raise EmbeddingUnavailableError (upstream: store-fail-loud / query-degrade-to-FTS), not stampede N data-plane instances into loading the model in-process (fleet-wide OOM). Test-only; the invariant already holds. Covers both the batch get_embeddings (remote ConnectError) and single get_embedding (remote 5xx) paths, asserting the mode is local_service (not inprocess) and that every local-embedder entry point (module-bound names + real classes' get/_load) is never called.
📝 WalkthroughWalkthroughThis PR adds a new test class to the embedding service provider test suite that verifies remote embedding failures raise ChangesRemote Failure No-Fallback Test Coverage
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/server/llm/test_embedding_service_provider.py (1)
859-891: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAdd
time.sleeppatch to the second test for consistency with the batch test.The first test patches
esp.time.sleepto no-op with the comment "keep the retry loop fast," indicating the retry loop usestime.sleepfor backoff. The second test omits this patch. If the 5xx failure also triggers retries, this test will sleep through real backoff delays unnecessarily, slowing the suite.♻️ Proposed fix
self._configure_remote_service(monkeypatch) model = "local/nomic-embed-text-v1.5" assert embedding_provider_mode(model) == "local_service" guards = self._guard_local_embedders(monkeypatch) + # No backoff sleep needed — keep the retry loop fast. + monkeypatch.setattr(esp.time, "sleep", lambda _s: None) class _ServerErrorResponse:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/server/llm/test_embedding_service_provider.py` around lines 859 - 891, The single-text 5xx failure test in test_single_remote_5xx_raises_and_never_loads_local should also stub out the retry backoff sleep for consistency with the batch case. Add the same time.sleep no-op patch used in the other test before exercising LiteLLMClient.get_embedding, so the retry loop stays fast while still verifying EmbeddingUnavailableError and that the local embedder guards are never called.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/server/llm/test_embedding_service_provider.py`:
- Around line 859-891: The single-text 5xx failure test in
test_single_remote_5xx_raises_and_never_loads_local should also stub out the
retry backoff sleep for consistency with the batch case. Add the same time.sleep
no-op patch used in the other test before exercising
LiteLLMClient.get_embedding, so the retry loop stays fast while still verifying
EmbeddingUnavailableError and that the local embedder guards are never called.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5234d2af-2917-45e0-8633-fbeb0ff40884
📒 Files selected for processing (1)
tests/server/llm/test_embedding_service_provider.py
What
Regression guard for a safety invariant of the embedding subsystem: a deployment configured for the remote embedding service (
REFLEXIO_EMBEDDING_DAEMON_HOSTset →local_servicemode) must NEVER silently load a local in-process embedder as a hidden fallback. On remote failure it must raiseEmbeddingUnavailableError(→ store fail-loud / query degrade-to-FTS), not quietly instantiateNomicEmbedder/LocalEmbedder.Why
Design item D4a / test-matrix row 8 of the embedding-stability redesign. Concretely: a self-host customer running N API instances against a shared GPU embedding service must not have a service blip trigger an N-way in-process CPU model-load stampede (wrong device, ~1GB each, fleet-wide OOM). This is the "no silent fallback" lesson from the co-located races, amplified at scale.
Invariant status: HOLDS (test-only, no source change)
Traced the path with
DAEMON_HOSTset + alocal/*model:embedding_provider_mode()returnslocal_serviceauthoritatively (no/healthprobe → can't re-resolve toinprocess);_post_embedding_batchraisesEmbeddingUnavailableErroron every HTTP failure class;_embed_textsreturns from the service path before theNomicEmbedder/LocalEmbedderbranches are considered. The 3× mode resolution is deterministic (pure env check with DAEMON_HOST), so no TOCTOU window. The serviceEmbeddingUnavailableErrorpropagates raw (not re-wrapped asLiteLLMClientError).Test
tests/server/llm/test_embedding_service_provider.py::TestRemoteServiceNoLocalFallback— batch + single-text paths. Each asserts the resolved mode islocal_service(so it can't degrade into testing nothing), patches every local-embedder entry point (NomicEmbedder/LocalEmbedder.get/._load) to raise if invoked, forces the remote HTTP call to fail (ConnectError / 503), assertsEmbeddingUnavailableError, andassert_not_called()proves no local embedder was touched. 2 new tests; 236 existing pass; ruff + pyright clean.Summary by CodeRabbit
local/*models when remote resolution is enforced.EmbeddingUnavailableErrorwithout loading in-process embedders.