@livekit/agents 1.0.32 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/inference/llm.cjs +0 -2
  2. package/dist/inference/llm.cjs.map +1 -1
  3. package/dist/inference/llm.d.ts.map +1 -1
  4. package/dist/inference/llm.js +0 -2
  5. package/dist/inference/llm.js.map +1 -1
  6. package/dist/llm/fallback_adapter.cjs +278 -0
  7. package/dist/llm/fallback_adapter.cjs.map +1 -0
  8. package/dist/llm/fallback_adapter.d.cts +73 -0
  9. package/dist/llm/fallback_adapter.d.ts +73 -0
  10. package/dist/llm/fallback_adapter.d.ts.map +1 -0
  11. package/dist/llm/fallback_adapter.js +254 -0
  12. package/dist/llm/fallback_adapter.js.map +1 -0
  13. package/dist/llm/fallback_adapter.test.cjs +176 -0
  14. package/dist/llm/fallback_adapter.test.cjs.map +1 -0
  15. package/dist/llm/fallback_adapter.test.js +175 -0
  16. package/dist/llm/fallback_adapter.test.js.map +1 -0
  17. package/dist/llm/index.cjs +3 -0
  18. package/dist/llm/index.cjs.map +1 -1
  19. package/dist/llm/index.d.cts +1 -0
  20. package/dist/llm/index.d.ts +1 -0
  21. package/dist/llm/index.d.ts.map +1 -1
  22. package/dist/llm/index.js +4 -0
  23. package/dist/llm/index.js.map +1 -1
  24. package/dist/llm/llm.cjs +1 -1
  25. package/dist/llm/llm.cjs.map +1 -1
  26. package/dist/llm/llm.js +1 -1
  27. package/dist/llm/llm.js.map +1 -1
  28. package/dist/log.cjs +3 -3
  29. package/dist/log.cjs.map +1 -1
  30. package/dist/log.d.cts +0 -5
  31. package/dist/log.d.ts +0 -5
  32. package/dist/log.d.ts.map +1 -1
  33. package/dist/log.js +3 -3
  34. package/dist/log.js.map +1 -1
  35. package/dist/stt/stt.cjs +1 -1
  36. package/dist/stt/stt.cjs.map +1 -1
  37. package/dist/stt/stt.js +1 -1
  38. package/dist/stt/stt.js.map +1 -1
  39. package/dist/tts/tts.cjs +2 -2
  40. package/dist/tts/tts.cjs.map +1 -1
  41. package/dist/tts/tts.js +2 -2
  42. package/dist/tts/tts.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/inference/llm.ts +0 -2
  45. package/src/llm/fallback_adapter.test.ts +238 -0
  46. package/src/llm/fallback_adapter.ts +391 -0
  47. package/src/llm/index.ts +6 -0
  48. package/src/llm/llm.ts +1 -1
  49. package/src/log.ts +3 -9
  50. package/src/stt/stt.ts +1 -1
  51. package/src/tts/tts.ts +2 -2
@@ -251,8 +251,6 @@ class LLMStream extends llm.LLMStream {
251
251
  options: { retryable }
252
252
  });
253
253
  }
254
- } finally {
255
- this.queue.close();
256
254
  }
257
255
  }
258
256
  parseChoice(id, choice) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inference/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport OpenAI from 'openai';\nimport {\n APIConnectionError,\n APIStatusError,\n APITimeoutError,\n DEFAULT_API_CONNECT_OPTIONS,\n type Expand,\n toError,\n} from '../index.js';\nimport * as llm from '../llm/index.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { type AnyString, createAccessToken } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://agent-gateway.livekit.cloud/v1';\n\nexport type OpenAIModels =\n | 'openai/gpt-5'\n | 'openai/gpt-5-mini'\n | 'openai/gpt-5-nano'\n | 'openai/gpt-4.1'\n | 'openai/gpt-4.1-mini'\n | 'openai/gpt-4.1-nano'\n | 'openai/gpt-4o'\n | 'openai/gpt-4o-mini'\n | 'openai/gpt-oss-120b';\n\nexport type GoogleModels = 'google/gemini-2.0-flash-lite';\n\nexport type QwenModels = 'qwen/qwen3-235b-a22b-instruct';\n\nexport type KimiModels = 'moonshotai/kimi-k2-instruct';\n\nexport type DeepSeekModels = 'deepseek-ai/deepseek-v3';\n\ntype ChatCompletionPredictionContentParam =\n Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;\ntype WebSearchOptions = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions>;\ntype ToolChoice = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice']>;\ntype Verbosity = 'low' | 'medium' | 'high';\n\nexport interface ChatCompletionOptions extends Record<string, unknown> {\n frequency_penalty?: number;\n logit_bias?: Record<string, number>;\n logprobs?: boolean;\n max_completion_tokens?: number;\n max_tokens?: number;\n metadata?: Record<string, string>;\n modalities?: Array<'text' | 'audio'>;\n n?: number;\n parallel_tool_calls?: boolean;\n prediction?: ChatCompletionPredictionContentParam | null;\n presence_penalty?: number;\n prompt_cache_key?: string;\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n safety_identifier?: string;\n seed?: number;\n service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority';\n stop?: string | string[];\n store?: boolean;\n temperature?: number;\n top_logprobs?: number;\n top_p?: number;\n user?: string;\n verbosity?: Verbosity;\n web_search_options?: WebSearchOptions;\n\n // livekit-typed arguments\n tool_choice?: ToolChoice;\n // TODO(brian): support response format\n // response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams['response_format']\n}\n\nexport type LLMModels =\n | OpenAIModels\n | GoogleModels\n | QwenModels\n | KimiModels\n | DeepSeekModels\n | AnyString;\n\nexport interface InferenceLLMOptions {\n model: LLMModels;\n provider?: string;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: ChatCompletionOptions;\n strictToolSchema?: boolean;\n}\n\nexport interface GatewayOptions {\n apiKey: string;\n apiSecret: string;\n}\n\n/**\n * Livekit Cloud Inference LLM\n */\nexport class LLM extends llm.LLM {\n private client: OpenAI;\n private opts: InferenceLLMOptions;\n\n constructor(opts: {\n model: LLMModels;\n provider?: string;\n baseURL?: string;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: InferenceLLMOptions['modelOptions'];\n strictToolSchema?: boolean;\n }) {\n super();\n\n const {\n model,\n provider,\n baseURL,\n apiKey,\n apiSecret,\n modelOptions,\n strictToolSchema = false,\n } = opts;\n\n const lkBaseURL = baseURL || process.env.LIVEKIT_INFERENCE_URL || DEFAULT_BASE_URL;\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n this.opts = {\n model,\n provider,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions: modelOptions || {},\n strictToolSchema,\n };\n\n this.client = new OpenAI({\n baseURL: this.opts.baseURL,\n apiKey: '', // leave a temporary empty string to avoid OpenAI complain about missing key\n });\n }\n\n label(): string {\n return 'inference.LLM';\n }\n\n get model(): string {\n return this.opts.model;\n }\n\n static fromModelString(modelString: string): LLM {\n return new LLM({ model: modelString });\n }\n\n chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n parallelToolCalls,\n toolChoice,\n // TODO(AJS-270): Add response_format parameter support\n extraKwargs,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n // TODO(AJS-270): Add responseFormat parameter\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n let modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };\n\n parallelToolCalls =\n parallelToolCalls !== undefined\n ? parallelToolCalls\n : this.opts.modelOptions.parallel_tool_calls;\n\n if (toolCtx && Object.keys(toolCtx).length > 0 && parallelToolCalls !== undefined) {\n modelOptions.parallel_tool_calls = parallelToolCalls;\n }\n\n toolChoice =\n toolChoice !== undefined\n ? toolChoice\n : (this.opts.modelOptions.tool_choice as llm.ToolChoice | undefined);\n\n if (toolChoice) {\n modelOptions.tool_choice = toolChoice as ToolChoice;\n }\n\n // TODO(AJS-270): Add response_format support here\n\n modelOptions = { ...modelOptions, ...this.opts.modelOptions };\n\n return new LLMStream(this, {\n model: this.opts.model,\n provider: this.opts.provider,\n client: this.client,\n chatCtx,\n toolCtx,\n connOptions,\n modelOptions,\n strictToolSchema: this.opts.strictToolSchema ?? false, // default to false if not set\n gatewayOptions: {\n apiKey: this.opts.apiKey,\n apiSecret: this.opts.apiSecret,\n },\n });\n }\n}\n\nexport class LLMStream extends llm.LLMStream {\n private model: LLMModels;\n private provider?: string;\n private providerFmt: llm.ProviderFormat;\n private client: OpenAI;\n private modelOptions: Record<string, unknown>;\n private strictToolSchema: boolean;\n\n private gatewayOptions?: GatewayOptions;\n private toolCallId?: string;\n private toolIndex?: number;\n private fncName?: string;\n private fncRawArguments?: string;\n\n constructor(\n llm: LLM,\n {\n model,\n provider,\n client,\n chatCtx,\n toolCtx,\n gatewayOptions,\n connOptions,\n modelOptions,\n providerFmt,\n strictToolSchema,\n }: {\n model: LLMModels;\n provider?: string;\n client: OpenAI;\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n gatewayOptions?: GatewayOptions;\n connOptions: APIConnectOptions;\n modelOptions: Record<string, unknown>;\n providerFmt?: llm.ProviderFormat;\n strictToolSchema: boolean;\n },\n ) {\n super(llm, { chatCtx, toolCtx, connOptions });\n this.client = client;\n this.gatewayOptions = gatewayOptions;\n this.provider = provider;\n this.providerFmt = providerFmt || 'openai';\n this.modelOptions = modelOptions;\n this.model = model;\n this.strictToolSchema = strictToolSchema;\n }\n\n protected async run(): Promise<void> {\n // current function call that we're waiting for full completion (args are streamed)\n // (defined inside the run method to make sure the state is reset for each run/attempt)\n let retryable = true;\n this.toolCallId = this.fncName = this.fncRawArguments = this.toolIndex = undefined;\n\n try {\n const messages = (await this.chatCtx.toProviderFormat(\n this.providerFmt,\n )) as OpenAI.ChatCompletionMessageParam[];\n\n const tools = this.toolCtx\n ? Object.entries(this.toolCtx).map(([name, func]) => {\n const oaiParams = {\n type: 'function' as const,\n function: {\n name,\n description: func.description,\n parameters: llm.toJsonSchema(\n func.parameters,\n true,\n this.strictToolSchema,\n ) as unknown as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function']['parameters'],\n } as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function'],\n };\n\n if (this.strictToolSchema) {\n oaiParams.function.strict = true;\n }\n\n return oaiParams;\n })\n : undefined;\n\n const requestOptions: Record<string, unknown> = { ...this.modelOptions };\n if (!tools) {\n delete requestOptions.tool_choice;\n }\n\n // Dynamically set the access token for the LiveKit Agent Gateway API\n if (this.gatewayOptions) {\n this.client.apiKey = await createAccessToken(\n this.gatewayOptions.apiKey,\n this.gatewayOptions.apiSecret,\n );\n }\n\n if (this.provider) {\n const extraHeaders = requestOptions.extra_headers\n ? (requestOptions.extra_headers as Record<string, string>)\n : {};\n extraHeaders['X-LiveKit-Inference-Provider'] = this.provider;\n requestOptions.extra_headers = extraHeaders;\n }\n\n const stream = await this.client.chat.completions.create(\n {\n model: this.model,\n messages,\n tools,\n stream: true,\n stream_options: { include_usage: true },\n ...requestOptions,\n },\n {\n timeout: this.connOptions.timeoutMs,\n },\n );\n\n for await (const chunk of stream) {\n for (const choice of chunk.choices) {\n if (this.abortController.signal.aborted) {\n break;\n }\n const chatChunk = this.parseChoice(chunk.id, choice);\n if (chatChunk) {\n retryable = false;\n this.queue.put(chatChunk);\n }\n }\n\n if (chunk.usage) {\n const usage = chunk.usage;\n retryable = false;\n this.queue.put({\n id: chunk.id,\n usage: {\n completionTokens: usage.completion_tokens,\n promptTokens: usage.prompt_tokens,\n promptCachedTokens: usage.prompt_tokens_details?.cached_tokens || 0,\n totalTokens: usage.total_tokens,\n },\n });\n }\n }\n } catch (error) {\n if (error instanceof OpenAI.APIConnectionTimeoutError) {\n throw new APITimeoutError({ options: { retryable } });\n } else if (error instanceof OpenAI.APIError) {\n throw new APIStatusError({\n message: error.message,\n options: {\n statusCode: error.status,\n body: error.error,\n requestId: error.requestID,\n retryable,\n },\n });\n } else {\n throw new APIConnectionError({\n message: toError(error).message,\n options: { retryable },\n });\n }\n } finally {\n this.queue.close();\n }\n }\n\n private parseChoice(\n id: string,\n choice: OpenAI.ChatCompletionChunk.Choice,\n ): llm.ChatChunk | undefined {\n const delta = choice.delta;\n\n // https://github.com/livekit/agents/issues/688\n // the delta can be None when using Azure OpenAI (content filtering)\n if (delta === undefined) return undefined;\n\n if (delta.tool_calls) {\n // check if we have functions to calls\n for (const tool of delta.tool_calls) {\n if (!tool.function) {\n continue; // oai may add other tools in the future\n }\n\n /**\n * The way OpenAI streams tool calls is a bit tricky.\n *\n * For any new tool call, it first emits a delta tool call with id, and function name,\n * the rest of the delta chunks will only stream the remaining arguments string,\n * until a new tool call is started or the tool call is finished.\n * See below for an example.\n *\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)\n * [ChoiceDeltaToolCall(index=0, id='call_LaVeHWUHpef9K1sd5UO8TtLg', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"P', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris\\}', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id='call_ThU4OmMdQXnnVmpXGOCknXIB', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"T', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='okyo', name=None), type=None)]\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)\n */\n let callChunk: llm.ChatChunk | undefined;\n // If we have a previous tool call and this is a new one, emit the previous\n if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {\n callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n }\n\n // Start or continue building the current tool call\n if (tool.function.name) {\n this.toolIndex = tool.index;\n this.toolCallId = tool.id;\n this.fncName = tool.function.name;\n this.fncRawArguments = tool.function.arguments || '';\n } else if (tool.function.arguments) {\n this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;\n }\n\n if (callChunk) {\n return callChunk;\n }\n }\n }\n\n // If we're done with tool calls, emit the final one\n if (\n choice.finish_reason &&\n ['tool_calls', 'stop'].includes(choice.finish_reason) &&\n this.toolCallId !== undefined\n ) {\n const callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n return callChunk;\n }\n\n // Regular content message\n if (!delta.content) {\n return undefined;\n }\n\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content,\n },\n };\n }\n\n private createRunningToolCallChunk(\n id: string,\n delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta,\n ): llm.ChatChunk {\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content || undefined,\n toolCalls: [\n llm.FunctionCall.create({\n callId: this.toolCallId || '',\n name: this.fncName || '',\n args: this.fncRawArguments || '',\n }),\n ],\n },\n };\n }\n}\n"],"mappings":"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","names":["OpenAI","llm"]}
1
+ {"version":3,"sources":["../../src/inference/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport OpenAI from 'openai';\nimport {\n APIConnectionError,\n APIStatusError,\n APITimeoutError,\n DEFAULT_API_CONNECT_OPTIONS,\n type Expand,\n toError,\n} from '../index.js';\nimport * as llm from '../llm/index.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { type AnyString, createAccessToken } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://agent-gateway.livekit.cloud/v1';\n\nexport type OpenAIModels =\n | 'openai/gpt-5'\n | 'openai/gpt-5-mini'\n | 'openai/gpt-5-nano'\n | 'openai/gpt-4.1'\n | 'openai/gpt-4.1-mini'\n | 'openai/gpt-4.1-nano'\n | 'openai/gpt-4o'\n | 'openai/gpt-4o-mini'\n | 'openai/gpt-oss-120b';\n\nexport type GoogleModels = 'google/gemini-2.0-flash-lite';\n\nexport type QwenModels = 'qwen/qwen3-235b-a22b-instruct';\n\nexport type KimiModels = 'moonshotai/kimi-k2-instruct';\n\nexport type DeepSeekModels = 'deepseek-ai/deepseek-v3';\n\ntype ChatCompletionPredictionContentParam =\n Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;\ntype WebSearchOptions = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions>;\ntype ToolChoice = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice']>;\ntype Verbosity = 'low' | 'medium' | 'high';\n\nexport interface ChatCompletionOptions extends Record<string, unknown> {\n frequency_penalty?: number;\n logit_bias?: Record<string, number>;\n logprobs?: boolean;\n max_completion_tokens?: number;\n max_tokens?: number;\n metadata?: Record<string, string>;\n modalities?: Array<'text' | 'audio'>;\n n?: number;\n parallel_tool_calls?: boolean;\n prediction?: ChatCompletionPredictionContentParam | null;\n presence_penalty?: number;\n prompt_cache_key?: string;\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n safety_identifier?: string;\n seed?: number;\n service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority';\n stop?: string | string[];\n store?: boolean;\n temperature?: number;\n top_logprobs?: number;\n top_p?: number;\n user?: string;\n verbosity?: Verbosity;\n web_search_options?: WebSearchOptions;\n\n // livekit-typed arguments\n tool_choice?: ToolChoice;\n // TODO(brian): support response format\n // response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams['response_format']\n}\n\nexport type LLMModels =\n | OpenAIModels\n | GoogleModels\n | QwenModels\n | KimiModels\n | DeepSeekModels\n | AnyString;\n\nexport interface InferenceLLMOptions {\n model: LLMModels;\n provider?: string;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: ChatCompletionOptions;\n strictToolSchema?: boolean;\n}\n\nexport interface GatewayOptions {\n apiKey: string;\n apiSecret: string;\n}\n\n/**\n * Livekit Cloud Inference LLM\n */\nexport class LLM extends llm.LLM {\n private client: OpenAI;\n private opts: InferenceLLMOptions;\n\n constructor(opts: {\n model: LLMModels;\n provider?: string;\n baseURL?: string;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: InferenceLLMOptions['modelOptions'];\n strictToolSchema?: boolean;\n }) {\n super();\n\n const {\n model,\n provider,\n baseURL,\n apiKey,\n apiSecret,\n modelOptions,\n strictToolSchema = false,\n } = opts;\n\n const lkBaseURL = baseURL || process.env.LIVEKIT_INFERENCE_URL || DEFAULT_BASE_URL;\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n this.opts = {\n model,\n provider,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions: modelOptions || {},\n strictToolSchema,\n };\n\n this.client = new OpenAI({\n baseURL: this.opts.baseURL,\n apiKey: '', // leave a temporary empty string to avoid OpenAI complain about missing key\n });\n }\n\n label(): string {\n return 'inference.LLM';\n }\n\n get model(): string {\n return this.opts.model;\n }\n\n static fromModelString(modelString: string): LLM {\n return new LLM({ model: modelString });\n }\n\n chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n parallelToolCalls,\n toolChoice,\n // TODO(AJS-270): Add response_format parameter support\n extraKwargs,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n // TODO(AJS-270): Add responseFormat parameter\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n let modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };\n\n parallelToolCalls =\n parallelToolCalls !== undefined\n ? parallelToolCalls\n : this.opts.modelOptions.parallel_tool_calls;\n\n if (toolCtx && Object.keys(toolCtx).length > 0 && parallelToolCalls !== undefined) {\n modelOptions.parallel_tool_calls = parallelToolCalls;\n }\n\n toolChoice =\n toolChoice !== undefined\n ? toolChoice\n : (this.opts.modelOptions.tool_choice as llm.ToolChoice | undefined);\n\n if (toolChoice) {\n modelOptions.tool_choice = toolChoice as ToolChoice;\n }\n\n // TODO(AJS-270): Add response_format support here\n\n modelOptions = { ...modelOptions, ...this.opts.modelOptions };\n\n return new LLMStream(this, {\n model: this.opts.model,\n provider: this.opts.provider,\n client: this.client,\n chatCtx,\n toolCtx,\n connOptions,\n modelOptions,\n strictToolSchema: this.opts.strictToolSchema ?? false, // default to false if not set\n gatewayOptions: {\n apiKey: this.opts.apiKey,\n apiSecret: this.opts.apiSecret,\n },\n });\n }\n}\n\nexport class LLMStream extends llm.LLMStream {\n private model: LLMModels;\n private provider?: string;\n private providerFmt: llm.ProviderFormat;\n private client: OpenAI;\n private modelOptions: Record<string, unknown>;\n private strictToolSchema: boolean;\n\n private gatewayOptions?: GatewayOptions;\n private toolCallId?: string;\n private toolIndex?: number;\n private fncName?: string;\n private fncRawArguments?: string;\n\n constructor(\n llm: LLM,\n {\n model,\n provider,\n client,\n chatCtx,\n toolCtx,\n gatewayOptions,\n connOptions,\n modelOptions,\n providerFmt,\n strictToolSchema,\n }: {\n model: LLMModels;\n provider?: string;\n client: OpenAI;\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n gatewayOptions?: GatewayOptions;\n connOptions: APIConnectOptions;\n modelOptions: Record<string, unknown>;\n providerFmt?: llm.ProviderFormat;\n strictToolSchema: boolean;\n },\n ) {\n super(llm, { chatCtx, toolCtx, connOptions });\n this.client = client;\n this.gatewayOptions = gatewayOptions;\n this.provider = provider;\n this.providerFmt = providerFmt || 'openai';\n this.modelOptions = modelOptions;\n this.model = model;\n this.strictToolSchema = strictToolSchema;\n }\n\n protected async run(): Promise<void> {\n // current function call that we're waiting for full completion (args are streamed)\n // (defined inside the run method to make sure the state is reset for each run/attempt)\n let retryable = true;\n this.toolCallId = this.fncName = this.fncRawArguments = this.toolIndex = undefined;\n\n try {\n const messages = (await this.chatCtx.toProviderFormat(\n this.providerFmt,\n )) as OpenAI.ChatCompletionMessageParam[];\n\n const tools = this.toolCtx\n ? Object.entries(this.toolCtx).map(([name, func]) => {\n const oaiParams = {\n type: 'function' as const,\n function: {\n name,\n description: func.description,\n parameters: llm.toJsonSchema(\n func.parameters,\n true,\n this.strictToolSchema,\n ) as unknown as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function']['parameters'],\n } as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function'],\n };\n\n if (this.strictToolSchema) {\n oaiParams.function.strict = true;\n }\n\n return oaiParams;\n })\n : undefined;\n\n const requestOptions: Record<string, unknown> = { ...this.modelOptions };\n if (!tools) {\n delete requestOptions.tool_choice;\n }\n\n // Dynamically set the access token for the LiveKit Agent Gateway API\n if (this.gatewayOptions) {\n this.client.apiKey = await createAccessToken(\n this.gatewayOptions.apiKey,\n this.gatewayOptions.apiSecret,\n );\n }\n\n if (this.provider) {\n const extraHeaders = requestOptions.extra_headers\n ? (requestOptions.extra_headers as Record<string, string>)\n : {};\n extraHeaders['X-LiveKit-Inference-Provider'] = this.provider;\n requestOptions.extra_headers = extraHeaders;\n }\n\n const stream = await this.client.chat.completions.create(\n {\n model: this.model,\n messages,\n tools,\n stream: true,\n stream_options: { include_usage: true },\n ...requestOptions,\n },\n {\n timeout: this.connOptions.timeoutMs,\n },\n );\n\n for await (const chunk of stream) {\n for (const choice of chunk.choices) {\n if (this.abortController.signal.aborted) {\n break;\n }\n const chatChunk = this.parseChoice(chunk.id, choice);\n if (chatChunk) {\n retryable = false;\n this.queue.put(chatChunk);\n }\n }\n\n if (chunk.usage) {\n const usage = chunk.usage;\n retryable = false;\n this.queue.put({\n id: chunk.id,\n usage: {\n completionTokens: usage.completion_tokens,\n promptTokens: usage.prompt_tokens,\n promptCachedTokens: usage.prompt_tokens_details?.cached_tokens || 0,\n totalTokens: usage.total_tokens,\n },\n });\n }\n }\n } catch (error) {\n if (error instanceof OpenAI.APIConnectionTimeoutError) {\n throw new APITimeoutError({ options: { retryable } });\n } else if (error instanceof OpenAI.APIError) {\n throw new APIStatusError({\n message: error.message,\n options: {\n statusCode: error.status,\n body: error.error,\n requestId: error.requestID,\n retryable,\n },\n });\n } else {\n throw new APIConnectionError({\n message: toError(error).message,\n options: { retryable },\n });\n }\n }\n }\n\n private parseChoice(\n id: string,\n choice: OpenAI.ChatCompletionChunk.Choice,\n ): llm.ChatChunk | undefined {\n const delta = choice.delta;\n\n // https://github.com/livekit/agents/issues/688\n // the delta can be None when using Azure OpenAI (content filtering)\n if (delta === undefined) return undefined;\n\n if (delta.tool_calls) {\n // check if we have functions to calls\n for (const tool of delta.tool_calls) {\n if (!tool.function) {\n continue; // oai may add other tools in the future\n }\n\n /**\n * The way OpenAI streams tool calls is a bit tricky.\n *\n * For any new tool call, it first emits a delta tool call with id, and function name,\n * the rest of the delta chunks will only stream the remaining arguments string,\n * until a new tool call is started or the tool call is finished.\n * See below for an example.\n *\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)\n * [ChoiceDeltaToolCall(index=0, id='call_LaVeHWUHpef9K1sd5UO8TtLg', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"P', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris\\}', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id='call_ThU4OmMdQXnnVmpXGOCknXIB', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"T', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='okyo', name=None), type=None)]\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)\n */\n let callChunk: llm.ChatChunk | undefined;\n // If we have a previous tool call and this is a new one, emit the previous\n if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {\n callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n }\n\n // Start or continue building the current tool call\n if (tool.function.name) {\n this.toolIndex = tool.index;\n this.toolCallId = tool.id;\n this.fncName = tool.function.name;\n this.fncRawArguments = tool.function.arguments || '';\n } else if (tool.function.arguments) {\n this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;\n }\n\n if (callChunk) {\n return callChunk;\n }\n }\n }\n\n // If we're done with tool calls, emit the final one\n if (\n choice.finish_reason &&\n ['tool_calls', 'stop'].includes(choice.finish_reason) &&\n this.toolCallId !== undefined\n ) {\n const callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n return callChunk;\n }\n\n // Regular content message\n if (!delta.content) {\n return undefined;\n }\n\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content,\n },\n };\n }\n\n private createRunningToolCallChunk(\n id: string,\n delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta,\n ): llm.ChatChunk {\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content || undefined,\n toolCalls: [\n llm.FunctionCall.create({\n callId: this.toolCallId || '',\n name: this.fncName || '',\n args: this.fncRawArguments || '',\n }),\n ],\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAmB;AACnB,eAOO;AACP,UAAqB;AAErB,mBAAkD;AAElD,MAAM,mBAAmB;AAqFlB,MAAM,YAAY,IAAI,IAAI;AAAA,EACvB;AAAA,EACA;AAAA,EAER,YAAY,MAQT;AACD,UAAM;AAEN,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,mBAAmB;AAAA,IACrB,IAAI;AAEJ,UAAM,YAAY,WAAW,QAAQ,IAAI,yBAAyB;AAClE,UAAM,WAAW,UAAU,QAAQ,IAAI,6BAA6B,QAAQ,IAAI;AAChF,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,UAAM,cACJ,aAAa,QAAQ,IAAI,gCAAgC,QAAQ,IAAI;AACvE,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AAEA,SAAK,OAAO;AAAA,MACV;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,cAAc,gBAAgB,CAAC;AAAA,MAC/B;AAAA,IACF;AAEA,SAAK,SAAS,IAAI,cAAAA,QAAO;AAAA,MACvB,SAAS,KAAK,KAAK;AAAA,MACnB,QAAQ;AAAA;AAAA,IACV,CAAC;AAAA,EACH;AAAA,EAEA,QAAgB;AACd,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,QAAgB;AAClB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,OAAO,gBAAgB,aAA0B;AAC/C,WAAO,IAAI,IAAI,EAAE,OAAO,YAAY,CAAC;AAAA,EACvC;AAAA,EAEA,KAAK;AAAA,IACH;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,EACF,GAQc;AACZ,QAAI,eAAwC,EAAE,GAAI,eAAe,CAAC,EAAG;AAErE,wBACE,sBAAsB,SAClB,oBACA,KAAK,KAAK,aAAa;AAE7B,QAAI,WAAW,OAAO,KAAK,OAAO,EAAE,SAAS,KAAK,sBAAsB,QAAW;AACjF,mBAAa,sBAAsB;AAAA,IACrC;AAEA,iBACE,eAAe,SACX,aACC,KAAK,KAAK,aAAa;AAE9B,QAAI,YAAY;AACd,mBAAa,cAAc;AAAA,IAC7B;AAIA,mBAAe,EAAE,GAAG,cAAc,GAAG,KAAK,KAAK,aAAa;AAE5D,WAAO,IAAI,UAAU,MAAM;AAAA,MACzB,OAAO,KAAK,KAAK;AAAA,MACjB,UAAU,KAAK,KAAK;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,kBAAkB,KAAK,KAAK,oBAAoB;AAAA;AAAA,MAChD,gBAAgB;AAAA,QACd,QAAQ,KAAK,KAAK;AAAA,QAClB,WAAW,KAAK,KAAK;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,kBAAkB,IAAI,UAAU;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACEC,MACA;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAYA;AACA,UAAMA,MAAK,EAAE,SAAS,SAAS,YAAY,CAAC;AAC5C,SAAK,SAAS;AACd,SAAK,iBAAiB;AACtB,SAAK,WAAW;AAChB,SAAK,cAAc,eAAe;AAClC,SAAK,eAAe;AACpB,SAAK,QAAQ;AACb,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,MAAgB,MAAqB;AAlRvC;AAqRI,QAAI,YAAY;AAChB,SAAK,aAAa,KAAK,UAAU,KAAK,kBAAkB,KAAK,YAAY;AAEzE,QAAI;AACF,YAAM,WAAY,MAAM,KAAK,QAAQ;AAAA,QACnC,KAAK;AAAA,MACP;AAEA,YAAM,QAAQ,KAAK,UACf,OAAO,QAAQ,KAAK,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;AACjD,cAAM,YAAY;AAAA,UAChB,MAAM;AAAA,UACN,UAAU;AAAA,YACR;AAAA,YACA,aAAa,KAAK;AAAA,YAClB,YAAY,IAAI;AAAA,cACd,KAAK;AAAA,cACL;AAAA,cACA,KAAK;AAAA,YACP;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,kBAAkB;AACzB,oBAAU,SAAS,SAAS;AAAA,QAC9B;AAEA,eAAO;AAAA,MACT,CAAC,IACD;AAEJ,YAAM,iBAA0C,EAAE,GAAG,KAAK,aAAa;AACvE,UAAI,CAAC,OAAO;AACV,eAAO,eAAe;AAAA,MACxB;AAGA,UAAI,KAAK,gBAAgB;AACvB,aAAK,OAAO,SAAS,UAAM;AAAA,UACzB,KAAK,eAAe;AAAA,UACpB,KAAK,eAAe;AAAA,QACtB;AAAA,MACF;AAEA,UAAI,KAAK,UAAU;AACjB,cAAM,eAAe,eAAe,gBAC/B,eAAe,gBAChB,CAAC;AACL,qBAAa,8BAA8B,IAAI,KAAK;AACpD,uBAAe,gBAAgB;AAAA,MACjC;AAEA,YAAM,SAAS,MAAM,KAAK,OAAO,KAAK,YAAY;AAAA,QAChD;AAAA,UACE,OAAO,KAAK;AAAA,UACZ;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,gBAAgB,EAAE,eAAe,KAAK;AAAA,UACtC,GAAG;AAAA,QACL;AAAA,QACA;AAAA,UACE,SAAS,KAAK,YAAY;AAAA,QAC5B;AAAA,MACF;AAEA,uBAAiB,SAAS,QAAQ;AAChC,mBAAW,UAAU,MAAM,SAAS;AAClC,cAAI,KAAK,gBAAgB,OAAO,SAAS;AACvC;AAAA,UACF;AACA,gBAAM,YAAY,KAAK,YAAY,MAAM,IAAI,MAAM;AACnD,cAAI,WAAW;AACb,wBAAY;AACZ,iBAAK,MAAM,IAAI,SAAS;AAAA,UAC1B;AAAA,QACF;AAEA,YAAI,MAAM,OAAO;AACf,gBAAM,QAAQ,MAAM;AACpB,sBAAY;AACZ,eAAK,MAAM,IAAI;AAAA,YACb,IAAI,MAAM;AAAA,YACV,OAAO;AAAA,cACL,kBAAkB,MAAM;AAAA,cACxB,cAAc,MAAM;AAAA,cACpB,sBAAoB,WAAM,0BAAN,mBAA6B,kBAAiB;AAAA,cAClE,aAAa,MAAM;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,UAAI,iBAAiB,cAAAD,QAAO,2BAA2B;AACrD,cAAM,IAAI,yBAAgB,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAAA,MACtD,WAAW,iBAAiB,cAAAA,QAAO,UAAU;AAC3C,cAAM,IAAI,wBAAe;AAAA,UACvB,SAAS,MAAM;AAAA,UACf,SAAS;AAAA,YACP,YAAY,MAAM;AAAA,YAClB,MAAM,MAAM;AAAA,YACZ,WAAW,MAAM;AAAA,YACjB;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,cAAM,IAAI,4BAAmB;AAAA,UAC3B,aAAS,kBAAQ,KAAK,EAAE;AAAA,UACxB,SAAS,EAAE,UAAU;AAAA,QACvB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YACN,IACA,QAC2B;AAC3B,UAAM,QAAQ,OAAO;AAIrB,QAAI,UAAU,OAAW,QAAO;AAEhC,QAAI,MAAM,YAAY;AAEpB,iBAAW,QAAQ,MAAM,YAAY;AACnC,YAAI,CAAC,KAAK,UAAU;AAClB;AAAA,QACF;AAmBA,YAAI;AAEJ,YAAI,KAAK,cAAc,KAAK,MAAM,KAAK,UAAU,KAAK,WAAW;AAC/D,sBAAY,KAAK,2BAA2B,IAAI,KAAK;AACrD,eAAK,aAAa,KAAK,UAAU,KAAK,kBAAkB;AAAA,QAC1D;AAGA,YAAI,KAAK,SAAS,MAAM;AACtB,eAAK,YAAY,KAAK;AACtB,eAAK,aAAa,KAAK;AACvB,eAAK,UAAU,KAAK,SAAS;AAC7B,eAAK,kBAAkB,KAAK,SAAS,aAAa;AAAA,QACpD,WAAW,KAAK,SAAS,WAAW;AAClC,eAAK,mBAAmB,KAAK,mBAAmB,MAAM,KAAK,SAAS;AAAA,QACtE;AAEA,YAAI,WAAW;AACb,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QACE,OAAO,iBACP,CAAC,cAAc,MAAM,EAAE,SAAS,OAAO,aAAa,KACpD,KAAK,eAAe,QACpB;AACA,YAAM,YAAY,KAAK,2BAA2B,IAAI,KAAK;AAC3D,WAAK,aAAa,KAAK,UAAU,KAAK,kBAAkB;AACxD,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,MAAM,SAAS;AAClB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,2BACN,IACA,OACe;AACf,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,MAAM,WAAW;AAAA,QAC1B,WAAW;AAAA,UACT,IAAI,aAAa,OAAO;AAAA,YACtB,QAAQ,KAAK,cAAc;AAAA,YAC3B,MAAM,KAAK,WAAW;AAAA,YACtB,MAAM,KAAK,mBAAmB;AAAA,UAChC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":["OpenAI","llm"]}
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/inference/llm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/inference/llm.ts"],"names":[],"mappings":"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"}
@@ -223,8 +223,6 @@ class LLMStream extends llm.LLMStream {
223
223
  options: { retryable }
224
224
  });
225
225
  }
226
- } finally {
227
- this.queue.close();
228
226
  }
229
227
  }
230
228
  parseChoice(id, choice) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inference/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport OpenAI from 'openai';\nimport {\n APIConnectionError,\n APIStatusError,\n APITimeoutError,\n DEFAULT_API_CONNECT_OPTIONS,\n type Expand,\n toError,\n} from '../index.js';\nimport * as llm from '../llm/index.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { type AnyString, createAccessToken } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://agent-gateway.livekit.cloud/v1';\n\nexport type OpenAIModels =\n | 'openai/gpt-5'\n | 'openai/gpt-5-mini'\n | 'openai/gpt-5-nano'\n | 'openai/gpt-4.1'\n | 'openai/gpt-4.1-mini'\n | 'openai/gpt-4.1-nano'\n | 'openai/gpt-4o'\n | 'openai/gpt-4o-mini'\n | 'openai/gpt-oss-120b';\n\nexport type GoogleModels = 'google/gemini-2.0-flash-lite';\n\nexport type QwenModels = 'qwen/qwen3-235b-a22b-instruct';\n\nexport type KimiModels = 'moonshotai/kimi-k2-instruct';\n\nexport type DeepSeekModels = 'deepseek-ai/deepseek-v3';\n\ntype ChatCompletionPredictionContentParam =\n Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;\ntype WebSearchOptions = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions>;\ntype ToolChoice = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice']>;\ntype Verbosity = 'low' | 'medium' | 'high';\n\nexport interface ChatCompletionOptions extends Record<string, unknown> {\n frequency_penalty?: number;\n logit_bias?: Record<string, number>;\n logprobs?: boolean;\n max_completion_tokens?: number;\n max_tokens?: number;\n metadata?: Record<string, string>;\n modalities?: Array<'text' | 'audio'>;\n n?: number;\n parallel_tool_calls?: boolean;\n prediction?: ChatCompletionPredictionContentParam | null;\n presence_penalty?: number;\n prompt_cache_key?: string;\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n safety_identifier?: string;\n seed?: number;\n service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority';\n stop?: string | string[];\n store?: boolean;\n temperature?: number;\n top_logprobs?: number;\n top_p?: number;\n user?: string;\n verbosity?: Verbosity;\n web_search_options?: WebSearchOptions;\n\n // livekit-typed arguments\n tool_choice?: ToolChoice;\n // TODO(brian): support response format\n // response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams['response_format']\n}\n\nexport type LLMModels =\n | OpenAIModels\n | GoogleModels\n | QwenModels\n | KimiModels\n | DeepSeekModels\n | AnyString;\n\nexport interface InferenceLLMOptions {\n model: LLMModels;\n provider?: string;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: ChatCompletionOptions;\n strictToolSchema?: boolean;\n}\n\nexport interface GatewayOptions {\n apiKey: string;\n apiSecret: string;\n}\n\n/**\n * Livekit Cloud Inference LLM\n */\nexport class LLM extends llm.LLM {\n private client: OpenAI;\n private opts: InferenceLLMOptions;\n\n constructor(opts: {\n model: LLMModels;\n provider?: string;\n baseURL?: string;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: InferenceLLMOptions['modelOptions'];\n strictToolSchema?: boolean;\n }) {\n super();\n\n const {\n model,\n provider,\n baseURL,\n apiKey,\n apiSecret,\n modelOptions,\n strictToolSchema = false,\n } = opts;\n\n const lkBaseURL = baseURL || process.env.LIVEKIT_INFERENCE_URL || DEFAULT_BASE_URL;\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n this.opts = {\n model,\n provider,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions: modelOptions || {},\n strictToolSchema,\n };\n\n this.client = new OpenAI({\n baseURL: this.opts.baseURL,\n apiKey: '', // leave a temporary empty string to avoid OpenAI complain about missing key\n });\n }\n\n label(): string {\n return 'inference.LLM';\n }\n\n get model(): string {\n return this.opts.model;\n }\n\n static fromModelString(modelString: string): LLM {\n return new LLM({ model: modelString });\n }\n\n chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n parallelToolCalls,\n toolChoice,\n // TODO(AJS-270): Add response_format parameter support\n extraKwargs,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n // TODO(AJS-270): Add responseFormat parameter\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n let modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };\n\n parallelToolCalls =\n parallelToolCalls !== undefined\n ? parallelToolCalls\n : this.opts.modelOptions.parallel_tool_calls;\n\n if (toolCtx && Object.keys(toolCtx).length > 0 && parallelToolCalls !== undefined) {\n modelOptions.parallel_tool_calls = parallelToolCalls;\n }\n\n toolChoice =\n toolChoice !== undefined\n ? toolChoice\n : (this.opts.modelOptions.tool_choice as llm.ToolChoice | undefined);\n\n if (toolChoice) {\n modelOptions.tool_choice = toolChoice as ToolChoice;\n }\n\n // TODO(AJS-270): Add response_format support here\n\n modelOptions = { ...modelOptions, ...this.opts.modelOptions };\n\n return new LLMStream(this, {\n model: this.opts.model,\n provider: this.opts.provider,\n client: this.client,\n chatCtx,\n toolCtx,\n connOptions,\n modelOptions,\n strictToolSchema: this.opts.strictToolSchema ?? false, // default to false if not set\n gatewayOptions: {\n apiKey: this.opts.apiKey,\n apiSecret: this.opts.apiSecret,\n },\n });\n }\n}\n\nexport class LLMStream extends llm.LLMStream {\n private model: LLMModels;\n private provider?: string;\n private providerFmt: llm.ProviderFormat;\n private client: OpenAI;\n private modelOptions: Record<string, unknown>;\n private strictToolSchema: boolean;\n\n private gatewayOptions?: GatewayOptions;\n private toolCallId?: string;\n private toolIndex?: number;\n private fncName?: string;\n private fncRawArguments?: string;\n\n constructor(\n llm: LLM,\n {\n model,\n provider,\n client,\n chatCtx,\n toolCtx,\n gatewayOptions,\n connOptions,\n modelOptions,\n providerFmt,\n strictToolSchema,\n }: {\n model: LLMModels;\n provider?: string;\n client: OpenAI;\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n gatewayOptions?: GatewayOptions;\n connOptions: APIConnectOptions;\n modelOptions: Record<string, unknown>;\n providerFmt?: llm.ProviderFormat;\n strictToolSchema: boolean;\n },\n ) {\n super(llm, { chatCtx, toolCtx, connOptions });\n this.client = client;\n this.gatewayOptions = gatewayOptions;\n this.provider = provider;\n this.providerFmt = providerFmt || 'openai';\n this.modelOptions = modelOptions;\n this.model = model;\n this.strictToolSchema = strictToolSchema;\n }\n\n protected async run(): Promise<void> {\n // current function call that we're waiting for full completion (args are streamed)\n // (defined inside the run method to make sure the state is reset for each run/attempt)\n let retryable = true;\n this.toolCallId = this.fncName = this.fncRawArguments = this.toolIndex = undefined;\n\n try {\n const messages = (await this.chatCtx.toProviderFormat(\n this.providerFmt,\n )) as OpenAI.ChatCompletionMessageParam[];\n\n const tools = this.toolCtx\n ? Object.entries(this.toolCtx).map(([name, func]) => {\n const oaiParams = {\n type: 'function' as const,\n function: {\n name,\n description: func.description,\n parameters: llm.toJsonSchema(\n func.parameters,\n true,\n this.strictToolSchema,\n ) as unknown as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function']['parameters'],\n } as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function'],\n };\n\n if (this.strictToolSchema) {\n oaiParams.function.strict = true;\n }\n\n return oaiParams;\n })\n : undefined;\n\n const requestOptions: Record<string, unknown> = { ...this.modelOptions };\n if (!tools) {\n delete requestOptions.tool_choice;\n }\n\n // Dynamically set the access token for the LiveKit Agent Gateway API\n if (this.gatewayOptions) {\n this.client.apiKey = await createAccessToken(\n this.gatewayOptions.apiKey,\n this.gatewayOptions.apiSecret,\n );\n }\n\n if (this.provider) {\n const extraHeaders = requestOptions.extra_headers\n ? (requestOptions.extra_headers as Record<string, string>)\n : {};\n extraHeaders['X-LiveKit-Inference-Provider'] = this.provider;\n requestOptions.extra_headers = extraHeaders;\n }\n\n const stream = await this.client.chat.completions.create(\n {\n model: this.model,\n messages,\n tools,\n stream: true,\n stream_options: { include_usage: true },\n ...requestOptions,\n },\n {\n timeout: this.connOptions.timeoutMs,\n },\n );\n\n for await (const chunk of stream) {\n for (const choice of chunk.choices) {\n if (this.abortController.signal.aborted) {\n break;\n }\n const chatChunk = this.parseChoice(chunk.id, choice);\n if (chatChunk) {\n retryable = false;\n this.queue.put(chatChunk);\n }\n }\n\n if (chunk.usage) {\n const usage = chunk.usage;\n retryable = false;\n this.queue.put({\n id: chunk.id,\n usage: {\n completionTokens: usage.completion_tokens,\n promptTokens: usage.prompt_tokens,\n promptCachedTokens: usage.prompt_tokens_details?.cached_tokens || 0,\n totalTokens: usage.total_tokens,\n },\n });\n }\n }\n } catch (error) {\n if (error instanceof OpenAI.APIConnectionTimeoutError) {\n throw new APITimeoutError({ options: { retryable } });\n } else if (error instanceof OpenAI.APIError) {\n throw new APIStatusError({\n message: error.message,\n options: {\n statusCode: error.status,\n body: error.error,\n requestId: error.requestID,\n retryable,\n },\n });\n } else {\n throw new APIConnectionError({\n message: toError(error).message,\n options: { retryable },\n });\n }\n } finally {\n this.queue.close();\n }\n }\n\n private parseChoice(\n id: string,\n choice: OpenAI.ChatCompletionChunk.Choice,\n ): llm.ChatChunk | undefined {\n const delta = choice.delta;\n\n // https://github.com/livekit/agents/issues/688\n // the delta can be None when using Azure OpenAI (content filtering)\n if (delta === undefined) return undefined;\n\n if (delta.tool_calls) {\n // check if we have functions to calls\n for (const tool of delta.tool_calls) {\n if (!tool.function) {\n continue; // oai may add other tools in the future\n }\n\n /**\n * The way OpenAI streams tool calls is a bit tricky.\n *\n * For any new tool call, it first emits a delta tool call with id, and function name,\n * the rest of the delta chunks will only stream the remaining arguments string,\n * until a new tool call is started or the tool call is finished.\n * See below for an example.\n *\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)\n * [ChoiceDeltaToolCall(index=0, id='call_LaVeHWUHpef9K1sd5UO8TtLg', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"P', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris\\}', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id='call_ThU4OmMdQXnnVmpXGOCknXIB', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"T', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='okyo', name=None), type=None)]\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)\n */\n let callChunk: llm.ChatChunk | undefined;\n // If we have a previous tool call and this is a new one, emit the previous\n if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {\n callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n }\n\n // Start or continue building the current tool call\n if (tool.function.name) {\n this.toolIndex = tool.index;\n this.toolCallId = tool.id;\n this.fncName = tool.function.name;\n this.fncRawArguments = tool.function.arguments || '';\n } else if (tool.function.arguments) {\n this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;\n }\n\n if (callChunk) {\n return callChunk;\n }\n }\n }\n\n // If we're done with tool calls, emit the final one\n if (\n choice.finish_reason &&\n ['tool_calls', 'stop'].includes(choice.finish_reason) &&\n this.toolCallId !== undefined\n ) {\n const callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n return callChunk;\n }\n\n // Regular content message\n if (!delta.content) {\n return undefined;\n }\n\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content,\n },\n };\n }\n\n private createRunningToolCallChunk(\n id: string,\n delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta,\n ): llm.ChatChunk {\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content || undefined,\n toolCalls: [\n llm.FunctionCall.create({\n callId: this.toolCallId || '',\n name: this.fncName || '',\n args: this.fncRawArguments || '',\n }),\n ],\n },\n };\n }\n}\n"],"mappings":"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","names":["llm"]}
1
+ {"version":3,"sources":["../../src/inference/llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport OpenAI from 'openai';\nimport {\n APIConnectionError,\n APIStatusError,\n APITimeoutError,\n DEFAULT_API_CONNECT_OPTIONS,\n type Expand,\n toError,\n} from '../index.js';\nimport * as llm from '../llm/index.js';\nimport type { APIConnectOptions } from '../types.js';\nimport { type AnyString, createAccessToken } from './utils.js';\n\nconst DEFAULT_BASE_URL = 'https://agent-gateway.livekit.cloud/v1';\n\nexport type OpenAIModels =\n | 'openai/gpt-5'\n | 'openai/gpt-5-mini'\n | 'openai/gpt-5-nano'\n | 'openai/gpt-4.1'\n | 'openai/gpt-4.1-mini'\n | 'openai/gpt-4.1-nano'\n | 'openai/gpt-4o'\n | 'openai/gpt-4o-mini'\n | 'openai/gpt-oss-120b';\n\nexport type GoogleModels = 'google/gemini-2.0-flash-lite';\n\nexport type QwenModels = 'qwen/qwen3-235b-a22b-instruct';\n\nexport type KimiModels = 'moonshotai/kimi-k2-instruct';\n\nexport type DeepSeekModels = 'deepseek-ai/deepseek-v3';\n\ntype ChatCompletionPredictionContentParam =\n Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;\ntype WebSearchOptions = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions>;\ntype ToolChoice = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice']>;\ntype Verbosity = 'low' | 'medium' | 'high';\n\nexport interface ChatCompletionOptions extends Record<string, unknown> {\n frequency_penalty?: number;\n logit_bias?: Record<string, number>;\n logprobs?: boolean;\n max_completion_tokens?: number;\n max_tokens?: number;\n metadata?: Record<string, string>;\n modalities?: Array<'text' | 'audio'>;\n n?: number;\n parallel_tool_calls?: boolean;\n prediction?: ChatCompletionPredictionContentParam | null;\n presence_penalty?: number;\n prompt_cache_key?: string;\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n safety_identifier?: string;\n seed?: number;\n service_tier?: 'auto' | 'default' | 'flex' | 'scale' | 'priority';\n stop?: string | string[];\n store?: boolean;\n temperature?: number;\n top_logprobs?: number;\n top_p?: number;\n user?: string;\n verbosity?: Verbosity;\n web_search_options?: WebSearchOptions;\n\n // livekit-typed arguments\n tool_choice?: ToolChoice;\n // TODO(brian): support response format\n // response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams['response_format']\n}\n\nexport type LLMModels =\n | OpenAIModels\n | GoogleModels\n | QwenModels\n | KimiModels\n | DeepSeekModels\n | AnyString;\n\nexport interface InferenceLLMOptions {\n model: LLMModels;\n provider?: string;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: ChatCompletionOptions;\n strictToolSchema?: boolean;\n}\n\nexport interface GatewayOptions {\n apiKey: string;\n apiSecret: string;\n}\n\n/**\n * Livekit Cloud Inference LLM\n */\nexport class LLM extends llm.LLM {\n private client: OpenAI;\n private opts: InferenceLLMOptions;\n\n constructor(opts: {\n model: LLMModels;\n provider?: string;\n baseURL?: string;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: InferenceLLMOptions['modelOptions'];\n strictToolSchema?: boolean;\n }) {\n super();\n\n const {\n model,\n provider,\n baseURL,\n apiKey,\n apiSecret,\n modelOptions,\n strictToolSchema = false,\n } = opts;\n\n const lkBaseURL = baseURL || process.env.LIVEKIT_INFERENCE_URL || DEFAULT_BASE_URL;\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n this.opts = {\n model,\n provider,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions: modelOptions || {},\n strictToolSchema,\n };\n\n this.client = new OpenAI({\n baseURL: this.opts.baseURL,\n apiKey: '', // leave a temporary empty string to avoid OpenAI complain about missing key\n });\n }\n\n label(): string {\n return 'inference.LLM';\n }\n\n get model(): string {\n return this.opts.model;\n }\n\n static fromModelString(modelString: string): LLM {\n return new LLM({ model: modelString });\n }\n\n chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n parallelToolCalls,\n toolChoice,\n // TODO(AJS-270): Add response_format parameter support\n extraKwargs,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n // TODO(AJS-270): Add responseFormat parameter\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n let modelOptions: Record<string, unknown> = { ...(extraKwargs || {}) };\n\n parallelToolCalls =\n parallelToolCalls !== undefined\n ? parallelToolCalls\n : this.opts.modelOptions.parallel_tool_calls;\n\n if (toolCtx && Object.keys(toolCtx).length > 0 && parallelToolCalls !== undefined) {\n modelOptions.parallel_tool_calls = parallelToolCalls;\n }\n\n toolChoice =\n toolChoice !== undefined\n ? toolChoice\n : (this.opts.modelOptions.tool_choice as llm.ToolChoice | undefined);\n\n if (toolChoice) {\n modelOptions.tool_choice = toolChoice as ToolChoice;\n }\n\n // TODO(AJS-270): Add response_format support here\n\n modelOptions = { ...modelOptions, ...this.opts.modelOptions };\n\n return new LLMStream(this, {\n model: this.opts.model,\n provider: this.opts.provider,\n client: this.client,\n chatCtx,\n toolCtx,\n connOptions,\n modelOptions,\n strictToolSchema: this.opts.strictToolSchema ?? false, // default to false if not set\n gatewayOptions: {\n apiKey: this.opts.apiKey,\n apiSecret: this.opts.apiSecret,\n },\n });\n }\n}\n\nexport class LLMStream extends llm.LLMStream {\n private model: LLMModels;\n private provider?: string;\n private providerFmt: llm.ProviderFormat;\n private client: OpenAI;\n private modelOptions: Record<string, unknown>;\n private strictToolSchema: boolean;\n\n private gatewayOptions?: GatewayOptions;\n private toolCallId?: string;\n private toolIndex?: number;\n private fncName?: string;\n private fncRawArguments?: string;\n\n constructor(\n llm: LLM,\n {\n model,\n provider,\n client,\n chatCtx,\n toolCtx,\n gatewayOptions,\n connOptions,\n modelOptions,\n providerFmt,\n strictToolSchema,\n }: {\n model: LLMModels;\n provider?: string;\n client: OpenAI;\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n gatewayOptions?: GatewayOptions;\n connOptions: APIConnectOptions;\n modelOptions: Record<string, unknown>;\n providerFmt?: llm.ProviderFormat;\n strictToolSchema: boolean;\n },\n ) {\n super(llm, { chatCtx, toolCtx, connOptions });\n this.client = client;\n this.gatewayOptions = gatewayOptions;\n this.provider = provider;\n this.providerFmt = providerFmt || 'openai';\n this.modelOptions = modelOptions;\n this.model = model;\n this.strictToolSchema = strictToolSchema;\n }\n\n protected async run(): Promise<void> {\n // current function call that we're waiting for full completion (args are streamed)\n // (defined inside the run method to make sure the state is reset for each run/attempt)\n let retryable = true;\n this.toolCallId = this.fncName = this.fncRawArguments = this.toolIndex = undefined;\n\n try {\n const messages = (await this.chatCtx.toProviderFormat(\n this.providerFmt,\n )) as OpenAI.ChatCompletionMessageParam[];\n\n const tools = this.toolCtx\n ? Object.entries(this.toolCtx).map(([name, func]) => {\n const oaiParams = {\n type: 'function' as const,\n function: {\n name,\n description: func.description,\n parameters: llm.toJsonSchema(\n func.parameters,\n true,\n this.strictToolSchema,\n ) as unknown as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function']['parameters'],\n } as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function'],\n };\n\n if (this.strictToolSchema) {\n oaiParams.function.strict = true;\n }\n\n return oaiParams;\n })\n : undefined;\n\n const requestOptions: Record<string, unknown> = { ...this.modelOptions };\n if (!tools) {\n delete requestOptions.tool_choice;\n }\n\n // Dynamically set the access token for the LiveKit Agent Gateway API\n if (this.gatewayOptions) {\n this.client.apiKey = await createAccessToken(\n this.gatewayOptions.apiKey,\n this.gatewayOptions.apiSecret,\n );\n }\n\n if (this.provider) {\n const extraHeaders = requestOptions.extra_headers\n ? (requestOptions.extra_headers as Record<string, string>)\n : {};\n extraHeaders['X-LiveKit-Inference-Provider'] = this.provider;\n requestOptions.extra_headers = extraHeaders;\n }\n\n const stream = await this.client.chat.completions.create(\n {\n model: this.model,\n messages,\n tools,\n stream: true,\n stream_options: { include_usage: true },\n ...requestOptions,\n },\n {\n timeout: this.connOptions.timeoutMs,\n },\n );\n\n for await (const chunk of stream) {\n for (const choice of chunk.choices) {\n if (this.abortController.signal.aborted) {\n break;\n }\n const chatChunk = this.parseChoice(chunk.id, choice);\n if (chatChunk) {\n retryable = false;\n this.queue.put(chatChunk);\n }\n }\n\n if (chunk.usage) {\n const usage = chunk.usage;\n retryable = false;\n this.queue.put({\n id: chunk.id,\n usage: {\n completionTokens: usage.completion_tokens,\n promptTokens: usage.prompt_tokens,\n promptCachedTokens: usage.prompt_tokens_details?.cached_tokens || 0,\n totalTokens: usage.total_tokens,\n },\n });\n }\n }\n } catch (error) {\n if (error instanceof OpenAI.APIConnectionTimeoutError) {\n throw new APITimeoutError({ options: { retryable } });\n } else if (error instanceof OpenAI.APIError) {\n throw new APIStatusError({\n message: error.message,\n options: {\n statusCode: error.status,\n body: error.error,\n requestId: error.requestID,\n retryable,\n },\n });\n } else {\n throw new APIConnectionError({\n message: toError(error).message,\n options: { retryable },\n });\n }\n }\n }\n\n private parseChoice(\n id: string,\n choice: OpenAI.ChatCompletionChunk.Choice,\n ): llm.ChatChunk | undefined {\n const delta = choice.delta;\n\n // https://github.com/livekit/agents/issues/688\n // the delta can be None when using Azure OpenAI (content filtering)\n if (delta === undefined) return undefined;\n\n if (delta.tool_calls) {\n // check if we have functions to calls\n for (const tool of delta.tool_calls) {\n if (!tool.function) {\n continue; // oai may add other tools in the future\n }\n\n /**\n * The way OpenAI streams tool calls is a bit tricky.\n *\n * For any new tool call, it first emits a delta tool call with id, and function name,\n * the rest of the delta chunks will only stream the remaining arguments string,\n * until a new tool call is started or the tool call is finished.\n * See below for an example.\n *\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)\n * [ChoiceDeltaToolCall(index=0, id='call_LaVeHWUHpef9K1sd5UO8TtLg', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"P', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris\\}', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id='call_ThU4OmMdQXnnVmpXGOCknXIB', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='\\{\"location\": \"T', name=None), type=None)]\n * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='okyo', name=None), type=None)]\n * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)\n */\n let callChunk: llm.ChatChunk | undefined;\n // If we have a previous tool call and this is a new one, emit the previous\n if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {\n callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n }\n\n // Start or continue building the current tool call\n if (tool.function.name) {\n this.toolIndex = tool.index;\n this.toolCallId = tool.id;\n this.fncName = tool.function.name;\n this.fncRawArguments = tool.function.arguments || '';\n } else if (tool.function.arguments) {\n this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;\n }\n\n if (callChunk) {\n return callChunk;\n }\n }\n }\n\n // If we're done with tool calls, emit the final one\n if (\n choice.finish_reason &&\n ['tool_calls', 'stop'].includes(choice.finish_reason) &&\n this.toolCallId !== undefined\n ) {\n const callChunk = this.createRunningToolCallChunk(id, delta);\n this.toolCallId = this.fncName = this.fncRawArguments = undefined;\n return callChunk;\n }\n\n // Regular content message\n if (!delta.content) {\n return undefined;\n }\n\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content,\n },\n };\n }\n\n private createRunningToolCallChunk(\n id: string,\n delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta,\n ): llm.ChatChunk {\n return {\n id,\n delta: {\n role: 'assistant',\n content: delta.content || undefined,\n toolCalls: [\n llm.FunctionCall.create({\n callId: this.toolCallId || '',\n name: this.fncName || '',\n args: this.fncRawArguments || '',\n }),\n ],\n },\n };\n }\n}\n"],"mappings":"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","names":["llm"]}
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var fallback_adapter_exports = {};
20
+ __export(fallback_adapter_exports, {
21
+ FallbackAdapter: () => FallbackAdapter
22
+ });
23
+ module.exports = __toCommonJS(fallback_adapter_exports);
24
+ var import_exceptions = require("../_exceptions.cjs");
25
+ var import_log = require("../log.cjs");
26
+ var import_types = require("../types.cjs");
27
+ var import_llm = require("./llm.cjs");
28
+ const DEFAULT_FALLBACK_API_CONNECT_OPTIONS = {
29
+ maxRetry: 0,
30
+ timeoutMs: import_types.DEFAULT_API_CONNECT_OPTIONS.timeoutMs,
31
+ retryIntervalMs: import_types.DEFAULT_API_CONNECT_OPTIONS.retryIntervalMs
32
+ };
33
+ class FallbackAdapter extends import_llm.LLM {
34
+ llms;
35
+ attemptTimeout;
36
+ maxRetryPerLLM;
37
+ retryInterval;
38
+ retryOnChunkSent;
39
+ /** @internal */
40
+ _status;
41
+ logger = (0, import_log.log)();
42
+ constructor(options) {
43
+ super();
44
+ if (!options.llms || options.llms.length < 1) {
45
+ throw new Error("at least one LLM instance must be provided.");
46
+ }
47
+ this.llms = options.llms;
48
+ this.attemptTimeout = options.attemptTimeout ?? 5;
49
+ this.maxRetryPerLLM = options.maxRetryPerLLM ?? 0;
50
+ this.retryInterval = options.retryInterval ?? 0.5;
51
+ this.retryOnChunkSent = options.retryOnChunkSent ?? false;
52
+ this._status = this.llms.map(() => ({
53
+ available: true,
54
+ recoveringTask: null
55
+ }));
56
+ for (const llm of this.llms) {
57
+ llm.on("metrics_collected", (metrics) => {
58
+ this.emit("metrics_collected", metrics);
59
+ });
60
+ }
61
+ }
62
+ get model() {
63
+ return "FallbackAdapter";
64
+ }
65
+ label() {
66
+ return "FallbackAdapter";
67
+ }
68
+ chat(opts) {
69
+ return new FallbackLLMStream(this, {
70
+ chatCtx: opts.chatCtx,
71
+ toolCtx: opts.toolCtx,
72
+ connOptions: opts.connOptions || DEFAULT_FALLBACK_API_CONNECT_OPTIONS,
73
+ parallelToolCalls: opts.parallelToolCalls,
74
+ toolChoice: opts.toolChoice,
75
+ extraKwargs: opts.extraKwargs
76
+ });
77
+ }
78
+ /**
79
+ * Emit availability changed event.
80
+ * @internal
81
+ */
82
+ _emitAvailabilityChanged(llm, available) {
83
+ const event = { llm, available };
84
+ this.emit(
85
+ "llm_availability_changed",
86
+ event
87
+ );
88
+ }
89
+ }
90
+ class FallbackLLMStream extends import_llm.LLMStream {
91
+ adapter;
92
+ parallelToolCalls;
93
+ toolChoice;
94
+ extraKwargs;
95
+ _currentStream;
96
+ _log = (0, import_log.log)();
97
+ constructor(adapter, opts) {
98
+ super(adapter, {
99
+ chatCtx: opts.chatCtx,
100
+ toolCtx: opts.toolCtx,
101
+ connOptions: opts.connOptions
102
+ });
103
+ this.adapter = adapter;
104
+ this.parallelToolCalls = opts.parallelToolCalls;
105
+ this.toolChoice = opts.toolChoice;
106
+ this.extraKwargs = opts.extraKwargs;
107
+ }
108
+ /**
109
+ * Override chatCtx to return current stream's context if available.
110
+ */
111
+ get chatCtx() {
112
+ var _a;
113
+ return ((_a = this._currentStream) == null ? void 0 : _a.chatCtx) ?? super.chatCtx;
114
+ }
115
+ /**
116
+ * Try to generate with a single LLM.
117
+ * Returns an async generator that yields chunks.
118
+ */
119
+ async *tryGenerate(llm, checkRecovery = false) {
120
+ const connOptions = {
121
+ ...this.connOptions,
122
+ maxRetry: this.adapter.maxRetryPerLLM,
123
+ timeoutMs: this.adapter.attemptTimeout * 1e3,
124
+ retryIntervalMs: this.adapter.retryInterval * 1e3
125
+ };
126
+ const stream = llm.chat({
127
+ chatCtx: super.chatCtx,
128
+ toolCtx: this.toolCtx,
129
+ connOptions,
130
+ parallelToolCalls: this.parallelToolCalls,
131
+ toolChoice: this.toolChoice,
132
+ extraKwargs: this.extraKwargs
133
+ });
134
+ let streamError;
135
+ const errorHandler = (ev) => {
136
+ streamError = ev.error;
137
+ };
138
+ llm.on("error", errorHandler);
139
+ try {
140
+ let shouldSetCurrent = !checkRecovery;
141
+ for await (const chunk of stream) {
142
+ if (shouldSetCurrent) {
143
+ shouldSetCurrent = false;
144
+ this._currentStream = stream;
145
+ }
146
+ yield chunk;
147
+ }
148
+ if (streamError) {
149
+ throw streamError;
150
+ }
151
+ } catch (error) {
152
+ if (error instanceof import_exceptions.APIError) {
153
+ if (checkRecovery) {
154
+ this._log.warn({ llm: llm.label(), error }, "recovery failed");
155
+ } else {
156
+ this._log.warn({ llm: llm.label(), error }, "failed, switching to next LLM");
157
+ }
158
+ throw error;
159
+ }
160
+ if (error instanceof Error && error.name === "AbortError") {
161
+ if (checkRecovery) {
162
+ this._log.warn({ llm: llm.label() }, "recovery timed out");
163
+ } else {
164
+ this._log.warn({ llm: llm.label() }, "timed out, switching to next LLM");
165
+ }
166
+ throw error;
167
+ }
168
+ if (checkRecovery) {
169
+ this._log.error({ llm: llm.label(), error }, "recovery unexpected error");
170
+ } else {
171
+ this._log.error({ llm: llm.label(), error }, "unexpected error, switching to next LLM");
172
+ }
173
+ throw error;
174
+ } finally {
175
+ llm.off("error", errorHandler);
176
+ }
177
+ }
178
+ /**
179
+ * Start background recovery task for an LLM.
180
+ */
181
+ tryRecovery(llm, index) {
182
+ const status = this.adapter._status[index];
183
+ if (status.recoveringTask !== null) {
184
+ return;
185
+ }
186
+ const recoverTask = async () => {
187
+ try {
188
+ for await (const _chunk of this.tryGenerate(llm, true)) {
189
+ }
190
+ status.available = true;
191
+ this._log.info({ llm: llm.label() }, "LLM recovered");
192
+ this.adapter._emitAvailabilityChanged(llm, true);
193
+ } catch {
194
+ } finally {
195
+ status.recoveringTask = null;
196
+ }
197
+ };
198
+ status.recoveringTask = recoverTask();
199
+ }
200
+ /**
201
+ * Main run method - iterates through LLMs with fallback logic.
202
+ */
203
+ async run() {
204
+ const startTime = Date.now();
205
+ const allFailed = this.adapter._status.every((s) => !s.available);
206
+ if (allFailed) {
207
+ this._log.error("all LLMs are unavailable, retrying...");
208
+ }
209
+ for (let i = 0; i < this.adapter.llms.length; i++) {
210
+ const llm = this.adapter.llms[i];
211
+ const status = this.adapter._status[i];
212
+ this._log.debug(
213
+ { llm: llm.label(), index: i, available: status.available, allFailed },
214
+ "checking LLM"
215
+ );
216
+ if (status.available || allFailed) {
217
+ let textSent = "";
218
+ const toolCallsSent = [];
219
+ try {
220
+ this._log.info({ llm: llm.label() }, "FallbackAdapter: Attempting provider");
221
+ let chunkCount = 0;
222
+ for await (const chunk of this.tryGenerate(llm, false)) {
223
+ chunkCount++;
224
+ if (chunk.delta) {
225
+ if (chunk.delta.content) {
226
+ textSent += chunk.delta.content;
227
+ }
228
+ if (chunk.delta.toolCalls) {
229
+ for (const tc of chunk.delta.toolCalls) {
230
+ if (tc.name) {
231
+ toolCallsSent.push(tc.name);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ this._log.debug({ llm: llm.label(), chunkCount }, "run: forwarding chunk to queue");
237
+ this.queue.put(chunk);
238
+ }
239
+ this._log.info(
240
+ { llm: llm.label(), totalChunks: chunkCount, textLength: textSent.length },
241
+ "FallbackAdapter: Provider succeeded"
242
+ );
243
+ return;
244
+ } catch (error) {
245
+ if (status.available) {
246
+ status.available = false;
247
+ this.adapter._emitAvailabilityChanged(llm, false);
248
+ }
249
+ if (textSent || toolCallsSent.length > 0) {
250
+ const extra = { textSent, toolCallsSent };
251
+ if (!this.adapter.retryOnChunkSent) {
252
+ this._log.error(
253
+ { llm: llm.label(), ...extra },
254
+ "failed after sending chunk, skip retrying. Set `retryOnChunkSent` to `true` to enable."
255
+ );
256
+ throw error;
257
+ }
258
+ this._log.warn(
259
+ { llm: llm.label(), ...extra },
260
+ "failed after sending chunk, retrying..."
261
+ );
262
+ }
263
+ }
264
+ }
265
+ this.tryRecovery(llm, i);
266
+ }
267
+ const duration = (Date.now() - startTime) / 1e3;
268
+ const labels = this.adapter.llms.map((l) => l.label()).join(", ");
269
+ throw new import_exceptions.APIConnectionError({
270
+ message: `all LLMs failed (${labels}) after ${duration.toFixed(2)}s`
271
+ });
272
+ }
273
+ }
274
+ // Annotate the CommonJS export names for ESM import in node:
275
+ 0 && (module.exports = {
276
+ FallbackAdapter
277
+ });
278
+ //# sourceMappingURL=fallback_adapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/llm/fallback_adapter.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { APIConnectionError, APIError } from '../_exceptions.js';\nimport { log } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport type { ChatContext } from './chat_context.js';\nimport type { ChatChunk } from './llm.js';\nimport { LLM, LLMStream } from './llm.js';\nimport type { ToolChoice, ToolContext } from './tool_context.js';\n\n/**\n * Default connection options for FallbackAdapter.\n * Uses max_retry=0 since fallback handles retries at a higher level.\n */\nconst DEFAULT_FALLBACK_API_CONNECT_OPTIONS: APIConnectOptions = {\n maxRetry: 0,\n timeoutMs: DEFAULT_API_CONNECT_OPTIONS.timeoutMs,\n retryIntervalMs: DEFAULT_API_CONNECT_OPTIONS.retryIntervalMs,\n};\n\n/**\n * Internal status tracking for each LLM instance.\n */\ninterface LLMStatus {\n available: boolean;\n recoveringTask: Promise<void> | null;\n}\n\n/**\n * Event emitted when an LLM's availability changes.\n */\nexport interface AvailabilityChangedEvent {\n llm: LLM;\n available: boolean;\n}\n\n/**\n * Options for creating a FallbackAdapter.\n */\nexport interface FallbackAdapterOptions {\n /** List of LLM instances to fallback to (in order). */\n llms: LLM[];\n /** Timeout for each LLM attempt in seconds. Defaults to 5.0. */\n attemptTimeout?: number;\n /** Internal retries per LLM before moving to next. Defaults to 0. */\n maxRetryPerLLM?: number;\n /** Interval between retries in seconds. Defaults to 0.5. */\n retryInterval?: number;\n /** Whether to retry when LLM fails after chunks are sent. Defaults to false. */\n retryOnChunkSent?: boolean;\n}\n\n/**\n * FallbackAdapter is an LLM that can fallback to a different LLM if the current LLM fails.\n *\n * @example\n * ```typescript\n * const fallbackLLM = new FallbackAdapter({\n * llms: [primaryLLM, secondaryLLM, tertiaryLLM],\n * attemptTimeout: 5.0,\n * maxRetryPerLLM: 1,\n * });\n * ```\n */\nexport class FallbackAdapter extends LLM {\n readonly llms: LLM[];\n readonly attemptTimeout: number;\n readonly maxRetryPerLLM: number;\n readonly retryInterval: number;\n readonly retryOnChunkSent: boolean;\n\n /** @internal */\n _status: LLMStatus[];\n\n private logger = log();\n\n constructor(options: FallbackAdapterOptions) {\n super();\n\n if (!options.llms || options.llms.length < 1) {\n throw new Error('at least one LLM instance must be provided.');\n }\n\n this.llms = options.llms;\n this.attemptTimeout = options.attemptTimeout ?? 5.0;\n this.maxRetryPerLLM = options.maxRetryPerLLM ?? 0;\n this.retryInterval = options.retryInterval ?? 0.5;\n this.retryOnChunkSent = options.retryOnChunkSent ?? false;\n\n // Initialize status for each LLM\n this._status = this.llms.map(() => ({\n available: true,\n recoveringTask: null,\n }));\n\n // Forward metrics_collected events from child LLMs\n for (const llm of this.llms) {\n llm.on('metrics_collected', (metrics) => {\n this.emit('metrics_collected', metrics);\n });\n }\n }\n\n get model(): string {\n return 'FallbackAdapter';\n }\n\n label(): string {\n return 'FallbackAdapter';\n }\n\n chat(opts: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n return new FallbackLLMStream(this, {\n chatCtx: opts.chatCtx,\n toolCtx: opts.toolCtx,\n connOptions: opts.connOptions || DEFAULT_FALLBACK_API_CONNECT_OPTIONS,\n parallelToolCalls: opts.parallelToolCalls,\n toolChoice: opts.toolChoice,\n extraKwargs: opts.extraKwargs,\n });\n }\n\n /**\n * Emit availability changed event.\n * @internal\n */\n _emitAvailabilityChanged(llm: LLM, available: boolean): void {\n const event: AvailabilityChangedEvent = { llm, available };\n // Use type assertion for custom event\n (this as unknown as { emit: (event: string, data: AvailabilityChangedEvent) => void }).emit(\n 'llm_availability_changed',\n event,\n );\n }\n}\n\n/**\n * LLMStream implementation for FallbackAdapter.\n * Handles fallback logic between multiple LLM providers.\n */\nclass FallbackLLMStream extends LLMStream {\n private adapter: FallbackAdapter;\n private parallelToolCalls?: boolean;\n private toolChoice?: ToolChoice;\n private extraKwargs?: Record<string, unknown>;\n private _currentStream?: LLMStream;\n private _log = log();\n\n constructor(\n adapter: FallbackAdapter,\n opts: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, unknown>;\n },\n ) {\n super(adapter, {\n chatCtx: opts.chatCtx,\n toolCtx: opts.toolCtx,\n connOptions: opts.connOptions,\n });\n this.adapter = adapter;\n this.parallelToolCalls = opts.parallelToolCalls;\n this.toolChoice = opts.toolChoice;\n this.extraKwargs = opts.extraKwargs;\n }\n\n /**\n * Override chatCtx to return current stream's context if available.\n */\n override get chatCtx(): ChatContext {\n return this._currentStream?.chatCtx ?? super.chatCtx;\n }\n\n /**\n * Try to generate with a single LLM.\n * Returns an async generator that yields chunks.\n */\n private async *tryGenerate(\n llm: LLM,\n checkRecovery: boolean = false,\n ): AsyncGenerator<ChatChunk, void, unknown> {\n const connOptions: APIConnectOptions = {\n ...this.connOptions,\n maxRetry: this.adapter.maxRetryPerLLM,\n timeoutMs: this.adapter.attemptTimeout * 1000,\n retryIntervalMs: this.adapter.retryInterval * 1000,\n };\n\n const stream = llm.chat({\n chatCtx: super.chatCtx,\n toolCtx: this.toolCtx,\n connOptions,\n parallelToolCalls: this.parallelToolCalls,\n toolChoice: this.toolChoice,\n extraKwargs: this.extraKwargs,\n });\n\n // Listen for error events - child LLMs emit errors via their LLM instance, not the stream\n let streamError: Error | undefined;\n const errorHandler = (ev: { error: Error }) => {\n streamError = ev.error;\n };\n llm.on('error', errorHandler);\n\n try {\n let shouldSetCurrent = !checkRecovery;\n for await (const chunk of stream) {\n if (shouldSetCurrent) {\n shouldSetCurrent = false;\n this._currentStream = stream;\n }\n yield chunk;\n }\n\n // If an error was emitted but not thrown through iteration, throw it now\n if (streamError) {\n throw streamError;\n }\n } catch (error) {\n if (error instanceof APIError) {\n if (checkRecovery) {\n this._log.warn({ llm: llm.label(), error }, 'recovery failed');\n } else {\n this._log.warn({ llm: llm.label(), error }, 'failed, switching to next LLM');\n }\n throw error;\n }\n\n // Handle timeout errors\n if (error instanceof Error && error.name === 'AbortError') {\n if (checkRecovery) {\n this._log.warn({ llm: llm.label() }, 'recovery timed out');\n } else {\n this._log.warn({ llm: llm.label() }, 'timed out, switching to next LLM');\n }\n throw error;\n }\n\n // Unexpected error\n if (checkRecovery) {\n this._log.error({ llm: llm.label(), error }, 'recovery unexpected error');\n } else {\n this._log.error({ llm: llm.label(), error }, 'unexpected error, switching to next LLM');\n }\n throw error;\n } finally {\n llm.off('error', errorHandler);\n }\n }\n\n /**\n * Start background recovery task for an LLM.\n */\n private tryRecovery(llm: LLM, index: number): void {\n const status = this.adapter._status[index]!;\n\n // Skip if already recovering\n if (status.recoveringTask !== null) {\n return;\n }\n\n const recoverTask = async (): Promise<void> => {\n try {\n // Try to generate (just iterate to check if it works)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n for await (const _chunk of this.tryGenerate(llm, true)) {\n // Just consume the stream to verify it works\n }\n\n // Recovery successful\n status.available = true;\n this._log.info({ llm: llm.label() }, 'LLM recovered');\n this.adapter._emitAvailabilityChanged(llm, true);\n } catch {\n // Recovery failed, stay unavailable\n } finally {\n status.recoveringTask = null;\n }\n };\n\n // Fire and forget\n status.recoveringTask = recoverTask();\n }\n\n /**\n * Main run method - iterates through LLMs with fallback logic.\n */\n protected async run(): Promise<void> {\n const startTime = Date.now();\n\n // Check if all LLMs are unavailable\n const allFailed = this.adapter._status.every((s) => !s.available);\n if (allFailed) {\n this._log.error('all LLMs are unavailable, retrying...');\n }\n\n for (let i = 0; i < this.adapter.llms.length; i++) {\n const llm = this.adapter.llms[i]!;\n const status = this.adapter._status[i]!;\n\n this._log.debug(\n { llm: llm.label(), index: i, available: status.available, allFailed },\n 'checking LLM',\n );\n\n if (status.available || allFailed) {\n let textSent = '';\n const toolCallsSent: string[] = [];\n\n try {\n this._log.info({ llm: llm.label() }, 'FallbackAdapter: Attempting provider');\n\n let chunkCount = 0;\n for await (const chunk of this.tryGenerate(llm, false)) {\n chunkCount++;\n // Track what's been sent\n if (chunk.delta) {\n if (chunk.delta.content) {\n textSent += chunk.delta.content;\n }\n if (chunk.delta.toolCalls) {\n for (const tc of chunk.delta.toolCalls) {\n if (tc.name) {\n toolCallsSent.push(tc.name);\n }\n }\n }\n }\n\n // Forward chunk to queue\n this._log.debug({ llm: llm.label(), chunkCount }, 'run: forwarding chunk to queue');\n this.queue.put(chunk);\n }\n\n // Success!\n this._log.info(\n { llm: llm.label(), totalChunks: chunkCount, textLength: textSent.length },\n 'FallbackAdapter: Provider succeeded',\n );\n return;\n } catch (error) {\n // Mark as unavailable if it was available before\n if (status.available) {\n status.available = false;\n this.adapter._emitAvailabilityChanged(llm, false);\n }\n\n // Check if we sent data before failing\n if (textSent || toolCallsSent.length > 0) {\n const extra = { textSent, toolCallsSent };\n\n if (!this.adapter.retryOnChunkSent) {\n this._log.error(\n { llm: llm.label(), ...extra },\n 'failed after sending chunk, skip retrying. Set `retryOnChunkSent` to `true` to enable.',\n );\n throw error;\n }\n\n this._log.warn(\n { llm: llm.label(), ...extra },\n 'failed after sending chunk, retrying...',\n );\n }\n }\n }\n\n // Trigger background recovery for this LLM\n this.tryRecovery(llm, i);\n }\n\n // All LLMs failed\n const duration = (Date.now() - startTime) / 1000;\n const labels = this.adapter.llms.map((l) => l.label()).join(', ');\n throw new APIConnectionError({\n message: `all LLMs failed (${labels}) after ${duration.toFixed(2)}s`,\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,73 @@
1
+ import { type APIConnectOptions } from '../types.js';
2
+ import type { ChatContext } from './chat_context.js';
3
+ import { LLM, LLMStream } from './llm.js';
4
+ import type { ToolChoice, ToolContext } from './tool_context.js';
5
+ /**
6
+ * Internal status tracking for each LLM instance.
7
+ */
8
+ interface LLMStatus {
9
+ available: boolean;
10
+ recoveringTask: Promise<void> | null;
11
+ }
12
+ /**
13
+ * Event emitted when an LLM's availability changes.
14
+ */
15
+ export interface AvailabilityChangedEvent {
16
+ llm: LLM;
17
+ available: boolean;
18
+ }
19
+ /**
20
+ * Options for creating a FallbackAdapter.
21
+ */
22
+ export interface FallbackAdapterOptions {
23
+ /** List of LLM instances to fallback to (in order). */
24
+ llms: LLM[];
25
+ /** Timeout for each LLM attempt in seconds. Defaults to 5.0. */
26
+ attemptTimeout?: number;
27
+ /** Internal retries per LLM before moving to next. Defaults to 0. */
28
+ maxRetryPerLLM?: number;
29
+ /** Interval between retries in seconds. Defaults to 0.5. */
30
+ retryInterval?: number;
31
+ /** Whether to retry when LLM fails after chunks are sent. Defaults to false. */
32
+ retryOnChunkSent?: boolean;
33
+ }
34
+ /**
35
+ * FallbackAdapter is an LLM that can fallback to a different LLM if the current LLM fails.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const fallbackLLM = new FallbackAdapter({
40
+ * llms: [primaryLLM, secondaryLLM, tertiaryLLM],
41
+ * attemptTimeout: 5.0,
42
+ * maxRetryPerLLM: 1,
43
+ * });
44
+ * ```
45
+ */
46
+ export declare class FallbackAdapter extends LLM {
47
+ readonly llms: LLM[];
48
+ readonly attemptTimeout: number;
49
+ readonly maxRetryPerLLM: number;
50
+ readonly retryInterval: number;
51
+ readonly retryOnChunkSent: boolean;
52
+ /** @internal */
53
+ _status: LLMStatus[];
54
+ private logger;
55
+ constructor(options: FallbackAdapterOptions);
56
+ get model(): string;
57
+ label(): string;
58
+ chat(opts: {
59
+ chatCtx: ChatContext;
60
+ toolCtx?: ToolContext;
61
+ connOptions?: APIConnectOptions;
62
+ parallelToolCalls?: boolean;
63
+ toolChoice?: ToolChoice;
64
+ extraKwargs?: Record<string, unknown>;
65
+ }): LLMStream;
66
+ /**
67
+ * Emit availability changed event.
68
+ * @internal
69
+ */
70
+ _emitAvailabilityChanged(llm: LLM, available: boolean): void;
71
+ }
72
+ export {};
73
+ //# sourceMappingURL=fallback_adapter.d.ts.map