diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..b1f7e07 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,58 @@ +# Настройка проекта + +## Проблема, которая была решена + +Сервисы не взаимодействовали между собой из-за отсутствующих файлов конфигурации `config.toml`. + +## Что было исправлено + +1. **Созданы файлы конфигурации:** + - `frontend-tg/config.toml` - конфигурация Telegram бота + - `llm-service/config.toml` - конфигурация LLM сервиса + +2. **Исправлена синхронизация NATS топиков:** + - Оба сервиса теперь используют топик `llm.inbox` + - Убрана неиспользуемая настройка `topic_out` + +## Настройка перед запуском + +### 1. Настройка Telegram бота + +Отредактируйте `frontend-tg/config.toml`: + +```toml +[telegram] +bot_token = "YOUR_BOT_TOKEN_HERE" # Получите у @BotFather +admin_id = 123456789 # Ваш Telegram user ID +``` + +### 2. Настройка LLM сервиса + +Отредактируйте `llm-service/config.toml`: + +```toml +[llm] +api_key = "YOUR_OPENROUTER_API_KEY_HERE" # Получите на openrouter.ai +``` + +### 3. Запуск + +```bash +docker-compose up --build +``` + +## Как получить необходимые ключи + +1. **Telegram Bot Token:** + - Напишите @BotFather в Telegram + - Создайте нового бота командой `/newbot` + - Скопируйте полученный токен + +2. **Telegram User ID:** + - Напишите @userinfobot в Telegram + - Скопируйте ваш ID + +3. **OpenRouter API Key:** + - Зарегистрируйтесь на https://openrouter.ai + - Перейдите в раздел API Keys + - Создайте новый ключ \ No newline at end of file diff --git a/frontend-tg/main.py b/frontend-tg/main.py index 251166c..4f19638 100644 --- a/frontend-tg/main.py +++ b/frontend-tg/main.py @@ -5,6 +5,7 @@ import json import nats from nats.errors import TimeoutError from loguru import logger +import uuid from aiogram import Bot, Dispatcher, types from aiogram.types import Message from aiogram.enums import ParseMode @@ -17,7 +18,8 @@ config = toml.load("config.toml") BOT_TOKEN = config["telegram"]["bot_token"] ADMIN_ID = config["telegram"]["admin_id"] NATS_URL = config["nats"]["url"] -NATS_SUBJECT = config["nats"].get("subject", "llm.inbox") +PUBLISH_TOPIC = config["nats"]["publish_topic"] +RESPONSE_TOPIC = config["nats"]["response_topic"] # Инициализация bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) @@ -25,6 +27,35 @@ dp = Dispatcher(storage=MemoryStorage()) # Подключение к NATS nats_client = None +pending_requests = {} # Словарь для отслеживания ожидающих ответов запросов + + +async def response_handler(msg): + """Обработчик ответов от сервисов""" + try: + data = json.loads(msg.data.decode()) + request_id = data.get("request_id") + response_text = data.get("response") + service_name = data.get("service", "unknown") + + if request_id in pending_requests: + message_obj = pending_requests.pop(request_id) # Удаляем после обработки + await message_obj.answer(f"🤖 {service_name}: {response_text}") + logger.success(f"Ответ от {service_name} доставлен пользователю {message_obj.from_user.id}") + else: + logger.warning(f"Получен ответ для неизвестного request_id: {request_id}") + except Exception as e: + logger.exception("Ошибка при обработке ответа от сервиса") + + +async def cleanup_request(request_id: str, timeout: int): + """Очистка pending_requests после таймаута""" + await asyncio.sleep(timeout) + if request_id in pending_requests: + message_obj = pending_requests.pop(request_id) + await message_obj.answer("⌛ Таймаут ожидания ответа от сервисов.") + logger.warning(f"Таймаут для request_id: {request_id}") + @dp.message() async def handle_message(message: Message): @@ -40,20 +71,31 @@ async def handle_message(message: Message): await message.answer("🚫 NATS клиент не инициализирован.") return + # Генерируем уникальный ID для запроса + request_id = str(uuid.uuid4()) + pending_requests[request_id] = message + payload = { + "request_id": request_id, "user_id": message.from_user.id, - "text": message.text + "text": message.text, + "timestamp": asyncio.get_event_loop().time() } try: - response = await nats_client.request(NATS_SUBJECT.encode(), json.dumps(payload).encode(), timeout=30) - data = json.loads(response.data.decode()) - await message.answer(data.get("response", "[no response]")) - except TimeoutError: - await message.answer("⌛ Превышен таймаут ожидания ответа.") + # Публикуем сообщение в топик для всех заинтересованных сервисов + await nats_client.publish(PUBLISH_TOPIC, json.dumps(payload).encode()) + await message.answer("📤 Сообщение отправлено сервисам для обработки...") + logger.info(f"Сообщение опубликовано в {PUBLISH_TOPIC} с request_id: {request_id}") + + # Устанавливаем таймаут для очистки pending_requests + asyncio.create_task(cleanup_request(request_id, 60)) # 60 секунд таймаут + except Exception as e: - logger.exception("Ошибка при получении ответа от LLM") - await message.answer("❌ Ошибка при получении ответа.") + logger.exception("Ошибка при публикации сообщения") + await message.answer("❌ Ошибка при отправке сообщения.") + if request_id in pending_requests: + del pending_requests[request_id] async def main(): @@ -61,6 +103,11 @@ async def main(): logger.info("Подключение к NATS...") nats_client = await nats.connect(servers=[NATS_URL]) logger.success("Подключение к NATS успешно.") + + # Подписка на топик ответов + await nats_client.subscribe(RESPONSE_TOPIC, cb=response_handler) + logger.info(f"Подписан на топик ответов: {RESPONSE_TOPIC}") + logger.info("Запуск polling Telegram-бота...") await dp.start_polling(bot) diff --git a/llm-service/main.py b/llm-service/main.py index 3b1579f..a27e973 100644 --- a/llm-service/main.py +++ b/llm-service/main.py @@ -9,8 +9,8 @@ from providers import get_provider config = toml.load("config.toml") NATS_URL = config["nats"]["url"] -TOPIC_IN = config["nats"]["topic_in"] -TOPIC_OUT = config["nats"]["topic_out"] +INCOMING_TOPIC = config["nats"]["incoming_topic"] +RESPONSE_TOPIC = config["nats"]["response_topic"] provider = get_provider( config["llm"]["provider"], @@ -25,6 +25,7 @@ async def message_handler(msg): try: data = json.loads(msg.data.decode()) + request_id = data.get("request_id") user_id = data.get("user_id") text = data.get("text") if not text: @@ -34,12 +35,14 @@ async def message_handler(msg): response = await provider.chat([{"role": "user", "content": text}]) reply = { + "request_id": request_id, "user_id": user_id, - "response": response + "response": response, + "service": "LLM-Service" } - await msg.respond(json.dumps(reply).encode()) - logger.success(f"Ответ отправлен в {TOPIC_OUT} для {user_id}") + await nc.publish(RESPONSE_TOPIC, json.dumps(reply).encode()) + logger.success(f"Ответ опубликован в {RESPONSE_TOPIC} для request_id: {request_id}") except Exception as e: logger.exception("Error handling message") @@ -47,8 +50,9 @@ async def message_handler(msg): async def main(): logger.info("Connecting to NATS...") + global nc nc = await nats.connect(servers=[NATS_URL]) - await nc.subscribe(TOPIC_IN, cb=message_handler) + await nc.subscribe(INCOMING_TOPIC, cb=message_handler) await nc.flush() logger.info(f"Subscribed to {TOPIC_IN}")