feat: preflight configured models before launching an optimizer trial#59
Conversation
18914fb to
5bcbbb2
Compare
A model that is configured but not deployed upstream is invisible until the run is over. The gateway forwards the request, the upstream 404s, the agent makes no progress, and every case is scored 0.0 and labelled `task_failure` — a run that looks honest and says the candidate failed. That is how swe-atlas-qna produced 469 zero-score cases across two runs (#51): `gpt-5.4-mini-2026-03-17` is not provisioned on the configured Azure resource, so 322/322 evaluation-scope and 79/79 finalization-scope requests returned 404 DeploymentNotFound while the producer scope ran clean. `vero harbor run` now sends one 16-token request per distinct allow-listed model before compiling the task, and aborts if any comes back 404. Only a definitive 404 is fatal. A timeout, a 429 or a 503 is inconclusive and is reported without blocking, so a transient upstream blip cannot stop a run that would have succeeded. Names are probed with the provider prefix stripped, which is the form the agents actually send. Verified against the live endpoint: gaia (which points at the undeployed model) now exits 1 in about two seconds with the DeploymentNotFound body quoted, instead of burning ~30 minutes and ~$2; a gpt-5.3-codex producer with a gpt-4o evaluation scope passes. Refs #51. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The probe only spoke the Responses API and treated any 404 as proof the deployment was absent. A route-level 404 (the upstream does not implement that path) and a model-level 404 (the deployment does not exist) share a status code and mean opposite things, so the preflight would hard-block a run against a Chat-Completions-only upstream that would have succeeded. That is now a live risk in this repo: officeqa, tau3, swe-atlas-qna and browsecomp-plus were ported to Chat Completions, leaving gaia as the only agent still on Responses. Try both routes, and only call a model missing when the 404 body names the model (`DeploymentNotFound`, `model_not_found`, or "model ... does not exist") rather than the route. If every route 404s on the route itself, the result is inconclusive and the run proceeds. Same fix for the model name: the provider prefix routes on a proxy and is meaningless to a single-provider endpoint, so probe the configured spelling first and fall back to the bare one before reporting anything missing. Previously the prefix was stripped unconditionally, which is wrong for `fireworks_ai/...` names. Verified against the live Azure endpoint: `gpt-4o` 200 on both routes; `openai/gpt-5.3-codex` 404s prefixed and passes preflight via the bare fallback; `fireworks_ai/gpt-oss-120b` is still correctly blocked; and a model 404 body is distinguished from a bare route 404. Refs #51. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
ccc7d07 to
ec708e9
Compare
d3b4791 to
f02232a
Compare
|
Restacked onto the new The defect: the probe only spoke the Responses API and treated any 404 as proof the deployment was absent. A route-level 404 (the upstream does not implement that path) and a model-level 404 (the deployment does not exist) share a status code and mean opposite things. Since Now it tries both routes and only calls a model missing when the 404 body names the model. Verified against the live Azure endpoint: Second fix in the same commit: the probe used to strip Three new tests cover the route/model split and the name fallback, including the both-routes-404 case that cannot be reached against a real endpoint. Not superseded by Full suite matches the base branch exactly: 11 pre-existing failures, none new. |
Refs #51. Independent of #58 — that one fixes swe-atlas-qna's config, this one makes the class of mistake self-reporting.
The failure mode this closes
A configured-but-undeployed model is invisible until the run is over:
404 DeploymentNotFound.task_failure.The result is a run that looks honest. It reports that the candidate failed the task. Nothing in
result.json,reward.jsonorfinalization.jsonsays "there was no model". You only find out by extractingsession.tar.gzand readingartifacts/inference/usage.json.That is exactly how swe-atlas-qna produced 469 zero-score cases across two runs: 322/322 evaluation-scope and 79/79 finalization-scope requests returned
DeploymentNotFoundwhile the producer scope ran 113 requests clean. Two full trials, ~an hour, ~$4, to discover a config typo.The change
vero harbor runsends one 16-token request per distinct allow-listed model (producer, evaluation, finalization) before compiling the task, and aborts if any returns 404.Only a definitive 404 is fatal. A timeout, a 429 or a 503 is inconclusive — it gets reported and the run proceeds. A preflight that blocks runs on a transient upstream blip would be worse than no preflight, and that is also why there is no
--skip-preflightescape hatch: the one blocking condition is never a false positive worth overriding.Model names are probed with the provider prefix stripped (
openai/gpt-4o→gpt-4o), which is the form the agents actually send aftermodel_name.removeprefix("openai/").Live verification against the real endpoint
Blocking path — gaia, which still points at the undeployed model:
About two seconds, before a single Modal sandbox is created.
Passing path — a
gpt-5.3-codexproducer with agpt-4oevaluation scope (the shape #58 moves swe-atlas-qna to) clears preflight and proceeds.That last note in the error message is worth keeping:
GET /openai/v1/modelson this resource returns 172 entries includinggpt-5.4-mini-2026-03-17. It lists the catalogue, not the deployments. Anyone who checks the obvious way concludes the model is fine.Tests
test_preflight_blocks_only_on_a_definitively_missing_deployment(404 aborts with the scope and upstream body; prefix stripped before probing) andtest_preflight_does_not_block_on_inconclusive_upstream_failures(429 / 503 / timeout / missing credentials / no gateway all proceed). Both offline.Greptile Summary
This PR adds a preflight model check to
vero harbor runthat sends a single 16-token request per configured model before compiling the task, aborting with a clear error if any returns a definitiveDeploymentNotFound404. It closes a silent failure mode where a mis-configured (but gateway-allowed) model would burn an entire optimizer trial producing only zero-score cases with no diagnostic signal._preflight_modelscollects distinct model names across producer/evaluation/finalization scopes, probes each with its full configured name first and the bare (prefix-stripped) name as a fallback, and raises aClickExceptiononly when a 404 definitively names the model — not when it names the route._probe_model/_model_is_missingseparate route-level 404s from model-level 404s by body inspection, and fall through to/chat/completionswhen/responsesreturns a route-level 404, so gateways that implement only one API shape are handled correctly.Confidence Score: 5/5
Safe to merge. The preflight is purely additive — it only aborts when a deployment is definitively absent, and all inconclusive outcomes (timeouts, rate limits, missing credentials, no gateway) are non-blocking pass-throughs.
The implementation carefully distinguishes route-level 404s from model-level 404s, handles both OpenAI API shapes, provides a correct prefix-stripping fallback, and is fully tested offline. The placement of _preflight_models before tempfile creation ensures it exits early without side effects. No existing behavior is modified.
Files Needing Attention: No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[vero harbor run] --> B[load config] B --> C{inference_gateway configured?} C -->|No| H[compile and launch] C -->|Yes| D{API key in env?} D -->|No| H D -->|Yes| E[collect distinct models across producer / evaluation / finalization] E --> F[for each model: probe full name, then bare name if prefixed] F --> G[_probe_model: try /responses then /chat/completions] G --> G1{HTTP 404 AND body names model?} G1 -->|Yes - definitive missing| MISS[mark model missing] G1 -->|No - route-level 404| G3[try next route] G3 --> G4{another route?} G4 -->|Yes| G G4 -->|No| INC[inconclusive - run proceeds] G1 -->|2xx / 429 / 503 / timeout| OK[probe passed or inconclusive] OK --> NEXT[next model] INC --> NEXT MISS --> NEXT NEXT --> NEXT2{all models probed?} NEXT2 -->|No| F NEXT2 -->|Yes| CHK{any missing?} CHK -->|Yes| ERR[ClickException: abort before compile] CHK -->|No| HReviews (4): Last reviewed commit: "fix: do not read a missing route as a mi..." | Re-trigger Greptile