@@ -11,6 +11,9 @@ import { ApiStream } from "../transform/stream"
1111import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
1212
1313export class ChutesHandler extends BaseOpenAiCompatibleProvider < ChutesModelId > {
14+ private retryCount = 3
15+ private retryDelay = 1000 // Start with 1 second delay
16+
1417 constructor ( options : ApiHandlerOptions ) {
1518 super ( {
1619 ...options ,
@@ -47,46 +50,127 @@ export class ChutesHandler extends BaseOpenAiCompatibleProvider<ChutesModelId> {
4750 override async * createMessage ( systemPrompt : string , messages : Anthropic . Messages . MessageParam [ ] ) : ApiStream {
4851 const model = this . getModel ( )
4952
50- if ( model . id . includes ( "DeepSeek-R1" ) ) {
51- const stream = await this . client . chat . completions . create ( {
52- ...this . getCompletionParams ( systemPrompt , messages ) ,
53- messages : convertToR1Format ( [ { role : "user" , content : systemPrompt } , ...messages ] ) ,
54- } )
55-
56- const matcher = new XmlMatcher (
57- "think" ,
58- ( chunk ) =>
59- ( {
60- type : chunk . matched ? "reasoning" : "text" ,
61- text : chunk . data ,
62- } ) as const ,
63- )
64-
65- for await ( const chunk of stream ) {
66- const delta = chunk . choices [ 0 ] ?. delta
67-
68- if ( delta ?. content ) {
69- for ( const processedChunk of matcher . update ( delta . content ) ) {
53+ // Add retry logic for transient errors
54+ let lastError : Error | null = null
55+ for ( let attempt = 0 ; attempt < this . retryCount ; attempt ++ ) {
56+ try {
57+ if ( model . id . includes ( "DeepSeek-R1" ) ) {
58+ const stream = await this . client . chat . completions . create ( {
59+ ...this . getCompletionParams ( systemPrompt , messages ) ,
60+ messages : convertToR1Format ( [ { role : "user" , content : systemPrompt } , ...messages ] ) ,
61+ } )
62+
63+ const matcher = new XmlMatcher (
64+ "think" ,
65+ ( chunk ) =>
66+ ( {
67+ type : chunk . matched ? "reasoning" : "text" ,
68+ text : chunk . data ,
69+ } ) as const ,
70+ )
71+
72+ for await ( const chunk of stream ) {
73+ const delta = chunk . choices [ 0 ] ?. delta
74+
75+ if ( delta ?. content ) {
76+ for ( const processedChunk of matcher . update ( delta . content ) ) {
77+ yield processedChunk
78+ }
79+ }
80+
81+ if ( chunk . usage ) {
82+ yield {
83+ type : "usage" ,
84+ inputTokens : chunk . usage . prompt_tokens || 0 ,
85+ outputTokens : chunk . usage . completion_tokens || 0 ,
86+ }
87+ }
88+ }
89+
90+ // Process any remaining content
91+ for ( const processedChunk of matcher . final ( ) ) {
7092 yield processedChunk
7193 }
94+ return // Success, exit the retry loop
95+ } else {
96+ yield * super . createMessage ( systemPrompt , messages )
97+ return // Success, exit the retry loop
7298 }
99+ } catch ( error : any ) {
100+ lastError = error
101+ console . error ( `ChutesAI API error (attempt ${ attempt + 1 } /${ this . retryCount } ):` , {
102+ status : error . status ,
103+ message : error . message ,
104+ response : error . response ,
105+ cause : error . cause ,
106+ } )
73107
74- if ( chunk . usage ) {
75- yield {
76- type : "usage" ,
77- inputTokens : chunk . usage . prompt_tokens || 0 ,
78- outputTokens : chunk . usage . completion_tokens || 0 ,
79- }
108+ // Check if it's a retryable error (5xx errors)
109+ if ( error . status && error . status >= 500 && error . status < 600 && attempt < this . retryCount - 1 ) {
110+ // Exponential backoff
111+ const delay = this . retryDelay * Math . pow ( 2 , attempt )
112+ console . log ( `Retrying ChutesAI request after ${ delay } ms...` )
113+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) )
114+ continue
80115 }
116+
117+ // For non-retryable errors or final attempt, throw with more context
118+ const enhancedError = new Error (
119+ `ChutesAI API error (${ error . status || "unknown status" } ): ${ error . message || "Empty response body" } . ` +
120+ `This may be a temporary issue with the ChutesAI service. ` +
121+ `Please verify your API key and try again.` ,
122+ )
123+ ; ( enhancedError as any ) . status = error . status
124+ ; ( enhancedError as any ) . originalError = error
125+ throw enhancedError
81126 }
127+ }
82128
83- // Process any remaining content
84- for ( const processedChunk of matcher . final ( ) ) {
85- yield processedChunk
129+ // If we've exhausted all retries
130+ if ( lastError ) {
131+ throw lastError
132+ }
133+ }
134+
135+ override async completePrompt ( prompt : string ) : Promise < string > {
136+ let lastError : Error | null = null
137+
138+ for ( let attempt = 0 ; attempt < this . retryCount ; attempt ++ ) {
139+ try {
140+ return await super . completePrompt ( prompt )
141+ } catch ( error : any ) {
142+ lastError = error
143+ console . error ( `ChutesAI completePrompt error (attempt ${ attempt + 1 } /${ this . retryCount } ):` , {
144+ status : error . status ,
145+ message : error . message ,
146+ } )
147+
148+ // Check if it's a retryable error (5xx errors)
149+ if ( error . status && error . status >= 500 && error . status < 600 && attempt < this . retryCount - 1 ) {
150+ // Exponential backoff
151+ const delay = this . retryDelay * Math . pow ( 2 , attempt )
152+ console . log ( `Retrying ChutesAI completePrompt after ${ delay } ms...` )
153+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) )
154+ continue
155+ }
156+
157+ // For non-retryable errors or final attempt, throw with more context
158+ const enhancedError = new Error (
159+ `ChutesAI completion error (${ error . status || "unknown status" } ): ${ error . message || "Empty response body" } . ` +
160+ `Please verify your API key and endpoint configuration.` ,
161+ )
162+ ; ( enhancedError as any ) . status = error . status
163+ ; ( enhancedError as any ) . originalError = error
164+ throw enhancedError
86165 }
87- } else {
88- yield * super . createMessage ( systemPrompt , messages )
89166 }
167+
168+ // If we've exhausted all retries
169+ if ( lastError ) {
170+ throw lastError
171+ }
172+
173+ throw new Error ( "ChutesAI completion failed after all retry attempts" )
90174 }
91175
92176 override getModel ( ) {
0 commit comments