раздел 05 · шаг 5/7
Шаг 5. AI-фича через Claude API
Ключевой шаг проекта. При создании задачи бэк зовёт Anthropic Claude Haiku 4.5, который определяет категорию по заголовку. "купить молоко" -> personal, "доделать отчёт" -> work, "почитать про SQLAlchemy" -> learning.
Что делаем
- Устанавливаем
anthropicSDK - Создаём сервис категоризации с prompt caching на system-промпте
- Подключаем сервис в POST /api/tasks
- Делаем graceful degradation: если API упал - категория
other, ошибку логируем - Тестируем руками
Где взять API-ключ
- Идёте на
console.anthropic.com - Sign up / Sign in
- Settings -> API Keys -> Create Key
- Копируете ключ (
sk-ant-...) - больше не покажут
Кладёте ключ в .env в корне проекта:
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxx
Убедитесь, что .env в .gitignore (мы его добавили на шаге 1).
Промпт для Claude
Добавь AI-категоризацию задач в backend/.
Требования:
- Установи anthropic SDK: добавь в requirements.txt anthropic>=0.40.0 и установи
- Создай backend/app/services/categorizer.py с функцией:
async def categorize(title: str) -> str
- Используй модель claude-haiku-4-5 через AsyncAnthropic клиент
- API-ключ берётся из env: ANTHROPIC_API_KEY (через python-dotenv или os.getenv)
- System prompt должен быть кешируемым (cache_control: ephemeral, type: "ephemeral")
- В system prompt чётко перечисли категории и критерии:
work - рабочие задачи, встречи, отчёты, проекты, дедлайны
personal - быт, покупки, семья, дом, личные дела
learning - учёба, чтение, курсы, изучение технологий
health - спорт, врачи, лекарства, питание, сон
other - всё остальное, что не подпадает явно
- User-message: только title задачи
- Ответ Claude - одно слово из списка категорий
- Парсинг: если ответ не входит в список - вернуть "other"
- Обработка ошибок: anthropic.APIError, asyncio.TimeoutError, любая другая -
логируем через logging.warning, возвращаем "other", НЕ падаем
- Timeout 10 секунд через anthropic клиент
В POST /api/tasks:
- После создания записи в БД вызывай categorize(task.title)
- Сохраняй результат в task.category, делай commit
- Возвращай в ответе уже с категорией
Тесты:
- В tests/conftest.py добавь фикстуру monkeypatch для categorize,
чтобы по умолчанию в тестах не звался реальный API
- Один интеграционный тест с моком: categorize мокается чтобы вернуть "work",
POST задачи возвращает category: "work"
Не забудь обновить .env.example: добавь ANTHROPIC_API_KEY=sk-ant-...
Что Claude создаст
Файл backend/app/services/categorizer.py будет примерно таким:
import logging
import os
from anthropic import AsyncAnthropic, APIError
logger = logging.getLogger(__name__)
ALLOWED_CATEGORIES = {"work", "personal", "learning", "health", "other"}
SYSTEM_PROMPT = """You are a task categorizer. Given a task title, respond with exactly one word from this list:
- work: job tasks, meetings, reports, projects, deadlines
- personal: errands, shopping, family, home, personal affairs
- learning: studying, reading, courses, exploring technology
- health: sports, doctors, medication, nutrition, sleep
- other: anything that doesn't clearly fit above
Respond with ONLY the category name, no explanation, no punctuation."""
_client: AsyncAnthropic | None = None
def get_client() -> AsyncAnthropic:
global _client
if _client is None:
_client = AsyncAnthropic(
api_key=os.environ["ANTHROPIC_API_KEY"],
timeout=10.0,
)
return _client
async def categorize(title: str) -> str:
try:
client = get_client()
message = await client.messages.create(
model="claude-haiku-4-5",
max_tokens=20,
system=[
{
"type": "text",
"text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": title}],
)
text = message.content[0].text.strip().lower()
if text in ALLOWED_CATEGORIES:
return text
logger.warning("Unknown category from Claude: %s", text)
return "other"
except APIError as e:
logger.warning("Anthropic API error: %s", e)
return "other"
except Exception as e:
logger.exception("Unexpected categorize error: %s", e)
return "other"
В роуте POST /api/tasks появится:
from app.services.categorizer import categorize
@router.post("", response_model=TaskRead, status_code=201)
async def create_task(
payload: TaskCreate,
session: AsyncSession = Depends(get_session),
):
task = Task(title=payload.title)
session.add(task)
await session.flush()
task.category = await categorize(task.title)
await session.commit()
await session.refresh(task)
return task
Установка зависимостей
source venv/bin/activate
cd backend
pip install -r requirements.txt
Загрузка .env
Чтобы FastAPI видел ANTHROPIC_API_KEY, нужно либо использовать python-dotenv, либо запускать uvicorn с подгрузкой:
# из корня проекта, чтобы .env подхватился
set -a && source .env && set +a
cd backend
uvicorn app.main:app --reload
Или поставьте python-dotenv и загружайте в app/main.py:
from dotenv import load_dotenv
load_dotenv()
Тест
curl -X POST http://localhost:8000/api/tasks \
-H 'Content-Type: application/json' \
-d '{"title":"купить молоко"}'
Ответ:
{
"id": 1,
"title": "купить молоко",
"category": "personal",
"done": false,
"created_at": "2026-05-27T10:30:00"
}
Попробуйте разные:
- "доделать квартальный отчёт" -> work
- "почитать главу про async в Python" -> learning
- "записаться к стоматологу" -> health
- "asdfgh" -> other (Claude поймёт что мусор)
Prompt caching: что важно
System prompt пометили cache_control: ephemeral. Это значит, что после первого вызова Anthropic кеширует промпт на 5 минут и следующие запросы дешевле в разы (для Haiku - в 10 раз дешевле на закешированных токенах).
В ответе SDK можно посмотреть usage:
print(message.usage.cache_read_input_tokens) # из кеша
print(message.usage.cache_creation_input_tokens) # положили в кеш
Для эффективности кеша блок должен быть >= 1024 токенов. Наш SYSTEM_PROMPT короче, поэтому реальная экономия включится если вы расширите его примерами (few-shot examples) - они и нужны для качества, и для длины блока.
Проверка тестов
cd backend
pytest -v
Все существующие тесты должны проходить, потому что в conftest.py мы замокали categorize. Реальный API в тестах не вызывается.
Точка сохранения
cd ..
git add .
git commit -m "step 5: AI categorization via Anthropic Claude Haiku 4.5"
Полезные ссылки
- Anthropic Python SDK - официальный SDK
- Prompt caching - как и зачем
- Model overview - актуальные модели