From 2026c673f5f06a21af2c22e06e856abf42f2d4df Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Thu, 12 Feb 2026 14:22:43 +0300 Subject: [PATCH] checking models capabilities --- src/commands/gemini-get-model.ts | 19 ++++++++ src/commands/info.ts | 70 +++++------------------------- src/commands/mistral-chat.ts | 21 ++++++++- src/commands/mistral-get-model.ts | 19 ++++++++ src/commands/ollama-chat.ts | 6 +-- src/commands/ollama-get-model.ts | 55 +++++++++++++++++------ src/commands/openai-get-model.ts | 16 +++++++ src/common/environment.ts | 10 +---- src/model/ai-capability-info.ts | 5 +++ src/model/ai-model-capabilities.ts | 8 ++++ src/model/ai-provider.ts | 6 +++ src/util/utils.ts | 67 +++++++++++++++++++++++++++- 12 files changed, 216 insertions(+), 86 deletions(-) create mode 100644 src/model/ai-capability-info.ts create mode 100644 src/model/ai-model-capabilities.ts create mode 100644 src/model/ai-provider.ts diff --git a/src/commands/gemini-get-model.ts b/src/commands/gemini-get-model.ts index d2ba9bf..38a053e 100644 --- a/src/commands/gemini-get-model.ts +++ b/src/commands/gemini-get-model.ts @@ -2,6 +2,8 @@ import {Command} from "../base/command"; import {Message} from "typescript-telegram-bot-api"; import {logError, replyToMessage} from "../util/utils"; import {Environment} from "../common/environment"; +import {googleAi} from "../index"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; export class GeminiGetModel extends Command { title = "/geminiGetModel"; @@ -10,4 +12,21 @@ export class GeminiGetModel extends Command { async execute(msg: Message): Promise { await replyToMessage({message: msg, text: `Текущая модель: "${Environment.GEMINI_MODEL}"`}).catch(logError); } + + async getModelCapabilities(): Promise { + try { + const info = await googleAi.models.get({model: Environment.GEMINI_MODEL}); + console.log(info); + + return { + vision: {supported: true}, + ocr: null, + thinking: {supported: info.thinking}, + tools: null + }; + } catch (e) { + logError(e); + return null; + } + } } \ No newline at end of file diff --git a/src/commands/info.ts b/src/commands/info.ts index 397d367..1b6f862 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -1,11 +1,10 @@ import {ChatCommand} from "../base/chat-command"; import {Message} from "typescript-telegram-bot-api"; import {callbackCommands, commands} from "../index"; -import {AiProvider, Environment} from "../common/environment"; -import {boolToEmoji, logError, replyToMessage} from "../util/utils"; -import {OllamaGetModel} from "./ollama-get-model"; - -type AiCapabilityInfo = { supported?: boolean, external?: boolean, model?: string }; +import {Environment} from "../common/environment"; +import {boolToEmoji, getCurrentModel, getCurrentModelCapabilities, logError, replyToMessage} from "../util/utils"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; +import {AiProvider} from "../model/ai-provider"; export class Info extends ChatCommand { command = ["info", "v"]; @@ -15,73 +14,28 @@ export class Info extends ChatCommand { async execute(msg: Message): Promise { const aiProvider = Environment.DEFAULT_AI_PROVIDER; - let aiModel: string; - let aiVisionSupported: AiCapabilityInfo = {}; - let aiThinkingSupported: AiCapabilityInfo = {}; - let aiToolsSupported: AiCapabilityInfo = {}; + const aiModel = getCurrentModel(); + let aiModelCapabilities: AiModelCapabilities = {}; try { - switch (aiProvider) { - case AiProvider.OLLAMA: { - const ollamaGetModel = commands.find(c => c instanceof OllamaGetModel); - - aiModel = Environment.OLLAMA_MODEL; - aiVisionSupported = { - supported: (await ollamaGetModel.loadImageModelInfo()).capabilities.includes("vision"), - external: Environment.OLLAMA_IMAGE_MODEL !== Environment.OLLAMA_MODEL, - model: Environment.OLLAMA_IMAGE_MODEL - }; - - aiThinkingSupported = { - supported: (await ollamaGetModel.loadThinkModelInfo()).capabilities.includes("thinking"), - external: Environment.OLLAMA_THINK_MODEL !== Environment.OLLAMA_MODEL, - model: Environment.OLLAMA_THINK_MODEL - }; - - aiToolsSupported = { - supported: (await ollamaGetModel.loadModelInfo()).capabilities.includes("tools"), - external: false, - model: Environment.OLLAMA_MODEL - }; - break; - } - case AiProvider.GEMINI: - aiModel = Environment.GEMINI_MODEL; - - aiVisionSupported = {supported: true}; - aiThinkingSupported = {}; - aiToolsSupported = {}; - break; - case AiProvider.MISTRAL: - aiModel = Environment.MISTRAL_MODEL; - - aiVisionSupported = {supported: true}; - aiThinkingSupported = {}; - aiToolsSupported = {}; - break; - case AiProvider.OPENAI: - aiModel = Environment.OPENAI_MODEL; - - aiVisionSupported = {}; - aiThinkingSupported = {}; - aiToolsSupported = {}; - break; - } + aiModelCapabilities = await getCurrentModelCapabilities(); } catch (e) { logError(e); await replyToMessage({message: msg, text: `Произошла ошибка: ${e}`}).catch(logError); return; } + const aiInfo = "```" + "AI\n" + `supported providers: ${Object.keys(AiProvider).filter(key => isNaN(Number(key))).length}\n\n` + `provider: ${aiProvider.toLowerCase()}\n` + `model: ${aiModel}\n\n` + - `vision${aiVisionSupported.external ? "(ext)" : ""}: ${boolToEmoji(aiVisionSupported.supported)}\n` + - `thinking${aiThinkingSupported.external ? "(ext)" : ""}: ${boolToEmoji(aiThinkingSupported.supported)}\n` + - `tools: ${boolToEmoji(aiToolsSupported.supported)}` + + `vision${aiModelCapabilities.vision?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.vision?.supported)}\n` + + `ocr${aiModelCapabilities.ocr?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.ocr?.supported)}\n` + + `thinking${aiModelCapabilities.thinking?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.thinking?.supported)}\n` + + `tools${aiModelCapabilities.tools?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities.tools?.supported)}` + "```"; const cmds = commands.filter(c => !(c instanceof ChatCommand)); diff --git a/src/commands/mistral-chat.ts b/src/commands/mistral-chat.ts index 87035b4..0c088ab 100644 --- a/src/commands/mistral-chat.ts +++ b/src/commands/mistral-chat.ts @@ -6,12 +6,14 @@ import { escapeMarkdownV2Text, logError, oldReplyToMessage, + replyToMessage, startIntervalEditor } from "../util/utils"; import {Environment} from "../common/environment"; -import {bot, mistralAi} from "../index"; +import {bot, commands, mistralAi} from "../index"; import {MessageStore} from "../common/message-store"; import {ChatCommand} from "../base/chat-command"; +import {MistralGetModel} from "./mistral-get-model"; export class MistralChat extends ChatCommand { command = "mistral"; @@ -67,6 +69,23 @@ export class MistralChat extends ChatCommand { return total + (curr.content.filter(c => c.type === "image_url")?.length ?? 0); }, 0); + if (imagesCount) { + try { + const modelInfo = await commands.find(c => c instanceof MistralGetModel).getModelCapabilities(); + if (modelInfo) { + if (!modelInfo.vision?.supported) { + await replyToMessage({ + message: msg, + text: "Моя текущая модель не умеет анализировать изображения 🥹" + }); + return; + } + } + } catch (e) { + logError(e); + } + } + waitMessage = await bot.sendMessage({ chat_id: chatId, text: imagesCount ? diff --git a/src/commands/mistral-get-model.ts b/src/commands/mistral-get-model.ts index 1d963ed..39de831 100644 --- a/src/commands/mistral-get-model.ts +++ b/src/commands/mistral-get-model.ts @@ -4,6 +4,8 @@ import {logError, replyToMessage} from "../util/utils"; import {Environment} from "../common/environment"; import {Requirements} from "../base/requirements"; import {Requirement} from "../base/requirement"; +import {mistralAi} from "../index"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; export class MistralGetModel extends Command { title = "/mistralGetModel"; @@ -14,4 +16,21 @@ export class MistralGetModel extends Command { async execute(msg: Message): Promise { await replyToMessage({message: msg, text: `Текущая модель: "${Environment.MISTRAL_MODEL}"`}).catch(logError); } + + async getModelCapabilities(): Promise { + try { + const info = await mistralAi.models.retrieve({modelId: Environment.MISTRAL_MODEL}); + console.log(info); + + return { + vision: {supported: info.capabilities.vision}, + ocr: {supported: info.capabilities.ocr}, + thinking: null, + tools: {supported: info.capabilities.functionCalling} + }; + } catch (e) { + logError(e); + return null; + } + } } \ No newline at end of file diff --git a/src/commands/ollama-chat.ts b/src/commands/ollama-chat.ts index 1c01ecb..9e7b1ad 100644 --- a/src/commands/ollama-chat.ts +++ b/src/commands/ollama-chat.ts @@ -59,8 +59,7 @@ export class OllamaChat extends ChatCommand { try { const modelInfo = await commands.find(c => c instanceof OllamaGetModel).loadImageModelInfo(); if (modelInfo) { - const caps = modelInfo.capabilities || []; - if (!caps.includes("vision")) { + if (!modelInfo.vision?.supported) { await replyToMessage({ message: msg, text: "Моя текущая модель не умеет анализировать изображения 🥹" @@ -77,8 +76,7 @@ export class OllamaChat extends ChatCommand { try { const modelInfo = await commands.find(c => c instanceof OllamaGetModel).loadThinkModelInfo(); if (modelInfo) { - const caps = modelInfo.capabilities || []; - if (!caps.includes("thinking")) { + if (!modelInfo.thinking?.supported) { await replyToMessage({ message: msg, text: "Моя текущая модель не умеет размышлять 🥹" diff --git a/src/commands/ollama-get-model.ts b/src/commands/ollama-get-model.ts index 89cc80c..a334a38 100644 --- a/src/commands/ollama-get-model.ts +++ b/src/commands/ollama-get-model.ts @@ -3,7 +3,7 @@ import {Message} from "typescript-telegram-bot-api"; import {boolToEmoji, logError, replyToMessage} from "../util/utils"; import {Environment} from "../common/environment"; import {ollama} from "../index"; -import {ShowResponse} from "ollama"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; export class OllamaGetModel extends Command { title = "/ollamaGetModel"; @@ -15,7 +15,7 @@ export class OllamaGetModel extends Command { const imageModel = Environment.OLLAMA_IMAGE_MODEL; const thinkModel = Environment.OLLAMA_THINK_MODEL; - const promises: (Promise | null)[] = [this.loadModelInfo()]; + const promises: (Promise | null)[] = [this.getModelCapabilities()]; if (imageModel && imageModel !== model) { promises.push(this.loadImageModelInfo()); @@ -62,24 +62,51 @@ export class OllamaGetModel extends Command { } } - private getModelText(model: string, info: ShowResponse): string { - const caps = info.capabilities; - + private getModelText(model: string, info: AiModelCapabilities): string { return `model: ${model}\n\n` + - `vision: ${boolToEmoji(caps.includes("vision"))}\n` + - `thinking: ${boolToEmoji(caps.includes("thinking"))}\n` + - `tools: ${boolToEmoji(caps.includes("tools"))}`; + `vision: ${boolToEmoji(info.vision?.supported)}\n` + + `thinking: ${boolToEmoji(info.thinking?.supported)}\n` + + `tools: ${boolToEmoji(info.tools?.supported)}`; } - async loadModelInfo(): Promise { - return ollama.show({model: Environment.OLLAMA_MODEL}); + async getModelCapabilities(model: string = Environment.OLLAMA_MODEL): Promise { + try { + const info = await ollama.show({model: model}); + console.log(info); + + return { + vision: { + supported: info.capabilities.includes("vision"), + external: model !== Environment.OLLAMA_MODEL, + model: model + }, + ocr: { + supported: info.capabilities.includes("ocr"), + external: model !== Environment.OLLAMA_MODEL, + model: model + }, + thinking: { + supported: info.capabilities.includes("thinking"), + external: model !== Environment.OLLAMA_MODEL, + model: model + }, + tools: { + supported: info.capabilities.includes("tools"), + external: model !== Environment.OLLAMA_MODEL, + model: model + }, + }; + } catch (e) { + logError(e); + return null; + } } - async loadImageModelInfo(): Promise { - return ollama.show({model: Environment.OLLAMA_IMAGE_MODEL}); + async loadImageModelInfo(): Promise { + return this.getModelCapabilities(Environment.OLLAMA_IMAGE_MODEL); } - async loadThinkModelInfo(): Promise { - return ollama.show({model: Environment.OLLAMA_THINK_MODEL}); + async loadThinkModelInfo(): Promise { + return this.getModelCapabilities(Environment.OLLAMA_THINK_MODEL); } } \ No newline at end of file diff --git a/src/commands/openai-get-model.ts b/src/commands/openai-get-model.ts index e0642d3..aa0f42f 100644 --- a/src/commands/openai-get-model.ts +++ b/src/commands/openai-get-model.ts @@ -2,6 +2,7 @@ import {Command} from "../base/command"; import {Message} from "typescript-telegram-bot-api"; import {logError, replyToMessage} from "../util/utils"; import {Environment} from "../common/environment"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; export class OpenAIGetModel extends Command { title = "/openAIGetModel"; @@ -10,4 +11,19 @@ export class OpenAIGetModel extends Command { async execute(msg: Message): Promise { await replyToMessage({message: msg, text: `Текущая модель: "${Environment.OPENAI_MODEL}"`}).catch(logError); } + + async getModelCapabilities(): Promise { + // TODO: 12/02/2026, Danil Nikolaev: find solution + try { + return { + vision: {supported: true}, + ocr: null, + thinking: {supported: true}, + tools: {supported: true}, + }; + } catch (e) { + logError(e); + return null; + } + } } \ No newline at end of file diff --git a/src/common/environment.ts b/src/common/environment.ts index e75217f..2ad9584 100644 --- a/src/common/environment.ts +++ b/src/common/environment.ts @@ -2,13 +2,7 @@ import path from "node:path"; import {saveData} from "../db/database"; import {Answers} from "../model/answers"; import {ifTrue} from "../util/utils"; - -export enum AiProvider { - OLLAMA = "OLLAMA", - GEMINI = "GEMINI", - MISTRAL = "MISTRAL", - OPENAI = "OPENAI", -} +import {AiProvider} from "../model/ai-provider"; export class Environment { static BOT_TOKEN: string; @@ -94,7 +88,7 @@ export class Environment { Environment.GEMINI_IMAGE_MODEL = process.env.GEMINI_IMAGE_MODEL || "gemini-2.5-flash-image"; Environment.MISTRAL_API_KEY = process.env.MISTRAL_API_KEY; - Environment.MISTRAL_MODEL = process.env.MISTRAL_MODEL || "mistral-small-latest"; + Environment.MISTRAL_MODEL = process.env.MISTRAL_MODEL || "mistral-tiny-latest"; Environment.OPENAI_BASE_URL = process.env.OPENAI_BASE_URL; Environment.OPENAI_API_KEY = process.env.OPENAI_API_KEY; diff --git a/src/model/ai-capability-info.ts b/src/model/ai-capability-info.ts new file mode 100644 index 0000000..345928a --- /dev/null +++ b/src/model/ai-capability-info.ts @@ -0,0 +1,5 @@ +export type AiCapabilityInfo = { + supported?: boolean, + external?: boolean, + model?: string +}; \ No newline at end of file diff --git a/src/model/ai-model-capabilities.ts b/src/model/ai-model-capabilities.ts new file mode 100644 index 0000000..ca540fb --- /dev/null +++ b/src/model/ai-model-capabilities.ts @@ -0,0 +1,8 @@ +import {AiCapabilityInfo} from "./ai-capability-info"; + +export class AiModelCapabilities { + vision?: AiCapabilityInfo; + ocr?: AiCapabilityInfo; + thinking?: AiCapabilityInfo; + tools?: AiCapabilityInfo; +} \ No newline at end of file diff --git a/src/model/ai-provider.ts b/src/model/ai-provider.ts new file mode 100644 index 0000000..cd611ea --- /dev/null +++ b/src/model/ai-provider.ts @@ -0,0 +1,6 @@ +export enum AiProvider { + OLLAMA = "OLLAMA", + GEMINI = "GEMINI", + MISTRAL = "MISTRAL", + OPENAI = "OPENAI", +} \ No newline at end of file diff --git a/src/util/utils.ts b/src/util/utils.ts index 876c858..96c9bd6 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -13,7 +13,7 @@ import { PhotoSize, User } from "typescript-telegram-bot-api"; -import {AiProvider, Environment} from "../common/environment"; +import {Environment} from "../common/environment"; import {TelegramError} from "typescript-telegram-bot-api/dist/errors"; import {bot, botUser, callbackCommands, commands, messageDao, ollama} from "../index"; import os from "os"; @@ -37,6 +37,12 @@ import {WebSearchResponse} from "../model/web-search-response"; import {GeminiChat} from "../commands/gemini-chat"; import {MistralChat} from "../commands/mistral-chat"; import {OpenAIChat} from "../commands/openai-chat"; +import {AiProvider} from "../model/ai-provider"; +import {AiModelCapabilities} from "../model/ai-model-capabilities"; +import {OllamaGetModel} from "../commands/ollama-get-model"; +import {GeminiGetModel} from "../commands/gemini-get-model"; +import {MistralGetModel} from "../commands/mistral-get-model"; +import {OpenAIGetModel} from "../commands/openai-get-model"; export const ignore = () => { }; @@ -1060,6 +1066,65 @@ export function photoPathByUniqueId(uniqueId: string): string { return path.join(Environment.DATA_PATH, "photo", uniqueId + ".jpg"); } +export function getCurrentModel(): string { + switch (Environment.DEFAULT_AI_PROVIDER) { + case AiProvider.OLLAMA: + return Environment.OLLAMA_MODEL; + case AiProvider.GEMINI: + return Environment.GEMINI_MODEL; + case AiProvider.MISTRAL: + return Environment.MISTRAL_MODEL; + case AiProvider.OPENAI: + return Environment.OPENAI_MODEL; + } +} + +export async function getCurrentModelCapabilities(): Promise { + let promise: Promise = null; + switch (Environment.DEFAULT_AI_PROVIDER) { + case AiProvider.OLLAMA: { + const ollamaGetModel = commands.find(c => c instanceof OllamaGetModel); + + // eslint-disable-next-line no-async-promise-executor + promise = new Promise(async (resolve, reject) => { + try { + const result = { + vision: (await ollamaGetModel.loadImageModelInfo()).vision, + ocr: null, + thinking: (await ollamaGetModel.loadThinkModelInfo()).thinking, + tools: (await ollamaGetModel.getModelCapabilities()).tools + }; + resolve(result); + } catch (e) { + reject(e); + } + }); + break; + } + case AiProvider.GEMINI: { + promise = commands.find(c => c instanceof GeminiGetModel).getModelCapabilities(); + break; + } + case AiProvider.MISTRAL: { + promise = commands.find(c => c instanceof MistralGetModel).getModelCapabilities(); + break; + } + case AiProvider.OPENAI: { + promise = commands.find(c => c instanceof OpenAIGetModel).getModelCapabilities(); + break; + } + } + + if (!promise) return null; + + try { + return await promise; + } catch (e) { + logError(e); + return null; + } +} + export async function processMyChatMember(u: ChatMemberUpdated): Promise { console.log("my_chat_member", u); }