1. ToolNode 是什么?
ToolNode 是 LangGraph 提供的预构建节点类,用来执行 LLM 返回的 tool_calls。
典型流程:
scss
HumanMessage
↓
LLM
↓
AIMessage(tool_calls)
↓
ToolNode
↓
ToolMessage
↓
LLM
↓
最终 AIMessage
关键分工:
- LLM:决定是否需要调用工具,以及调用哪个工具、传什么参数。
- ToolNode:真正执行 Python 工具函数。
- ToolMessage:把工具执行结果放回消息历史。
- Router / conditional edge:决定下一步去 ToolNode 还是 END。
bind_tools()只是把工具说明绑定给模型,并不会自动执行 Python 函数。
2. Agent Loop 的核心逻辑
经典 Tool Calling Agent:
sql
START
↓
Agent
↓
有 tool_calls?
├─ Yes → ToolNode → Agent
└─ No → END
这里的 END 是:
模型已经完成这一次最终回答,工作流没有下一步了。
例如用户要求"算 123 + 456,再讲个笑话":
- 第一次 LLM 调用
add(123, 456)。 - ToolNode 返回
579。 - 再次进入 LLM。
- LLM 读取工具结果,输出"579",并继续完成"讲笑话"这个要求。
- 这一次没有
tool_calls,于是进入 END。
二级结论:工具是 LLM 获取外部信息或执行动作的手段,最终自然语言回答本身仍然是 LLM 的工作。
3. return {"messages": [response]} 的意义
如果节点中:
arduino
response = model.invoke(...)
return {"messages": [response]}
它的意思是:
把本次 LLM 返回的
AIMessage作为messages字段的更新结果交给 LangGraph。
response 是单条消息,而 messages 是消息序列,因此要写:
vbscript
{"messages": [response]}
而不是:
vbscript
{"messages": response}
LangGraph 的节点不一定要返回完整 State,可以只返回需要更新的字段。
如果 State 定义为:
kotlin
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
那么 add_messages 会负责把新消息合并进旧消息历史,而不是简单覆盖。
4. Annotated 与 Sequence
Sequence
css
Sequence[BaseMessage]
表示"一个按顺序排列、元素类型为 BaseMessage 的序列"。
它比 list 更抽象,可以表示 list、tuple 等序列类型。
但如果代码里明确需要列表操作,或者写:
css
[system_prompt] + state["messages"]
那么直接使用:
css
list[BaseMessage]
会更直观,也避免 list + tuple 这样的类型问题。
Annotated
css
Annotated[类型, 附加信息]
用于给一个类型附加额外元数据。
LangGraph 中:
ini
messages: Annotated[list[BaseMessage], add_messages]
可以拆成:
list[BaseMessage]:这个字段的数据类型。add_messages:这个字段收到新值时的合并规则,也就是 reducer。
二级结论:类型告诉 LangGraph"这里装什么",reducer 告诉 LangGraph"新旧值怎么合并"。
5. BaseMessage 及常见消息类型
BaseMessage 是消息类型的共同基类。
常见类型:
HumanMessage:用户消息。AIMessage:模型消息。SystemMessage:系统提示词。ToolMessage:工具执行结果。
因此用户输入应该写成:
ini
HumanMessage(content=user_input)
而不是直接实例化 BaseMessage。
6. Tool 定义时最好标注参数类型
推荐:
python
@tool
def add(a: float, b: float) -> float:
"""计算两个数字的和。"""
return a + b
参数类型和 docstring 都会帮助模型理解工具 schema,例如:
- 工具叫什么。
- 参数有哪些。
- 参数是什么类型。
- 什么时候应该调用这个工具。
7. 一个完整的 Tool Calling Agent 骨架
python
from typing import TypedDict, Annotated
from dotenv import load_dotenv
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_mistralai import ChatMistralAI
load_dotenv()
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@tool
def add(a: float, b: float) -> float:
"""计算两个数字的和。"""
return a + b
@tool
def subtract(a: float, b: float) -> float:
"""计算两个数字的差。"""
return a - b
tools = [add, subtract]
model = ChatMistralAI(
model="ministral-3b-2512"
).bind_tools(tools)
def model_call(state: AgentState):
system_prompt = SystemMessage(
content="你是一个专业处理数学计算问题的工程师,请尽你最大的能力回答我的要求。"
)
response = model.invoke(
[system_prompt] + state["messages"]
)
return {
"messages": [response]
}
def route_tools(state: AgentState):
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return "end"
tool_node = ToolNode(tools)
graph = StateGraph(AgentState)
graph.add_node("agent", model_call)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges(
"agent",
route_tools,
{
"tools": "tools",
"end": END
}
)
graph.add_edge("tools", "agent")
agent = graph.compile()
9. 如何调用 Agent
单轮调用:
ini
user_input = input("Enter: ")
result = agent.invoke({
"messages": [
HumanMessage(content=user_input)
]
})
print(result["messages"][-1].content)
10. 常见错误:{HumanMessage(...)}
错误:
css
"messages": {HumanMessage(content=user_input)}
这里的 {...} 在 Python 中表示 set,不是 list。
set 中的元素必须可哈希,而 HumanMessage 不是可哈希对象,因此会报:
bash
TypeError: unhashable type: 'HumanMessage'
正确写法:
json
"messages": [HumanMessage(content=user_input)]
记忆:
csharp
[x] # list
{x} # set
11. 最终职责图
sql
AgentState
│
├─ messages
│ ├─ HumanMessage
│ ├─ AIMessage
│ └─ ToolMessage
│
↓
Agent / LLM 决策下一步
↓
route_tools 负责路由
↓
ToolNode 执行 Python 工具
↓
add_messages 合并消息历史
↓
Agent
↓
END
总结
LangGraph Tool Calling Agent 的核心,就是让 LLM 负责"决定",ToolNode 负责"执行",State 负责"保存上下文",conditional edge 负责"控制流程"。