@@ -2,6 +2,7 @@ package hfapi
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
67 "io"
78 "net/http"
@@ -10,6 +11,7 @@ import (
1011 "sort"
1112 "strconv"
1213 "strings"
14+ "time"
1315
1416 "github.com/mudler/LocalAI/pkg/httpclient"
1517)
@@ -88,57 +90,128 @@ type SearchParams struct {
8890
8991// Client represents a Hugging Face API client
9092type Client struct {
91- baseURL string
92- client * http.Client
93+ baseURL string
94+ client * http.Client
95+ maxRetries int
96+ retryBackoff time.Duration
97+ maxBackoff time.Duration
98+ sleepFn func (time.Duration )
9399}
94100
101+ var ErrRateLimited = errors .New ("huggingface API rate limited" )
102+
95103// NewClient creates a new Hugging Face API client
96104func NewClient () * Client {
97105 return & Client {
98- baseURL : "https://huggingface.co/api/models" ,
99- client : httpclient .New (httpclient .WithFollowRedirects ()),
106+ baseURL : "https://huggingface.co/api/models" ,
107+ client : httpclient .New (httpclient .WithFollowRedirects ()),
108+ maxRetries : 5 ,
109+ retryBackoff : 1 * time .Second ,
110+ maxBackoff : 30 * time .Second ,
111+ sleepFn : time .Sleep ,
100112 }
101113}
102114
103115// SearchModels searches for models using the Hugging Face API
104116func (c * Client ) SearchModels (params SearchParams ) ([]Model , error ) {
105- req , err := http .NewRequest ("GET" , c .baseURL , nil )
106- if err != nil {
107- return nil , fmt .Errorf ("failed to create request: %w" , err )
108- }
117+ for attempt := 1 ; attempt <= c .maxRetries ; attempt ++ {
118+ req , err := http .NewRequest ("GET" , c .baseURL , nil )
119+ if err != nil {
120+ return nil , fmt .Errorf ("failed to create request: %w" , err )
121+ }
109122
110- // Add query parameters
111- q := req .URL .Query ()
112- q .Add ("sort" , params .Sort )
113- q .Add ("direction" , fmt .Sprintf ("%d" , params .Direction ))
114- q .Add ("limit" , fmt .Sprintf ("%d" , params .Limit ))
115- q .Add ("search" , params .Search )
116- req .URL .RawQuery = q .Encode ()
123+ // Add query parameters
124+ q := req .URL .Query ()
125+ q .Add ("sort" , params .Sort )
126+ q .Add ("direction" , fmt .Sprintf ("%d" , params .Direction ))
127+ q .Add ("limit" , fmt .Sprintf ("%d" , params .Limit ))
128+ q .Add ("search" , params .Search )
129+ req .URL .RawQuery = q .Encode ()
130+
131+ resp , err := c .client .Do (req )
132+ if err != nil {
133+ if attempt < c .maxRetries {
134+ c .sleepFn (c .exponentialBackoff (attempt ))
135+ continue
136+ }
137+ return nil , fmt .Errorf ("failed to make request: %w" , err )
138+ }
117139
118- // Make the HTTP request
119- resp , err := c .client .Do (req )
120- if err != nil {
121- return nil , fmt .Errorf ("failed to make request: %w" , err )
122- }
123- defer resp .Body .Close ()
140+ if resp .StatusCode != http .StatusOK {
141+ if err := resp .Body .Close (); err != nil {
142+ return nil , fmt .Errorf ("failed to close response body: %w" , err )
143+ }
144+ if c .isRetryableStatus (resp .StatusCode ) && attempt < c .maxRetries {
145+ c .sleepFn (c .retryDelay (resp , attempt ))
146+ continue
147+ }
148+ if resp .StatusCode == http .StatusTooManyRequests {
149+ return nil , fmt .Errorf ("%w: failed to fetch models. Status code: %d" , ErrRateLimited , resp .StatusCode )
150+ }
151+ return nil , fmt .Errorf ("failed to fetch models. Status code: %d" , resp .StatusCode )
152+ }
124153
125- if resp .StatusCode != http .StatusOK {
126- return nil , fmt .Errorf ("failed to fetch models. Status code: %d" , resp .StatusCode )
127- }
154+ // Read the response body
155+ body , err := io .ReadAll (resp .Body )
156+ closeErr := resp .Body .Close ()
157+ if err != nil {
158+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
159+ }
160+ if closeErr != nil {
161+ return nil , fmt .Errorf ("failed to close response body: %w" , closeErr )
162+ }
128163
129- // Read the response body
130- body , err := io .ReadAll (resp .Body )
131- if err != nil {
132- return nil , fmt .Errorf ("failed to read response body: %w" , err )
164+ // Parse the JSON response
165+ var models []Model
166+ if err := json .Unmarshal (body , & models ); err != nil {
167+ return nil , fmt .Errorf ("failed to parse JSON response: %w" , err )
168+ }
169+
170+ return models , nil
133171 }
134172
135- // Parse the JSON response
136- var models []Model
137- if err := json .Unmarshal (body , & models ); err != nil {
138- return nil , fmt .Errorf ("failed to parse JSON response: %w" , err )
173+ return nil , fmt .Errorf ("%w: failed to fetch models. Status code: %d" , ErrRateLimited , http .StatusTooManyRequests )
174+ }
175+
176+ func (c * Client ) isRetryableStatus (code int ) bool {
177+ return code == http .StatusTooManyRequests || (code >= http .StatusInternalServerError && code <= http .StatusNetworkAuthenticationRequired )
178+ }
179+
180+ func (c * Client ) retryDelay (resp * http.Response , attempt int ) time.Duration {
181+ if retryAfter := strings .TrimSpace (resp .Header .Get ("Retry-After" )); retryAfter != "" {
182+ if seconds , err := strconv .Atoi (retryAfter ); err == nil && seconds > 0 {
183+ delay := time .Duration (seconds ) * time .Second
184+ if delay > c .maxBackoff {
185+ return c .maxBackoff
186+ }
187+ return delay
188+ }
189+ if at , err := http .ParseTime (retryAfter ); err == nil {
190+ delay := time .Until (at )
191+ if delay > 0 {
192+ if delay > c .maxBackoff {
193+ return c .maxBackoff
194+ }
195+ return delay
196+ }
197+ }
139198 }
140199
141- return models , nil
200+ return c .exponentialBackoff (attempt )
201+ }
202+
203+ func (c * Client ) exponentialBackoff (attempt int ) time.Duration {
204+ delay := c .retryBackoff
205+ for i := 1 ; i < attempt ; i ++ {
206+ delay *= 2
207+ if delay >= c .maxBackoff {
208+ return c .maxBackoff
209+ }
210+ }
211+ if delay > c .maxBackoff {
212+ return c .maxBackoff
213+ }
214+ return delay
142215}
143216
144217// GetLatest fetches the latest GGUF models
0 commit comments