test(e2e): make dynamic model provisioning robust on split deployments - #32670
Conversation
create_model now waits until the new deployment is servable on the data plane (polls /v1/models) before returning, instead of assuming /model/new makes it instantly callable. On a split control/data-plane proxy the gateway only sees a model after its next DB reload, so an immediate call raced the reload and 400'd with "Invalid model name passed" (embeddings, responses, messages, ocr, ...). It also stops pinning model_info.id to the model_name, letting the proxy assign a unique model_id. Re-registering a fixed-name deployment (the batch suite's openai-batch et al.) after a failed teardown no longer collides on the model_id unique constraint (prisma UniqueViolationError surfaced as the generic 500 "Failed to add model to db", erroring every batch_lifecycle case at setup)
Greptile SummaryThis PR hardens e2e test model provisioning against two real failures observed in split control/data-plane stage runs: a
Confidence Score: 5/5All changes are confined to the tests/e2e directory and fix two well-documented provisioning bugs with no production code touched. The two root causes are clearly diagnosed, the fixes are minimal and targeted, all existing test assertions are updated to reflect correct behavior (not weakened), and the four new unit tests verify the wait/timeout/error-surface paths deterministically. No custom rules are violated. No files require special attention.
|
| Filename | Overview |
|---|---|
| tests/e2e/e2e_gateway.py | create_model now omits pinned model_info.id and polls /v1/models until servable; _await_model_servable surfaces the last non-Success poll result in its AssertionError. |
| tests/e2e/models.py | ModelInfoBody.id made optional (None default); new ModelListEntry and ModelsListResponse models added for /v1/models polling. |
| tests/e2e/test_e2e_gateway.py | _RecordingTransport gains /v1/models handling with configurable servable_after_gets and models_error; four new tests cover wait-for-servable, loud failure, and error surface behavior; existing tests updated to reflect removed model_id pinning. |
Reviews (2): Last reviewed commit: "test(e2e): surface last /v1/models error..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Mubashir Osmani seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
…ch test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Both failures were diagnosed from the 2026-07-09 stage e2e run (image
1.0.0-main.20260709072624, split control/data-plane deployment). The fix is in this branch atf1ffbe6d94.Root cause 1,
model_idcollision. The harness pinnedmodel_info.id = model_name, so re-registering a fixed-name deployment collided on themodel_idunique constraint. The backend surfaced it as aprisma.errors.UniqueViolationErrorswallowed into the generic 500. Reproduced and fixed live against a local proxy (curltolocalhost:4000); the 500 body matches the stage error byte-for-byte:Root cause 2, split-plane propagation.
/model/newsucceeded on the control plane (the stage backend logged 20xPOST /model/new -> 200 OK), but the gateway had not reloaded the model from the DB when the test called it, so/chat,/embeddings,/v1/messages,/ocr,/responsesreturnedInvalid model name passed. This only shows on a split deployment; a monolithic proxy shares the process, which is why it passes locally.create_modelnow polls the data-plane/v1/modelsuntil the model is servable before returning. Verified against a local proxy that create-then-immediately-invoke still works (the rust OCR suite registers each provider via/model/newthen calls/ocr):The definitive end-to-end proof is the next stage e2e run going green on the suites that were red purely because of provisioning (batches, embeddings, responses, messages, image, rerank, audio, ocr); that run happens automatically after this merges to staging.
Type
🐛 Bug Fix
✅ Test
Changes
create_modelno longer assumes/model/newmakes a deployment instantly callable. It polls the data-plane/v1/modelsuntil the newmodel_nameis listed, then returns, and fails loudly with a clear message if the model never becomes servable withinpoll_timeout(a real propagation orSTORE_MODEL_IN_DBreload problem, surfaced at the source instead of as a downstreamInvalid model name passed). In the monolithic case the model is present on the first poll, so this adds a single request.create_modelalso stops settingmodel_info.id, so the proxy assigns a uniquemodel_idper deployment. Every suite except batches already usedunique_marker()names and never collided; batches register fixed names (openai-batch,azure-batch,vertex-batch,bedrock-batch), so a leftover row from a crashed or evicted prior run made the next run's create collide on the id constraint and errored everytest_batch_lifecyclecase at setup. With a proxy-assigned id, re-registration is collision-free._await_model_servableremembers the last/v1/modelspoll result and, when it never returned aSuccess(a 5xx on the data plane or a network partition rather than the model simply not being listed yet), appends it to the timeoutAssertionErrorso a split-deployment failure names the real cause instead of always pointing at propagation/reload.Tests:
test_e2e_gateway.pygains coverage thatcreate_modelwaits for data-plane visibility, fails loudly when the model never appears, and surfaces the last data-plane error when/v1/modelskeeps erroring; it also assertscreate_modelno longer pinsmodel_info.id. The typed fakeTransportnow serves/v1/models(and can return a canned error) so a regression in the wait or error-surfacing behavior fails there rather than in a live stage run.Link to Devin session: https://app.devin.ai/sessions/226a1a920e6d4e1196a3cd3384140721
Requested by: @mubashir1osmani