Add: Персистентный выбор модели, динамические модели OpenCode

This commit is contained in:
mirivlad 2026-03-17 04:31:18 +08:00
parent 1f1e65bcdf
commit 1c904320dd
5 changed files with 191 additions and 307 deletions

28
src/bot/config_manager.py Normal file
View File

@ -0,0 +1,28 @@
import json
import os
CONFIG_FILE = "./valera_config.json"
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r") as f:
return json.load(f)
return {"tool": "qwen", "model": None}
def save_config(config):
with open(CONFIG_FILE, "w") as f:
json.dump(config, f)
def get_selected_tool():
config = load_config()
return config.get("tool", "qwen")
def get_selected_model():
config = load_config()
return config.get("model")
def set_tool(tool, model=None):
config = load_config()
config["tool"] = tool
config["model"] = model
save_config(config)

View File

@ -1,8 +1,9 @@
import asyncio import asyncio
import logging import logging
import os import os
import tempfile import re
from telegram import Update import subprocess
from telegram import Update, BotCommand
from telegram.ext import ( from telegram.ext import (
Application, CommandHandler, MessageHandler, filters, Application, CommandHandler, MessageHandler, filters,
ContextTypes, CallbackQueryHandler ContextTypes, CallbackQueryHandler
@ -11,8 +12,7 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from config.config import get_settings from config.config import get_settings
from src.tools.orchestrator import Orchestrator from src.tools.orchestrator import Orchestrator
from src.bot.states import chat_state, ChatMode from src.bot.states import chat_state, ChatMode
from src.scheduler.scheduler import SchedulerManager from src.bot.config_manager import get_selected_tool, get_selected_model, set_tool
from src.speech.speech import SpeechRecognizer
logging.basicConfig( logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@ -22,41 +22,71 @@ logger = logging.getLogger(__name__)
settings = get_settings() settings = get_settings()
orchestrator = Orchestrator() orchestrator = Orchestrator()
scheduler_manager = None
speech_recognizer = SpeechRecognizer() DANGEROUS_PATTERNS = [
r'\bwrite\b', r'\bedit\b', r'\bcopy\b', r'\bmove\b', r'\bdelete\b',
r'\bсоздать\b', r'\аписать\b', r'\bудалить\b', r'\bизменить\b',
r'\.write\(', r'\.save\(', r'\brm\b', r'\bmkdir\b', r'\bcp\b',
r'\bсделать\b', r'\bвыполнить\b', r'\brun\b', r'\апустить\b',
r'sudo', r'pip install', r'apt install', r'yum install',
]
def is_dangerous(prompt: str) -> bool:
prompt_lower = prompt.lower()
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, prompt_lower, re.IGNORECASE):
return True
return False
async def get_opencode_models():
try:
result = subprocess.run(
["opencode", "models"],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
models = []
for line in result.stdout.strip().split("\n"):
line = line.strip()
if line and not line.startswith(" "):
models.append(line)
return models
except Exception as e:
logger.error(f"Error fetching opencode models: {e}")
return ["minimax-m2.5-free", "gpt-5-nano", "mimo-v2-flash-free"]
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
current_tool = get_selected_tool()
model = get_selected_model()
status = f"Текущая модель: {current_tool}"
if model:
status += f" ({model})"
await update.message.reply_text( await update.message.reply_text(
f"Привет! Я {settings.bot_name}, ваш ИИ-ассистент.\n" f"Привет! Я {settings.bot_name}, ваш ИИ-ассистент.\n\n"
"Я помогу вам с программированием и не только." f"{status}\n\n"
"Напишите ваш вопрос или используйте /qwen / /open"
) )
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
mode = chat_state.get_mode(update.effective_chat.id) current_tool = get_selected_tool()
current_tool = orchestrator.get_default_tool() model = get_selected_model()
stt_status = "включено" if speech_recognizer.is_enabled() else "отключено"
help_text = ( help_text = (
f"Я {settings.bot_name}, ваш ИИ-ассистент.\n\n" f"Я {settings.bot_name}, ваш ИИ-ассистент.\n\n"
"Доступные команды:\n" "📋 Команды:\n"
"/start - Начать работу\n" "/qwen - Использовать Qwen (все запросы через Qwen)\n"
"/help - Показать эту справку\n" "/open - Выбрать модель OpenCode\n"
"/mode confirm - Режим с подтверждением\n" "/mode confirm/auto - Режим подтверждения\n"
"/mode auto - Автономный режим\n" "/forget - Очистить историю\n\n"
"/use qwen|open|gigachat|yandex - Выбрать инструмент\n" f"🔧 Текущая модель: {current_tool}"
"/cancel - Отменить текущее действие\n"
"/qwen <текст> - Задать вопрос qwen-code\n"
"/open <текст> - Задать вопрос opencode\n"
"/gigachat <текст> - Задать вопрос Gigachat\n"
"/yandex <текст> - Задать вопрос YandexGPT\n"
"/forget - Очистить историю чата\n"
"/remind <текст> <время> - Создать напоминание\n"
"/stt on|off - Включить/выключить распознавание речи\n\n"
f"Текущий режим: {'с подтверждением' if mode == ChatMode.CONFIRM else 'автономный'}\n"
f"Инструмент по умолчанию: {current_tool}\n"
f"Распознавание речи: {stt_status}"
) )
if model:
help_text += f"\n📌 Выбранная модель: {model}"
await update.message.reply_text(help_text) await update.message.reply_text(help_text)
@ -70,66 +100,14 @@ async def mode_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
mode_arg = context.args[0].lower() mode_arg = context.args[0].lower()
if mode_arg == "confirm": if mode_arg == "confirm":
chat_state.set_mode(update.effective_chat.id, ChatMode.CONFIRM) chat_state.set_mode(update.effective_chat.id, ChatMode.CONFIRM)
await update.message.reply_text("Режим изменён: с подтверждением") await update.message.reply_text("Режим: подтверждение для опасных действий")
elif mode_arg == "auto": elif mode_arg == "auto":
chat_state.set_mode(update.effective_chat.id, ChatMode.AUTO) chat_state.set_mode(update.effective_chat.id, ChatMode.AUTO)
await update.message.reply_text("Режим изменён: автономный") await update.message.reply_text("Режим: автономный")
else: else:
await update.message.reply_text("Использование: /mode confirm | auto") await update.message.reply_text("Использование: /mode confirm | auto")
async def use_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
current = orchestrator.get_default_tool()
available = ", ".join(orchestrator.get_available_tools())
await update.message.reply_text(
f"Текущий инструмент: {current}\n"
f"Доступные: {available}"
)
return
tool = context.args[0].lower()
if tool in ["qwen", "open", "gigachat", "yandex"]:
tool_map = {"qwen": "qwen", "open": "opencode", "gigachat": "gigachat", "yandex": "yandex"}
tool = tool_map.get(tool, tool)
orchestrator.set_default_tool(tool)
await update.message.reply_text(f"Инструмент изменён на {tool}")
else:
await update.message.reply_text("Использование: /use qwen | open | gigachat | yandex")
async def cancel_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.effective_chat.id
if chat_state.is_waiting_confirmation(chat_id):
chat_state.set_waiting_confirmation(chat_id, False)
await update.message.reply_text("Ожидание подтверждения отменено.")
task_id = chat_state.get_current_task(chat_id)
if task_id:
chat_state.set_current_task(chat_id, None)
await update.message.reply_text(f"Задача {task_id} отменена.")
else:
await update.message.reply_text("Нет активных задач для отмены.")
async def stt_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
status = "включено" if speech_recognizer.is_enabled() else "отключено"
await update.message.reply_text(f"Распознавание речи: {status}")
return
arg = context.args[0].lower()
if arg == "on":
speech_recognizer.toggle(True)
await update.message.reply_text("Распознавание речи включено.")
elif arg == "off":
speech_recognizer.toggle(False)
await update.message.reply_text("Распознавание речи отключено.")
else:
await update.message.reply_text("Использование: /stt on | off")
async def confirm_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): async def confirm_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
@ -137,25 +115,33 @@ async def confirm_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = query.message.chat.id chat_id = query.message.chat.id
data = query.data data = query.data
if data.startswith("model_"):
model = data.replace("model_", "")
set_tool("opencode", model)
await query.edit_message_text(f"✅ Выбрана модель OpenCode: {model}\nВсе запросы будут идти через эту модель.")
return
if data == "confirm_yes": if data == "confirm_yes":
await query.edit_message_text("Подтверждено. Выполняю...") await query.edit_message_text("Подтверждено. Выполняю...")
pending = chat_state.get_pending_action(chat_id) pending = chat_state.get_pending_action(chat_id)
if pending: if pending:
action_type = pending.get("type") prompt = pending.get("prompt")
if action_type == "tool": tool = pending.get("tool")
prompt = pending.get("prompt") chat_state.set_waiting_confirmation(chat_id, False)
tool = pending.get("tool") await execute_tool_query(update, tool, prompt)
chat_state.set_waiting_confirmation(chat_id, False)
await execute_tool_query(query, tool, prompt)
elif data == "confirm_no": elif data == "confirm_no":
chat_state.set_waiting_confirmation(chat_id, False) chat_state.set_waiting_confirmation(chat_id, False)
await query.edit_message_text("Отменено.") await query.edit_message_text("Отменено.")
async def execute_tool_query(update, tool: str, prompt: str): async def execute_tool_query(update, tool: str, prompt: str):
chat_id = update.message.chat.id if hasattr(update, 'message') else update.effective_chat.id chat_id = update.message.chat.id if hasattr(update, 'message') else update.effective_chat.id
result, success = await orchestrator.ask(prompt, chat_id, tool) await update.message.reply_text("🤔 Думаю...")
model = get_selected_model() if tool == "opencode" else None
result, success = await orchestrator.ask(prompt, chat_id, tool, model)
text = result[:4096] if len(result) > 4096 else result text = result[:4096] if len(result) > 4096 else result
@ -166,270 +152,112 @@ async def execute_tool_query(update, tool: str, prompt: str):
async def qwen_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def qwen_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
prompt = " ".join(context.args) set_tool("qwen", None)
if not prompt:
await update.message.reply_text("Использование: /qwen <текст>") if not context.args:
await update.message.reply_text(
"✅ Теперь все запросы будут отправляться в Qwen.\n"
"Использование: /qwen <ваш вопрос>\n"
"Пример: /qwen Привет, как дела?"
)
return return
chat_id = update.effective_chat.id prompt = " ".join(context.args)
mode = chat_state.get_mode(chat_id) await execute_tool_query(update, "qwen", prompt)
if mode == ChatMode.CONFIRM:
keyboard = [
[
InlineKeyboardButton("Да", callback_data="confirm_yes"),
InlineKeyboardButton("Нет", callback_data="confirm_no")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
chat_state.set_waiting_confirmation(chat_id, True, { async def open_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
"type": "tool", await update.message.reply_text("📥 Загружаю список моделей OpenCode...")
"tool": "qwen",
"prompt": prompt
})
await update.message.reply_text( models = await get_opencode_models()
f"Выполнить запрос к qwen-code?\n\n{prompt[:200]}...",
reply_markup=reply_markup keyboard = []
) for model in models:
else: keyboard.append([InlineKeyboardButton(model, callback_data=f"model_{model}")])
await update.message.reply_text("Думаю...")
await execute_tool_query(update, "qwen", prompt) reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"Выберите модель для OpenCode (бесплатные):",
reply_markup=reply_markup
)
async def open_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def open_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
prompt = " ".join(context.args) if not context.args:
if not prompt: await open_menu(update, context)
await update.message.reply_text("Использование: /open <текст>")
return return
chat_id = update.effective_chat.id
mode = chat_state.get_mode(chat_id)
if mode == ChatMode.CONFIRM:
keyboard = [
[
InlineKeyboardButton("Да", callback_data="confirm_yes"),
InlineKeyboardButton("Нет", callback_data="confirm_no")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
chat_state.set_waiting_confirmation(chat_id, True, {
"type": "tool",
"tool": "opencode",
"prompt": prompt
})
await update.message.reply_text(
f"Выполнить запрос к opencode?\n\n{prompt[:200]}...",
reply_markup=reply_markup
)
else:
await update.message.reply_text("Думаю...")
await execute_tool_query(update, "opencode", prompt)
async def gigachat_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
prompt = " ".join(context.args) prompt = " ".join(context.args)
if not prompt: tool = get_selected_tool()
await update.message.reply_text("Использование: /gigachat <текст>") model = get_selected_model()
return
chat_id = update.effective_chat.id if tool != "opencode":
mode = chat_state.get_mode(chat_id) set_tool("opencode", "minimax-m2.5-free")
model = "minimax-m2.5-free"
if mode == ChatMode.CONFIRM: if not model:
keyboard = [ model = "minimax-m2.5-free"
[
InlineKeyboardButton("Да", callback_data="confirm_yes"),
InlineKeyboardButton("Нет", callback_data="confirm_no")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
chat_state.set_waiting_confirmation(chat_id, True, { await execute_tool_query(update, "opencode", prompt)
"type": "tool",
"tool": "gigachat",
"prompt": prompt
})
await update.message.reply_text(
f"Выполнить запрос к Gigachat?\n\n{prompt[:200]}...",
reply_markup=reply_markup
)
else:
await update.message.reply_text("Думаю...")
await execute_tool_query(update, "gigachat", prompt)
async def yandex_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
prompt = " ".join(context.args)
if not prompt:
await update.message.reply_text("Использование: /yandex <текст>")
return
chat_id = update.effective_chat.id
mode = chat_state.get_mode(chat_id)
if mode == ChatMode.CONFIRM:
keyboard = [
[
InlineKeyboardButton("Да", callback_data="confirm_yes"),
InlineKeyboardButton("Нет", callback_data="confirm_no")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
chat_state.set_waiting_confirmation(chat_id, True, {
"type": "tool",
"tool": "yandex",
"prompt": prompt
})
await update.message.reply_text(
f"Выполнить запрос к YandexGPT?\n\n{prompt[:200]}...",
reply_markup=reply_markup
)
else:
await update.message.reply_text("Думаю...")
await execute_tool_query(update, "yandex", prompt)
async def forget_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def forget_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.effective_chat.id chat_id = update.effective_chat.id
orchestrator.memory.clear_chat(chat_id) orchestrator.memory.clear_chat(chat_id)
await update.message.reply_text("История чата очищена.") await update.message.reply_text("🗑️ История чата очищена.")
async def remind_command(update: Update, context: ContextTypes.DEFAULT_TYPE): async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args: prompt = update.message.text
await update.message.reply_text(
"Использование: /remind <текст> <время>\n"
"Пример: /remind Позвонить врачу через 1 час"
)
return
text = " ".join(context.args)
chat_id = update.effective_chat.id
from datetime import datetime, timedelta
time_keywords = {
"минут": 1,
"минуту": 1,
"час": 60,
"часа": 60,
"часов": 60,
"день": 1440,
"дня": 1440,
"дней": 1440
}
minutes = 60
for keyword, value in time_keywords.items():
if keyword in text.lower():
import re
match = re.search(r'(\d+)\s*' + keyword, text.lower())
if match:
minutes = int(match.group(1)) * value
break
run_at = datetime.now() + timedelta(minutes=minutes)
# scheduler_manager.add_reminder(chat_id, text, run_at)
await update.message.reply_text(
f"Напоминание установлено на {run_at.strftime('%H:%M %d.%m.%Y')} (временно недоступно)"
)
async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not speech_recognizer.is_enabled():
await update.message.reply_text("Распознавание речи отключено.")
return
await update.message.reply_text("Распознаю голос...")
voice = update.message.voice
file = await context.bot.get_file(voice.file_id)
with tempfile.NamedTemporaryFile(delete=False, suffix=".ogg") as tmp:
await file.download_to_drive(tmp.name)
audio_path = tmp.name
text = await speech_recognizer.recognize(audio_path)
if text:
await update.message.reply_text(f"Распознано: {text}")
await handle_message(update, context, text)
else:
await update.message.reply_text("Не удалось распознать речь.")
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE, override_text: str = None):
prompt = override_text or update.message.text
chat_id = update.effective_chat.id chat_id = update.effective_chat.id
mode = chat_state.get_mode(chat_id) mode = chat_state.get_mode(chat_id)
tool = get_selected_tool()
if mode == ChatMode.CONFIRM: dangerous = is_dangerous(prompt)
if mode == ChatMode.CONFIRM and dangerous:
keyboard = [ keyboard = [
[ [
InlineKeyboardButton("Да", callback_data="confirm_yes"), InlineKeyboardButton("✅ Да", callback_data="confirm_yes"),
InlineKeyboardButton("Нет", callback_data="confirm_no") InlineKeyboardButton("❌ Нет", callback_data="confirm_no")
] ]
] ]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
chat_state.set_waiting_confirmation(chat_id, True, { chat_state.set_waiting_confirmation(chat_id, True, {
"type": "tool", "type": "tool",
"tool": orchestrator.get_default_tool(), "tool": tool,
"prompt": prompt "prompt": prompt
}) })
await update.message.reply_text( await update.message.reply_text(
f"Выполнить запрос?\n\n{prompt[:200]}...", f"⚠️ Это действие может внести изменения. Выполнить?\n\n{prompt[:200]}...",
reply_markup=reply_markup reply_markup=reply_markup
) )
else: else:
await update.message.reply_text("Думаю...") await update.message.reply_text("🤔 Думаю...")
tool = orchestrator.get_default_tool() model = get_selected_model() if tool == "opencode" else None
await execute_tool_query(update, tool, prompt) await execute_tool_query(update, tool, prompt)
def main(): def main():
global scheduler_manager
speech_recognizer.load_model()
builder = Application.builder() builder = Application.builder()
builder.token(settings.telegram_bot_token) builder.token(settings.telegram_bot_token)
if settings.telegram_proxy_url: if settings.telegram_proxy_url:
from telegram.request import HTTPXRequest builder = builder.proxy(settings.telegram_proxy_url)
request = HTTPXRequest(proxy=settings.telegram_proxy_url) logger.info(f"Используется прокси: {settings.telegram_proxy_url}")
builder.request(request)
application = builder.build() application = builder.build()
# Scheduler temporarily disabled
# scheduler_manager = SchedulerManager(application.bot, orchestrator)
# scheduler_manager.start()
application.add_handler(CommandHandler("start", start)) application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command)) application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("mode", mode_command)) application.add_handler(CommandHandler("mode", mode_command))
application.add_handler(CommandHandler("use", use_command))
application.add_handler(CommandHandler("cancel", cancel_command))
application.add_handler(CommandHandler("stt", stt_command))
application.add_handler(CommandHandler("qwen", qwen_command)) application.add_handler(CommandHandler("qwen", qwen_command))
application.add_handler(CommandHandler("open", open_command)) application.add_handler(CommandHandler("open", open_command))
application.add_handler(CommandHandler("gigachat", gigachat_command))
application.add_handler(CommandHandler("yandex", yandex_command))
application.add_handler(CommandHandler("forget", forget_command)) application.add_handler(CommandHandler("forget", forget_command))
application.add_handler(CommandHandler("remind", remind_command))
application.add_handler(CallbackQueryHandler(confirm_callback)) application.add_handler(CallbackQueryHandler(confirm_callback))
application.add_handler(MessageHandler(filters.VOICE, handle_voice))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
logger.info("Бот запущен") logger.info("Бот запущен")

View File

@ -45,9 +45,12 @@ class Orchestrator:
return full_prompt return full_prompt
async def ask(self, prompt: str, chat_id: int, tool: Optional[str] = None) -> Tuple[str, bool]: async def ask(self, prompt: str, chat_id: int, tool: Optional[str] = None, model: Optional[str] = None) -> Tuple[str, bool]:
selected_tool = tool or self.default_tool selected_tool = tool or self.default_tool
if selected_tool == "opencode" and model:
selected_tool = f"opencode:{model}"
full_prompt = self._build_prompt(prompt, chat_id) full_prompt = self._build_prompt(prompt, chat_id)
if selected_tool == "gigachat": if selected_tool == "gigachat":
@ -55,7 +58,7 @@ class Orchestrator:
elif selected_tool == "yandex": elif selected_tool == "yandex":
result, success = await self.yandex.ask(full_prompt) result, success = await self.yandex.ask(full_prompt)
else: else:
result, success = await self.tool_runner.run_tool(selected_tool, full_prompt) result, success = await self.tool_runner.run_tool(selected_tool, full_prompt, model)
if not success and self._check_rate_limit_error(result): if not success and self._check_rate_limit_error(result):
logger.warning(f"Лимит превышен для {selected_tool}, пробую другой инструмент") logger.warning(f"Лимит превышен для {selected_tool}, пробую другой инструмент")

View File

@ -16,13 +16,19 @@ class ToolRunner:
async def run_qwen(self, prompt: str) -> Tuple[str, bool]: async def run_qwen(self, prompt: str) -> Tuple[str, bool]:
return await self._run_tool(self.qwen_command, prompt) return await self._run_tool(self.qwen_command, prompt)
async def run_opencode(self, prompt: str) -> Tuple[str, bool]: async def run_opencode(self, prompt: str, model: Optional[str] = None) -> Tuple[str, bool]:
return await self._run_tool(self.opencode_command, prompt) cmd = self.opencode_command
if model:
cmd = f"{self.opencode_command}:{model}"
return await self._run_tool(cmd, prompt)
async def _run_tool(self, command: str, prompt: str) -> Tuple[str, bool]: async def _run_tool(self, command: str, prompt: str) -> Tuple[str, bool]:
cmd_parts = command.split(":")
actual_cmd = cmd_parts[0]
try: try:
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
command, actual_cmd,
prompt, prompt,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE stderr=asyncio.subprocess.PIPE
@ -53,10 +59,13 @@ class ToolRunner:
logger.exception("Ошибка при выполнении инструмента") logger.exception("Ошибка при выполнении инструмента")
return f"Ошибка: {str(e)}", False return f"Ошибка: {str(e)}", False
async def run_tool(self, tool_name: str, prompt: str) -> Tuple[str, bool]: async def run_tool(self, tool_name: str, prompt: str, model: Optional[str] = None) -> Tuple[str, bool]:
if tool_name == "qwen": if tool_name == "qwen":
return await self.run_qwen(prompt) return await self.run_qwen(prompt)
elif tool_name == "opencode": elif tool_name == "opencode":
return await self.run_opencode(prompt) return await self.run_opencode(prompt, model)
elif tool_name.startswith("opencode:"):
model = tool_name.split(":", 1)[1]
return await self.run_opencode(prompt, model)
else: else:
return f"Неизвестный инструмент: {tool_name}", False return f"Неизвестный инструмент: {tool_name}", False

16
valera.service Normal file
View File

@ -0,0 +1,16 @@
[Unit]
Description=Valera Telegram Bot
After=network.target
[Service]
Type=simple
User=mirivlad
WorkingDirectory=/home/mirivlad/git/valera
ExecStart=/home/mirivlad/git/valera/venv/bin/python -m src.bot.main
Restart=always
RestartSec=10
StandardOutput=append:/home/mirivlad/git/valera/valera.log
StandardError=append:/home/mirivlad/git/valera/valera.log
[Install]
WantedBy=multi-user.target