Заказ был простой: внутренний чат-бот для техподдержки. Компания небольшая, 30 человек, саппорт по сути ведёт один человек — и он уже задолбался отвечать на одно и то же по десять раз на дню. Мне дали неделю и бюджет, который я честно не назову вслух. Обычная история для фрилансера.
Раньше я бы взял LangChain, напихал туда всего по максимуму и месяц разбирался, почему ничего не работает. В этот раз решил попробовать иначе — и за субботу сделал то, что раньше делал неделю.
Делюсь, что понял на практике.
Почему именно Claude API, а не готовый фреймворк
Был соблазн взять что-то типа Botpress или готовый конструктор. Но там ты привязан к их логике, к их ограничениям, и кастомизация уходит в ад. А тут задача конкретная: бот отвечает на вопросы по продукту компании, знает внутреннюю базу знаний и не галлюцинирует безбожно.
Claude хорош в двух вещах, которые здесь критичны. Во-первых — длинный контекст. Я могу засунуть в один запрос всю базу знаний и не париться насчёт RAG-архитектуры для простого случая. Во-вторых — он реально читает инструкции. Если сказать «отвечай только по базе знаний, если не знаешь — говори "я не знаю"» — он так и делает, в отличие от той же GPT-3.5, которая любит додумать.
Что я использовал и почему
Стек минимальный: Python, FastAPI и клиент Anthropic. Никаких LangChain, LlamaIndex и прочего. Честно скажу — сначала хотел добавить LangChain для «серьёзности». Потом сел и подумал: зачем? Мне нужно отправлять запросы, получать ответы, хранить историю переписки. Это можно сделать на чистом клиенте за 50 строк кода.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system="Ты техподдержка компании X. Отвечай кратко и по делу.",
messages=[
{"role": "user", "content": "Как восстановить пароль?"}
]
)
Вот и весь бот. Ладно, не весь — дальше добавился роутинг, обработка ошибок и интеграция с базой знаний. Но основа — это действительно десятки строк.
Как я боролся с галлюцинациями
Первая версия отвечала уверенно и красиво. Вот только половина ответов была выдумана. Клиент покачал головой и сказал фразу, которую я запомню: «Лучше бот, который говорит "я не знаю", чем бот, который врёт».
Решение оказалось простым. Я не стал городить RAG с эмбеддингами и векторными базами — это оверкилл для базы знаний в 50 страниц текста. Вместо этого сделал так.
Инструкция в system prompt говорит: «Тебе дана база знаний. Если вопрос можно ответить на её основе — отвечай. Если информации недостаточно — напиши "Я не нашёл точного ответа в базе знаний. Передам вопрос команде поддержки."» А дальше просто передаю текст базы знаний в каждый запрос как первый user message. Да, токены расходуются, но при таком объёме это копейки. Зато ноль галлюцинаций по существу — проверил на 40 тестовых вопросах.
Для большой базы знаний я бы уже смотрел в сторону векторного поиска. Но для 50 страниц — избыточно.
История переписки: где я обжёгся
Сначала сделал наивно: хранил историю в памяти процесса. Запустил — вроде работает. Потом деплой, перезапуск пода — и у пользователя чистая история. Обидно.
Переделал на хранение сессий в Redis. Теперь у каждого пользователя ключ с историей переписки, и при каждом запросе я достаю последние N сообщений и докидываю в массив messages. Поскольку у Claude есть лимит контекста — 200K для новых моделей — я ещё сделал простой тримминг: если история длиннее 20 сообщений, обрезаю до последних 10.
def build_context(session_id: str, new_message: str) -> list:
history = redis.lrange(f"session:{session_id}", 0, -1)
context = [json.loads(m) for m in history]
context.append({"role": "user", "content": new_message})
# Тримминг если контекст большой
if len(context) > 20:
context = context[-10:]
return context
Мелочь, но без неё бот терял нить разговора на третьем сообщении.
Формат ответов — отдельная боль
Поначалу бот отвечал простынями текста. Юзеры говорили: «Это читать? Серьёзно?» Понял, что надо ограничивать длину и задавать формат.
В system prompt добавил: «Отвечай не более трёх предложений. Используй списки только если пунктов больше трёх.» И результат сразу другой — лаконичный, по делу.
Ещё полезная штука: научить бота понимать, когда вопрос задан не по теме. Добавил в инструкцию фразу: «Если вопрос не относится к продукту или техподдержке — вежливо перенаправь.» Теперь бот не пытается обсуждать с клиентами их отношения с женой.
Что в итоге
За субботу сделал рабочего бота. За воскресенье — интеграцию с веб-интерфейсом. За понедельник — первый фидбек от заказчика. Он сказал: «Работает лучше, чем я ожидал от прототипа». Это было приятно.
Что понял для себя.
Для простого бота с конкретной задачей — если тебе нужно просто отвечать на вопросы — возможно, хватит чего-то попроще. Но вот где важна логика, много контекста и соблюдение инструкций — Claude подходит отлично. Особенно с длинным контекстом.
Не городи фреймворки без необходимости. Мне понадобилось два дня, чтобы перестать бояться написать бота на чистом API. А всё оказалось проще, чем я думал.
Если есть что-то, что хочешь уточнить — пиши в комментариях. Разберёмся вместе.
