Главная / Статьи

Создание вашего первого агента LangGraph: практическое руководство для начинающих

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

Издатель: 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» сначала запишите спецификацию: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Создавайте контрольные точки после дорогостоящих операций. Механизм возобновления работы не должен повторно взимать плату за один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий узел заново.

Настройка проекта

Во время этапа настройки проекта сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Выполняйте контрольные проверки после дорогостоящих шагов. Система возмещения расходов не должна снова взимать плату за один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий этап. Во время этапа настройки проекта сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам.

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: Определение состояния агента

Наиболее эффективным способом выполнения шага 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 «Извлечение данных» сначала запишите условия работы модуля: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность последующих изменений в коде. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое какого-либо шага причина неудачи должна указывать на конкретную ответственность, а не на запутанную структуру обработки данных. Вносите контрольные точки после дорогостоящих шагов. Механизм возобновления работы не должен повторно взимать плату за один и тот же вызов большой языковой модели при повторной попытке обработки последующего элемента. При работе над шагом 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. Планирование

Во время работы над третьим этапом планирования сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Если какой-то шаг не сработает, причина неудачи должна указывать на конкретную ответственность, а не на запутанную структуру обработки данных. Выполняйте проверки после дорогостоящих шагов. Система не должна снова взимать плату за один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий этап. Во время работы над третьим этапом планирования сначала запишите условия работы системы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам.

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

Четвертый этап условного маршрутизирования работает наилучшим образом, если рассматривать его как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма исследования. Храните конфигурацию вне кода приложения: файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код.

5. Заключительные выводы

Возможные улучшения

Заключительные замечания

Большое спасибо за прочтение.

Если вы хотите поддерживать связь или узнать больше о моей работе, вы можете найти меня здесь:

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