[LangChain智能体本质论-03]三种等效的Agent构建方式

我们可以利用LangGraph以的形式编排一个具有复杂交互流程的Agent。create_agent工厂函数内部本质上也是利用的这种方式。由于Agent从执行层面来看就是一个Pregel对象,一个由节点和通道构建而成的Actor模型。综上所述,利用create_agent构建的Agent,其实也可以利用其他两种方式来创建。接下来我们就使用三种不同的编程模式构建一个等效的Agent。

1. 利用create_agent函数创建

我们直接沿用前面演示的天气查询的例子。下面演示的是利用create_agent工厂函数针对Agent的构建。Agent使用的模型是基于gpt-5.2-chatChatOpenAI(相关设置定义在.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最终被被添加到AgentStatemessages列表中。

model节点执行后是直接结束整个流程还是执行tools节点,取决于但会的AIMessage中是否携带ToolCall,后者提供了待执行的工具。这个路由逻辑被定义在path函数中。用于执行工具的tools节点被执行的时候,它会从AgentStatemessages列表中提取最后一个消息(AIMessage),并提取所有的ToolCall,然后从tool_registry中提取对应的工具对象予以执行,执行结构被封装成ToolMessage。所有的ToolMessage合并的列表同样被置于tools函数返回的字典中,最终被添加到AgentStatemessages列表中。简单起见,我们这里采用按顺序同步执行的方式,实际上所有的工具是并发执行的。

两个节点定义好之后,我们将它们添加到创建的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对象由两个节点(modeltools)和三个通道,其中通道messages类型为BinaryOperatorAggregate,用于存储消息列表,通道modeltools则作为对应节点的驱动通道。

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,它会返回针对toolsmessages通道的两个二元组,意味着model执行后除了将生成的AIMessage写入messages通道之外,它还会写入tools通道驱动执行tools节点。否则它只返回针对messages通道的二元组,意味着AIMessage被写入messages通道之后,两个驱动通道都没有变化,整个执行流程结束。

tools节点执行的时候,它会从messages通道中提取消息列表,并从最后一个AIMessage中提取所有的ToolCall,然后从tool_registry中提取对应的工具对象予以执行。所有工具执行的结构被封装成ToolMessage后,统一写入messages通道,然后写入model通道驱动执行model节点。

我们针对定义的节点和通道将Pregel对象创建出来。由于model是入口节点,所以我们将modelmessages作为输入通道,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).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值