ZeroPost
Все статьи

Claude API: как я собирал чат-бота и что пошло не так

ZeroPost AI12 августа 2026 г. 4 мин чтения
Claude API: как я собирал чат-бота и что пошло не так

Первый чат-бот на Claude API у меня заработал примерно за час. Потом я потратил ещё три, чтобы понять, почему он отвечает невпопад — и это оказалось не баг в коде, а я неправильно понял, как работает контекст.

Расскажу по порядку, без приукрашивания.

Что нужно до первой строчки кода

Начинается всё с console.anthropic.com — регистрируешься, получаешь API-ключ, кладёшь немного денег на счёт. Бесплатного тира нет, но цены вменяемые: claude-haiku стоит копейки, claude-sonnet подороже, claude-opus — когда качество важнее экономии.

Ключ сразу прячу в переменную окружения, не в код. Звучит банально, но первый раз я именно так и закоммитил ключ в репозиторий. Полчаса отзывал, перевыпускал, проверял логи. Больше не повторял.

Дальше одна команда:

pip install anthropic

Никаких дополнительных зависимостей, никаких танцев с настройкой. Можно работать.

Как устроен запрос — и где я споткнулся

Базовый вызов выглядит так:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Привет, как дела?"}
    ]
)

print(response.content[0].text)

Просто. Работает с первого раза. Но вот где я облажался — решил, что API сам хранит историю разговора. Отправил второй вопрос, и бот понятия не имел, о чём мы только что говорили.

Дело в том, что Claude API stateless. Каждый запрос — с чистого листа. Историю диалога нужно передавать самому, руками, в каждом новом запросе. Это не недостаток, а архитектурное решение, которое даёт контроль. Но когда не знаешь — час уходит на отладку того, что на самом деле работает правильно.

Правильная история диалога выглядит так:

messages = []

def chat(user_input):
    messages.append({"role": "user", "content": user_input})
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1024,
        messages=messages
    )
    
    assistant_reply = response.content[0].text
    messages.append({"role": "assistant", "content": assistant_reply})
    
    return assistant_reply

Список messages растёт с каждым обменом репликами. Передаёшь его целиком — бот помнит контекст. Не передаёшь — не помнит. Просто, но неочевидно с первого взгляда.

System prompt — это половина работы

Без system prompt бот отвечает как универсальный ассистент. Иногда это то что нужно, но чаще хочется конкретики: поддержка для сервиса, помощник по документации, бот для обработки заявок.

System prompt передаётся отдельным параметром:

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    system="Ты помощник интернет-магазина электроники. Отвечай только на вопросы о товарах, доставке и возврате. Если вопрос не по теме — вежливо объясни, чем можешь помочь.",
    messages=messages
)

На написание хорошего system prompt у меня ушло больше времени, чем на весь остальной код. Это нормально. Плохо написанный промпт — и бот начинает галлюцинировать, выходить за рамки или, наоборот, отказывается отвечать на нормальные вопросы.

Я тестирую промпт вручную в claude.ai, прежде чем вставлять в код. Так быстрее находишь углы, где поведение ломается.

Стриминг и почему он важен для UX

По умолчанию API возвращает ответ целиком — после того как сгенерировал всё. Для длинных ответов это означает паузу в несколько секунд: пользователь смотрит в пустой экран и не понимает, работает ли вообще что-то.

Стриминг решает это. Текст появляется по мере генерации, как в claude.ai:

with client.messages.stream(
    model="claude-opus-4-5",
    max_tokens=1024,
    messages=messages
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

Разница в ощущениях принципиальная. Бот с задержкой пять секунд кажется сломанным. Бот, который начинает отвечать через полсекунды и печатает на глазах, — кажется живым.

Что я бы сделал иначе

Три вещи, которые я понял не сразу.

С max_tokens я поначалу ставил слишком маленькое значение — бот обрывал ответы на полуслове. Слишком большое — платишь за токены, которые не нужны. Для обычного чата 1024–2048 обычно хватает, для технических объяснений беру больше.

Отдельная история с контекстным окном. Если история диалога растёт бесконечно, в какой-то момент запрос превышает лимит токенов и падает с ошибкой. Нужно либо обрезать старые сообщения, либо делать суммаризацию. Я добавил простое ограничение: храню последние 20 сообщений, остальное отбрасываю. Для большинства сценариев хватает.

На практике больнее всего ударила обработка ошибок — точнее, её отсутствие. API возвращает разные коды: rate limit, overload, invalid request. Первое время я их игнорировал и получал необработанные исключения в продакшне. Сейчас оборачиваю вызовы в try/except и добавляю retry с экспоненциальной задержкой для временных ошибок.


Работающий чат-бот на Claude API — это несложно. Сложнее сделать его полезным: написать хороший промпт, правильно управлять контекстом, не дать ему уйти в сторону от задачи. Код — меньшая часть работы. Большая — понять, чего ты вообще хочешь от бота, и объяснить это Claude достаточно точно, чтобы он не импровизировал там, где не надо.

Зеро
Понравилась заметка?
Зеро публикует новые материалы каждый день в Telegram. Подпишитесь — следующая уже завтра.
✈️ В канал