Skip to content

Commit e1f7b28

Browse files
committed
Retry HF calls
1 parent c2c4a9c commit e1f7b28

2 files changed

Lines changed: 55 additions & 15 deletions

File tree

apps/api/src/providers/__tests__/huggingface.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ describe('HuggingFaceProvider', () => {
7777
}
7878

7979
function mockGradioQueueError(status: number, errorText: string) {
80-
vi.mocked(fetch).mockResolvedValueOnce({
80+
// callGradioApi retries transient statuses (404/503), so return the same failing
81+
// response for all attempts.
82+
vi.mocked(fetch).mockResolvedValue({
8183
ok: false,
8284
status,
8385
text: async () => errorText,

apps/api/src/utils/gradio.ts

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
import { Errors } from '@z-image/shared'
66

77
const PROVIDER_NAME = 'HuggingFace'
8+
const MAX_GRADIO_RETRIES = 3
9+
10+
function sleep(ms: number): Promise<void> {
11+
return new Promise((resolve) => setTimeout(resolve, ms))
12+
}
813

914
/**
1015
* Parse HuggingFace error message into appropriate ApiError
@@ -98,31 +103,64 @@ export async function callGradioApi(
98103
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
99104
if (hfToken) headers.Authorization = `Bearer ${hfToken}`
100105

101-
const queue = await fetch(`${baseUrl}/gradio_api/call/${endpoint}`, {
102-
method: 'POST',
103-
headers,
104-
body: JSON.stringify({ data }),
105-
})
106+
// HuggingFace Spaces can be "cold" (starting/loading) and sometimes return transient 404/503.
107+
// Retry a few times to reduce false-negative failures in serverless runtimes (e.g. Cloudflare).
108+
let queueData: { event_id?: string } | null = null
109+
for (let attempt = 0; attempt < MAX_GRADIO_RETRIES; attempt++) {
110+
const queue = await fetch(`${baseUrl}/gradio_api/call/${endpoint}`, {
111+
method: 'POST',
112+
headers,
113+
body: JSON.stringify({ data }),
114+
})
115+
116+
if (queue.ok) {
117+
queueData = (await queue.json()) as { event_id?: string }
118+
break
119+
}
106120

107-
if (!queue.ok) {
108121
const errText = await queue.text().catch(() => '')
109-
throw parseHuggingFaceError(errText || `Queue request failed: ${queue.status}`, queue.status)
122+
const status = queue.status
123+
const shouldRetry = attempt < MAX_GRADIO_RETRIES - 1 && (status === 404 || status === 503)
124+
if (shouldRetry) {
125+
await sleep(600 * (attempt + 1))
126+
continue
127+
}
128+
throw parseHuggingFaceError(errText || `Queue request failed: ${status}`, status)
129+
}
130+
131+
if (!queueData) {
132+
throw Errors.providerError(PROVIDER_NAME, 'Queue request failed after retries')
110133
}
111134

112-
const queueData = (await queue.json()) as { event_id?: string }
113135
if (!queueData.event_id) {
114136
throw Errors.providerError(PROVIDER_NAME, 'No event_id returned from queue')
115137
}
116138

117-
const result = await fetch(`${baseUrl}/gradio_api/call/${endpoint}/${queueData.event_id}`, {
118-
headers,
119-
})
120-
if (!result.ok) {
139+
let text = ''
140+
for (let attempt = 0; attempt < MAX_GRADIO_RETRIES; attempt++) {
141+
const result = await fetch(`${baseUrl}/gradio_api/call/${endpoint}/${queueData.event_id}`, {
142+
headers,
143+
})
144+
145+
if (result.ok) {
146+
text = await result.text()
147+
break
148+
}
149+
121150
const errText = await result.text().catch(() => '')
122-
throw parseHuggingFaceError(errText || `Result request failed: ${result.status}`, result.status)
151+
const status = result.status
152+
const shouldRetry = attempt < MAX_GRADIO_RETRIES - 1 && (status === 404 || status === 503)
153+
if (shouldRetry) {
154+
await sleep(600 * (attempt + 1))
155+
continue
156+
}
157+
throw parseHuggingFaceError(errText || `Result request failed: ${status}`, status)
158+
}
159+
160+
if (!text) {
161+
throw Errors.providerError(PROVIDER_NAME, 'Empty result after retries')
123162
}
124163

125-
const text = await result.text()
126164
const complete = extractCompleteEventData(text)
127165

128166
// Normalize the "complete" payload to the common `unknown[]` that providers expect.

0 commit comments

Comments
 (0)