diff --git a/app/api/llms/stream/route.ts b/app/api/llms/stream/route.ts index b0873a013..fed3b5d47 100644 --- a/app/api/llms/stream/route.ts +++ b/app/api/llms/stream/route.ts @@ -1,2 +1,2 @@ export const runtime = 'edge'; -export { llmStreamingRelayHandler as POST } from '~/modules/llms/server/llms.streaming'; \ No newline at end of file +export { llmStreamingRelayHandler as POST } from '~/modules/llms/server/llm.server.streaming'; \ No newline at end of file diff --git a/src/apps/call/CallUI.tsx b/src/apps/call/CallUI.tsx index a118e3266..1da62a002 100644 --- a/src/apps/call/CallUI.tsx +++ b/src/apps/call/CallUI.tsx @@ -13,10 +13,9 @@ import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver'; import { useChatLLMDropdown } from '../chat/components/applayout/useLLMDropdown'; -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; import { EXPERIMENTAL_speakTextStream } from '~/modules/elevenlabs/elevenlabs.client'; import { SystemPurposeId, SystemPurposes } from '../../data'; -import { llmStreamChatGenerate } from '~/modules/llms/client/llmStreamChatGenerate'; +import { llmStreamingChatGenerate, VChatMessageIn } from '~/modules/llms/llm.client'; import { useElevenLabsVoiceDropdown } from '~/modules/elevenlabs/useElevenLabsVoiceDropdown'; import { Link } from '~/common/components/Link'; @@ -216,7 +215,7 @@ export function CallUI(props: { responseAbortController.current = new AbortController(); let finalText = ''; let error: any | null = null; - llmStreamChatGenerate(chatLLMId, callPrompt, responseAbortController.current.signal, (updatedMessage: Partial) => { + llmStreamingChatGenerate(chatLLMId, callPrompt, null, null, responseAbortController.current.signal, (updatedMessage: Partial) => { const text = updatedMessage.text?.trim(); if (text) { finalText = text; diff --git a/src/apps/call/components/CallMessage.tsx b/src/apps/call/components/CallMessage.tsx index 525a586c2..63a3b76cb 100644 --- a/src/apps/call/components/CallMessage.tsx +++ b/src/apps/call/components/CallMessage.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { Chip, ColorPaletteProp, VariantProp } from '@mui/joy'; import { SxProps } from '@mui/joy/styles/types'; -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; +import type { VChatMessageIn } from '~/modules/llms/llm.client'; export function CallMessage(props: { diff --git a/src/apps/chat/editors/chat-stream.ts b/src/apps/chat/editors/chat-stream.ts index b8dd4e3f0..e3c5a5b4e 100644 --- a/src/apps/chat/editors/chat-stream.ts +++ b/src/apps/chat/editors/chat-stream.ts @@ -2,7 +2,7 @@ import { DLLMId } from '~/modules/llms/store-llms'; import { SystemPurposeId } from '../../../data'; import { autoSuggestions } from '~/modules/aifn/autosuggestions/autoSuggestions'; import { autoTitle } from '~/modules/aifn/autotitle/autoTitle'; -import { llmStreamChatGenerate } from '~/modules/llms/client/llmStreamChatGenerate'; +import { llmStreamingChatGenerate } from '~/modules/llms/llm.client'; import { speakText } from '~/modules/elevenlabs/elevenlabs.client'; import { DMessage, useChatStore } from '~/common/state/store-chats'; @@ -63,7 +63,7 @@ async function streamAssistantMessage( const messages = history.map(({ role, text }) => ({ role, content: text })); try { - await llmStreamChatGenerate(llmId, messages, abortSignal, + await llmStreamingChatGenerate(llmId, messages, null, null, abortSignal, (updatedMessage: Partial) => { // update the message in the store (and thus schedule a re-render) editMessage(updatedMessage); diff --git a/src/apps/personas/useLLMChain.ts b/src/apps/personas/useLLMChain.ts index 45f0be785..66682b977 100644 --- a/src/apps/personas/useLLMChain.ts +++ b/src/apps/personas/useLLMChain.ts @@ -1,8 +1,7 @@ import * as React from 'react'; -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; import { DLLMId, useModelsStore } from '~/modules/llms/store-llms'; -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow, VChatMessageIn } from '~/modules/llms/llm.client'; export interface LLMChainStep { diff --git a/src/modules/aifn/autosuggestions/autoSuggestions.ts b/src/modules/aifn/autosuggestions/autoSuggestions.ts index 8a0bbb9c5..65097a9b4 100644 --- a/src/modules/aifn/autosuggestions/autoSuggestions.ts +++ b/src/modules/aifn/autosuggestions/autoSuggestions.ts @@ -1,5 +1,4 @@ -import type { VChatFunctionIn } from '~/modules/llms/client/llm.client.types'; -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow, VChatFunctionIn } from '~/modules/llms/llm.client'; import { useModelsStore } from '~/modules/llms/store-llms'; import { useChatStore } from '~/common/state/store-chats'; diff --git a/src/modules/aifn/autotitle/autoTitle.ts b/src/modules/aifn/autotitle/autoTitle.ts index 3c0da5d99..4172b6506 100644 --- a/src/modules/aifn/autotitle/autoTitle.ts +++ b/src/modules/aifn/autotitle/autoTitle.ts @@ -1,4 +1,4 @@ -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow } from '~/modules/llms/llm.client'; import { useModelsStore } from '~/modules/llms/store-llms'; import { useChatStore } from '~/common/state/store-chats'; diff --git a/src/modules/aifn/digrams/DiagramsModal.tsx b/src/modules/aifn/digrams/DiagramsModal.tsx index 9206957fc..b0f8da1e7 100644 --- a/src/modules/aifn/digrams/DiagramsModal.tsx +++ b/src/modules/aifn/digrams/DiagramsModal.tsx @@ -8,7 +8,7 @@ import ReplayIcon from '@mui/icons-material/Replay'; import StopOutlinedIcon from '@mui/icons-material/StopOutlined'; import TelegramIcon from '@mui/icons-material/Telegram'; -import { llmStreamChatGenerate } from '~/modules/llms/client/llmStreamChatGenerate'; +import { llmStreamingChatGenerate } from '~/modules/llms/llm.client'; import { ChatMessage } from '../../../apps/chat/components/message/ChatMessage'; @@ -86,7 +86,7 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi const diagramPrompt = bigDiagramPrompt(diagramType, diagramLanguage, systemMessage.text, subject, customInstruction); try { - await llmStreamChatGenerate(diagramLlm.id, diagramPrompt, stepAbortController.signal, + await llmStreamingChatGenerate(diagramLlm.id, diagramPrompt, null, null, stepAbortController.signal, (update: Partial<{ text: string, typing: boolean, originLLM: string }>) => { assistantMessage = { ...assistantMessage, ...update }; setMessage(assistantMessage); diff --git a/src/modules/aifn/digrams/diagrams.data.ts b/src/modules/aifn/digrams/diagrams.data.ts index 8a4b675de..d1b424d35 100644 --- a/src/modules/aifn/digrams/diagrams.data.ts +++ b/src/modules/aifn/digrams/diagrams.data.ts @@ -1,6 +1,5 @@ -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; - import type { FormRadioOption } from '~/common/components/forms/FormRadioControl'; +import type { VChatMessageIn } from '~/modules/llms/llm.client'; export type DiagramType = 'auto' | 'mind'; diff --git a/src/modules/aifn/imagine/imaginePromptFromText.ts b/src/modules/aifn/imagine/imaginePromptFromText.ts index 27c1b47d8..c1556630b 100644 --- a/src/modules/aifn/imagine/imaginePromptFromText.ts +++ b/src/modules/aifn/imagine/imaginePromptFromText.ts @@ -1,4 +1,4 @@ -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow } from '~/modules/llms/llm.client'; import { useModelsStore } from '~/modules/llms/store-llms'; diff --git a/src/modules/aifn/react/react.ts b/src/modules/aifn/react/react.ts index 4aac50e78..5a264fba6 100644 --- a/src/modules/aifn/react/react.ts +++ b/src/modules/aifn/react/react.ts @@ -2,11 +2,10 @@ * porting of implementation from here: https://til.simonwillison.net/llms/python-react-pattern */ -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; import { DLLMId } from '~/modules/llms/store-llms'; import { callApiSearchGoogle } from '~/modules/google/search.client'; import { callBrowseFetchPage } from '~/modules/browse/browse.client'; -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow, VChatMessageIn } from '~/modules/llms/llm.client'; // prompt to implement the ReAct paradigm: https://arxiv.org/abs/2210.03629 diff --git a/src/modules/aifn/summarize/summerize.ts b/src/modules/aifn/summarize/summerize.ts index 1d9179cff..1635a95f1 100644 --- a/src/modules/aifn/summarize/summerize.ts +++ b/src/modules/aifn/summarize/summerize.ts @@ -1,5 +1,5 @@ import { DLLMId, findLLMOrThrow } from '~/modules/llms/store-llms'; -import { llmChatGenerateOrThrow } from '~/modules/llms/client/llmChatGenerate'; +import { llmChatGenerateOrThrow } from '~/modules/llms/llm.client'; // prompt to be tried when doing recursive summerization. diff --git a/src/modules/aifn/useStreamChatText.ts b/src/modules/aifn/useStreamChatText.ts index 6239c637f..4c54b65f4 100644 --- a/src/modules/aifn/useStreamChatText.ts +++ b/src/modules/aifn/useStreamChatText.ts @@ -1,8 +1,7 @@ import * as React from 'react'; import type { DLLMId } from '~/modules/llms/store-llms'; -import type { VChatMessageIn } from '~/modules/llms/client/llm.client.types'; -import { llmStreamChatGenerate } from '~/modules/llms/client/llmStreamChatGenerate'; +import { llmStreamingChatGenerate, VChatMessageIn } from '~/modules/llms/llm.client'; export function useStreamChatText() { @@ -25,7 +24,7 @@ export function useStreamChatText() { try { let lastText = ''; - await llmStreamChatGenerate(llmId, prompt, abortControllerRef.current.signal, (update) => { + await llmStreamingChatGenerate(llmId, prompt, null, null, abortControllerRef.current.signal, (update) => { if (update.text) { lastText = update.text; setPartialText(lastText); diff --git a/src/modules/llms/client/llm.client.types.ts b/src/modules/llms/client/llm.client.types.ts deleted file mode 100644 index 3732e64a9..000000000 --- a/src/modules/llms/client/llm.client.types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { OpenAIWire } from '../server/openai/openai.wiretypes'; - - -// Model List types -// export { type ModelDescriptionSchema } from '../server/llm.server.types'; - - -// Chat Generate types - -export interface VChatMessageIn { - role: 'assistant' | 'system' | 'user'; // | 'function'; - content: string; - //name?: string; // when role: 'function' -} - -export type VChatFunctionIn = OpenAIWire.ChatCompletion.RequestFunctionDef; - -export interface VChatMessageOut { - role: 'assistant' | 'system' | 'user'; - content: string; - finish_reason: 'stop' | 'length' | null; -} - -export interface VChatMessageOrFunctionCallOut extends VChatMessageOut { - function_name: string; - function_arguments: object | null; -} \ No newline at end of file diff --git a/src/modules/llms/client/llmChatGenerate.ts b/src/modules/llms/client/llmChatGenerate.ts deleted file mode 100644 index abf9618b3..000000000 --- a/src/modules/llms/client/llmChatGenerate.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { DLLMId } from '../store-llms'; -import { findVendorForLlmOrThrow } from '../vendors/vendors.registry'; - -import type { VChatFunctionIn, VChatMessageIn, VChatMessageOrFunctionCallOut, VChatMessageOut } from './llm.client.types'; - - -export async function llmChatGenerateOrThrow( - llmId: DLLMId, messages: VChatMessageIn[], functions: VChatFunctionIn[] | null, forceFunctionName: string | null, maxTokens?: number, -): Promise { - - // id to DLLM and vendor - const { llm, vendor } = findVendorForLlmOrThrow(llmId); - - // FIXME: relax the forced cast - const options = llm.options as TLLMOptions; - - // get the access - const partialSourceSetup = llm._source.setup; - const access = vendor.getTransportAccess(partialSourceSetup); - - // execute via the vendor - return await vendor.rpcChatGenerateOrThrow(access, options, messages, functions, forceFunctionName, maxTokens); -} diff --git a/src/modules/llms/llm.client.ts b/src/modules/llms/llm.client.ts new file mode 100644 index 000000000..73957f08c --- /dev/null +++ b/src/modules/llms/llm.client.ts @@ -0,0 +1,74 @@ +import type { DLLMId } from './store-llms'; +import type { OpenAIWire } from './server/openai/openai.wiretypes'; +import { findVendorForLlmOrThrow } from './vendors/vendors.registry'; + + +// LLM Client Types +// NOTE: Model List types in '../server/llm.server.types'; + +export interface VChatMessageIn { + role: 'assistant' | 'system' | 'user'; // | 'function'; + content: string; + //name?: string; // when role: 'function' +} + +export type VChatFunctionIn = OpenAIWire.ChatCompletion.RequestFunctionDef; + +export interface VChatMessageOut { + role: 'assistant' | 'system' | 'user'; + content: string; + finish_reason: 'stop' | 'length' | null; +} + +export interface VChatMessageOrFunctionCallOut extends VChatMessageOut { + function_name: string; + function_arguments: object | null; +} + + +// LLM Client Functions + +export async function llmChatGenerateOrThrow( + llmId: DLLMId, + messages: VChatMessageIn[], + functions: VChatFunctionIn[] | null, forceFunctionName: string | null, + maxTokens?: number, +): Promise { + + // id to DLLM and vendor + const { llm, vendor } = findVendorForLlmOrThrow(llmId); + + // FIXME: relax the forced cast + const options = llm.options as TLLMOptions; + + // get the access + const partialSourceSetup = llm._source.setup; + const access = vendor.getTransportAccess(partialSourceSetup); + + // execute via the vendor + return await vendor.rpcChatGenerateOrThrow(access, options, messages, functions, forceFunctionName, maxTokens); +} + + +export async function llmStreamingChatGenerate( + llmId: DLLMId, + messages: VChatMessageIn[], + functions: VChatFunctionIn[] | null, + forceFunctionName: string | null, + abortSignal: AbortSignal, + onUpdate: (update: Partial<{ text: string, typing: boolean, originLLM: string }>, done: boolean) => void, +): Promise { + + // id to DLLM and vendor + const { llm, vendor } = findVendorForLlmOrThrow(llmId); + + // FIXME: relax the forced cast + const llmOptions = llm.options as TLLMOptions; + + // get the access + const partialSourceSetup = llm._source.setup; + const access = vendor.getTransportAccess(partialSourceSetup); // as ChatStreamInputSchema['access']; + + // execute via the vendor + return await vendor.streamingChatGenerateOrThrow(access, llmId, llmOptions, messages, functions, forceFunctionName, abortSignal, onUpdate); +} diff --git a/src/modules/llms/server/llms.streaming.ts b/src/modules/llms/server/llm.server.streaming.ts similarity index 93% rename from src/modules/llms/server/llms.streaming.ts rename to src/modules/llms/server/llm.server.streaming.ts index 7383d89f5..47a08a312 100644 --- a/src/modules/llms/server/llms.streaming.ts +++ b/src/modules/llms/server/llm.server.streaming.ts @@ -9,6 +9,9 @@ import { createEmptyReadableStream, debugGenerateCurlCommand, safeErrorString, S import type { AnthropicWire } from './anthropic/anthropic.wiretypes'; import { anthropicAccess, anthropicAccessSchema, anthropicChatCompletionPayload } from './anthropic/anthropic.router'; +// Gemini server imports +import { geminiAccessSchema } from './gemini/gemini.router'; + // Ollama server imports import { wireOllamaChunkedOutputSchema } from './ollama/ollama.wiretypes'; import { OLLAMA_PATH_CHAT, ollamaAccess, ollamaAccessSchema, ollamaChatCompletionPayload } from './ollama/ollama.router'; @@ -37,24 +40,24 @@ type EventStreamFormat = 'sse' | 'json-nl'; type AIStreamParser = (data: string) => { text: string, close: boolean }; -const chatStreamInputSchema = z.object({ - access: z.union([anthropicAccessSchema, ollamaAccessSchema, openAIAccessSchema]), +const chatStreamingInputSchema = z.object({ + access: z.union([anthropicAccessSchema, geminiAccessSchema, ollamaAccessSchema, openAIAccessSchema]), model: openAIModelSchema, history: openAIHistorySchema, }); -export type ChatStreamInputSchema = z.infer; +export type ChatStreamingInputSchema = z.infer; -const chatStreamFirstOutputPacketSchema = z.object({ +const chatStreamingFirstOutputPacketSchema = z.object({ model: z.string(), }); -export type ChatStreamFirstOutputPacketSchema = z.infer; +export type ChatStreamingFirstOutputPacketSchema = z.infer; export async function llmStreamingRelayHandler(req: NextRequest): Promise { // inputs - reuse the tRPC schema const body = await req.json(); - const { access, model, history } = chatStreamInputSchema.parse(body); + const { access, model, history } = chatStreamingInputSchema.parse(body); // access/dialect dependent setup: // - requestAccess: the headers and URL to use for the upstream API call @@ -240,7 +243,7 @@ function createAnthropicStreamParser(): AIStreamParser { // hack: prepend the model name to the first packet if (!hasBegun) { hasBegun = true; - const firstPacket: ChatStreamFirstOutputPacketSchema = { model: json.model }; + const firstPacket: ChatStreamingFirstOutputPacketSchema = { model: json.model }; text = JSON.stringify(firstPacket) + text; } @@ -276,7 +279,7 @@ function createOllamaChatCompletionStreamParser(): AIStreamParser { // hack: prepend the model name to the first packet if (!hasBegun && chunk.model) { hasBegun = true; - const firstPacket: ChatStreamFirstOutputPacketSchema = { model: chunk.model }; + const firstPacket: ChatStreamingFirstOutputPacketSchema = { model: chunk.model }; text = JSON.stringify(firstPacket) + text; } @@ -317,7 +320,7 @@ function createOpenAIStreamParser(): AIStreamParser { // hack: prepend the model name to the first packet if (!hasBegun) { hasBegun = true; - const firstPacket: ChatStreamFirstOutputPacketSchema = { model: json.model }; + const firstPacket: ChatStreamingFirstOutputPacketSchema = { model: json.model }; text = JSON.stringify(firstPacket) + text; } diff --git a/src/modules/llms/vendors/IModelVendor.ts b/src/modules/llms/vendors/IModelVendor.ts index 6c2290b6a..e7cc7fbb0 100644 --- a/src/modules/llms/vendors/IModelVendor.ts +++ b/src/modules/llms/vendors/IModelVendor.ts @@ -1,10 +1,10 @@ import type React from 'react'; import type { TRPCClientErrorBase } from '@trpc/client'; -import type { DLLM, DModelSourceId } from '../store-llms'; +import type { DLLM, DLLMId, DModelSourceId } from '../store-llms'; import type { ModelDescriptionSchema } from '../server/llm.server.types'; import type { ModelVendorId } from './vendors.registry'; -import type { VChatFunctionIn, VChatMessageIn, VChatMessageOrFunctionCallOut, VChatMessageOut } from '../client/llm.client.types'; +import type { VChatFunctionIn, VChatMessageIn, VChatMessageOrFunctionCallOut, VChatMessageOut } from '~/modules/llms/llm.client'; export interface IModelVendor> { @@ -43,4 +43,14 @@ export interface IModelVendor Promise; + streamingChatGenerateOrThrow: ( + access: TAccess, + llmId: DLLMId, + llmOptions: TLLMOptions, + messages: VChatMessageIn[], + functions: VChatFunctionIn[] | null, forceFunctionName: string | null, + abortSignal: AbortSignal, + onUpdate: (update: Partial<{ text: string, typing: boolean, originLLM: string }>, done: boolean) => void, + ) => Promise; + } diff --git a/src/modules/llms/vendors/anthropic/anthropic.vendor.ts b/src/modules/llms/vendors/anthropic/anthropic.vendor.ts index c8d4fd2d1..e007d8f9f 100644 --- a/src/modules/llms/vendors/anthropic/anthropic.vendor.ts +++ b/src/modules/llms/vendors/anthropic/anthropic.vendor.ts @@ -5,7 +5,8 @@ import { apiAsync, apiQuery } from '~/common/util/trpc.client'; import type { AnthropicAccessSchema } from '../../server/anthropic/anthropic.router'; import type { IModelVendor } from '../IModelVendor'; -import type { VChatMessageOut } from '../../client/llm.client.types'; +import type { VChatMessageOut } from '../../llm.client'; +import { unifiedStreamingClient } from '../unifiedStreamingClient'; import { LLMOptionsOpenAI } from '../openai/openai.vendor'; import { OpenAILLMOptions } from '../openai/OpenAILLMOptions'; @@ -77,4 +78,7 @@ export const ModelVendorAnthropic: IModelVendor( + access: ChatStreamingInputSchema['access'], llmId: DLLMId, + llmOptions: TLLMOptions, messages: VChatMessageIn[], - abortSignal: AbortSignal, - onUpdate: (update: Partial<{ text: string, typing: boolean, originLLM: string }>, done: boolean) => void, -): Promise { - const { llm, vendor } = findVendorForLlmOrThrow(llmId); - const access = vendor.getTransportAccess(llm._source.setup) as ChatStreamInputSchema['access']; - return await vendorStreamChat(access, llm, messages, abortSignal, onUpdate); -} - - -async function vendorStreamChat( - access: ChatStreamInputSchema['access'], - llm: DLLM, - messages: VChatMessageIn[], + functions: VChatFunctionIn[] | null, forceFunctionName: string | null, abortSignal: AbortSignal, onUpdate: (update: Partial<{ text: string, typing: boolean, originLLM: string }>, done: boolean) => void, ) { @@ -80,12 +65,12 @@ async function vendorStreamChat( } // model params (llm) - const { llmRef, llmTemperature, llmResponseTokens } = (llm.options as any) || {}; + const { llmRef, llmTemperature, llmResponseTokens } = (llmOptions as any) || {}; if (!llmRef || llmTemperature === undefined || llmResponseTokens === undefined) - throw new Error(`Error in configuration for model ${llm.id}: ${JSON.stringify(llm.options)}`); + throw new Error(`Error in configuration for model ${llmId}: ${JSON.stringify(llmOptions)}`); // prepare the input, similarly to the tRPC openAI.chatGenerate - const input: ChatStreamInputSchema = { + const input: ChatStreamingInputSchema = { access, model: { id: llmRef, @@ -132,7 +117,7 @@ async function vendorStreamChat( incrementalText = incrementalText.substring(endOfJson + 1); parsedFirstPacket = true; try { - const parsed: ChatStreamFirstOutputPacketSchema = JSON.parse(json); + const parsed: ChatStreamingFirstOutputPacketSchema = JSON.parse(json); onUpdate({ originLLM: parsed.model }, false); } catch (e) { // error parsing JSON, ignore diff --git a/src/modules/llms/vendors/vendors.registry.ts b/src/modules/llms/vendors/vendors.registry.ts index 061c34ea1..054a8a6bf 100644 --- a/src/modules/llms/vendors/vendors.registry.ts +++ b/src/modules/llms/vendors/vendors.registry.ts @@ -1,6 +1,6 @@ import { ModelVendorAnthropic } from './anthropic/anthropic.vendor'; import { ModelVendorAzure } from './azure/azure.vendor'; -import { ModelVendorGemini } from './googleai/gemini.vendor'; +import { ModelVendorGemini } from './gemini/gemini.vendor'; import { ModelVendorLocalAI } from './localai/localai.vendor'; import { ModelVendorMistral } from './mistral/mistral.vendor'; import { ModelVendorOllama } from './ollama/ollama.vendor';