RAG-база знаний, классификация интентов, эскалация на оператора — полный пайплайн поддержки клиентов на Python за один вечер
AI-агент поддержки — это не просто чат-бот с ChatGPT под капотом. Архитектура строится вокруг трёх ключевых слоёв: маршрутизация (классификация интента), поиск (RAG по базе знаний) и генерация (LLM с guardrails). Четвёртый слой — эскалация на живого оператора, когда уверенность модели ниже порога или клиент явно просит человека.
Запрос клиента приходит через Telegram-бота или веб-чат-виджет → FastAPI-эндпоинт принимает webhook → классификатор определяет категорию: faq / billing / technical / escalation → для faq/technical запускается RAG-поиск по векторной базе → LLM генерирует ответ с контекстом из найденных документов. Если классификатор вернул escalation или confidence ниже 0.7 — запрос уходит оператору с полным контекстом диалога. Все шаги логируются в JSON для аналитики. Стоимость такого решения — от $0.01 за диалог при использовании GPT-4o mini или полностью бесплатно с локальной Ollama.
Ключевое преимущество self-hosted подхода: ваши данные остаются у вас. В отличие от Intercom Fin или Zendesk AI, где каждый диалог проходит через чужие серверы, собственный агент на Python даёт полный контроль над PII, логами и моделью. Масштабируется горизонтально через несколько воркеров FastAPI за nginx.
Первый рубеж агента — понять, что нужно клиенту. Используем pydantic-модель для строгой типизации ответа LLM и функцию classify_intent, которая возвращает категорию и confidence. Модель получает системный промпт с описанием категорий и должна вернуть JSON. Это дешевле и быстрее, чем гонять полную генерацию — используем gpt-4o-mini или локальную модель.
# intent_classifier.py — классификация запроса клиента import json from pydantic import BaseModel from openai import AsyncOpenAI class Intent(BaseModel): category: str # faq | billing | technical | escalation confidence: float # 0.0 – 1.0 reason: str async def classify_intent(client: AsyncOpenAI, text: str) -> Intent: prompt = f"""Классифицируй запрос клиента в JSON: Категории: faq, billing, technical, escalation. Запрос: "{text}" Верни ТОЛЬКО JSON: {{"category":"...","confidence":0.0,"reason":"..."}}""" resp = await client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "system", "content": prompt}], temperature=0.0, response_format={"type": "json_object"} ) data = json.loads(resp.choices[0].message.content) return Intent(**data) # Порог: всё, что ниже 0.7 — эскалация на оператора ESCALATION_THRESHOLD = 0.7
Модель Intent валидирует ответ через pydantic — если LLM вернула мусор, вы получите исключение, а не сломанный пайплайн. Temperature=0.0 гарантирует детерминированную классификацию. Категория escalation обрабатывается отдельно — запрос сразу уходит оператору, минуя RAG и генерацию.
RAG (Retrieval-Augmented Generation) — сердце агента поддержки. Вместо того чтобы LLM «придумывала» ответы, мы подаём ей реальные документы из базы знаний: FAQ, инструкции, условия тарифов, политику возврата. Используем chromadb как легковесную векторную БД (не требует отдельного сервера — SQLite под капотом) и эмбеддинги от OpenAI text-embedding-3-small ($0.02 за 1M токенов).
# rag_engine.py — поиск по базе знаний import chromadb from openai import AsyncOpenAI class RAGEngine: def __init__(self, client: AsyncOpenAI, collection_name="knowledge_base"): self.client = client self.chroma = chromadb.PersistentClient(path="./chroma_data") self.collection = self.chroma.get_or_create_collection(collection_name) async def embed(self, text: str) -> list[float]: resp = await self.client.embeddings.create( model="text-embedding-3-small", input=text ) return resp.data[0].embedding async def search(self, query: str, top_k=5) -> list[str]: q_embed = await self.embed(query) results = self.collection.query( query_embeddings=[q_embed], n_results=top_k ) return results["documents"][0] # top-K чанков async def index_docs(self, documents: list[dict]): """Индексация: {id, text} → эмбеддинг → chromadb""" for i in range(0, len(documents), 20): # батчи по 20 batch = documents[i:i+20] texts = [d["text"] for d in batch] ids = [d["id"] for d in batch] embeds = [await self.embed(t) for t in texts] self.collection.add(embeddings=embeds, documents=texts, ids=ids)
ChromaDB хранит данные локально в ./chroma_data — никакого отдельного сервера. Для продакшена с высокими нагрузками можно переключиться на Qdrant (in-memory, gRPC) — замена одной строки. Индексация идёт батчами по 20 документов — это оптимально для API OpenAI, у которого rate limit ~500 запросов/мин для embeddings.
После классификации и поиска релевантных документов — генерация финального ответа. Ключевое правило: модель НЕ выдумывает. Если в найденных чанках нет ответа — честно говорим «я не знаю» и предлагаем эскалацию. Используем AsyncOpenAI для неблокирующих запросов и стриминг для быстрого первого токена. Промпт строится из системной инструкции + контекста из RAG + вопроса клиента.
# answer_generator.py — генерация ответа с guardrails from openai import AsyncOpenAI SYSTEM_PROMPT = """Ты — агент поддержки компании. Правила: 1. Отвечай ТОЛЬКО на основе переданного контекста. 2. Если в контексте нет ответа — скажи "Уточню у коллег" и предложи оператора. 3. Не извиняйся без причины. Будь конкретным и полезным. 4. Максимум 3 предложения. Без маркдауна.""" async def generate_answer( client: AsyncOpenAI, query: str, context_chunks: list[str] ) -> str: context = "\n\n".join( f"ДОКУМЕНТ {i+1}:\n{c}" for i, c in enumerate(context_chunks) ) resp = await client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"Контекст:\n{context}\n\nВопрос: {query}"} ], temperature=0.3, max_tokens=300 ) return resp.choices[0].message.content.strip() # Fallback: если RAG не нашёл релевантных чанков FALLBACK_ANSWER = "Пока не могу ответить точно. Хотите, подключу оператора?"
Главный guardrail — правило №1 в системном промпте: «отвечай только на основе контекста». Без него модель начнёт «додумывать» тарифы, сроки и политики — катастрофа для поддержки. Стриминг включается флагом stream=True — клиент в Telegram видит, как агент «печатает». Для голосовых ответов можно подключить ElevenLabs TTS — озвучка ответа в реальном времени.
Не каждый запрос должен решаться автоматически. Эскалация срабатывает в трёх случаях: confidence классификатора ниже порога, категория escalation, или клиент явно просит человека (слова-триггеры: «оператор», «человек», «позовите»). При эскалации создаётся тикет в CRM через webhook, контекст диалога прикрепляется, а оператору в Telegram приходит уведомление.
# escalation.py — эскалация на живого оператора import httpx from datetime import datetime, timezone ESCALATION_TRIGGERS = ["оператор", "человек", "позовите", "живой"] def should_escalate(intent, user_text: str) -> bool: if intent.category == "escalation": return True if intent.confidence < ESCALATION_THRESHOLD: return True if any(t in user_text.lower() for t in ESCALATION_TRIGGERS): return True return False async def escalate_to_operator( user_id: str, user_text: str, history: list, crm_webhook: str ): ticket = { "user_id": user_id, "query": user_text, "history": history, "created_at": datetime.now(timezone.utc).isoformat(), "source": "ai-agent-escalation" } async with httpx.AsyncClient() as hc: await hc.post(crm_webhook, json=ticket, timeout=10) # Уведомление оператору в Telegram alert = f"🔔 Эскалация!\nUser: {user_id}\nЗапрос: {user_text[:200]}" await hc.post( f"https://api.telegram.org/bot{OPERATOR_BOT_TOKEN}/sendMessage", json={"chat_id": OPERATOR_CHAT_ID, "text": alert} ) return ticket
Тикет содержит полную историю диалога — оператор видит контекст и не переспрашивает клиента. CRM-вебхук может вести в amoCRM, Bitrix24, или самописную админку. Уведомление в Telegram дублирует тикет — оператор получает push мгновенно, даже если CRM открыта не у всех.
Telegram — идеальный канал для AI-поддержки: 900M+ пользователей, бесплатный Bot API, webhook-доставка, поддержка inline-кнопок и форматирования. Используем aiogram 3.x — асинхронный фреймворк с нативной поддержкой webhook. Бот принимает сообщение → прогоняет через пайплайн (классификация → RAG → генерация) → отправляет ответ. Диалог хранится в Redis с TTL 24 часа для контекста.
# main.py — FastAPI + aiogram + полный пайплайн from fastapi import FastAPI, Request from aiogram import Bot, Dispatcher, types from aiogram.webhook.aiohttp_server import SimpleRequestHandler from contextlib import asynccontextmanager from intent_classifier import classify_intent, ESCALATION_THRESHOLD from rag_engine import RAGEngine from answer_generator import generate_answer, FALLBACK_ANSWER from escalation import should_escalate, escalate_to_operator bot = Bot(token="YOUR_BOT_TOKEN") dp = Dispatcher() llm = AsyncOpenAI(api_key="sk-...") rag = RAGEngine(llm) @dp.message() async def handle_message(message: types.Message): text = message.text intent = await classify_intent(llm, text) if should_escalate(intent, text): await escalate_to_operator( str(message.from_user.id), text, [], CRM_WEBHOOK ) await message.answer( "Переключаю на оператора. Ожидайте ответа в течение 5 минут." ) return chunks = await rag.search(text, top_k=5) if not chunks: await message.answer(FALLBACK_ANSWER) return answer = await generate_answer(llm, text, chunks) await message.answer(answer) # FastAPI-приложение с webhook для Telegram @asynccontextmanager async def lifespan(app: FastAPI): await bot.set_webhook(f"https://your-domain.com/webhook") yield await bot.delete_webhook() app = FastAPI(lifespan=lifespan) @app.post("/webhook") async def tg_webhook(request: Request): update = await request.json() await dp.feed_update(bot, types.Update(**update)) return {"ok": True} # Запуск: uvicorn main:app --port 8000
Один файл main.py содержит весь пайплайн. Для продакшена добавьте Redis для хранения истории диалогов (aiogram.fsm.storage.redis) и nginx для HTTPS. Запуск через systemd или Docker Compose с авторестартом. Полный стек можно развернуть на Ollama — локальная LLM без внешних API.
Без метрик агент поддержки — чёрный ящик. Отслеживаем четыре ключевых показателя: CSAT (удовлетворённость клиента через кнопки 👍/👎), resolution rate (доля запросов, решённых без эскалации), latency p95 (время от получения запроса до ответа) и cost per conversation (токены × цена модели). Все события пишутся в JSON-лог — простая и надёжная альтернатива Prometheus для старта.
# metrics.py — логирование и расчёт метрик import json, time from pathlib import Path LOG_PATH = Path("./logs/conversations.jsonl") def log_conversation(user_id: str, intent: str, answer: str, latency_ms: float, tokens_used: int, escalated: bool): entry = { "ts": time.time(), "user_id": user_id, "intent": intent, "answer_len": len(answer), "latency_ms": round(latency_ms, 1), "tokens": tokens_used, "escalated": escalated } with open(LOG_PATH, "a") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") # Быстрый отчёт за сегодня (jq-стиль, без БД) def daily_report() -> dict: lines = LOG_PATH.read_text().strip().split("\n") today = [json.loads(l) for l in lines] return { "total": len(today), "avg_latency_ms": sum(d["latency_ms"] for d in today) / len(today), "escalation_rate": sum(1 for d in today if d["escalated"]) / len(today), "total_tokens": sum(d["tokens"] for d in today) } # Подключите в main.py: # t0 = time.time() # answer = await generate_answer(...) # log_conversation(user_id, intent.category, answer, # (time.time()-t0)*1000, tokens, escalated)
JSONL-логи читаются любым анализатором: от jq в терминале до Pandas в Jupyter. Для визуализации подойдёт Grafana с Loki или простой Streamlit-дашборд. Критично отслеживать escalation_rate — если он растёт, значит база знаний устарела или модель не справляется с новыми типами запросов. Порог тревоги: >30% эскалаций за день.
| Критерий | Свой бот (Python) | Intercom Fin | Zendesk AI | Voiceflow |
|---|---|---|---|---|
| Цена | $0.01–0.05 / диалог (API) или бесплатно (Ollama) | от $0.99 / resolved conversation | от $49/мес + $1/решение | от $249/мес (Pro) |
| Кастомизация | Полный контроль: модель, промпты, triggers | Контент — да, логика — ограничена | Flow builder, расширения | Визуальный редактор, без кода |
| Где данные | Ваш сервер. Полный control. | Серверы Intercom (США/ЕС) | Серверы Zendesk (AWS) | Облако Voiceflow |
| RAG / база знаний | chromadb / Qdrant / FAISS — любой | Встроенный Answers API | Knowledge Base + Help Center | Knowledge Base (базовая) |
| Модель | Любая: GPT-4o, DeepSeek, локальная | GPT-4o (фиксировано) | GPT-4o / Claude (на выбор) | Выбор модели из списка |
| Внедрение | 1-3 дня (разработка) | 30 минут (готовый виджет) | 1 час (в экосистеме) | 1-2 часа (drag-n-drop) |
Вывод: свой бот выигрывает по стоимости на масштабе >100 диалогов/день и даёт полный контроль над данными и моделью. Готовые платформы выигрывают в скорости запуска, но привязывают к вендору и стоят в 5–50 раз дороже. Для стартапа с бюджетом — однозначно свой бот на Python с бесплатной Ollama. Для enterprise с compliance-требованиями — тоже свой, но на GPT-4o.
AI-агент поддержки на Python — это не магия, а инженерная сборка из четырёх компонентов: классификатор интентов (pydantic + LLM), RAG-поиск (chromadb + эмбеддинги), генератор ответов с guardrails и модуль эскалации на оператора. Весь стек умещается в 300 строк Python и один main.py.
Стартовые затраты: $0 на инфраструктуру (один VPS за $5/мес тянет 1000+ диалогов/день) и $0.01–0.05 на диалог через OpenAI API — или $0 при использовании локальной Ollama и открытых моделей. Для голосовых ответов — цены ElevenLabs от $5/мес за озвучку. Следите за обновлениями и новыми инструментами в нашем Telegram-канале: