Библиотека · Ассистенты и боты в мессенджерах

Менеджеры чат-ботов — NLP, intent routing и умная эскалация на человека

Строитель60 минОбновлено: октябрь 2026
58 из 105 в библиотеке

Модуль: 19. Voice & Real-Time AI | Время: ~25 мин теории + 35 мин практики


Суть урока

🎨 Образ: Чат-бот менеджер — это авиадиспетчер аэропорта. Каждое сообщение от клиента — это самолёт, запрашивающий посадку. Диспетчер мгновенно определяет: это рейс с вопросом о цене (полоса А), это недовольный пассажир (полоса Б), это VIP-клиент с крупной сделкой (полоса В — срочно к ручному управлению). Без диспетчера — хаос и столкновения. С ним — каждый самолёт получает правильную инструкцию и садится там, где нужно.

Большинство предпринимателей строят чат-боты так: бот отвечает на FAQ и всё. Это как аэропорт с одной полосой. В этом уроке вы научитесь строить систему с умной маршрутизацией: бот понимает намерение, извлекает ключевые данные, и знает — когда можно ответить самому, а когда немедленно передать живому человеку.


Ключевые концепции

  • Intent classification — автоматическое определение намерения пользователя (вопрос, жалоба, покупка, запрос к оператору) с помощью Claude Haiku или правил
  • Entity extraction — извлечение конкретных данных из сообщения: имена, суммы, даты, артикулы товаров
  • Intent routing — логика направления диалога: правила (ключевые слова) vs AI-классификация vs гибридный подход
  • Escalation triggers — набор условий, при которых бот передаёт разговор человеку: злость клиента, прямой запрос, сложный вопрос, крупная сумма
  • State management — сохранение контекста разговора между сообщениями через Redis или Supabase
  • Multi-channel unification — единая логика маршрутизации для Telegram, WhatsApp и веб-виджета
  • Fallback handling — что делать, когда бот не понял намерение: уточнить, предложить варианты или сразу эскалировать
  • Метрики качества — escalation rate (% переданных операторам), resolution rate (% решённых ботом), CSAT (оценка от клиента)

Теория

Что такое intent classification и зачем он нужен

Когда клиент пишет "хочу вернуть товар" — это не просто текст. Это намерение (возврат), которое требует конкретного сценария ответа. Когда пишет "вы вообще нормальные?!" — это сигнал тревоги: клиент раздражён, боту отвечать опасно, нужен живой человек.

Intent classification — это автоматическое присвоение метки намерения каждому входящему сообщению. Без этого бот работает как кассир-новичок: не понимает, что от него хотят, и пытается ответить одним шаблоном на всё подряд.

Основные категории намерений для бизнес-бота:

  • FAQ — вопросы о ценах, условиях доставки, режиме работы
  • Complaint — жалоба, проблема, негатив
  • Purchase — интерес к покупке, запрос консультации
  • Support — технический вопрос, проблема с заказом
  • Human request — явная просьба поговорить с оператором ("хочу к менеджеру")
  • Out of scope — нерелевантный запрос, спам

Как работает классификация: три подхода

Подход 1: Правила (keywords)

Самый простой. Словарь ключевых слов для каждого намерения. "верн", "возврат" → возврат. "цена", "сколько стоит" → FAQ о цене. Быстро, дёшево, предсказуемо. Минус — хрупко: "хочу вернуться к вашим услугам" ложно сработает на возврат.

Подход 2: AI-классификация (Claude Haiku)

Передаём сообщение модели с просьбой классифицировать. Точнее, понимает контекст и сарказм. Дороже правил (платишь за токены), чуть медленнее. Зато заметно реже ошибается на живой речи. Цена за сообщение небольшая и считается по токенам: актуальные цены — на странице Актуальное сейчас.

Подход 3: Гибрид (рекомендуется)

Сначала проверяем правила — если совпало с высокой уверенностью, используем правило (дёшево). Если нет — отправляем в Claude Haiku (точно). Это оптимальный баланс скорости и стоимости для нагруженных ботов.

🎨 Образ: Гибридная маршрутизация — как сортировочный конвейер на почте. Стандартные письма идут по автоматической ленте (правила). Нестандартные бандероли — к живому сортировщику (AI). Производительность высокая, ошибок мало.

Entity extraction — что скрыто в тексте

Кроме намерения, важно извлечь конкретные данные. Клиент пишет: "хочу заказать 3 коробки к пятнице на адрес Ленина 15". Здесь:

  • Количество: 3
  • Дедлайн: пятница
  • Адрес: Ленина 15

Без entity extraction бот ответит "окей, оформим" и упустит всю информацию. С ним — подтянет данные в CRM, проверит наличие, скажет, успеваем ли к пятнице.

Для entity extraction также используем Claude или regex. Claude лучше справляется с нечёткими формулировками ("послезавтра", "тысяч пять примерно").

Escalation triggers — когда передавать человеку

Это самая важная часть системы. Неправильная эскалация (слишком редкая) — клиент уходит злой. Слишком частая — оператор тонет в простых вопросах.

Умная эскалация работает по нескольким сигналам:

1. Явный запрос на оператора "хочу поговорить с менеджером", "переключи на человека", "call me" — бот немедленно эскалирует без вопросов.

2. Детектор фрустрации Анализируем тон: капслок, восклицательные знаки, слова-маркеры ("кошмар", "ужасно", "никогда больше", "верните деньги"). Claude Haiku возвращает sentiment score. Если > порога — эскалация.

3. Сложный запрос Намерение не распознано 2 раза подряд. Вопрос требует информации из нескольких систем. Юридический или финансовый вопрос (по правилам компании).

4. Высокоценный лид Сумма в запросе > $N. Клиент спрашивает про корпоративный тариф. Упомянул конкурента — нужна личная работа.

5. Системный fallback После 3 неудачных попыток ответить — автоматическая эскалация. Лучше передать человеку, чем раздражать клиента бесконечным "не понял".

State management — память разговора

🎨 Образ: Хорошая официантка помнит, что вы заказали, даже если отвлеклась на других гостей. Плохая бот без state — каждый раз спрашивает "чем могу помочь?" даже в середине разговора.

Для хранения состояния используем Redis (быстро, для активных сессий) или Supabase (персистентно, для истории). Переписка с клиентами содержит персональные данные: храни только нужное, ограничь срок хранения и не отправляй в модель лишнего. В state храним:

python
{
    "session_id": "uuid",
    "user_id": "telegram_chat_id",
    "history": [...],       # последние N сообщений
    "current_intent": "complaint",
    "entities": {"order_id": "12345"},
    "frustration_score": 0.3,
    "escalated": False,
    "turns_without_resolution": 1
}

При каждом новом сообщении обновляем state и принимаем решение о маршрутизации с учётом всей истории, не только последней фразы.

Multi-channel: один движок, много каналов

Логика intent routing не должна быть привязана к Telegram или WhatsApp. Правильная архитектура:

Код
[Telegram] → adapter → [Intent Engine] → action → [Telegram response]
[WhatsApp] → adapter → [Intent Engine] → action → [WhatsApp response]
[Web Widget] → adapter → [Intent Engine] → action → [Web response]

Adapter нормализует входящее сообщение в единый формат. Intent Engine работает одинаково для всех. Ответ формируется в формате нужного канала. Это позволяет поддерживать 3 канала с одним кодом.

Альтернативы: Botpress, Dialogflow, Rasa

Botpress — open-source платформа с визуальным построителем диалогов. Хорош для сложных деревьев диалогов, встроенный NLU. Выбирайте, если команда не техническая и нужен visual editor.

Dialogflow (Google) — enterprise-решение с мощным NLU. Хорошо интегрируется с Google Workspace. Дороже, vendor lock-in. Выбирайте для крупных корпоративных проектов.

Rasa — open-source с максимальной гибкостью. Требует ML-экспертизы. Выбирайте, если нужен on-premise и полный контроль над данными.

Claude direct (наш подход) — лучший выбор для solo founders и малых команд. Быстрый старт, низкие затраты, гибкость. Haiku для классификации, Sonnet для сложных ответов.

Мониторинг: три ключевых метрики

Escalation rate — процент разговоров, переданных оператору. Единой нормы нет: она зависит от ниши и сложности вопросов. Если почти все разговоры уходят оператору, бот бесполезен. Если почти никто не уходит, возможно, бот не эскалирует когда нужно.

Resolution rate — процент вопросов, закрытых ботом без оператора. Цель задай по своему пилоту, а не по чужим цифрам. Растёт по мере улучшения базы знаний.

CSAT (Customer Satisfaction Score) — оценка клиента после завершения диалога (1-5). Отслеживайте отдельно для бот-сессий и оператор-сессий. Разрыв покажет, где слабое место.

Дашборд для мониторинга строим в Grafana или через простую Telegram-команду /stats для владельца.

Код: pipeline intent routing на Python + FastAPI

python
import os
import json
import redis
from fastapi import FastAPI, Request
from anthropic import Anthropic

app = FastAPI()
client = Anthropic()
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

ESCALATION_PHRASES = [
    "хочу к менеджеру", "оператор", "живой человек",
    "переключи", "call me", "позвоните"
]

FRUSTRATION_WORDS = [
    "ужасно", "кошмар", "никогда", "мошенники",
    "верните деньги", "обман", "безобразие"
]


def get_session(session_id: str) -> dict:
    data = r.get(f"session:{session_id}")
    if data:
        return json.loads(data)
    return {
        "history": [],
        "frustration_score": 0.0,
        "turns_without_resolution": 0,
        "escalated": False,
        "current_intent": None
    }


def save_session(session_id: str, state: dict):
    r.setex(f"session:{session_id}", 3600, json.dumps(state))


def check_escalation_rules(message: str, state: dict) -> tuple[bool, str]:
    """Правила эскалации — быстрая проверка без AI."""
    msg_lower = message.lower()
    
    # Явный запрос оператора
    if any(phrase in msg_lower for phrase in ESCALATION_PHRASES):
        return True, "explicit_request"
    
    # Детектор фрустрации по словам
    frustration_hit = sum(1 for w in FRUSTRATION_WORDS if w in msg_lower)
    if frustration_hit >= 2:
        return True, "frustration_detected"
    
    # Накопленная фрустрация по сессии
    if state["frustration_score"] > 0.7:
        return True, "cumulative_frustration"
    
    # Бот не смог помочь 3 раза подряд
    if state["turns_without_resolution"] >= 3:
        return True, "repeated_fallback"
    
    return False, ""


def classify_intent(message: str, history: list) -> dict:
    """Claude Haiku классифицирует намерение и извлекает сущности."""
    history_text = "\n".join([
        f"{m['role']}: {m['content']}" for m in history[-4:]
    ])
    
    response = client.messages.create(
        model="claude-haiku-4-5",   # проверь, что модель ещё доступна в API; актуальные модели: страница «Актуальное сейчас»
        max_tokens=300,
        system="""Ты классификатор намерений для чат-бота интернет-магазина.
Верни JSON с полями:
- intent: одно из [faq_price, faq_delivery, faq_return, complaint, purchase_intent, order_status, out_of_scope]
- confidence: 0.0-1.0
- entities: объект с извлечёнными данными (order_id, amount, date, product)
- sentiment: positive/neutral/negative
- frustration_score: 0.0-1.0

Только JSON, без пояснений.""",
        messages=[{
            "role": "user",
            "content": f"История диалога:\n{history_text}\n\nНовое сообщение: {message}"
        }]
    )
    
    try:
        return json.loads("".join(b.text for b in response.content if b.type == "text"))
    except Exception:
        return {
            "intent": "out_of_scope",
            "confidence": 0.0,
            "entities": {},
            "sentiment": "neutral",
            "frustration_score": 0.3
        }


def generate_bot_response(intent: str, message: str, entities: dict, history: list) -> str:
    """Генерируем ответ на основе намерения."""
    
    intent_prompts = {
        "faq_price": "Ответь на вопрос о цене товара. Если нет конкретного товара — спроси уточнение.",
        "faq_delivery": "Ответь о доставке: срок 2-5 дней, бесплатно от 3000 руб.",
        "faq_return": "Ответь о возврате: 14 дней без объяснений, нужен чек.",
        "complaint": "Прими жалобу с сочувствием. Уточни детали для решения.",
        "purchase_intent": "Помоги с покупкой. Уточни детали, предложи добавить в корзину.",
        "order_status": "Запроси номер заказа если не указан.",
    }
    
    prompt = intent_prompts.get(intent, "Вежливо скажи, что не понял вопрос, и предложи варианты.")
    
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=200,
        system=f"Ты вежливый помощник интернет-магазина. {prompt}. Отвечай коротко — 1-3 предложения.",
        messages=[{"role": "user", "content": message}]
    )
    return "".join(b.text for b in response.content if b.type == "text")


async def notify_operator(session_id: str, message: str, reason: str, state: dict):
    """Уведомление оператора в Telegram."""
    import httpx
    
    bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
    operator_chat_id = os.environ.get("OPERATOR_CHAT_ID")
    
    history_preview = "\n".join([
        f"{'👤' if m['role']=='user' else '🤖'} {m['content']}"
        for m in state["history"][-3:]
    ])
    
    text = (
        f"🚨 Эскалация | {reason}\n"
        f"Сессия: {session_id}\n\n"
        f"Последние сообщения:\n{history_preview}\n\n"
        f"Последнее: {message}"
    )
    
    async with httpx.AsyncClient() as http:
        await http.post(
            f"https://api.telegram.org/bot{bot_token}/sendMessage",
            json={"chat_id": operator_chat_id, "text": text}
        )


@app.post("/chat")
async def chat_endpoint(request: Request):
    body = await request.json()
    session_id = body["session_id"]
    message = body["message"]
    
    state = get_session(session_id)
    
    # Быстрая проверка правил эскалации
    should_escalate, escalation_reason = check_escalation_rules(message, state)
    
    if should_escalate and not state["escalated"]:
        state["escalated"] = True
        state["history"].append({"role": "user", "content": message})
        save_session(session_id, state)
        await notify_operator(session_id, message, escalation_reason, state)
        return {
            "response": "Понял вас. Переключаю на оператора — он ответит в течение 5 минут.",
            "escalated": True,
            "reason": escalation_reason
        }
    
    # AI-классификация намерения
    classification = classify_intent(message, state["history"])
    intent = classification.get("intent", "out_of_scope")
    confidence = classification.get("confidence", 0.0)
    
    # Обновляем state
    state["current_intent"] = intent
    state["frustration_score"] = max(
        state["frustration_score"],
        classification.get("frustration_score", 0.0)
    )
    
    # Низкая уверенность — считаем нерешённым
    if confidence < 0.5 or intent == "out_of_scope":
        state["turns_without_resolution"] += 1
    else:
        state["turns_without_resolution"] = 0
    
    # Генерируем ответ
    bot_response = generate_bot_response(
        intent, message,
        classification.get("entities", {}),
        state["history"]
    )
    
    # Сохраняем историю
    state["history"].append({"role": "user", "content": message})
    state["history"].append({"role": "assistant", "content": bot_response})
    state["history"] = state["history"][-20:]  # Храним последние 20 сообщений
    save_session(session_id, state)
    
    return {
        "response": bot_response,
        "intent": intent,
        "confidence": confidence,
        "escalated": False
    }

Этот код запускается как FastAPI-сервис. Telegram-бот, WhatsApp-адаптер или веб-виджет отправляют POST-запросы на /chat — и получают ответ с intent, confidence и флагом эскалации.


Практика

Шаг 1: Подготовка среды (5 мин)

bash
mkdir chatbot-manager && cd chatbot-manager
python -m venv venv && source venv/bin/activate
pip install fastapi uvicorn anthropic redis httpx python-dotenv

# .env файл
echo "ANTHROPIC_API_KEY=your_key" >> .env
echo "TELEGRAM_BOT_TOKEN=your_bot_token" >> .env
echo "OPERATOR_CHAT_ID=your_chat_id" >> .env

# Запускаем Redis локально (или используем Redis Cloud)
docker run -d -p 6379:6379 redis:alpine

Шаг 2: Настройте базу знаний для FAQ (10 мин)

Создайте файл knowledge_base.json с ответами на типичные вопросы вашего бизнеса. Структура:

json
{
  "faq_price": "Цены на наши товары начинаются от 500 руб. Актуальный каталог: shop.example.com/catalog",
  "faq_delivery": "Доставка 2-5 рабочих дней. Бесплатно при заказе от 3000 руб.",
  "faq_return": "Возврат в течение 14 дней. Нужен чек и оригинальная упаковка. Деньги возвращаем за 3-5 дней."
}

Замените intent_prompts в коде на реальные ответы из вашей базы знаний.

Шаг 3: Запустите и протестируйте routing (10 мин)

bash
uvicorn main:app --reload --port 8000

Тестируйте через curl или Postman:

bash
# Обычный вопрос
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test1", "message": "сколько стоит доставка?"}'

# Тест эскалации
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test2", "message": "это УЖАСНО, верните деньги немедленно!!!"}'

# Явный запрос оператора
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"session_id": "test3", "message": "хочу поговорить с живым человеком"}'

Проверьте, что Telegram-уведомление приходит при эскалации.

Шаг 4: Подключите к Telegram-боту (8 мин)

python
# telegram_adapter.py
from telegram.ext import Application, MessageHandler, filters
import httpx, os

async def handle_message(update, context):
    session_id = str(update.effective_chat.id)
    message = update.message.text
    
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "http://localhost:8000/chat",
            json={"session_id": session_id, "message": message}
        )
        data = resp.json()
    
    await update.message.reply_text(data["response"])

app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()

Шаг 5: Настройте мониторинг (5 мин)

Добавьте эндпоинт /stats для быстрого просмотра метрик из Redis:

python
@app.get("/stats")
async def get_stats():
    keys = r.keys("session:*")
    total = len(keys)
    escalated = sum(1 for k in keys if json.loads(r.get(k)).get("escalated"))
    return {
        "total_sessions": total,
        "escalated": escalated,
        "escalation_rate": f"{escalated/total*100:.1f}%" if total else "0%",
        "active_last_hour": total  # упрощённо
    }

Инструменты и ресурсы

Инструмент Для чего Цена
Claude Haiku Intent classification, entity extraction Оплата по токенам, на октябрь 2026: $1 / $5 за миллион токенов (вход / выход)
Redis Хранение state сессий (быстро) Бесплатно self-hosted, облако по тарифам провайдера
Supabase Персистентная история диалогов Есть бесплатный уровень с лимитами
FastAPI Backend для webhook-эндпоинтов Бесплатно open-source
Botpress Визуальный конструктор диалогов (альтернатива) Есть бесплатный план, тарифы на сайте
Dialogflow Enterprise NLU (когда нужна платформа) Оплата по использованию, тарифы на сайте Google Cloud
Rasa On-premise, максимальный контроль Бесплатно open-source
Telegram Bot API Канал + оповещения операторов Бесплатно
Grafana Дашборд мониторинга метрик Бесплатно self-hosted

Рекомендуемый стек для старта: FastAPI + Claude Haiku + Redis + Telegram — собирается быстро и недорого. Расход на модель считай так: два вызова Haiku на сообщение (классификация и ответ) × токены × цена за миллион токенов. Прогони пилот на сотне сообщений и посмотри usage в ответах API.


Ключевые выводы

"Бот без intent routing — это кассир, который отвечает на всё: 'номер три'. Бот с routing — это диспетчер, который знает, куда направить каждый запрос."

"Правило эскалации простое: если сомневаешься — передавай человеку. Лучше потратить время оператора, чем потерять клиента из-за неудачного ботовского ответа."

"Escalation rate — честный KPI вашего бота. Если он зашкаливает, бот не работает. Если почти нулевой, возможно, он эскалирует слишком редко и клиенты уходят молча."


Следующий урок

→ Продуктовая аналитика — PostHog, Mixpanel и умные инсайты

Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс