Головна / Статті / Агенти ReAct у LangGraph: крок за кроком — мислення, дія та спостереження.

Агенти ReAct у LangGraph: крок за кроком — мислення, дія та спостереження.

Впровадіть цикл ReAct у вигляді явних графових вузлів із типовим станом, викликами інструментів та умовами зупинки, які можна перевірити.

4143 слів

У цьому посібнику створюється функціональний шлях для: «ReAct Agents Explained: Покрокова реалізація з використанням LangGraph». Основна увага приділяється контрактам, перевіркам та коду, який можна додати до репозиторію без необхідності здогадуватися щодо його призначення. Для загального огляду перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись здогадатися про прихований стан. Краще використовувати невеликі, тестирувані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну ієрархію операцій.

Вступ

На етапі введення необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте мовчазного часткового завершення. Розділіть планування та виконання за допомогою інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями.

Що таке агент ReAct?

У розділі «Що таке агент ReAct?» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Раннє бачення допомагає уникнути несподіваних рахунків, коли процес переходить від демо-середовища до спільних. Розділяйте планування та виконання завдань за допомогою інструментів. Планувальник пропонує варіанти; виконавець їх реалізує; перевірювач порівнює результати з поставленими цілями.

Чому ReAct кращий за чисту модель Chain-of-Thought

Щоб зрозуміти, чому ReAct кращий за чисту модель Chain-of-Thought, необхідно перед змінами коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь алгоритм. Необхідно розділити процес планування та виконання завдань за допомогою інструментів. Планувальник пропонує шляхи виконання; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями. Щоб зрозуміти, чому ReAct кращий за чисту модель Chain-of-Thought, необхідно перед змінами коду визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність.

а не складною ієрархією процесів.

Thought: I don’t know the answer yet. I should search.
Action: Search("Paris weather this week")
Observation: It will rain on Thursday.
Thought: I should suggest indoor activities.
Final Answer: ...

ReAct Prompting

Для ReAct Prompting необхідно спочатку визначити вхідні дані, відповідального за кожен крок та критерії завершення, перш ніж змінювати код. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви для кожного елемента, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Суворо обмежуйте схеми інструментів. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють аудит.

Мета ReAct Prompting

Для мети ReAct Prompting необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Рання видимість допомагає уникнути несподіваних рахунків, коли процес переходить від демо-середовища до спільних. Суворо обмежуйте схеми інструментів. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та роблять аудити дорогими.

Ключові елементи ReAct Prompting

Для ключових елементів підходу ReAct Prompting необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь код. Схеми інструментів слід суворо обмежувати. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють перевірки. Для ключових елементів підходу ReAct Prompting необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість величезних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних.

1. Міркування з послідовними кроками

Для методу 1. Міркування з послідовними кроками необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення роботи. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного оплати однакової роботи.

2. Чіткий простір дій

2. Для простору експліцитних дій необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Раннє відображення цих даних запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного нарахування за ту саму роботу.

3. Інтеграція спостережень

Для пункту 3. Інтеграція спостережень необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь алгоритм. Створюйте точки контролю після дорогих викликів моделей, щоб повторна спроба не призводила до подвійного обчислення тієї самої роботи. Для пункту 3. Інтеграція спостережень необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Віддавайте перевагу невеликим, тестованим одиницям перед складними скриптами. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних.

4. Ітеративне циклювання

Для 4. Ітеративного циклу необхідно визначити вхідні дані, відповідального за кожен крок та критерії завершення ще до змін коду. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте беззвучного часткового завершення. Розділіть планування та виконання за допомогою інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленою метою.

5. Створення кінцевої відповіді

Для кроку 5. Створення остаточної відповіді необхідно перед зміною коду визначити вхідні дані, виконавця цього кроку та критерії завершення. Оператори мають мати можливість знову запустити крок з відомої точки контролю, не здогадуючись про прихований стан. Фіксуйте час виконання та витрати поруч із функціональними результатами. Раннє бачення допомагає уникнути несподіваних рахунків, коли процес переходить від демо-середовища до спільних. Розділяйте планування та виконання за допомогою інструментів: планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями.

Канонічна структура запиту ReAct

Для канонічної структури запиту ReAct необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь код. Необхідно розділити процес планування та виконання завдань за допомогою інструментів. Планувальник пропонує рішення; виконавець його реалізує; перевірювач порівнює результати з поставленими цілями. Для канонічної структури запиту ReAct необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестовані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну структуру виконання завдань.

Необхідно.

Question: <user question>Thought: <reason about what to do next>

Action: <selected tool>
Action Input: <tool input>
Observation: <tool output>
... (repeat as needed)
Thought: I now know the final answer
Final Answer: <answer to the user>

Zero-Shot ReAct Prompting

Для методу Zero-Shot ReAct Prompting необхідно визначити вхідні дані, власника кроку та критерії завершення ще до зміни коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте створювані елементи, визначте критерії успіху та не допускайте беззвучного часткового виконання. Суворо обмежуйте схеми інструментів. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють аудит.

ReAct Prompting проти ReAct Agents

Для сценаріїв ReAct Prompting та ReAct Agents необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Фіксуйте час виконання та витрати поруч із функціональними результатами. Рання видимість допомагає уникнути несподіваних рахунків під час переходу від демо-середовищ до спільних. Схеми інструментів слід суворо обмежувати; широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та підвищують витрати на аудит.

Чому саме LangGraph для ReAct Agents?

Щодо питання «Чому саме LangGraph для агентів ReAct?», необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь граф. Схеми інструментів слід суворо обмежувати. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють перевірки. Щодо питання «Чому саме LangGraph для агентів ReAct?», необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану систему обробки даних.

Основна проблема: ReAct — це машина станів, а не запит

У розділі «Основна проблема: ReAct — це машина станів, а не запит» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте елементи, визначте критерії успіху та не допускайте беззвучного часткового завершення. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного обчислення тієї самої роботи.

Що ламається без LangGraph

Для сценаріїв, які не працюють із LangGraph, перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Раннє відображення цих даних запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного нарахування за ту саму роботу.

1. Імпліцитний потік керування

Для 1. Імпліцитного потоку керування необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь алгоритм. Створюйте точки контролю після дорогих викликів моделей, щоб повторна спроба не призводила до подвійного обчислення тієї самої роботи. Для 1. Імпліцитного потоку керування необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Віддавайте перевагу невеликим, тестованим одиницям перед об’ємними скриптами. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру обробки даних.

while True:
    llm_output = llm(prompt)
    if "Action:" in llm_output:
        tool_result = call_tool(...)
    else:
        break

2. Крихке управління станом

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

3. Відсутність семантики циклів першого класу

Для пункту 3. Оскільки немає семантики циклу першого класу, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Рання видимість допомагає уникнути несподіваних рахунків, коли процес переходить від демо-середовища до спільних. Розділяйте планування та виконання за допомогою інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями.

4. Недостатня готовність до виробництва

Для пункту 4. Низька готовність до експлуатації необхідно визначити вхідні дані, відповідальну особу за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Необхідно розділити планування та виконання завдань за допомогою інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями. Для пункту 4. Низька готовність до експлуатації необхідно визначити вхідні дані, відповідальну особу за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, тестирувані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну структуру обробки даних.

Ключові концепції в LangGraph (підхід, орієнтований на агента)

Для ключових концепцій у LangGraph (підхід, орієнтований на агента) необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви для результатів роботи, визначте критерії успіху та не допускайте мовчазного часткового завершення. Суворо обмежуйте схеми інструментів. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють аудит.

1. Стан: пам’ять агента

Для кроку 1. «Пам’ять агента»: перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Фіксуйте час виконання та витрати поруч із функціональними результатами. Чітка видимість у ранньому етапі запобігає несподіваним рахункам під час переходу з демо-середовища у спільні. Схеми інструментів слід суворо обмежувати. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та роблять аудити дорогими.

2. Вузли: когнітивні та операційні одиниці

Для 2. Вузлів: когнітивних та операційних одиниць, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевіряти, не читаючи весь граф. Схеми інструментів слід суворо обмежувати. Широкі параметри у вигляді вільного тексту сприяють втручанню ззовні та ускладнюють аудит. Для 2. Вузлів: когнітивних та операційних одиниць, необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану систему обробки даних.

3. Краї: Явний потік керування

У розділі 3. Краї: Явний потік керування необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте мовчазного часткового завершення. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного оплати однакової роботи.

4. Детерміністична екзекуція з гнучкістю

Для 4. Детермінована екзекуція з гнучкістю: визначте вхідні дані, власника кроку та критерії завершення ще до зміни коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати поруч із функціональними результатами. Рання видимість допомагає уникнути несподіваних рахунків, коли процес переходить від демо-середовища до спільних. Створюйте точки контролю після дорогих викликів моделей, щоб повторна спроба не призводила до подвійного нарахування за ту саму роботу.

ReAct + LangGraph: ідеальне поєднання

Для ReAct + LangGraph: це ідеальне поєднання. Перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь граф. Створюйте точки контролю після дорогих викликів моделі, щоб повторна спроба не призводила до подвійного обчислення тієї самої роботи. Для ReAct + LangGraph: це ідеальне поєднання. Перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Віддавайте перевагу невеликим, тестованим одиницям перед величезними скриптами. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану систему обробки даних.

Сценарій використання: Асистент з скасування бронювання в готелі (правила + розрахунок повернення коштів)

Для сценарію використання «Асистент з скасування бронювання в готелі (правила + розрахунок повернення коштів)» необхідно визначити вхідні дані, відповідального за кожен етап та критерії завершення перед зміною коду. Оператори мають мати можливість знову виконати етап, починаючи з відомої точки контролю, без необхідності здогадуватися про прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте мовчазного часткового завершення роботи. Розділіть планування та виконання завдань за допомогою інструментів. Планувальник пропонує рішення; виконавець його реалізує; перевірювач порівнює результати з поставленими цілями.

Опис проблеми

Для опису проблеми необхідно визначити вхідні дані, відповідальну особу за крок та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Розділіть планування від виконання інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями.

Крок 1: Встановити залежності

Розділіть планування від виконання інструментів. Планувальник пропонує; виконавець здійснює зміни; перевіряючий порівнює результати з поставленими цілями.

pip install -U langgraph langchain langchain-openai
export OPENAI_API_KEY="..."

Крок 2: Визначити інструменти (ваші „Дії“)

Суворо обмежте схеми інструментів. Широкі аргументи у вигляді вільного тексту сприяють втручанню та ускладнюють аудити.

from typing import TypedDict, Annotated
from datetime import datetime
import json

from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.messages import (
    BaseMessage,
    HumanMessage,
    ToolMessage,
    SystemMessage
)
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import tools_condition

@tool
def get_cancellation_policy(rate_plan: str) -> str:
    """
    Returns cancellation policy text for a given rate plan.
    """
    policies = {
        "flexible": "Free cancellation until 24 hours before check-in. After that, first night is charged.",
        "semi-flex": "Free cancellation until 72 hours before check-in. After that, 50% of the stay is charged.",
        "non-refundable": "No refund after booking. Full stay amount is charged on cancellation."
    }
    key = rate_plan.strip().lower()
    return policies.get(key, "Policy not found. Supported: flexible, semi-flex, non-refundable.")
@tool
def calculate_refund(
    rate_plan: str,
    check_in: str,
    cancel_date: str,
    nightly_rate: float,
    nights: int
) -> str:
    """
    Calculates refund amount based on a simplified policy model.
    Dates format: YYYY-MM-DD
    """
    rp = rate_plan.strip().lower()
    ci = datetime.strptime(check_in, "%Y-%m-%d").date()
    cd = datetime.strptime(cancel_date, "%Y-%m-%d").date()
    total = nightly_rate * nights
    days_before = (ci - cd).days
    if rp == "non-refundable":
        refund = 0.0
        charged = total
        rule = "Non-refundable: no refund."
    elif rp == "flexible":
        if days_before >= 1:
            refund = total
            charged = 0.0
            rule = "Flexible: cancelled >= 24h before check-in, full refund."
        else:
            charged = nightly_rate  # 1 night penalty
            refund = max(total - charged, 0.0)
            rule = "Flexible: late cancel, 1 night charged."
    elif rp == "semi-flex":
        if days_before >= 3:
            refund = total
            charged = 0.0
            rule = "Semi-flex: cancelled >= 72h before check-in, full refund."
        else:
            charged = 0.5 * total
            refund = total - charged
            rule = "Semi-flex: late cancel, 50% charged."
    else:
        return "Unsupported rate plan. Use: flexible, semi-flex, non-refundable."
    return (
        f"Rule: {rule}\n"
        f"Days before check-in: {days_before}\n"
        f"Total: ${total:.2f}\n"
        f"Charged: ${charged:.2f}\n"
        f"Refund: ${refund:.2f}"
    )

Крок 3: Створити цикл ReAct у LangGraph (Міркування → Інструмент → Міркування)

Суворо обмежте схеми інструментів. Широкі аргументи у вигляді вільного тексту сприяють втручанню та ускладнюють аудити.

from typing import TypedDict, Annotated
from langchain_core.messages import BaseMessage, HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

from langchain_openai import ChatOpenAI
from langchain_core.tools import Tool
from langgraph.prebuilt import ToolNode, tools_condition
# 1) Define state
class AgentState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    booking_id: str
# 2) Define structured Output schema
class RefundDecision(BaseModel):
    booking_id: str
    rate_plan: str
    total_amount: float
    charged_amount: float
    refund_amount: float
    policy_summary: str
    explanation: str
# 2) Choose model and System prompt
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
SYSTEM_PROMPT = SystemMessage(
    content="""
You are a hotel cancellation assistant.
Rules:
- Use tools when needed.
- Never guess policy or refund.
- Final answer MUST be valid JSON with this schema:
{
  "booking_id": "...",
  "rate_plan": "...",
  "total_amount": number,
  "charged_amount": number,
  "refund_amount": number,
  "policy_summary": "...",
  "explanation": "..."
}
"""
)
# 3) Register tools
tools = [get_cancellation_policy, calculate_refund]
def safe_tool_node(state):
    last_msg = state["messages"][-1]
    if not hasattr(last_msg, "tool_calls") or not last_msg.tool_calls:
        return {}
    tool_call = last_msg.tool_calls[0]
    tool_name = tool_call["name"]
    if tool_name not in ALLOWED_TOOLS:
        return {
            "messages": [
                ToolMessage(
                    content=f"Tool '{tool_name}' is not allowed.",
                    tool_call_id=tool_call["id"]
                )
            ]
        }
    for tool in tools:
        if tool.name == tool_name:
            result = tool.invoke(tool_call["args"])
            return {
                "messages": [
                    ToolMessage(
                        content=result,
                        tool_call_id=tool_call["id"]
                    )
                ]
            }
# 4)Before reasoning, check if we already processed this booking.
REFUND_MEMORY = {}
def memory_lookup_node(state):
    booking_id = state["booking_id"]
    if booking_id in REFUND_MEMORY:
        return {
            "messages": [
                HumanMessage(
                    content=f"Cached decision found:\n{REFUND_MEMORY[booking_id]}"
                )
            ]
        }
    return {}
# 5) Reasoning node: LLM decides next action (tool call) or final answer
def agent_node(state: AgentState):
    # Bind tools so the model can produce tool calls
    llm_with_tools = llm.bind_tools(tools)
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}
# 6) After final decision, store it.
def memory_write_node(state):
    booking_id = state["booking_id"]
    final_answer = state["messages"][-1].content
    REFUND_MEMORY[booking_id] = final_answer
    return {}

Створення та компіляція графу

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

# 7) Build the graph
builder = StateGraph(AgentState)

# Nodes
builder.add_node("memory_lookup", memory_lookup_node)
builder.add_node("agent", agent_node)
builder.add_node("tools", safe_tool_node)
builder.add_node("memory_write", memory_write_node)
# Flow
builder.add_edge(START, "memory_lookup")
builder.add_edge("memory_lookup", "agent")
builder.add_conditional_edges(
    "agent",
    tools_condition,
    {
        "tools": "tools",   # model wants to act
        END: "memory_write" # model finished reasoning
    }
)
builder.add_edge("tools", "agent")
builder.add_edge("memory_write", END)
graph = builder.compile()

Крок 4: Запуск агента у конкретному випадку використання

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

query = """
Booking details:
Rate plan: Non-Refundable
Check-in: 2026-01-20
Nights: 2
Nightly rate: 120
Cancelled on: 2026-01-18
"""

result = graph.invoke({
    "booking_id": "BKG-12345",
    "messages": [
        SYSTEM_PROMPT,
        HumanMessage(content=query)
    ]
})
final_output = result["messages"][-1].content
print(final_output)

Результат

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

{
  "booking_id": "BKG-12345",
  "rate_plan": "Non-Refundable",
  "total_amount": 240.0,
  "charged_amount": 240.0,
  "refund_amount": 0.0,
  "policy_summary": "Non-refundable bookings do not allow refunds after confirmation.",
  "explanation": "The booking was made under a non-refundable rate plan, which charges the full stay amount regardless of cancellation timing."
}

Що відбувається всередині (поведінка ReAct)

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

1) Мислення (Обґрунтування)

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

2) Дія (Виклик інструменту)

Схеми інструментів мають бути суворо обмежені. Широкі параметри у вигляді вільного тексту сприяють втручанню та роблять аудити дорогими.

3) Спостереження (результати роботи інструментів)

Чекпоїнт після дорогих викликів моделі, щоб повторна спроба не стягувала плату за ту саму роботу.

4) Кінцева відповідь

Чекпоїнт після дорогих викликів моделі, щоб повторна спроба не стягувала плату за ту саму роботу.

Чому це є «ReAct» (а не просто інструменти)

Чекпоїнт після дорогих викликів моделі, щоб повторна спроба не стягувала плату за ту саму роботу.

Висновок

Розділіть планування від виконання інструментів. Планувальник пропонує; виконавець змінює; перевіряючий порівнює результати з поставленими цілями.

Чек-лист для експлуатації

Суворо обмежте схеми інструментів. Широкі аргументи у вигляді вільного тексту сприяють злому та ускладнюють аудити.

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

Розміщуйте типи разом із компонентами та тримайте їхні властивості обмеженими. Широкий набір властивостей стає причиною проблем, яких мав запобігти TypeScript.

Напишіть короткий посібник: як обертати ключі, як спорожнювати чергу, як скасовувати останні зміни.