v1.8.3: добавлены docstrings и комментарии по AGENTS.md ко всем ключевым файлам
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
"""
|
||||
Обработчик приветствий и основных сообщений бота.
|
||||
|
||||
Управляет жизненным циклом диалога:
|
||||
- приветствие → создание пользователя и диалога
|
||||
- маршрутизация по состояниям (state machine)
|
||||
- анализ намерений через YandexGPT
|
||||
- быстрые паттерны (заявка/приветствие) до NLP
|
||||
- контекст из последних 10 минут
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
from typing import Optional
|
||||
@@ -7,6 +17,7 @@ from app.database import async_session
|
||||
MOSCOW_TZ = datetime.timezone(datetime.timedelta(hours=3))
|
||||
|
||||
def moscow_now():
|
||||
"""Возвращает текущее время в UTC+3 (Москва) без tzinfo."""
|
||||
return datetime.datetime.now(MOSCOW_TZ).replace(tzinfo=None)
|
||||
from app.models import BotUser, BotConversation, BotMessage, BotKnowledgeBase, BotCategory, BotUnknownQuestion
|
||||
from app.max_api import max_api
|
||||
@@ -26,6 +37,7 @@ async def handle_greeting(
|
||||
last_name: str = "",
|
||||
username: str = "",
|
||||
) -> None:
|
||||
"""Обработка приветствия: создаёт/обновляет пользователя, создаёт диалог, отправляет приветствие."""
|
||||
assistant_name = await settings_cache.get("assistant_name", "София")
|
||||
|
||||
greeting_text = (
|
||||
@@ -94,6 +106,14 @@ async def handle_greeting(
|
||||
|
||||
|
||||
async def handle_message(user_id: int, text: str) -> None:
|
||||
"""
|
||||
Основной обработчик сообщений. Маршрутизирует по состояниям:
|
||||
- greeting/analyzing/awaiting_input → analyze_and_respond
|
||||
- awaiting_consent/consent_refused → обработка согласия
|
||||
- awaiting_contact → ручной ввод контакта
|
||||
- awaiting_inquiry → обработка заявки
|
||||
- completed/escalated → повторный анализ (escape hatch)
|
||||
"""
|
||||
async with async_session() as db:
|
||||
result = await db.execute(
|
||||
select(BotConversation)
|
||||
@@ -199,6 +219,7 @@ async def handle_message(user_id: int, text: str) -> None:
|
||||
|
||||
|
||||
async def _get_user_history_context(user_id: int) -> str:
|
||||
"""Формирует контекст из последних сообщений (10 мин) и открытых заявок пользователя."""
|
||||
from app.models import BotTicket
|
||||
async with async_session() as db:
|
||||
ten_min_ago = moscow_now() - datetime.timedelta(minutes=10)
|
||||
@@ -237,12 +258,20 @@ async def _get_user_history_context(user_id: int) -> str:
|
||||
|
||||
|
||||
async def _select_ai():
|
||||
"""Выбор AI-провайдера (только YandexGPT)."""
|
||||
if await yandex_gpt.is_available():
|
||||
return yandex_gpt
|
||||
return None
|
||||
|
||||
|
||||
async def analyze_and_respond(user_id: int, conv_id: int, text: str) -> None:
|
||||
"""
|
||||
Анализ намерений и генерация ответа.
|
||||
|
||||
Быстрые паттерны (заявка/приветствие) проверяются ПЕРЕД NLP.
|
||||
Затем: analyze_intent → ticket/question/unknown → обработка.
|
||||
clarify_intent вызывается не более 1 раза за диалог.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"analyze_and_respond: user={user_id} conv={conv_id} text={text[:80]}")
|
||||
|
||||
@@ -359,6 +388,7 @@ async def analyze_and_respond(user_id: int, conv_id: int, text: str) -> None:
|
||||
|
||||
|
||||
async def _limited_mode(user_id: int, conv_id: int) -> None:
|
||||
"""Режим без AI: показывает контакты компании."""
|
||||
async with async_session() as db:
|
||||
result = await db.execute(
|
||||
select(BotConversation).where(BotConversation.id == conv_id)
|
||||
@@ -372,6 +402,7 @@ async def _limited_mode(user_id: int, conv_id: int) -> None:
|
||||
|
||||
|
||||
async def _send_contacts(user_id: int, conv_id: int = None) -> None:
|
||||
"""Отправка контактных данных компании (телефон, email)."""
|
||||
phones = config_reader.get_phones()
|
||||
email = config_reader.get_site_mail()
|
||||
|
||||
@@ -387,6 +418,7 @@ async def _send_contacts(user_id: int, conv_id: int = None) -> None:
|
||||
|
||||
|
||||
async def _get_knowledge_base_context(query: str) -> str:
|
||||
"""Формирует контекст из базы знаний по ключевым словам (ранжирование по score)."""
|
||||
async with async_session() as db:
|
||||
result = await db.execute(
|
||||
select(BotKnowledgeBase)
|
||||
@@ -433,6 +465,7 @@ async def _get_knowledge_base_context(query: str) -> str:
|
||||
|
||||
|
||||
def _texts_match(text_a: str, text_b: str) -> bool:
|
||||
"""Проверка схожести двух текстов по пересечению слов (≥50%)."""
|
||||
stopwords = {"какой", "какая", "какие", "какое", "ваш", "ваша", "ваши",
|
||||
"где", "когда", "сколько", "что", "это", "или", "для", "при",
|
||||
"подскажите", "расскажите", "объясните", "пожалуйста"}
|
||||
@@ -445,6 +478,7 @@ def _texts_match(text_a: str, text_b: str) -> bool:
|
||||
|
||||
|
||||
async def _auto_generate_and_add_kb(db, question_record):
|
||||
"""Автогенерация карточки KB через YandexGPT при 3+ обращениях."""
|
||||
card_data = await yandex_gpt.generate_kb_card([question_record.question_text])
|
||||
if not card_data:
|
||||
return
|
||||
@@ -466,6 +500,7 @@ async def _auto_generate_and_add_kb(db, question_record):
|
||||
|
||||
|
||||
async def _save_unknown_question(user_id: int, question_text: str) -> None:
|
||||
"""Сохранение неизвестного вопроса с дедупликацией и автогенерацией при 3+ обращениях."""
|
||||
normalized_input = question_text.lower().strip()
|
||||
|
||||
async with async_session() as db:
|
||||
|
||||
Reference in New Issue
Block a user