1. Архитектура: ключ только на backend
Правило, из которого следует всё остальное: секретный ключ живёт на вашем сервере и нигде больше. Telegram-клиент присылает текст вашему backend (webhook или polling), backend добавляет ключ и обращается к NormaHub, ответ уходит пользователю. Клиент, мини-приложение и логи ключа не видят никогда.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["NORMAHUB_API_KEY"], # только backend!
base_url="https://api.normahub.cc/v1",
)
def answer(text: str) -> str:
result = client.chat.completions.create(
model="MODEL_ID",
messages=[
{"role": "system", "content": "Отвечай коротко и по-русски."},
{"role": "user", "content": text[:2000]},
],
max_tokens=256,
)
return result.choices[0].message.content or ""Обрезайте вход (text[:2000]) и ограничивайте выход (max_tokens=256): без этих двух строк один «пошути длинно» от пользователя стоит как сто обычных ответов. Ключ создайте в личном кабинете отдельно под бота, со своим spend limit.
2. История диалога
Модель не помнит прошлые сообщения: память — это вы. Храните на пользователя скользящее окно последних 5–6 реплик и передавайте его в messages вместе с системным промптом. Весь лог слать нельзя: каждое сообщение будет стоить как сумма всей переписки, детали математики — в гайде по стоимости.
# История на пользователя: последние 6 сообщений, не весь лог
# Хранилище: dict в памяти для MVP, Redis/Postgres для продакшена
history: dict[int, list] = {}
def push(user_id: int, role: str, text: str) -> None:
history.setdefault(user_id, []).append({"role": role, "content": text})
history[user_id] = history[user_id][-6:] # скользящее окноДля MVP хватит dict в памяти, для продакшена — Redis или Postgres с TTL. Системный промпт задаёт язык, длину и характер ответов: чем точнее формат, тем меньше токенов на уточнения.
3. Защита бюджета
Публичный бот — это открытый кошелёк, прикройте его тремя слоями. Первый: rate limit в коде (N сообщений в минуту на пользователя, капча или allowlist для дорогих команд). Второй: spend limit на API-ключе в кабинете — аварийный тормоз при атаке. Третий: max_tokens и обрезка входа на каждый вызов. Мониторьте списания ежедневно первую неделю: аномалии видны сразу.
Разделите модели по командам: дешёвая отвечает в личке, сильная — только по явной команде или для админов. Цены входа и выхода каждой модели — в каталоге.
4. Ошибки и UX
Пользователю нельзя показывать traceback: маппите ошибки в человеческие ответы. 402 — «баланс на обслуживании, вернитесь позже» + алерт вам; 429/503 — «модель перегружена, попробуйте через минуту» с одной кнопкой повтора; 401/403 — только вам в лог, пользователь их чинить не может. Request ID каждого вызова пишите в лог рядом с user_id — поддержка найдёт операцию без вашего ключа.
Медленные ответы обрабатывайте стадиями: сразу «Думаю…», потом editMessageText с результатом. Полная таблица кодов и retry-политики — в гайде по ошибкам. Обзор архитектуры целиком — на странице решения для Telegram-бота.