44 * const llm = new LLM({ aiService: "anthropic", model: "claude-3-5-sonnet-20240620", maxTokens: 2048, temperature: 0 });
55 * const response = await llm.chat([{ role: "user", content: "Hello, world!" }]);
66 * console.log(response);
7+ * // You may cancel all llm operations (for the given instance) by calling abort() method on the ResilientLLM instance
8+ * llm.abort();
79 */
810import { Tiktoken } from "js-tiktoken/lite" ;
911import o200k_base from "js-tiktoken/ranks/o200k_base" ;
@@ -12,10 +14,10 @@ import ResilientOperation from "./ResilientOperation.js";
1214class ResilientLLM {
1315 static encoder ;
1416 static DEFAULT_MODELS = {
15- anthropic : "claude-3-5-sonnet-20240620" ,
17+ anthropic : "claude-3-5-sonnet-20240620" ,
1618 openai : "gpt-4o-mini" ,
1719 gemini : "gemini-2.0-flash" ,
18- ollama : "openai "
20+ ollama : "llama3.1:8b "
1921 }
2022
2123 constructor ( options ) {
@@ -29,16 +31,11 @@ class ResilientLLM {
2931 this . topP = options ?. topP || process . env . AI_TOP_P || 0.95 ;
3032 // Add rate limit config options if provided
3133 this . rateLimitConfig = options ?. rateLimitConfig || { requestsPerMinute : 10 , llmTokensPerMinute : 150000 } ;
32- // Instantiate ResilientOperation for LLM calls
33- this . resilientOperation = new ResilientOperation ( {
34- bucketId : this . aiService ,
35- rateLimitConfig : this . rateLimitConfig ,
36- retries : options ?. retries || 3 ,
37- timeout : this . timeout ,
38- backoffFactor : options ?. backoffFactor || 2 ,
39- onRateLimitUpdate : options ?. onRateLimitUpdate ,
40- cacheStore : this . cacheStore
41- } ) ;
34+ this . retries = options ?. retries || 3 ;
35+ this . backoffFactor = options ?. backoffFactor || 2 ;
36+ this . onRateLimitUpdate = options ?. onRateLimitUpdate ;
37+ this . _abortController = null ;
38+ this . resilientOperations = { } ; // Store resilient operation instances for observability
4239 }
4340
4441 getApiUrl ( aiService ) {
@@ -159,11 +156,25 @@ class ResilientLLM {
159156 throw new Error ( 'Invalid provider specified. Use "anthropic" or "openai" or "gemini" or "ollama".' ) ;
160157 }
161158 try {
159+ // Instantiate ResilientOperation for LLM calls
160+ const resilientOperation = new ResilientOperation ( {
161+ bucketId : this . aiService ,
162+ rateLimitConfig : this . rateLimitConfig ,
163+ retries : this . retries ,
164+ timeout : this . timeout ,
165+ backoffFactor : this . backoffFactor ,
166+ onRateLimitUpdate : this . onRateLimitUpdate ,
167+ cacheStore : this . cacheStore
168+ } ) ;
169+ // Use single instance of abort controller for all operations
170+ this . _abortController = this . _abortController || new AbortController ( ) ;
171+ this . resilientOperations [ resilientOperation . id ] = resilientOperation ;
162172 // Wrap the LLM API call in ResilientOperation for rate limiting, retries, etc.
163- const { data, statusCode } = await this . resilientOperation
173+ const { data, statusCode } = await resilientOperation
164174 . withTokens ( estimatedLLMTokens )
165175 . withCache ( )
166- . execute ( this . _makeHttpRequest , apiUrl , requestBody , headers ) ;
176+ . withAbortControl ( this . _abortController )
177+ . execute ( this . _makeHttpRequest , apiUrl , requestBody , headers , this . _abortController . signal ) ;
167178 /**
168179 * OpenAI chat completion response
169180 * {
@@ -223,6 +234,7 @@ class ResilientLLM {
223234 content = this . parseOllamaChatCompletion ( data , llmOptions ?. tools ) ;
224235 break ;
225236 }
237+ delete this . resilientOperations [ resilientOperation . id ] ;
226238 return content ;
227239 } catch ( error ) {
228240 console . error ( `Error calling ${ aiService } API:` , error ) ;
@@ -256,6 +268,8 @@ class ResilientLLM {
256268 * @returns {Promise<{data: any, statusCode: number}> }
257269 */
258270 async _makeHttpRequest ( apiUrl , requestBody , headers , abortSignal ) {
271+ console . log ( "Making HTTP request to:" , apiUrl ) ;
272+ console . log ( "You may cancel it by calling abort() method on the ResilientLLM instance" ) ;
259273 const startTime = Date . now ( ) ;
260274
261275 try {
@@ -291,7 +305,8 @@ class ResilientLLM {
291305
292306 /**
293307 * Parse errors from various LLM APIs to create uniform error communication
294- * @param {* } error
308+ * @param {number|null } statusCode - HTTP status code or null for general errors
309+ * @param {Error|Object|null } error - Error object
295310 * @reference https://platform.openai.com/docs/guides/error-codes/api-error-codes
296311 * @reference https://docs.anthropic.com/en/api/errors
297312 */
@@ -305,8 +320,6 @@ class ResilientLLM {
305320 throw new Error ( error ?. message || "Invalid API Key" ) ;
306321 case 403 :
307322 throw new Error ( error ?. message || "You are not authorized to access this resource" ) ;
308- case 400 :
309- throw new Error ( error ?. message || "Bad request" ) ;
310323 case 429 :
311324 throw new Error ( error ?. message || "Rate limit exceeded" ) ;
312325 case 404 :
@@ -380,7 +393,12 @@ class ResilientLLM {
380393 return data ?. choices ?. [ 0 ] ?. message ?. content ;
381394 }
382395
383-
396+ abort ( ) {
397+ this . _abortController ?. abort ( ) ;
398+ this . _abortController = null ;
399+ this . resilientOperations = { } ;
400+ this . _abortController = null ;
401+ }
384402
385403 /**
386404 * Estimate the number of tokens in a text
0 commit comments