Part 1 · 起步

03 手搭一个会调用工具的客服助手

03.1 我们最终做成什么

做一个客服助手:能聊天、能查天气、能把用户信息存库。

做完你就能看清一张图的骨架:节点、边、状态。后面所有复杂系统都是这个骨架长出来的。

03.2 定义状态:图里的"共享白板"

from typing import Annotated, TypedDict
from langchain_core.messages import AnyMessage
import operator

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]

operator.add 是"归约器"(reducer)。新消息加到旧列表后面,不是覆盖。这点很关键。

03.3 搭节点和边

from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.messages import HumanMessage

tools = [get_weather, save_user_info]
tool_node = ToolNode(tools)
llm_with_tools = llm.bind_tools(tools)

def call_model(state):
    resp = llm_with_tools.invoke(state["messages"])
    return {"messages": [resp]}

g = StateGraph(AgentState)
g.add_node("agent", call_model)   # AI 决策节点
g.add_node("tools", tool_node)    # 工具执行节点
g.add_edge(START, "agent")        # 入口
g.add_conditional_edges("agent", tools_condition, {"tools": "tools", END: END})
g.add_edge("tools", "agent")      # 工具跑完回到 AI 总结
graph = g.compile()

tools_condition 是 LangGraph 预置的路由函数。它看最后一条消息有没有 tool_calls,有就走 tools,没有就 END

03.4 跑一下

out = graph.invoke(
    {"messages": [HumanMessage(content="北京今天天气怎么样?")]},
    {"recursion_limit": 10},
)
print(out["messages"][-1].content)

预期结果:Agent 调 get_weather,再总结成一句人话。

注意:一定要设递归上限 没设 recursion_limit,AI 一旦陷进"调工具—不满意—再调"的循环,进程就卡死。我默认设 10。

03.5 回顾

我们从零搭了一张带循环的真图。你看到了图的三个核心零件:节点(算什么)、边(走哪条)、状态(存什么)。

核心建议:先手搭,再偷懒 手搭一遍,你就懂 create_react_agent 内部在干嘛。之后用预置的,心里才有底。

▸ 向前桥接 这张图还很简单。下一章,我把"节点、边、状态"拆开讲透。