Суть
Весь 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(), а когда нужен ручной граф (контроль над циклом, кастомные узлы)