LangGraph ReAct Loop

Минимальный агентный цикл на LangGraph: узел agent (LLM с привязанными инструментами) и ToolNode замкнуты условным ребром. Маршрутизатор смотрит на последнее сообщение: есть tool_calls → идём в инструменты и возвращаемся в agent; нет → END. Это ReAct, выраженный как граф.

Суть

Весь ReAct-цикл «думай → вызови инструмент → наблюдай → думай» сводится к двум узлам и одному условному ребру. LLM сама решает, нужен ли инструмент (через tool_calls); граф направляет поток и возвращает результат инструмента обратно в LLM до тех пор, пока та не ответит без вызова.

Зачем это нужно

Это «hello world» LangGraph и одновременно скелет любого инструментального агента: показывает, как tool_calls от модели превращаются в исполнение через ToolNode и как замыкается цикл. Высокоуровневая create_agent() из LangChain делает по сути то же самое — и сама написана на LangGraph (см. LangGraph vs LangChain).

Как работает

  • State: messages: Annotated[list, add_messages] — история накапливается (LangGraph Reducers).
  • Узел agent: добавляет system prompt, вызывает model.invoke(messages), возвращает ответ в messages.
  • model.bind_tools([...]): модель получает схемы инструментов и может вернуть tool_calls.
  • ToolNode([...]): готовый узел, исполняющий запрошенные инструменты.
  • Условный router: если у последнего сообщения есть tool_calls"tools", иначе → END.
  • Рёбра цикла: agent → (router) → tools → agent.

Пример

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

model = ChatOpenAI(model=MODEL, base_url=BASE_URL).bind_tools([pay])

def agent(state): 
    return {"messages": [model.invoke([SystemMessage(SYSTEM_PROMPT)] + state["messages"])]}

def router(state):
    last = state["messages"][-1]
    return "tools" if getattr(last, "tool_calls", None) else END

builder = StateGraph(State)
builder.add_node("agent", agent); builder.add_node("tools", ToolNode([pay]))
builder.set_entry_point("agent")
builder.add_conditional_edges("agent", router)
builder.add_edge("tools", "agent")          # замыкаем цикл
app = builder.compile()

Связано с

  • ReAct — паттерн, который этот граф реализует
  • Tool Calling — bind_tools/tool_calls/ToolNode = механика вызова инструментов
  • LangGraph Nodes and Edges — цикл = условное ребро + ребро возврата
  • LangGraph — базовый строительный блок инструментального агента

Открытые вопросы

  • когда хватает create_agent(), а когда нужен ручной граф (контроль над циклом, кастомные узлы)