From b0541faa3db70db41d457eabea384e05f5be7747 Mon Sep 17 00:00:00 2001 From: huker667 Date: Tue, 15 Sep 2026 21:34:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=9B=D0=AC=D0=A2=D0=A0=D0=90=20=D0=9A?= =?UTF-8?q?=D0=A0=D0=A3=D0=A2=D0=9E=D0=99=20=D0=92=D0=90=D0=99=D0=91=D0=9A?= =?UTF-8?q?=D0=9E=D0=94=20=D0=A4=D0=98=D0=9A=D0=A1=D0=AB=20=D0=98=20=D0=90?= =?UTF-8?q?=D0=92=D0=A2=D0=9E=D0=9C=D0=9E=D0=94=D0=95=D0=9B=D0=AC=20=D0=A2?= =?UTF-8?q?=D0=95=D0=A1=D0=9B=D0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- callbacks.py | 65 ++++++++---- config_def.py | 10 +- helpers.py | 39 ++++++++ main.py | 25 +++-- requirements.txt | 2 +- supergenerator.py | 249 +++++++++++++++++++++++++++++++--------------- trust.py | 36 +++++++ vars.py | 8 ++ 8 files changed, 328 insertions(+), 106 deletions(-) create mode 100644 trust.py diff --git a/callbacks.py b/callbacks.py index 2182d45..2b392e7 100644 --- a/callbacks.py +++ b/callbacks.py @@ -24,32 +24,39 @@ async def select_provider_callback(callback: CallbackQuery): config = get_user_config(user_id) user_model = config.get("model") - + buttons = [] i = 0 + shown = set() for model in MODELS.get(provider): + base_model = model.split("*")[0] if "*" in model else model + if base_model in shown: + continue + shown.add(base_model) + i += 1 - if f"{provider}*{model}" == user_model: - text = f"{model} ✅" + full_model = f"{provider}*{base_model}" + if full_model == user_model or user_model.startswith(full_model + "*"): + text = f"{base_model} ✅" else: - text = model - + text = base_model + if MODELS[provider][model].get("alias"): - model = MODELS[provider][model].get("alias") + base_model = MODELS[provider][model].get("alias") if not i % 2: buttons[len(buttons)-1] +=[ InlineKeyboardButton( - text=text, callback_data=f"set_model%{user_id}%{provider}*{model}" + text=text, callback_data=f"set_model%{user_id}%{provider}*{base_model}" ) ] else: buttons.append( [ InlineKeyboardButton( - text=text, callback_data=f"set_model%{user_id}%{provider}*{model}" + text=text, callback_data=f"set_model%{user_id}%{provider}*{base_model}" ) ] ) @@ -75,9 +82,12 @@ async def select_provider_callback(callback: CallbackQuery): @router.callback_query(F.data.startswith("models%")) -async def models_callback(callback: CallbackQuery): +async def models_callback(callback: CallbackQuery, alo=""): current_user_id = str(callback.from_user.id) - data = callback.data.split("%", 1) + if alo: + data = alo.split("%", 1) + else: + data = callback.data.split("%", 1) user_id = data[1] if user_id == current_user_id: config = get_user_config(user_id) @@ -85,6 +95,16 @@ async def models_callback(callback: CallbackQuery): buttons = [] i = 0 + + if AUTO_MODEL and AUTO_MODELS: + if config["model"] == "all*auto": + buttons.append( + [InlineKeyboardButton(text="✅ автовыбор", callback_data=f"set_model%{user_id}%{DEFAULT_MODEL}")] + ) + else: + buttons.append( + [InlineKeyboardButton(text="😱 автовыбор", callback_data=f"set_model%{user_id}%all*auto")] + ) for provider in MODELS: i += 1 @@ -134,12 +154,16 @@ async def set_model_callback(callback: CallbackQuery): user_id, new_model = data[1], data[2] provider = data[2].split("*", 1)[0] amodel = data[2].split("*", 1)[1] - new_model = provider + "*" + amodel - - for model in MODELS.get(provider): - nmodel = MODELS[provider][model].get("alias") or None - if nmodel == amodel: - new_model = provider + "*" + model + + if amodel == "auto" and provider == "all": + new_model = "all*auto" + else: + new_model = provider + "*" + amodel + for model in MODELS.get(provider, {}): + nmodel = MODELS[provider][model].get("alias") or None + if nmodel == amodel: + new_model = provider + "*" + model + break if user_id == current_user_id: current_config = get_user_config(user_id) @@ -153,7 +177,14 @@ async def set_model_callback(callback: CallbackQuery): current_config.get("markdown"), current_config.get("prompts") ) - await callback.message.edit_text(f"выбрана модель: `{new_model}`") + await callback.message.edit_text( + f"выбрана модель: `{new_model}`", + reply_markup=InlineKeyboardMarkup(inline_keyboard= + [ + [InlineKeyboardButton(text="⬅️ назад", callback_data=f"models%{user_id}")] + ] + ) + ) await callback.answer(f"выбрано: {new_model}") else: await callback.answer("нет, не трогай") diff --git a/config_def.py b/config_def.py index 60195be..3504a2b 100644 --- a/config_def.py +++ b/config_def.py @@ -34,6 +34,14 @@ BOT_USERNAME = "UzbekGPTTestBot" # на что бот откликается BOT_CALLS = ["узбек", "uzbek", f"@{BOT_USERNAME}"] +# включить в настройках автовыбор модели +AUTO_MODEL = True +AUTO_MODELS = [ + "ollama*gemma4:31b-cloud*1", + "ollama*gemma4:31b-cloud*2", + "krutoiapi*claude-mythos-5", +] + # дефолт настройки для новых юзеров DEFAULT_MODEL = "ollama*gemma4:31b-cloud" DEFAULT_STREAM = "none" @@ -75,7 +83,7 @@ PROVIDERS = { "name": "оллама", "module": "openai", "url": "http://127.0.0.1:11434/v1", - "key": "da", + "key": ["da", "da2"], }, "krutoiapi": { "name": "ваше крутое api", diff --git a/helpers.py b/helpers.py index a65890a..359603e 100644 --- a/helpers.py +++ b/helpers.py @@ -269,6 +269,45 @@ def clean_tail_repeats(text: str, max_repeat: int = 10): return cleaned, removed_count +def get_key(provider, model_full): + prov = PROVIDERS.get(provider, {}) + key = prov.get("key", "") + if isinstance(key, list): + parts = model_full.split("*") + if len(parts) >= 3: + try: + idx = int(parts[2]) - 1 + except ValueError: + return key[0] + if 0 <= idx < len(key): + return key[idx] + return key[0] + return key + + +_expanded_auto_models = None + + +def expand_auto_models(): + global _expanded_auto_models + if _expanded_auto_models is not None: + return _expanded_auto_models + result = [] + for entry in AUTO_MODELS: + parts = entry.split("*", 2) + provider = parts[0] + model = parts[1] + suffix = parts[2] if len(parts) >= 3 else None + prov_key = PROVIDERS.get(provider, {}).get("key", "") + if isinstance(prov_key, list) and not suffix: + for i in range(len(prov_key)): + result.append(f"{provider}*{model}*{i + 1}") + else: + result.append(entry) + _expanded_auto_models = result + return result + + def parse_response(text: str): if "connection error." in text: return { diff --git a/main.py b/main.py index 23b6829..822cbe4 100644 --- a/main.py +++ b/main.py @@ -161,20 +161,29 @@ async def guest_middleware(handler, event, data): voice = None - model = config["model"].split("*", 1) + model = config["model"].split("*") + is_auto = config["model"] == "all*auto" and AUTO_MODEL and AUTO_MODELS if VOSK_API != "": if message.voice: voice = message.voice elif replied and replied.voice: voice = replied.voice - - process = { - "text": "wait", - "photo_a": "wait" if replied and replied.photo and MODELS[model[0]].get("vision", False) == False else None, - "photo": "wait" if message.photo and MODELS[model[0]][model[1]].get("vision", False) == False else None, - "stt": "wait" if voice else None, - } + + if is_auto: + process = { + "text": "wait", + "photo_a": "wait" if replied and replied.photo else None, + "photo": "wait" if message.photo else None, + "stt": "wait" if voice else None, + } + else: + process = { + "text": "wait", + "photo_a": "wait" if replied and replied.photo and MODELS[model[0]].get("vision", False) == False else None, + "photo": "wait" if message.photo and MODELS[model[0]][model[1]].get("vision", False) == False else None, + "stt": "wait" if voice else None, + } if event.message: if (config["stream"] == "edit") or (config["stream"] == "native" and user_id == message.chat.id): diff --git a/requirements.txt b/requirements.txt index 2b3414d..f1f0550 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiogram==3.29.1 +aiogram==3.31.0 python-dotenv==1.2.1 pillow==12.0.0 requests diff --git a/supergenerator.py b/supergenerator.py index 36d9929..ec94f44 100644 --- a/supergenerator.py +++ b/supergenerator.py @@ -1,20 +1,13 @@ -import random -import re -import shelve -import time import ai -from configparser import DEFAULTSECT - -from pprint import pprint - from statistics import * from colors import * from helpers import * from config import * from vars import * +from trust import * -from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError +from aiogram.exceptions import TelegramBadRequest async def generate( @@ -25,19 +18,19 @@ async def generate( user_contexts[user_id] = [] config = get_user_config(user_id) - if "*" in config.get("model", ""): - model = config.get("model").split("*", 2) - else: - print(f"{RED} -- err model - failed to get prov&model in \"{config.get('model','')}\"{RESET}") + model_str = config.get("model", "") + if "*" not in model_str: + print(f"{RED} -- err model - failed to get prov&model in \"{model_str}\"{RESET}") return "произошла ошибка при парсинге вашей выбранной модели. либо хост бота гандон либо тебе надо сменить модель в настройках!!" - settings = MODELS.get(model[0]).get(model[1]) - + is_auto = model_str == "all*auto" and AUTO_MODEL and AUTO_MODELS + model = model_str.split("*") + stream_cf = config.get("stream", "none") system_prompt_name = config.get("prompt", "") user_prompts = config.get("prompts", {}) prompts = SYSTEM_PROMPTS | user_prompts - + if system_prompt_name in prompts: is_user_prompt = False if system_prompt_name in SYSTEM_PROMPTS: @@ -55,18 +48,12 @@ async def generate( f"2. конфиг бота настроен неправильно и надо связаться с тем кто его хостит" return text - if stream_cf == "native" and user_id == chat_id and settings.get("nostream", False) == False: - stream = True - elif stream_cf == "edit" and chat_id and message_id and settings.get("nostream", False) == False: - stream = True - elif stream_cf == "edit" or stream_cf == "native" and inline_id: - stream = True - else: - stream = False - content = [] - - if images: + + if images and not is_auto: + provider_name = model[0] + model_name = model[1] + settings = MODELS.get(provider_name, {}).get(model_name, {}) if settings.get("vision"): for image in images: content += [{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image['data']}"}}] @@ -89,7 +76,7 @@ async def generate( await msg.edit_text(make_graph(message, process)) content += [{"type": "text", "text": prompt}] - + user_contexts[user_id].append({"role": "user", "content": content}) user_contexts[user_id] = user_contexts[user_id][-MAX_CONTEXT:] @@ -101,22 +88,10 @@ async def generate( messages = [{"role": "system", "content": system_prompt}] + context else: - model = DEFAULT_MODEL.split("*", 2) - settings = MODELS.get(model[0]).get(model[1]) - stream = False + is_auto = True + model = ["all", "auto"] stream_cf = "none" content = [] - if images: - if settings.get("vision"): - for image in images: - content += [{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image['data']}"}}] - else: - for image in images: - try: - image_text = await image2text(image["data"]) - prompt = f"<фото>{image_text}\n{prompt}" - except Exception as e: - print(f"{RED} -- err photo - {e} : {model}{RESET}") content += [{"type": "text", "text": prompt}] messages = [{"role": "system", "content": SYSTEM_PROMPTS[DEFAULT_PROMPT]["text"]+ADD_TO_PROMPT+OLD_MD}] + [ @@ -130,37 +105,112 @@ async def generate( add_one_to("stats/gens") - try: - provider = PROVIDERS[model[0]] - except Exception: - text = "❌произошла ошибка, возможные проблемы:\n" \ - "1. у вас выбрана удалённая модель или провайдер\n" \ - "2. конфиг бота настроен неправильно и надо связаться с тем кто его хостит" - return text + if is_auto: + photo_summary = "" + if images: + for image in images: + try: + if process and msg and message: + process[image["process"]] = "make" + await msg.edit_text(make_graph(message, process)) + image_text = await image2text(image["data"]) + photo_summary += f"<фото>{image_text}\n" + except Exception as e: + if process and msg and message: + process[image["process"]] = "error" + await msg.edit_text(make_graph(message, process)) + print(f"{RED} -- err photo - {e} : all*auto{RESET}") + else: + if process and msg and message: + process[image["process"]] = "done" + await msg.edit_text(make_graph(message, process)) - if process and msg and message: - process["text"] = "make" - await msg.edit_text(make_graph(message, process)) - - text = await openai_generate_text( - api=provider, - bot=bot, - model=model[1], - settings=settings, - messages=messages, - stream=stream, - stream_cf=stream_cf, - chat_id=chat_id, - draft_id=chat_id, - message_id=message_id, - inline_id=inline_id - ) + expanded = expand_auto_models() + expanded = sorted(expanded, key=lambda e: get_trust(e.split("*"))[1], reverse=True) + last_type = "" + for entry in expanded: + if last_type == "da": + break + parts = entry.split("*") + provider_name = parts[0] + model_name = parts[1] + provider = PROVIDERS.get(provider_name) + if not provider: + continue + settings = MODELS.get(provider_name, {}).get(model_name, {}) + key = get_key(provider_name, entry) + + auto_content = [{"type": "text", "text": photo_summary + prompt if images and not settings.get("vision") else prompt}] + if images and settings.get("vision"): + for image in images: + auto_content += [{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image['data']}"}}] + + auto_messages = [{"role": "system", "content": messages[0]["content"]}, {"role": "user", "content": auto_content}] + messages[2:] + + text = await openai_generate_text( + api=provider, + api_key=key, + bot=bot, + model=model_name, + settings=settings, + messages=auto_messages, + stream=False, + stream_cf=stream_cf, + chat_id=chat_id, + draft_id=chat_id, + message_id=message_id, + inline_id=inline_id + ) + update_trust(parts, text) + last_type = text["type"] + + if text == "": + text = {"type": "error", "text": "все модели в автовыборе вернули ошибку"} + else: + provider_name = model[0] + model_name = model[1] + provider = PROVIDERS.get(provider_name) + if not provider: + return "❌произошла ошибка, возможные проблемы:\n" \ + "1. у вас выбрана удалённая модель или провайдер\n" \ + "2. конфиг бота настроен неправильно и надо связаться с тем кто его хостит" + settings = MODELS.get(provider_name, {}).get(model_name, {}) + key = get_key(provider_name, model_str) + + if stream_cf == "native" and user_id == chat_id and settings.get("nostream", False) == False: + stream = True + elif stream_cf == "edit" and chat_id and message_id and settings.get("nostream", False) == False: + stream = True + elif stream_cf == "edit" or stream_cf == "native" and inline_id: + stream = True + else: + stream = False + + if process and msg and message: + process["text"] = "make" + await msg.edit_text(make_graph(message, process)) + + text = await openai_generate_text( + api=provider, + api_key=key, + bot=bot, + model=model_name, + settings=settings, + messages=messages, + stream=stream, + stream_cf=stream_cf, + chat_id=chat_id, + draft_id=chat_id, + message_id=message_id, + inline_id=inline_id + ) + update_trust(model, text) if user_id: - user_contexts[user_id].append({"role": "assistant", "content": [{"type": "text", "text": text}]}) + user_contexts[user_id].append({"role": "assistant", "content": {"type": "text", "text": text["text"]}}) user_contexts[user_id] = user_contexts[user_id][-MAX_CONTEXT:] - return text + return text["text"] async def image2text(image) -> str: @@ -178,12 +228,23 @@ async def image2text(image) -> str: } ] async for completion in ai.completions( - url=api["url"], api_key=api["key"], model=model[1], messages=messages, stream=False + url=api["url"], api_key=get_key(model[0], IMAGE_MODEL), model=model[1], messages=messages, stream=False ): pass return completion["choices"][0]["message"]["content"] +def extract_error(resp): + if isinstance(resp, dict): + err = resp.get("error") + if isinstance(err, dict): + return str(err.get("message") or err) + elif err: + return str(err) + return str(resp.get("message") or "") if "message" in resp else "" + return "" + + async def openai_generate_text( api, bot, @@ -195,7 +256,8 @@ async def openai_generate_text( chat_id=None, draft_id=None, message_id=None, - inline_id=None + inline_id=None, + api_key=None ) -> str: try: extra_body = { @@ -206,10 +268,10 @@ async def openai_generate_text( alo = { "url": api["url"], - "api_key": api["key"], + "api_key": api_key or api["key"], "model": model, "messages": messages, - "stream": stream, + "stream": stream, "max_tokens": MAX_TOKENS, "extra": extra_body } @@ -222,10 +284,17 @@ async def openai_generate_text( if not draft_id: draft_id = chat_id async for chunk in ai.completions(**alo): - # chunk = json.loads(chunk) try: content = chunk["choices"][0]["delta"]["content"] except: + err_text = extract_error(chunk) + if err_text: + error = parse_response(err_text.lower()) + print(f"{RED} -- {error['type']} - {err_text}{RESET}") + return { + "type": "error", + "text": error["message"] + } continue if content: message += content @@ -234,9 +303,9 @@ async def openai_generate_text( if counter > MAX_SYMBOLS: break - + if int(counter / STREAMING_SPLIT_SYMBOLS) == counter_2: - counter_2 += 1 + counter_2 += 1 display_text, s = clean_tail_repeats(message) if s > 0: display_text += f"*... <{s} обрезано>*" @@ -245,7 +314,7 @@ async def openai_generate_text( if counter < 3950: display_text += "▋" - + try: if stream_cf == "native" and chat_id and draft_id: await bot.send_message_draft( @@ -295,15 +364,37 @@ async def openai_generate_text( else: async for completion in ai.completions(**alo): pass - text = completion["choices"][0]["message"]["content"] + try: + text = completion["choices"][0]["message"]["content"] + except Exception as e: + err_text = extract_error(completion) + if err_text: + error = parse_response(err_text.lower()) + print(f"{RED} -- {error['type']} - {err_text}{RESET}") + return { + "type": "error", + "text": error["message"] + } + error = parse_response(str(completion).lower()) + print(f"{RED} -- {error['type']} - {completion}{RESET}") + return { + "type": "error", + "text": error["message"] + } except Exception as e: error = parse_response(str(e).lower()) print(f"{RED} -- {error['type']} - {e}{RESET}") - return error["message"] + return { + "type": "error", + "text": error["message"] + } else: text, s = clean_tail_repeats(text) if s > 0: text += f"*... <{s} обрезано>*" if text == "": text = "(пустой ответ)" - return text + return { + "type": "da", + "text": text + } diff --git a/trust.py b/trust.py new file mode 100644 index 0000000..ae6b96a --- /dev/null +++ b/trust.py @@ -0,0 +1,36 @@ +from vars import models_trust + + +def trust_key(m: list): + if len(m) >= 3: + return m[0] + '*' + m[1] + '*' + m[2] + return m[0] + '*' + m[1] + + +def update_trust(m: list, text: dict): + provider_key = m[0] + model_key = trust_key(m) + if provider_key not in models_trust: + set_trust(provider_key, 80.0) + if model_key not in models_trust: + set_trust(model_key, 60.0) + if text['type'] == 'error': + if models_trust[model_key] > 10: + models_trust[model_key] += -0.5 + models_trust[provider_key] += -0.08 + elif text['type'] == 'net': + if models_trust[model_key] > 10: + models_trust[model_key] += -0.2 + models_trust[provider_key] += -0.05 + elif text['type'] == 'da': + if models_trust[model_key] < 100: + models_trust[model_key] += 2 + models_trust[provider_key] += 0.2 + + +def get_trust(m: list): + return models_trust.get(m[0], 80.0), models_trust.get(trust_key(m), 60.0) + + +def set_trust(m: str, trust: float): + models_trust[m] = trust \ No newline at end of file diff --git a/vars.py b/vars.py index b0dad1f..793ca2e 100644 --- a/vars.py +++ b/vars.py @@ -1,6 +1,14 @@ +# таймаут last_command_time = {} +# от флуда ошибками last_error_time = {} +# надо удалить но боюсь что придётся фиксить last_block_time = {} +# для ввода текста msg_input.py alo_command = {} +# контекст пользователей user_contexts = {} +# темпбан temp_benned = {} +# доверие к провейдерам и моделям по ошибкам для автовыбора и значков в выборе моделей +models_trust = {}