Skip to content

Commit d4582a1

Browse files
authored
Merge pull request #60 from scaleapi/fix/classify-missing-deployment
fix: a missing upstream deployment is not a task failure
2 parents 988aa83 + 10515d1 commit d4582a1

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

vero/src/vero/evaluation/scoring/error_taxonomy.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class ErrorCategory(str, Enum):
4343
#: Authentication/authorization failed (e.g. a wrong or cycled key). Does
4444
#: not heal on retry: terminate loudly.
4545
AUTH_FAILURE = "auth_failure"
46+
#: The requested model is not deployed on the configured upstream. A
47+
#: permanent misconfiguration, not the candidate's doing: never scored as a
48+
#: sample, and terminating, because every remaining case will fail the same
49+
#: way and the run has nothing left to measure.
50+
UPSTREAM_MODEL_UNAVAILABLE = "upstream_model_unavailable"
4651
#: Transient external infrastructure (rate limit, timeout, connection, 5xx,
4752
#: overloaded). Retryable; if it persists it renders the aggregate invalid.
4853
TRANSIENT_INFRA = "transient_infra"
@@ -91,6 +96,16 @@ class CategoryPolicy:
9196
is_informative_sample=False,
9297
diagnostic_code="auth_failure",
9398
),
99+
ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE: CategoryPolicy(
100+
retryable=False,
101+
terminating=True,
102+
# Unlike auth, keep counting these toward invalidity: if the terminating
103+
# path is ever bypassed, the aggregate must still come out invalid
104+
# rather than silently averaging a shrinking set of survivors.
105+
counts_toward_invalidity=True,
106+
is_informative_sample=False,
107+
diagnostic_code="upstream_model_unavailable",
108+
),
94109
ErrorCategory.TRANSIENT_INFRA: CategoryPolicy(
95110
retryable=True,
96111
terminating=False,
@@ -149,6 +164,21 @@ def policy(category: ErrorCategory) -> CategoryPolicy:
149164
),
150165
ErrorCategory.AUTH_FAILURE,
151166
),
167+
(
168+
# A model that is configured but not provisioned upstream. Left
169+
# unclassified this is the worst kind of silent failure: the agent's
170+
# every call 404s, it writes no answer, and the case is recorded as an
171+
# informative task failure: the harness blaming the candidate for a
172+
# model that does not exist. Matched on the provider's own error codes
173+
# and on the exact Azure sentence, never on a bare "does not exist",
174+
# which would also swallow "loading container: file does not exist".
175+
re.compile(
176+
r"deploymentnotfound|model_not_found|"
177+
r"the api deployment for this resource does not exist",
178+
re.IGNORECASE,
179+
),
180+
ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE,
181+
),
152182
(
153183
re.compile(
154184
r"rate.?limit|too.?many.?requests|time(?:d.?)?out|connection|"
@@ -205,6 +235,9 @@ def classify_case(signals: list[str]) -> ErrorCategory:
205235
for category in (
206236
ErrorCategory.INFERENCE_BUDGET_EXHAUSTED,
207237
ErrorCategory.AUTH_FAILURE,
238+
# Ahead of infra: a case that saw both a dead sandbox and a missing
239+
# deployment should report the deterministic, operator-fixable one.
240+
ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE,
208241
ErrorCategory.TRANSIENT_INFRA,
209242
):
210243
if category in categories:

vero/tests/test_v05_error_taxonomy.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,62 @@ def test_classify_case_treats_environment_loss_as_infra_not_task_failure():
9595
)
9696
# A candidate's own bug is still an informative task failure, unchanged.
9797
assert classify_case(["ValueError"]) is ErrorCategory.TASK_FAILURE
98+
99+
100+
def test_a_missing_upstream_deployment_is_not_a_task_failure():
101+
"""The 2026-07-25 swe-atlas-qna run's exact terminal exceptions.
102+
103+
145 of its 469 cases died on a model that is not provisioned. Classified as
104+
a task failure, each was recorded as an informative score of 0.0: the
105+
harness blaming the candidate for a model that does not exist.
106+
"""
107+
azure = (
108+
"Error code: 404 - {'error': {'type': 'invalid_request_error', "
109+
"'code': 'DeploymentNotFound', 'message': 'The API deployment for this "
110+
"resource does not exist. If you created the deployment within the "
111+
"last 5 minutes, please wait a moment and try again.'}}"
112+
)
113+
assert (
114+
classify_signal(azure) is ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE
115+
)
116+
assert (
117+
classify_signal("model_not_found") is ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE
118+
)
119+
assert (
120+
classify_case(["NotFoundError", azure])
121+
is ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE
122+
)
123+
124+
unavailable = policy(ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE)
125+
assert not unavailable.is_informative_sample
126+
assert not unavailable.retryable
127+
assert unavailable.terminating
128+
assert unavailable.counts_toward_invalidity
129+
130+
# It outranks a co-occurring sandbox death: the deterministic,
131+
# operator-fixable cause is the one worth reporting.
132+
assert (
133+
classify_case(
134+
[
135+
"NotFoundError",
136+
"Modal Sandbox with container ID ta-01KY not found.",
137+
azure,
138+
]
139+
)
140+
is ErrorCategory.UPSTREAM_MODEL_UNAVAILABLE
141+
)
142+
143+
144+
def test_missing_deployment_pattern_does_not_swallow_container_load_failures():
145+
"""`FetchSpec failed: loading container: file does not exist` is infra.
146+
147+
It is 102 of that same run's cases, and it also contains "does not exist",
148+
so the deployment pattern must not be written loosely enough to claim it.
149+
"""
150+
fetchspec = "FetchSpec failed: loading container: file does not exist\n"
151+
assert classify_signal(fetchspec) is ErrorCategory.TRANSIENT_INFRA
152+
assert classify_case(["RuntimeError", fetchspec]) is ErrorCategory.TRANSIENT_INFRA
153+
assert (
154+
classify_case(["AddTestsDirError", "Failed to add tests directory to environment."])
155+
is ErrorCategory.TRANSIENT_INFRA
156+
)

0 commit comments

Comments
 (0)