Розробка ШІ-агентів: від прототипу до продакшену
У листопаді 2024 року власник вантажної брокерської компанії у Гданську попросив мене перевести його диспетчерського помічника з демо в щоденну роботу. Демо відповідало на двадцять зразкових питань про вантажі, тарифи й доки. Воно працювало на сорока чистих документах. Воно вразило кімнату. Я підключив того самого помічника до живого сховища документів на тисячу сто файлів, дав доступ сорока диспетчерам і побачив чотири збої до обіду. Він процитував тариф із договору, що сплив у березні. Він вигадав номер дока для складу з шістьма доками. Він відповідав двадцять дві секунди на одне питання про затриманий вантаж. Він спалював дев'ять центів за виклик за його обсягу. Власник поставив запуск на паузу того самого дня. Кожен агент, якого я випускаю в продакшен, тепер проходить дорогу нижче. Шість етапів ведуть його від того провального ранку до рівної щоденної роботи. Я веду їх по черзі. Кожен етап наводить цифри, які я заміряв на клієнтських збірках.
Чому демо помирають у продакшені
Демо живуть у теплиці. Двадцять зразкових питань, сорок свіжих документів, один користувач, нуль лічильника витрат. Продакшен відчиняє двері. Дві тисячі живих питань приходять з одруками, половинами фраз і фотографіями паперів. Тисяча сто документів включає прострочені договори, дублі тарифів і три версії одного прайса. Сорок людей питають разом у понеділок зранку. Кожен виклик лягає в рахунок.
Три вбивці закінчують більшість демо в перший тиждень. Протухлий пошук годує модель старими сторінками, і модель цитує їх з повною впевненістю. Відсутність оцінок ховає шкоду: ніхто не проганяє фіксований набір питань після кожної зміни, тому дрейф розповзається, і його ніхто не бачить. Незаміряні вартість і затримки б'ють власника наприкінці місяця, коли десять тисяч викликів по дев'ять центів кожен дають дев'ятсот доларів за відповіді, яким ніхто не вірить.
Порядок лагодження важить. Спочатку я лагоджу пошук: жодна правка формулювань не переживе невірні вихідні сторінки. Потім додаю набір оцінки: мені потрібне табло раніше будь-яких інших змін. Третім обмежую вартість і затримки: бюджети визначають вибір моделі. Четвертим закриваю комплаєнс-шар: імена клієнтів і номери телефонів заслуговують окремої перевірки. Потім випускаю. Пропустіть порядок, і ви налаштовуватимете фрази поверх зламаного пошуку.
RAG-конвеєр, який тримає навантаження
Документи я зберігаю в Postgres із pgvector на Neon. Одна таблиця тримає фрагменти: chunk_id uuid, tenant_id text, source_url text, updated_at timestamptz, content text, embedding vector(1536). HNSW-індекс на колонці ембеддингів віддає пошук найближчих сусідів з m 16 та ef_construction 64. Btree-індекс за tenant_id з updated_at ріже кожен запит під потрібного клієнта й спочатку під свіжі файли.
Текст я ріжу на фрагменти по чотириста-шістсот токенів із перекриттям вісімдесят токенів. Кожен фрагмент зберігає вихідну сторінку й дату. Короткі фрагменти губили контекст у моїх замірах: повнота на фіксованому наборі питань стала на шістдесяти одному відсотку. Довгі фрагменти розмивали збіг: модель цитувала цілі сторінки замість окремих рядків. Вікно чотириста-шістсот підняло повноту до вісімдесяти восьми відсотків на тому самому наборі.
Кожен запит фільтрується за tenant_id, шукає п'ять найближчих фрагментів і відкидає все, що власник помітив простроченим. Відповідь друкує джерела внизу: ім'я документа, сторінку, дату. Диспетчери відкривають їх і вірять тому, що перевірили. Перевірку повноти я проганяю щотижня. У кожного документа один автор, і протухлі копії покидають сховище в день заміни.
Оцінки: звичка двохсот прикладів
Я тримаю двісті фіксованих питань із записаними відповідями й вихідними сторінками. Вісімдесят закривають рядову роботу: номери вантажів, тарифи, години доків. Шістдесят несуть одруки, пропущені вулиці й напівзабуті імена. Сорок вимагають відмови: бот повідомляє, що не знайшов документ, і передає чат диспетчеру. Двадцять ставлять пастки: прострочені тарифи, конфліктні прайси, два склади на одній вулиці.
Повний набір я проганяю перед кожним релізом і записую дату, версію моделі й рахунок. Просідання сильніше трьох пунктів зупиняє реліз, і це правило тримається. Гданська збірка показала навіщо: один березневий файл тарифів сплив, набір спіймав одинадцять невірних цитат за один прогін, і реліз зачекав день, доки я замінив файл. Без набору ці одинадцять цитат дійшли б до сорока перевізників.
Кожен промах із продакшену входить у набір у день появи. Водій дзвонить про невірне вікно, я додаю точне питання з вірною відповіддю й джерелом. Згодом набір росте за двісті. Ім'я лишається. Двісті задає мінімум, і мінімум тримає планку.
Бюджети вартості й затримок
Ціну кожного виклику я рахую до вибору моделі. Одна гданська відповідь тягла п'ять фрагментів приблизно по триста п'ятдесят токенів кожен, додавала триста токенів інструкцій та історії й писала триста п'ятдесят токенів у відповідь. На вхід ішло близько двох тисяч ста токенів. На вихід ішло триста п'ятдесят.
За тарифами малої моделі п'ятнадцять центів за мільйон вхідних токенів і шістдесят центів за мільйон вихідних такий виклик коштує близько п'яти десятитисячних долара. Дві тисячі сто вхідних токенів по $0.15 за мільйон дають $0.00032. Триста п'ятдесят вихідних токенів по $0.60 за мільйон дають $0.00021. Ембеддинги додають $0.00002. Разом близько $0.0005. Десять тисяч викликів на місяць коштують п'ять доларів. Та сама форма на флагманській моделі по $5 за мільйон вхідних і $15 за мільйон вихідних коштує близько $0.016 за виклик, або сто шістдесят доларів на місяць за ті самі десять тисяч викликів.
Тому рядові читання я спрямовую на малу модель, а флагман тримаю для листів за претензіями про збитки, де один невірний пункт коштує дорожче за місяць викликів. Я ставлю дві планки затримок: перша видима відповідь всередині двох секунд через потоковий статус, повна відповідь всередині дев'яти секунд. Щотижня я читаю p50 і p95 із журналу викликів. Коли p95 переходить планку два тижні поспіль, я врізаю фрагменти раніше, ніж чіпаю модель.
Комплаєнс-шар
Агенти, які читають імена клієнтів, номери телефонів і договори, потребують малого шару даних з першого дня: я пишу кожен виклик інструмента з посиланням на схвалення, чищу ідентифікатори до потрапляння в журнали й ставлю на кожен рядок дату видалення, щоб старі листи покидали сховище за розкладом. Точні таблиці, налаштування очищення й нічну задачу видалення я описав у пості Чому вашому ШІ-агенту потрібен комплаєнс-шар і додаю цей шар у кожну збірку для продакшену до подачі живого трафіку.
Чек-лист перед релізом
Цей список я проганяю вранці перед будь-яким запуском.
- Пошук фільтрує за tenant_id і відкидає прострочені файли.
- Кожна відповідь друкує ім'я джерела, сторінку й дату.
- Набір із двохсот питань проганяється в зеленій зоні без просідання сильніше трьох пунктів.
- Ціна виклику лежить на папері з прикладеною математикою місячного обсягу.
- p95 відповідає всередині дев'яти секунд два тижні поспіль.
- Вихідні повідомлення чекають посилання на схвалення.
- Кожен збережений рядок несе дату видалення з нічною задачею видалення за спиною.
- Один прапорець відкочує реліз за хвилини.
Якщо ваш помічник досі живе у формі демо, замовте ШІ-аудит: я оціню вашу збірку за кожним етапом вище.