第一篇我们打印过 create_agent 的内部结构:两个节点 model 和 tools,一条条件边,一条回边。经过前三篇,State、Reducer、条件边、循环都已经讲过,这张图里已经没有陌生的部件了。这一篇就亲手把它搭出来——不是为了替代 create_agent,而是为了在需要定制时知道该改哪里。后半篇转向工程问题:节点怎么拿到"这一次调用"的配置(比如当前用户是谁),以及怎样让调用外部服务的节点更经得起折腾。

一、先拆解:Agent 循环需要哪些部件#

回顾 LangChain 系列第五篇讲过的工具调用流程:模型并不会自己执行工具,它只是返回一条"工具调用指令",由代码去执行,再把结果交回给模型。把这个流程按第一篇的方法拆一遍:

拆步骤:调用模型、执行工具,两步。

定数据:整个过程就是一串消息——用户的问题、模型的回复(可能带工具调用指令)、工具的执行结果……所以 State 只需要一个消息列表,而且要用上一篇讲的 add_messages 让它不断追加。现成的 MessagesState 正好合适。

连线:调用模型之后要做一个判断——回复里有工具调用,就去执行工具;没有,说明模型已经给出了最终答案,结束。执行完工具,总是回到模型。

flowchart LR
  S([开始]) --> M[model:调用模型]
  M -.->|有工具调用| T[tools:执行工具]
  T --> M
  M -.->|没有工具调用| E([结束])

三个部件都有现成的积木:State 用 MessagesState,"执行工具"用 LangGraph 提供的 ToolNode,"有没有工具调用"的判断用 tools_condition。

二、动手搭建#

2.1 模型节点#

模型节点做的事很简单:把消息历史交给绑定了工具的模型,把它的回复作为新消息交回去。

Python
from langchain.chat_models import init_chat_model
from langgraph.graph import MessagesState

model = init_chat_model("openai:gpt-4o-mini").bind_tools([get_weather])

def call_model(state: MessagesState):
    reply = model.invoke(state["messages"])
    return {"messages": [reply]}

bind_tools 把工具的说明一起发给模型,模型才知道有哪些工具可以用。get_weather 就是用 @tool 定义的普通工具,和 LangChain 系列里的写法完全一样。

2.2 工具节点:ToolNode#

工具节点要做的事情稍多:从最后一条 AI 消息里取出工具调用指令,找到对应的工具执行,再把结果包装成 ToolMessage(带上对应的调用 ID,模型才知道哪个结果对应哪次调用)。如果模型一次要求调用多个工具,还应该并行执行。

这些琐碎的工作,ToolNode 已经全部做好:

Python