ReAct у LangGraph: від ручно створеного циклу до ToolNode та StateGraph
Створіть агента ReAct двічі: спочатку як ручний асинхронний цикл, а потім за допомогою ToolNode від LangGraph, tools_condition та StateGraph для автоматичного оброблення послідовних викликів інструментів.
Джерела: Agentic AI with LangChain — Part 4: ReAct Pattern in LangGraph
Базовий цикл виклику інструментів виконується один раз: модель запитує про інструменти, надходять результати, потім видається відповідь. Завдання, які потребують послідовних викликів, де другий виклик залежить від результату першого, вимагають агента, який може мислити, діяти, оцінювати результат та знову діяти. Цей цикл і є патерном ReAct. Нижче наведено пояснення цієї ідеї, вручну написаний цикл ReAct та той самий агент, перебудований у LangGraph із використанням вузла інструментів, повідомлень про інструменти, умовної дуги та графу станів.
Елементи, які надає LangGraph для цього:
- Вузол інструментів: аналізує виклики інструментів, запускає їх та повертає результати.
- Повідомлення про інструмент: передає результати роботи інструменту назад до LLM.
- tools_condition: функція маршрутизації, яка перевіряє виклики інструментів та керує умовною дугою.
- StateGraph: керує виконанням робочого процесу.
Налаштування
Зберігайте конфіденційну інформацію у файлі .env у корені проекту:
OPENAI_API_KEY="<Your OpenAI API Key>"
TAVILY_API_KEY=<Your TAVILY API Key>
Завантажте їх за допомогою класу Pydantic BaseSettings:
class BaseConfig(BaseSettings):
OPENAI_API_KEY: Optional[str]
PINECONE_API_KEY: Optional[str]
TAVILY_API_KEY: Optional[str]
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
Структура проекту:
project/
├── react_loop/
│ └── weather_assistant.py # implement a vanilla react loop
│
├── react_langgraph # implement ReAct by LangGraph workflow
│ ├── nodes.py # graph nodes. Defined as Python functions
│ ├── graph.py # graph workflow
│ ├── consts.py # define node names as constants
│ └── state.py # typed dictionary to store graph states
|
├── tools
│ ├── tool_call.py # implement simple tool-calling loop
| └── weather.py # get_weather LangChain tool using open source APIs
|
|── # implement tool-calling loop using LangChain tool
├── config.py # pydantic BaseConfig
├── utils.py # utility method to print messages
├── .env # environment variable definition
├── .gitignore
└── requirements.txt # package requirements
Що означає ReAct
ReAct — це скорочення від Reasoning plus Acting. Агент, який виконує багатоетапне завдання, спочатку міркує, потім діє, спостерігає та знову міркує, поки не завершить роботу. Базовий цикл дозволяє лише один запуск інструменту, і саме цю прогалину заповнює ReAct.
Один запуск включає шість кроків:
- Користувач надсилає повідомлення.
- LLM читає запит, визначає, яку інформацію йому потрібно, та вирішує, чи й які інструменти використати.
- Агент запускає один або кілька інструментів, наприклад, шукає інформацію в Інтернеті, здійснює запит до API або надсилає електронний лист.
- Результати повертаються та додаються до поточного стану агента.
- LLM оцінює нову інформацію та або дає відповідь, або просить про додаткові виклики інструментів.
- Як тільки більше дій не потрібно, надсилається остаточна відповідь.
Цей цикл є потужним, оскільки агент може скоригувати свій план відповідно до того, що щойно дізнався, тож кожне рішення може залежати від попередніх результатів.
Простий цикл ReAct
Ця версія знаходиться у файлі react_loop/weather_assistant.py та схожа на цикл одного проходу, але має дві відмінності:
async def chat(self, message: str):
# Add user message
self.messages.append(HumanMessage(content=message))
# loop to handle serial tool calls
while True:
# Get AI response with potential tool calls
response = await self.llm_with_tools.ainvoke(self.messages)
self.messages.append(response)
# Check if there is any tools to call
if not response.tool_calls:
# no tools to call
break
# Execute each tool call
# process tool calls asynchronously
results = await asyncio.gather(*[self.tools[tc["name"]].ainvoke(tc) for tc in response.tool_calls])
self.messages.extend(results)
По-перше, виклики інструментів відбуваються асинхронно за допомогою asyncio.gather, тож кілька з них можуть виконуватися паралельно. По-друге, крок виконання інструментів знаходиться всередині while-циклу, який постійно повторює послідовність «аналіз — дія» до тих пір, поки відповідь моделі більше не міститиме викликів інструментів.
Той самий агент у LangGraph
Ручний цикл працює, але змушує вас самостійно керувати циклом «аналіз — дія», списком повідомлень, словником інструментів та збиранням результатів. LangGraph бере на себе кожен з цих аспектів: ToolNode парсує виклики та виконує інструменти, StateGraph зберігає повідомлення у стані графу, а ребро tools_condition замінює вручну написаний while-цикл та його перевірку викликів інструментів.
Стан. Типований словник зберігає список повідомлень (у react_langgraph/state.py):
from typing import TypedDict, Annotated
from langgraph.graph.message import AnyMessage, add_messages
class ReactState(TypedDict):
"""
Define the state schema for our hotel assistant.
Attributes:
messages: list of messages
"""
messages: Annotated[list[AnyMessage], add_messages]
Вузли читають та оновлюють повідомлення через нього, а функція add_messages редюсер додає все, що повертає вузол.
Вузли. У файлі react_langgraph/nodes.py процес налаштування створює список інструментів LangChain, системний запит, який пояснює моделі, як ними користуватися, та модель OpenAI, прив’язану до цього списку інструментів:
tools = [TavilySearch(max_results=3, tavily_api_key=TAVILY_API_KEY), get_weather]
SYSTEM_PROMPT = f"""You are an assistant for question-answering tasks with access to multiple tools.
use these tools to retrieve context to answer the question. If you don't know the answer,
just say that you don't know. Use three sentences maximum and keep the answer concise.
Tool Use Instructions:
- Use tools thoughtfully—only when they add value to the response
- Chain tools when needed (e.g., search for info, then use that to search
for relevant hotels)
- Be conversational and helpful
"""
system_message = SystemMessage(content=SYSTEM_PROMPT)
llm = ChatOpenAI(api_key=api_key, model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)
async def run_agent_reasoning(state: ReactState) -> dict:
"""
run agent reasoning node
:param state: ReactState, manage graph state
:return: dict containing new message
"""
print("----AGENT REASONING------")
messages = [system_message] + state["messages"]
if isinstance(messages[-1], ToolMessage):
print_message(messages[-1])
response = await llm_with_tools.ainvoke(messages)
print_message(response)
return {"messages": [response]}
tool_node = ToolNode(tools)
Утворюються два вузли. Вузол обробки логіки створює список повідомлень, викликає LLM та повертає його відповідь. Вузол дії є ToolNode, який аналізує виклики інструментів, запускає їх та повертає повідомлення від інструментів.
Граф. У файлі react_langgraph/graph.py StateGraph реєструє вузли та з’єднує їх ребрами:
workflow = StateGraph(ReactState)
workflow.add_node(AGENT_REASON, run_agent_reasoning)
workflow.add_node(ACT, tool_node)
workflow.add_edge(START, AGENT_REASON)
workflow.add_edge(ACT, AGENT_REASON)
workflow.add_conditional_edges(
AGENT_REASON,
tools_condition,
{"tools": ACT, END: END}
)tion is invoked first
Виконання відбувається згідно з чотирма правилами. Граф починається з вузла міркувань. Якщо у відповіді LLM є виклики інструментів, tools_condition спрямовує процес до вузла дії. Після виконання інструментів керування завжди повертається до міркувань. Якщо у відповіді немає викликів інструментів, граф закінчується та повертає остаточну відповідь. Це відповідає циклу ReAct без ручного ланцюга або керованих вручну повідомлень.
Компілюйте та запускайте. Компіляція створює агента, а діаграму графа можна експортувати у форматі PNG:
react_agent = workflow.compile()
react_agent.get_graph().draw_mermaid_png(output_file_path="graphs.png")
question = """
tell me the first hotel you find in Boston if
its temperature is lower than 30 degree."""
async def main():
print("hello ReAct agent by Langraph")
message = HumanMessage(content=question)
res = await react_agent.ainvoke({"messages": [message]})
if __name__ == "__main__":
asyncio.run(main())
Викличте його за допомогою повідомлення від користувача, і отриманий список повідомлень покаже вузол обґрунтування та вузол інструменту зі зміною черговості використання, що є функціональним багатокроковим процесом.
Яке ми маємо зараз
Для виконання цього завдання використовуються три функції LangGraph: ToolNode для пошуку та запуску інструментів, tools_condition для керування циклами та add_messages для автоматичної обробки повідомлень. Після того, як ReAct буде зрозумілим та реалізованим, наступним кроком є повернення до пайплайну LangGraph RAG та зроблення його повністю агентним.