From cfc237b3724612281c2e8d9a0890cdf41f07e7f6 Mon Sep 17 00:00:00 2001 From: oleg444556 Date: Wed, 11 Mar 2026 22:06:18 +0300 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=D0=B5=20=D0=B2=20?= =?UTF-8?q?.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 20672e4..ba66c03 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ key.json .DS_Store gigakey.txt *.crt +venv/ +.env +git +.vscode \ No newline at end of file From 2c1413887bfc6c906ceb39576002ed69b643aa1d Mon Sep 17 00:00:00 2001 From: oleg444556 Date: Wed, 11 Mar 2026 22:08:30 +0300 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=20api=20?= =?UTF-8?q?=D0=B2=20=D0=B1=D0=BE=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- answer/handlers/ask_bot.py | 64 +------- answer/handlers/start.py | 43 +----- answer/routes/base.py | 207 ++++++++----------------- answer/services/__init__.py | 4 +- answer/services/bot_service.py | 267 +++++++++++++++++++++++++++++++++ 5 files changed, 337 insertions(+), 248 deletions(-) create mode 100644 answer/services/bot_service.py diff --git a/answer/handlers/ask_bot.py b/answer/handlers/ask_bot.py index 9f7fa05..0617ad7 100644 --- a/answer/handlers/ask_bot.py +++ b/answer/handlers/ask_bot.py @@ -1,9 +1,9 @@ import logging -import httpx from aiogram import Router from aiogram.types import Message +from answer.services.bot_service import get_bot_service from answer.settings import Settings, get_settings from answer.utils.validation import ( get_safe_user_info, @@ -11,64 +11,10 @@ validate_question, ) - logger = logging.getLogger(__name__) router = Router() settings: Settings = get_settings() - - -async def call_internal_api(text: str, chat_id: str = "", generate_ai_response: bool = False): - """Вызов внутреннего API через HTTP-запрос к эндпоинту /greet""" - try: - request_data = {"text": text, "generate_ai_response": generate_ai_response, "user_chat_id": chat_id} - - base_url = f"http://{settings.HOST}:{settings.PORT}" - - async with httpx.AsyncClient() as client: - response = await client.post( - f"{base_url}/greet", json=request_data, headers={"Content-Type": "application/json"}, timeout=30.0 - ) - - if response.status_code == 200: - return response.json() - else: - logger.error(f"HTTP ошибка {response.status_code}: {response.text}") - return None - - except Exception as e: - logger.error(f"Ошибка HTTP-запроса к внутреннему API: {e}", exc_info=True) - return None - - -async def save_conversation_api(user_chat_id: str, request: str, response: str, is_response_with_buttons: bool = False): - """Сохранение диалога через API""" - try: - base_url = f"http://{settings.HOST}:{settings.PORT}" - request_data = { - "user_chat_id": user_chat_id, - "request": request, - "response": response, - "is_response_with_buttons": is_response_with_buttons, - } - - async with httpx.AsyncClient() as client: - response = await client.post( - f"{base_url}/conversations", - json=request_data, - headers={"Content-Type": "application/json"}, - timeout=10.0, - ) - - if response.status_code == 200: - logger.info(f"Диалог успешно сохранен для пользователя {user_chat_id}") - return True - else: - logger.error(f"Ошибка сохранения диалога: {response.status_code} - {response.text}") - return False - - except Exception as e: - logger.error(f"Ошибка HTTP-запроса сохранения диалога: {e}", exc_info=True) - return False +bot_service = get_bot_service() @router.message() @@ -98,7 +44,7 @@ async def handle_any_message(message: Message): search_message = await message.answer("🔍 Ищу информацию и готовлю развернутый ответ...") - api_result = await call_internal_api( + api_result = bot_service.generate_response( text=validated_question, chat_id=str(message.chat.id), generate_ai_response=True ) @@ -113,7 +59,7 @@ async def handle_any_message(message: Message): answer = api_result["ai_answer"] - await save_conversation_api(str(message.chat.id), validated_question, answer, is_response_with_buttons=False) + bot_service.save_conversation(str(message.chat.id), validated_question, answer, is_response_with_buttons=False) await search_message.delete() await message.answer(f"💡 Ответ:\n\n{answer}\n\n{settings.warning_message}") @@ -310,4 +256,4 @@ async def handle_topic_selection(callback: CallbackQuery, state: FSMContext): logger.error(f"Ошибка выбора топика: {e}", exc_info=True) await callback.answer("❌ Произошла ошибка при получении информации по топику.") await state.clear() -''' \ No newline at end of file +''' diff --git a/answer/handlers/start.py b/answer/handlers/start.py index b21a29c..1bbee3c 100644 --- a/answer/handlers/start.py +++ b/answer/handlers/start.py @@ -1,55 +1,18 @@ import logging -import httpx from aiogram import F, Router from aiogram.filters import CommandStart from aiogram.types import CallbackQuery, Message from answer.handlers.keyboards import get_base_menu +from answer.services.bot_service import get_bot_service from answer.settings import Settings, get_settings from answer.utils.validation import get_safe_user_info, validate_callback_query, validate_message - logger = logging.getLogger(__name__) start_router = Router() settings: Settings = get_settings() - - -async def get_or_create_user_api(chat_id: str): - """Получение или создание пользователя через API. Возвращает (user_data, is_new_user)""" - try: - base_url = f"http://{settings.HOST}:{settings.PORT}" - - async with httpx.AsyncClient() as client: - get_response = await client.get(f"{base_url}/users/{chat_id}", timeout=10.0) - - if get_response.status_code == 200: - user_data = get_response.json() - logger.info(f"Найден существующий пользователь: {chat_id}") - return user_data, False - - elif get_response.status_code == 404: - request_data = {"chat_id": chat_id} - create_response = await client.post( - f"{base_url}/users", json=request_data, headers={"Content-Type": "application/json"}, timeout=10.0 - ) - - if create_response.status_code == 200: - user_data = create_response.json() - logger.info(f"Создан новый пользователь: {chat_id}") - return user_data, True - else: - logger.error( - f"Ошибка создания пользователя: {create_response.status_code} - {create_response.text}" - ) - return None, False - else: - logger.error(f"Ошибка получения пользователя: {get_response.status_code} - {get_response.text}") - return None, False - - except Exception as e: - logger.error(f"Ошибка HTTP-запроса пользователя: {e}", exc_info=True) - return None, False +bot_service = get_bot_service() @start_router.message(CommandStart()) @@ -66,7 +29,7 @@ async def command_start_handler(message: Message) -> None: logger.info(f"Received /start command from user {message.from_user.id}") chat_id = str(message.chat.id) - user_data, is_new_user = await get_or_create_user_api(chat_id) + user_data, is_new_user = bot_service.get_or_create_user(chat_id) if user_data: if is_new_user: diff --git a/answer/routes/base.py b/answer/routes/base.py index 0a00393..d483dce 100644 --- a/answer/routes/base.py +++ b/answer/routes/base.py @@ -1,4 +1,4 @@ -import datetime +import asyncio import logging import sys @@ -22,7 +22,7 @@ from sqlalchemy.orm import sessionmaker from answer import __version__ -from answer.bot.tg_bot.initialisation import bot_shutdown, bot_startup +from answer.bot.tg_bot.initialisation import bot_shutdown, bot_startup, start_polling from answer.models.db import Conversation, User from answer.schemas.api_models import ( ConversationContextResponse, @@ -32,10 +32,9 @@ UserResponse, ) from answer.schemas.db_models import StatusMessage +from answer.services.bot_service import get_bot_service from answer.services import get_search_service from answer.settings import get_settings -from llm.llm import get_answer -from search.filter import length_filter from search.nn import FilteredEnsembleRetriever, init_embedder from search.preprocess import preprocess_stem, TextPreprocessor from search.search import generate_keywords_dict, get_context, get_documents_from_qdrant @@ -43,6 +42,7 @@ settings = get_settings() search_service = get_search_service() +bot_service = get_bot_service() logger = logging.getLogger(__name__) bot = None @@ -119,9 +119,13 @@ async def webhook_handler(request: Request): async def init_resources(): global bot, dp - bot, dp = await bot_startup() + bot, dp = await bot_startup(use_webhook=settings.USE_WEBHOOK) app.state.bot = bot + if not settings.USE_WEBHOOK: + asyncio.create_task(start_polling()) + logger.info("Polling task started in background") + app.state.embedder = init_embedder() app.state.qdrant_client = QdrantClient( @@ -129,7 +133,7 @@ async def init_resources(): api_key=settings.QDRANT_API_KEY, prefer_grpc=False ) - + documents = get_documents_from_qdrant( client=app.state.qdrant_client, collection_name=settings.collection_name, @@ -142,23 +146,23 @@ async def init_resources(): ) app.state.vector_store = QdrantVectorStore( - client=app.state.qdrant_client, + client=app.state.qdrant_client, collection_name=settings.collection_name, embedding=app.state.embedder, ) app.state.vector_retriever = app.state.vector_store.as_retriever(search_kwargs={"k": settings.retrivier_k}) - - app.state.ensemble_retriever = EnsembleRetriever(retrievers=[app.state.bm25_retriever, app.state.vector_retriever], + + app.state.ensemble_retriever = EnsembleRetriever(retrievers=[app.state.bm25_retriever, app.state.vector_retriever], weights=[0.5, 0.5]) - - app.state.filtered_ensemble_retriever = FilteredEnsembleRetriever(app.state.vector_store, - app.state.bm25_retriever, - retriever_k=settings.retrivier_k, + + app.state.filtered_ensemble_retriever = FilteredEnsembleRetriever(app.state.vector_store, + app.state.bm25_retriever, + retriever_k=settings.retrivier_k, ensemble_k=settings.ensemble_k) - + app.state.keywords_dict = generate_keywords_dict( - vector_store=app.state.vector_store, + vector_store=app.state.vector_store, output_json_path="file/key_words_dict.json" ) @@ -203,162 +207,71 @@ async def generate_response(user_input: UserInput): ensemble_k=settings.ensemble_k, verbose=True, ) - - formatted_results = [ - { - "topic": getattr(r, 'topic', ''), - "full_text": getattr(r, 'full_text', str(r)), - "metadata": getattr(r, 'metadata', {}) - } - for r in results - ] - - if user_input.generate_ai_response: - if length_filter(text=user_input.text, max_len=settings.max_length): - ai_answer = get_answer( - context=combined_text, - question=user_input.text, - settings=settings, - ) - - response = {"results": formatted_results} - if ai_answer: - response["ai_answer"] = ai_answer - - return response - else: - return { - "results": [], - "ai_answer": 'Ваш запрос слишком длинный :( Сделайте короче или используйте режим без GPT.' - } - - if len(formatted_results) > 0: - return {"results": formatted_results} - else: - return { - "results": [], - "ai_answer": 'Извините, я не понял Ваш запрос. Попробуйте использовать GPT версию.' - } + + if results is None: + raise HTTPException(status_code=500, detail="Ошибка генерации ответа") + + return results @app.post("/users", response_model=UserResponse) async def create_user(user_request: CreateUserRequest, user=Depends(UnionAuth())): """Создание нового пользователя""" - try: - with Session() as session: - existing_user = session.query(User).filter(User.chat_id == user_request.chat_id).first() - if existing_user: - return UserResponse( - id=existing_user.id, - chat_id=existing_user.chat_id, - create_ts=existing_user.create_ts, - is_deleted=existing_user.is_deleted, - ) - - new_user = User( - chat_id=user_request.chat_id, create_ts=datetime.datetime.now(datetime.timezone.utc), is_deleted=False - ) - session.add(new_user) - session.commit() - session.refresh(new_user) - - logger.info(f"Создан новый пользователь с chat_id: {user_request.chat_id}") - - return UserResponse( - id=new_user.id, chat_id=new_user.chat_id, create_ts=new_user.create_ts, is_deleted=new_user.is_deleted - ) - - except Exception as e: - logger.error(f"Ошибка создания пользователя: {e}", exc_info=True) + user_data, is_new = bot_service.get_or_create_user(user_request.chat_id) + if user_data is None: raise HTTPException(status_code=500, detail="Ошибка создания пользователя") + return UserResponse( + id=user_data["id"], + chat_id=user_data["chat_id"], + create_ts=user_data["create_ts"], + is_deleted=user_data["is_deleted"], + ) + @app.get("/users/{chat_id}", response_model=UserResponse) async def get_user(chat_id: str, user=Depends(UnionAuth())): """Получение пользователя по chat_id""" - try: - with Session() as session: - user = session.query(User).filter(User.chat_id == chat_id).one_or_none() - if user is None: - raise HTTPException(status_code=404, detail="Пользователь не найден") - - return UserResponse(id=user.id, chat_id=user.chat_id, create_ts=user.create_ts, is_deleted=user.is_deleted) - - except HTTPException: - raise - except Exception as e: - logger.error(f"Ошибка получения пользователя: {e}", exc_info=True) - raise HTTPException(status_code=500, detail="Ошибка получения пользователя") + user_data = bot_service.get_user(chat_id) + if user_data is None: + raise HTTPException(status_code=404, detail="Пользователь не найден") + + return UserResponse( + id=user_data["id"], + chat_id=user_data["chat_id"], + create_ts=user_data["create_ts"], + is_deleted=user_data["is_deleted"], + ) @app.get("/users/{chat_id}/context", response_model=ConversationContextResponse) async def get_conversation_context(chat_id: str, user=Depends(UnionAuth())): """Получение контекста диалогов пользователя""" - try: - with Session() as session: - user = session.query(User).filter(User.chat_id == chat_id).one_or_none() - if user is None: - raise HTTPException(status_code=404, detail="Пользователь не найден") - - conversations = ( - session.query(Conversation) - .filter(and_(Conversation.user_id == user.id, Conversation.is_deleted == False)) - .order_by(desc(Conversation.create_ts)) - .limit(settings.CONTEXT_DEPTH) - .all() - ) - - if not conversations: - return ConversationContextResponse(context="", conversations_count=0) - - conversations = list(reversed(conversations)) - context_parts = [] - for conv in conversations: - context_parts.append(f"Пользователь: {conv.request}") - context_parts.append(f"Ассистент: {conv.response}") - - context_string = "\n".join(context_parts) - - return ConversationContextResponse(context=context_string, conversations_count=len(conversations)) - - except HTTPException: - raise - except Exception as e: - logger.error(f"Ошибка получения контекста диалогов: {e}", exc_info=True) - raise HTTPException(status_code=500, detail="Ошибка получения контекста диалогов") + user_data = bot_service.get_user(chat_id) + if user_data is None: + raise HTTPException(status_code=404, detail="Пользователь не найден") + + context = bot_service.get_conversation_context(chat_id) + conversations_count = len(context.split("\n")) // 2 if context else 0 + + return ConversationContextResponse(context=context, conversations_count=conversations_count) @app.post("/conversations") async def save_conversation(conversation_request: SaveConversationRequest, user=Depends(UnionAuth())): """Сохранение диалога""" - try: - with Session() as session: - user = session.query(User).filter(User.chat_id == conversation_request.user_chat_id).one_or_none() - if user is None: - raise HTTPException(status_code=404, detail="Пользователь не найден") - - conversation = Conversation( - user_id=user.id, - request=conversation_request.request, - response=conversation_request.response, - is_response_with_buttons=conversation_request.is_response_with_buttons, - create_ts=datetime.datetime.now(datetime.timezone.utc), - is_deleted=False, - ) - - session.add(conversation) - session.commit() - - logger.info(f"Диалог сохранен для пользователя {conversation_request.user_chat_id}") - - return {"status": "success", "message": "Диалог успешно сохранен"} + success = bot_service.save_conversation( + user_chat_id=conversation_request.user_chat_id, + request=conversation_request.request, + response=conversation_request.response, + is_response_with_buttons=conversation_request.is_response_with_buttons, + ) - except HTTPException: - raise - except Exception as e: - logger.error(f"Ошибка сохранения диалога: {e}", exc_info=True) + if not success: raise HTTPException(status_code=500, detail="Ошибка сохранения диалога") + return {"status": "success", "message": "Диалог успешно сохранен"} + @app.get("/", response_class=HTMLResponse) async def read_root(): diff --git a/answer/services/__init__.py b/answer/services/__init__.py index e74ce97..cbead2b 100644 --- a/answer/services/__init__.py +++ b/answer/services/__init__.py @@ -1,6 +1,6 @@ """Инициализация модуля services.""" +from .bot_service import BotService, get_bot_service from .search_service import SearchService, get_search_service - -__all__ = ['get_search_service', 'SearchService'] +__all__ = ['get_search_service', 'SearchService', 'get_bot_service', 'BotService'] diff --git a/answer/services/bot_service.py b/answer/services/bot_service.py new file mode 100644 index 0000000..29016f0 --- /dev/null +++ b/answer/services/bot_service.py @@ -0,0 +1,267 @@ +"""Сервисный слой для бота — прямые вызовы бизнес-логики без HTTP.""" + +import datetime +import logging +from typing import Any, Dict, Optional, Tuple + +from sqlalchemy import and_, desc +from sqlalchemy.orm import Session as DbSession + +from answer.models.db import Conversation, User +from answer.services.search_service import get_search_service +from answer.settings import get_settings +from llm.llm import get_answer +from search.filter import length_filter +from search.search import get_context + +logger = logging.getLogger(__name__) +settings = get_settings() + + +class BotService: + """Сервис для обработки сообщений бота и управления данными.""" + + def __init__(self): + self._search_service = get_search_service() + + def _get_app_state(self) -> Optional[Dict[str, Any]]: + """Получает состояние приложения с инициализированными компонентами.""" + return self._search_service._app_state + + def _get_db_session(self) -> DbSession: + """Создаёт новую сессию базы данных.""" + from sqlalchemy.engine import create_engine + from sqlalchemy.orm import sessionmaker + + engine = create_engine(str(settings.DB_DSN), pool_pre_ping=True, pool_recycle=300) + return sessionmaker(bind=engine)() + + def get_or_create_user(self, chat_id: str) -> Tuple[Optional[Dict], bool]: + """ + Получает пользователя или создаёт нового. + + Args: + chat_id: Telegram chat ID пользователя + + Returns: + Кортеж (user_data, is_new_user) или (None, False) при ошибке + """ + try: + session = self._get_db_session() + with session: + existing_user = session.query(User).filter(User.chat_id == chat_id).first() + if existing_user: + logger.info(f"Найден существующий пользователь: {chat_id}") + return { + "id": existing_user.id, + "chat_id": existing_user.chat_id, + "create_ts": existing_user.create_ts, + "is_deleted": existing_user.is_deleted, + }, False + + new_user = User( + chat_id=chat_id, + create_ts=datetime.datetime.now(datetime.timezone.utc), + is_deleted=False, + ) + session.add(new_user) + session.commit() + session.refresh(new_user) + + logger.info(f"Создан новый пользователь: {chat_id}") + return { + "id": new_user.id, + "chat_id": new_user.chat_id, + "create_ts": new_user.create_ts, + "is_deleted": new_user.is_deleted, + }, True + + except Exception as e: + logger.error(f"Ошибка получения/создания пользователя: {e}", exc_info=True) + return None, False + + def generate_response( + self, text: str, chat_id: str = "", generate_ai_response: bool = False + ) -> Optional[Dict[str, Any]]: + """ + Генерирует ответ на запрос пользователя. + + Args: + text: Текст запроса + chat_id: ID чата (для контекста) + generate_ai_response: Флаг генерации AI-ответа + + Returns: + Словарь с результатами поиска и/или AI-ответом, или None при ошибке + """ + try: + app_state = self._get_app_state() + if not app_state: + logger.error("App state не инициализирован") + return None + + ensemble_retriever = ( + app_state["ensemble_retriever"] + if generate_ai_response + else app_state.get("filtered_ensemble_retriever", app_state["ensemble_retriever"]) + ) + + results, combined_text = get_context( + query=text, + key_words_dict=app_state["keywords_dict"], + ensemble_retriever=ensemble_retriever, + vector_store=app_state["vector_store"], + ensemble_k=settings.ensemble_k, + verbose=True, + ) + + formatted_results = [ + { + "topic": getattr(r, "topic", ""), + "full_text": getattr(r, "full_text", str(r)), + "metadata": getattr(r, "metadata", {}), + } + for r in results + ] + + response: Dict[str, Any] = {"results": formatted_results} + + if generate_ai_response: + if length_filter(text=text, max_len=settings.max_length): + ai_answer = get_answer( + context=combined_text, + question=text, + settings=settings, + ) + if ai_answer: + response["ai_answer"] = ai_answer + else: + response["ai_answer"] = ( + "Ваш запрос слишком длинный :( Сделайте короче или используйте режим без GPT." + ) + elif len(formatted_results) == 0: + response["ai_answer"] = "Извините, я не понял Ваш запрос. Попробуйте использовать GPT версию." + + return response + + except Exception as e: + logger.error(f"Ошибка генерации ответа: {e}", exc_info=True) + return None + + def save_conversation( + self, user_chat_id: str, request: str, response: str, is_response_with_buttons: bool = False + ) -> bool: + """ + Сохраняет диалог в базу данных. + + Args: + user_chat_id: ID чата пользователя + request: Текст запроса + response: Текст ответа + is_response_with_buttons: Флаг ответа с кнопками + + Returns: + True при успехе, False при ошибке + """ + try: + session = self._get_db_session() + with session: + user = session.query(User).filter(User.chat_id == user_chat_id).one_or_none() + if not user: + logger.error(f"Пользователь не найден: {user_chat_id}") + return False + + conversation = Conversation( + user_id=user.id, + request=request, + response=response, + is_response_with_buttons=is_response_with_buttons, + create_ts=datetime.datetime.now(datetime.timezone.utc), + is_deleted=False, + ) + + session.add(conversation) + session.commit() + + logger.info(f"Диалог сохранен для пользователя {user_chat_id}") + return True + + except Exception as e: + logger.error(f"Ошибка сохранения диалога: {e}", exc_info=True) + return False + + def get_user(self, chat_id: str) -> Optional[Dict]: + """ + Получает пользователя по chat_id. + + Args: + chat_id: Telegram chat ID + + Returns: + Данные пользователя или None если не найден + """ + try: + session = self._get_db_session() + with session: + user = session.query(User).filter(User.chat_id == chat_id).one_or_none() + if user is None: + return None + + return { + "id": user.id, + "chat_id": user.chat_id, + "create_ts": user.create_ts, + "is_deleted": user.is_deleted, + } + + except Exception as e: + logger.error(f"Ошибка получения пользователя: {e}", exc_info=True) + return None + + def get_conversation_context(self, chat_id: str) -> str: + """ + Получает контекст последних диалогов пользователя. + + Args: + chat_id: Telegram chat ID + + Returns: + Строка с контекстом диалогов + """ + try: + session = self._get_db_session() + with session: + user = session.query(User).filter(User.chat_id == chat_id).one_or_none() + if user is None: + return "" + + conversations = ( + session.query(Conversation) + .filter(and_(Conversation.user_id == user.id, Conversation.is_deleted == False)) + .order_by(desc(Conversation.create_ts)) + .limit(settings.CONTEXT_DEPTH) + .all() + ) + + if not conversations: + return "" + + conversations = list(reversed(conversations)) + context_parts = [] + for conv in conversations: + context_parts.append(f"Пользователь: {conv.request}") + context_parts.append(f"Ассистент: {conv.response}") + + return "\n".join(context_parts) + + except Exception as e: + logger.error(f"Ошибка получения контекста диалогов: {e}", exc_info=True) + return "" + + +_bot_service = BotService() + + +def get_bot_service() -> BotService: + """Возвращает экземпляр BotService.""" + return _bot_service From 3c8c9ec7d0cfef4633e89983f702f73c3e7321a4 Mon Sep 17 00:00:00 2001 From: oleg444556 Date: Wed, 18 Mar 2026 20:25:47 +0300 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=20api=20?= =?UTF-8?q?=D0=B2=20=D0=B1=D0=BE=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- answer/handlers/ask_bot.py | 2 +- answer/routes/base.py | 24 +++++++----------------- answer/services/bot_service.py | 10 ++++++++-- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/answer/handlers/ask_bot.py b/answer/handlers/ask_bot.py index 0617ad7..ee8589c 100644 --- a/answer/handlers/ask_bot.py +++ b/answer/handlers/ask_bot.py @@ -44,7 +44,7 @@ async def handle_any_message(message: Message): search_message = await message.answer("🔍 Ищу информацию и готовлю развернутый ответ...") - api_result = bot_service.generate_response( + api_result = await bot_service.generate_response( text=validated_question, chat_id=str(message.chat.id), generate_ai_response=True ) diff --git a/answer/routes/base.py b/answer/routes/base.py index d483dce..1b9c3c6 100644 --- a/answer/routes/base.py +++ b/answer/routes/base.py @@ -175,6 +175,7 @@ async def init_resources(): "vector_store": app.state.vector_store, "ensemble_retriever": app.state.ensemble_retriever, "keywords_dict": app.state.keywords_dict, + "text_preprocessor": app.state.text_preprocessor, } search_service.set_app_state(app_state_dict) @@ -191,27 +192,16 @@ async def shutdown_resources(): async def generate_response(user_input: UserInput): if not user_input.text: raise HTTPException(status_code=400, detail="Text cannot be empty") - - if user_input.generate_ai_response: - ensemble_retriever = app.state.ensemble_retriever - else: - ensemble_retriever = app.state.filtered_ensemble_retriever - - processed_text = app.state.text_preprocessor.preprocess(user_input.text) - - results, combined_text = get_context( - query=processed_text, - key_words_dict=app.state.keywords_dict, - ensemble_retriever=ensemble_retriever, - vector_store=app.state.vector_store, - ensemble_k=settings.ensemble_k, - verbose=True, + + response = await bot_service.generate_response( + text=user_input.text, + generate_ai_response=user_input.generate_ai_response, ) - if results is None: + if response is None: raise HTTPException(status_code=500, detail="Ошибка генерации ответа") - return results + return response @app.post("/users", response_model=UserResponse) diff --git a/answer/services/bot_service.py b/answer/services/bot_service.py index 29016f0..359fbb2 100644 --- a/answer/services/bot_service.py +++ b/answer/services/bot_service.py @@ -80,7 +80,7 @@ def get_or_create_user(self, chat_id: str) -> Tuple[Optional[Dict], bool]: logger.error(f"Ошибка получения/создания пользователя: {e}", exc_info=True) return None, False - def generate_response( + async def generate_response( self, text: str, chat_id: str = "", generate_ai_response: bool = False ) -> Optional[Dict[str, Any]]: """ @@ -106,8 +106,10 @@ def generate_response( else app_state.get("filtered_ensemble_retriever", app_state["ensemble_retriever"]) ) + processed_text = app_state["text_preprocessor"].preprocess(text) + results, combined_text = get_context( - query=text, + query=processed_text, key_words_dict=app_state["keywords_dict"], ensemble_retriever=ensemble_retriever, vector_store=app_state["vector_store"], @@ -115,6 +117,10 @@ def generate_response( verbose=True, ) + if results is None: + logger.error("Ошибка генерации ответа от get_context") + return None + formatted_results = [ { "topic": getattr(r, "topic", ""),