diff --git a/README.md b/README.md index 20942f7e..18cebae7 100644 --- a/README.md +++ b/README.md @@ -38,17 +38,17 @@ Hexabot - Create exceptional chatbot experiences. 100% Open Source. | Product Hunt ## Features -- **Analytics Dashboard:** Monitor chatbot interactions and performance with insightful metrics and visualizations. +- **LLMs & NLU Support:** Integrate with your favorite LLM model whether it's by using Ollama, ChatGPT, Mistral or Gemini ... Manage training datasets for machine learning models that detect user intent and language, providing intelligent responses. - **Multi-Channel Support:** Create consistent chatbot experiences across multiple channels like web, mobile, and social media platforms. - **Visual Editor:** Design and manage chatbot flows with an intuitive drag-and-drop interface. Supports text messages, quick replies, carousels, and more. - **Plugin System:** Extend Hexabot's functionality by developing custom plugins. Enable features like text-to-action responses, 3rd party system integrations, and more. -- **NLU (Natural Language Understanding) Management:** Manage training datasets for machine learning models that detect user intent and language, providing intelligent responses. - **Multi-lingual Support:** Define multiple languages, allowing the chatbot to interact with users in their preferred language. - **Knowledge Base:** Seamlessly integrate and manage dynamic content such as product catalogs and store lists for more engaging conversations. - **User Roles & Permissions:** Granular access control to manage user roles and permissions for different parts of the system. - **Contextual Data:** Define variables to collect and leverage relevant information about end-users to deliver personalized responses. - **Subscribers & Labels:** Organize users by assigning labels and customize their chat experience based on defined segments. - **Inbox & Handover:** Provides a real-time chat window where conversations can be monitored and handed over to human agents when necessary. +- **Analytics Dashboard:** Monitor chatbot interactions and performance with insightful metrics and visualizations. ## Directory Structure @@ -99,10 +99,10 @@ or for development mode: $ npx hexabot dev ``` -You can also enable services such as the NLU engine or Nginx : +You can also enable services such as the NLU engine and Ollama (The services are declared under the `./docker` folder) : ```bash -$ npx hexabot --enable=nlu +$ npx hexabot dev --enable=ollama,nlu ``` **Note:** The first time you run the app, Docker will take some time to download all the required images. diff --git a/api/package-lock.json b/api/package-lock.json index 126fff02..ab250f55 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -43,6 +43,7 @@ "nestjs-dynamic-providers": "^0.3.4", "nestjs-i18n": "^10.4.0", "nodemailer": "^6.9.13", + "ollama": "^0.5.9", "papaparse": "^5.4.1", "passport": "^0.6.0", "passport-anonymous": "^1.0.1", @@ -14879,6 +14880,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ollama": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz", + "integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -19059,6 +19068,11 @@ "node": ">=0.10.0" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", diff --git a/api/package.json b/api/package.json index e3e0aacd..c8017466 100644 --- a/api/package.json +++ b/api/package.json @@ -64,6 +64,7 @@ "nestjs-dynamic-providers": "^0.3.4", "nestjs-i18n": "^10.4.0", "nodemailer": "^6.9.13", + "ollama": "^0.5.9", "papaparse": "^5.4.1", "passport": "^0.6.0", "passport-anonymous": "^1.0.1", diff --git a/api/src/extensions/helpers/ollama/i18n/en/help.json b/api/src/extensions/helpers/ollama/i18n/en/help.json new file mode 100644 index 00000000..d9f39816 --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/en/help.json @@ -0,0 +1,23 @@ +{ + "api_url": "URL of the Ollama server.", + "model": "Determines which model to run. You need to ensure to pull the model in Ollama to be able to use it.", + "keep_alive": "Time to keep the model in memory.", + "max_messages_ctx": "Number of messages to include in the context.", + "context": "Provide context to the assistant (e.g., You are an AI assistant).", + "instructions": "Instructions to give to the assistant.", + "fallback_message": "Message to return in case there is an API error.", + "mirostat": "Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0)", + "mirostat_eta": "Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1)", + "mirostat_tau": "Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0)", + "num_ctx": "Sets the size of the context window used to generate the next token. (Default: 2048)", + "repeat_last_n": "Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)", + "repeat_penalty": "Sets how strongly to penalize repetitions. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1)", + "temperature": "The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)", + "seed": "Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0)", + "stop": "Sets the stop sequences to use. When this pattern is encountered the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate `stop` parameters in a modelfile.", + "tfs_z": "Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)", + "num_predict": "Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context)", + "top_k": "Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)", + "top_p": "Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)", + "min_p": "Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter *p* represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with *p*=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)" +} diff --git a/api/src/extensions/helpers/ollama/i18n/en/label.json b/api/src/extensions/helpers/ollama/i18n/en/label.json new file mode 100644 index 00000000..d78009aa --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/en/label.json @@ -0,0 +1,23 @@ +{ + "api_url": "API URL", + "model": "Model", + "keep_alive": "Keep Alive", + "max_messages_ctx": "Max Context Messages", + "context": "Context", + "instructions": "Instructions", + "fallback_message": "Fallback Message", + "mirostat": "Mirostat", + "mirostat_eta": "Mirostat Eta", + "mirostat_tau": "Mirostat Tau", + "num_ctx": "Context Window Size", + "repeat_last_n": "Repeat Last N", + "repeat_penalty": "Repeat Penalty", + "temperature": "Temperature", + "seed": "Seed", + "stop": "Stop", + "tfs_z": "TFS Z", + "num_predict": "Maximum number of tokens", + "top_k": "Top K", + "top_p": "Top P", + "min_p": "Min P" +} diff --git a/api/src/extensions/helpers/ollama/i18n/en/title.json b/api/src/extensions/helpers/ollama/i18n/en/title.json new file mode 100644 index 00000000..85109553 --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/en/title.json @@ -0,0 +1,3 @@ +{ + "ollama": "Ollama" +} diff --git a/api/src/extensions/helpers/ollama/i18n/fr/help.json b/api/src/extensions/helpers/ollama/i18n/fr/help.json new file mode 100644 index 00000000..f4f0e3ba --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/fr/help.json @@ -0,0 +1,23 @@ +{ + "api_url": "Adresse URL du serveur Ollama.", + "model": "Détermine le modèle à utiliser. Assurez-vous de charger le modèle sur Ollama pour pouvoir l'utiliser.", + "keep_alive": "Temps pendant lequel le modèle reste en mémoire.", + "max_messages_ctx": "Nombre maximum de messages à inclure dans le contexte.", + "context": "Fournit un contexte à l'assistant (par exemple : Vous êtes un assistant IA).", + "instructions": "Instructions à donner à l'assistant.", + "fallback_message": "Message à retourner en cas d'erreur API.", + "mirostat": "Active le prélèvement de Mirostat pour contrôler la perplexité. (par défaut : 0, 0 = désactivé, 1 = Mirostat, 2 = Mirostat 2.0)", + "mirostat_eta": "Influence la rapidité de réaction de l'algorithme aux retours du texte généré. Un taux d'apprentissage plus bas entraînera des ajustements plus lents, tandis qu'un taux plus élevé rendra l'algorithme plus réactif. (Par défaut : 0.1)", + "mirostat_tau": "Contrôle l'équilibre entre la cohérence et la diversité de la sortie. Une valeur plus basse résulte en un texte plus concentré et cohérent. (Par défaut : 5.0)", + "num_ctx": "Définit la taille de la fenêtre de contexte utilisée pour générer le prochain jeton. (Par défaut : 2048)", + "repeat_last_n": "Définit jusqu'où le modèle doit regarder en arrière pour éviter la répétition. (Par défaut : 64, 0 = désactivé, -1 = num_ctx)", + "repeat_penalty": "Définit la force de la pénalité pour les répétitions. Une valeur plus élevée (par exemple, 1.5) pénalisera plus fortement les répétitions, tandis qu'une valeur plus basse (par exemple, 0.9) sera plus clémente. (Par défaut : 1.1)", + "temperature": "La température du modèle. Augmenter la température rendra le modèle plus créatif. (Par défaut : 0.8)", + "seed": "Définit la graine de nombre aléatoire à utiliser pour la génération. Fixer ce numéro permettra au modèle de générer le même texte pour la même invite. (Par défaut : 0)", + "stop": "Définit les séquences d'arrêt à utiliser. Lorsque ce motif est rencontré, le modèle cessera de générer du texte et retournera. Plusieurs motifs d'arrêt peuvent être définis en spécifiant plusieurs paramètres `stop` séparés dans un fichier de modèle.", + "tfs_z": "L'échantillonnage sans queue est utilisé pour réduire l'impact des jetons moins probables dans la sortie. Une valeur plus élevée (par exemple, 2.0) réduira davantage l'impact, tandis qu'une valeur de 1.0 désactive ce paramètre. (par défaut : 1)", + "num_predict": "Nombre maximum de jetons à prédire lors de la génération de texte. (Par défaut : 128, -1 = génération infinie, -2 = remplir le contexte)", + "top_k": "Réduit la probabilité de générer des non-sens. Une valeur plus élevée (par exemple, 100) donnera des réponses plus diverses, tandis qu'une valeur plus basse (par exemple, 10) sera plus conservatrice. (Par défaut : 40)", + "top_p": "Fonctionne conjointement avec top-k. Une valeur plus élevée (par exemple, 0.95) conduira à un texte plus diversifié, tandis qu'une valeur plus basse (par exemple, 0.5) générera un texte plus concentré et conservateur. (Par défaut : 0.9)", + "min_p": "Alternative au top_p, et vise à assurer un équilibre entre la qualité et la variété. Le paramètre *p* représente la probabilité minimum pour qu'un jeton soit considéré, par rapport à la probabilité du jeton le plus probable. Par exemple, avec *p* = 0.05 et le jeton le plus probable ayant une probabilité de 0.9, les logits d'une valeur inférieure à 0.045 sont filtrés. (Par défaut : 0.0)" +} diff --git a/api/src/extensions/helpers/ollama/i18n/fr/label.json b/api/src/extensions/helpers/ollama/i18n/fr/label.json new file mode 100644 index 00000000..f6522305 --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/fr/label.json @@ -0,0 +1,23 @@ +{ + "api_url": "URL de l'API", + "model": "Modèle", + "keep_alive": "Maintien en Vie", + "max_messages_ctx": "Nombre Maximum de Messages", + "context": "Contexte", + "instructions": "Instructions", + "fallback_message": "Message de Secours", + "mirostat": "Mirostat", + "mirostat_eta": "Mirostat Eta", + "mirostat_tau": "Mirostat Tau", + "num_ctx": "Num Ctx", + "repeat_last_n": "Répéter Dernier N", + "repeat_penalty": "Pénalité de Répétition", + "temperature": "Température", + "seed": "Graine", + "stop": "Arrêt", + "tfs_z": "TFS Z", + "num_predict": "Nombre de Tokens", + "top_k": "Top K", + "top_p": "Top P", + "min_p": "Min P" +} diff --git a/api/src/extensions/helpers/ollama/i18n/fr/title.json b/api/src/extensions/helpers/ollama/i18n/fr/title.json new file mode 100644 index 00000000..85109553 --- /dev/null +++ b/api/src/extensions/helpers/ollama/i18n/fr/title.json @@ -0,0 +1,3 @@ +{ + "ollama": "Ollama" +} diff --git a/api/src/extensions/helpers/ollama/index.d.ts b/api/src/extensions/helpers/ollama/index.d.ts new file mode 100644 index 00000000..e922cc4f --- /dev/null +++ b/api/src/extensions/helpers/ollama/index.d.ts @@ -0,0 +1,14 @@ +import { OLLAMA_HELPER_GROUP, OLLAMA_HELPER_SETTINGS } from './settings'; + +declare global { + interface Settings extends SettingTree {} +} + +declare module '@nestjs/event-emitter' { + interface IHookExtensionsOperationMap { + [OLLAMA_HELPER_GROUP]: TDefinition< + object, + SettingMapByType + >; + } +} diff --git a/api/src/extensions/helpers/ollama/index.helper.ts b/api/src/extensions/helpers/ollama/index.helper.ts new file mode 100644 index 00000000..f1d14fc4 --- /dev/null +++ b/api/src/extensions/helpers/ollama/index.helper.ts @@ -0,0 +1,127 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Ollama } from 'ollama'; + +import { AnyMessage } from '@/chat/schemas/types/message'; +import { HelperService } from '@/helper/helper.service'; +import BaseLlmHelper from '@/helper/lib/base-llm-helper'; +import { LoggerService } from '@/logger/logger.service'; +import { Setting } from '@/setting/schemas/setting.schema'; +import { SettingService } from '@/setting/services/setting.service'; + +import { OLLAMA_HELPER_NAME, OLLAMA_HELPER_SETTINGS } from './settings'; + +@Injectable() +export default class OllamaLlmHelper + extends BaseLlmHelper + implements OnApplicationBootstrap +{ + private client: Ollama; + + /** + * Instantiate the LLM helper + * + * @param logger - Logger service + */ + constructor( + settingService: SettingService, + helperService: HelperService, + protected readonly logger: LoggerService, + ) { + super( + OLLAMA_HELPER_NAME, + OLLAMA_HELPER_SETTINGS, + settingService, + helperService, + logger, + ); + } + + async onApplicationBootstrap() { + const settings = await this.getSettings(); + + this.client = new Ollama({ host: settings.api_url }); + } + + @OnEvent('hook:ollama:api_url') + handleApiUrlChange(setting: Setting) { + this.client = new Ollama({ host: setting.value }); + } + + /** + * Generates a response using LLM + * + * @param prompt - The input text from the user + * @param model - The model to be used + * @returns {Promise} - The generated response from the LLM + */ + async generateResponse(prompt: string, model: string): Promise { + const response = await this.client.generate({ + model, + prompt, + }); + + return response.response ? response.response : ''; + } + + /** + * Formats messages to the Ollama required data structure + * + * @param messages - Message history to include + * + * @returns Ollama message array + */ + private formatMessages(messages: AnyMessage[]) { + return messages.map((m) => { + return { + role: 'sender' in m && m.sender ? 'user' : 'assistant', + content: 'text' in m.message && m.message.text ? m.message.text : '', + }; + }); + } + + /** + * Send a chat completion request with the conversation history. + * You can use this same approach to start the conversation + * using multi-shot or chain-of-thought prompting. + * + * @param prompt - The input text from the user + * @param model - The model to be used + * @param history - Array of messages + * @returns {Promise} - The generated response from the LLM + */ + public async generateChatCompletion( + prompt: string, + model: string, + systemPrompt: string, + history: AnyMessage[] = [], + { keepAlive = '5m', options = {} }, + ) { + const response = await this.client.chat({ + model, + messages: [ + { + role: 'system', + content: systemPrompt, + }, + ...this.formatMessages(history), + { + role: 'user', + content: prompt, + }, + ], + keep_alive: keepAlive, + options, + }); + + return response.message.content ? response.message.content : ''; + } +} diff --git a/api/src/extensions/helpers/ollama/package.json b/api/src/extensions/helpers/ollama/package.json new file mode 100644 index 00000000..cebb7841 --- /dev/null +++ b/api/src/extensions/helpers/ollama/package.json @@ -0,0 +1,10 @@ +{ + "name": "hexabot-helper-ollama", + "version": "2.0.0", + "description": "The Ollama Helper Extension for Hexabot Chatbot / Agent Builder to enable the LLM Capability", + "dependencies": { + "ollama": "^0.5.9" + }, + "author": "Hexastack", + "license": "AGPL-3.0-only" +} \ No newline at end of file diff --git a/api/src/extensions/helpers/ollama/settings.ts b/api/src/extensions/helpers/ollama/settings.ts new file mode 100644 index 00000000..0d234134 --- /dev/null +++ b/api/src/extensions/helpers/ollama/settings.ts @@ -0,0 +1,125 @@ +import { HelperSetting } from '@/helper/types'; +import { SettingType } from '@/setting/schemas/types'; + +export const OLLAMA_HELPER_NAME = 'ollama'; + +export const OLLAMA_HELPER_GROUP = 'ollama'; + +export const OLLAMA_HELPER_SETTINGS = [ + { + label: 'api_url', + group: OLLAMA_HELPER_GROUP, + type: SettingType.text, + value: 'http://ollama:11434', // Default value + }, + { + label: 'model', + group: OLLAMA_HELPER_GROUP, + type: SettingType.text, + value: 'llama3.2', // Default model + }, + { + label: 'keep_alive', + group: OLLAMA_HELPER_GROUP, + type: SettingType.text, + value: '5m', // Default value for keeping the model in memory + }, + { + label: 'mirostat', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0, // Default: disabled + }, + { + label: 'mirostat_eta', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0.1, // Default value + }, + { + label: 'mirostat_tau', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 5.0, // Default value + }, + { + label: 'num_ctx', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 2048, // Default value + }, + { + label: 'repeat_last_n', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 64, // Default value + }, + { + label: 'repeat_penalty', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 1.1, // Default value + }, + { + label: 'temperature', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0.8, // Default value + }, + { + label: 'seed', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0, // Default value + }, + { + label: 'stop', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.text, + value: 'AI assistant:', // Default stop sequence + }, + { + label: 'tfs_z', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 1, // Default value, 1.0 means disabled + }, + { + label: 'num_predict', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 20, // Default value + }, + { + label: 'top_k', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 40, // Default value + }, + { + label: 'top_p', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0.9, // Default value + }, + { + label: 'min_p', + group: OLLAMA_HELPER_GROUP, + subgroup: 'options', + type: SettingType.number, + value: 0.0, // Default value + }, +] as const satisfies HelperSetting[]; diff --git a/api/src/extensions/plugins/ollama/i18n/en/help.json b/api/src/extensions/plugins/ollama/i18n/en/help.json new file mode 100644 index 00000000..d9f39816 --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/en/help.json @@ -0,0 +1,23 @@ +{ + "api_url": "URL of the Ollama server.", + "model": "Determines which model to run. You need to ensure to pull the model in Ollama to be able to use it.", + "keep_alive": "Time to keep the model in memory.", + "max_messages_ctx": "Number of messages to include in the context.", + "context": "Provide context to the assistant (e.g., You are an AI assistant).", + "instructions": "Instructions to give to the assistant.", + "fallback_message": "Message to return in case there is an API error.", + "mirostat": "Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0)", + "mirostat_eta": "Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1)", + "mirostat_tau": "Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0)", + "num_ctx": "Sets the size of the context window used to generate the next token. (Default: 2048)", + "repeat_last_n": "Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)", + "repeat_penalty": "Sets how strongly to penalize repetitions. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1)", + "temperature": "The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)", + "seed": "Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0)", + "stop": "Sets the stop sequences to use. When this pattern is encountered the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate `stop` parameters in a modelfile.", + "tfs_z": "Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)", + "num_predict": "Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context)", + "top_k": "Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)", + "top_p": "Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)", + "min_p": "Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter *p* represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with *p*=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)" +} diff --git a/api/src/extensions/plugins/ollama/i18n/en/label.json b/api/src/extensions/plugins/ollama/i18n/en/label.json new file mode 100644 index 00000000..d78009aa --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/en/label.json @@ -0,0 +1,23 @@ +{ + "api_url": "API URL", + "model": "Model", + "keep_alive": "Keep Alive", + "max_messages_ctx": "Max Context Messages", + "context": "Context", + "instructions": "Instructions", + "fallback_message": "Fallback Message", + "mirostat": "Mirostat", + "mirostat_eta": "Mirostat Eta", + "mirostat_tau": "Mirostat Tau", + "num_ctx": "Context Window Size", + "repeat_last_n": "Repeat Last N", + "repeat_penalty": "Repeat Penalty", + "temperature": "Temperature", + "seed": "Seed", + "stop": "Stop", + "tfs_z": "TFS Z", + "num_predict": "Maximum number of tokens", + "top_k": "Top K", + "top_p": "Top P", + "min_p": "Min P" +} diff --git a/api/src/extensions/plugins/ollama/i18n/en/title.json b/api/src/extensions/plugins/ollama/i18n/en/title.json new file mode 100644 index 00000000..85109553 --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/en/title.json @@ -0,0 +1,3 @@ +{ + "ollama": "Ollama" +} diff --git a/api/src/extensions/plugins/ollama/i18n/fr/help.json b/api/src/extensions/plugins/ollama/i18n/fr/help.json new file mode 100644 index 00000000..f4f0e3ba --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/fr/help.json @@ -0,0 +1,23 @@ +{ + "api_url": "Adresse URL du serveur Ollama.", + "model": "Détermine le modèle à utiliser. Assurez-vous de charger le modèle sur Ollama pour pouvoir l'utiliser.", + "keep_alive": "Temps pendant lequel le modèle reste en mémoire.", + "max_messages_ctx": "Nombre maximum de messages à inclure dans le contexte.", + "context": "Fournit un contexte à l'assistant (par exemple : Vous êtes un assistant IA).", + "instructions": "Instructions à donner à l'assistant.", + "fallback_message": "Message à retourner en cas d'erreur API.", + "mirostat": "Active le prélèvement de Mirostat pour contrôler la perplexité. (par défaut : 0, 0 = désactivé, 1 = Mirostat, 2 = Mirostat 2.0)", + "mirostat_eta": "Influence la rapidité de réaction de l'algorithme aux retours du texte généré. Un taux d'apprentissage plus bas entraînera des ajustements plus lents, tandis qu'un taux plus élevé rendra l'algorithme plus réactif. (Par défaut : 0.1)", + "mirostat_tau": "Contrôle l'équilibre entre la cohérence et la diversité de la sortie. Une valeur plus basse résulte en un texte plus concentré et cohérent. (Par défaut : 5.0)", + "num_ctx": "Définit la taille de la fenêtre de contexte utilisée pour générer le prochain jeton. (Par défaut : 2048)", + "repeat_last_n": "Définit jusqu'où le modèle doit regarder en arrière pour éviter la répétition. (Par défaut : 64, 0 = désactivé, -1 = num_ctx)", + "repeat_penalty": "Définit la force de la pénalité pour les répétitions. Une valeur plus élevée (par exemple, 1.5) pénalisera plus fortement les répétitions, tandis qu'une valeur plus basse (par exemple, 0.9) sera plus clémente. (Par défaut : 1.1)", + "temperature": "La température du modèle. Augmenter la température rendra le modèle plus créatif. (Par défaut : 0.8)", + "seed": "Définit la graine de nombre aléatoire à utiliser pour la génération. Fixer ce numéro permettra au modèle de générer le même texte pour la même invite. (Par défaut : 0)", + "stop": "Définit les séquences d'arrêt à utiliser. Lorsque ce motif est rencontré, le modèle cessera de générer du texte et retournera. Plusieurs motifs d'arrêt peuvent être définis en spécifiant plusieurs paramètres `stop` séparés dans un fichier de modèle.", + "tfs_z": "L'échantillonnage sans queue est utilisé pour réduire l'impact des jetons moins probables dans la sortie. Une valeur plus élevée (par exemple, 2.0) réduira davantage l'impact, tandis qu'une valeur de 1.0 désactive ce paramètre. (par défaut : 1)", + "num_predict": "Nombre maximum de jetons à prédire lors de la génération de texte. (Par défaut : 128, -1 = génération infinie, -2 = remplir le contexte)", + "top_k": "Réduit la probabilité de générer des non-sens. Une valeur plus élevée (par exemple, 100) donnera des réponses plus diverses, tandis qu'une valeur plus basse (par exemple, 10) sera plus conservatrice. (Par défaut : 40)", + "top_p": "Fonctionne conjointement avec top-k. Une valeur plus élevée (par exemple, 0.95) conduira à un texte plus diversifié, tandis qu'une valeur plus basse (par exemple, 0.5) générera un texte plus concentré et conservateur. (Par défaut : 0.9)", + "min_p": "Alternative au top_p, et vise à assurer un équilibre entre la qualité et la variété. Le paramètre *p* représente la probabilité minimum pour qu'un jeton soit considéré, par rapport à la probabilité du jeton le plus probable. Par exemple, avec *p* = 0.05 et le jeton le plus probable ayant une probabilité de 0.9, les logits d'une valeur inférieure à 0.045 sont filtrés. (Par défaut : 0.0)" +} diff --git a/api/src/extensions/plugins/ollama/i18n/fr/label.json b/api/src/extensions/plugins/ollama/i18n/fr/label.json new file mode 100644 index 00000000..f6522305 --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/fr/label.json @@ -0,0 +1,23 @@ +{ + "api_url": "URL de l'API", + "model": "Modèle", + "keep_alive": "Maintien en Vie", + "max_messages_ctx": "Nombre Maximum de Messages", + "context": "Contexte", + "instructions": "Instructions", + "fallback_message": "Message de Secours", + "mirostat": "Mirostat", + "mirostat_eta": "Mirostat Eta", + "mirostat_tau": "Mirostat Tau", + "num_ctx": "Num Ctx", + "repeat_last_n": "Répéter Dernier N", + "repeat_penalty": "Pénalité de Répétition", + "temperature": "Température", + "seed": "Graine", + "stop": "Arrêt", + "tfs_z": "TFS Z", + "num_predict": "Nombre de Tokens", + "top_k": "Top K", + "top_p": "Top P", + "min_p": "Min P" +} diff --git a/api/src/extensions/plugins/ollama/i18n/fr/title.json b/api/src/extensions/plugins/ollama/i18n/fr/title.json new file mode 100644 index 00000000..85109553 --- /dev/null +++ b/api/src/extensions/plugins/ollama/i18n/fr/title.json @@ -0,0 +1,3 @@ +{ + "ollama": "Ollama" +} diff --git a/api/src/extensions/plugins/ollama/index.plugin.ts b/api/src/extensions/plugins/ollama/index.plugin.ts new file mode 100644 index 00000000..bcf0ea10 --- /dev/null +++ b/api/src/extensions/plugins/ollama/index.plugin.ts @@ -0,0 +1,112 @@ +import { Injectable } from '@nestjs/common'; + +import { Block } from '@/chat/schemas/block.schema'; +import { Context } from '@/chat/schemas/types/context'; +import { + OutgoingMessageFormat, + StdOutgoingTextEnvelope, +} from '@/chat/schemas/types/message'; +import { MessageService } from '@/chat/services/message.service'; +import { ContentService } from '@/cms/services/content.service'; +import OllamaLlmHelper from '@/extensions/helpers/ollama/index.helper'; +import { HelperService } from '@/helper/helper.service'; +import { HelperType } from '@/helper/types'; +import { LoggerService } from '@/logger/logger.service'; +import { BaseBlockPlugin } from '@/plugins/base-block-plugin'; +import { PluginService } from '@/plugins/plugins.service'; + +import { OLLAMA_PLUGIN_SETTINGS } from './settings'; + +@Injectable() +export class OllamaPlugin extends BaseBlockPlugin< + typeof OLLAMA_PLUGIN_SETTINGS +> { + public readonly settings = OLLAMA_PLUGIN_SETTINGS; + + constructor( + pluginService: PluginService, + private helperService: HelperService, + private logger: LoggerService, + private contentService: ContentService, + private messageService: MessageService, + ) { + super('ollama', OLLAMA_PLUGIN_SETTINGS, pluginService); + + this.template = { name: 'Ollama Plugin' }; + this.effects = { + onStoreContextData: () => {}, + }; + } + + async process(block: Block, context: Context, _convId: string) { + const args = this.getArguments(block); + + try { + const ragContent = await this.contentService.textSearch(context.text); + + const systemPrompt = [ + `CONTEXT: ${args.context}`, + `DOCUMENTS:`, + ...ragContent.map( + (curr, index) => + `\tDOCUMENT ${index + 1} \n\t\tTitle: ${curr.title} \n\t\tData: ${curr.rag}`, + ), + `INSTRUCTIONS:`, + args.instructions, + ].join('\n'); + + this.logger.debug('Ollama: Prompt', systemPrompt); + + const ollamaHelper = this.helperService.use( + HelperType.LLM, + OllamaLlmHelper, + ); + + const history = await this.messageService.findLastMessages( + context.user, + args.max_messages_ctx, + ); + + const options = this.settings + .filter( + (setting) => 'subgroup' in setting && setting.subgroup === 'options', + ) + .reduce((acc, { label }) => { + acc[label] = args[label]; + return acc; + }, {}); + + // Call Ollama API + const result = await ollamaHelper.generateChatCompletion( + context.text, + args.model, + systemPrompt, + history, + { + keepAlive: args.keep_alive, + options, + }, + ); + + const envelope: StdOutgoingTextEnvelope = { + format: OutgoingMessageFormat.text, + message: { + text: result, + }, + }; + + return envelope; + } catch (err) { + this.logger.error('Ollama Plugin: Something went wrong ...'); + // Send fallback message + const envelope: StdOutgoingTextEnvelope = { + format: OutgoingMessageFormat.text, + message: { + text: args.fallback_message, + }, + }; + + return envelope; + } + } +} diff --git a/api/src/extensions/plugins/ollama/package.json b/api/src/extensions/plugins/ollama/package.json new file mode 100644 index 00000000..049ce581 --- /dev/null +++ b/api/src/extensions/plugins/ollama/package.json @@ -0,0 +1,11 @@ +{ + "name": "hexabot-ollama", + "version": "2.0.0", + "description": "The Ollama Plugin Extension for Hexabot Chatbot / Agent Builder that provides a custom block for Generative AI + RAG", + "dependencies": {}, + "extensions": { + "hexabot-helper-ollama": "2.0.0" + }, + "author": "Hexastack", + "license": "AGPL-3.0-only" +} diff --git a/api/src/extensions/plugins/ollama/settings.ts b/api/src/extensions/plugins/ollama/settings.ts new file mode 100644 index 00000000..4289d1cc --- /dev/null +++ b/api/src/extensions/plugins/ollama/settings.ts @@ -0,0 +1,125 @@ +import { PluginSetting } from '@/plugins/types'; +import { SettingType } from '@/setting/schemas/types'; + +export const OLLAMA_PLUGIN_SETTINGS = [ + { + label: 'model', + group: 'default', + type: SettingType.text, + value: 'llama3.2', // Default model + }, + { + label: 'keep_alive', + group: 'default', + type: SettingType.text, + value: '5m', // Default value for keeping the model in memory + }, + { + label: 'max_messages_ctx', + group: 'default', + type: SettingType.number, + value: 5, // Default number of messages to retrieve for context + }, + { + label: 'context', + group: 'default', + type: SettingType.text, + value: `You are an AI Assistant that works for Hexastack, the IT company behind Hexabot the chatbot builder.`, // Default value for keeping the model in memory + }, + { + label: 'instructions', + group: 'default', + type: SettingType.textarea, + value: `Answer the user QUESTION using the DOCUMENTS text above. Keep your answer ground in the facts of the DOCUMENT. If the DOCUMENT doesn’t contain the facts to answer the QUESTION, apologize and try to give an answer that promotes the company and its values. DO NOT SAY ANYTHING ABOUT THESE DOCUMENTS, nor their EXISTENCE.`, + }, + { + label: 'fallback_message', + group: 'default', + type: SettingType.textarea, + value: `Something went wrong ... please try again later.`, + }, + { + label: 'mirostat', + group: 'options', + type: SettingType.number, + value: 0, // Default: disabled + }, + { + label: 'mirostat_eta', + group: 'options', + type: SettingType.number, + value: 0.1, // Default value + }, + { + label: 'mirostat_tau', + group: 'options', + type: SettingType.number, + value: 5.0, // Default value + }, + { + label: 'num_ctx', + group: 'options', + type: SettingType.number, + value: 2048, // Default value + }, + { + label: 'repeat_last_n', + group: 'options', + type: SettingType.number, + value: 64, // Default value + }, + { + label: 'repeat_penalty', + group: 'options', + type: SettingType.number, + value: 1.1, // Default value + }, + { + label: 'temperature', + group: 'options', + type: SettingType.number, + value: 0.8, // Default value + }, + { + label: 'seed', + group: 'options', + type: SettingType.number, + value: 0, // Default value + }, + { + label: 'stop', + group: 'options', + type: SettingType.text, + value: 'AI assistant:', // Default stop sequence + }, + { + label: 'tfs_z', + group: 'options', + type: SettingType.number, + value: 1, // Default value, 1.0 means disabled + }, + { + label: 'num_predict', + group: 'options', + type: SettingType.number, + value: 20, // Default value + }, + { + label: 'top_k', + group: 'options', + type: SettingType.number, + value: 40, // Default value + }, + { + label: 'top_p', + group: 'options', + type: SettingType.number, + value: 0.9, // Default value + }, + { + label: 'min_p', + group: 'options', + type: SettingType.number, + value: 0.0, // Default value + }, +] as const satisfies PluginSetting[]; diff --git a/api/src/helper/helper.service.ts b/api/src/helper/helper.service.ts index 8b8e53d7..ddd07d9d 100644 --- a/api/src/helper/helper.service.ts +++ b/api/src/helper/helper.service.ts @@ -67,6 +67,29 @@ export class HelperService { return Array.from(helpers.values()); } + /** + * Get a helper by class. + * + * @param type - The type of helper. + * @param name - The helper's name. + * + * @returns - The helper + */ + public use< + T extends HelperType, + C extends new (...args: any[]) => TypeOfHelper, + >(type: T, cls: C) { + const helpers = this.getAllByType(type); + + const helper = helpers.find((h) => h instanceof cls); + + if (!helper) { + throw new Error(`Unable to find the requested helper`); + } + + return helper as InstanceType; + } + /** * Get default NLU helper. * @@ -86,4 +109,24 @@ export class HelperService { return defaultHelper; } + + /** + * Get default LLM helper. + * + * @returns - The helper + */ + async getDefaultLlmHelper() { + const settings = await this.settingService.getSettings(); + + const defaultHelper = this.get( + HelperType.LLM, + settings.chatbot_settings.default_llm_helper, + ); + + if (!defaultHelper) { + throw new Error(`Unable to find default LLM helper`); + } + + return defaultHelper; + } } diff --git a/api/src/helper/lib/base-llm-helper.ts b/api/src/helper/lib/base-llm-helper.ts new file mode 100644 index 00000000..413e8598 --- /dev/null +++ b/api/src/helper/lib/base-llm-helper.ts @@ -0,0 +1,59 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { AnyMessage } from '@/chat/schemas/types/message'; +import { LoggerService } from '@/logger/logger.service'; +import { SettingService } from '@/setting/services/setting.service'; + +import { HelperService } from '../helper.service'; +import { HelperSetting, HelperType } from '../types'; + +import BaseHelper from './base-helper'; + +export default abstract class BaseLlmHelper< + N extends string, +> extends BaseHelper { + protected readonly type: HelperType = HelperType.LLM; + + constructor( + name: N, + settings: HelperSetting[], + settingService: SettingService, + helperService: HelperService, + logger: LoggerService, + ) { + super(name, settings, settingService, helperService, logger); + } + + /** + * Generates a response using LLM + * + * @param prompt - The input text from the user + * @param model - The model to be used + * @returns {Promise} - The generated response from the LLM + */ + abstract generateResponse(prompt: string, model: string): Promise; + + /** + * Send a chat completion request with the conversation history. + * You can use this same approach to start the conversation + * using multi-shot or chain-of-thought prompting. + * + * @param prompt - The input text from the user + * @param model - The model to be used + * @param history - Array of messages + * @returns {Promise} - The generated response from the LLM + */ + abstract generateChatCompletion( + prompt: string, + model: string, + systemPrompt?: string, + history?: AnyMessage[], + extra?: any, + ): Promise; +} diff --git a/api/src/helper/lib/base-nlp-helper.ts b/api/src/helper/lib/base-nlp-helper.ts index cf07c5f9..302cdd17 100644 --- a/api/src/helper/lib/base-nlp-helper.ts +++ b/api/src/helper/lib/base-nlp-helper.ts @@ -27,7 +27,6 @@ import { HelperSetting, HelperType, Nlp } from '../types'; import BaseHelper from './base-helper'; -// eslint-disable-next-line prettier/prettier export default abstract class BaseNlpHelper< N extends string, > extends BaseHelper { diff --git a/api/src/helper/types.ts b/api/src/helper/types.ts index e8104742..e3820520 100644 --- a/api/src/helper/types.ts +++ b/api/src/helper/types.ts @@ -1,6 +1,7 @@ import { SettingCreateDto } from '@/setting/dto/setting.dto'; import BaseHelper from './lib/base-helper'; +import BaseLlmHelper from './lib/base-llm-helper'; import BaseNlpHelper from './lib/base-nlp-helper'; export namespace Nlp { @@ -24,12 +25,15 @@ export namespace Nlp { export enum HelperType { NLU = 'nlu', + LLM = 'llm', UTIL = 'util', } -export type TypeOfHelper = T extends HelperType.NLU - ? BaseNlpHelper - : BaseHelper; +export type TypeOfHelper = T extends HelperType.LLM + ? BaseLlmHelper + : T extends HelperType.NLU + ? BaseNlpHelper + : BaseHelper; export type HelperRegistry = Map< HelperType, diff --git a/api/src/setting/seeds/setting.seed-model.ts b/api/src/setting/seeds/setting.seed-model.ts index 15816ca9..cd999cdf 100644 --- a/api/src/setting/seeds/setting.seed-model.ts +++ b/api/src/setting/seeds/setting.seed-model.ts @@ -26,17 +26,24 @@ export const DEFAULT_SETTINGS = [ }, { group: 'chatbot_settings', - label: 'global_fallback', - value: true, - type: SettingType.checkbox, - weight: 3, + label: 'default_llm_helper', + value: 'ollama', + type: SettingType.select, + config: { + multiple: false, + allowCreate: false, + entity: 'Helper', + idKey: 'name', + labelKey: 'name', + }, + weight: 2, }, { group: 'chatbot_settings', label: 'global_fallback', value: true, type: SettingType.checkbox, - weight: 4, + weight: 3, }, { group: 'chatbot_settings', @@ -51,7 +58,7 @@ export const DEFAULT_SETTINGS = [ idKey: 'id', labelKey: 'name', }, - weight: 5, + weight: 4, }, { group: 'chatbot_settings', @@ -61,7 +68,7 @@ export const DEFAULT_SETTINGS = [ "I'm really sorry but i don't quite understand what you are saying :(", ] as string[], type: SettingType.multiple_text, - weight: 6, + weight: 5, }, { group: 'contact', diff --git a/docker/docker-compose.ollama.dev.yml b/docker/docker-compose.ollama.dev.yml new file mode 100644 index 00000000..7cf95404 --- /dev/null +++ b/docker/docker-compose.ollama.dev.yml @@ -0,0 +1,6 @@ +version: "3.9" + +services: + ollama: + ports: + - "11434:11434" diff --git a/docker/docker-compose.ollama.yml b/docker/docker-compose.ollama.yml new file mode 100644 index 00000000..33996e69 --- /dev/null +++ b/docker/docker-compose.ollama.yml @@ -0,0 +1,28 @@ +version: "3.9" + +services: + ollama: + image: ollama/ollama + container_name: ollama + volumes: + - ollama:/root/.ollama + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + restart: unless-stopped + networks: + - ollama-network + + api: + networks: + - ollama-network + +volumes: + ollama: + +networks: + ollama-network: diff --git a/frontend/public/locales/en/chatbot_settings.json b/frontend/public/locales/en/chatbot_settings.json index d99b21ee..d29072dd 100644 --- a/frontend/public/locales/en/chatbot_settings.json +++ b/frontend/public/locales/en/chatbot_settings.json @@ -5,11 +5,13 @@ "label": { "global_fallback": "Enable Global Fallback?", "fallback_message": "Fallback Message", - "default_nlu_helper": "Default NLU Helper" + "default_nlu_helper": "Default NLU Helper", + "default_llm_helper": "Default LLM Helper" }, "help": { "global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.", "fallback_message": "If no fallback block is selected, then one of these messages will be sent.", - "default_nlu_helper": "The NLU helper is responsible for processing and understanding user inputs, including tasks like intent prediction, language detection, and entity recognition." + "default_nlu_helper": "The NLU helper is responsible for processing and understanding user inputs, including tasks like intent prediction, language detection, and entity recognition.", + "default_llm_helper": "The LLM helper leverages advanced generative AI to perform tasks such as text generation, chat completion, and complex query responses." } -} +} \ No newline at end of file diff --git a/frontend/public/locales/fr/chatbot_settings.json b/frontend/public/locales/fr/chatbot_settings.json index cba90376..5199a932 100644 --- a/frontend/public/locales/fr/chatbot_settings.json +++ b/frontend/public/locales/fr/chatbot_settings.json @@ -5,11 +5,13 @@ "label": { "global_fallback": "Activer la réponse de secours globale ?", "fallback_message": "Message de secours", - "default_nlu_helper": "Utilitaire NLU par défaut" + "default_nlu_helper": "Utilitaire NLU par défaut", + "default_llm_helper": "Utilitaire LLM par défaut" }, "help": { "global_fallback": "La réponse de secours globale vous permet d'envoyer des messages personnalisés lorsque l'entrée de l'utilisateur ne correspond à aucun des messages des blocs.", "fallback_message": "Si aucun bloc de secours n'est sélectionné, l'un de ces messages sera envoyé.", - "default_nlu_helper": "Utilitaire du traitement et de la compréhension des entrées des utilisateurs, incluant des tâches telles que la prédiction d'intention, la détection de langue et la reconnaissance d'entités." + "default_nlu_helper": "Utilitaire du traitement et de la compréhension des entrées des utilisateurs, incluant des tâches telles que la prédiction d'intention, la détection de langue et la reconnaissance d'entités.", + "default_llm_helper": "Utilitaire responsable de l'intelligence artificielle générative avancée pour effectuer des tâches telles que la génération de texte, la complétion de chat et les réponses à des requêtes complexes." } -} +} \ No newline at end of file diff --git a/frontend/src/components/settings/SettingInput.tsx b/frontend/src/components/settings/SettingInput.tsx index cdb6f018..5f58249d 100644 --- a/frontend/src/components/settings/SettingInput.tsx +++ b/frontend/src/components/settings/SettingInput.tsx @@ -139,6 +139,23 @@ const SettingInput: React.FC = ({ {...rest} /> ); + } else if (setting.label === "default_llm_helper") { + const { onChange, ...rest } = field; + + return ( + + searchFields={["name"]} + entity={EntityType.LLM_HELPER} + format={Format.BASIC} + labelKey="name" + idKey="name" + label={t("label.default_llm_helper")} + helperText={t("help.default_llm_helper")} + multiple={false} + onChange={(_e, selected, ..._) => onChange(selected?.name)} + {...rest} + /> + ); } return ( diff --git a/frontend/src/services/api.class.ts b/frontend/src/services/api.class.ts index f9e891f7..4d7509d1 100644 --- a/frontend/src/services/api.class.ts +++ b/frontend/src/services/api.class.ts @@ -66,6 +66,7 @@ export const ROUTES = { [EntityType.CHANNEL]: "/channel", [EntityType.HELPER]: "/helper", [EntityType.NLU_HELPER]: "/helper/nlu", + [EntityType.LLM_HELPER]: "/helper/llm", } as const; export class ApiClient { diff --git a/frontend/src/services/entities.ts b/frontend/src/services/entities.ts index 595673ec..3a3bc382 100644 --- a/frontend/src/services/entities.ts +++ b/frontend/src/services/entities.ts @@ -296,6 +296,13 @@ export const NluHelperEntity = new schema.Entity( }, ); +export const LlmHelperEntity = new schema.Entity( + EntityType.LLM_HELPER, + undefined, + { + idAttribute: ({ name }) => name, + }, +); export const ENTITY_MAP = { [EntityType.SUBSCRIBER]: SubscriberEntity, @@ -325,4 +332,5 @@ export const ENTITY_MAP = { [EntityType.CHANNEL]: ChannelEntity, [EntityType.HELPER]: HelperEntity, [EntityType.NLU_HELPER]: NluHelperEntity, + [EntityType.LLM_HELPER]: LlmHelperEntity, } as const; diff --git a/frontend/src/services/types.ts b/frontend/src/services/types.ts index 71a94fae..355a517f 100644 --- a/frontend/src/services/types.ts +++ b/frontend/src/services/types.ts @@ -37,6 +37,7 @@ export enum EntityType { CHANNEL = "Channel", HELPER = "Helper", NLU_HELPER = "NluHelper", + LLM_HELPER = "LlmHelper", } export type NormalizedEntities = Record>; diff --git a/frontend/src/types/base.types.ts b/frontend/src/types/base.types.ts index 58fea277..d20d123a 100644 --- a/frontend/src/types/base.types.ts +++ b/frontend/src/types/base.types.ts @@ -115,6 +115,7 @@ export const POPULATE_BY_TYPE = { [EntityType.CHANNEL]: [], [EntityType.HELPER]: [], [EntityType.NLU_HELPER]: [], + [EntityType.LLM_HELPER]: [], } as const; export type Populate = @@ -205,6 +206,7 @@ export interface IEntityMapTypes { [EntityType.CHANNEL]: IEntityTypes; [EntityType.HELPER]: IEntityTypes; [EntityType.NLU_HELPER]: IEntityTypes; + [EntityType.LLM_HELPER]: IEntityTypes; } export type TType =