Автоматизація обробки даних з використанням AI-агентів

Лекція з практичними прикладами у ChatGPT

Лекції
Аналітика даних
Author

Юрій Клебан

Published

20 жовтня 2025 р.

1 Презентація

2 🎯 Мета заняття та очікувані результати

Мета: ознайомити з поняттям AI-агента, його архітектурою та життєвим циклом; показати підходи до створення агентів для автоматизації аналітичних і рутинних бізнес-процесів за допомогою Python-екосистеми, фреймворку LangChain, а також «no-code/low-code» сервісів на кшталт OpenAI Agent Builder, Make і n8n. Після лекції ви розумітимете відмінності між реактивними та планувальними агентами, місце пам’яті, інструментів і середовища, умітимете формулювати завдання та межі відповідальності агента, а також проєктувати сценарії контролю якості, безпеки й етики в автоматизованих пайплайнах.

Після заняття ви зможете:

  • пояснити складові AI-агента (LLM-мозок, інструменти, пам’ять, середовище) і цикл «сприйняття → план → дія → перевірка → навчання»;
  • вибрати доречний тип агента під задачу (помічник, маршрутизатор, екстрактор даних, інтегратор, класифікатор, генератор контенту);
  • спроєктувати теоретичну схему агента для аналітики (джерела, інструменти, політики та пороги рішень);
  • окреслити ризики (галюцинації, витік даних, етичні обмеження) і запобіжники (людина-в-циклі, валідація, логування та версіонування).

3 🧭 План заняття

  • Блок 1. AI-агенти: що це таке; спеціалізовані GPTs (ігрова демонстрація); створення агентів у Python (концепції, без коду).
  • Блок 2. LangChain: ключові ідеї; практичний кейс — агент для підготовки відповіді на запит «Прошу підписати мені індивідуальний план».
  • Блок 3. No-code сервіси: OpenAI Agent Builder, Make, n8n; кейс — «Казочки на ніч» без програмування.
  • Додатково: Batch API для пакетної обробки та приклади класифікації відгуків.
  • Підсумки та Q&A.

4 🧠 Блок 1. AI-агенти: визначення, архітектура та робочий цикл

Що таке AI-агент. Агент — це система на основі ШІ, здатна самостійно сприймати інформацію, приймати рішення та виконувати дії для досягнення мети. Концептуально він поєднує LLM як «мозок» (розуміє запит, планує кроки), інструменти (виконують конкретні дії: пошук, робота з файлами, виклики API, бази даних), пам’ять (зберігає контекст діалогу, попередній досвід, проміжні артефакти) і середовище (джерела та системи, у яких агент діє). Така композиція створює «розумний оркестратор», що уміє ставити підзадачі, викликати потрібні засоби та перевіряти результати.

Робочий цикл агента. Типова петля — Observe → Think/Plan → Act → Check → Learn. Спершу агент сприймає запит і контекст, далі планує послідовність кроків (які інструменти, у якій черзі), виконує дії (інколи з проміжними підзапитами), перевіряє проміжні результати (еталонами, правилами, підрахунками) та оновлює пам’ять/стратегію. У складніших сценаріях додають розділення на «планувальника» і «виконавця», або використовують багатоагентні схеми (модератор, дослідник, виконавець).

Спеціалізовані GPTs: ігрова демонстрація. Прості ігри («Слова зі слів») демонструють здатність агента: чітко визначати правила, контролювати хід, повертати валідацію результату та адаптувати інструкції під користувача. Навчальна цінність — у вмінні формулювати інструкції й перевірки: агент не лише генерує варіанти, а й підтверджує коректність (існування слів, дотримання обмежень), позначає помилки й пропонує ескалацію складності. Це переноситься на «серйозні» задачі — екстракція даних, аудит звітів, контроль бізнес-правил.

Створення AI-агентів у Python. Етапи: визначити роль і межі відповідальності; описати інструментарій (які джерела/АПІ дозволені, яким чином перевіряємо відповіді); спроєктувати пам’ять (короткочасну для розмови, довготривалу для фактів/налаштувань, епізодичну для «історії кейсу»); визначити політики безпеки (чутливі дані, приватність, логування). Проєктний результат — паспорт агента: місія, вхід/вихід, індикатори якості (SLA/SLI), обмеження вартості/затримки, сценарії відмови та ескалації на людину.

4.1 Напиши хоку про AI

У цьому прикладі показано, як за допомогою API OpenAI створити простий генератор хоку українською мовою. Код послідовно ініціалізує клієнт, надсилає запит до моделі й виводить результат.

Tip

Попередньо я створив файл config.py та зберіг там змінні для використання у наступних завданнях:

Code
OPENAI_KEY = "<OPENAI_API_KY>"
SHEET_URL = "https://docs.google.com/spreadsheets/d/000000000000"
Code
from openai import OpenAI
import config as C

Цей блок імпортує необхідні бібліотеки. Модуль openai надає інтерфейс для звернення до API, а config зберігає ключ доступу до сервісу (у змінній OPENAI_KEY).

Code
client = OpenAI(
  api_key=C.OPENAI_KEY
)

Створюємо екземпляр клієнта OpenAI, передаючи йому ключ API.
Це дозволяє виконувати запити до моделей OpenAI із заданими параметрами.


Code
completion = client.chat.completions.create(
  model="gpt-4o-mini",
  store=True,
  messages=[
    {"role": "user", "content": "напиши хоку про AI українською мовою"}
  ]
)

print(completion.choices[0].message)

У цьому фрагменті формується запит до моделі GPT-4o-mini.
Ми передаємо повідомлення з інструкцією: “напиши хоку про AI українською мовою”.
Отримана відповідь зберігається у змінній completion і одразу виводиться на екран.
Аргумент store=True дозволяє зберегти історію виклику в системі OpenAI для подальшого аналізу.


Code
for line in completion.choices[0].message.content.splitlines():
    print(f"{line}")

Цикл проходить рядки тексту відповіді моделі та виводить кожен окремо. > Це забезпечує охайний формат виведення, де кожен рядок хоку з’являється на новому рядку.


TipПідсумок

Цей приклад демонструє базовий сценарій взаємодії з API OpenAI для генерації поетичних текстів.
Його можна розширити для створення інтерфейсу або інтеграції в навчальний проєкт.

4.2 Чат-бот із використанням OpenAI API

Code
import openai
import config as C

У цьому фрагменті імпортуються необхідні бібліотеки для роботи з API OpenAI. Модуль openai використовується для створення запитів до моделей, а інші бібліотеки можуть забезпечувати конфігурацію або обробку даних.

Code
openai.api_key = C.OPENAI_KEY

Цей код є допоміжним або демонстраційним фрагментом, який підтримує основну логіку чат-бота.

Code
class SimpleChatAgent:
    def __init__(self):
        # Store conversation history
        self.conversation_history = [
            {"role": "system", "content": "Ти консультант, що відповідає на запитання про AI українською мовою та дуже піклуєшся про етичне використання AI у освіті та науці. Нагадуй про чесність у користуванні AI постійно."}
        ]

    def get_response(self, user_input):
        # Add user's message to conversation history
        self.conversation_history.append({"role": "user", "content": user_input})

        try:
            # Make API call to OpenAI
            response = openai.chat.completions.create(
                model="gpt-4o-mini",
                messages=self.conversation_history,
                max_tokens=250,        # Limit response length
                temperature=0.7        # Controls creativity (0-1)
            )

            # Get AI's response
            ai_response = response.choices[0].message.content

            # Add AI's response to conversation history
            self.conversation_history.append({"role": "assistant", "content": ai_response})

            return ai_response

        except Exception as e:
            return f"Sorry, I encountered an error: {str(e)}"

Цей код створює запит до моделі GPT, що імітує поведінку чат-бота. У параметрі messages визначається роль користувача та його запит. Результат зберігається у змінній completion, з якої потім можна отримати текст відповіді.

Code
# Create instance of our AI agent
agent = SimpleChatAgent()

print("Вітаю у чаті!")
print("Введи 'quit', якщо бажаєш вийти")

while True:
    # Get user input
    user_input = input("\nYou: ")

    # Check if user wants to quit
    if user_input.lower() == 'quit':
        print("Бувай!")
        break

    # Get and display AI response
    response = agent.get_response(user_input)
    print(f"AI: {response}")

Цей блок забезпечує інтерактивність: користувач може вводити текстові запити до чат-бота. Код приймає введений текст і надсилає його моделі для отримання відповіді.

4.3 Конвертація одиниць вимірювання за допомогою OpenAI API

Code
import openai
import config as C

Імпорт необхідних бібліотек. Модуль openai використовується для доступу до моделей, а інші — для налаштування або зчитування конфігурації.

Code
class FoodMeasureAgent:
    def __init__(self):

        # Initialize OpenAI client with API key
        self.client = openai.OpenAI(api_key=C.OPENAI_KEY)
        self.conversation_history = [
            {"role": "system",
             "content": "Конвертуй кожну одиницю харчових мір, яку надає користувач, у грами. Виводь лише значення у грамах. Наприклад, якщо запитують про склянку рису, відповідь має бути лише: '195 грамів'"
             }
        ]

    def get_response(self, user_input):
        # Add user message to conversation history
        self.conversation_history.append({"role": "user", "content": user_input})

        # Get response from OpenAI
        response = self.client.chat.completions.create(
            model="gpt-4o-mini",  # You can change to "gpt-4" if you have access
            messages=self.conversation_history,
            max_tokens=1000,
            temperature=0.7
        )

        # Extract the assistant's response
        assistant_response = response.choices[0].message.content.strip()

        # Add assistant's response to conversation history
        self.conversation_history.append({"role": "assistant", "content": assistant_response})

        return assistant_response

Створюється екземпляр клієнта OpenAI, використовуючи API-ключ із конфігураційного файлу. Цей клієнт дозволяє відправляти запити до моделей OpenAI.

Code
agent = FoodMeasureAgent()
print("Ласкаво просимо! Введіть одиницю виміру продукту — і я конвертую її в грами. Щоб вийти, наберіть 'quit'.")
while True:
    user_input = input("\nYou: ")
    print(f"\nYou: {user_input}")
    if user_input.lower() == 'quit':
        print("Бувай!")
        break
    response = agent.get_response(user_input)
    print(f"AI: {response}")

Забезпечується інтерактивне введення користувача. Користувач вводить запит (наприклад, конвертацію), який надсилається моделі.

4.4 Антискам: перевірка повідомлень на шахрайство та підготовка листа-відповіді

Code
from openai import OpenAI
import config as C
# Імпорт необхідних бібліотек для роботи з API, обробки тексту та даних

Цей код реалізує допоміжну логіку антискам-системи — обробку даних, підготовку запиту або форматування відповіді.

Code
client = OpenAI(
# Ініціалізація клієнта OpenAI з ключем API для взаємодії з моделлю
  api_key=C.OPENAI_KEY
)

Ініціалізація клієнта OpenAI — ключовий крок для взаємодії з моделлю GPT. Ключ API має бути захищений і не зберігатися у відкритому коді.

Code
# --- Agent 1: Scam Detector ---
class ScamDetector:
    def __init__(self, client):
        self.client = client

    def analyze_email(self, email_text):
        response = self.client.chat.completions.create(
# Формування запиту до моделі для аналізу повідомлення на шахрайство
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": 'Твоє завдання — класифікувати електронні листи як шахрайські або ні, на основі наданого тексту. Відповідай лише "YES" (якщо це шахрайство) або "NO" (якщо це не шахрайство).'},
                {"role": "user", "content": email_text}
            ],
            max_tokens=10,
            temperature=0.1
        )
        return response.choices[0].message.content.strip()

Цей блок коду формує запит до моделі OpenAI. У prompt описано задачу — розпізнати ознаки шахрайства та підготувати відповідь-шаблон, яка змусить шахрая витратити час. Модель аналізує контент, шукає ключові ризикові патерни (прохання про гроші, посилання, емоційний тиск). Рекомендація: зробити prompt структурованим — попросити модель повертати JSON з полями: label, confidence, reason, reply_template. Це спростить обробку результатів.

Code
# --- Agent 2: Time Waster ---
class TimeWaster():
    def __init__(self, client, signature_name="Юрій"):
        self.client = client
        self.signature_name = signature_name

    def craft_response(self, email_text):
        response = self.client.chat.completions.create(
# Формування запиту до моделі для аналізу повідомлення на шахрайство
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": f"Напиши довгу відповідь на цей шахрайський електронний лист (на підставі тексту, який надасть користувач), щоб затримати та марнувати час шахрая. Не повідомляй жодної особистої інформації або реальних контактних даних. Мета — утримати шахрая в листуванні якомога довше: став заплутані уточнювальні питання, вимагай зайвих деталей/документів, проси пояснити суперечності, показуй надмірну зацікавленість, погоджуйся на дивні процедури тощо — але ніякої реальної інформації про себе. Підписуйся як {self.signature_name} (замінити на потрібне ім’я при використанні). Формат відповіді: лише текст листа (без пояснень або інструкцій для користувача)."},
                {"role": "user", "content": email_text}
            ],
            max_tokens=500,
            temperature=0.1 # increase temperature to make response more crazy
        )
        return response.choices[0].message.content.strip()

Цей блок коду формує запит до моделі OpenAI. У prompt описано задачу — розпізнати ознаки шахрайства та підготувати відповідь-шаблон, яка змусить шахрая витратити час. Модель аналізує контент, шукає ключові ризикові патерни (прохання про гроші, посилання, емоційний тиск). Рекомендація: зробити prompt структурованим — попросити модель повертати JSON з полями: label, confidence, reason, reply_template. Це спростить обробку результатів.

Code
# --- Multi-Agent System ---
class EmailHandler:
    def __init__(self, client):
        self.scam_detector = ScamDetector(client)
        self.time_waster = TimeWaster(client)

    def process_email(self, email_text):
        # Step 1: Detect scam
        status = self.scam_detector.analyze_email(email_text)
        print(f"Email: {email_text}")
        print(f"Scam Detector: {status}")

        # Step 2: If scam, waste time
        if status == "YES":
            reply = self.time_waster.craft_response(email_text)
            print(f"Відповідь шахраям: {reply}")
        else:
            print("Відповідь не створено.")
        print("---")

Цей код реалізує допоміжну логіку антискам-системи — обробку даних, підготовку запиту або форматування відповіді.

Code
# Run the system
handler = EmailHandler(client)

# Simulated email examples
emails = [
    "Шановний користувачу, ви були обрані для отримання спеціального грошового гранту у розмірі 250 000 грн. Для переказу коштів нам потрібен номер вашої картки та ПІН-код.",
    "Привіт! Не забудь, сьогодні в нас зустріч з новими клієнтами о 10:30. Зустрічаємось в переговорній №2.",
    "Вітаю! Я юрист покійного мільярдера, і ви зазначені в його заповіті як спадкоємець. Терміново зв’яжіться зі мною для оформлення спадщини.",
    "Аллллоо! Ти скинеш фінальну версію презентації до 18:00? Ми плануємо все зібрати до завтра.",
    "Ваш акаунт у ПриватБанку заблоковано. Щоб відновити доступ, натисніть на посилання нижче та введіть усі особисті дані для підтвердження."
]

Цей код реалізує допоміжну логіку антискам-системи — обробку даних, підготовку запиту або форматування відповіді.

Code
for email in emails:
    handler.process_email(email)
    input("Press enter to continue next email.")
# Отримання повідомлення від користувача для перевірки

Цей код реалізує допоміжну логіку антискам-системи — обробку даних, підготовку запиту або форматування відповіді.


5 🔗 Блок 2. LangChain: фреймворк для ланцюгів дій і агентів

Що таке LangChain. Це фреймворк (Python/JavaScript) для побудови застосунків і агентів на базі LLM: він поєднує ланцюги мислення/дій (chains), зовнішні інструменти, пам’ять, ретрієвал (доступ до файлів/БД/пошуку) та агентні петлі (вибір наступної дії залежно від проміжного результату). Перевага — «конструктора» з готових блоків: промпт-шаблони, parsers, memory stores, tool adapters, retrievers; можливість додавати власні інструменти й правила валідації, будувати керовані сценарії із прозорим логуванням.

Практичний кейс: агент для запиту «Прошу підписати мені індивідуальний план». Задача — автоматизувати обробку вхідних листів і підготовку відповідей.

  • Вхід: поштові повідомлення; ознаки: відправник, тема, тіло, вкладення.

  • Ціль: знайти запити на підписання ІП, зібрати список студентів, підготувати шаблон-відповідь (інструкції, терміни, перелік кроків).

  • Інструменти: доступ до пошти (читання), шаблонізатор відповідей, таблиця/CRM для реєстру, календар/таск-менеджер для нагадувань.

  • Пам’ять: збереження вже оброблених листів, статусів (очікує документів/підписано/відхилено), історії комунікації.

  • Перевірки: чи справді лист про ІП (класифікація), чи всі обов’язкові поля заповнено, чи немає дубля.

  • Вихід: чернетки відповідей (для людини), оновлений список студентів, журнал рішень.

Теоретичні нюанси. Визначаємо порогові правила для класифікатора теми; політики ескалації, якщо невпевненість висока; вікна пам’яті для контексту; часові обмеження; облік вартості викликів; вимоги до приватності. Агент не відправляє листи самостійно без «human-in-the-loop» — це контроль ризиків і відповідальності.

5.1 Автоматизований GPT-помічник для обробки листів студентів

Автоматизований GPT-помічник для обробки листів студентів щодо індивідуальних планів (Gmail → LangChain → Google Sheets)

Цей ноутбук: - читає листи з Gmail; - класифікує їх за допомогою GPT (LangChain 1.x); - витягує ПІБ та групу студента; - підбирає шаблон відповіді з аркуша “Шаблони” існуючого документа Google Sheets; - формує текст відповіді (GPT); - створює чернетку-відповідь у Gmail; - записує результат у аркуш “Запити” (існуючий документ Google Sheets).

⚙️ Пайплайн зібраний у єдиний Runnable Chain (LangChain 1.x).

5.1.1 Встановлення залежностей (одноразово)

За потреби розкоментуйте та виконайте:

# !pip install -U langchain langchain-openai langchain-core
# !pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
# !pip install gspread gspread-formatting pandas rapidfuzz python-dotenv

5.1.2 Імпорт бібліотек

Code
import os, re, json, datetime, base64, string
# Імпорт необхідних бібліотек (робота з API, обробка пошти, AI-моделі)
import pandas as pd
# Імпорт необхідних бібліотек (робота з API, обробка пошти, AI-моделі)
from email.mime.text import MIMEText
from rapidfuzz import process
from dotenv import load_dotenv

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google.oauth2 import service_account  # not used, but handy for alt flows
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# Підключення до Gmail API для читання та надсилання листів
import gspread
# Імпорт необхідних бібліотек (робота з API, обробка пошти, AI-моделі)
from gspread_formatting import *

# LangChain (modern, 1.x style)
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda, RunnableSequence

Цей блок реалізує допоміжну функціональність — наприклад, обробку тексту, логування або контроль потоку.

5.1.3 Конфігурація: OpenAI ключ, модель; модуль config

Ми використовуємо ваш локальний модуль config.py з полями: - OPENAI_KEY — OpenAI API ключ - SHEET_URL — URL на вже існуючий документ Google Sheets (“Індивідуальні плани”)

Code
import config as C
# Імпорт необхідних бібліотек (робота з API, обробка пошти, AI-моделі)

OPENAI_API_KEY = C.OPENAI_KEY
MODEL_NAME = "gpt-4o-mini"   # можна змінити на "gpt-4o", якщо доступно
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY

print("🔑 OpenAI API Key завантажено")
print("🧠 Модель:", MODEL_NAME)

Цей блок реалізує допоміжну функціональність — наприклад, обробку тексту, логування або контроль потоку.

5.1.4 Авторизація Google API (Gmail + Sheets)

Використовується існуючий токен з secret/token.json та OAuth-клієнт secret/client_secret.json.
Якщо token.json відсутній або прострочений — відбудеться інтерактивний OAuth-потік у браузері.

Code
SCOPES = [
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/gmail.compose",
    "https://www.googleapis.com/auth/spreadsheets"
]

creds = None
if os.path.exists("secret/token.json"):
    creds = Credentials.from_authorized_user_file("secret/token.json", SCOPES)
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file("secret/client_secret.json", SCOPES)
        creds = flow.run_local_server(port=0)
    with open("secret/token.json", "w") as token:
        token.write(creds.to_json())

gmail_service = build("gmail", "v1", credentials=creds)
gc = gspread.authorize(creds)

print("✅ Авторизація Gmail і Sheets виконана")
# Підключення до Gmail API для читання та надсилання листів

Цей блок реалізує взаємодію з Gmail API. Він може отримувати вхідні листи, читати теми та тіла повідомлень, а також автоматично надсилати сформовані відповіді. Варто забезпечити фільтрацію (наприклад, лише листи від студентів або з темою ‘індивідуальний план’).

5.1.5 Ініціалізація LLM та базових компонентів LangChain

Code
llm = ChatOpenAI(model=MODEL_NAME, temperature=0)
# Ініціалізація клієнта OpenAI для створення AI-відповідей на листи
parser = StrOutputParser()

Ініціалізація клієнта OpenAI забезпечує інтеграцію з моделлю для автоматичного створення відповідей. Ключ API має бути збережений у середовищі безпеки, не в коді.

####Підключення до вже існуючого Google Sheets і читання шаблонів

Ми не створюємо нових документів. Використовується URL з config.SHEET_URL. Обов’язкові аркуші: “Шаблони” (курс, група, шаблон) і “Запити”.

Code
spreadsheet = gc.open_by_url(C.SHEET_URL)
print(f"Зчитано документ: {spreadsheet.title}")
print("Аркуші:")
for ws in spreadsheet.worksheets():
    print("  -", ws.title)

Цей блок реалізує допоміжну функціональність — наприклад, обробку тексту, логування або контроль потоку.

Code
# Основні аркуші
sheet_requests = spreadsheet.worksheet("Запити")
sheet_templates = spreadsheet.worksheet("Шаблони")
Code
# Шаблони у форматі {(Курс, Група): "Шаблон"}
tpl_data = sheet_templates.get_all_records()
templates = {(t["Курс"], t["Група"]): t["Шаблон"] for t in tpl_data}

for k, v in templates.items():
    print(f"{k[0]} / {k[1]}")
print(f"📚 Завантажено шаблонів: {len(templates)}")

5.1.6 Службові функції: читання листів і створення чернеток

Code
def get_latest_emails(query="subject:план OR індивідуальний план", max_results=30):
    """Читає останні листи за пошуковим запитом.
    Повертає список словників з ключами: id, threadId, message_id, from, subject, body.
    """
    res = gmail_service.users().messages().list(userId="me", q=query, maxResults=max_results).execute()
    messages = res.get("messages", [])
    emails = []
    for msg in messages:
        data = gmail_service.users().messages().get(userId="me", id=msg["id"], format="full").execute()
        snippet = data.get("snippet", "")
        headers = data["payload"].get("headers", [])
        sender = next((h["value"] for h in headers if h["name"].lower()=="from"), "Unknown")
        subject = next((h["value"] for h in headers if h["name"].lower()=="subject"), "No subject")
        message_id = next((h["value"] for h in headers if h["name"].lower()=="message-id"), None)
        emails.append({
            "id": msg["id"],
            "threadId": data.get("threadId"),
            "message_id": message_id,
            "from": sender,
            "subject": subject,
            "body": snippet
        })
    return emails
Code
def create_reply_draft(original_email: dict, to_email: str, reply_body: str):
    """Створює чернетку-відповідь у існуючому треді (якщо можливо)."""
    # Формуємо MIME-повідомлення
    message = MIMEText(reply_body, 'plain', 'utf-8')
    message['To'] = to_email
    message['Subject'] = f"Re: {original_email.get('subject','')}"
    if original_email.get("message_id"):
        message['In-Reply-To'] = original_email["message_id"]
        message['References'] = original_email["message_id"]

    raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')

    body = {'message': {'raw': raw_message}}
    # Прив'язуємо до треду, якщо він відомий
    if original_email.get("threadId"):
        body['message']['threadId'] = original_email["threadId"]

    gmail_service.users().drafts().create(userId='me', body=body).execute()

5.1.7 Вибір шаблону відповіді (пошук за групою і / або предметом)

Використовується підхід з твого ноутбука: спершу шукаємо за групою (низький поріг, як у тебе), потім — за назвою курсу/предмету.

Code
def find_best_template_by_group(group: str):
    if not group:
        return None
    candidates = [f"{c[1]}" for c in templates.keys()]
    query = f"{group}"
    best, score, _ = process.extractOne(query, candidates) if candidates else (None, 0, None)
    if best and score >= 65:
        for k, v in templates.items():
            if f"{k[1]}" == best:
                return v
    return None


def find_best_template_by_subject(sbj: str):
    if not sbj:
        return None
    candidates = [f"{c[0]}" for c in templates.keys()]
    query = f"{sbj}"
    best, score, _ = process.extractOne(query, candidates) if candidates else (None, 0, None)
    if best and score >= 65:
        for k, v in templates.items():
            if f"{k[0]}" == best:
                return v
    return None

5.1.8 Промпти (аналіз листа → генерація відповіді)

Code
prompt_analyze = ChatPromptTemplate.from_template("""
Ти — асистент викладача університету.
Завдання:
1) Визнач, чи цей емейл є проханням студента/студентки про підписання індивідуального навчального плану.
2) Якщо так — знайди у тексті:
   - ПІБ студента/ки (дозволені формати "Прізвище Ім'я По батькові" або "Прізвище По батькові")
   - групу (напр. КН-31, ІТ-22 тощо). Якщо не вдалося ідентифікувати групу - поверни пустий рядок, не намагайся вгадати. Серед доступних груп: {groups}.
   - назву курсу / предмета (якщо є), назва курсу повинна бути схожою на {courses} (обери найближчу, якщо співпадіння 0.6 і більше) або не обирай зовсім
   - e-mail відправника (якщо є в тексті, інакше використай поле From)
3) Якщо НЕ є таким проханням — поверни літерал `None` (без JSON).

Вхідні дані:
Від: {sender}
Тема: {subject}
Текст: {body}

Відповідь у JSON БЕЗ markdown:
{{
  "student_name": "...",
  "group": "...",
  "subject": "...",
  "sender_email": "..."
}}
АБО `None`.
""")

prompt_reply = ChatPromptTemplate.from_template("""
Сформулюй коротку професійну відповідь студенту/студентці, який/яка просить підписати індивідуальний план.
Використай шаблон (адаптуй під конкретні дані):
{template_text}

Повне ім'я студента/студентки: {name}
Група: {group}
Предмет/курс: {subject_name}

❗ Якщо в шаблоні є плейсхолдер `#FIRST_NAME`, заміни його на ім'я у кличному відмінку (якщо можливо).
""")

5.1.9 Побудова саб-ланцюгів LangChain

  • analyze_chain = prompt_analyze | llm | parser
  • reply_chain = prompt_reply | llm | parser
Code
analyze_chain = prompt_analyze | llm | parser
reply_chain = prompt_reply | llm | parser

5.1.10 Єдиний end-to-end Runnable Chain

Архітектура:

email(dict) 
→ analyze (GPT) 
→ parse/update state 
→ find template (group→subject) 
→ reply (GPT) 
→ write to Sheets 
→ create Gmail draft
Code
# Налаштування: ваша адреса, щоб не обробляти власні листи
MY_EMAIL = "yuriy.kleban@oa.edu.ua"  # ← за потреби змініть

def _init_state(email: dict):
    """Ініціалізація стану для ланцюга."""
    return {"email": email, "skip": False, "status": None}

def _analyze_step(state: dict):
    """Крок аналізу: GPT класифікація + вилучення полів або skip."""
    email = state["email"]
    sender = email.get("from", "")
    subject = email.get("subject", "")
    body = email.get("body", "")

    # Не обробляємо власні листи
    if MY_EMAIL.lower() in sender.lower():
        state.update({"skip": True, "status": "own_email"})
        return state

    result = analyze_chain.invoke({"sender": sender, "subject": subject, "body": body, "courses": ", ".join(f"{k[0]}" for k in templates.keys()), "groups": ", ".join(f"{k[1]}" for k in templates.keys())}).strip()

    if result == "None":
        state.update({"skip": True, "status": "not_request"})
        return state

    # Очікуємо JSON
    try:
# Обробка винятків — запобігає зупинці при помилках API або мережі
        data = json.loads(result)
    except Exception:
# Обробка винятків — запобігає зупинці при помилках API або мережі
        state.update({"skip": True, "status": "bad_json"})
        return state

    # Уніфікація регістру/формату
    name = data.get("student_name")
    group = data.get("group")
    subj  = data.get("subject")
    sender_email = data.get("sender_email") or sender

    # Легка нормалізація
    name = string.capwords(name) if isinstance(name, str) else None
    group = group.upper() if isinstance(group, str) else None

    state.update({
        "student_name": name,
        "group": group,
        "subject_name": subj,
        "sender_email": sender_email,
        "status": "analyzed"
    })
    return state

def _choose_template_step(state: dict):
    """Крок вибору шаблону: спершу група, потім предмет."""
    if state.get("skip"):
        return state

    tpl = None
    if state.get("group"):
        tpl = find_best_template_by_group(state["group"])

    if not tpl and state.get("subject_name"):
        tpl = find_best_template_by_subject(state["subject_name"])

    if tpl:
        state.update({"template_text": tpl, "status": "template_found"})
    else:
        state.update({"template_text": None, "status": "template_not_found"})
    return state

def _generate_reply_step(state: dict):
    """Крок генерації відповіді (GPT), якщо є шаблон."""
    if state.get("skip"):
        return state

    if state.get("status") != "template_found":
        state.update({"draft_text": None})
        return state

    draft_text = reply_chain.invoke({
        "template_text": state["template_text"],
        "name": state.get("student_name",""),
        "group": state.get("group",""),
        "subject_name": state.get("subject_name","")
    })
    state.update({"draft_text": draft_text, "status": "draft_ready"})
    return state

def _write_outputs_step(state: dict):
    """Запис у Sheets та створення Gmail чернетки (якщо є draft)."""
# Підключення до Gmail API для читання та надсилання листів
    email = state["email"]

    if state.get("skip"):
        # нічого не пишемо
        return state

    # Статус за замовчуванням
    status_to_write = "Не потребує відповіді"
    if state.get("status") == "template_not_found":
        status_to_write = "Шаблон не знайдено"
    elif state.get("status") == "draft_ready":
        status_to_write = "Чернетку створено"
        # Створюємо чернетку
        try:
# Обробка винятків — запобігає зупинці при помилках API або мережі
            create_reply_draft(email, state.get("sender_email",""), state.get("draft_text",""))
        except Exception as e:
# Обробка винятків — запобігає зупинці при помилках API або мережі
            status_to_write = f"Draft error: {e}"

    # Запис у аркуш "Запити"
    try:
# Обробка винятків — запобігає зупинці при помилках API або мережі
        sheet_requests.append_row([
            state.get("student_name",""),
            state.get("group",""),
            state.get("subject_name",""),
            state.get("sender_email",""),
            datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
            status_to_write
        ])
    except Exception as e:
# Обробка винятків — запобігає зупинці при помилках API або мережі
        # Якщо помилка — до стану
        state["status"] = f"Sheets error: {e}"
# Підключення до Google Sheets для збереження результатів (журнал відповідей або статусів)

    return state

Цей блок реалізує взаємодію з Gmail API. Він може отримувати вхідні листи, читати теми та тіла повідомлень, а також автоматично надсилати сформовані відповіді. Варто забезпечити фільтрацію (наприклад, лише листи від студентів або з темою ‘індивідуальний план’).

Code
# Єдиний Runnable Chain
full_chain = RunnableSequence(
    RunnableLambda(_init_state),
    RunnableLambda(_analyze_step),
    RunnableLambda(_choose_template_step),
    RunnableLambda(_generate_reply_step),
    RunnableLambda(_write_outputs_step),
)

5.1.11 Запуск: читання листів з Gmail і обробка через єдиний chain

Налаштуйте MY_EMAIL вище (щоб пропускати власні листи).

Code
emails = get_latest_emails(max_results=10)
print(f"📨 Отримано листів: {len(emails)}")

processed = 0
created = 0
skipped = 0
no_template = 0

for em in emails:
    #print(em['from'])
    state = full_chain.invoke(em)
    processed += 1

    st = state.get("status","")
    if st in ("own_email","not_request","bad_json"):
        skipped += 1
    elif st == "template_not_found":
        no_template += 1
    elif st in ("draft_ready",):
        created += 1

print("—"*40)
print("Звіт:")
print("  Оброблено:", processed)
print("  Пропущено (не запит / власний лист / bad json):", skipped)
print("  Без шаблону:", no_template)
print("  Чернеток створено:", created)
print("—"*40)
print("✅ Готово.")

5.1.12 Підсумок

  • Всі знайдені листи були проаналізовані GPT (LangChain 1.x).
  • Для релевантних листів згенеровані відповіді на основі шаблонів із аркуша “Шаблони”.
  • Результати записані у аркуш “Запити”.
  • У Gmail створені відповідні чернетки.

За потреби відкоригуйте: - MY_EMAIL (ваша адреса, щоб листи від себе пропускати); - пороги схожості в find_best_template_by_*; - get_latest_emails(query=...) — змініть пошуковий запит під ваші умови.


6 🧩 Блок 3. No-code/Low-code сервіси: OpenAI Agent Builder, Make, n8n

Навіщо «без коду». Частину процесів зручніше збирати як workflow: подія-тригер → фільтри → виклики інструментів → перевірки → повідомлення. Це пришвидшує прототипування, дозволяє бізнес-командам самостійно обслуговувати автоматизації та спиратися на корпоративні інтеграції. Проте архітектурні принципи лишаються: чітка роль агента, журнал рішень, політики приватності, ескалації на людину, контроль вартості/затримки.

Кейс: «Казочки на ніч». Ідея — за вхідними даними (тематика, вік дитини, довжина, стиль) агент генерує казку, надсилає її на email і публікує в Notion із підходящими емодзі.

  • Тригер: надходить лист або веб-форма.
  • Кроки: валідація параметрів → генерація чернетки → перевірка змісту (відсутність небажаних тем) → стилістична правка → створення нотатки в Notion → відправлення листа.
  • Політики: чіткі гайдлайни щодо контенту; вимоги до приватності (жодних персональних даних у промптах); логування результату; права на повторну публікацію.
  • Ризики: небажаний контент, помилкові адреси, збої інтеграцій; рішення — попередній перегляд людиною, ретраї, алерти.

Порівняння підходів. No-code хороші для швидкого MVP і стабільних сценаріїв; код (або LangChain) потрібен, коли складна логіка планування, нестандартні інструменти або тонкі вимоги до якості/трасування.


7 💸 Batch API: пакетна обробка як спосіб зменшити вартість

Ідея. Batch API — режим асинхронної пакетної обробки великої кількості запитів. Замість тисяч окремих викликів передаємо один файл (наприклад, JSONL) і отримуємо результати після завершення. Переваги: менша вартість на обсяг, стабільніший тротлінг і зручність для завдань на кшталт масової класифікації відгуків, розмітки документів, витягу сутностей. Обмеження: довший latency «від заявки до результату», вимоги до ідемпотентності та ретраїв, необхідність чіткої схеми валідної/невалідної відповіді. Для бізнес-пайплайнів важливо мати моніторинг прогресу, «мертві черги» та автоматичні сповіщення.

Приклад: класифікація відгуків (тональність і тема: доставка/підтримка/якість/пакування/інше). Теоретична постановка: готуємо інструкцію агента, обмежуємо формат відповіді (поле name, topic, sentiment), визначаємо політику для неоднозначних випадків («невпевнено» → на ручну валідацію), проєктуємо метрики якості (збіг анотацій, частка «невпевнено», продуктивність на 1000+ записів).


8 🧪 Демонстрації з презентації — теоретичні розбори

Демо 1–2. «Слова зі слів». Агент керує грою: задає правила, запускає таймер, перевіряє слова, підсвічує помилки, пропонує ускладнення (тематика). Освітній сенс — навчитися формулювати інструкції, перевірки та feedback-loop у легкому середовищі.

Демо 3–4. Творчість і консалтинг. «Хайку українською», «Нудний чат про AI з етичним нагадуванням»: показують рольову інструкцію й тон відповіді, необхідність стабільних політик (нагадування про чесність). Переноситься на корпоративні сценарії (відповіді саппорту з політиками).

Демо 5. Конвертер харчових мір. Агент-конвертер приймає запит у довільній формі, але повертає лише грамове значення — демонстрація суворого формату відповіді, що зручно для автоматизованих ланцюгів (жодного «води», тільки число й одиниці).

Демо 6. Антискам. Двоетапний сценарій: (1) класифікація листа на шахрайство/ні (тільки YES/NO), (2) генерація «довгої відповіді» для затягування часу шахрая без розголошення персональних даних — приклад етичних обмежень і чіткого формату виходу. У виробництві додаються чорні списки, ліміти частоти, правила ескалації.

Демо 7. Batch-класифікація відгуків. Форматований JSON-вихід (name, topic, sentiment), політика для «невпевнених» випадків, узгодженість з анотаціями людини — приклад, як агент інтегрується в аналітичний конвеєр.

Демо 8. LangChain-агент «Підпишіть інд. план». Теоретичний пайплайн: читання пошти → фільтрація листів про ІП → консолідація списку студентів → генерація чернеток відповідей за шаблонами → логування рішень → попередній перегляд людиною.

Демо 9. «Казкар для малюків». Інтеграція з поштою і Notion, політики контенту, публікація з емодзі. Важливо: модуль перевірки заборонених тем і ручне погодження перед розсилкою.


9 🛡️ Якість, безпека, етика й відповідальність

Якість та валідація. Для кожного агента визначаємо індикатори: частка коректних відповідей, дотримання формату, час відгуку, вартість на запит, частка ескалацій на людину. Використовуємо вибіркову перевірку «ground-truth», журналимо вхід/вихід і рішення (з урахуванням приватності), порівнюємо ітерації агента під різними налаштуваннями.

Безпека і приватність. Мінімізуємо дані у промптах (data minimization), деперсоналізуємо приклади, шифруємо журнали, обмежуємо інструменти агента (whitelist API). Для чутливих сценаріїв — незалежний аудит і відмова від повної автоматизації.

Етика й прозорість. Повідомляємо користувачам про участь ШІ, забезпечуємо можливість оскарження рішень, відстежуємо упередження (за групами, сегментами) і документуємо межі застосовності агентів.


10 ✅ Підсумки

  • AI-агенти = LLM + інструменти + пам’ять + середовище з чіткою роллю та межами.
  • LangChain допомагає будувати керовані ланцюги дій, no-code — швидко збирати робочі процеси.
  • Batch-режим знижує вартість для масових завдань; потрібні формати, валідація та моніторинг.
  • Людина-в-циклі — ключ до якості, безпеки та етики в автоматизації аналітики.

11 📚 Матеріали

  • Презентація «Автоматизація обробки даних з використанням AI-агентів» (структура блоків, демо-сценарії, сервіси та кейси).

Лого

Лого

Проєкт реалізується за підтримки Європейського Союзу в межах програми Дім Європи.