From f746a205401ae4adf75b0a575c77823d16ee5bce Mon Sep 17 00:00:00 2001 From: Earnest Angel <57413115+earnestangel@users.noreply.github.com> Date: Mon, 25 Sep 2023 00:15:26 +0800 Subject: [PATCH] Implement Ririko HuggingChat Support (#245) * Add support for Ririko HuggingChat Server * 0.12.0 --- config.example.js | 10 +- package-lock.json | 98 ++----------------- package.json | 4 +- .../Providers/AI/RirikoHuggingChatProvider.js | 48 +++++++++ src/app/Providers/AIProviderBase.js | 4 +- src/app/RirikoAI-NLP.js | 31 +++--- 6 files changed, 85 insertions(+), 110 deletions(-) create mode 100644 src/app/Providers/AI/RirikoHuggingChatProvider.js diff --git a/config.example.js b/config.example.js index d722d5ca..7549027f 100644 --- a/config.example.js +++ b/config.example.js @@ -66,7 +66,8 @@ module.exports = { // Prefix of the AI part of the bot Prefix: ".", - // The provider to use for the bot. Must be one of: NLPCloudProvider | OpenAIProvider | RirikoLLaMAProvider + // The provider to use for the bot. Must be one of: + // NLPCloudProvider | OpenAIProvider | RirikoLLaMAProvider | RirikoHuggingChatProvider Provider: "OpenAIProvider", // Provider Token @@ -77,7 +78,7 @@ module.exports = { // URL of the local server for Ririko AI. Leave this empty if you don't have a local server. // Example: http://localhost:5000/api/v1/ask - LocalServerURL: "", + LocalServerURL: "http://localhost:5000/api/v1/ask", // Enable or disable the Whitelist. EnableWhitelist: true, @@ -87,6 +88,8 @@ module.exports = { * It is also possible to make Ririko cosplay as someone - you gotta try to experiment them it's fun :) * Placeholders: * - %CURRENT_TIME% = to always include current time in the prompt, will be replaced in RirikoAI-NLP + * + * Does not work with RirikoHuggingChatProvider (for now) */ Personality: [ "This is a chat between a [Human] and [Friend]. ", @@ -97,11 +100,12 @@ module.exports = { ], // This is the past prompts, also adds the abilities + // Does not work with RirikoHuggingChatProvider (for now) Prompts: [ "Human: When you're asked to play any song, please reply with this format: Now playing 🎵 insert the song title here 🎵", "Friend: Understood! I will play the song you requested in the given format whenever you ask me to do so.", "Human: Also, when you're asked to play a link similar to these https://www.youtube.com/ or https://open.spotify.com/, " + - "reply with this format: Now playing 🎵 insert the link here 🎵", + "reply with this format: Now playing 🎵 insert the link here 🎵", "Friend: Understood! I'll always use the format asked to play a link.", "Human: Play https://youtube.com/watch?v=Lh63pBzylFg", "Friend: Sure! Now Playing 🎵 https://youtube.com/watch?v=Lh63pBzylFg 🎵", diff --git a/package-lock.json b/package-lock.json index 5a98d6ed..1c420e3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ririko", - "version": "0.11.0", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ririko", - "version": "0.11.0", + "version": "0.12.0", "license": "MIT", "dependencies": { "@discordjs/builders": "^1.6.3", @@ -57,8 +57,8 @@ "pino-pretty": "^10.0.0", "pretty-ms": "^8.0.0", "quick.db": "^9.1.6", - "redis": "^4.6.7", "replicate": "^0.17.0", + "ririkohuggingchat-bot-client": "^1.0.1", "ririkollama-bot-client": "^1.0.0", "save-dev": "^0.0.1-security", "semver": "^7.5.3", @@ -3037,64 +3037,6 @@ "dev": true, "optional": true }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", - "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/client/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", - "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", - "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, "node_modules/@sapphire/async-queue": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", @@ -5248,14 +5190,6 @@ "node": ">=6" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6561,14 +6495,6 @@ "node": ">=10" } }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "engines": { - "node": ">= 4" - } - }, "node_modules/genius-lyrics": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/genius-lyrics/-/genius-lyrics-4.4.6.tgz", @@ -11040,19 +10966,6 @@ "node": ">= 12.13.0" } }, - "node_modules/redis": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", - "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.8", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.3", - "@redis/time-series": "1.0.4" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11232,6 +11145,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ririkohuggingchat-bot-client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ririkohuggingchat-bot-client/-/ririkohuggingchat-bot-client-1.0.1.tgz", + "integrity": "sha512-iI6GxQY3ofsJ0Zbw9wlrnZVxxW7acG4vCJnlvH3GWGcKGDfWGpln3cA+Ie+8aIAwC3FjwTkLqVjP4s8WXMsdkA==" + }, "node_modules/ririkollama-bot-client": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ririkollama-bot-client/-/ririkollama-bot-client-1.0.0.tgz", diff --git a/package.json b/package.json index f9e3525e..e4d90f3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ririko", - "version": "0.11.0", + "version": "0.12.0", "description": "Ririko - A powerful AI-powered general Discord bot that you can call your companion", "author": "Earnest Angel", "email": "me@angel.net.my", @@ -89,8 +89,8 @@ "pino-pretty": "^10.0.0", "pretty-ms": "^8.0.0", "quick.db": "^9.1.6", - "redis": "^4.6.7", "replicate": "^0.17.0", + "ririkohuggingchat-bot-client": "^1.0.1", "ririkollama-bot-client": "^1.0.0", "save-dev": "^0.0.1-security", "semver": "^7.5.3", diff --git a/src/app/Providers/AI/RirikoHuggingChatProvider.js b/src/app/Providers/AI/RirikoHuggingChatProvider.js new file mode 100644 index 00000000..eeef5634 --- /dev/null +++ b/src/app/Providers/AI/RirikoHuggingChatProvider.js @@ -0,0 +1,48 @@ +const {RirikoHuggingChatClient} = require("ririkohuggingchat-bot-client"); +const {AIProviderBase} = require("app/Providers/AIProviderBase"); +const config = require("config"); +const getconfig = require("helpers/getconfig"); + +class RirikoHuggingChatProvider extends AIProviderBase { + constructor() { + super(); + this.ririkoHuggingChatClient = new RirikoHuggingChatClient({ + apiUrl: getconfig.localAIServerURL(), + token: this.token = getconfig.AIToken(), + settings: { + } + }); + } + + getClient() { + return this.ririkoHuggingChatClient; + } + + /** + * Send chat to NLP Cloud + * @param {String} messageText + * @param {String} context + * @param {Array} history + * @param discordMessage + * @returns {Promise<*>} + */ + async sendChat(messageText, context, history, discordMessage) { + try { + // Send request to NLP Cloud. + const response = await this.ririkoHuggingChatClient.ask( + messageText, + discordMessage.author.username + ); + + if (typeof response.data["answer"] !== "undefined") { + return response.data["answer"]; + } else { + return "(no response)"; + } + } catch (e) { + throw e; + } + } +} + +module.exports = {RirikoHuggingChatProvider}; diff --git a/src/app/Providers/AIProviderBase.js b/src/app/Providers/AIProviderBase.js index 4e75cfa5..36d64803 100644 --- a/src/app/Providers/AIProviderBase.js +++ b/src/app/Providers/AIProviderBase.js @@ -22,11 +22,11 @@ class AIProviderBase { * set as the AI's personality. * @param history {*} Some providers requires the chat history to be in a separate payload, * but oftentimes can be combined with the context parameter. - * + * @param discordMessage (Optional) Discord Message object * Returns response from the provider as string. * @returns {Promise} Response from the provider */ - async sendChat(messageText, context, history) {} + async sendChat(messageText, context, history, discordMessage) {} } module.exports = { AIProviderBase }; diff --git a/src/app/RirikoAI-NLP.js b/src/app/RirikoAI-NLP.js index e97b0eed..a3e3dc9d 100644 --- a/src/app/RirikoAI-NLP.js +++ b/src/app/RirikoAI-NLP.js @@ -6,6 +6,7 @@ const colors = require("colors"); const { OpenAIProvider } = require("app/Providers/AI/OpenAIProvider"); const { NLPCloudProvider } = require("app/Providers/AI/NLPCloudProvider"); const { RirikoLLaMAProvider } = require("app/Providers/AI/RirikoLLaMAProvider"); +const { RirikoHuggingChatProvider } = require("app/Providers/AI/RirikoHuggingChatProvider"); const getconfig = require("helpers/getconfig"); const { AIProvider, AIPersonality, AIPrompts } = require("helpers/getconfig"); @@ -59,6 +60,8 @@ class RirikoAINLP { this.provider = new OpenAIProvider(); } else if (AIProvider() === 'RirikoLLaMAProvider') { this.provider = new RirikoLLaMAProvider(); + } else if (AIProvider() === 'RirikoHuggingChatProvider') { + this.provider = new RirikoHuggingChatProvider(); } // AI provider has been initialized @@ -155,7 +158,7 @@ class RirikoAINLP { const currentToken = this.calculateTokenWithEverything(discordMessage); - let answer = await this.sendChatRequest(messageText, chatHistory); + let answer = await this.sendChatRequest(messageText, chatHistory, discordMessage); if (!answer) { return await this.retryAsk(messageText, discordMessage); @@ -179,13 +182,15 @@ class RirikoAINLP { * Send the chat request to the AI provider * @param {String} messageText * @param {Array} chatHistory + * @param discordMessage * @returns {Promise<*>} */ - async sendChatRequest(messageText, chatHistory) { + async sendChatRequest(messageText, chatHistory, discordMessage) { return await this.provider.sendChat( messageText, this.getPersonalitiesAndAbilities(), - chatHistory + chatHistory, + discordMessage ); } @@ -215,17 +220,17 @@ class RirikoAINLP { logTokenCost(totalToken) { console.info( "[RirikoAI-NLP] Request complete, costs ".blue + - totalToken + - ` tokens, that's about `.blue + - `$${(this.costPerToken * totalToken).toFixed(5)}` + totalToken + + ` tokens, that's about `.blue + + `$${(this.costPerToken * totalToken).toFixed(5)}` ); } handleRequestError(e) { console.error( "Something went wrong when trying to send the request to the AI provider: " + - "Check if your API key is still valid, or if your prompts are not corrupted / too long. Also try to clear your " + - "chat history with Ririko by entering .clear in Discord." + "Check if your API key is still valid, or if your prompts are not corrupted / too long. Also try to clear your " + + "chat history with Ririko by entering .clear in Discord." ); if (e.response) { @@ -272,7 +277,7 @@ class RirikoAINLP { // check upfront if the current message does not exceed the maxChatToken const tokens = this.calculateToken( this.getPersonalitiesAndAbilities() + - this.getCurrentPrompt(discordMessage) + this.getCurrentPrompt(discordMessage) ); if (tokens > this.maxChatTokens - 1000) { @@ -348,8 +353,8 @@ class RirikoAINLP { calculateTokenWithEverything(discordMessage) { return this.calculateToken( this.getPersonalitiesAndAbilities() + - this.getChatHistory(discordMessage).toString() + - this.getCurrentPrompt(discordMessage) + this.getChatHistory(discordMessage).toString() + + this.getCurrentPrompt(discordMessage) ); } @@ -364,8 +369,8 @@ class RirikoAINLP { console.info( "[RirikoAI-NLP] A new request with ".blue + - chatTokens + - " tokens is being prepared.".blue + chatTokens + + " tokens is being prepared.".blue ); return chatTokens < this.maxChatTokens;