Skip to content

Commit 4294014

Browse files
committed
fix(rest): pass upstream 4xx through /v1/infer instead of 502
"task not served" / bad-input rejections from the inference upstream returned as generic 502 inference_upstream; now 4xx status + detail pass through as inference_rejected.
1 parent 9a8a900 commit 4294014

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎src/rest.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ rest.post("/v1/infer", async (c) => {
170170
}).catch(() => null)
171171
if (!upstream || !upstream.ok) {
172172
const detail = upstream ? await upstream.text().catch(() => "") : "upstream unreachable"
173+
if (upstream && upstream.status >= 400 && upstream.status < 500) {
174+
// upstream rejected the request itself (unknown model, task not
175+
// served, oversized input) — pass the status + detail through
176+
return errorResponse(upstream.status, "inference_rejected", detail.slice(0, 500))
177+
}
173178
return errorResponse(502, "inference_upstream", detail.slice(0, 500))
174179
}
175180
const result = (await upstream.json()) as Record<string, unknown>

‎test/rest.test.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,40 @@ describe("REST models + inference", () => {
163163
expect(missing.status).toBe(404)
164164
})
165165

166+
it("POST /v1/infer passes upstream 4xx status through (not 502)", async () => {
167+
const stubEnv = {
168+
ASSETS: assets,
169+
ML_ENDPOINT: "https://ml-stub.example",
170+
ML_TOKEN: "t",
171+
}
172+
const originalFetch = globalThis.fetch
173+
globalThis.fetch = (async (url: string | URL | Request) => {
174+
if (String(url).includes("ml-stub.example")) {
175+
return new Response(
176+
JSON.stringify({ detail: "model x task translit is not served" }),
177+
{ status: 400, headers: { "content-type": "application/json" } },
178+
)
179+
}
180+
return originalFetch(url as RequestInfo)
181+
}) as typeof fetch
182+
try {
183+
const res = await app.request(
184+
"https://example.org/v1/infer",
185+
{
186+
method: "POST",
187+
headers: JSON_HEADERS,
188+
body: JSON.stringify({ model: "khm-latn-1.0", input: "x" }),
189+
},
190+
stubEnv,
191+
)
192+
expect(res.status).toBe(400)
193+
const body = (await res.json()) as { error: { code: string } }
194+
expect(body.error.code).not.toBe("inference_upstream")
195+
} finally {
196+
globalThis.fetch = originalFetch
197+
}
198+
})
199+
166200
it("POST /v1/infer validates without an upstream configured", async () => {
167201
const unconfigured = await post("/v1/infer", { model: "heb-diac-1.0", input: "x" })
168202
expect(unconfigured.status).toBe(503)

0 commit comments

Comments
 (0)