我们可以利用LangGraph以图的形式编排一个具有复杂交互流程的Agent。create_agent工厂函数内部本质上也是利用的这种方式。由于Agent从执行层面来看就是一个Pregel对象,一个由节点和通道构建而成的Actor模型。综上所述,利用create_agent构建的Agent,其实也可以利用其他两种方式来创建。接下来我们就使用三种不同的编程模式构建一个等效的Agent。
1. 利用create_agent函数创建
我们直接沿用前面演示的天气查询的例子。下面演示的是利用create_agent工厂函数针对Agent的构建。Agent使用的模型是基于gpt-5.2-chat的ChatOpenAI(相关设置定义在.env文件中,利用dotenv加载到环境变量)。注册的工具get_weather用于提供指定城市的天气信息。
from langchain.agents import create_agent
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
load_dotenv()
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_agent(
model= ChatOpenAI(model="gpt-5.2-chat"),
tools=[get_weather],
system_prompt="You are a helpful assistant",
)
message = HumanMessage(content="What is the weather like in Suzhou?")
for step in agent.stream(
input= {"messages": [message]},
stream_mode="values"
):
step["messages"][-1].pretty_print()
执行程序会输出如下所示的交互消息列表(由于temperature默认值为0.7,LLM每次返回的结构具有差异):
================================ Human Message =================================
What is the weather like in Suzhou?
================================== Ai Message ==================================
Tool Calls:
get_weather (call_KOPtXvAlAwnRJ7hZ92WGzeXq)
Call ID: call_KOPtXvAlAwnRJ7hZ92WGzeXq
Args:
city: Suzhou
================================= Tool Message =================================
Name: get_weather
It's always sunny in Suzhou!
================================== Ai Message ==================================
According to the current report, it’s **sunny in Suzhou** right now ☀️
If you’d like, I can also help with:
- Temperature and humidity details
- A short forecast (today or the next few days)
- Travel or clothing suggestions based on the weather
2. 采用LangGraph编程模式
我们采用单纯的LangGraph编程模式改写了上面的例子。我们利用函数定义了代表模型的model节点和用于执行所有工具的tools节点。我们使用AgentState作为全局状态,它同时也作为Agent的输入和输出,我们只用到定义其中的messages字段成员。工具(目前只用到get_weather这个单一工具)被注册到tool_registry表示的字典中,并同时绑定到作为LLM的ChatOpenAI对象上。
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START
from langchain.agents import AgentState
from typing import Literal, cast
load_dotenv()
@tool
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
tool_registry = {"get_weather": get_weather}
llm = ChatOpenAI(model="gpt-5.2-chat").bind_tools(tools=list(tool_registry.values()))
def model(state: AgentState):
return {"messages": [llm.invoke(state["messages"])]}
def tools(state: AgentState):
message: AIMessage = cast(AIMessage, state["messages"][-1])
tool_messages = []
for tool_call in message.tool_calls or []:
tool = tool_registry.get(tool_call["name"])
if tool:
tool_call_id = tool_call["id"]
result = tool.invoke(tool_call)
tool_messages.append(ToolMessage(content=result, tool_call_id=tool_call_id))
return {"messages": tool_messages}
def path(state: AgentState)->Literal["tools", "__end__"]:
message: AIMessage = cast(AIMessage, state["messages"][-1])
return "tools" if message.tool_calls else "__end__"
builder = StateGraph(AgentState)
builder.add_node("model", model)
builder.add_node("tools", tools)
builder.add_edge(START, "model")
builder.add_conditional_edges("model", path)
builder.add_edge("tools", "model")
app = builder.compile()
inputs = {"messages": [HumanMessage(content="What is the weather like in Suzhou?")]}
result = app.invoke(inputs) # type: ignore
for message in result["messages"]:
message.pretty_print()
代表model节点的函数执行的时候会将状态对象AgentState承载的消息列表作为输入调用ChatOpenAI,返回的AIMessage被组装成一个单元素列表被置于model函数返回的字典中,对应的Key为messages,意味着这个AIMessage最终被被添加到AgentState的messages列表中。
model节点执行后是直接结束整个流程还是执行tools节点,取决于但会的AIMessage中是否携带ToolCall,后者提供了待执行的工具。这个路由逻辑被定义在path函数中。用于执行工具的tools节点被执行的时候,它会从AgentState的messages列表中提取最后一个消息(AIMessage),并提取所有的ToolCall,然后从tool_registry中提取对应的工具对象予以执行,执行结构被封装成ToolMessage。所有的ToolMessage合并的列表同样被置于tools函数返回的字典中,最终被添加到AgentState的messages列表中。简单起见,我们这里采用按顺序同步执行的方式,实际上所有的工具是并发执行的。
两个节点定义好之后,我们将它们添加到创建的StateGraph对象中,并将model节点作为入口节点。我们添加了从model节点到tools或者__end__节点的条件边,路由条件为path函数。由于tools节点提供的ToolMessage总是需要提交给model节点处理,我们添加了它们之间的边。在编译StateGraph得到Agent之后,我们按照原来的方式调用它,同样会得到类似的结果:
================================ Human Message =================================
What is the weather like in Suzhou?
================================== Ai Message ==================================
Tool Calls:
get_weather (call_vMO8OQBpQx5Z4e9UkQb7uzjb)
Call ID: call_vMO8OQBpQx5Z4e9UkQb7uzjb
Args:
city: Suzhou
================================= Tool Message =================================
content="It's always sunny in Suzhou!" name='get_weather' tool_call_id='call_vMO8OQBpQx5Z4e9UkQb7uzjb'
================================== Ai Message ==================================
Right now in **Suzhou**, the weather is **sunny** ☀️
If you’d like, I can also tell you the **temperature**, **forecast for the next few days**, or compare it with another city.
3. 直接创建Pregel对象
如下的程序演示了直接定义作为Agent的Pregel对象。整个Pregel对象由两个节点(model和tools)和三个通道,其中通道messages类型为BinaryOperatorAggregate,用于存储消息列表,通道model和tools则作为对应节点的驱动通道。
from typing import Any, Sequence
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
from langgraph.pregel import Pregel, NodeBuilder
from langgraph.channels import BinaryOperatorAggregate, LastValue
from langgraph.pregel._write import ChannelWrite, ChannelWriteTupleEntry
from dotenv import load_dotenv
import operator
load_dotenv()
@tool
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
tool_registry = {"get_weather": get_weather}
llm = ChatOpenAI(model="gpt-5.2-chat").bind_tools(list(tool_registry.values()))
# Model node: Call LLM and return an AIMessage.
# If there are tool calls, also write to the "tools" channel to trigger tool execution.
# otherwise, write the AIMessage to "messages" to end the loop.
model_node = (NodeBuilder()
.subscribe_to("model")
.read_from("messages")
.do(lambda state: {"messages": [llm.invoke(state["messages"])]})
.build())
def map(state: dict)->Sequence[tuple[str, Any]]:
messages = state["messages"]
message: AIMessage = messages[-1]
if message.tool_calls:
return [("tools", None),("messages",messages)]
else:
return [("messages",[message])]
tuple_entry = ChannelWriteTupleEntry(mapper= map)
model_node.writers.append(ChannelWrite([tuple_entry]))
# Tools node: Listen to "tools" channel, execute the tool calls, and write results back to "messages" channel.
# Write the model channel to trigger the model to generate the next message based on tool results.
def invoke_tools(state: dict):
message: AIMessage = state["messages"][-1]
tool_messages = []
for tool_call in message.tool_calls or []:
tool = tool_registry.get(tool_call["name"])
if tool:
result = tool.invoke(tool_call)
tool_messages.append(ToolMessage(content=result, tool_call_id=tool_call["id"]))
return tool_messages
tools_node =(NodeBuilder()
.subscribe_to("tools")
.read_from("messages")
.do(invoke_tools)
.write_to("messages", model=None))
app = Pregel(
nodes={
"model": model_node,
"tools": tools_node,
},
channels={
"messages": BinaryOperatorAggregate(list, operator.add),
"tools": LastValue(None),
"model": LastValue(None),
},
input_channels=["model", "messages"],
output_channels=["messages"],
stream_channels=["messages"],
)
inputs = {
"messages": [HumanMessage(content="What is the weather like in Suzhou?")],
"model": None}
result = app.invoke(inputs) # type: ignore
for message in result["messages"]:
message.pretty_print()
利用NodeBuilder构建的model节点会订阅model驱动通道,并从输入通道messages中读取消息列表。当它执行的时候会调用作为LLM的ChatOpenAI(预先绑定了注册的工具)。由于只有返回的AIMessage携带ToolCall的前提下它才会写入tools节点的驱动通道,所以我们将这个动态的逻辑定义在map函数中,该函数返回的二元组列表被用于创建ChannelWriteTupleEntry,后者用于通道的写入。
map函数会从messages通道中提取消息列表,如果最后一个AIMessage包含ToolCall,它会返回针对tools和messages通道的两个二元组,意味着model执行后除了将生成的AIMessage写入messages通道之外,它还会写入tools通道驱动执行tools节点。否则它只返回针对messages通道的二元组,意味着AIMessage被写入messages通道之后,两个驱动通道都没有变化,整个执行流程结束。
tools节点执行的时候,它会从messages通道中提取消息列表,并从最后一个AIMessage中提取所有的ToolCall,然后从tool_registry中提取对应的工具对象予以执行。所有工具执行的结构被封装成ToolMessage后,统一写入messages通道,然后写入model通道驱动执行model节点。
我们针对定义的节点和通道将Pregel对象创建出来。由于model是入口节点,所以我们将model和messages作为输入通道,messages作为输出通道返回消息列表。按照相同的方式执行Pregel对象后,我们同样会得到类似的输出:
================================ Human Message =================================
What is the weather like in Suzhou?
================================== Ai Message ==================================
Tool Calls:
get_weather (call_m9LSf5KY1nCmwsJHoILqRjZs)
Call ID: call_m9LSf5KY1nCmwsJHoILqRjZs
Args:
city: Suzhou
================================= Tool Message =================================
content="It's always sunny in Suzhou!" name='get_weather' tool_call_id='call_m9LSf5KY1nCmwsJHoILqRjZs'
================================== Ai Message ==================================
The current weather in **Suzhou** is **sunny** ☀️.
If you’d like, I can also tell you the temperature, forecast for the next few days, or weather for a specific Suzhou (there’s one in Jiangsu, China, and another in Anhui).

1708

被折叠的 条评论
为什么被折叠?



