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). Key показується лише один раз, починається з sk-ant- і більше не може бути отриманий повторно. Скопіюйте його одразу або видаліть і створіть новий.
Щодо оплати: станом на July 2026 постійного free tier для API немає. У документації Anthropic із цінами зазначено, що нові користувачі отримують невелику кількість безкоштовних credits для тестування. Точна кількість відображається в Console під час реєстрації. Після вичерпання credits потрібно поповнити рахунок, інакше запити не виконуватимуться. Це окремо від підписки claude.ai. План Pro або Max не включає API credits, а API key не надає доступу до chat app. Якщо ви порівнюєте підписку з 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-4-8",
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')]Це не помилка, а представлення об’єкта. Відповіді можуть містити кілька типів блоків: текст, виклики інструментів і міркування. Тому перебирайте їх у циклі та перевіряйте block.type == "text" перед зверненням до .text. Додайте цей цикл одразу. Тоді цілий клас непорозумінь на кшталт «виводиться незрозумілий текст» не виникатиме.
Використовуйте точний ідентифікатор моделі claude-opus-4-8. Ідентифікатори поточного покоління не містять дати. Не додавайте суфікс із датою за звичкою або за порадою зі старого допису в блозі. Це призводить до помилки 404, яку розглянуто нижче.
Фактичний інструмент: пояснення
Нижче наведено повну програму: вона читає дані зі stdin, виводить діагностику в потоковому режимі та обробляє помилки:
#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic
MODEL = "claude-opus-4-8"
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=1500,
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, а потім додайте обгортку, яка завантажує ключ для інтерактивного використання:
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(Обгортка має запускатися через sudo, або env-файл має належати до групи, у якій є ваш адміністративний користувач. Виберіть один із варіантів свідомо, а не послаблюйте права на файл до 644.)
Чому потоковий режим. client.messages.stream виводить токени в міру їх надходження, а не мовчить протягом усього генерування. Це також дає змогу уникнути тайм-аутів HTTP для довгих відповідей. SDK фактично відхиляє дуже великі значення max_tokens у непотокових викликах саме з цієї причини. Якщо після цього потрібен зібраний об’єкт, викличте 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 4.8 вони коштують уп’ятеро дорожче за вхідні, а max_tokens жорстко обмежує їхню кількість. Неконтрольовано великий prompt не зможе створити більше вихідних даних, ніж ви дозволили. Встановлюйте значення відповідно до завдання: 1,500 достатньо для діагностики журналу, а для класифікації потрібно 100. Якщо відповіді обриваються посеред речення з stop_reason: "max_tokens", ліміт замалий. Свідомо збільшіть його, а не встановлюйте одразу надмірно велике значення.
Підраховуйте токени до надсилання. Вхідні дані також оплачуються, а журнали можуть бути великими. В API є endpoint для підрахунку, яким можна користуватися безкоштовно. Для нього діють окремі rate limits, незалежні від створення повідомлень:
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)Використовуйте його, щоб випадково не передати через інструмент журнал розміром 2 GB. Не використовуйте для цього tiktoken. Це tokenizer від OpenAI, і для типового тексту він недооцінює кількість токенів Claude приблизно на 15–20%, а для коду — ще більше.
Обирайте модель за завданням, а не через прихильність до певного продукту. Станом на July 2026 Opus 4.8 (claude-opus-4-8) коштує $5 за мільйон вхідних токенів і $25 за мільйон вихідних; Haiku 4.5 (claude-haiku-4-5) — $1/$5 і має контекст 200K; Sonnet 5 (claude-sonnet-5) займає проміжне положення — $3/$15, а до August 31, 2026 діє introductory pricing $2/$10. Наприклад, фрагмент журналу на 2,000 токенів із відповіддю на 500 токенів коштує приблизно $0.0225 в Opus і $0.0045 у Haiku. Почніть з Opus, поки оцінюєте якість результатів, а потім перевірте ті самі prompts у Haiku. Для простих масових перетворень різниця часто непомітна, а ціна становить лише п’яту частину. Перед тим як закладати ці значення в бюджет, перевірте актуальні дані на сторінці pricing.
Використовуйте Batches для всього, що може зачекати. Batches API обробляє запити асинхронно за 50% стандартної ціни, і більшість пакетів завершується протягом години. Нічні зведення, backfill, масова класифікація та інші завдання, для яких не потрібно чекати людині, слід виконувати через нього.
Використовуйте prompt caching для контексту, який повторюється. Якщо кожен виклик повторно надсилає той самий великий system prompt або runbook, позначте його як cacheable:
response = client.messages.create(
model="claude-opus-4-8",
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 ціни вхідних даних, а читання з кешу — приблизно 0.1x. TTL становить 5 хвилин, тому вже другий виклик у цьому інтервалі компенсує витрати на перший. Є два нюанси. Кешований prefix має перевищувати мінімальний обсяг для конкретної моделі — для Opus це кілька тисяч токенів. Тому короткий system prompt може взагалі не потрапити в кеш. Якщо cache_read_input_tokens залишається нульовим для ідентичних викликів, частина prefix змінюється під час кожного запиту. Зазвичай причиною є timestamp.
Пам’ятайте, що вважається вхідними даними. System prompts, tool definitions і вся історія в multi-turn conversations, яку ви повторно надсилаєте під час кожного turn, оплачуються як вхідні токени. Chat loop, у якому історія ніколи не скорочується, збільшує витрати квадратично. Повний принцип розрахунку варто зрозуміти до створення будь-якої 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'}Якщо ключ працює у вашій оболонці, але сервіс повертає 401, сервіс не отримав цей ключ. Пам’ятайте, що systemd не читає .bashrc; перевірте, чи EnvironmentFile= вказує на правильний шлях. Інші причини: лапки, вставлені у файл змінних середовища (ANTHROPIC_API_KEY="sk-ant-..."; systemd вилучає ці лапки, але обгортка оболонки з . file залишає їх у значенні, якщо лапки використано незвично), кінцеві пробіли або ключ, який ви відкликали в Console минулого тижня.
404 через помилку в назві моделі. Найпоширеніший варіант цієї помилки — додавання суфікса з датою до актуального ID моделі:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}ID актуальних моделей потрібно вказувати точно, як написано: claude-opus-4-8, claude-haiku-4-5, claude-sonnet-5. Копіюйте їх із документації до моделей, а не з пам’яті чи старого підручника.
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" означає, що модель досягла ліміту виводу посеред відповіді. Це очікувана поведінка. За потреби навмисно збільште ліміт.
Коли перший застосунок запрацює, створення AI-агента з Claude дасть змогу перетворити ті самі виклики API на агента, який використовує інструменти.
FAQ
Скільки коштує тестування Claude API?
Для такого інструмента це справді недорого. Станом на July 2026 Opus 4.8 коштує $5 за мільйон вхідних токенів і $25 за мільйон вихідних, тому типова діагностика журналу — кілька тисяч токенів на вході та кілька сотень на виході — коштує близько двох центів, а для Haiku 4.5 ($1/$5) — менше половини цента. Місяць щоденних дайджестів коштує дешевше за чашку кави. Ризик пов’язаний не з ціною окремого запиту, а з необмеженими циклами та необмеженим max_tokens, тому в цьому посібнику обидва параметри задаються явно.
Чи є безкоштовний рівень для Claude API?
Станом на July 2026 постійного безкоштовного рівня немає. У документації Anthropic щодо тарифів зазначено, що нові користувачі отримують невеликий обсяг безкоштовних кредитів для тестування API — одноразовий пробний ліміт, точна сума якого відображається в Console під час реєстрації. Після цього потрібно поповнювати рахунок. Якщо вам потрібна нульова гранична вартість запиту, а не якість frontier-моделей, можна самостійно розгорнути модель з відкритими вагами за допомогою Ollama і витрачати RAM замість токенів.
Як захистити API key на сервері?
Ніколи не зберігайте його в коді чи git і не експортуйте з .bashrc. Не вводьте його в shell, де він залишиться в історії. Зберігайте ключ у файлі, власником якого є root, із правами 600. Завантажуйте його окремо для кожного процесу: через wrapper script для інтерактивного використання та через EnvironmentFile= для systemd. Використовуйте окремий ключ для кожного сервера або проєкту, щоб відкликання скомпрометованого ключа було точковою операцією, а не масштабною заміною. Якщо ключ потрапив на paste site або в git commit, негайно відкличте його в Console. Видалення commit не усуває витік ключа.
З якої Claude-моделі почати?
Почніть із claude-opus-4-8, поки оцінюєте, чи достатньо якісні результати для подальшого використання. Так ви оціните ідею на максимальній якості, а за hobby-обсягу різниця у вартості становить лише центи. Коли prompt буде узгоджено, повторно обробіть реальні вхідні дані за допомогою claude-haiku-4-5. Для узагальнення, класифікації та первинного аналізу журналів ця модель часто дає настільки ж добрі результати за п’яту частину вартості. Переходьте на Haiku або Sonnet на основі вимірювань, а не за замовчуванням.