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

Створення вашого першого агента LangGraph: Практичний посібник для початківців

Перший агент LangGraph для початківців: чому агенти мають значення, що додає LangGraph до LangChain, налаштування проекту, а потім стани, вузли, ребра та функціональний цикл з LLM.

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

Джерела: Building Your First LangGraph Agent: A Practical Beginner’s Guide

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

Чому важливі агенти

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

User question → LLM → Answer
User question → Retrieve context → LLM → Answer
User question → Agent decides what to do → Calls tools → Updates state → Produces answer

Що таке LangGraph?

Під час роботи над етапом «Що таке LangGraph» спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки. Створюйте контрольні точки після дорогих операцій. Функція відновлення не повинна знову стягувати плату за той самий виклик LLM, коли оператор намагається знову виконати пізнішу операцію.

Налаштування проекту

Під час виконання етапу налаштування проекту спочатку запишіть умови використання: необхідні дані вхіду, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Якщо якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну ієрархію операцій. Робіть перевірки після дорогих кроків. Система повторного запуску не повинна знову стягувати плату за один і той самий виклик ШІ, коли оператор намагається виконати наступний етап. Під час виконання етапу налаштування проекту спочатку запишіть умови використання: необхідні дані вхіду, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

pip install langgraph langchain langchain-openai langchain-community python-dotenv pydantic
pip install jupyter notebook ipython
OPENAI_API_KEY=your_api_key_here
import os
import json
from typing import Optional, List

from dotenv import load_dotenv
from pydantic import BaseModel

from langgraph.graph import StateGraph, START, END
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain.tools import tool
load_dotenv()
llm = init_chat_model("openai:gpt-4o-mini")

Крок 1: Визначення стану агента

Етап «Визначення стану» найкраще функціонує, якщо його розглядати як вимірювану структуру. Збережіть один ідеальний зразок даних, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Зберігайте стан графа у простій та типованій формі. Вкладені структури даних приховують інформацію про те, який вузол заповнив певне поле, що ускладнює продовження роботи після перерв.

class PlayerState(BaseModel):
    question: str
    selected_tools: Optional[List[str]] = None

    name: Optional[str] = None
    club: Optional[str] = None
    country: Optional[str] = None
    number: Optional[int] = None
    rating: Optional[int] = None

    goals: Optional[List[int]] = None
    minutes_played: Optional[List[int]] = None

    summary: Optional[str] = None

Крок 2: Створення інструментів

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

Інструмент 1: Отримання інформації про гравця

Етап Tool 1 Fetch Player працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування перед розширенням обсягу. Віддавайте перевагу малим, тестованим одиницям перед величезними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій. Використовуйте інструменти з вузькими схемами та чіткими позначеннями побічних ефектів. Хостам потрібно знати, які виклики змінюють стан, перш ніж вони автоматично схвалять їх. Етап Tool 1 Fetch Player працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування перед розширенням обсягу. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

@tool
def fetch_player_information_tool(name: str):
    """
    Returns the football club and country of a player.
    """
    data = {
        "Haaland": {
            "club": "Manchester City",
            "country": "Norway"
        },
        "Kane": {
            "club": "Bayern Munich",
            "country": "England"
        },
        "Lautaro": {
            "club": "Inter Milan",
            "country": "Argentina"
        },
        "Ronaldo": {
            "club": "Al-Nassr",
            "country": "Portugal"
        }
    }

    return data.get(name, {
        "club": "unknown",
        "country": "unknown"
    })
def fetch_player_information(state: PlayerState):
    return fetch_player_information_tool.invoke({
        "name": state.name
    })

Інструмент 2: Отримання номера форми

Для етапу Tool 2 Fetch Jersey необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід тримати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, яке оператори можуть перевірити, не читаючи весь код. Аутентифікуватися потрібно на шлюзі, а повторна авторизація — на рівні обробки даних. Один лише токен-носій не є межею окремого тенантства.

@tool
def fetch_player_jersey_number_tool(name: str):
    """
    Returns the jersey number of a football player.
    """
    data = {
        "Haaland": 9,
        "Kane": 9,
        "Lautaro": 10,
        "Ronaldo": 7
    }

    return {
        "number": data.get(name, 0)
    }
def fetch_player_jersey_number(state: PlayerState):
    return fetch_player_jersey_number_tool.invoke({
        "name": state.name
    })

Інструмент 3: Отримання рейтингу гравця

Для етапу Tool 3 Fetch Player необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Аутентифікація відбувається на шлюзі, а повторна авторизація — на рівні обробки даних. Один лише токен-носій не є межею окремого тенантства.

@tool
def fetch_player_rating_tool(name: str):
    """
    Returns the player's football video game rating.
    """
    data = {
        "Haaland": 92,
        "Kane": 89,
        "Lautaro": 88,
        "Ronaldo": 90
    }

    return {
        "rating": data.get(name, 0)
    }
def fetch_player_rating(state: PlayerState):
    return fetch_player_rating_tool.invoke({
        "name": state.name
    })

Крок 3: Додати вузли отримання даних

На етапі Крок 3 «Додати дані» необхідно визначити вхідні дані, власника цього кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову запустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Краще використовувати невеликі, перевірювані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану структуру процесу. Наводьте ті уривки, які фактично лежать в основі відповіді. Без посилань оператори не зможуть відрізнити галюцинації від проблем з індексуванням. На етапі Крок 3 «Додати дані» необхідно визначити вхідні дані, власника цього кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову запустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-середовищ до спільних.

Отримання цілей

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

def retrieve_goals(state: PlayerState):
    data = {
        "Haaland": [25, 40, 28, 33, 36],
        "Kane": [33, 37, 41, 38, 29],
        "Lautaro": [19, 25, 27, 24, 25],
        "Ronaldo": [27, 32, 28, 30, 36]
    }

    return {
        "goals": data.get(state.name, [0])
    }

Отримання даних про кількість пройдених хвилин

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

def retrieve_minutes_played(state: PlayerState):
    data = {
        "Haaland": [2108, 3102, 3156, 2617, 2758],
        "Kane": [2924, 2850, 3133, 2784, 2680],
        "Lautaro": [2445, 2498, 2519, 2773],
        "Ronaldo": [3001, 2560, 2804, 2487, 2771]
    }

    return {
        "minutes_played": data.get(state.name, [0])
    }

Крок 4: Витягнення імені гравця

Під час виконання кроку 4 «Витягнення даних», спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Віддавайте перевагу невеликим, тестованим одиницям перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій. Робіть контрольні пункти після дорогих кроків. Система повторного запуску не повинна знову стягувати плату за той самий виклик LLM, коли оператор намагається знову виконати пізніший етап. Під час виконання кроку 4 «Витягнення даних», спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних середовищ.

def extract_name(state: PlayerState):
    prompt = f"""
You are a football name extraction assistant.

Extract only the surname of the football player mentioned in the question.

Question:
{state.question}

Return only one word.
"""

    response = llm.invoke([
        HumanMessage(content=prompt)
    ]).content.strip()

    return {
        "name": response
    }
Will Erling Haaland win the World Cup with Norway?
{"name": "Haaland"}

Крок 5: Створення вузла планувальника

Етап створення на кроці 5 працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію поза кодом додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Тримайте стан графа простим та типованим. Вкладені блоки приховують інформацію про те, який вузол записав яке поле, і ускладнюють продовження роботи після перерв.

def planner(state: PlayerState):
    prompt = f"""
You are a football assistant planner.

Available tools:
[
  "fetch_player_jersey_number",
  "fetch_player_information",
  "fetch_player_rating"
]

User question:
{state.question}

Choose which tools are needed to answer the question.

Return only a valid JSON array of tool names.
Example:
["fetch_player_information", "fetch_player_rating"]
"""

    response = llm.invoke([
        HumanMessage(content=prompt)
    ]).content.strip()

    try:
        selected_tools = json.loads(response)
    except Exception:
        selected_tools = []

    return {
        "selected_tools": selected_tools
    }

Крок 6: Написання остаточного підсумку

Етап 6 «Написання коду» функціонує найкраще, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний варіант виконання, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії роботи. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки. Зберігайте стан графа у простому та типованому вигляді. Вкладені структури приховують інформацію про те, який вузол заповнив яке поле, що ускладнює продовження роботи після перерв.

def write_summary(state: PlayerState):
    player_data = {
        "name": state.name,
        "club": state.club,
        "country": state.country,
        "number": state.number,
        "rating": state.rating,
        "goals": state.goals,
        "minutes_played": state.minutes_played
    }

    prompt = f"""
You are a football journalist.

Using the data below, write a concise markdown summary about the player.

Player data:
{json.dumps(player_data, indent=4)}

The summary should include:
- Full player name if it can be inferred
- Country with a flag emoji if available
- Club if available
- Jersey number if available
- Rating if available
- Total goals in the last three seasons
- Average minutes per goal over the available data
- A direct answer to the user's question

User question:
{state.question}
"""

    response = llm.invoke([
        HumanMessage(content=prompt)
    ]).content

    return {
        "summary": response
    }

Етап 7: Створення робочого процесу LangGraph

Етап 7 «Створення сцени» працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу невеликим, тестованим одиницям перед об’ємними скриптами. Коли якийсь етап зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну ієрархію процесів. Зберігайте стан графа у простій та типованій формі. Вкладені структури приховують інформацію про те, який вузол заповнив яке поле, що ускладнює продовження роботи після перерв. Етап 7 «Створення сцени» працює найкраще, якщо його розглядати як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ.

graph_builder = StateGraph(PlayerState)
graph_builder.add_node("extract_name", extract_name)
graph_builder.add_node("planner", planner)

graph_builder.add_node("fetch_player_jersey_number", fetch_player_jersey_number)
graph_builder.add_node("fetch_player_information", fetch_player_information)
graph_builder.add_node("fetch_player_rating", fetch_player_rating)

graph_builder.add_node("retrieve_goals", retrieve_goals)
graph_builder.add_node("retrieve_minutes_played", retrieve_minutes_played)
graph_builder.add_node("write_summary", write_summary)

Етап 8: Додавання країв

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

graph_builder.add_edge(START, "extract_name")
graph_builder.add_edge("extract_name", "planner")
def route_tools(state: PlayerState):
    if state.selected_tools:
        return state.selected_tools

    return ["retrieve_goals"]
graph_builder.add_conditional_edges(
    "planner",
    route_tools,
    {
        "fetch_player_jersey_number": "fetch_player_jersey_number",
        "fetch_player_information": "fetch_player_information",
        "fetch_player_rating": "fetch_player_rating",
        "retrieve_goals": "retrieve_goals"
    }
)
graph_builder.add_edge("fetch_player_jersey_number", "retrieve_goals")
graph_builder.add_edge("fetch_player_information", "retrieve_goals")
graph_builder.add_edge("fetch_player_rating", "retrieve_goals")
graph_builder.add_edge("retrieve_goals", "retrieve_minutes_played")
graph_builder.add_edge("retrieve_minutes_played", "write_summary")
graph_builder.add_edge("write_summary", END)
graph = graph_builder.compile()

Крок 9: Запустити агента

Для кроку 9 «Запустити етап» необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Встановлюйте людське схвалення для операцій, які призводять до витрат грошей або змінюють дані у продакшені. Підключення на етапі компіляції не є гарантією повноти функціоналу продукту.

result = graph.invoke({
    "question": "Will Haaland be able to win the FIFA World Cup for Norway based on his recent performance?"
})
print(result["summary"])
## Erling Haaland Performance Summary

- **Player:** Erling Haaland
- **Country:** Norway 🇳🇴
- **Club:** Manchester City
- **FIFA-style rating:** 92
- **Recent goals:** 28, 33, and 36 in the last three seasons
- **Total goals in last three seasons:** 97
- **Average minutes per goal:** Around 88 minutes per goal

Haaland’s individual numbers are outstanding. His scoring rate, consistency, and elite rating show that he is one of the most dangerous forwards in world football.

However, winning the FIFA World Cup depends on much more than one player. Norway would need strong squad depth, tactical discipline, defensive quality, and consistent performances throughout the tournament.

So, based on recent performance, Haaland can significantly improve Norway’s chances, but a World Cup title would still be difficult to predict from individual statistics alone.
result

Чому цей приклад має значення

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

1. Стан

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

2. Використання інструментів

Під час роботи над етапом 2 «Використання інструментів» спочатку запишіть контракт: необхідні вхідні дані, сигнал успіху та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Одночасно задокументуйте шлях успішного виконання та шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки. Фіксуйте назву інструменту, хеш аргументів, час відгуку та результат кожного виклику. Без цих записів дебагування може займати години.

3. Планування

Під час виконання третього етапу планування спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Якщо якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну послідовність операцій. Робіть перевірки після дорогих кроків. Система не повинна знову стягувати плату за один і той самий виклик LLM, коли оператор намагається знову виконати пізніший етап. Під час виконання третього етапу планування спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ.

4. Умовне маршрутизування

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

5. Остаточні міркування

Можливі покращення

Заключні думки

Дуже дякую за прочитання.

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

Чек-лист для операцій