Перед уроком
Обери свою групу й себе зі списку. Тоді відповіді й перевірки завдань запишуться саме на тебе.
Зрозуміти, як одна програма просить іншу через API: розібрати запит до ШІ по рядках, навчити свого бота відповідати на команду /ask за допомогою безкоштовної ШІ-моделі й побачити, як працює ШІ-помічник для програмістів OpenCode.
Замість <логін> підстав свій — той, який тобі видали на першому занятті. Логін має вигляд bohdan.g9: ім’я, крапка, номер групи. Кабінет — сторінка, де видно, які пункти автоперевірки тобі вже зараховано і що саме написав скрипт про кожен незарахований.
JSON — текстовий формат запису даних, у якому програми обмінюються ними між собою: фігурні дужки, усередині пари «ключ: значення», список у квадратних дужках. Ти зустрічав його на заняттях 02 і 05. Сьогодні в такому форматі бот обмінюватиметься даними зі ШІ.
Досі твій бот відповідав лише тим, що ти сам у нього вписав. Сьогодні він навчиться питати розумну програму — ШІ (штучний інтелект) — і передавати людині її відповідь. У бота з’явиться команда /ask: пишеш /ask що таке порт? — і отримуєш пояснення, якого в коді немає.
Заодно ти вперше розглянеш запит до API по рядках: що таке адреса, метод, заголовки, тіло запиту і код відповіді. Це та сама мова, якою програми розмовляють між собою всюди: у Telegram, у картах, у відео-сервісах.
Наприкінці заняття викладач покаже OpenCode — безкоштовного ШІ-помічника для програмістів, який працює в терміналі. Ти побачиш, чим він відрізняється від звичайного чату з ШІ, і зможеш вдома спробувати сам.
Про ШІШІ часто відповідає цікаво, але іноді впевнено каже неправду. Бот лише передає його слова, і за те, що він відповів, відповідаєш ти, бо це твій бот. Тому сьогодні ми ще й перевіряємо, що бот не ламається, коли ШІ недоступний.
Запит за потреби, а не серверУ твоєму боті немає власного «сервера API», який постійно слухає й займає пам’ять. Бот — клієнт: він звертається до Telegram чи до ШІ лише тоді, коли треба (прийшло повідомлення — зробили запит, отримали відповідь, забули). Постійно ввімкнений сервер, навпаки, цілодобово чекає на чужі запити й їсть пам’ять, а ще відчиняє двері назовні. Тому в тебе на сервері такого немає.
Сьогодні ти вчишся саме бути клієнтом.
API — спосіб, яким одна програма просить іншу щось зробити (так само, як у словничку заняття 02): Telegram має своє API, ШІ має своє. Ти надсилаєш запит у певному порядку, а у відповідь отримуєш дані. Запит іде за правилами, які називають HTTP — це ті самі правила, за якими браузер відкриває сайти. Коли бот питає ШІ, він посилає саме такий запит. Тільки замість гарної сторінки у відповідь приходять дані. Ось запит, який посилає твій бот (скорочено):
POST /llm/v1/chat/completions HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Authorization: Bearer мій-ключ
{"model": "openrouter/free",
"messages": [{"role": "user", "content": "що таке порт?"}]}Розберемо рядок за рядком:
POST — метод, тобто що зробити. GET означає «дай мені», а POST — «ось тобі дані, зроби з ними щось». Питання до ШІ — це дані, тому POST./llm/v1/chat/completions — шлях: в які «двері» на сервері стукаємо. Слово completions означає «дописати»: ШІ дописує відповідь до розмови.Host — на чиїй адресі ми стукаємо. Далі йде порожній рядок: він каже «заголовки скінчилися, далі тіло запиту».Content-Type: application/json — заголовок: наклейка на посилці — що всередині й хто відправник. Тут вона каже, що тіло написане у форматі JSON (ти вже бачив його на занятті 02).Authorization: Bearer мій-ключ — ще одна записка: «ось мій ключ, дозволь мені користуватися». Слово Bearer («той, хто пред’явив») каже серверу: далі йде мій ключ. Без ключа більшість сервісів відповідають «стоп».model (модель, тобто який саме ШІ) ми просимо і що саме пишемо в messages (повідомлення). Роль user — «людина».На кожен запит сервер відповідає кодом — числом, яке коротко каже, як усе минуло. Ці коди ти вже зустрічав:
| Код | Що означає | Що робить наш бот |
|---|---|---|
200 | Усе добре, ось відповідь | Дістає текст і віддає людині |
401, 403 | Ключ не підійшов або його немає | Каже: «ШІ не пускає, перевір ключ у .env» |
429 | Забагато запитів — ліміт вичерпано | Каже: «Спробуй пізніше» |
503 | ШІ ще не підключено або тимчасово не працює | Каже: «ШІ ще не підключено, скажи викладачу» |
Коли код 200, у тілі відповіді приходить JSON. Потрібний нам текст лежить глибоко, тому до нього йдемо вкладеністю — так само, як на занятті 02 діставали ім’я з оновлення Telegram:
{"choices": [
{"message": {"role": "assistant",
"content": "Порт — це номер «дверей» програми на сервері."}}
]}У Python це data["choices"][0]["message"]["content"]: «візьми список choices, його перший елемент (рахунок іде з нуля), потім message, потім content».
Бот отримав від сервера ШІ код 429. Що це означає?
Куди ходити по ШІ, з яким ключем і яку модель вибрати — це три налаштування. Їх ми кладемо у файл ~/app/.env (той самий, де лежить токен бота), а в коді лише читаємо:
LLM_BASE_URL=http://127.0.0.1/llm/v1
LLM_API_KEY=school
LLM_MODEL=openrouter/freeЄ дві причини так робити. Перша — безпека: ключ — це пароль, а паролі не лежать у програмі, яку ти відправляєш на GitHub. Друга — гнучкість: захочеш інший ШІ — змінюєш три рядки у .env і перезапускаєш службу; програму не чіпаєш. Так програмісти змінюють ШІ чи його адресу, не переписуючи код.
Слово «модель»Тут «модель» — це сам ШІ. Не плутай із заняттям 05, де «модель» означала опис полів даних.
Рядки починаються з LLM_ — це скорочення від «великі мовні моделі» (Large Language Models), так називають ШІ, що вміють писати текст. Слово free в назві моделі означає «безкоштовна».
Безкоштовне — не означає безмежне. Сервіс OpenRouter, через який ми користуємося безкоштовними моделями, дає без поповнення рахунку лише 50 запитів на добу на один ключ (а з разовим поповненням — до тисячі). Тому ми не ставимо по ключу кожному: ключ один, і він у викладача, бо ключ від такого сервісу оформлює доросла людина.
Щоб ключ не дістався учням, на сервері стоїть шлюз школи — «швейцар», який приймає запити від твого бота й сам додає справжній ключ. Адреса шлюзу — http://127.0.0.1/llm/v1 (127.0.0.1 означає «цей самий комп’ютер»), а ззовні, з інтернету, туди не достукатись. Ключ знає лише швейцар, тому ти його не бачиш і не можеш загубити. Щоб запитів вистачало всім, шлюз пропускає не більше десяти на хвилину на весь клас.
Якщо ШІ відповідає «ще не підключено»Поки викладач не вставив у шлюз ключ, шлюз відповідає кодом 503. Це не твоя помилка. Усі кроки уроку все одно можна пройти: автоперевірка має власну «тестову модель» і не витрачає спільний ліміт.
Інтернет, ліміти, чужі сервери — усе може зламатися. Хороший бот переживає це спокійно: пробує запит і, якщо щось пішло не так, прямо каже про це людині. У Python для цього є try / except — «спробуй» / «якщо не вийшло». Ось головна думка з файлу llm.py, який ти скопіюєш (це схема, а не код для вставки):
try:
відповідь = питаємо_ШІ(питання)
except:
відповідь = "Не вдалося дістатися до ШІ. Спробуй пізніше."Без цього одна несправність чужого сервера зупинила б обробку повідомлення — і людина отримала б мовчання або «Ой, щось пішло не так». А з цим — зрозуміле пояснення. Автоперевірка L06.4 навмисно «ламає» адресу ШІ й дивиться, що твій бот не впав.
Навіщо в боті try / except навколо запиту до ШІ?
У чаті ти питаєш — ШІ відповідає текстом, а копіювати його в код ти мусиш сам. Агент — це ШІ, якому дали «руки»: він сам читає файли твого проєкту, пропонує зміни й за твоєю згодою запускає команди. OpenCode — безкоштовна відкрита програма-агент для терміналу. Вона не має власного «мозку»: ти підключаєш до неї будь-який ШІ — наприклад, безкоштовні моделі OpenRouter, ті самі, що й твій бот, або моделі, що працюють на твоєму комп’ютері.
/connect, а потім вибрати модель командою /models..env з токенами; читай кожну зміну, перш ніж погодитися; нічого не вставляй наосліп на сервері.Не плутайЄ ще платна послуга OpenCode Zen, яку треба оплачувати, — нам вона не потрібна. Безкоштовний шлях — підключити безкоштовні моделі OpenRouter або локальні моделі.
Чим агент (як OpenCode) відрізняється від звичайного чату з ШІ?
| Слово | Що означає |
|---|---|
| API, запит, відповідь | Спосіб, яким одна програма просить іншу щось зробити, і те, що приходить у відповідь |
| метод (GET, POST) | Слово на початку запиту: «дай» (GET) або «ось дані, прийми їх» (POST) |
| заголовок | Службова записка до запиту: формат даних, ключ тощо |
| тіло запиту | Самі дані, які надсилаємо, зазвичай у форматі JSON |
| код відповіді | Число, що коротко каже, як усе минуло: 200, 401, 429, 503 |
| ключ (API-ключ) | Пароль програми: за ним сервіс розуміє, хто питає |
| модель | Конкретний ШІ, до якого звертаємося |
| ліміт запитів | Скільки звернень сервіс дозволяє за добу чи за хвилину |
| шлюз | «Швейцар»: приймає запити й сам додає справжній ключ, не показуючи його |
| try / except | Спробуй виконати; якщо не вийшло — зроби це замість падіння |
| ШІ-агент, OpenCode | ШІ, який сам читає файли й пропонує зміни; програма для терміналу з відкритим кодом |
Виконуй кроки по черзі. Якщо ШІ школи ще не підключено (код 503), усе одно йди далі: у кроках 2 і 6 побачиш пояснення замість відповіді ШІ, і це нормально.
ssh <логін>@91.219.61.4
cd ~/app
systemctl --user is-active club-bot
tail -n 5 ~/logs/club-bot.logМає бути active і кілька рядків журналу. Якщо ні — спершу полагодь бота за уроками 02–05.
Програма curl дозволяє надіслати такий самий запит з терміналу. Спробуй (це одна довга команда):
curl -s http://127.0.0.1/llm/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"openrouter/free","messages":[{"role":"user","content":"Скажи одне слово: привіт"}]}'Знайди у своїй команді метод (у curl з -d це POST), адресу, заголовок і тіло — усе, що розбирали в теорії. У відповідь прийде або JSON із полем choices (ШІ відповів — знайди в ньому свій «привіт»), або {"error":{"message":"шлюз ШІ школи ще не налаштовано…"}} — шлюз чекає на ключ викладача. Обидва варіанти правильні.
cp /opt/club/kurs/06/llm.py ~/app/
nano ~/app/llm.pyЗнайди у файлі: де читаються LLM_BASE_URL, LLM_API_KEY, LLM_MODEL (з .env), де формується тіло запиту (json.dumps), де стоїть заголовок Authorization, і де обробляються коди 429, 401, 503. Це та сама теорія, тільки мовою Python. Вийти з редактора: Ctrl+X.
nano ~/app/.envДопиши внизу три рядки (токен бота вище не чіпай):
LLM_BASE_URL=http://127.0.0.1/llm/v1
LLM_API_KEY=school
LLM_MODEL=openrouter/freeЗбережи: Ctrl+O, Enter, Ctrl+X. Перевір, що файл і далі закритий для інших і що там три нових рядки:
chmod 600 ~/app/.env
grep -c "^LLM_" ~/app/.envДруга команда має показати 3. Ключ school — просто слово: справжній ключ додає шлюз.
Відкрий ~/app/bot.py. У функції handle_update знайди рядок if text.startswith("/"): — він відповідає «Не знаю такої команди». Блок для /ask вставляй перед ним, одразу після блоку /help. Порядок має бути такий: /start, /help, /ask, потім «будь-яка інша команда», і наприкінці return f"Ти написав: …". Якщо поставити /ask нижче, повідомлення «/ask …» перехопить рядок «Не знаю такої команди» і до твого блоку не дійде.
if text.startswith("/ask"):
question = text[len("/ask"):].strip()
if not question:
return "Напиши питання після команди, наприклад: /ask що таке API?"
return answer(question)А під функцією handle_update, на початку рядка (без відступу), додай нову функцію:
def answer(question):
import llm
return llm.ask(question)text[len("/ask"):] відрізає слово /ask і залишає лише питання; .strip() прибирає зайві пробіли. У тексті підказки /help додай рядок /ask питання — відповім за допомогою ШІ. Якщо заплутався, порівняй зі зразком: diff /opt/club/kurs/06/bot.py ~/app/bot.py (покаже лише відмінності; твої власні тексти там теж відрізнятимуться — це нормально).
cd ~/app
python3 try_bot.py "/ask що таке API?"
python3 try_bot.py "/ask"Перша команда — або відповідь ШІ, або «ШІ школи ще не підключено. Скажи викладачу.». Друга — підказка «Напиши питання після команди…». Кілька рядків з Traceback — це помилка: читай останній рядок. No module named 'llm' — ти не в ~/app або не скопіював llm.py (крок 3).
LLM_BASE_URL=http://127.0.0.1:9/v1 python3 try_bot.py "/ask привіт"Ми на одну команду підмінили адресу на неіснуючу (порт 9 — там нічого не слухає). Бот має відповісти Не вдалося дістатися до ШІ. Спробуй пізніше. — і не впасти. Саме це перевіряє критерій L06.4. Файл .env від цього не змінився.
/opt/club/venv/bin/python3 -c "import bot" && echo готово
systemctl --user restart club-bot
sleep 3
systemctl --user is-active club-bot
tail -n 8 ~/logs/club-bot.logСпочатку готово, потім active. У Telegram напиши своєму боту /ask що таке порт?. Якщо відповів — зроби скріншот для критерію L06.5, який перевіряє викладач. Без Telegram — покажи викладачу вивід try_bot.py з кроку 6.
Викладач на екрані підключає OpenCode до безкоштовної моделі й просить його пояснити файл bot.py. Слідкуй за тим, що він робить сам: читає файл, пропонує зміну, питає дозвіл. А якщо захочеш спробувати вдома:
npm install -g opencode-ai
mkdir opencode-demo
cd opencode-demo
opencodeДля npm потрібна програма Node.js (nodejs.org). В інтерфейсі набери /connect, вибери OpenRouter (потрібен безкоштовний ключ із сайту openrouter.ai; за правилами сервісу ключ оформлює людина від 18 років, тож роби це разом із дорослим або підключи локальну модель — її викладач покаже окремо), потім /models і вибери модель із позначкою :free. Попроси: «Створи файл hello.py, який друкує привіт, і поясни кожен рядок». Правило: запускай агента лише у порожній навчальній теці, як opencode-demo вище, — ніколи там, де лежать паролі й токени.
cd ~/app
git status
git check-ignore -v .envУ git status має бути llm.py (новий) і bot.py (змінений), а .env — відсутній. Тоді:
git add -A
git commit -m "Заняття 06: команда /ask питає ШІ"
git pushОнови сторінку репозиторію на GitHub: llm.py там є, а .env — ні.
Автоперевірка заходить на сервер від твого імені й перевіряє чотири автоматичні пункти (п’ятий — людський). L06.1 і L06.2 — що у ~/app/.env є рядки LLM_BASE_URL= і LLM_MODEL=. L06.3 — що /ask справді йде в ШІ: перевірка підставляє власну «тестову модель» за адресою з .env, і спільний ліміт не витрачається. L06.4 — що бот не падає, коли адреса ШІ неправильна. L06.5 — живу відповідь у Telegram — оцінює викладач. Натисни «Перевірити практичну частину» внизу сторінки.
| Код | Що перевіряється | Якщо не зараховано |
|---|---|---|
| L06.1 | у .env задано, куди ходити по ШІ (LLM_BASE_URL) автоперевірка | У ~/app/.env немає LLM_BASE_URL. Учням до 18 років — шлюз школи: LLM_BASE_URL=http://127.0.0.1/llm/v1 |
| L06.2 | у .env задано модель (LLM_MODEL) автоперевірка | У ~/app/.env немає LLM_MODEL. Безкоштовний варіант: LLM_MODEL=openrouter/free |
| L06.3 | /ask іде в ШІ й повертає його відповідь автоперевірка | Перевірка підставила свій «ШІ» за адресою з LLM_BASE_URL і не дочекалась правильного запиту або його відповіді в боті. Потрібен POST {LLM_BASE_URL}/chat/completions з JSON {model, messages}, а текст відповіді — з choices[0].message.content. |
| L06.4 | коли ШІ недоступний, бот не падає автоперевірка | Коли ШІ не відповідає, бот має сказати це людині, а не впасти. Обгорни запит у try/except і поверни зрозумілий текст. |
| L06.5 | живий бот у Telegram відповідає на /ask викладач | Позначку ставить викладач на занятті або після нього. |
Кожне питання оцінюється відразу в браузері — це лишається так само. Ця кнопка лише надсилає підсумок учителю, щоб відповіді не зникли разом із закритою вкладкою.
Список критеріїв і що саме вони означають — у таблиці вище, «Що перевіряє автоматика». Тут — та сама перевірка, але прямо зараз, без очікування на найближчий прохід за розкладом.
/ask і запиши відповіді. Познач, у яких ШІ помилився чи вигадав. Підготуй відповідь: чому не можна сліпо вірити ШІ?llm.py знайди текст SYSTEM — це інструкція, яку ШІ отримує перед кожним питанням. Зміни її (наприклад, щоб бот відповідав як веселий астроном) і перевір через try_bot.py.club-bot) → шлюз → ШІ. Підпиши, де API, де ключ, де ліміт, і постав помітку там, де запит іде «за потреби».git add -A, git commit -m "…", git push.Це код 503: шлюз чекає ключа викладача. Твій код у порядку. Покажи викладачу вивід try_bot.py — критерії L06.1–L06.4 зарахуються й без справжньої відповіді ШІ.
Код 401 або 403. Відкрий ~/app/.env і перевір, що рядок такий: LLM_API_KEY=school — без пробілів навколо знака = і без лапок. Після правки .env перезапусти службу: systemctl --user restart club-bot.
Код 429: безкоштовний ліміт на сьогодні (або на цю хвилину) вичерпано всім класом. Почекай хвилину й повтори. Це нормальна поведінка безкоштовного сервісу, а не помилка твого коду.
Виконай cd ~/app && ls llm.py. Немає файлу — скопіюй його знову (крок 3). Файл є — прочитай останній рядок Traceback: SyntaxError чи IndentationError означають помилку у bot.py: перевір відступи нового блоку /ask (чотири пропуски від краю, як в іншого if поруч) і порівняй зі зразком: diff /opt/club/kurs/06/bot.py ~/app/bot.py.
Блок /ask стоїть нижче за рядок if text.startswith("/"):. Перенеси його вище: порядок має бути /start, /help, /ask, потім «будь-яка інша команда». Перевір: python3 try_bot.py "/ask привіт".
Перевірка підставила власну «тестову модель» за адресою з LLM_BASE_URL і дивиться три речі. Бот має звернутися за цією адресою (/ask дійшов до llm.py); запит має бути POST …/chat/completions з JSON, де є model і messages; відповідь ШІ має дійти до людини — answer() повертає те, що повернув llm.ask(), нічого не гублячи.
Перевірка підміняє адресу ШІ на неіснуючу й чекає, що бот спробує звернутися і чемно відповість замість мовчання чи помилки. Якщо не зараховано, спершу переконайся, що /ask узагалі доходить до llm.py (див. картку вище), а потім що запит в llm.py обгорнуто в try / except (він уже є у файлі, який ти скопіював).
Перевірка шукає рядки саме з такими іменами: LLM_BASE_URL= і LLM_MODEL= на початку рядка, без пробілу перед ними й без «#». Подивися: grep -n "^LLM_" ~/app/.env — має бути три рядки.
opencode: command not found — програма не встановилася або термінал треба закрити й відкрити знову. npm: command not found — спершу встанови Node.js. Агент мовчить або пише про ліміт — безкоштовні моделі мають обмеження; спробуй іншу модель із :free через /models або повтори пізніше.