Claude API: Python-застосунок на VPS з Ubuntu 24.04
Створіть Python-інструмент для пояснення журналів: ключ Claude API, virtualenv, streaming, типізовані помилки, systemd і контроль витрат на Ubuntu 24.04.
Що ви створюєте
Інструмент командного рядка на новому VPS з Ubuntu 24.04. Ви передаєте йому повідомлення про помилку або фрагмент журналу, а він повертає діагноз звичайною мовою: journalctl -u nginx -n 50 | explain. У ньому приблизно шістдесят рядків Python. На цьому прикладі ви опануєте все необхідне для реального застосунку Claude API: правильне зберігання ключа, virtualenv, структури відповіді SDK, потокову передачу, типізований ланцюжок винятків і unit systemd, щоб інструмент запускався без вашої участі.
Я навмисно вибрав саме цей проєкт. У більшості посібників для першого API-застосунку пропонують створити чат-бота, якого ви більше ніколи не відкриєте. Інструмент для пояснення журналів корисний на сервері від самого початку. Він також змушує розібратися з двома речами, у яких новачки найчастіше помиляються: правильним читанням об’єкта відповіді та контролем витрат. API стягує плату за токени без іншого обмеження, крім установлених вами лімітів. Тому контроль витрат тут є частиною проєктування, а не питанням, про яке згадують постфактум. Така сама дисципліна потрібна, коли ви переходите до запуску Claude Code на цьому самому VPS у tmux.
Отримайте API key у Console
Доступ до API керується в Anthropic Console за адресою platform.claude.com. Зареєструйтеся, а потім створіть key у розділі Settings → API Keys (документація веде безпосередньо до platform.claude.com/settings/keys). Console попросить назвати key, вибрати строк дії та прив’язати його до вашого облікового запису або service account. Anthropic рекомендує використовувати ключі service account для production-сервісів і всього, що використовується спільно. Key відображається лише один раз, починається з sk-ant-, і повторно отримати його неможливо. Скопіюйте його одразу або видаліть і випустіть новий.
Щодо оплати: станом на October 2026 постійного безкоштовного рівня для API немає. У документації Anthropic щодо тарифів зазначено, що нові користувачі отримують невеликий обсяг безкоштовних кредитів для тестування. Точний обсяг указано в Console під час реєстрації. Після його вичерпання потрібно поповнити рахунок, перш ніж запити почнуть виконуватися. Це окремо від підписки claude.ai, а API key не надає доступу до chat app. План Pro не містить API-кредитів, але їх містять плани Max і Team: станом на October 2026 це $100 на місяць для Max 5x і $200 для Max 20x; у Team передбачено $20 на кожне Standard seat і $100 на кожне Premium seat, максимум $500. Щоб отримати ці кредити, потрібно прив’язати організацію Console у налаштуваннях оплати claude.ai. Невикористані кредити expire наприкінці кожного billing cycle і не покривають Claude Code; докладні умови наведено в як працюють щомісячні API-кредити Max і Team. Якщо ви порівнюєте підписку з API, це окрема тема: який план Claude вам насправді потрібен.
Створіть key з обмеженням на один project або server. Якщо key витече, а з часом це станеться з одним із ключів, ви зможете відкликати його, не порушуючи роботу всіх інших ресурсів.
Не зберігайте ключ у .bashrc
Типова реакція — export ANTHROPIC_API_KEY=sk-ant-... у ~/.bashrc. Не робіть цього. Це створює три окремі проблеми:
- Його успадковує кожен процес. Змінна середовища, експортована у вашій login shell, передається всім запущеним процесам: вебзастосунку, засобу звітування про збої, який може записати все середовище до звіту про помилку, і сторінці
phpinfo(), яку хтось залишив увімкненою. Поверхня доступу до ключа стає рівною «усьому, що коли-небудь запускає цей користувач». - Під час введення він потрапляє до
~/.bash_history. Якщо один раз виконати export вручну, ключ назавжди залишиться у відкритому текстовому файлі та потрапить до кожної резервної копії домашнього каталогу. - Під час запуску через systemd його там немає. Сервіси не читають ваш
.bashrc, тому цей підхід не працює саме тоді, коли ви перетворюєте скрипт на unit. Зазвичай це проявляється як незрозуміла помилка 401 о 6 ранку.
На сервері слід використовувати окремий файл середовища з правами 600, який завантажує лише процес, що потребує цього ключа:
sudo mkdir -p /opt/explain
sudo install -m 600 -o root -g root /dev/null /etc/claude-explain.env
printf 'ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE\n' | sudo tee /etc/claude-explain.env >/dev/nullВикористовуйте tee із printf замість редактора, якщо хочете не залишати ключ у тимчасових файлах редактора. У будь-якому разі перевірте за допомогою ls -l /etc/claude-explain.env, що він читає -rw------- і належить root. Інтерактивні shell отримують ключ для кожного окремого запуску через wrapper (нижче), а systemd — через EnvironmentFile=. root читає файл до скидання привілеїв, тому користувач сервісу не потребує доступу на читання. Ключ ніколи не з’являється в коді, git, виводі ps або історії shell.
Встановлення SDK у venv
Ubuntu 24.04 постачається з Python 3.12 і застосовує обмеження PEP 668, тому звичайний виклик pip install anthropic для системного інтерпретатора завершується помилкою error: externally-managed-environment. Це штатна поведінка ОС. Використовуйте virtualenv:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropicНа сервері не потрібно окремо активувати середовище: прямий виклик /opt/explain/venv/bin/python завжди використовує пакети з venv.
Перший виклик і правильне читання відповіді
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1000,
messages=[{"role": "user", "content": "Explain what a systemd unit file is in three sentences."}],
)
for block in response.content:
if block.type == "text":
print(block.text)У цих дванадцяти рядках є два важливі положення, які формують основну модель роботи API. По-перше, anthropic.Anthropic() без аргументів читає ключ із середовища. Ніколи не передавайте ключ як рядковий літерал. По-друге, response.content — це список блоків вмісту, а не рядок. Якщо вивести його безпосередньо, ви отримаєте типовий результат для першого виклику:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]Це не помилка, а repr об’єкта. Відповіді можуть містити кілька типів блоків: текст, виклики інструментів і міркування. Тому перебирайте список і перевіряйте block.type == "text", перш ніж звертатися до .text. У Opus 5.5 це важливо вже під час першого виклику: міркування завжди увімкнені. Якщо модель не пропускає міркування для простого запиту, список починається з блоку thinking, поле text якого за замовчуванням порожнє, а response.content[0].text — це не ваша відповідь. Додайте цей цикл одразу. Тоді цілий клас проблем із повідомленнями на кшталт «виводиться незрозумілий текст» не виникатиме.
Використовуйте точний ідентифікатор моделі claude-opus-5-5. Починаючи з покоління 4.6, ідентифікатори моделей не містять дати, а ідентифікатор без дати вказує на закріплений snapshot. Не додавайте суфікс із датою до поточної моделі через звичку або за порадою зі старої статті. Це спричиняє помилку 404, яку розглянуто нижче. Старіші моделі працюють інакше: канонічний ідентифікатор Haiku 4.5 — датований claude-haiku-4-5-20251001.
Фактичний інструмент: пояснення
Ось повна програма: вона читає дані зі stdin, потоково виводить діагностику й обробляє помилки:
#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic
MODEL = "claude-opus-5-5"
def main() -> int:
text = sys.stdin.read().strip()
if not text:
print("usage: journalctl -u nginx -n 50 | explain", file=sys.stderr)
return 1
client = anthropic.Anthropic()
try:
with client.messages.stream(
model=MODEL,
max_tokens=4096,
system=(
"You are a senior Linux sysadmin. The user pipes you server "
"logs or error output. Name the most likely cause outright, "
"then give the commands to confirm and fix it. Be terse."
),
messages=[{"role": "user", "content": text}],
) as stream:
for chunk in stream.text_stream:
print(chunk, end="", flush=True)
print()
except anthropic.RateLimitError as e:
retry_after = e.response.headers.get("retry-after", "60")
print(f"rate limited; retry in {retry_after}s", file=sys.stderr)
return 2
except anthropic.APIStatusError as e:
print(f"API error {e.status_code}: {e.message}", file=sys.stderr)
return 2
except anthropic.APIConnectionError:
print("network error reaching the API", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())Збережіть її як /opt/explain/explain.py, а потім додайте wrapper, який завантажує ключ для інтерактивного використання:
sudo tee /usr/local/bin/explain >/dev/null <<'EOF'
#!/bin/sh
set -a; . /etc/claude-explain.env; set +a
exec /opt/explain/venv/bin/python /opt/explain/explain.py "$@"
EOF
sudo chmod 755 /usr/local/bin/explain(Wrapper має запускатися через sudo, або env-файл має належати до групи, у якій є ваш адміністративний користувач. Зробіть свідомий вибір, а не послаблюйте права до 644.)
Чому використовується потокова передача. client.messages.stream виводить токени в міру їх надходження, а не мовчить протягом усього генерування. Це також дає змогу уникнути тайм-аутів HTTP для довгих результатів. SDK фактично відхиляє дуже великі значення max_tokens у непотокових викликах саме з цієї причини. text_stream повертає лише текст відповіді. За ввімкненого за замовчуванням відображення міркувань в Opus 5.5 текст міркувань взагалі не передається потоково, тому вивід залишається чистим. Якщо після цього потрібен зібраний об’єкт, викличте stream.get_final_message() всередині блоку with.
Чому винятки розташовано в такому порядку. SDK генерує типізовані винятки, від найспецифічнішого до загальнішого: RateLimitError — це 429, і він містить заголовок retry-after, який повідомляє, скільки потрібно чекати; APIStatusError охоплює інші відповіді, відмінні від 2xx (перевірте e.status_code >= 500, щоб виявити проблеми на стороні сервера); APIConnectionError означає, що відповідь на запит взагалі не надійшла. Перш ніж створювати цикл повторних спроб, врахуйте: SDK уже сам повторює запити після помилок 429 і 5xx — за замовчуванням двічі з експоненційною затримкою (max_retries на клієнті). Коли виконується ваш except, повторні спроби вже вичерпані. Тому для CLI правильно повідомити про помилку й завершити роботу, а не чекати та створювати додаткове навантаження.
Контроль витрат
Це потребує окремого розділу, оскільки API не має вбудованого місячного ліміту понад той, який ви налаштуєте, а кожна помилка тут непомітно збільшує витрати.
max_tokens — це максимальна сума витрат на один виклик. Вихідні токени є дорожчими за вхідні: для Opus 5.5 їхня ціна вп’ятеро вища, а max_tokens встановлює жорсткий ліміт на кількість токенів, які може згенерувати модель. Надто великий запит не призведе до витрат на вихідні токени понад дозволений вами ліміт. У моделях 5.5 цей ліміт також охоплює обдумування: токени обдумування враховуються в max_tokens і тарифікуються як вихідні, хоча текст обдумування вам не повертається. Встановлюйте ліміт відповідно до завдання: 4,096 достатньо, щоб модель виконала обдумування перед стислим аналізом журналу; для класифікації потрібно значно менше. Якщо відповіді перериваються з stop_reason: "max_tokens", ліміт замалий. Свідомо збільште його або зменште параметр effort, щоб модель виконувала менше обдумування, замість бездумно встановлювати велике значення.
Підраховуйте токени перед надсиланням. Вхідні дані також коштують грошей, а журнали можуть бути великими. API має endpoint для підрахунку токенів. Він безкоштовний, але має власні ліміти, окремі від лімітів створення повідомлень:
count = client.messages.count_tokens(
model="claude-opus-5-5",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Використовуйте його, щоб випадково не передати через інструмент журнал розміром 2 GB. Передавайте саме ту модель, яку фактично викликатимете: Claude 4.7 і новіші моделі використовують новий tokenizer, який для того самого тексту створює приблизно на 30% більше токенів, ніж попередні моделі Claude. Не використовуйте для цього tiktoken. Це tokenizer OpenAI, а не Claude, тому його підрахунок не відповідатиме сумі, за яку вам виставлять рахунок.
Обирайте модель для конкретного завдання, а не за принципом лояльності. Станом на October 2026 Opus 5.5 (claude-opus-5-5) коштує $4 за мільйон вхідних токенів і $20 за мільйон вихідних; Haiku 5.5 (claude-haiku-5-5) — $0.10/$0.50 для запитів до 100,000 токенів ($0.50/$2.50 для більших запитів) із контекстом 1M; Sonnet 5.5 (claude-sonnet-5-5) займає проміжне місце й коштує $2/$10. Наприклад, фрагмент журналу на 2,000 токенів із відповіддю на 500 токенів коштує приблизно $0.018 в Opus і $0.00045 у Haiku, без урахування обдумування моделі, яке тарифікується як вихідні токени. Почніть з Opus, поки оцінюєте якість результатів, а потім перевірте ті самі запити в Haiku. Для простих перетворень у великих обсягах результат часто не відрізняється, тоді як ціна токена приблизно в сорок разів нижча. Перед тим як закладати ці значення в бюджет, перевірте актуальні ціни на сторінці pricing.
Використовуйте batches для завдань, які можуть зачекати. Batches API обробляє запити асинхронно за 50% стандартної ціни, і більшість пакетів завершується протягом години. Нічні зведення, backfill, масова класифікація та інші завдання, для яких не потрібно, щоб користувач чекав, слід виконувати через batches.
Використовуйте prompt caching для контексту, який повторюється. Якщо кожен виклик повторно надсилає той самий великий системний prompt або runbook, позначте його як придатний для кешування:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1000,
system=[{
"type": "text",
"text": RUNBOOK_TEXT, # the same 30K tokens on every call
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": question}],
)
print(response.usage.cache_read_input_tokens) # non-zero from the second call onЗапис у кеш коштує 1.25x ціни вхідних даних за TTL 5 хвилин, а читання з кешу — 0.05x в Opus 5.5 і Sonnet 5.5 (0.1x у більшості інших моделей). Тому другий виклик у межах цього інтервалу вже компенсує вартість першого. Є два обмеження. Кешований префікс має досягати мінімального розміру для конкретної моделі: 512 токенів в Opus 5.5. Тому дуже короткий системний prompt може взагалі не кешуватися. Якщо cache_read_input_tokens залишається нульовим у послідовних ідентичних викликах, це означає, що частина префікса змінюється під час кожного запиту. Найчастіше причиною є timestamp.
Пам’ятайте, що саме враховується як вхідні дані. System prompts, визначення інструментів і вся історія multi-turn розмови, яку ви повторно надсилаєте під час кожного ходу, тарифікуються як вхідні токени. Цикл чату, який ніколи не скорочує історію, спричиняє квадратичне зростання витрат. Перед створенням conversational застосунку варто зрозуміти повний порядок обліку: як насправді підсумовуються використання токенів Claude і тарифікація.
Запустіть це через systemd
Перевага дисциплінованого використання файлів середовища: таймер, який щоранку підсумовує вчорашні помилки.
# /etc/systemd/system/log-digest.service
[Unit]
Description=Daily error-log digest via the Claude API
[Service]
Type=oneshot
User=explain
Group=systemd-journal
EnvironmentFile=/etc/claude-explain.env
ExecStart=/bin/sh -c 'journalctl -p err --since yesterday | /opt/explain/venv/bin/python /opt/explain/explain.py >> /var/log/log-digest.txt'# /etc/systemd/system/log-digest.timer
[Unit]
Description=Run the log digest every morning
[Timer]
OnCalendar=06:15
Persistent=true
[Install]
WantedBy=timers.targetsudo useradd -r -s /usr/sbin/nologin explain
sudo touch /var/log/log-digest.txt && sudo chown explain /var/log/log-digest.txt
sudo systemctl daemon-reload
sudo systemctl enable --now log-digest.timer
sudo systemctl start log-digest.service # test it once, right nowЗверніть увагу, що дає EnvironmentFile=: systemd читає файл, власником якого є root і для якого встановлено режим 600, до переходу на непривілейованого користувача explain. Тому процес отримує змінну, а користувач не може прочитати файл із ключем. Група systemd-journal надає доступ до журналів. Перевірте це вручну за допомогою systemctl start і прочитайте journalctl -u log-digest.service. Не чекайте 06:15, щоб виявити помилку в тексті. Коли цей підхід переросте shell-конвеєр, той самий підхід із ключем у файлі середовища можна безпосередньо застосувати у робочих процесах n8n на основі Claude на тому самому сервері.
Типові відмови та повідомлення, які ви побачите
401 для робочого ключа. Виняток має такий вигляд:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}Якщо ключ працює у вашій shell, але сервіс отримує 401, сервіс не отримав цей ключ. Пам’ятайте, що systemd не читає .bashrc. Перевірте, що EnvironmentFile= вказує на правильний шлях. Інші причини: лапки, вставлені у файл середовища (ANTHROPIC_API_KEY="sk-ant-..."; systemd вилучає ці лапки, але обгортка shell із . file залишає їх у значенні, якщо лапки було додано некоректно), пробіли в кінці рядка, ключ, відкликаний у Console минулого тижня, або ключ, термін дії якого завершився відповідно до вибраного під час створення строку.
404 через помилку в назві моделі. Найпоширеніший варіант — додати суфікс із датою до ID моделі, яка не має версії з датою:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-5-5-20260922'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}ID моделей поточного покоління потрібно вказувати точно так, як їх записано: claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5, claude-fable-5-1. Винятком є моделі, випущені до покоління 4.6: claude-haiku-4-5 — це зручний псевдонім для ID із датою claude-haiku-4-5-20251001, і працюють обидві форми. Копіюйте ID з документації до моделей, а не з пам’яті чи старого посібника.
429 rate_limit_error. Рядок типу помилки — rate_limit_error, а відповідь містить заголовок retry-after із кількістю секунд до наступної спроби. SDK уже двічі повторив запит із backoff, перш ніж ви побачили виняток. Тому постійні 429 означають, що ваш стабільний темп запитів справді перевищує ліміт вашого рівня. Розбийте пакет на частини або розподіліть роботу в часі. Не зменшуйте інтервал між повторними спробами.
Виводиться об’єкт, а не текст. Вивід має такий вигляд: [TextBlock(citations=None, text='...', type='text')]. Ви вивели response.content замість того, щоб перебрати блоки та прочитати .text у тих блоках, де block.type == "text". У кожному прикладі SDK вище це зроблено правильно. Скопіюйте цикл.
error: externally-managed-environment. Ви виконали pip install для системного Python в Ubuntu 24.04. Використовуйте venv. Не запускайте --break-system-packages на сервері, який потрібно захистити.
Обрізані відповіді. response.stop_reason == "max_tokens" означає, що модель досягла заданого обмеження виводу посеред міркування. Це очікувана поведінка. За потреби збільште обмеження вручну. Пам’ятайте, що в моделях 5.5 міркування використовує той самий ліміт.
Після того як перший застосунок запрацює, створення AI-агента з Claude перетворить ті самі API-виклики на агента, який використовує інструменти.
FAQ
Скільки коштує спробувати Claude API?
Для такого інструмента це справді небагато. Станом на October 2026 модель Opus 5.5 коштує $4 за мільйон вхідних токенів і $20 за мільйон вихідних. Типова діагностика журналу з кількома тисячами вхідних токенів і кількома сотнями вихідних коштує близько двох центів без урахування thinking tokens, які тарифікуються як вихідні. Для Haiku 5.5 ($0.10/$0.50) це значно менше десятої частини цента. Щоденні дайджести протягом місяця коштують менше, ніж чашка кави. Ризик пов’язаний не з ціною одного виклику, а з необмеженими циклами та необмеженим max_tokens. Саме тому в цьому посібнику обидва параметри задано явно.
Чи є безкоштовний рівень для Claude API?
Станом на October 2026 постійного безкоштовного рівня немає. У документації щодо тарифів Anthropic зазначено, що нові користувачі отримують невеликий обсяг безкоштовних кредитів для тестування API. Це одноразовий пробний період, а точну суму показано в Console під час реєстрації. Після його завершення потрібно поповнити рахунок. Виняток становлять передплатники Max і Team: їхні плани містять щомісячні кредити API, про які йдеться вище. Якщо вам потрібна нульова гранична вартість запиту, а не якість frontier-рівня, можна самостійно розгорнути модель з відкритими вагами за допомогою Ollama і витрачати RAM замість токенів.
Як захистити API key на сервері?
Ніколи не зберігайте його в коді, git або змінних середовища .bashrc. Не вводьте його в shell, де він залишиться в історії. Зберігайте ключ у файлі, власником якого є root, із правами 600, і завантажуйте його окремо для кожного процесу: через wrapper script для інтерактивного використання та через EnvironmentFile= для systemd. Використовуйте окремий ключ для кожного сервера або проєкту. Тоді відкликання скомпрометованого ключа буде точковою операцією, а не масштабною заміною доступів. Якщо ключ потрапив на сайт для обміну текстом або в git commit, негайно відкличте його в Console. Видалення commit не усуває витік ключа.
Яку модель Claude вибрати спочатку?
Почніть із claude-opus-5-5, якщо ви перевіряєте, чи достатньо якісні результати для подальшого використання, хочете оцінити ідею на максимальній якості, а обсяг використання на рівні hobby незначний за вартістю. Коли prompt буде узгоджено, повторно обробіть реальні вхідні дані за допомогою claude-haiku-5-5. Для узагальнення, класифікації та первинного аналізу журналів ця модель часто достатньо якісна, а її ціна за токен приблизно в 40 разів нижча. Переходьте на Haiku або Sonnet на основі вимірювань, а не за замовчуванням.