diff --git a/.husky/pre-commit b/.husky/pre-commit index f07e6d74..de3cbec6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ . "$(dirname -- "$0")/_/husky.sh" npm run lint -tsc --noEmit +npx tsc --noEmit diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..0828ab79 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 82fd3b5a..b0af6a97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5013,10 +5013,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", - "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", - "dev": true + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" }, "node_modules/@types/range-parser": { "version": "1.2.7", @@ -14681,21 +14680,30 @@ } }, "node_modules/openai": { - "version": "4.32.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.32.1.tgz", - "integrity": "sha512-3e9QyCY47tgOkxBe2CSVKlXOE2lLkMa24Y0s3LYZR40yYjiBU9dtVze+C3mu1TwWDGiRX52STpQAEJZvRNuIrA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.60.0.tgz", + "integrity": "sha512-U/wNmrUPdfsvU1GrKRP5mY5YHR3ev6vtdfNID6Sauz+oquWD8r+cXPL1xiUlYniosPKajy33muVHhGS/9/t6KA==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", + "@types/qs": "^6.9.15", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "qs": "^6.10.3" }, "bin": { "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, "node_modules/optionator": { diff --git a/src/bot.ts b/src/bot.ts index 89483713..930b7b3e 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -34,7 +34,7 @@ import { WalletConnect } from './modules/walletconnect' import { BotPayments } from './modules/payment' import { BotSchedule } from './modules/schedule' import config from './config' -import { commandsHelpText, FEEDBACK, LOVE, MODELS, SUPPORT, TERMS, LANG, ALIAS } from './constants' +import { commandsHelpText, FEEDBACK, LOVE, SUPPORT, TERMS, LANG, ALIAS } from './constants' import prometheusRegister, { PrometheusMetrics } from './metrics/prometheus' import { chatService, statsService } from './database/services' @@ -57,6 +57,7 @@ import { VoiceToVoiceGPTBot } from './modules/voice-to-voice-gpt' // import { VoiceCommand } from './modules/voice-command' import { createInitialSessionData } from './helpers' import { LlamaAgent } from './modules/subagents' +import { llmModelManager } from './modules/llms/utils/llmModelsManager' Events.EventEmitter.defaultMaxListeners = 30 @@ -504,10 +505,13 @@ bot.command('support', async (ctx) => { }) bot.command('models', async (ctx) => { + const models = llmModelManager.generateTelegramOutput() + console.log(models) writeCommandLog(ctx as OnMessageContext).catch(logErrorHandler) - return await ctx.reply(MODELS.text, { + return await ctx.reply(models, { parse_mode: 'Markdown', - link_preview_options: { is_disabled: true } + link_preview_options: { is_disabled: true }, + message_thread_id: ctx.message?.message_thread_id }) }) diff --git a/src/helpers.ts b/src/helpers.ts index 29eef76a..c446376b 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,6 @@ import config from './config' -import { LlmsModelsEnum } from './modules/llms/utils/types' +import { LlmModelsEnum } from './modules/llms/utils/llmModelsManager' +import { type DalleImageSize } from './modules/llms/utils/types' import { type BotSessionData } from './modules/types' export function createInitialSessionData (): BotSessionData { @@ -38,7 +39,7 @@ export function createInitialSessionData (): BotSessionData { }, dalle: { numImages: config.openAi.dalle.sessionDefault.numImages, - imgSize: config.openAi.dalle.sessionDefault.imgSize, + imgSize: config.openAi.dalle.sessionDefault.imgSize as DalleImageSize, isEnabled: config.openAi.dalle.isEnabled, imgRequestQueue: [], isProcessingQueue: false, @@ -46,6 +47,6 @@ export function createInitialSessionData (): BotSessionData { isInscriptionLotteryEnabled: config.openAi.dalle.isInscriptionLotteryEnabled, imgInquiried: [] }, - currentModel: LlmsModelsEnum.GPT_4 + currentModel: LlmModelsEnum.GPT_4O } } diff --git a/src/modules/llms/api/athropic.ts b/src/modules/llms/api/athropic.ts index a470c0b6..35c3400a 100644 --- a/src/modules/llms/api/athropic.ts +++ b/src/modules/llms/api/athropic.ts @@ -6,9 +6,9 @@ import { pino } from 'pino' import config from '../../../config' import { type OnCallBackQueryData, type OnMessageContext, type ChatConversation } from '../../types' import { type LlmCompletion } from './llmApi' -import { LlmsModelsEnum } from '../utils/types' import { sleep } from '../../sd-images/utils' import { headers, headersStream } from './helper' +import { LlmModelsEnum } from '../utils/llmModelsManager' const logger = pino({ name: 'anthropic - llmsBot', @@ -22,7 +22,7 @@ const API_ENDPOINT = config.llms.apiEndpoint // 'http://127.0.0.1:5000' // confi export const anthropicCompletion = async ( conversation: ChatConversation[], - model = LlmsModelsEnum.CLAUDE_OPUS + model = LlmModelsEnum.CLAUDE_3_OPUS ): Promise => { logger.info(`Handling ${model} completion`) const data = { @@ -59,7 +59,7 @@ export const anthropicCompletion = async ( export const anthropicStreamCompletion = async ( conversation: ChatConversation[], - model = LlmsModelsEnum.CLAUDE_OPUS, + model = LlmModelsEnum.CLAUDE_3_OPUS, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens = true @@ -158,7 +158,7 @@ export const anthropicStreamCompletion = async ( export const toolsChatCompletion = async ( conversation: ChatConversation[], - model = LlmsModelsEnum.CLAUDE_OPUS + model = LlmModelsEnum.CLAUDE_3_OPUS ): Promise => { logger.info(`Handling ${model} completion`) const input = { diff --git a/src/modules/llms/api/llmApi.ts b/src/modules/llms/api/llmApi.ts index 59b1f648..f8ed9e8a 100644 --- a/src/modules/llms/api/llmApi.ts +++ b/src/modules/llms/api/llmApi.ts @@ -2,9 +2,9 @@ import axios from 'axios' import config from '../../../config' import { type ChatConversation } from '../../types' import pino from 'pino' -import { LlmsModels, LlmsModelsEnum } from '../utils/types' import { type ChatModel } from '../utils/types' import { headers } from './helper' +import { llmModelManager, LlmModelsEnum } from '../utils/llmModelsManager' // import { type ChatModel } from '../../open-ai/types' @@ -39,8 +39,8 @@ interface QueryUrlDocument { conversation?: ChatConversation[] } -export const getChatModel = (modelName: string): ChatModel => { - return LlmsModels[modelName] +export const getChatModel = (modelName: string): ChatModel | undefined => { + return llmModelManager.getModel(modelName) as ChatModel// LlmsModels[modelName] } export const getChatModelPrice = ( @@ -111,7 +111,7 @@ export const deleteCollection = async (collectionName: string): Promise => export const llmCompletion = async ( conversation: ChatConversation[], - model = LlmsModelsEnum.BISON + model = LlmModelsEnum.CHAT_BISON ): Promise => { const data = { model, // chat-bison@001 'chat-bison', //'gpt-3.5-turbo', diff --git a/src/modules/llms/api/openai.ts b/src/modules/llms/api/openai.ts index 6232a74c..3bb1cbe9 100644 --- a/src/modules/llms/api/openai.ts +++ b/src/modules/llms/api/openai.ts @@ -10,15 +10,15 @@ import { } from '../../types' import { pino } from 'pino' import { - type DalleGPTModel, - DalleGPTModels, - LlmsModelsEnum, - type ChatModel + type ImageModel, + type ChatModel, + type DalleImageSize } from '../utils/types' import type fs from 'fs' import { type ChatCompletionMessageParam } from 'openai/resources/chat/completions' import { type Stream } from 'openai/streaming' import { getChatModel, getChatModelPrice, type LlmCompletion } from './llmApi' +import { LlmModelsEnum } from '../utils/llmModelsManager' const openai = new OpenAI({ apiKey: config.openAiKey }) @@ -83,36 +83,43 @@ export async function chatCompletion ( model = config.openAi.chatGpt.model, limitTokens = true ): Promise { + const messages = conversation.filter(c => c.model === model).map(m => { return { content: m.content, role: m.role } }) const response = await openai.chat.completions.create({ model, - max_tokens: limitTokens ? config.openAi.chatGpt.maxTokens : undefined, - temperature: config.openAi.dalle.completions.temperature, - messages: conversation as ChatCompletionMessageParam[] + max_completion_tokens: limitTokens ? config.openAi.chatGpt.maxTokens : undefined, + temperature: model === LlmModelsEnum.O1 ? 1 : config.openAi.dalle.completions.temperature, + messages: messages as ChatCompletionMessageParam[] }) const chatModel = getChatModel(model) if (response.usage?.prompt_tokens === undefined) { throw new Error('Unknown number of prompt tokens used') } - const price = getChatModelPrice( - chatModel, - true, - response.usage?.prompt_tokens, - response.usage?.completion_tokens - ) + const price = chatModel + ? getChatModelPrice( + chatModel, + true, + response.usage?.prompt_tokens, + response.usage?.completion_tokens + ) + : 0 + const inputTokens = response.usage?.prompt_tokens + const outputTokens = response.usage?.completion_tokens return { completion: { content: response.choices[0].message?.content ?? 'Error - no completion available', role: 'assistant' }, - usage: response.usage?.total_tokens, - price: price * config.openAi.chatGpt.priceAdjustment + usage: 2010, // response.usage?.total_tokens, + price: price * config.openAi.chatGpt.priceAdjustment, + inputTokens, + outputTokens } } export const streamChatCompletion = async ( conversation: ChatConversation[], ctx: OnMessageContext | OnCallBackQueryData, - model = LlmsModelsEnum.GPT_4, + model = LlmModelsEnum.GPT_4, msgId: number, limitTokens = true ): Promise => { @@ -196,7 +203,7 @@ export const streamChatCompletion = async ( export const streamChatVisionCompletion = async ( ctx: OnMessageContext | OnCallBackQueryData, - model = LlmsModelsEnum.GPT_4_VISION_PREVIEW, + model = LlmModelsEnum.GPT_4_VISION, prompt: string, imgUrls: string[], msgId: number, @@ -298,19 +305,16 @@ export const getTokenNumber = (prompt: string): number => { return encode(prompt).length } -export const getDalleModel = (modelName: string): DalleGPTModel => { - logger.info(modelName) - return DalleGPTModels[modelName] -} - export const getDalleModelPrice = ( - model: DalleGPTModel, + model: ImageModel, + size: DalleImageSize, inCents = true, numImages = 1, hasEnhacedPrompt = false, chatModel?: ChatModel ): number => { - let price = model.price * numImages || 0 + const modelPrice = model.price[size] + let price = modelPrice * numImages || 0 if (hasEnhacedPrompt && chatModel) { const averageToken = 250 // for 100 words price += getChatModelPrice(chatModel, inCents, averageToken, averageToken) @@ -338,3 +342,188 @@ export async function speechToText (readStream: fs.ReadStream): Promise }) return result.text } + +// const testText = ` +// Yes, according to theoretical physics, black holes are predicted to produce thermal radiation due to quantum effects near their event horizons. This phenomenon is known as **Hawking radiation**, named after physicist Stephen Hawking, who first proposed it in 1974. +// ### **Hawking Radiation and Black Hole Thermodynamics** + +// While classical general relativity suggests that nothing can escape a black hole, quantum mechanics introduces a different perspective. In quantum field theory, the uncertainty principle allows for particle-antiparticle pairs to spontaneously appear near the event horizon of a black hole. One of these particles can fall into the black hole while the other escapes, leading to a net loss of mass-energy from the black hole. This process makes the black hole appear as if it is emitting radiation. + +// ### **Mathematical Description Using Functions** + +// #### **Black Hole Temperature** + +// The temperature \( T \) of a non-rotating, non-charged (Schwarzschild) black hole is given by the Hawking temperature formula: + +// \[ +// T = \frac{\hbar c^3}{8\pi G M k_B} +// \] + +// Where: + +// - \( \hbar \) is the reduced Planck constant (\( \hbar = \frac{h}{2\pi} \approx 1.055 \times 10^{-34} \ \text{J} \cdot \text{s} \)) +// - \( c \) is the speed of light in a vacuum (\( c \approx 3.00 \times 10^8 \ \text{m/s} \)) +// - \( G \) is the gravitational constant (\( G \approx 6.674 \times 10^{-11} \ \text{N} \cdot \text{m}^2/\text{kg}^2 \)) +// - \( M \) is the mass of the black hole +// - \( k_B \) is the Boltzmann constant (\( k_B \approx 1.381 \times 10^{-23} \ \text{J/K} \)) + +// This equation shows that the black hole's temperature is **inversely proportional** to its mass: + +// \[ +// T \propto \frac{1}{M} +// \] + +// #### **Black Hole Luminosity** + +// The power \( P \) radiated by the black hole can be approximated using the Stefan-Boltzmann law for blackbody radiation: + +// \[ +// P = A \sigma T^4 +// \] + +// Where: + +// - \( A \) is the surface area of the black hole's event horizon +// - \( \sigma \) is the Stefan-Boltzmann constant (\( \sigma \approx 5.670 \times 10^{-8} \ \text{W}/\text{m}^2\text{K}^4 \)) +// - \( T \) is the Hawking temperature + +// The surface area \( A \) of the black hole is: + +// \[ +// A = 4\pi r_s^2 +// \] + +// The Schwarzschild radius \( r_s \) is given by: + +// \[ +// r_s = \frac{2GM}{c^2} +// \] + +// Combining these equations: + +// \[ +// A = 4\pi \left( \frac{2GM}{c^2} \right)^2 +// \] + +// Substitute \( A \) and \( T \) back into the power equation to find \( P \) as a function of the black hole mass \( M \): + +// \[ +// P = 4\pi \left( \frac{2GM}{c^2} \right)^2 \sigma \left( \frac{\hbar c^3}{8\pi G M k_B} \right)^4 +// \] + +// Simplify the equation to show how power depends on mass: + +// \[ +// P \propto \frac{1}{M^2} +// \] + +// This inverse square relationship indicates that as the black hole loses mass (through Hawking radiation), it becomes hotter and emits radiation more rapidly. + +// ### **Implications and Observational Challenges** + +// - **Black Hole Evaporation:** Over astronomical timescales, Hawking radiation leads to the gradual loss of mass from a black hole, potentially resulting in complete evaporation. + +// - **Temperature Estimates:** For stellar-mass black holes (about the mass of our Sun), the Hawking temperature is extremely low, around \( 10^{-8} \) Kelvin, making the emitted radiation virtually undetectable. + +// - **Micro Black Holes:** Hypothetical tiny black holes (with masses much smaller than a stellar mass) would have higher temperatures and could emit significant amounts of radiation before evaporating completely. + +// ### **Current Status** + +// - **Experimental Evidence:** As of the knowledge cutoff in 2023, Hawking radiation has not been observed directly due to its incredibly weak signal compared to cosmic background radiation and other sources. + +// - **Theoretical Significance:** Hawking radiation is a crucial concept in theoretical physics because it links quantum mechanics, general relativity, and thermodynamics, providing insights into the fundamental nature of gravity and spacetime. + +// ### **Conclusion** + +// Black holes are theoretically predicted to produce thermal radiation as a result of quantum effects near their event horizons. This radiation, characterized by the Hawking temperature, implies that black holes are not entirely black but emit energy over time. While direct detection remains a challenge, Hawking radiation is a significant prediction that continues to influence research in quantum gravity and cosmology. +// Yes, according to theoretical physics, black holes are predicted to produce thermal radiation due to quantum effects near their event horizons. This phenomenon is known as **Hawking radiation**, named after physicist Stephen Hawking, who first proposed it in 1974. +// ### **Hawking Radiation and Black Hole Thermodynamics** + +// While classical general relativity suggests that nothing can escape a black hole, quantum mechanics introduces a different perspective. In quantum field theory, the uncertainty principle allows for particle-antiparticle pairs to spontaneously appear near the event horizon of a black hole. One of these particles can fall into the black hole while the other escapes, leading to a net loss of mass-energy from the black hole. This process makes the black hole appear as if it is emitting radiation. + +// ### **Mathematical Description Using Functions** + +// #### **Black Hole Temperature** + +// The temperature \( T \) of a non-rotating, non-charged (Schwarzschild) black hole is given by the Hawking temperature formula: + +// \[ +// T = \frac{\hbar c^3}{8\pi G M k_B} +// \] + +// Where: + +// - \( \hbar \) is the reduced Planck constant (\( \hbar = \frac{h}{2\pi} \approx 1.055 \times 10^{-34} \ \text{J} \cdot \text{s} \)) +// - \( c \) is the speed of light in a vacuum (\( c \approx 3.00 \times 10^8 \ \text{m/s} \)) +// - \( G \) is the gravitational constant (\( G \approx 6.674 \times 10^{-11} \ \text{N} \cdot \text{m}^2/\text{kg}^2 \)) +// - \( M \) is the mass of the black hole +// - \( k_B \) is the Boltzmann constant (\( k_B \approx 1.381 \times 10^{-23} \ \text{J/K} \)) + +// This equation shows that the black hole's temperature is **inversely proportional** to its mass: + +// \[ +// T \propto \frac{1}{M} +// \] + +// #### **Black Hole Luminosity** + +// The power \( P \) radiated by the black hole can be approximated using the Stefan-Boltzmann law for blackbody radiation: + +// \[ +// P = A \sigma T^4 +// \] + +// Where: + +// - \( A \) is the surface area of the black hole's event horizon +// - \( \sigma \) is the Stefan-Boltzmann constant (\( \sigma \approx 5.670 \times 10^{-8} \ \text{W}/\text{m}^2\text{K}^4 \)) +// - \( T \) is the Hawking temperature + +// The surface area \( A \) of the black hole is: + +// \[ +// A = 4\pi r_s^2 +// \] + +// The Schwarzschild radius \( r_s \) is given by: + +// \[ +// r_s = \frac{2GM}{c^2} +// \] + +// Combining these equations: + +// \[ +// A = 4\pi \left( \frac{2GM}{c^2} \right)^2 +// \] + +// Substitute \( A \) and \( T \) back into the power equation to find \( P \) as a function of the black hole mass \( M \): + +// \[ +// P = 4\pi \left( \frac{2GM}{c^2} \right)^2 \sigma \left( \frac{\hbar c^3}{8\pi G M k_B} \right)^4 +// \] + +// Simplify the equation to show how power depends on mass: + +// \[ +// P \propto \frac{1}{M^2} +// \] + +// This inverse square relationship indicates that as the black hole loses mass (through Hawking radiation), it becomes hotter and emits radiation more rapidly. + +// ### **Implications and Observational Challenges** + +// - **Black Hole Evaporation:** Over astronomical timescales, Hawking radiation leads to the gradual loss of mass from a black hole, potentially resulting in complete evaporation. + +// - **Temperature Estimates:** For stellar-mass black holes (about the mass of our Sun), the Hawking temperature is extremely low, around \( 10^{-8} \) Kelvin, making the emitted radiation virtually undetectable. + +// - **Micro Black Holes:** Hypothetical tiny black holes (with masses much smaller than a stellar mass) would have higher temperatures and could emit significant amounts of radiation before evaporating completely. + +// ### **Current Status** + +// - **Experimental Evidence:** As of the knowledge cutoff in 2023, Hawking radiation has not been observed directly due to its incredibly weak signal compared to cosmic background radiation and other sources. + +// - **Theoretical Significance:** Hawking radiation is a crucial concept in theoretical physics because it links quantum mechanics, general relativity, and thermodynamics, providing insights into the fundamental nature of gravity and spacetime. + +// ### **Conclusion** +// ` diff --git a/src/modules/llms/api/vertex.ts b/src/modules/llms/api/vertex.ts index 5618a808..13757949 100644 --- a/src/modules/llms/api/vertex.ts +++ b/src/modules/llms/api/vertex.ts @@ -5,8 +5,8 @@ import { type LlmCompletion } from './llmApi' import { type Readable } from 'stream' import { GrammyError } from 'grammy' import { pino } from 'pino' -import { LlmsModelsEnum } from '../utils/types' import { headers, headersStream } from './helper' +import { LlmModelsEnum } from '../utils/llmModelsManager' const API_ENDPOINT = config.llms.apiEndpoint // config.llms.apiEndpoint // 'http://127.0.0.1:5000' // config.llms.apiEndpoint @@ -28,7 +28,7 @@ export const vertexCompletion = async ( messages: conversation.filter(c => c.model === model) .map((msg) => { const msgFiltered: ChatConversation = { content: msg.content, model: msg.model } - if (model === LlmsModelsEnum.BISON) { + if (model === LlmModelsEnum.CHAT_BISON) { msgFiltered.author = msg.role } else { msgFiltered.role = msg.role @@ -61,7 +61,7 @@ export const vertexCompletion = async ( export const vertexStreamCompletion = async ( conversation: ChatConversation[], - model = LlmsModelsEnum.CLAUDE_OPUS, + model = LlmModelsEnum.CLAUDE_3_OPUS, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens = true diff --git a/src/modules/llms/claudeBot.ts b/src/modules/llms/claudeBot.ts index f4273847..ab432978 100644 --- a/src/modules/llms/claudeBot.ts +++ b/src/modules/llms/claudeBot.ts @@ -4,25 +4,18 @@ import { type OnCallBackQueryData, type ChatConversation } from '../types' -import { - hasClaudeOpusPrefix, - SupportedCommands -} from './utils/helpers' +import { hasCommandPrefix, SupportedCommands } from './utils/helpers' import { type LlmCompletion } from './api/llmApi' -import { LlmsModelsEnum } from './utils/types' - import { anthropicCompletion, anthropicStreamCompletion, toolsChatCompletion } from './api/athropic' import { LlmsBase } from './llmsBase' - -const models = [ - LlmsModelsEnum.CLAUDE_HAIKU, - LlmsModelsEnum.CLAUDE_OPUS, - LlmsModelsEnum.CLAUDE_SONNET -] +import { type ModelVersion } from './utils/llmModelsManager' export class ClaudeBot extends LlmsBase { + private readonly opusPrefix: string[] + constructor (payments: BotPayments) { - super(payments, 'ClaudeBot', 'llms', models) + super(payments, 'ClaudeBot', 'llms') + this.opusPrefix = this.modelManager.getPrefixByModel(this.modelsEnum.CLAUDE_3_OPUS) ?? [] } public getEstimatedPrice (ctx: any): number { @@ -33,18 +26,17 @@ export class ClaudeBot extends LlmsBase { ctx: OnMessageContext | OnCallBackQueryData ): boolean { const hasCommand = ctx.hasCommand([ - SupportedCommands.claudeOpus, - SupportedCommands.opus, - SupportedCommands.opusShort, - SupportedCommands.claudeShort, - SupportedCommands.claudeShortTools, - SupportedCommands.sonnetShortTools, - SupportedCommands.sonnetTools, - SupportedCommands.claudeSonnet, - SupportedCommands.sonnet, - SupportedCommands.sonnetShort, - SupportedCommands.claudeHaiku, - SupportedCommands.haikuShort]) + this.commandsEnum.CLAUDE, + this.commandsEnum.OPUS, + this.commandsEnum.O, + this.commandsEnum.C, + this.commandsEnum.CTOOL, + this.commandsEnum.STOOL, + this.commandsEnum.CLAUDES, + this.commandsEnum.SONNET, + this.commandsEnum.S, + this.commandsEnum.HAIKU, + this.commandsEnum.H]) if (ctx.hasCommand(SupportedCommands.new) && this.checkModel(ctx)) { return true @@ -58,19 +50,19 @@ export class ClaudeBot extends LlmsBase { hasPrefix (prompt: string): string { return ( - hasClaudeOpusPrefix(prompt) + hasCommandPrefix(prompt, this.opusPrefix) ) } async chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean): Promise { return await anthropicStreamCompletion( conversation, - model as LlmsModelsEnum, + model, ctx, msgId, true // telegram messages has a character limit @@ -79,7 +71,7 @@ export class ClaudeBot extends LlmsBase { async chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, hasTools: boolean ): Promise { if (hasTools) { @@ -95,42 +87,42 @@ export class ClaudeBot extends LlmsBase { this.logger.warn(`### unsupported command ${ctx.message?.text}`) return } - if (ctx.hasCommand([SupportedCommands.claudeShortTools])) { - this.updateSessionModel(ctx, LlmsModelsEnum.CLAUDE_OPUS) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_OPUS, false, true) + if (ctx.hasCommand([this.commandsEnum.CTOOL])) { + this.updateSessionModel(ctx, this.modelsEnum.CLAUDE_3_OPUS) + await this.onChat(ctx, this.modelsEnum.CLAUDE_3_OPUS, false, true) return } - if (ctx.hasCommand([SupportedCommands.sonnetTools, SupportedCommands.sonnetShortTools])) { - this.updateSessionModel(ctx, LlmsModelsEnum.CLAUDE_SONNET) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_SONNET, false, true) + if (ctx.hasCommand([this.commandsEnum.STOOL])) { + this.updateSessionModel(ctx, this.modelsEnum.CLAUDE_35_SONNET) + await this.onChat(ctx, this.modelsEnum.CLAUDE_35_SONNET, false, true) return } if ( (ctx.hasCommand(SupportedCommands.new) && this.checkModel(ctx)) ) { await this.onStop(ctx) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_OPUS, true, false) + await this.onChat(ctx, this.modelsEnum.CLAUDE_3_OPUS, true, false) return } if (ctx.hasCommand([ - SupportedCommands.claudeOpus, - SupportedCommands.opus, - SupportedCommands.opusShort, - SupportedCommands.claudeShort]) || - (hasClaudeOpusPrefix(ctx.message?.text ?? '') !== '') + this.commandsEnum.CLAUDE, + this.commandsEnum.OPUS, + this.commandsEnum.O, + this.commandsEnum.C]) || + (hasCommandPrefix(ctx.message?.text ?? '', this.opusPrefix) !== '') ) { - this.updateSessionModel(ctx, LlmsModelsEnum.CLAUDE_OPUS) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_OPUS, true, false) + this.updateSessionModel(ctx, this.modelsEnum.CLAUDE_3_OPUS) + await this.onChat(ctx, this.modelsEnum.CLAUDE_3_OPUS, true, false) return } - if (ctx.hasCommand([SupportedCommands.claudeSonnet, SupportedCommands.sonnet, SupportedCommands.sonnetShort])) { - this.updateSessionModel(ctx, LlmsModelsEnum.CLAUDE_SONNET) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_SONNET, true, false) + if (ctx.hasCommand([this.commandsEnum.CLAUDES, this.commandsEnum.SONNET, this.commandsEnum.S])) { + this.updateSessionModel(ctx, this.modelsEnum.CLAUDE_35_SONNET) + await this.onChat(ctx, this.modelsEnum.CLAUDE_35_SONNET, true, false) return } - if (ctx.hasCommand([SupportedCommands.claudeHaiku, SupportedCommands.haikuShort])) { - this.updateSessionModel(ctx, LlmsModelsEnum.CLAUDE_HAIKU) - await this.onChat(ctx, LlmsModelsEnum.CLAUDE_HAIKU, false, false) + if (ctx.hasCommand([this.commandsEnum.HAIKU, this.commandsEnum.H])) { + this.updateSessionModel(ctx, this.modelsEnum.CLAUDE_3_HAIKU) + await this.onChat(ctx, this.modelsEnum.CLAUDE_3_HAIKU, false, false) } } } diff --git a/src/modules/llms/dalleBot.ts b/src/modules/llms/dalleBot.ts index 91b93f70..0348f3ce 100644 --- a/src/modules/llms/dalleBot.ts +++ b/src/modules/llms/dalleBot.ts @@ -10,15 +10,13 @@ import { getMinBalance, getPromptPrice, getUrlFromText, - hasDallePrefix, + hasCommandPrefix, MAX_TRIES, PRICE_ADJUSTMENT, promptHasBadWords, - sendMessage, - SupportedCommands + sendMessage } from './utils/helpers' import { type LlmCompletion } from './api/llmApi' -import { LlmsModelsEnum } from './utils/types' import * as Sentry from '@sentry/node' import { LlmsBase } from './llmsBase' import config from '../../config' @@ -26,7 +24,6 @@ import { now } from '../../utils/perf' import { appText } from '../../utils/text' import { chatCompletion, - getDalleModel, getDalleModelPrice, postGenerateImg, streamChatCompletion, @@ -34,10 +31,19 @@ import { } from './api/openai' import { type PhotoSize } from 'grammy/types' import { InlineKeyboard } from 'grammy' +import { type ModelVersion } from './utils/llmModelsManager' +import { type ImageModel } from './utils/types' export class DalleBot extends LlmsBase { + private readonly model: ImageModel + private readonly commands: string[] + private readonly prefix: string[] + constructor (payments: BotPayments) { super(payments, 'DalleBot', 'dalle') + this.model = this.modelManager.getModelsByBot('DalleBot')[0] as ImageModel + this.commands = this.modelManager.getCommandsByBot('DalleBot') + this.prefix = this.modelManager.getPrefixByModel(this.modelsEnum.DALLE_3) ?? [] if (!config.openAi.dalle.isEnabled) { this.logger.warn('DALL·E 2 Image Bot is disabled in config') } @@ -46,18 +52,15 @@ export class DalleBot extends LlmsBase { public getEstimatedPrice (ctx: any): number { try { const session = this.getSession(ctx) + if (!this.commands) { + throw new Error('Not command list found') + } if ( - ctx.hasCommand([ - SupportedCommands.dalle, - SupportedCommands.dalleImg, - SupportedCommands.dalleShort, - SupportedCommands.dalleShorter - ]) + ctx.hasCommand(this.commands) ) { const imageNumber = session.numImages const imageSize = session.imgSize - const model = getDalleModel(imageSize) - const price = getDalleModelPrice(model, true, imageNumber) // cents + const price = getDalleModelPrice(this.model, imageSize, true, imageNumber) // cents return price * PRICE_ADJUSTMENT } return 0 @@ -71,12 +74,7 @@ export class DalleBot extends LlmsBase { public isSupportedEvent ( ctx: OnMessageContext | OnCallBackQueryData ): boolean { - const hasCommand = ctx.hasCommand([ - SupportedCommands.dalle, - SupportedCommands.dalleImg, - SupportedCommands.dalleShort, - SupportedCommands.dalleShorter - ]) + const hasCommand = ctx.hasCommand(this.commands) const session = this.getSession(ctx) const photo = ctx.message?.reply_to_message?.photo if (photo) { @@ -113,7 +111,7 @@ export class DalleBot extends LlmsBase { if ( prompt && (ctx.chat?.type === 'private' || - ctx.hasCommand(SupportedCommands.vision)) + ctx.hasCommand(this.commandsEnum.VISION)) ) { // && !isNaN(+prompt) return true @@ -128,7 +126,7 @@ export class DalleBot extends LlmsBase { async chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean @@ -136,7 +134,7 @@ export class DalleBot extends LlmsBase { return await streamChatCompletion( conversation, ctx, - model as LlmsModelsEnum, + model, msgId, true // telegram messages has a character limit ) @@ -144,14 +142,14 @@ export class DalleBot extends LlmsBase { async chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum + model: ModelVersion ): Promise { return await chatCompletion(conversation, model) } hasPrefix (prompt: string): string { return ( - hasDallePrefix(prompt) + hasCommandPrefix(prompt, this.prefix) ) } @@ -189,7 +187,7 @@ export class DalleBot extends LlmsBase { return } - if (ctx.hasCommand(SupportedCommands.vision)) { + if (ctx.hasCommand(this.commandsEnum.VISION)) { const photoUrl = getUrlFromText(ctx) if (photoUrl) { const prompt = ctx.match @@ -208,17 +206,12 @@ export class DalleBot extends LlmsBase { } if ( - ctx.hasCommand([ - SupportedCommands.dalle, - SupportedCommands.dalleImg, - SupportedCommands.dalleShort, - SupportedCommands.dalleShorter - ]) || - hasDallePrefix(ctx.message?.text ?? '') !== '' || + ctx.hasCommand(this.commands) || + hasCommandPrefix(ctx.message?.text ?? '', this.prefix) !== '' || (ctx.message?.text?.startsWith('image ') && ctx.chat?.type === 'private') ) { let prompt = (ctx.match ? ctx.match : ctx.message?.text) as string - const prefix = hasDallePrefix(prompt) + const prefix = hasCommandPrefix(prompt, this.prefix) if (prefix) { prompt = prompt.slice(prefix.length) } @@ -361,7 +354,7 @@ export class DalleBot extends LlmsBase { ctx.message?.reply_to_message?.message_thread_id }) ).message_id - const model = LlmsModelsEnum.GPT_4_VISION_PREVIEW + const model = this.modelsEnum.GPT_4_VISION const completion = await streamChatVisionCompletion(ctx, model, prompt ?? '', imgList, msgId, true) if (completion) { ctx.transient.analytics.sessionState = RequestState.Success diff --git a/src/modules/llms/llmsBase.ts b/src/modules/llms/llmsBase.ts index 20a6d7b7..a3b81e19 100644 --- a/src/modules/llms/llmsBase.ts +++ b/src/modules/llms/llmsBase.ts @@ -21,30 +21,39 @@ import { getMinBalance, getPromptPrice, preparePrompt, - sendMessage - // SupportedCommands + sendMessage, + splitTelegramMessage } from './utils/helpers' import { type LlmCompletion, deleteCollection } from './api/llmApi' import * as Sentry from '@sentry/node' import { now } from '../../utils/perf' -import { type LlmsModelsEnum } from './utils/types' +import { type LLMModel } from './utils/types' import { ErrorHandler } from '../errorhandler' import { SubagentBase } from '../subagents/subagentBase' +import { + LlmCommandsEnum, + llmModelManager, + LlmModelsEnum, + type LLMModelsManager, + type ModelVersion +} from './utils/llmModelsManager' export abstract class LlmsBase implements PayableBot { public module: string protected sessionDataKey: string protected readonly logger: Logger protected readonly payments: BotPayments + protected modelManager: LLMModelsManager + protected modelsEnum = LlmModelsEnum + protected commandsEnum = LlmCommandsEnum protected subagents: SubagentBase[] protected botSuspended: boolean - protected supportedModels: LlmsModelsEnum[] + protected supportedModels: LLMModel[] // LlmsModelsEnum[] errorHandler: ErrorHandler constructor (payments: BotPayments, module: string, sessionDataKey: string, - models?: LlmsModelsEnum[], subagents?: SubagentBase[] ) { this.module = module @@ -55,11 +64,11 @@ export abstract class LlmsBase implements PayableBot { options: { colorize: true } } }) + this.modelManager = llmModelManager this.sessionDataKey = sessionDataKey this.botSuspended = false this.payments = payments this.subagents = subagents ?? [] - this.supportedModels = models ?? [] this.errorHandler = new ErrorHandler() } @@ -72,14 +81,14 @@ export abstract class LlmsBase implements PayableBot { protected abstract chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean): Promise protected abstract chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, usesTools: boolean ): Promise @@ -93,12 +102,12 @@ export abstract class LlmsBase implements PayableBot { return (ctx.session[this.sessionDataKey as keyof BotSessionData] as LlmsSessionData & ImageGenSessionData) } - protected updateSessionModel (ctx: OnMessageContext | OnCallBackQueryData, model: LlmsModelsEnum): void { + protected updateSessionModel (ctx: OnMessageContext | OnCallBackQueryData, model: ModelVersion): void { ctx.session.currentModel = model } protected checkModel (ctx: OnMessageContext | OnCallBackQueryData): boolean { - return !!this.supportedModels.find(model => model === ctx.session.currentModel) + return !!this.supportedModels.find(model => model.version === ctx.session.currentModel) } protected async runSubagents (ctx: OnMessageContext | OnCallBackQueryData, msg: ChatConversation): Promise { @@ -129,7 +138,6 @@ export abstract class LlmsBase implements PayableBot { async onChat (ctx: OnMessageContext | OnCallBackQueryData, model: string, stream: boolean, usesTools: boolean): Promise { const session = this.getSession(ctx) - // console.log('onChat ::::::', this.constructor.name, ctx.message?.text, ':::', model, 'ID:', ctx.message?.message_id) try { if (this.botSuspended) { ctx.transient.analytics.sessionState = RequestState.Error @@ -169,7 +177,6 @@ export abstract class LlmsBase implements PayableBot { async onChatRequestHandler (ctx: OnMessageContext | OnCallBackQueryData, stream: boolean, usesTools: boolean): Promise { const session = this.getSession(ctx) - // console.log('onChatRequestHandler ::::::', this.constructor.name, ctx.message?.text, 'ID:', ctx.message?.message_id) while (session.requestQueue.length > 0) { try { const msg = session.requestQueue.shift() @@ -177,7 +184,7 @@ export abstract class LlmsBase implements PayableBot { const model = msg?.model let agentCompletions: string[] = [] const { chatConversation } = session - const minBalance = await getMinBalance(ctx, msg?.model as LlmsModelsEnum) + const minBalance = await getMinBalance(ctx, msg?.model as string) let enhancedPrompt = '' if (await this.hasBalance(ctx, minBalance)) { if (!prompt) { @@ -206,7 +213,7 @@ export abstract class LlmsBase implements PayableBot { continue } } - if (chatConversation.length === 0) { + if (chatConversation.length === 0 && model !== this.modelsEnum.O1) { chatConversation.push({ role: 'system', content: config.openAi.chatGpt.chatCompletionContext, @@ -283,7 +290,7 @@ export abstract class LlmsBase implements PayableBot { ctx.chatAction = 'typing' } const completion = await this.chatStreamCompletion(conversation, - model as LlmsModelsEnum, + model, ctx, msgId, true // telegram messages has a character limit @@ -309,7 +316,7 @@ export abstract class LlmsBase implements PayableBot { } } } else { - const response = await this.chatCompletion(conversation, model as LlmsModelsEnum, usesTools) + const response = await this.chatCompletion(conversation, model, usesTools) conversation.push({ role: 'assistant', content: response.completion?.content ?? '', @@ -340,13 +347,28 @@ export abstract class LlmsBase implements PayableBot { await ctx.reply('...', { message_thread_id: ctx.message?.message_thread_id }) ).message_id ctx.chatAction = 'typing' - const response = await this.chatCompletion(conversation, model as LlmsModelsEnum, usesTools) + const response = await this.chatCompletion(conversation, model, usesTools) if (response.completion) { - await ctx.api.editMessageText( - ctx.chat.id, - msgId, - response.completion.content as string - ) + if (model === this.modelsEnum.O1) { + const msgs = splitTelegramMessage(response.completion.content as string) + await ctx.api.editMessageText( + ctx.chat.id, + msgId, + msgs[0], + { parse_mode: 'Markdown' } + ) + if (msgs.length > 1) { + for (let i = 1; i < msgs.length; i++) { + await ctx.api.sendMessage(ctx.chat.id, msgs[i], { parse_mode: 'Markdown' }) + } + } + } else { + await ctx.api.editMessageText( + ctx.chat.id, + msgId, + response.completion.content as string + ) + } conversation.push(response.completion) const price = getPromptPrice(response, data) this.logger.info( diff --git a/src/modules/llms/llmsBot.ts b/src/modules/llms/llmsBot.ts index f22e2e98..b80e811e 100644 --- a/src/modules/llms/llmsBot.ts +++ b/src/modules/llms/llmsBot.ts @@ -4,19 +4,14 @@ import { type OnCallBackQueryData, type ChatConversation } from '../types' -import { - isMentioned, - SupportedCommands -} from './utils/helpers' +import { isMentioned } from './utils/helpers' import { llmCompletion, type LlmCompletion } from './api/llmApi' -import { LlmsModelsEnum } from './utils/types' import { LlmsBase } from './llmsBase' - -const models = [LlmsModelsEnum.J2_ULTRA] +import { type ModelVersion } from './utils/llmModelsManager' export class LlmsBot extends LlmsBase { constructor (payments: BotPayments) { - super(payments, 'LlmsBot', 'llms', models) + super(payments, 'LlmsBot', 'llms') // this.supportedModels = models } @@ -27,9 +22,10 @@ export class LlmsBot extends LlmsBase { public isSupportedEvent ( ctx: OnMessageContext | OnCallBackQueryData ): boolean { - const hasCommand = ctx.hasCommand([ - SupportedCommands.j2Ultra - ]) + const hasCommand = false + // ctx.hasCommand([ + // SupportedCommands.j2Ultra + // ]) if (isMentioned(ctx)) { return true } @@ -42,7 +38,7 @@ export class LlmsBot extends LlmsBase { async chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean): Promise { @@ -57,7 +53,7 @@ export class LlmsBot extends LlmsBase { async chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum + model: ModelVersion ): Promise { return await llmCompletion(conversation, model) } @@ -71,11 +67,11 @@ export class LlmsBot extends LlmsBase { const isSupportedEvent = this.isSupportedEvent(ctx) if (!isSupportedEvent && ctx.chat?.type !== 'private') { this.logger.warn(`### unsupported command ${ctx.message?.text}`) - return - } - if (ctx.hasCommand(SupportedCommands.j2Ultra)) { - this.updateSessionModel(ctx, LlmsModelsEnum.J2_ULTRA) - await this.onChat(ctx, LlmsModelsEnum.J2_ULTRA, false, false) + // return } + // if (ctx.hasCommand(SupportedCommands.j2Ultra)) { + // this.updateSessionModel(ctx, this.modelsEnum.J2_ULTRA) + // await this.onChat(ctx, this.modelsEnum.J2_ULTRA, false, false) + // } } } diff --git a/src/modules/llms/menu/openaiMenu.ts b/src/modules/llms/menu/openaiMenu.ts index c2287457..f213445f 100644 --- a/src/modules/llms/menu/openaiMenu.ts +++ b/src/modules/llms/menu/openaiMenu.ts @@ -2,8 +2,8 @@ import { Menu } from '@grammyjs/menu' import { type BotContext } from '../../types' import { MenuIds, menuText } from '../../../constants' -import { LlmsModelsEnum } from '../utils/types' import { getStartMenuText } from '../../../pages' +import { LlmModelsEnum } from '../utils/llmModelsManager' export const chatGptMenuText = { helpText: `*🖌️ ChatGPT* @@ -29,21 +29,17 @@ export const chatMainMenu = new Menu(MenuIds.CHAT_GPT_MAIN) const chatGPTimageDefaultOptions = new Menu(MenuIds.CHAT_GPT_MODEL) // gpt-4, gpt-4-0613, gpt-4-32k, gpt-4-32k-0613, gpt-3.5-turbo, gpt-3.5-turbo-0613, gpt-3.5-turbo-16k, gpt-3.5-turbo-16k-0613 .text( - (ctx) => `${getLabel(LlmsModelsEnum.GPT_4, ctx)}`, - (ctx) => { setModel(LlmsModelsEnum.GPT_4, ctx) } + (ctx) => `${getLabel(LlmModelsEnum.GPT_4, ctx)}`, + (ctx) => { setModel(LlmModelsEnum.GPT_4, ctx) } ) .text( - (ctx) => `${getLabel(LlmsModelsEnum.GPT_4_32K, ctx)}`, - (ctx) => { setModel(LlmsModelsEnum.GPT_4_32K, ctx) } + (ctx) => `${getLabel(LlmModelsEnum.GPT_4_32K, ctx)}`, + (ctx) => { setModel(LlmModelsEnum.GPT_4_32K, ctx) } ) .row() .text( - (ctx) => `${getLabel(LlmsModelsEnum.GPT_35_TURBO, ctx)}`, - (ctx) => { setModel(LlmsModelsEnum.GPT_35_TURBO, ctx) } - ) - .text( - (ctx) => `${getLabel(LlmsModelsEnum.GPT_35_TURBO_16K, ctx)}`, - (ctx) => { setModel(LlmsModelsEnum.GPT_35_TURBO_16K, ctx) } + (ctx) => `${getLabel(LlmModelsEnum.GPT_35_TURBO, ctx)}`, + (ctx) => { setModel(LlmModelsEnum.GPT_35_TURBO, ctx) } ) .row() .back('Back') diff --git a/src/modules/llms/openaiBot.ts b/src/modules/llms/openaiBot.ts index 24c27ac7..d5a860ad 100644 --- a/src/modules/llms/openaiBot.ts +++ b/src/modules/llms/openaiBot.ts @@ -6,15 +6,13 @@ import { RequestState } from '../types' import { - hasChatPrefix, - hasDallePrefix, + hasCommandPrefix, hasNewPrefix, isMentioned, sendMessage, SupportedCommands } from './utils/helpers' import { type LlmCompletion } from './api/llmApi' -import { LlmsModelsEnum } from './utils/types' import * as Sentry from '@sentry/node' import { LlmsBase } from './llmsBase' import config from '../../config' @@ -25,18 +23,14 @@ import { streamChatCompletion } from './api/openai' import { type SubagentBase } from '../subagents' - -const models = [ - LlmsModelsEnum.GPT_35_TURBO, - LlmsModelsEnum.GPT_35_TURBO_16K, - LlmsModelsEnum.GPT_4, - LlmsModelsEnum.GPT_4_32K, - LlmsModelsEnum.GPT_4_VISION_PREVIEW -] +import { type ModelVersion } from './utils/llmModelsManager' export class OpenAIBot extends LlmsBase { + private readonly gpt4oPrefix: string[] + constructor (payments: BotPayments, subagents?: SubagentBase[]) { - super(payments, 'OpenAIBot', 'chatGpt', models, subagents) + super(payments, 'OpenAIBot', 'chatGpt', subagents) + this.gpt4oPrefix = this.modelManager.getPrefixByModel(this.modelsEnum.GPT_4O) ?? [] if (!config.openAi.dalle.isEnabled) { this.logger.warn('DALL·E 2 Image Bot is disabled in config') } @@ -55,15 +49,8 @@ export class OpenAIBot extends LlmsBase { public isSupportedEvent ( ctx: OnMessageContext | OnCallBackQueryData ): boolean { - const hasCommand = ctx.hasCommand([ - SupportedCommands.chat, - SupportedCommands.ask, - SupportedCommands.gpt4, - SupportedCommands.gpt, - SupportedCommands.ask32, - SupportedCommands.ask35, - SupportedCommands.last - ]) + const commands = ['last', ...this.modelManager.getCommandsByProvider('openai')] + const hasCommand = ctx.hasCommand(commands) if (ctx.hasCommand(SupportedCommands.new) && this.checkModel(ctx)) { return true } @@ -80,7 +67,7 @@ export class OpenAIBot extends LlmsBase { async chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean @@ -88,7 +75,7 @@ export class OpenAIBot extends LlmsBase { return await streamChatCompletion( conversation, ctx, - model as LlmsModelsEnum, + model, msgId, true // telegram messages has a character limit ) @@ -96,14 +83,14 @@ export class OpenAIBot extends LlmsBase { async chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum + model: ModelVersion ): Promise { - return await chatCompletion(conversation, model) + return await chatCompletion(conversation, model, model !== this.modelsEnum.O1) // limitTokens doesn't apply for o1-preview } hasPrefix (prompt: string): string { return ( - hasChatPrefix(prompt) || hasDallePrefix(prompt) || hasNewPrefix(prompt) + hasCommandPrefix(prompt, this.gpt4oPrefix) || hasNewPrefix(prompt) // hasDallePrefix(prompt) ) } @@ -121,19 +108,19 @@ export class OpenAIBot extends LlmsBase { if ( ctx.hasCommand([ - SupportedCommands.chat, - SupportedCommands.ask, - SupportedCommands.gpt, - SupportedCommands.gpto + this.commandsEnum.CHAT, + this.commandsEnum.ASK, + this.commandsEnum.GPT, + this.commandsEnum.GPTO ]) || - hasChatPrefix(ctx.message?.text ?? '') || + hasCommandPrefix(ctx.message?.text ?? '', this.gpt4oPrefix) || isMentioned(ctx) || ((ctx.message?.text?.startsWith('chat ') ?? ctx.message?.text?.startsWith('ask ')) && ctx.chat?.type === 'private') ) { - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_4O) - await this.onChat(ctx, LlmsModelsEnum.GPT_4O, true, false) + this.updateSessionModel(ctx, this.modelsEnum.GPT_4O) + await this.onChat(ctx, this.modelsEnum.GPT_4O, true, false) return } @@ -148,26 +135,32 @@ export class OpenAIBot extends LlmsBase { (ctx.message?.text?.startsWith('new ') && ctx.chat?.type === 'private') && this.checkModel(ctx)) ) { await this.onStop(ctx) - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_4O) - await this.onChat(ctx, LlmsModelsEnum.GPT_4O, true, false) + this.updateSessionModel(ctx, this.modelsEnum.GPT_4O) + await this.onChat(ctx, this.modelsEnum.GPT_4O, true, false) return } - if (ctx.hasCommand(SupportedCommands.ask35)) { - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_35_TURBO_16K) - await this.onChat(ctx, LlmsModelsEnum.GPT_35_TURBO_16K, true, false) + if (ctx.hasCommand(this.commandsEnum.ASK35)) { + this.updateSessionModel(ctx, this.modelsEnum.GPT_35_TURBO) + await this.onChat(ctx, this.modelsEnum.GPT_35_TURBO, true, false) return } - if (ctx.hasCommand(SupportedCommands.gpt4)) { - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_4) - await this.onChat(ctx, LlmsModelsEnum.GPT_4, true, false) + if (ctx.hasCommand(this.commandsEnum.GPT4)) { + this.updateSessionModel(ctx, this.modelsEnum.GPT_4) + await this.onChat(ctx, this.modelsEnum.GPT_4, true, false) return } - if (ctx.hasCommand(SupportedCommands.ask32)) { - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_4_32K) - await this.onChat(ctx, LlmsModelsEnum.GPT_4_32K, true, false) + // if (ctx.hasCommand(this.commandsEnum.ASK32)) { + // this.updateSessionModel(ctx, this.modelsEnum.GPT_4_32K) + // await this.onChat(ctx, this.modelsEnum.GPT_4_32K, true, false) + // return + // } + + if (ctx.hasCommand([this.commandsEnum.O1, this.commandsEnum.ASK1])) { + this.updateSessionModel(ctx, this.modelsEnum.O1) + await this.onChat(ctx, this.modelsEnum.O1, false, false) return } @@ -177,8 +170,8 @@ export class OpenAIBot extends LlmsBase { } if (ctx.chat?.type === 'private' || session.isFreePromptChatGroups) { - this.updateSessionModel(ctx, LlmsModelsEnum.GPT_4) - await this.onChat(ctx, LlmsModelsEnum.GPT_4, true, false) + this.updateSessionModel(ctx, this.modelsEnum.GPT_4O) + await this.onChat(ctx, this.modelsEnum.GPT_4O, true, false) return } diff --git a/src/modules/llms/utils/helpers.ts b/src/modules/llms/utils/helpers.ts index b43fcc8d..65c3d421 100644 --- a/src/modules/llms/utils/helpers.ts +++ b/src/modules/llms/utils/helpers.ts @@ -16,55 +16,18 @@ import config from '../../../config' export const PRICE_ADJUSTMENT = config.openAi.chatGpt.priceAdjustment export enum SupportedCommands { - bardF = 'bard', - claudeOpus = 'claude', - opus = 'opus', - opusShort = 'o', - claudeSonnet = 'claudes', - claudeShortTools = 'ctool', - claudeShort = 'c', - sonnet = 'sonnet', - sonnetTools = 'sonnett', - sonnetShortTools = 'stool', - sonnetShort = 's', - claudeHaiku = 'haiku', - haikuShort = 'h', - bard = 'b', - j2Ultra = 'j2-ultra', sum = 'sum', ctx = 'ctx', pdf = 'pdf', - gemini = 'gemini', - gShort = 'g', - gemini15 = 'gemini15', - g15short = 'g15', - chat = 'chat', - ask = 'ask', - vision = 'vision', - ask35 = 'ask35', new = 'new', - gpt4 = 'gpt4', - ask32 = 'ask32', - gpto = 'gpto', - gpt = 'gpt', last = 'last', - dalle = 'dalle', - dalleImg = 'image', - dalleShort = 'img', - dalleShorter = 'i', - // genImgEn = 'genImgEn', on = 'on', off = 'off', talk = 'talk' } export const MAX_TRIES = 3 -const LLAMA_PREFIX_LIST = ['* '] -const BARD_PREFIX_LIST = ['b. ', 'B. '] -const CLAUDE_OPUS_PREFIX_LIST = ['c. '] -const GEMINI_PREFIX_LIST = ['g. '] -const DALLE_PREFIX_LIST = ['i. ', ', ', 'd. '] -const CHAT_GPT_PREFIX_LIST = ['a. ', '. '] + const NEW_PREFIX_LIST = ['n. ', '.. '] export const isMentioned = ( @@ -115,12 +78,12 @@ export const promptHasBadWords = (prompt: string): boolean => { // const hasTabooWords = tabooWords.some( // word => lowerCasePrompt.includes(word.toLowerCase()) - // ); + // ) return hasChildrenWords && hasSexWords } -const hasCommandPrefix = (prompt: string, prefixList: string[]): string => { +export const hasCommandPrefix = (prompt: string, prefixList: string[]): string => { for (let i = 0; i < prefixList.length; i++) { if (prompt.toLocaleLowerCase().startsWith(prefixList[i])) { return prefixList[i] @@ -128,29 +91,6 @@ const hasCommandPrefix = (prompt: string, prefixList: string[]): string => { } return '' } -export const hasLlamaPrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, LLAMA_PREFIX_LIST) -} - -export const hasBardPrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, BARD_PREFIX_LIST) -} - -export const hasClaudeOpusPrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, CLAUDE_OPUS_PREFIX_LIST) -} - -export const hasGeminiPrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, GEMINI_PREFIX_LIST) -} - -export const hasChatPrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, CHAT_GPT_PREFIX_LIST) -} - -export const hasDallePrefix = (prompt: string): string => { - return hasCommandPrefix(prompt, DALLE_PREFIX_LIST) -} export const hasNewPrefix = (prompt: string): string => { return hasCommandPrefix(prompt, NEW_PREFIX_LIST) @@ -308,9 +248,10 @@ export const sendMessage = async ( export const getPromptPrice = (completion: LlmCompletion, data: ChatPayload, updateSession = true): { price: number, promptTokens: number, completionTokens: number } => { const { ctx, model } = data const modelPrice = getChatModel(model) - const price = - getChatModelPrice(modelPrice, true, completion.inputTokens ?? 0, completion.outputTokens ?? 0) * + const price = modelPrice + ? getChatModelPrice(modelPrice, true, completion.inputTokens ?? 0, completion.outputTokens ?? 0) * config.openAi.chatGpt.priceAdjustment + : 0 if (updateSession) { ctx.session.llms.usage += completion.outputTokens ?? 0 ctx.session.llms.price += price @@ -351,3 +292,45 @@ export const hasCodeSnippet = (ctx: OnMessageContext | OnCallBackQueryData): boo const entities = ctx.entities('pre') // pre => code snippets return entities.length > 0 } + +export const splitTelegramMessage = (text: string): string[] => { + const maxLength = 4096 + const result: string[] = [] + + // Regular expression to match Markdown entities + const markdownRegex = /(\*\*|__|\[.*?\]\(.*?\)|```[\s\S]*?```|`[^`\n]+`)/g + + // Function to find the end index that avoids splitting Markdown entities + const findEndIndex = (startIndex: number, chunk: string): number => { + const matches = [...chunk.matchAll(markdownRegex)] + if (matches.length === 0) return startIndex + maxLength + + const lastMatch = matches[matches.length - 1] + const lastMatchEnd = lastMatch.index + lastMatch[0].length + return lastMatchEnd > chunk.length ? startIndex + lastMatch.index : startIndex + maxLength + } + + let startIndex = 0 + while (startIndex < text.length) { + let endIndex = findEndIndex(startIndex, text.slice(startIndex, startIndex + maxLength)) + endIndex = Math.min(endIndex, text.length) // Ensure endIndex is within bounds + + // Find a natural break point if necessary + if (endIndex < text.length) { + const lastSpaceIndex = text.slice(startIndex, endIndex).lastIndexOf(' ') + if (lastSpaceIndex > 0) { + endIndex = startIndex + lastSpaceIndex + } + } + + result.push(text.slice(startIndex, endIndex).trim()) + startIndex = endIndex + + // Move past any spaces or special characters that might cause issues + while (startIndex < text.length && /\s/.test(text[startIndex])) { + startIndex++ + } + } + + return result +} diff --git a/src/modules/llms/utils/llmModelsManager.ts b/src/modules/llms/utils/llmModelsManager.ts new file mode 100644 index 00000000..2d277c33 --- /dev/null +++ b/src/modules/llms/utils/llmModelsManager.ts @@ -0,0 +1,180 @@ +import { llmData } from './llmsData' +import { + type Provider, + type LLMData, + type LLMModel, + type ImageModel +} from './types' + +export class LLMModelsManager { + private readonly models = new Map() + private readonly modelsEnum: Record + private readonly commandsEnum: Record + + constructor (llmData: LLMData) { + this.loadModels(llmData) + console.log(this.models) + this.modelsEnum = this.createModelsEnum() + this.commandsEnum = this.createCommandsEnum() + } + + private loadModels (data: LLMData): void { + Object.values(data.chatModels).forEach(model => { this.addModel(model) }) + Object.values(data.imageModels).forEach(model => { this.addModel(model) }) + } + + addModel (model: LLMModel): void { + this.models.set(model.version, model) + } + + private createModelsEnum (): Record { + const modelsEnum: Record = {} + this.models.forEach(model => { + const key = this.sanitizeEnumKey(model.name) + modelsEnum[key] = model.version + }) + return modelsEnum + } + + private createCommandsEnum (): Record { + const commandsEnum: Record = {} + this.models.forEach(model => { + model.commands.forEach(command => { + const key = this.sanitizeEnumKey(command) + commandsEnum[key] = command + }) + }) + return commandsEnum + } + + getModel (version: string): LLMModel | undefined { + return this.models.get(version) + } + + getAllModels (): LLMModel[] { + return Array.from(this.models.values()) + } + + getModelsByProvider(provider: Provider): T[] { + return this.getAllModels().filter(model => model.provider === provider) as T[] + } + + getModelsByBot(botName: string): T[] { + return this.getAllModels().filter(model => model.botName === botName) as T[] + } + + // Add a new method specifically for image models + getImageModelsByProvider (provider: Provider): ImageModel[] { + return this.getModelsByProvider(provider) + } + + getCommandsByProvider (provider: Provider): string[] { + let commands: string[] = [] + this.models.forEach(model => { + if (model.provider === provider) { + commands = [...commands, ...model.commands] + } + }) + return commands + } + + getCommandsByBot (botName: string): string[] { + let commands: string[] = [] + this.models.forEach(model => { + if (model.botName === botName) { + commands = [...commands, ...model.commands] + } + }) + return commands + } + + getModelsEnum (): Record & { + [K in keyof typeof this.modelsEnum]: (typeof this.modelsEnum)[K] + } { + return new Proxy(this.modelsEnum, { + get (target, prop) { + if (typeof prop === 'string' && prop in target) { + return target[prop] + } + throw new Error(`Invalid model: ${String(prop)}`) + } + }) as any + } + + getCommandsEnum (): Record & { + [K in keyof typeof this.commandsEnum]: (typeof this.commandsEnum)[K] + } { + return new Proxy(this.commandsEnum, { + get (target, prop) { + if (typeof prop === 'string' && prop in target) { + return target[prop] + } + throw new Error(`Invalid command: ${String(prop)}`) + } + }) as any + } + + isValidModel (model: string): model is (typeof this.modelsEnum)[keyof typeof this.modelsEnum] { + return Object.values(this.modelsEnum).includes(model) + } + + isValidCommand (command: string): command is (typeof this.commandsEnum)[keyof typeof this.commandsEnum] { + return Object.values(this.commandsEnum).includes(command) + } + + getCommandsByModel (version: string): string[] | undefined { + return this.models.get(version)?.commands + } + + getPrefixByModel (version: string): string[] | undefined { + return this.models.get(version)?.prefix + } + + getModelByCommand (command: string): LLMModel | undefined { + return Array.from(this.models.values()).find(model => + model.commands.includes(command) + ) + } + + generateTelegramOutput (): string { + let output = '' + const providers = Array.from(new Set(this.getAllModels().map(model => model.provider))) + for (const provider of providers) { + output += `*${provider.toUpperCase()} models:*\n` + this.getModelsByProvider(provider).forEach(model => { + console.log(model.name, model.provider, model.botName) + if (model.commands.length > 0) { + output += `${model.fullName}: [${model.version}](${model.apiSpec})\n` + output += `Commands: _/${model.commands.join(' /')}_\n` + if (model.prefix) { + output += `Shortcuts: _${model.prefix.join(' ')}_\n\n` + } else { + output += '\n' + } + } + }) + } + return output.trim() + } + + private sanitizeEnumKey (key: string): string { + // Replace spaces and special characters with underscores + return key.replace(/[\s-]+/g, '_').replace(/[^\w]+/g, '').toUpperCase() + } +} + +export const llmModelManager = new LLMModelsManager(llmData) + +export const LlmModelsEnum = llmModelManager.getModelsEnum() +export type ModelVersion = (typeof LlmModelsEnum)[keyof typeof LlmModelsEnum] + +export const LlmCommandsEnum = llmModelManager.getCommandsEnum() +export type ModelCommand = (typeof LlmCommandsEnum)[keyof typeof LlmCommandsEnum] + +export const isValidModel = llmModelManager.isValidModel.bind(llmModelManager) +export const isValidCommand = llmModelManager.isValidCommand.bind(llmModelManager) + +// console.log(LlmModelsEnum) +// console.log(LlmCommandsEnum) +// Helper type for IntelliSense +export type LlmModelsEnumType = typeof LlmModelsEnum diff --git a/src/modules/llms/utils/llmsData.ts b/src/modules/llms/utils/llmsData.ts new file mode 100644 index 00000000..c089ddf3 --- /dev/null +++ b/src/modules/llms/utils/llmsData.ts @@ -0,0 +1,216 @@ +import { type LLMData } from './types' + +export const llmData: LLMData = { + chatModels: { + // 'chat-bison': { + // provider: 'vertex', + // name: 'chat-bison', + // fullName: 'chat-bison', + // version: 'chat-bison', + // commands: ['bison', 'b'], + // apiSpec: 'https://example.com/chat-bison-api-spec', + // inputPrice: 0.03, + // outputPrice: 0.06, + // maxContextTokens: 8192, + // chargeType: 'CHAR' + // }, + 'gemini-10': { + provider: 'vertex', + name: 'gemini-10', + botName: 'VertexBot', + fullName: 'gemini-1.0-pro', + version: 'gemini-1.0-pro', + commands: ['gemini', 'g'], + prefix: ['g. '], + apiSpec: 'https://deepmind.google/technologies/gemini/pro/', + inputPrice: 0.000125, + outputPrice: 0.000375, + maxContextTokens: 30720, + chargeType: 'CHAR' + }, + 'gemini-15': { + provider: 'vertex', + name: 'gemini-15', + fullName: 'gemini-1.5-pro-latest', + botName: 'VertexBot', + version: 'gemini-1.5-pro-latest', + commands: ['gemini15', 'g15'], + apiSpec: 'https://deepmind.google/technologies/gemini/pro/', + inputPrice: 0.0025, + outputPrice: 0.0075, + maxContextTokens: 1048576, + chargeType: 'CHAR' + }, + // 'j2-ultra': { + // provider: 'jurassic', + // name: 'j2_Ultra', + // fullName: 'j2-ultra', + // version: 'j2-ultra', + // commands: ['j2ultra'], + // apiSpec: 'https://example.com/j2-ultra-api-spec', + // inputPrice: 0.06, + // outputPrice: 0.12, + // maxContextTokens: 32000, + // chargeType: 'TOKEN' + // }, + 'claude-3-opus': { + provider: 'claude', + name: 'claude-3-opus', + fullName: 'Claude Opus', + botName: 'ClaudeBot', + version: 'claude-3-opus-20240229', + commands: ['claude', 'opus', 'c', 'o', 'ctool'], + prefix: ['c. '], + apiSpec: 'https://www.anthropic.com/news/claude-3-family', + inputPrice: 0.015, + outputPrice: 0.075, + maxContextTokens: 4096, + chargeType: 'TOKEN' + }, + 'claude-35-sonnet': { + provider: 'claude', + name: 'claude-35-sonnet', + fullName: 'Claude Sonnet 3.5', + botName: 'ClaudeBot', + version: 'claude-3-5-sonnet-20240620', + commands: ['sonnet', 'claudes', 's', 'stool'], + apiSpec: 'https://www.anthropic.com/news/claude-3-5-sonnet', + inputPrice: 0.003, + outputPrice: 0.015, + maxContextTokens: 8192, + chargeType: 'TOKEN' + }, + 'claude-3-haiku': { + provider: 'claude', + name: 'claude-3-haiku', + fullName: 'Claude Haiku', + botName: 'ClaudeBot', + version: 'claude-3-haiku-20240307', + commands: ['haiku', 'h'], + apiSpec: 'https://www.anthropic.com/news/claude-3-family', + inputPrice: 0.00025, + outputPrice: 0.00125, + maxContextTokens: 4096, + chargeType: 'TOKEN' + }, + 'gpt-4': { + provider: 'openai', + name: 'gpt-4', + fullName: 'GPT-4', + botName: 'OpenAIBot', + version: 'gpt-4', + commands: ['gpt4'], + apiSpec: 'https://openai.com/index/gpt-4/', + inputPrice: 0.03, + outputPrice: 0.06, + maxContextTokens: 8192, + chargeType: 'TOKEN' + }, + // 'gpt-4-32k': { + // provider: 'openai', + // name: 'gpt-4-32k', + // fullName: 'GPT-4 32k', + // version: 'gpt-4-32k', + // commands: ['gpt4-32k', 'ask32'], + // apiSpec: 'https://example.com/gpt-4-32k-api-spec', + // inputPrice: 0.06, + // outputPrice: 0.12, + // maxContextTokens: 32000, + // chargeType: 'TOKEN' + // }, + 'gpt-35-turbo': { + provider: 'openai', + name: 'gpt-35-turbo', + fullName: 'GPT-3.5 Turbo', + botName: 'OpenAIBot', + version: 'gpt-3.5-turbo', + commands: ['ask35'], + apiSpec: 'https://platform.openai.com/docs/models/gpt-3-5-turbo', + inputPrice: 0.0015, + outputPrice: 0.002, + maxContextTokens: 4000, + chargeType: 'TOKEN' + }, + // 'gpt-35-turbo-16k': { + // provider: 'openai', + // name: 'GPT-3.5 Turbo 16k', + // version: 'gpt-3.5-turbo-16k', + // commands: ['gpt35-16k'], + // apiSpec: 'https://example.com/gpt-3.5-turbo-16k-api-spec', + // inputPrice: 0.003, + // outputPrice: 0.004, + // maxContextTokens: 16000, + // chargeType: 'TOKEN' + // }, + 'gpt-4-vision': { + provider: 'openai', + name: 'gpt-4-vision', + fullName: 'GPT-4 Vision', + botName: 'OpenAIBot', + version: 'gpt-4-vision-preview', + commands: ['vision'], + apiSpec: 'https://platform.openai.com/docs/guides/vision', + inputPrice: 0.03, + outputPrice: 0.06, + maxContextTokens: 16000, + chargeType: 'TOKEN' + }, + 'gpt-4o': { + provider: 'openai', + name: 'gpt-4o', + fullName: 'GPT-4o', + botName: 'OpenAIBot', + version: 'gpt-4o', + commands: ['gpto', 'ask', 'chat', 'gpt'], + prefix: ['a. ', '. '], + apiSpec: 'https://platform.openai.com/docs/models/gpt-4o', + inputPrice: 0.005, + outputPrice: 0.0015, + maxContextTokens: 128000, + chargeType: 'TOKEN' + }, + o1: { + provider: 'openai', + name: 'o1', + fullName: 'O1 Preview', + botName: 'OpenAIBot', + version: 'o1-preview', + commands: ['o1', 'ask1'], + apiSpec: 'https://platform.openai.com/docs/models/o1', + inputPrice: 0.015, + outputPrice: 0.06, + maxContextTokens: 128000, + chargeType: 'TOKEN' + }, + 'o1-mini': { + provider: 'openai', + name: 'o1-mini', + fullName: 'O1 Mini', + botName: 'OpenAIBot', + version: 'o1-mini-2024-09-12', + commands: ['omini'], + apiSpec: 'https://platform.openai.com/docs/models/o1', + inputPrice: 0.003, + outputPrice: 0.012, + maxContextTokens: 128000, + chargeType: 'TOKEN' + } + }, + imageModels: { + 'dalle-3': { + provider: 'openai', + name: 'dalle-3', + fullName: 'DALL-E 3', + botName: 'DalleBot', + version: 'dall-e-3', + commands: ['dalle', 'image', 'img', 'i'], + prefix: ['i. ', ', ', 'd. '], + apiSpec: 'https://openai.com/index/dall-e-3/', + price: { + '1024x1024': 0.8, + '1024x1792': 0.12, + '1792x1024': 0.12 + } + } + } +} diff --git a/src/modules/llms/utils/types.ts b/src/modules/llms/utils/types.ts index ad642b30..36a59e07 100644 --- a/src/modules/llms/utils/types.ts +++ b/src/modules/llms/utils/types.ts @@ -1,145 +1,35 @@ -export enum LlmsModelsEnum { - GPT_4_32K = 'gpt-4-32k', - BISON = 'chat-bison', - J2_ULTRA = 'j2-ultra', - CLAUDE_OPUS = 'claude-3-opus-20240229', - CLAUDE_SONNET = 'claude-3-5-sonnet-20240620', - CLAUDE_HAIKU = 'claude-3-haiku-20240307', - GEMINI_15 = 'gemini-1.5-pro-latest', - GEMINI = 'gemini-1.0-pro', - GPT_4 = 'gpt-4', - GPT_35_TURBO = 'gpt-3.5-turbo', - GPT_35_TURBO_16K = 'gpt-3.5-turbo-16k', - GPT_4_VISION_PREVIEW = 'gpt-4-vision-preview', - GPT_4O = 'gpt-4o-2024-05-13' -} +export type Provider = 'openai' | 'claude' | 'vertex' | 'palm' | 'jurassic' +export type ChargeType = 'TOKEN' | 'CHAR' -export interface DalleGPTModel { - size: string - price: number -} +export type DalleImageSize = '1024x1024' | '1024x1792' | '1792x1024' + +type ImagePrice = Record -export interface ChatModel { +interface BaseModel { + provider: Provider name: string + fullName: string + botName: string + version: string + commands: string[] + prefix?: string[] + apiSpec: string +} +export interface ChatModel extends BaseModel { inputPrice: number outputPrice: number maxContextTokens: number - chargeType: 'TOKEN' | 'CHAR' + chargeType: ChargeType } -export const LlmsModels: Record = { - 'chat-bison': { - name: 'chat-bison', - inputPrice: 0.03, - outputPrice: 0.06, - maxContextTokens: 8192, - chargeType: 'CHAR' - }, - 'gemini-1.0-pro': { - name: 'gemini-1.0-pro', - inputPrice: 0.000125, // 3.00 (1M Tokens) => 0.003 (1K tokens) - outputPrice: 0.000375, - maxContextTokens: 30720, - chargeType: 'CHAR' - }, - 'gemini-1.5-pro-latest': { - name: 'gemini-1.5-pro-latest', - inputPrice: 0.0025, // 3.00 (1M Tokens) => 0.003 (1K tokens) - outputPrice: 0.0075, - maxContextTokens: 1048576, - chargeType: 'CHAR' - }, - 'j2-ultra': { - name: 'j2-ultra', - inputPrice: 0.06, - outputPrice: 0.12, - maxContextTokens: 32000, - chargeType: 'TOKEN' - }, - 'claude-3-opus-20240229': { - name: 'claude-3-opus-20240229', - inputPrice: 0.015, // 15.00 (1M Tokens) => 0.015 (1K tokens) - outputPrice: 0.075, - maxContextTokens: 4096, - chargeType: 'TOKEN' - }, - 'claude-3-5-sonnet-20240620': { - name: 'claude-3-5-sonnet-20240620', - inputPrice: 0.003, // 3.00 (1M Tokens) => 0.003 (1K tokens) - outputPrice: 0.015, - maxContextTokens: 8192, - chargeType: 'TOKEN' - }, - 'claude-3-haiku-20240307': { - name: 'claude-3-haiku-20240307', - inputPrice: 0.00025, // 3.00 (1M Tokens) => 0.003 (1K tokens) - outputPrice: 0.00125, - maxContextTokens: 4096, - chargeType: 'TOKEN' - }, - 'gpt-4': { - name: 'gpt-4', - inputPrice: 0.03, // 3 - outputPrice: 0.06, // 6 - maxContextTokens: 8192, - chargeType: 'TOKEN' - }, - 'gpt-4-32k': { - name: 'gpt-4-32k', - inputPrice: 0.06, // 6 - outputPrice: 0.12, // 12 - maxContextTokens: 32000, - chargeType: 'TOKEN' - }, - 'gpt-3.5-turbo': { - name: 'gpt-3.5-turbo', - inputPrice: 0.0015, // 0.15 - outputPrice: 0.002, // 0.2 - maxContextTokens: 4000, - chargeType: 'TOKEN' - }, - 'gpt-3.5-turbo-16k': { - name: 'gpt-3.5-turbo-16k', - inputPrice: 0.003, // 0.3 - outputPrice: 0.004, // 0.4 - maxContextTokens: 16000, - chargeType: 'TOKEN' - }, - 'gpt-4-vision-preview': { - name: 'gpt-4-vision-preview', - inputPrice: 0.03, // 3 - outputPrice: 0.06, // 6 - maxContextTokens: 16000, - chargeType: 'TOKEN' - }, - 'gpt-4o-2024-05-13': { - name: 'gpt-4o-2024-05-13', - inputPrice: 0.005, // 3 - outputPrice: 0.0015, // 6 - maxContextTokens: 128000, - chargeType: 'TOKEN' - } +export interface ImageModel extends BaseModel { + apiSpec: string + price: ImagePrice } -export const DalleGPTModels: Record = { - '1024x1792': { - size: '1024x1792', - price: 12 // 0.12 - }, - '1792x1024': { - size: '1792x1024', - price: 12 // 0.12 - }, - '1024x1024': { - size: '1024x1024', - price: 8 // 0.08 - }, - '512x512': { - size: '512x512', - price: 8 // 0.10 - }, - '256x256': { - size: '256x256', - price: 8 // 0.10 - } +export type LLMModel = ChatModel | ImageModel + +export interface LLMData { + chatModels: Record + imageModels: Record } diff --git a/src/modules/llms/vertexBot.ts b/src/modules/llms/vertexBot.ts index b84d2379..0c8c375d 100644 --- a/src/modules/llms/vertexBot.ts +++ b/src/modules/llms/vertexBot.ts @@ -5,22 +5,25 @@ import { type ChatConversation } from '../types' import { - hasBardPrefix, - hasGeminiPrefix, + hasCommandPrefix, isMentioned, SupportedCommands } from './utils/helpers' import { type LlmCompletion } from './api/llmApi' -import { LlmsModelsEnum } from './utils/types' import { LlmsBase } from './llmsBase' import { vertexCompletion, vertexStreamCompletion } from './api/vertex' import { type SubagentBase } from '../subagents' +import { + LlmModelsEnum, + type ModelVersion +} from './utils/llmModelsManager' -const models = [LlmsModelsEnum.BISON, LlmsModelsEnum.GEMINI, LlmsModelsEnum.GEMINI_15] export class VertexBot extends LlmsBase { + private readonly geminiPrefix: string[] constructor (payments: BotPayments, subagents?: SubagentBase[]) { - super(payments, 'VertexBot', 'llms', models, subagents) + super(payments, 'VertexBot', 'llms', subagents) + this.geminiPrefix = this.modelManager.getPrefixByModel(LlmModelsEnum.GEMINI_10) ?? [] } public getEstimatedPrice (ctx: any): number { @@ -31,12 +34,10 @@ export class VertexBot extends LlmsBase { ctx: OnMessageContext | OnCallBackQueryData ): boolean { const hasCommand = ctx.hasCommand([ - // SupportedCommands.bard, - // SupportedCommands.bardF, - SupportedCommands.gemini, - SupportedCommands.gShort, - SupportedCommands.g15short, - SupportedCommands.gemini15]) + this.commandsEnum.GEMINI, + this.commandsEnum.G, + this.commandsEnum.G15, + this.commandsEnum.GEMINI15]) if (isMentioned(ctx)) { return true } @@ -49,12 +50,12 @@ export class VertexBot extends LlmsBase { async chatStreamCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum, + model: ModelVersion, ctx: OnMessageContext | OnCallBackQueryData, msgId: number, limitTokens: boolean): Promise { return await vertexStreamCompletion(conversation, - model as LlmsModelsEnum, + model, ctx, msgId, true // telegram messages has a character limit @@ -63,14 +64,14 @@ export class VertexBot extends LlmsBase { async chatCompletion ( conversation: ChatConversation[], - model: LlmsModelsEnum + model: ModelVersion ): Promise { return await vertexCompletion(conversation, model) } hasPrefix (prompt: string): string { return ( - hasGeminiPrefix(prompt) || hasBardPrefix(prompt) + hasCommandPrefix(prompt, this.geminiPrefix) ) } @@ -86,14 +87,14 @@ export class VertexBot extends LlmsBase { // await this.onChat(ctx, LlmsModelsEnum.BISON, false, false) // return // } - if (ctx.hasCommand([SupportedCommands.gemini, SupportedCommands.gShort]) || (hasGeminiPrefix(ctx.message?.text ?? '') !== '')) { - this.updateSessionModel(ctx, LlmsModelsEnum.GEMINI) - await this.onChat(ctx, LlmsModelsEnum.GEMINI, true, false) + if (ctx.hasCommand([this.commandsEnum.GEMINI, this.commandsEnum.G]) || (hasCommandPrefix(ctx.message?.text ?? '', this.geminiPrefix))) { + this.updateSessionModel(ctx, LlmModelsEnum.GEMINI_10) + await this.onChat(ctx, LlmModelsEnum.GEMINI_10, true, false) return } - if (ctx.hasCommand([SupportedCommands.gemini15, SupportedCommands.g15short])) { - this.updateSessionModel(ctx, LlmsModelsEnum.GEMINI_15) - await this.onChat(ctx, LlmsModelsEnum.GEMINI_15, true, false) + if (ctx.hasCommand([this.commandsEnum.GEMINI15, this.commandsEnum.G15])) { + this.updateSessionModel(ctx, LlmModelsEnum.GEMINI_15) + await this.onChat(ctx, LlmModelsEnum.GEMINI_15, true, false) // return } diff --git a/src/modules/types.ts b/src/modules/types.ts index a5d23de3..efe12154 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -12,11 +12,12 @@ import { type AutoChatActionFlavor } from '@grammyjs/auto-chat-action' import { type PhotoSize, type ParseMode } from 'grammy/types' import { type InlineKeyboardMarkup } from 'grammy/out/types' import type { FileFlavor } from '@grammyjs/files' -import { type LlmsModelsEnum } from './llms/utils/types' +import { type ModelVersion } from './llms/utils/llmModelsManager' +import { type DalleImageSize } from './llms/utils/types' export interface ImageGenSessionData { numImages: number - imgSize: string + imgSize: DalleImageSize isEnabled: boolean isInscriptionLotteryEnabled: boolean imgRequestQueue: ImageRequest[] @@ -170,7 +171,7 @@ export interface BotSessionData { chatGpt: LlmsSessionData subagents: SubagentSessionData dalle: ImageGenSessionData - currentModel: LlmsModelsEnum + currentModel: ModelVersion } export interface TransientStateContext { diff --git a/src/modules/voice-command/index.ts b/src/modules/voice-command/index.ts index e2a0a5a0..7d04bb49 100644 --- a/src/modules/voice-command/index.ts +++ b/src/modules/voice-command/index.ts @@ -2,21 +2,29 @@ import fs from 'fs' import pino from 'pino' import type { Logger } from 'pino' import { speechToText } from '../llms/api/openai' -import { RequestState, type OnMessageContext, type PayableBot } from '../types' +import { + RequestState, + type OnMessageContext, + type PayableBot +} from '../types' import { download } from '../../utils/files' import config from '../../config' import { type OpenAIBot } from '../llms' -import { sendMessage, SupportedCommands as OpenAISupportedCommands } from '../llms/utils/helpers' +import { + sendMessage, + SupportedCommands as OpenAISupportedCommands +} from '../llms/utils/helpers' import { promptHasBadWords } from '../sd-images/helpers' import { now } from '../../utils/perf' +import { + LlmCommandsEnum, + LlmModelsEnum +} from '../llms/utils/llmModelsManager' -const VOICE_COMMAND_LIST = [ - OpenAISupportedCommands.vision, - OpenAISupportedCommands.ask, - OpenAISupportedCommands.dalleImg, - OpenAISupportedCommands.talk -] export class VoiceCommand implements PayableBot { + private readonly voiceCommandList: string[] + protected modelsEnum = LlmModelsEnum + protected commandsEnum = LlmCommandsEnum public readonly module = 'VoiceCommand' private readonly logger: Logger private readonly openAIBot: OpenAIBot @@ -30,6 +38,12 @@ export class VoiceCommand implements PayableBot { options: { colorize: true } } }) + this.voiceCommandList = [ + this.commandsEnum.VISION, + this.commandsEnum.ASK, + this.commandsEnum.DALLE, + OpenAISupportedCommands.talk + ] this.openAIBot = openAIBot } @@ -49,10 +63,9 @@ export class VoiceCommand implements PayableBot { } getCommand (transcribedText: string): string { - const prefixList = VOICE_COMMAND_LIST - for (let i = 0; i < prefixList.length; i++) { - if (transcribedText.toLocaleLowerCase().startsWith(prefixList[i])) { - return prefixList[i] + for (let i = 0; i < this.voiceCommandList.length; i++) { + if (transcribedText.toLocaleLowerCase().startsWith(this.voiceCommandList[i])) { + return this.voiceCommandList[i] } } return '' diff --git a/src/modules/voice-to-voice-gpt/index.ts b/src/modules/voice-to-voice-gpt/index.ts index 3034d29c..526639e8 100644 --- a/src/modules/voice-to-voice-gpt/index.ts +++ b/src/modules/voice-to-voice-gpt/index.ts @@ -5,8 +5,8 @@ import type { Logger } from 'pino' import type { BotPayments } from '../payment' import { speechToText, chatCompletion } from '../llms/api/openai' import type { OnMessageContext, PayableBot } from '../types' -import { LlmsModelsEnum } from '../llms/utils/types' import { generateVoiceFromText } from './helpers' +import { LlmModelsEnum } from '../llms/utils/llmModelsManager' export class VoiceToVoiceGPTBot implements PayableBot { private readonly payments: BotPayments @@ -64,7 +64,7 @@ export class VoiceToVoiceGPTBot implements PayableBot { fs.rmSync(filename) const conversation = [{ role: 'user', content: resultText }] - const response = await chatCompletion(conversation, LlmsModelsEnum.GPT_35_TURBO_16K) + const response = await chatCompletion(conversation, LlmModelsEnum.GPT_35_TURBO) const voiceResult = await generateVoiceFromText(response.completion?.content as string) // const voiceResult = await gcTextToSpeedClient.ssmlTextToSpeech({ text: response.completion, ssmlGender: 'MALE', languageCode: 'en-US' })