Головна / Статті

Від чатбота з одним вузлом до агента, підтримуваного MCP у LangGraph

Створіть додаток LangGraph шар за шаром: стани та редуктори, краї, цикли інструментів, потоки з контролем стану, три режими стрімінгу та інструменти, які надаються через MCP.

Видавець: REACTAPP.TOP18 хв читання4252 слів

Джерела: Building AI Applications with LangGraph: From a Basic Chatbot to Tools, Memory, Streaming and MCP

Більшість прототипів ШІ-моделей починаються з одного виклику: надсилається запит, друкується відповідь. Така структура руйнується як тільки додатку потрібен пошук в Інтернеті, рішення щодо наступного кроку, пам’ять про попередні дії чи відображення поточного прогресу в інтерфейсі. LangGraph вирішує цю проблему, перетворюючи логіку керування на чіткий граф, який описує спільний стан. У цьому огляді додаток розвивається від чат-бота з одним вузлом до агента ReAct, який використовує інструменти, має збережену пам’ять, підтримує потоковий формат даних та отримує інструменти від окремого сервера MCP, а також вказуються деталі, які часто стають перешкодою на цьому шляху.

Ментальна модель: стан проходить через вузли по ребрах

Звичайна інтеграція LLM працює за простою схемою від запиту до моделі та подальшої відповіді. LangGraph ж вимагає мислити циклічно: об’єкт state потрапляє до графа, nodes його читають та повертають зміни, edges визначають, який вузол буде оброблений далі, а остаточний оновлений стан виходить наприкінці. Отже, три основні складові є наступними:

  • State: дані, які передаються між кроками, наприклад історія повідомлень.
  • Nodes: звичайні функції, які виконують певну роботу та повертають часткове оновлення.
  • Edges: правила, які з’єднують вузли, включаючи початок та кінець виконання.

Найпростіший можливий граф має один вузол між точкою входу та виходу:

                ┌─────────────┐
                │    State    │
                └──────┬──────┘
                       │
                       ▼
                ┌─────────────┐
                │    Node     │
                │   chatbot   │
                └──────┬──────┘
                       │
                       ▼
                ┌─────────────┐
                │     END     │
                └─────────────┘

Розділення „які дані існують“ від „що виконується у якому порядку“ є основним принципом проектування. Ви можете змінити послідовність виконання, не торкаючись способу зберігання даних, а також перевірити їхній стан у будь-який момент, не займаючись зворотною інженерією ланцюга викликів функцій.

Чому граф замість ланцюга викликів

Мінімальний чат-бот — це пряма лінія від користувача до моделі та назад:

User
 ↓
LLM
 ↓
Response

Тепер додамо реальні вимоги: пошук у Інтернеті, кілька інструментів, логіку розгалуження, пам’ять, крок схвалення людиною, більше одного агента, потоковий вивід та різну поведінку залежно від результатів моделі. Кожна з цих вимог додає розгалуження чи цикл, а вкладені оператори if навколо викликів API швидко ускладнюють розуміння коду. Навіть найпоширеніший варіант — „викликати інструмент, якщо модель цього просить“ — вже містить цикл:

                         ┌─────────────┐
                         │     LLM     │
                         └──────┬──────┘
                                │
                         Need a tool?
                         /           \
                       Yes            No
                        ↓              ↓
                     Tool          Response
                        │
                        ↓
                       LLM

Як тільки логіка програми перетворюється на граф, гілки та цикли стають видимими об’єктами, які можна назвати, протестувати та намалювати, замість того щоб вони залишалися у формі контролю потоку, прихованого всередині допоміжних функцій.

Крок 1: чат-бот з одним вузлом за допомогою StateGraph

Перша версія є навмисно простою — вона складається з одного вузла, підключеного між початковим та кінцевим маркерами:

START
  ↓
Chatbot Node
  ↓
END

Визначення стану та його редюсера

Для чат-бота стан переважно потребує історії розмови. Першою спробою є використання TypedDict із списком messages:

from typing import TypedDict

class State(TypedDict):
    messages: list

Концептуально цей список накопичує по черзі повідомлення від людини та ШІ:

State
 └── messages
      ├── HumanMessage
      ├── AIMessage
      ├── HumanMessage
      └── AIMessage

Тут дуже важлива одна деталь. У LangGraph кожен ключ у стані може мати редуктор, який визначає, як значення, що повертається вузлом, об’єднується з існуючим значенням. Проста анотація list не має редуктора, тому вузол, який повертає {"messages": [reply]}, замінює всю список замість того, щоб додати до нього елемент. Для історії чату майже завжди потрібен редуктор add_messages, оголошений як Annotated[list, add_messages] (або успадкований від готового MessagesState). З його допомогою повернені повідомлення додаються, а повідомлення зі збігаючим ID оновлюються на місці. Наведений вище фрагмент підходить як уявний приклад, але у реальному коді слід використовувати анотовану форму.

Створення вузла чат-бота

Вузол — це просто функція, яка отримує поточний стан та повертає ключі, які вона хоче змінити. Вузол чат-бота передає історію розмови моделі та повертає відповідь:

def chatbot(state: State):
    return {
        "messages": [
            llm.invoke(state["messages"])
        ]
    }

Читайте його зсередини назовні. Функції передається весь стан:

state

Вона виділяє проведену досі розмову:

state["messages"]

Вона надсилає цю історію моделі:

llm.invoke(state["messages"])

І вона обгортає відповідь моделі у словник, ключами якого є поля, які вона оновлює. Зауважте, що вузол не змінює стан безпосередньо; він повертає часткове оновлення та дозволяє LangGraph його об’єднати:

{
    "messages": [
        llm.invoke(state["messages"])
    ]
}

Як схема потоку даних, вузол розташований між двома версіями одного й того ж стану:

              State
                │
                │ messages
                ▼
          ┌─────────────┐
          │  Chatbot    │
          │    Node     │
          └──────┬──────┘
                 │
                 │ LLM Response
                 ▼
               State

Реєстрація вузла

Функція стає частиною графу після того, як її реєструють у конструкторі під певною назвою:

graph_builder.add_node(
    "llmchatbot",
    chatbot
)

Перший аргумент — це ідентифікатор вузла, на який будуть посилатися краї та журнали:

"llmchatbot"

Другий аргумент — це функція, яка виконується щоразу, коли виконання доходить до цього вузла:

chatbot

Крок 2: підключення країв та компіляція

Вузол сам по собі ніколи не виконується; краї повідомляють LangGraph, як до нього дістатися та куди йти далі. Для цього графу потрібні два краї. Перший з’єднує спеціальний маркер START з чат-ботом:

graph_builder.add_edge(
    START,
    "llmchatbot"
)

У вигляді діаграми виконання потрапляє до графу та йде прямо до вузла:

START
  ↓
llmchatbot

Другий край передає контроль від чат-бота до END, що завершує виконання:

graph_builder.add_edge(
    "llmchatbot",
    END
)

Це відповідає:

llmchatbot
  ↓
 END

Разом вони утворюють повний, хоча й мінімальний, граф:

       START
         │
         ▼
 ┌────────────────┐
 │   llmchatbot   │
 │      Node      │
 └────────┬───────┘
          │
          ▼
         END

Компіляція та виклик

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

graph = graph_builder.compile()

Його запускають, передавши початковий стан. Приймаються звичайні словники ролей/контенту, які потім перетворюються на об’єкти повідомлень LangChain:

result = graph.invoke({
    "messages": [
        {
            "role": "user",
            "content": "What is LangGraph?"
        }
    ]
})

Під капотом вхідний стан потрапляє у стан START, вузол чат-бота викликає модель, відповідь об’єднується зі станом, і запуск завершується:

Input State
    ↓
START
    ↓
llmchatbot
    ↓
LLM
    ↓
Updated State
    ↓
END

Значення, яке повертається функцією invoke, — це кінцевий стан, тому відповідь моделі є останнім елементом у result["messages"].

Крок 3: створення графа

Компільований граф може самостійно відображатися. У Jupyter Notebook допоміжні функції IPython відображають PNG-зображення, створене на основі опису графа у форматі Mermaid:

from IPython.display import Image, display

display(
    Image(
        graph.get_graph().draw_mermaid_png()
    )
)

Для цього графа зображення є коротким:

   ┌───────┐
   │ START │
   └───┬───┘
       │
       ▼
┌──────────────┐
│  llmchatbot  │
└──────┬───────┘
       │
       ▼
   ┌───────┐
   │  END  │
   └───────┘

З трьома поліцями значення не є очевидним, але воно швидко зростає. Як тільки у вас з’являються умовні краї, вузли інструментів та цикли, відтворений діаграма — це найшвидший спосіб переконатися, що граф, який ви створили, — це саме той граф, який ви хотіли створити. Зверніть увагу, що draw_mermaid_png() за замовчуванням відтворює зображення через зовнішній сервіс Mermaid, тому йому потрібен доступ до мережі; якщо такого доступу немає, draw_mermaid() повертає текст Mermaid, який можна відтворити в іншому місці.

Крок 4: надання моделі інструменту пошуку

Чат-бот може відповідати лише на основі того, що модель навчилася під час тренування. Питання про останні досягнення в галузі агентних ШІ потребують свіжої інформації, тому наступний крок полягає у додаванні інструменту веб-пошуку. У прикладі використовується Tavily Search, а запланований процес виглядає так:

User
 ↓
LLM
 ↓
Search required?
 ↓
Tavily Search
 ↓
Search Results
 ↓
LLM
 ↓
Final Answer

Підключення інструментів — це не те саме, що їх виконання

Перша половина завдання — це підключення інструментів: вказування моделі, які інструменти існують та які аргументи вони приймають.

llm_with_tools = llm.bind_tools(
    [tavily_search]
)

Після підключення модель може відповісти структурованим запитом до інструменту („викликати tavily_search з цим запитом“) замість простого тексту. Вона сама не може виконати цей запит. Щось у вашому додатку має помітити цей запит, запустити інструмент, додати результат у вигляді ToolMessage та надіслати все назад до моделі. Надання моделі доступу до інструменту не робить її агентом; це робить навколишній потік виконання. Саме такий потік забезпечується графом.

Крок 5: перетворення на цикл ReAct

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

START
  ↓
LLM
  ↓
END

перетворюється на граф із точкою прийняття рішення та шляхом, який веде назад до моделі:

                 START
                   │
                   ▼
                 Agent
                   │
                   ▼
              ┌─────────┐
              │   LLM   │
              └────┬────┘
                   │
             Need a tool?
              /         \
            Yes          No
             │            │
             ▼            ▼
          Tool          END
             │
             ▼
           Agent
             │
             ▼
            LLM

У коді LangGraph це рішення є умовною кромкою. Заздалегідь створений ToolNode виконує те, що міститься у останньому повідомленні від інструменту, а tools_condition спрямовує потік до цього вузла, якщо існують виклики інструментів, або до END, якщо їх немає. Кромка від вузла інструменту назад до моделі закриває цикл. Заздалегідь створений допоміжний функціонал create_react_agent формує таку саму структуру за вас; перевірте актуальну документацію LangGraph для отримання точного місця розташування, оскільки допоміжні функції агентів з часом переміщувалися між різними пакетами.

Відстеження одного проходження через цикл

Припустимо, користувач просить найновіші новини про LangGraph. Одне виконання циклу створює таку послідовність повідомлень та кроків:

HumanMessage
     ↓
    Agent
     ↓
    LLM
     ↓
"I need current information."
     ↓
Tool Call
     ↓
Tavily Search
     ↓
Search Results
     ↓
ToolMessage
     ↓
    Agent
     ↓
    LLM
     ↓
Final Answer

Важлива структурна відмінність полягає у циклі. Перший чат-бот був лінійним:

START → LLM → END

Граф ReAct може звертатися до моделі стільки разів, скільки це необхідно:

  START
    ↓
  Agent
    ↓
  LLM
    ↓
  Tool?
 ┌───────┐
 │       │
Yes      No
 │       │
 ↓       ↓
Tool    END
 │
 ↓
Agent

Оскільки цикли, у принципі, можуть виконуватися без кінця, LangGraph встановлює обмеження на рекурсію під час кожного запуску (яке можна налаштувати за допомогою параметра recursion_limit). Якщо модель продовжує запитувати інструменти, замість нескінченного рахунку ви отримуєте помилку.

Крок 6: зберігання розмов за допомогою MemorySaver

Досі кожен виклик invoke починався з нуля. Розглянемо два кроки в розмові. Спочатку користувач представляється:

User:
My name is Himanshi.

Потім, у окремому запиті, ставить додаткове запитання:

User:
What is my name?

Другу спробу можна обробити лише тоді, коли у графі все ще є перша. LangGraph вирішує цю проблему за допомогою checkpointer: після кожного кроку він зберігає знімок стану, і наступна обробка тієї самої розмови продовжується з цього знімка. MemorySaver — це найпростіша реалізація:

             ┌─────────────┐
             │    Agent    │
             └──────┬──────┘
                    │
                    ▼
              ┌───────────┐
              │   State   │
              └─────┬─────┘
                    │
                    ▼
             ┌─────────────┐
             │ MemorySaver │
             └─────────────┘

Ви приєднуєте його під час компіляції за допомогою graph_builder.compile(checkpointer=memory). Замість того, щоб кожен виклик був окремою обробкою, тепер кожна робота належить до певної розмови, а її стан зберігається між роботами.

Завдання тримають розмови окремо

Ключ, який пов’язує роботи між собою, — це ідентифікатор завдання, який передається в конфігурації у вигляді {"configurable": {"thread_id": "..."}}. Кожне завдання має власну історію та власний поточний стан:

Thread 1
 ├── Message 1
 ├── Message 2
 ├── Message 3
 └── Current State

Thread 2
 ├── Message 1
 ├── Message 2
 └── Current State

Отже, двоє користувачів у двох потоках ніколи не бачать повідомлень один одного:

User A → Thread A → State A

User B → Thread B → State B

Існують два важливі моменти. MemorySaver зберігає все у пам’яті процесу, тому вся історія зникає після перезапуску та не є спільною між інстанціями сервера; у продакшн-розгортаннях використовується постійний чекпоїнт, наприклад у Postgres або SQLite. У останніх версіях клас також експортується як InMemorySaver. Щоб детальніше дізнатися, що саме зберігається у чекпоїнті, перегляньте як чекпоїнти, записи та блоки даних поєднуються в InMemorySaver.

Крок 7: потік стану під час виконання графа

Усе, що було описано вище, використовувало блокуючі виклики:

graph.invoke(...)

Він чекає, поки не завершиться весь граф, перш ніж повернутися. Для агентів з кількома кроками це може означати кілька секунд порожнього екрана, що здається користувачам некоректним. Компільований граф також відкриває доступ до stream, який генерує результати під час тривання виконання.

stream_mode="values": повний стан після кожного кроку

Перший режим, про який варто знати:

stream_mode="values"

У поєднанні з циклом він надає знімок всього стану після кожного кроку:

for chunk in graph.stream(
    input_state,
    stream_mode="values"
):
    print(chunk)

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

START
  ↓
State Update #1
  ↓
Node Execution
  ↓
State Update #2
  ↓
Tool Execution
  ↓
State Update #3
  ↓
END

Це зручно, коли стан невеликий, і ви просто хочете відображати „найновішу версію“ щоразу.

stream_mode="updates": лише те, що змінився в кожному вузлі

Другий режим:

stream_mode="updates"

Різниця полягає у тому, що містить кожен фрагмент. У одному режимі ви отримуєте повний стан:

values

У іншому ви отримуєте лише дельту, позначену назвою вузла, який її створив:

updates

Швидкий спосіб запам’ятати цю різницю:

  • values відповідають на запитання „Як виглядає стан зараз?“
  • updates відповідають на запитання „Що саме змінив цей конкретний вузол?“

updates зазвичай є кращим вибором для дебаггінгу та логування, оскільки він показує, який вузол був відповідальний за кожну зміну, та допомагає зберігати фрагменти невеликими при великому обсязі стану. Жоден з режимів не передає окремі токени від моделі; для отримання даних по одному токену існує окремий режим потоку messages, і ви можете передати список режимів, щоб отримувати кілька типів даних одночасно.

Крок 8: детальний рівень видимості за допомогою astream_events

Знімки стану та зміни вузлів показують, що змінилося, але не все, що відбулося. astream_events заглиблюється на один рівень глибше та генерує подію для кожної миті життєвого циклу кожного компонента під час виконання:

Graph Execution
      │
      ├── Graph Started
      │
      ├── Node Started
      │
      ├── LLM Started
      │
      ├── LLM Streaming
      │
      ├── LLM Completed
      │
      ├── Tool Started
      │
      ├── Tool Completed
      │
      ├── Node Completed
      │
      └── Graph Completed

Ви отримуєте ці дані асинхронно. Зверніть увагу, що виклик print у наведеному нижче фрагменті має бути внутрішнім у тілі циклу; як показано, Python відхилить його через помилку індентації:

async for event in graph.astream_events(
    input_state,
    version="v2"
):

print(event)

Кожна подія містить тип (наприклад on_chat_model_stream або on_tool_start), назву компонента та його дані. Це робить потік подій корисним для:

  • дебаггінгу та структурованого журналінгу
  • загального контролю за виконанням агента
  • відстеження кожного виклику LLM та кожного виклику інструменту
  • створення індивідуального інтерфейсу потокового відображення, який показує токени та активність інструментів
  • розуміння того, де витрачається час
  • Інженери з досвідом роботи з бекендом впізнають цю схему: це більше схоже на відстеження розподіленого запиту, ніж на читання значення повернення окремої функції. Питання змінюється з „Чи працював агент?“ на „Що саме зробив агент, у якій послідовності та скільки часу зайняла кожна частина?“

    Як складаються елементи

    Якщо озирнутися назад, кожен крок додавав одну можливість до тієї самої структури. Усе починалося з одного виклику моделі:

    START
     ↓
    LLM
     ↓
    END
    

    Інструменти додали подорож туди й назад у зовнішній світ:

    START
     ↓
    LLM
     ↓
    Tool
     ↓
    LLM
     ↓
    END
    

    Функція перевірки стану дозволила циклу пам’ятати:

    START
     ↓
    Agent
     ↓
    LLM ↔ Tools
     ↓
    Memory / Checkpoint
     ↓
    END
    

    Потокове відображення та події зробили процес доступним для інтерфейсу та моніторингу:

    Graph
     ↓
    Events
     ↓
    UI / Logs / Observability
    

    Жоден з цих кроків не вимагав переписування попередніх; кожен з них є новим вузлом, ребром, опцією компіляції чи викликом методу в межах того самого графа.

    Крок 9: розміщення інструментів за сервером MCP

    До цього моменту інструменти знаходилися всередині додатку. Протокол Model Context Protocol (MCP) використовує інший підхід: окремий сервер надає доступ до інструментів (та ресурсів), а будь-який MCP клієнт може їх знаходити та використовувати. Додатку більше не потрібно містити реалізації кожного інструменту:

                     ┌──────────────┐
                     │ MCP Client   │
                     └──────┬───────┘
                            │
                            │
                            ▼
                     ┌──────────────┐
                     │ MCP Server   │
                     └──────┬───────┘
                            │
                 ┌──────────┼──────────┐
                 ↓          ↓          ↓
              add()     multiply()   weather()
    

    Мінімальний сервер із трьома інструментами

    Достатньо невеликого сервера для навчання, щоб побачити архітектуру. Він надає доступ до інструменту додавання:

    add(a, b)
    

    інструменту множення:

    multiply(a, b)
    

    та інструменту для отримання інформації про погоду, який приймає назву міста:

    weather(city)
    

    Інструмент для перевірки погоди не викликає справжню службу погоди. Він повертає фіксований рядок, що змушує увагу бути зосередженою на протоколі, а не на ключах API:

    "It's sunny in Mumbai."
    

    Важливим є процес обміну даними, а не прогноз погоди:

    Client
      ↓
    MCP Server
      ↓
    Tool
      ↓
    Result
      ↓
    Client
    

    Інструменти більше не обов’язково мають знаходитися в застосунку-користувачі

    Основна ідея полягає у тому, що інструменти можуть розміщуватися незалежно від застосунків, які їх використовують. Застосунок взаємодіє з клієнтом MCP, клієнт передає дані через протокол на сервер, а сервер відповідає за реалізацію:

                    Application
                         │
                         ▼
                     MCP Client
                         │
                   MCP Protocol
                         │
                         ▼
                     MCP Server
                    /     |      \
                   /      |       \
                  ↓       ↓        ↓
                Add    Multiply   Weather
    

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

    Database Tools
    File Tools
    Git Tools
    Search Tools
    Weather Tools
    Internal APIs
    

    Кілька різних агентів, клієнтів чату чи інтеграцій у IDE можуть використовувати один і той самий сервер без необхідності дублювання коду.

    Крок 10: поєднання LangGraph та MCP

    Ці дві технології вирішують різні аспекти проблеми. LangGraph керує робочим процесом: станом, маршрутизацією, циклами, пам’яттю та стрімінгом. Стандарт MCP уніфікує спосіб використання інструментів та ресурсів. У поєднаному підході вузол інструментів агента взаємодіє з клієнтом MCP замість того, щоб викликати локальні функції:

                              User
                               │
                               ▼
                         LangGraph Agent
                               │
                               ▼
                              LLM
                               │
                        Need a tool?
                               │
                               ▼
                          MCP Client
                               │
                               ▼
                          MCP Server
                        /      |       \
                       ↓       ↓        ↓
                     Math    Search   Weather
    

    Агенту не потрібно знати, як реалізований інструмент. Йому достатньо знати, які можливості існують та як їх викликати, а решту бере на себе сервер MCP. У Python пакет langchain-mcp-adapters є поширеним способом завантаження інструментів MCP як інструментів LangChain, щоб їх можна було прив’язати до моделі та використовувати у ToolNode. Існуюча стаття на стандартизації інтеграції інструментів у LangGraph за допомогою MCP детальніше розглядає цю інтеграцію.

    Повна когнітивна модель

    Якщо об’єднати все разом, агент LangGraph виглядає так: вхідні дані користувача стають станом, вузли працюють з ними, модель вирішує, чи потрібен інструмент, результати роботи інструмента надходять назад до моделі, а виконання завершується відповіддю:

                              USER
                               │
                               ▼
                        ┌────────────┐
                        │ LangGraph  │
                        │   Agent    │
                        └──────┬─────┘
                               │
                               ▼
                             STATE
                               │
                 ┌─────────────┼─────────────┐
                 │             │             │
                 ▼             ▼             ▼
               NODE          NODE          NODE
                 │             │             │
                 └─────────────┼─────────────┘
                               │
                               ▼
                             LLM
                               │
                         Need a tool?
                          /          \
                        Yes           No
                         │             │
                         ▼             ▼
                      TOOL          RESPONSE
                         │
                         ▼
                      RESULT
                         │
                         └──────► LLM
                                   │
                                   ▼
                                  END
    

    Навколо цього основного циклу знаходяться функції, які вмикаються за потреби:

                  ┌──────────────────────────────┐
                  │          Capabilities        │
                  │                              │
                  │  Memory / Checkpointing      │
                  │  Streaming                   │
                  │  Event Tracking              │
                  │  MCP                         │
                  │  Tools                       │
                  └──────────────────────────────┘
    

    Де закінчується LangChain та починається LangGraph

    Уроки, які застосовуються до більших графів

    Стан є основою

    Замість передачі даних через аргументи функцій кожен вузол читає та записує дані з одного спільного стану, а редюсери визначають, як поєднуються оновлення:

    State
     ↓
    Node
     ↓
    Updated State
     ↓
    Next Node
    

    Вузли — це одиниці роботи

    Кожен вузол має виконувати одну чітко визначену задачу, що полегшує розбирання та тестування складних процесів:

    chatbot node
    search node
    tool node
    validation node
    human approval node
    

    Краї кодують алгоритм виконання

    Край відповідає на одне запитання: після завершення роботи цього вузла куди продовжиться виконання? Прості краї створюють послідовності; умовні краї — гілки та цикли.

    Діаграми краще за читання коду

    Для графа з багатьма вузлами та умовними шляхами візуалізована діаграма краще передає архітектуру, ніж читання коду її створення:

    Node → Decision → Tool → Agent → END
    

    Чекпоїнти — це більше, ніж історія чату

    Чекпоінтер зберігає повний стан графу, а не лише повідомлення. Саме це дозволяє здійснювати багаторазові розмови, а також є основою для довготривалих агентів, перерв під час роботи з участю людини та відновлення після збоїв.

    Стрімінг корисний як для користувачів, так і для розробників

    Стрімінг покращує враження про швидкість реакції, а той самий вихідний даний допомагає у виправленні помилок та спостереженні за процесом виконання.

    Виконання агента — це безліч операцій

    За допомогою astream_events стає зрозуміло, що один виклик агента — це вкладена послідовність операцій з графом, вузлами, моделями та інструментами:

    Graph
     ↓
    Node
     ↓
    LLM
     ↓
    Tool
     ↓
    LLM
     ↓
    Node
     ↓
    Graph
    

    Чим складніший додаток, тим більше вам потрібна така прозорість.

    MCP відокремлює інструменти від додатків

    Інструменти, які доступні через MCP, можна поширювати між додатками та розвивати незалежно від агентів, які їх використовують.

    Куди рухатися далі

    Це фундація відкриває кілька напрямків, які варто дослідити: умовні краї та паралельні гілки, перерви з участю людини, довгострокова пам’ять та постійні точки перевірки, генерація з підсиленням за допомогою пошуку всередині графа, багатоагентні системи, глибша інтеграція MCP, спостережуваність та оцінка агентів, впровадження у продакшн та стійкість до помилок із повторними спробами.

    Спільною рисою є те, що AI-застосунок — це не просто модель, яка генерує текст. Це система, створена навколо моделі, яка може отримати запит, прийняти рішення, діяти, спостерігати за результатом, запам’ятати його та продовжувати роботу:

    Understand
        ↓
    Decide
        ↓
    Act
        ↓
    Observe
        ↓
    Remember
        ↓
    Continue
    

    Граф — це природний спосіб виразити такий цикл.

    Підсумок

    Увесь процес почався з найменшого можливого графа:

    START → Chatbot → END
    

    і поступово, шар за шаром, перетворився на систему з інструментами, циклом агентів, пам’яттю, потоковою обробкою та MCP:

                  ┌─────────────┐
                  │    LLM      │
                  └──────┬──────┘
                         │
                         ▼
                  ┌─────────────┐
                  │    Tools    │
                  └──────┬──────┘
                         │
                         ▼
                  ┌─────────────┐
                  │    Agent    │
                  └──────┬──────┘
                         │
              ┌──────────┼──────────┐
              ↓          ↓          ↓
           Memory    Streaming     MCP
              │          │          │
              └──────────┼──────────┘
                         ↓
                    AI Application
    

    Якщо розглядати їх разом, ці елементи вже не схожі на простий список, а нагадують єдину архітектуру:

    • Стан зберігає дані та, через контрольні точки, пам’ять; для списків повідомлень використовується функція-редюсер add_messages.
    • Вузли виконують роботу та повертають часткові оновлення.
    • Ребра визначають напрямок потоку, а умовні ребра перетворюють його на цикл.
    • Модель забезпечує логіку та мову; інструменти дозволяють їй впливати на зовнішній світ.
    • Стрімінг та події роблять кожен запуск прозорим – від оновлень інтерфейсу до записів дій.
    • MCP стандартизує спосіб використання інструментів, щоб вони могли існувати поза агентом, який їх використовує.