У новому повному курсі на YouTube розробник і автор каналу Tech With Tim показує, як будувати production‑готові AI‑агенти в Python на базі фреймворку Agentspan. Один із найцікавіших прикладів — сапорт‑агент, що працює за RAG‑логікою, вміє шукати по базі знань і замовленнях, видає структурований Pydantic‑вивід, вимагає людського погодження перед рефандом і блокує prompt‑інʼєкції через guardrails. Саме цей агент вартий окремого розгляду як прототип реального AI‑сапорту.

Від «майже RAG» до реального сапорт‑сценарію
Другий агент у курсі позиціонується як «kind of a RAG‑based agent where we’re going to be able to look up some info in something like a database or documentation or whatever we have». Автор одразу застерігає: це не повний RAG зі векторними індексами — але логіка та інтерфейси максимально наближені до реального сценарію.
Замінником векторного пошуку виступають прості Python‑інструменти:
- один шукає по «документації» — мок‑базі знань зі статтями підтримки,
- інший звертається до мок‑бази замовлень,
- третій відповідає за запуск рефанду.
На цьому шарі інструментів будується повноцінний сапорт‑флоу: агент спершу шукає відповідь у знаннях, за потреби дістає дані по замовленню, а коли справа доходить до повернення коштів — зупиняється й чекає на схвалення людини.
Важлива деталь: цей агент не просто генерує «гарний текст». Він повертає жорстко структуровану відповідь через Pydantic‑модель, що робить його придатним для вбудовування в будь‑який бекенд чи сапорт‑панель.
Pydantic‑відповідь: коли LLM повертає не текст, а структуру
Щоб уникнути вільного текстового виводу та полегшити інтеграцію, агент налаштований на структурований output. Для цього оголошується Pydantic‑клас:
class SupportResponse(BaseModel):
stage: str = Field(description="stage like answered, refunded, or rejected")
successful: bool
message: str
Автор підкреслює: «I want this AI model to actually give me output that has the following fields… a stage… successful boolean… and then we’re going to have a message».
Stage описує етап обробки звернення («answered», «refunded», «rejected» тощо), successful сигналізує, чи завершився запит успішно, message містить текст для користувача.
Сенс такого підходу в тому, що LLM більше не розглядається як «генератор тексту». Модель виводить дані, які гарантовано вкладаються в схему. Сам автор акцентує: «This is a super basic structured response, but if we wanted it to give us like a price or a number or a time… we’d just set that as a field and the model will automatically fill in these values».
Тобто будь‑які додаткові параметри — сума, дата, SLA, категорія тікету — легко перетворюються на окремі поля Pydantic‑моделі. Це важливо для бізнес‑логіки: система може не лише показати текст оператору, а й автоматично приймати рішення на основі полів stage і successful.
Пошук по базі знань: простий keyword‑інструмент замість складного RAG
Перша критична для сапорту можливість — пошук по knowledge base. Замість складної інфраструктури з векторами використовується максимально простий інструмент:
«The first tool is going to be one that can search our knowledge base… we’re just doing a quick keyword search… if any keyword of what the user typed in was in this… then we’re just going to return whatever the body or the content of this is».
Функція перебирає пари title, body у мок‑словнику документації, приводить запит до нижнього регістру і перевіряє, чи входить заголовок статті у запит. У разі збігу повертається body — текст статті підтримки. Якщо нічого не знайдено, повертається фраза на кшталт «no matching support articles found».
Це далеко не ідеальний пошук, але він чітко показує патерн:
- Ззовні це оголошений tool Agentspan з чітким описом («search support docs»).
- Усередині — будь‑яка логіка Python: від простого keyword‑пошуку до реального RAG‑провайдера.
LLM бачить цей інструмент як функцію, яку можна викликати. Якщо користувач запитує про «shipping», агент викликає tool пошуку в документації, отримує текст статті про доставку і вже на його основі формує структуровану SupportResponse.
Замовлення та помилки: lookup_order як базовий building block
Друга група функцій — робота з замовленнями. Тут використовується окремий інструмент:
«We’re going to have some tools related to looking up some orders… define lookup_order… return the mock_db[‘orders’].get(order_id)… or return a dict error: order not found.»
Логіка знову ж проста:
- на вхід —
order_id(рядок), - спроба знайти его в
mock_db['orders'], - у разі успіху — повертається словник із деталями замовлення (включно із сумою),
- у разі невдачі — словник
{"error": "order not found"}.
Для LLM це ще один tool, яким можна скористатися після уточнення ID замовлення. Для розробника — очевидний шаблон: місце, де в реальній системі підключається справжня БД або API.
Цей інструмент критичний ще й тому, що він готує дані для наступного кроку — рефанду. Саме з відповіді lookup_order агент дістає суму, яку згодом пропонує повернути.
Рефанд із human‑in‑the‑loop: approval_required як запобіжник
Ключова частина сценарію підтримки — обробка повернень коштів. Автор одразу задає бізнес‑умову: «We’re going to have one tool that will allow the user to refund. However, before we call that tool, we are going to ask for a human in the loop approval because we don’t want to just automatically refund something unless the user actually allows that.»
Для цього оголошується третій tool — process_refund — із особливим параметром:
«This time we are going to say approval_required is equal to True… this means that we need to manually approve this in order for the function to execute.»
Функція приймає order_id і amount та повертає рядок на кшталт «refunded $amount for order order_id». Насправді ж це лише мок, жодних реальних транзакцій немає. Але логіка human‑in‑the‑loop реалізована повноцінно.
Механіка така:
- LLM, дійшовши до необхідності рефанду, формує виклик
process_refundз параметрами. - Agentspan, бачачи
approval_required=True, не запускає функцію автоматично, а ставить агент у стан очікування. - Код на Python отримує подію типу
WAITINGу стримі подій.
Далі включається CLI‑шар: розробник показує, як цим станом керувати вручну.
Обробка очікування: події, approve/reject і прозоре UX для оператора
Щоб коректно реагувати на запити рефанду, агент підключається до потоку подій (handle.stream()) і переглядає їх у циклі. Автор додає декілька умовних блоків, які зчитують потрібні дані з подій типу tool_call та tool_result: шукають order_id і amount у аргументах та результатах інструментів.
Критичний фрагмент — обробка події очікування:
«What I’m going to do is… if event.type is equal to EventType.WAITING… I’m going to print… “approval required, refund $amount for order order_id”… ask them approve yes or no… if decision is ‘y’ then handle.approve, otherwise handle.reject.»
У CLI це виглядає як:
- вивід повідомлення «approval required, refund $49.99 for order A100»;
- input‑запит «approve yes or no»;
- у разі
yвикликаєтьсяhandle.approve(), що запускає рефанд‑tool; - інакше —
handle.reject("user rejected").
По суті, це мінімалістичний інтерфейс оператора сапорту, підʼєднаний до AI‑агента через стрим подій Agentspan. У реальній системі це повідомлення легко перетворити на UI‑кнопку в адмінці, але патерн лишається тим самим: LLM ініціює дію, інфраструктура ставить її «на паузу», людина підтверджує або блокує.
Guardrails проти prompt‑інʼєкцій: фільтрація ще до LLM
Останній важливий шар захисту сапорт‑агента — guardrails. Вони дозволяють перехопити потенційно шкідливі або неприйнятні запити ще до того, як вони потраплять у LLM.
Автор пояснює: «I’m going to show you how we write a guardrail… block obvious prompt injection attempts… before the LLM even sees it… we can actually prevent against that by building in these guardrails.»
Створюється guardrail‑функція safe_support_request, яка приймає prompt: str і повертає GuardrailResult. Усередині формується список підозрілих фраз:
blocked = ["ignore", "ignore previous", "system prompt", "jailbreak"]
passed = not any(phrase in prompt.lower() for phrase in blocked)
return GuardrailResult(
passed=passed,
message="please ask a normal question this is blocked"
)
Потім вона підʼєднується до агента як окреме правило:
«We have this guardrail… safe_support_request… position is Position.INPUT and then on_fail is OnFail.RAISE… so as soon as we get some input to our agent, run it through the guardrail… if there is something wrong, then tell us and fail.»
Ключові параметри:
position=Position.INPUT— guardrail застосовується до вхідного запиту, до LLM;on_fail=OnFail.RAISE— у разі порушення правило генерує помилку й повністю зупиняє виконання.
Результат: будь‑яка спроба «jailbreak this prompt» чи «ignore previous instructions» не дійде до моделі, а користувач отримає відповідь із повідомленням guardrail‑функції.
У поєднанні зі структурованим виводом і human‑in‑the‑loop це створює цікавий тришаровий захист: інʼєкції блокуються на вході, реквест‑флоу описаний у схемі, а критичні операції підтверджує людина.
Висновок: прототип сапорт‑бота, який можна вбудовувати в продакшн
Сапорт‑агент із курсу Tech With Tim показує один із найпрактичніших шаблонів використання Agentspan:
- базовий RAG‑подібний пошук через інструменти по документації та замовленнях;
- структурований Pydantic‑вивід (
SupportResponse) зі stage і прапорцемsuccessful; - явний human‑in‑the‑loop для рефандів через
approval_required=Trueі обробку подійWAITING; - input‑guardrails, що блокують примітивні prompt‑інʼєкції до звернення до LLM.
Для команд, які будують AI‑сапорт‑сервіси, це готовий каркас: достатньо замінити мок‑базу на реальні джерела, поглибити пошук до повноцінного RAG і перенести CLI‑схвалення в веб‑інтерфейс. Важливіше, однак, інше — цей приклад демонструє, як будувати агентів одразу з продакшн‑вимогами: контроль, прозорість, безпека та передбачуваний вивід закладені в архітектуру з першого дня.
Джерело
YouTube: Build 3 PRODUCTION AI Agents in Python – Full Course (Agentspan)


