Skip to content

Commit e16d81a

Browse files
committed
HF fallback
1 parent e1f7b28 commit e16d81a

2 files changed

Lines changed: 60 additions & 19 deletions

File tree

apps/api/src/providers/huggingface.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* HuggingFace Provider Implementation
33
*/
44

5-
import { Errors, HF_SPACES } from '@z-image/shared'
5+
import { ApiError, ApiErrorCode, Errors, HF_SPACES } from '@z-image/shared'
66
import { MAX_INT32 } from '../constants'
77
import { callGradioApi } from '../utils'
88
import type { ImageProvider, ProviderGenerateRequest, ProviderGenerateResult } from './types'
@@ -28,6 +28,26 @@ function parseSeedFromResponse(modelId: string, result: unknown[], fallbackSeed:
2828
return fallbackSeed
2929
}
3030

31+
function getCandidateBaseUrls(modelId: string): string[] {
32+
const primary = HF_SPACES[modelId as keyof typeof HF_SPACES] || HF_SPACES['z-image-turbo']
33+
// Known mirror space for z-image-turbo. Helps when a space is cold/blocked/deleted.
34+
const fallbacks =
35+
modelId === 'z-image-turbo' ? ['https://luca115-z-image-turbo.hf.space'] : ([] as string[])
36+
return [primary, ...fallbacks].filter(Boolean)
37+
}
38+
39+
function isNotFoundProviderError(err: unknown): boolean {
40+
if (err instanceof ApiError) {
41+
return err.code === ApiErrorCode.PROVIDER_ERROR && (err.details?.upstream || '').includes('404')
42+
}
43+
if (err && typeof err === 'object' && 'code' in err) {
44+
const code = (err as { code?: unknown }).code
45+
const details = (err as { details?: unknown }).details as { upstream?: string } | undefined
46+
return code === ApiErrorCode.PROVIDER_ERROR && (details?.upstream || '').includes('404')
47+
}
48+
return false
49+
}
50+
3151
/** Model-specific Gradio configurations */
3252
const MODEL_CONFIGS: Record<
3353
string,
@@ -58,31 +78,50 @@ export class HuggingFaceProvider implements ImageProvider {
5878
async generate(request: ProviderGenerateRequest): Promise<ProviderGenerateResult> {
5979
const seed = request.seed ?? Math.floor(Math.random() * MAX_INT32)
6080
const modelId = request.model || 'z-image-turbo'
61-
const baseUrl = HF_SPACES[modelId as keyof typeof HF_SPACES] || HF_SPACES['z-image-turbo']
6281
const config = MODEL_CONFIGS[modelId] || MODEL_CONFIGS['z-image-turbo']
6382

6483
// Debug: log model info (uncomment for debugging)
6584
// console.log(`[HuggingFace] Model: ${modelId}, BaseURL: ${baseUrl}`)
6685

67-
const data = await callGradioApi(
68-
baseUrl,
69-
config.endpoint,
70-
config.buildData(request, seed),
71-
request.authToken
72-
)
86+
let lastErr: unknown
87+
let imageUrl: string | undefined
88+
let data: unknown[] | undefined
89+
90+
for (const baseUrl of getCandidateBaseUrls(modelId)) {
91+
try {
92+
data = await callGradioApi(
93+
baseUrl,
94+
config.endpoint,
95+
config.buildData(request, seed),
96+
request.authToken
97+
)
98+
99+
const result = data as Array<{ url?: string } | number | string>
100+
const first = result[0]
101+
const rawUrl =
102+
typeof first === 'string' ? first : (first as { url?: string } | null | undefined)?.url
103+
imageUrl = rawUrl ? normalizeImageUrl(baseUrl, rawUrl) : undefined
104+
if (!imageUrl) {
105+
// A successful call without an image payload is not a base-URL issue.
106+
throw Errors.generationFailed('HuggingFace', 'No image returned')
107+
}
108+
break
109+
} catch (err) {
110+
lastErr = err
111+
// Try next base URL only for 404-type provider errors.
112+
if (isNotFoundProviderError(err)) continue
113+
throw err
114+
}
115+
}
73116

74-
const result = data as Array<{ url?: string } | number | string>
75-
const first = result[0]
76-
const rawUrl =
77-
typeof first === 'string' ? first : (first as { url?: string } | null | undefined)?.url
78-
const imageUrl = rawUrl ? normalizeImageUrl(baseUrl, rawUrl) : undefined
79117
if (!imageUrl) {
118+
if (lastErr) throw lastErr
80119
throw Errors.generationFailed('HuggingFace', 'No image returned')
81120
}
82121

83122
return {
84123
url: imageUrl,
85-
seed: parseSeedFromResponse(modelId, result, seed),
124+
seed: parseSeedFromResponse(modelId, data as unknown[], seed),
86125
}
87126
}
88127
}

apps/api/src/utils/gradio.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ export async function callGradioApi(
106106
// HuggingFace Spaces can be "cold" (starting/loading) and sometimes return transient 404/503.
107107
// Retry a few times to reduce false-negative failures in serverless runtimes (e.g. Cloudflare).
108108
let queueData: { event_id?: string } | null = null
109+
const queueUrl = `${baseUrl}/gradio_api/call/${endpoint}`
109110
for (let attempt = 0; attempt < MAX_GRADIO_RETRIES; attempt++) {
110-
const queue = await fetch(`${baseUrl}/gradio_api/call/${endpoint}`, {
111+
const queue = await fetch(queueUrl, {
111112
method: 'POST',
112113
headers,
113114
body: JSON.stringify({ data }),
@@ -125,20 +126,21 @@ export async function callGradioApi(
125126
await sleep(600 * (attempt + 1))
126127
continue
127128
}
128-
throw parseHuggingFaceError(errText || `Queue request failed: ${status}`, status)
129+
throw parseHuggingFaceError(`${status} ${queueUrl}${errText ? `: ${errText}` : ''}`, status)
129130
}
130131

131132
if (!queueData) {
132-
throw Errors.providerError(PROVIDER_NAME, 'Queue request failed after retries')
133+
throw Errors.providerError(PROVIDER_NAME, `Queue request failed after retries: ${queueUrl}`)
133134
}
134135

135136
if (!queueData.event_id) {
136137
throw Errors.providerError(PROVIDER_NAME, 'No event_id returned from queue')
137138
}
138139

139140
let text = ''
141+
const resultUrl = `${baseUrl}/gradio_api/call/${endpoint}/${queueData.event_id}`
140142
for (let attempt = 0; attempt < MAX_GRADIO_RETRIES; attempt++) {
141-
const result = await fetch(`${baseUrl}/gradio_api/call/${endpoint}/${queueData.event_id}`, {
143+
const result = await fetch(resultUrl, {
142144
headers,
143145
})
144146

@@ -154,7 +156,7 @@ export async function callGradioApi(
154156
await sleep(600 * (attempt + 1))
155157
continue
156158
}
157-
throw parseHuggingFaceError(errText || `Result request failed: ${status}`, status)
159+
throw parseHuggingFaceError(`${status} ${resultUrl}${errText ? `: ${errText}` : ''}`, status)
158160
}
159161

160162
if (!text) {

0 commit comments

Comments
 (0)