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

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

Функція LangGraph compile() перевіряє підключення, а не повноту бізнес-даних. Граф від покупки до рахунку-фактури може бути створений, якщо пропустити один вузол, тому необхідно переконатися, що виконані всі обов’язкові кроки.

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

Джерела: The Node That Never Ran: What LangGraph Does Not Check at Compile Time

Робочий процес LangGraph від покупки до рахунку-фактури може завершитися успішно, пропустивши певний вузол. Цей спосіб збою гірший за повний зсув: фінансовий відділ вважає, що рахунок-фактура існує, хоча граф ніколи не виконував цей крок. Причиною є прогалина у тому, що перевіряє функція compile(), та відсутність двох механізмів, які б це виявляли.

Відтворення ситуації

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

graph.add_edge("create_purchase_order_from_request", "create_an_invoice")

При повторному запуску:

Executing: load_purchase_request
Executing: validate_purchase_request_is_approved
Executing: create_purchase_order_from_request

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

Що насправді перевіряє функція compile()

StateGraph.compile() перевіряє, чи посилання на ребра вказують на справжні вузли, чи існує запис для START, та чи є місцем призначення ребра вузли або END. Він не підтверджує, що кожен вузол є доступним, що кожен вузол може дістатися до END, або що кожен вузол виконується. Туторіали, які стверджують, що перевірки доступності охоплюють усе, перебільшують можливості цього API.

  1. Одинокий вузол — зареєстрований, але без вхідного ребра; ніколи не виконується; компіляція все одно успішна.
  2. Пошкоджений шлях до END — видалення ребра посеред графа призводить до мовчазного часткового успіху з невідповідним статусом.

Обидва випадки виглядають як успішні компіляції.

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

Чому END недостатньо спеціальний

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

Чому фреймворк робить такий вибір

Аналіз досяжності та обов’язкових вузлів є проблемами логіки програми. Бібліотека перевіряє структурну схему, а потім дозволяє коду продукту визначати, які вузли є обов’язковими. Це прискорює компіляцію та уникає суб’єктивних бізнес-правил, але перекладає відповідальність на тести.

Як це виявити

Додайте явну перевірку після виконання (або на рівні графа), чи були виконані необхідні вузли — за допомогою лічильників у стані, хуків повернення або предикату завершення:

def find_detached_nodes(compiled):
    g = compiled.get_graph()
    sources = {e.source for e in g.edges}
    targets = {e.target for e in g.edges}
    dead_ends = [n for n in g.nodes if n not in sources and n != "__end__"]
    orphans  = [n for n in g.nodes if n not in targets and n != "__start__"]
    return dead_ends, orphans

Також додайте тести на одиниці, які навмисно видаляють ребра та очікують, що захист не спрацює. Додаткові статичні перевірки можуть аналізувати список сусідств для перевірки досяжності від START до кожного необхідного вузла та далі до END.

Висновки

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

Пов’язування бага з фінансовими операціями

Процес від покупки до рахунку-фактури не прощає помилок. Якщо функція create_invoice ніколи не виконується, системи обліку боргів, експорт податкової інформації та портали постачальників починають працювати некоректно. Повернення значення success із параметром status=purchase_order_created є ознакою проблеми в додатку: сервіси верхнього рівня інтерпретують такі результати як завершення процесу. Необхідно узгодити статус HTTP/завдання з переліком обов’язкових ідентифікаторів вузлів, які зберігаються у стані системи.

Мінімальний контракт обов’язкових вузлів

Треба підтримувати значення state["executed"] у вигляді зведувача множин. Кожен вузол додає свою назву до цієї множини. Перед кінцем виконання (або після виклику функції у тому, хто її запускає) необхідно перевірити, чи виконується умова {"load","validate","create_po","create_invoice"} <= executed. Відсутні назви вузлів спричиняють появу спеціальної помилки IncompleteGraphError, на яку може звернути увагу система моніторингу. Це перетворює допускальний результат від фреймворку на серйозну помилку в конкретній сфері застосування.

Перевірки статичної сусідності в CI

У тестах серіалізуйте список ребер графа. Обчислюйте досяжність від точки START. Припиніть виконання CI, якщо потрібна вузол є недосяжною або не може дістатися до точки END. Ці тести виявляють коментовані ребра вже того ж дня, коли вони додаються — без необхідності чекати на їх проігнорування у реальних умовах.

Що має означати термін „компіляція“ у посібниках з експлуатації

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

Пов’язані проблеми

  • Умовні ребра, які можуть вести нікуди, якщо прапорець стану не встановлений.
  • Повторна спроба виклику без усунення часткових побічних ефектів, що призводить до створення дублікатів PO, при цьому інвойси все одно ігноруються.
  • Полегшення лише на скріншотах від LangSmith замість перевірок, які можуть виконуватися машиною у процесі CI.
  • Розглядайте топологію графа як продуктивні дані. Перевіряйте її як схеми — а не як необов’язкові коментарі.

    Аналіз коректного та несправного виклику

    У здоровому графі функції-виклики чи оператори виводу демонструють послідовність: завантаження → перевірка → створення PO → створення інвойсу → позначка про завершення. Оператори, які порівнюють журнали з посібником, можуть позначити кожну стадію. Після видалення ребра позначка інвойсу ніколи не з’являється, проте функція на Python все одно отримує словник. Системи нижчого рівня, які перевіряють лише успішність передачі даних, вводять значення “done” у інструмент керування випадками. Саме так тихі пропуски перетворюються на фінансові інциденти.

    Проектування переліків статусів, які не можуть брехати

    Бажано використовувати статуси терміналу, які встановлюються лише спеціалізованим вузлом завершення після того, як пройде необхідна перевірка: completed, failed_incomplete, failed_exception. Для інтерфейсів у форматі стрімінгу можуть існувати проміжні статуси, але API, які повідомляють про виконання завдань іншим сервісам, повинні відправляти лише completed після проходження перевірки. Підключіть failed_incomplete до компенсаційного тикету, у якому буде перелічено назви відсутніх вузлів.

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

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

    Навчання без страху

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

    Чек-лист перед тим, як назвати граф «готовим»

    1. Кожен необхідний бізнес-вузол присутній у розділі executed.
    2. Умовні маршрутизатори мають повні гілки, включаючи чітко визначений шлях невдачі.
    3. Жоден вузол не виконує незворотні операції введення/виведення без ключа ідемпотентності, збереженого у стані.
    4. Компіляція проходить успішно та тести доступності також проходять успішно в CI.
    5. Результати виклику містять позначку про завершення, читабельну машиною, окремо від коду HTTP 200.

    Коли ці п’ять умов дотримуються, пропущені рахунки-фактури стають виявленими дефектами замість „тихих“ успіхів. Саме такий рівень функціональності потрібен додаткам LangGraph, коли на кону гроші чи дотримання правил — а не просто зелений журнал компіляції.