From 605910b76d8b3be1053aa47de99c4d05d8eef979 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 14 May 2025 15:26:14 -0700 Subject: [PATCH] forgot files for DPAIS --- .../LLMSelection/DPAISOptions/index.jsx | 181 +++++++++++++++ frontend/src/media/llmprovider/dpais.png | Bin 0 -> 10124 bytes .../AiProviders/dellProAiStudio/index.js | 210 ++++++++++++++++++ .../aibitat/providers/dellProAiStudio.js | 122 ++++++++++ 4 files changed, 513 insertions(+) create mode 100644 frontend/src/components/LLMSelection/DPAISOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/dpais.png create mode 100644 server/utils/AiProviders/dellProAiStudio/index.js create mode 100644 server/utils/agents/aibitat/providers/dellProAiStudio.js diff --git a/frontend/src/components/LLMSelection/DPAISOptions/index.jsx b/frontend/src/components/LLMSelection/DPAISOptions/index.jsx new file mode 100644 index 00000000..e2c7187a --- /dev/null +++ b/frontend/src/components/LLMSelection/DPAISOptions/index.jsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from "react"; +import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { DPAIS_COMMON_URLS } from "@/utils/constants"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; + +export default function DellProAIStudioOptions({ + settings, + showAlert = false, +}) { + const { + autoDetecting: loading, + basePath, + basePathValue, + showAdvancedControls, + setShowAdvancedControls, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "dpais", + initialBasePath: settings?.DellProAiStudioBasePath, + ENDPOINTS: DPAIS_COMMON_URLS, + }); + + return ( +
+
+ {!settings?.credentialsOnly && ( + <> + +
+ + e.target.blur()} + defaultValue={settings?.DellProAiStudioTokenLimit} + required={true} + autoComplete="off" + /> +
+ + )} +
+
+ +
+ +
+ ); +} + +function DellProAiStudioModelSelection({ settings, basePath = null }) { + const [customModels, setCustomModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + if (!basePath) { + setCustomModels([]); + setLoading(false); + return; + } + setLoading(true); + const { models } = await System.customModels( + "dpais", + null, + basePath, + 2_000 + ); + setCustomModels(models || []); + setLoading(false); + } + findCustomModels(); + }, [basePath]); + + if (loading || customModels.length == 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/media/llmprovider/dpais.png b/frontend/src/media/llmprovider/dpais.png new file mode 100644 index 0000000000000000000000000000000000000000..3678f96909bda20601e57b6c4d88b8f24eb7253a GIT binary patch literal 10124 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 literal 0 HcmV?d00001 diff --git a/server/utils/AiProviders/dellProAiStudio/index.js b/server/utils/AiProviders/dellProAiStudio/index.js new file mode 100644 index 00000000..2cca4c8b --- /dev/null +++ b/server/utils/AiProviders/dellProAiStudio/index.js @@ -0,0 +1,210 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { + handleDefaultStreamResponseV2, + formatChatHistory, +} = require("../../helpers/chat/responses"); +const { + LLMPerformanceMonitor, +} = require("../../helpers/chat/LLMPerformanceMonitor"); + +// hybrid of openAi LLM chat completion for Dell Pro AI Studio +class DellProAiStudioLLM { + constructor(embedder = null, modelPreference = null) { + if (!process.env.DPAIS_LLM_BASE_PATH) + throw new Error("No Dell Pro AI Studio Base Path was set."); + + const { OpenAI: OpenAIApi } = require("openai"); + this.dpais = new OpenAIApi({ + baseURL: DellProAiStudioLLM.parseBasePath(), + apiKey: null, + }); + + this.model = modelPreference || process.env.DPAIS_LLM_MODEL_PREF; + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = embedder ?? new NativeEmbedder(); + this.defaultTemp = 0.7; + this.log( + `Dell Pro AI Studio LLM initialized with ${this.model}. ctx: ${this.promptWindowLimit()}` + ); + } + + /** + * Parse the base path for the Dell Pro AI Studio API + * so we can use it for inference requests + * @param {string} providedBasePath + * @returns {string} + */ + static parseBasePath(providedBasePath = process.env.DPAIS_LLM_BASE_PATH) { + try { + const baseURL = new URL(providedBasePath); + const basePath = `${baseURL.origin}/v1/openai`; + return basePath; + } catch (e) { + return null; + } + } + + log(text, ...args) { + console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args); + } + + #appendContext(contextTexts = []) { + if (!contextTexts || !contextTexts.length) return ""; + return ( + "\nContext:\n" + + contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("") + ); + } + + streamingEnabled() { + return "streamGetChatCompletion" in this; + } + + static promptWindowLimit(_modelName) { + const limit = process.env.DPAIS_LLM_MODEL_TOKEN_LIMIT || 4096; + if (!limit || isNaN(Number(limit))) + throw new Error("No Dell Pro AI Studio token context limit was set."); + return Number(limit); + } + + // Ensure the user set a value for the token limit + // and if undefined - assume 4096 window. + promptWindowLimit() { + const limit = process.env.DPAIS_LLM_MODEL_TOKEN_LIMIT || 4096; + if (!limit || isNaN(Number(limit))) + throw new Error("No Dell Pro AI Studio token context limit was set."); + return Number(limit); + } + + async isValidChatCompletionModel(_ = "") { + return true; + } + + /** + * Generates appropriate content array for a message + attachments. + * @param {{userPrompt:string, attachments: import("../../helpers").Attachment[]}} + * @returns {string|object[]} + */ + #generateContent({ userPrompt, attachments = [] }) { + if (!attachments.length) return userPrompt; + + const content = [{ type: "text", text: userPrompt }]; + for (let attachment of attachments) { + content.push({ + type: "image_url", + image_url: { + url: attachment.contentString, + detail: "auto", + }, + }); + } + return content.flat(); + } + + /** + * Construct the user prompt for this model. + * @param {{attachments: import("../../helpers").Attachment[]}} param0 + * @returns + */ + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + _attachments = [], // not used for Dell Pro AI Studio - `attachments` passed in is ignored + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [ + prompt, + ...formatChatHistory(chatHistory, this.#generateContent), + { + role: "user", + content: this.#generateContent({ userPrompt, _attachments }), + }, + ]; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.model) + throw new Error( + `Dell Pro AI Studio chat: ${this.model} is not valid or defined model for chat completion!` + ); + + const result = await LLMPerformanceMonitor.measureAsyncFunction( + this.dpais.chat.completions.create({ + model: this.model, + messages, + temperature, + }) + ); + + if ( + !result.output.hasOwnProperty("choices") || + result.output.choices.length === 0 + ) + return null; + + return { + textResponse: result.output.choices[0].message.content, + metrics: { + prompt_tokens: result.output.usage?.prompt_tokens || 0, + completion_tokens: result.output.usage?.completion_tokens || 0, + total_tokens: result.output.usage?.total_tokens || 0, + outputTps: result.output.usage?.completion_tokens / result.duration, + duration: result.duration, + }, + }; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.model) + throw new Error( + `Dell Pro AI Studio chat: ${this.model} is not valid or defined model for chat completion!` + ); + + const measuredStreamRequest = await LLMPerformanceMonitor.measureStream( + this.dpais.chat.completions.create({ + model: this.model, + stream: true, + messages, + temperature, + }), + messages + ); + return measuredStreamRequest; + } + + handleStream(response, stream, responseProps) { + return handleDefaultStreamResponseV2(response, stream, responseProps); + } + + // Simple wrapper for dynamic embedder & normalize interface for all LLM implementations + async embedTextInput(textInput) { + return await this.embedder.embedTextInput(textInput); + } + async embedChunks(textChunks = []) { + return await this.embedder.embedChunks(textChunks); + } + + async compressMessages(promptArgs = {}, rawHistory = []) { + const { messageArrayCompressor } = require("../../helpers/chat"); + const messageArray = this.constructPrompt(promptArgs); + return await messageArrayCompressor(this, messageArray, rawHistory); + } +} + +module.exports = { + DellProAiStudioLLM, +}; diff --git a/server/utils/agents/aibitat/providers/dellProAiStudio.js b/server/utils/agents/aibitat/providers/dellProAiStudio.js new file mode 100644 index 00000000..07f86416 --- /dev/null +++ b/server/utils/agents/aibitat/providers/dellProAiStudio.js @@ -0,0 +1,122 @@ +const OpenAI = require("openai"); +const Provider = require("./ai-provider.js"); +const InheritMultiple = require("./helpers/classes.js"); +const UnTooled = require("./helpers/untooled.js"); +const { + DellProAiStudioLLM, +} = require("../../../AiProviders/dellProAiStudio/index.js"); + +/** + * The agent provider for Dell Pro AI Studio. + */ +class DellProAiStudioProvider extends InheritMultiple([Provider, UnTooled]) { + model; + + /** + * + * @param {{model?: string}} config + */ + constructor(config = {}) { + super(); + const model = config?.model || process.env.DPAIS_LLM_MODEL_PREF; + const client = new OpenAI({ + baseURL: DellProAiStudioLLM.parseBasePath(), // Will use process.env.DPAIS_LLM_BASE_PATH if not provided + apiKey: null, + }); + + this._client = client; + this.model = model; + this.verbose = true; + } + + get client() { + return this._client; + } + + async #handleFunctionCallChat({ messages = [] }) { + return await this.client.chat.completions + .create({ + model: this.model, + messages, + }) + .then((result) => { + if (!result.hasOwnProperty("choices")) + throw new Error("DellProAiStudio chat: No results!"); + if (result.choices.length === 0) + throw new Error("DellProAiStudio chat: No results length!"); + return result.choices[0].message.content; + }) + .catch((_) => { + return null; + }); + } + + /** + * Create a completion based on the received messages. + * + * @param messages A list of messages to send to the API. + * @param functions + * @returns The completion. + */ + async complete(messages, functions = []) { + try { + let completion; + if (functions.length > 0) { + const { toolCall, text } = await this.functionCall( + messages, + functions, + this.#handleFunctionCallChat.bind(this) + ); + + if (toolCall !== null) { + this.providerLog(`Valid tool call found - running ${toolCall.name}.`); + this.deduplicator.trackRun(toolCall.name, toolCall.arguments); + return { + result: null, + functionCall: { + name: toolCall.name, + arguments: toolCall.arguments, + }, + cost: 0, + }; + } + completion = { content: text }; + } + + if (!completion?.content) { + this.providerLog( + "Will assume chat completion without tool call inputs." + ); + const response = await this.client.chat.completions.create({ + model: this.model, + messages: this.cleanMsgs(messages), + }); + completion = response.choices[0].message; + } + + // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent + // from calling the exact same function over and over in a loop within a single chat exchange + // _but_ we should enable it to call previously used tools in a new chat interaction. + this.deduplicator.reset("runs"); + return { + result: completion.content, + cost: 0, + }; + } catch (error) { + throw error; + } + } + + /** + * Get the cost of the completion. + * + * @param _usage The completion to get the cost for. + * @returns The cost of the completion. + * Stubbed since LMStudio has no cost basis. + */ + getCost(_usage) { + return 0; + } +} + +module.exports = DellProAiStudioProvider;