ReAct у LangGraph: ад самаробнага цыкла да ToolNode і StateGraph
Будуем ReAct-агента двойчы: спачатку як ручны асінхронны цыкл, потым з ToolNode, tools_condition і StateGraph з LangGraph, якія самі апрацоўваюць ланцужкі выклікаў інструментаў.
Крыніцы: 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>
Завантажыце іх за дапамою класа BaseSettings з Pydantic:
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 плюс 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 і зроблення яго цалкам автономнім.