From 5bc96bca887e7a7f0affb7d64da720785b9ddeeb Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Mon, 21 Oct 2024 16:32:49 -0700 Subject: [PATCH] Add Grok/XAI support for LLM & agents (#2517) * Add Grok/XAI support for LLM & agents * forgot files --- .vscode/settings.json | 1 + docker/.env.example | 4 + .../LLMSelection/XAiLLMOptions/index.jsx | 114 ++++++++++++ frontend/src/hooks/useGetProvidersModels.js | 1 + frontend/src/media/llmprovider/xai.png | Bin 0 -> 14108 bytes .../GeneralSettings/LLMPreference/index.jsx | 11 ++ .../Steps/DataHandling/index.jsx | 8 + .../Steps/LLMPreference/index.jsx | 9 + .../AgentConfig/AgentLLMSelection/index.jsx | 1 + server/.env.example | 4 + server/models/systemSettings.js | 4 + server/utils/AiProviders/modelMap.js | 3 + server/utils/AiProviders/xai/index.js | 168 ++++++++++++++++++ server/utils/agents/aibitat/index.js | 2 + .../agents/aibitat/providers/ai-provider.js | 8 + .../utils/agents/aibitat/providers/index.js | 2 + server/utils/agents/aibitat/providers/xai.js | 116 ++++++++++++ server/utils/agents/index.js | 6 + server/utils/helpers/customModels.js | 33 ++++ server/utils/helpers/index.js | 6 + server/utils/helpers/updateENV.js | 11 ++ 21 files changed, 512 insertions(+) create mode 100644 frontend/src/components/LLMSelection/XAiLLMOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/xai.png create mode 100644 server/utils/AiProviders/xai/index.js create mode 100644 server/utils/agents/aibitat/providers/xai.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 8405c5281..14efd3fae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,6 +53,7 @@ "uuidv", "vectordbs", "Weaviate", + "XAILLM", "Zilliz" ], "eslint.experimental.useFlatConfig": true, diff --git a/docker/.env.example b/docker/.env.example index a6cabe655..7bb07ebef 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -109,6 +109,10 @@ GID='1000' # APIPIE_LLM_API_KEY='sk-123abc' # APIPIE_LLM_MODEL_PREF='openrouter/llama-3.1-8b-instruct' +# LLM_PROVIDER='xai' +# XAI_LLM_API_KEY='xai-your-api-key-here' +# XAI_LLM_MODEL_PREF='grok-beta' + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/frontend/src/components/LLMSelection/XAiLLMOptions/index.jsx b/frontend/src/components/LLMSelection/XAiLLMOptions/index.jsx new file mode 100644 index 000000000..d760a8ba4 --- /dev/null +++ b/frontend/src/components/LLMSelection/XAiLLMOptions/index.jsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from "react"; +import System from "@/models/system"; + +export default function XAILLMOptions({ settings }) { + const [inputValue, setInputValue] = useState(settings?.XAIApiKey); + const [apiKey, setApiKey] = useState(settings?.XAIApiKey); + + return ( +
+
+ + setInputValue(e.target.value)} + onBlur={() => setApiKey(inputValue)} + /> +
+ + {!settings?.credentialsOnly && ( + + )} +
+ ); +} + +function XAIModelSelection({ apiKey, settings }) { + const [customModels, setCustomModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + if (!apiKey) { + setCustomModels([]); + setLoading(true); + return; + } + + try { + setLoading(true); + const { models } = await System.customModels("xai", apiKey); + setCustomModels(models || []); + } catch (error) { + console.error("Failed to fetch custom models:", error); + setCustomModels([]); + } finally { + setLoading(false); + } + } + findCustomModels(); + }, [apiKey]); + + if (loading) { + return ( +
+ + +

+ Enter a valid API key to view all available models for your account. +

+
+ ); + } + + return ( +
+ + +

+ Select the xAI model you want to use for your conversations. +

+
+ ); +} diff --git a/frontend/src/hooks/useGetProvidersModels.js b/frontend/src/hooks/useGetProvidersModels.js index ece31c2b5..a493438c7 100644 --- a/frontend/src/hooks/useGetProvidersModels.js +++ b/frontend/src/hooks/useGetProvidersModels.js @@ -49,6 +49,7 @@ const PROVIDER_DEFAULT_MODELS = { textgenwebui: [], "generic-openai": [], bedrock: [], + xai: ["grok-beta"], }; // For providers with large model lists (e.g. togetherAi) - we subgroup the options diff --git a/frontend/src/media/llmprovider/xai.png b/frontend/src/media/llmprovider/xai.png new file mode 100644 index 0000000000000000000000000000000000000000..93106761e4a616f0ddf38b4cdd2292b4c029a406 GIT binary patch literal 14108 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 , + description: "Run xAI's powerful LLMs like Grok-2 and more.", + requiredConfig: ["XAIApiKey", "XAIModelPref"], + }, + { name: "Native", value: "native", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index e3b4e2ee8..33750cba2 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -22,6 +22,7 @@ import LiteLLMLogo from "@/media/llmprovider/litellm.png"; import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; +import XAILogo from "@/media/llmprovider/xai.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import ZillizLogo from "@/media/vectordbs/zilliz.png"; @@ -210,6 +211,13 @@ export const LLM_SELECTION_PRIVACY = { ], logo: APIPieLogo, }, + xai: { + name: "xAI", + description: [ + "Your model and chat contents are visible to xAI in accordance with their terms of service.", + ], + logo: XAILogo, + }, }; export const VECTOR_DB_PRIVACY = { diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 1b69369f5..cc17acfd3 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -22,6 +22,7 @@ import LiteLLMLogo from "@/media/llmprovider/litellm.png"; import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; +import XAILogo from "@/media/llmprovider/xai.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; @@ -47,6 +48,7 @@ import LiteLLMOptions from "@/components/LLMSelection/LiteLLMOptions"; import AWSBedrockLLMOptions from "@/components/LLMSelection/AwsBedrockLLMOptions"; import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions"; import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; +import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import System from "@/models/system"; @@ -219,6 +221,13 @@ const LLMS = [ options: (settings) => , description: "Run powerful foundation models privately with AWS Bedrock.", }, + { + name: "xAI", + value: "xai", + logo: XAILogo, + options: (settings) => , + description: "Run xAI's powerful LLMs like Grok-2 and more.", + }, { name: "Native", value: "native", diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx index d0b0b4893..c59a77e71 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx @@ -26,6 +26,7 @@ const ENABLED_PROVIDERS = [ "deepseek", "litellm", "apipie", + "xai", // TODO: More agent support. // "cohere", // Has tool calling and will need to build explicit support // "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested. diff --git a/server/.env.example b/server/.env.example index f2d16b310..9c513f62f 100644 --- a/server/.env.example +++ b/server/.env.example @@ -99,6 +99,10 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long. # APIPIE_LLM_API_KEY='sk-123abc' # APIPIE_LLM_MODEL_PREF='openrouter/llama-3.1-8b-instruct' +# LLM_PROVIDER='xai' +# XAI_LLM_API_KEY='xai-your-api-key-here' +# XAI_LLM_MODEL_PREF='grok-beta' + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index e5de59376..55569be07 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -525,6 +525,10 @@ const SystemSettings = { // APIPie LLM API Keys ApipieLLMApiKey: !!process.env.APIPIE_LLM_API_KEY, ApipieLLMModelPref: process.env.APIPIE_LLM_MODEL_PREF, + + // xAI LLM API Keys + XAIApiKey: !!process.env.XAI_LLM_API_KEY, + XAIModelPref: process.env.XAI_LLM_MODEL_PREF, }; }, diff --git a/server/utils/AiProviders/modelMap.js b/server/utils/AiProviders/modelMap.js index 84e480b31..390278f37 100644 --- a/server/utils/AiProviders/modelMap.js +++ b/server/utils/AiProviders/modelMap.js @@ -61,6 +61,9 @@ const MODEL_MAP = { "deepseek-chat": 128_000, "deepseek-coder": 128_000, }, + xai: { + "grok-beta": 131_072, + }, }; module.exports = { MODEL_MAP }; diff --git a/server/utils/AiProviders/xai/index.js b/server/utils/AiProviders/xai/index.js new file mode 100644 index 000000000..7a25760df --- /dev/null +++ b/server/utils/AiProviders/xai/index.js @@ -0,0 +1,168 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { + handleDefaultStreamResponseV2, +} = require("../../helpers/chat/responses"); +const { MODEL_MAP } = require("../modelMap"); + +class XAiLLM { + constructor(embedder = null, modelPreference = null) { + if (!process.env.XAI_LLM_API_KEY) + throw new Error("No xAI API key was set."); + const { OpenAI: OpenAIApi } = require("openai"); + + this.openai = new OpenAIApi({ + baseURL: "https://api.x.ai/v1", + apiKey: process.env.XAI_LLM_API_KEY, + }); + this.model = + modelPreference || process.env.XAI_LLM_MODEL_PREF || "grok-beta"; + 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; + } + + #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) { + return MODEL_MAP.xai[modelName] ?? 131_072; + } + + promptWindowLimit() { + return MODEL_MAP.xai[this.model] ?? 131_072; + } + + isValidChatCompletionModel(modelName = "") { + switch (modelName) { + case "grok-beta": + return true; + default: + return false; + } + } + + /** + * 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: "high", + }, + }); + } + return content.flat(); + } + + /** + * Construct the user prompt for this model. + * @param {{attachments: import("../../helpers").Attachment[]}} param0 + * @returns + */ + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + attachments = [], // This is the specific attachment for only this prompt + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [ + prompt, + ...chatHistory, + { + role: "user", + content: this.#generateContent({ userPrompt, attachments }), + }, + ]; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.isValidChatCompletionModel(this.model)) + throw new Error( + `xAI chat: ${this.model} is not valid for chat completion!` + ); + + const result = await this.openai.chat.completions + .create({ + model: this.model, + messages, + temperature, + }) + .catch((e) => { + throw new Error(e.message); + }); + + if (!result.hasOwnProperty("choices") || result.choices.length === 0) + return null; + return result.choices[0].message.content; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.isValidChatCompletionModel(this.model)) + throw new Error( + `xAI chat: ${this.model} is not valid for chat completion!` + ); + + const streamRequest = await this.openai.chat.completions.create({ + model: this.model, + stream: true, + messages, + temperature, + }); + return streamRequest; + } + + 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 = { + XAiLLM, +}; diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index 51dc57553..24f027cff 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -789,6 +789,8 @@ ${this.getHistory({ to: route.to }) return new Providers.LiteLLMProvider({ model: config.model }); case "apipie": return new Providers.ApiPieProvider({ model: config.model }); + case "xai": + return new Providers.XAIProvider({ model: config.model }); default: throw new Error( diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index afaefa1c9..c9925d1cd 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -146,6 +146,14 @@ class Provider { apiKey: process.env.DEEPSEEK_API_KEY ?? null, ...config, }); + case "xai": + return new ChatOpenAI({ + configuration: { + baseURL: "https://api.x.ai/v1", + }, + apiKey: process.env.XAI_LLM_API_KEY ?? null, + ...config, + }); // OSS Model Runners // case "anythingllm_ollama": diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js index f5ae66420..47e2d8716 100644 --- a/server/utils/agents/aibitat/providers/index.js +++ b/server/utils/agents/aibitat/providers/index.js @@ -17,6 +17,7 @@ const FireworksAIProvider = require("./fireworksai.js"); const DeepSeekProvider = require("./deepseek.js"); const LiteLLMProvider = require("./litellm.js"); const ApiPieProvider = require("./apipie.js"); +const XAIProvider = require("./xai.js"); module.exports = { OpenAIProvider, @@ -38,4 +39,5 @@ module.exports = { FireworksAIProvider, LiteLLMProvider, ApiPieProvider, + XAIProvider, }; diff --git a/server/utils/agents/aibitat/providers/xai.js b/server/utils/agents/aibitat/providers/xai.js new file mode 100644 index 000000000..9461d865f --- /dev/null +++ b/server/utils/agents/aibitat/providers/xai.js @@ -0,0 +1,116 @@ +const OpenAI = require("openai"); +const Provider = require("./ai-provider.js"); +const InheritMultiple = require("./helpers/classes.js"); +const UnTooled = require("./helpers/untooled.js"); + +/** + * The agent provider for the xAI provider. + */ +class XAIProvider extends InheritMultiple([Provider, UnTooled]) { + model; + + constructor(config = {}) { + const { model = "grok-beta" } = config; + super(); + const client = new OpenAI({ + baseURL: "https://api.x.ai/v1", + apiKey: process.env.XAI_LLM_API_KEY, + maxRetries: 3, + }); + + 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, + temperature: 0, + messages, + }) + .then((result) => { + if (!result.hasOwnProperty("choices")) + throw new Error("xAI chat: No results!"); + if (result.choices.length === 0) + throw new Error("xAI 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 = null) { + 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. + */ + getCost(_usage) { + return 0; + } +} + +module.exports = XAIProvider; diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index 98caea5cd..fd7d06e8b 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -169,6 +169,10 @@ class AgentHandler { if (!process.env.APIPIE_LLM_API_KEY) throw new Error("ApiPie API Key must be provided to use agents."); break; + case "xai": + if (!process.env.XAI_LLM_API_KEY) + throw new Error("xAI API Key must be provided to use agents."); + break; default: throw new Error( @@ -228,6 +232,8 @@ class AgentHandler { return process.env.LITE_LLM_MODEL_PREF ?? null; case "apipie": return process.env.APIPIE_LLM_MODEL_PREF ?? null; + case "xai": + return process.env.XAI_LLM_MODEL_PREF ?? "grok-beta"; default: return null; } diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index 086144bfe..7ccbf13c7 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -21,6 +21,7 @@ const SUPPORT_CUSTOM_MODELS = [ "groq", "deepseek", "apipie", + "xai", ]; async function getCustomModels(provider = "", apiKey = null, basePath = null) { @@ -60,6 +61,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getDeepSeekModels(apiKey); case "apipie": return await getAPIPieModels(apiKey); + case "xai": + return await getXAIModels(apiKey); default: return { models: [], error: "Invalid provider for custom models" }; } @@ -466,6 +469,36 @@ async function getDeepSeekModels(apiKey = null) { return { models, error: null }; } +async function getXAIModels(_apiKey = null) { + const { OpenAI: OpenAIApi } = require("openai"); + const apiKey = + _apiKey === true + ? process.env.XAI_LLM_API_KEY + : _apiKey || process.env.XAI_LLM_API_KEY || null; + const openai = new OpenAIApi({ + baseURL: "https://api.x.ai/v1", + apiKey, + }); + const models = await openai.models + .list() + .then((results) => results.data) + .catch((e) => { + console.error(`XAI:listModels`, e.message); + return [ + { + created: 1725148800, + id: "grok-beta", + object: "model", + owned_by: "xai", + }, + ]; + }); + + // Api Key was successful so lets save it for future uses + if (models.length > 0 && !!apiKey) process.env.XAI_LLM_API_KEY = apiKey; + return { models, error: null }; +} + module.exports = { getCustomModels, }; diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index f3f19fb9d..84f971cc6 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -165,6 +165,9 @@ function getLLMProvider({ provider = null, model = null } = {}) { case "apipie": const { ApiPieLLM } = require("../AiProviders/apipie"); return new ApiPieLLM(embedder, model); + case "xai": + const { XAiLLM } = require("../AiProviders/xai"); + return new XAiLLM(embedder, model); default: throw new Error( `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` @@ -294,6 +297,9 @@ function getLLMProviderClass({ provider = null } = {}) { case "apipie": const { ApiPieLLM } = require("../AiProviders/apipie"); return ApiPieLLM; + case "xai": + const { XAiLLM } = require("../AiProviders/xai"); + return XAiLLM; default: return null; } diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 202ffcd99..d705fb730 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -539,6 +539,16 @@ const KEY_MAPPING = { envKey: "APIPIE_LLM_MODEL_PREF", checks: [isNotEmpty], }, + + // xAI Options + XAIApiKey: { + envKey: "XAI_LLM_API_KEY", + checks: [isNotEmpty], + }, + XAIModelPref: { + envKey: "XAI_LLM_MODEL_PREF", + checks: [isNotEmpty], + }, }; function isNotEmpty(input = "") { @@ -643,6 +653,7 @@ function supportedLLM(input = "") { "bedrock", "deepseek", "apipie", + "xai", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; }