[langgraph] Build Agent

一天可以快速过一遍的课程:LangGraph大模型全套教程,从入门到企业级实战

Since: 3 langgraph2 使用LangGraph构建Agent智能体 上

好的 AI Agent 的标准:"仅通过简单沟通,即可把一个复杂的任务交给对方"。

"Through simple communication, you can delegate a complex task to it."

 

1. 构建智能体

构建时考虑到了 tools。

from langgraph.prebuilt import create_react_agent

agent = create_react_agent(
    model=llm,
    tools=[],
    prompt="you are a helpful assistant",
)

 

打印的两种方式:

agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "你是谁?能帮我解决什么问题?",
            }
        ]
    }
)

###########################################################
for chunk in agent.stream( { "messages": [ { "role": "user", "content": "你是谁?能帮我解决什么问题?", } ] }, # stream_mode="messages" ): print(chunk) print("\n")

 

 

2. 工具调用

LangGraph 里工具调用至少有两种风格。

a) 这个是 ReAct agent 的风格。

  • LLM 决定调用什么工具
  • ToolNode 负责执行工具

适用于:

  • 开放式问答
  • 让 Agent 自己判断是否需要工具
  • 工具很多
  • 任务路径不固定

核心代码:

from typing import Annotated, TypedDict
from langchain_core.messages import AnyMessage
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, tools_condition

# State 只负责保存消息历史
class State(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

# 工具 1:查日期
@tool
def get_current_date() -> str:
    """获取今天日期。"""
    return "2026-05-17"

# 工具 2:做乘法
@tool
def multiply(a: int, b: int) -> int:
    """计算两个整数的乘法。"""
    return a * b

tools = [get_current_date, multiply]

------------------------------------------------------
# 关键 1:把工具告诉 LLM # LLM 现在知道自己有两个工具可以选 llm_with_tools = llm.bind_tools(tools)
builder = StateGraph(State)
# 关键 2:LLM 节点 # 它只负责判断: # - 要不要调用工具 # - 调用哪个工具 # - 参数是什么 def agent_node(state: State): ai_msg = llm_with_tools.invoke(state["messages"]) return {"messages": [ai_msg]} builder.add_node("agent", agent_node) # 关键 3:ToolNode # 它只负责执行 LLM 生成的 tool_calls tool_node = ToolNode(tools) builder.add_node("tools", tool_node)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
builder.add_edge(START,
"agent") # 如果 agent 生成了 tool_calls,就去 tools # 如果没有 tool_calls,就结束 builder.add_conditional_edges("agent", tools_condition) # 工具执行完后,回到 agent # 因为 LLM 还要读取工具结果,再生成最终回答 builder.add_edge("tools", "agent")
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
graph
= builder.compile()
#
# 一个更为高级的 add_conditional_edges 的例子~

#
builder.add_node("agent", agent_node) builder.add_node("tools", tool_node) builder.add_node("summary", summary_node) builder.add_edge(START, "agent") builder.add_conditional_edges( "agent", tools_condition, { "tools": "tools", END: "summary", }, ) builder.add_edge("tools", "agent") builder.add_edge("summary", END)

  解释:

agent 执行完后:
如果 tools_condition 返回 "tools"去 tools 节点

如果 tools_condition 返回 END
→ 不是真的结束,而是转去 summary 节点
agent ├── 有 tool_callstools → agent └── 没有 tool_callssummary → END

  

若自定义一个 tools_condition,例如 route_after_agent

def route_after_agent(state):
    last_msg = state["messages"][-1]

    if getattr(last_msg, "tool_calls", None):
        return "tools"

    return "summary"

就会 适配下面的 写法。

builder.add_conditional_edges(
    "agent",
    route_after_agent,
    {
        "tools": "tools",
        "summary": "summary",
    },
)

 

最后补充一个典型的node的实现方式:

def summary_node(state):
    response = llm.invoke(
        [
            {
                "role": "system",
                "content": "请把前面的对话和工具结果总结成一句清晰的中文答案。",
            },
            *state["messages"],
        ]
    )

    return {
        "messages": [response],
        "final_summary": response.content,
    }

 

 

b) 另一种则是:Workflow-driven tool calling。

  • Graph 里的某个 node 固定调用某个工具
  • LLM 不决定工具调用
from typing import TypedDict
from langgraph.graph import StateGraph, START, END


class State(TypedDict):
    city: str
    weather: str
    final_answer: str


# 普通工具函数:查询天气
# 注意:这个工具不是给 LLM 自动选择的
# 它是我们在 weather_node 里手动调用的
def get_weather(city: str) -> str:
    if city == "Sydney":
        return "sunny"
    return "rainy"


# Node 1:程序员决定,这一步就是查天气
def weather_node(state: State):
    weather = get_weather(state["city"])

    return {
        "weather": weather
    }


# Router:根据 state 决定下一步走哪条分支
def route_by_weather(state: State):
    if state["weather"] == "sunny":
        return "sunny"

    return "not_sunny"


# 晴天分支
def outdoor_node(state: State):
    return {
        "final_answer": "今天是晴天,适合出门散步。"
    }


# 非晴天分支
def indoor_node(state: State):
    return {
        "final_answer": "今天不是晴天,建议安排室内活动。"
    }


builder = StateGraph(State)

builder.add_node("weather", weather_node)
builder.add_node("outdoor", outdoor_node)
builder.add_node("indoor", indoor_node)

builder.add_edge(START, "weather")

builder.add_conditional_edges(
    "weather",
    route_by_weather,
    {
        "sunny": "outdoor",
        "not_sunny": "indoor",
    },
)

builder.add_edge("outdoor", END)
builder.add_edge("indoor", END)

graph = builder.compile()

 

 

现在我们设计一个 "综合性" 的例子:

image

 

main.py

from langchain_core.messages import HumanMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.types import Command

from config.load_key import load_key
from graph_builder import SmartPlanGraphBuilder
from services import PlanSaver
from tools import get_weather, multiply


llm = ChatTongyi(
    model="qwen-plus",
    api_key=load_key("BAILIAN_API_KEY"),
)

tools = [
    get_weather,  # ---- ----> 
    multiply,     # ---- ---->
]

graph = SmartPlanGraphBuilder(  # ---->
    llm=llm,
    tools=tools,
    plan_saver=PlanSaver(),
).build()

config = {
    "configurable": {
        "thread_id": "smart-plan-demo-001"
    }
}

first_result = graph.invoke(
    {
        "messages": [
            HumanMessage(
                content=(
                    "我在 Sydney。"
                    "如果天气好,帮我安排一个户外计划;"
                    "另外帮我算一下 23 乘以 17。"
                    "最后生成计划草稿。"
                )
            )
        ],
        "draft_plan": None,
        "approved": None,
        "final_answer": None,
    },
    config=config,
)

if "__interrupt__" in first_result:
    print(first_result["__interrupt__"])

    user_input = input("Approve saving this plan? y/n: ").strip().lower()
    approved = user_input in {"y", "yes"}

    final_result = graph.invoke(
        Command(
            resume={
                "approved": approved
            }
        ),
        config=config,
    )
else:
    final_result = first_result

print(final_result["messages"][-1].content)

 

# tools.py

from
langchain_core.tools import tool @tool def get_weather(city: str) -> str: """ 查询某个城市今天的天气。 Args: city: 城市名称,例如 Sydney。 """ if city.lower() == "sydney": return "sunny" return "rainy" @tool def multiply(a: int, b: int) -> int: """ 计算两个整数的乘法。 Args: a: 第一个整数。 b: 第二个整数。 """ return a * b

 

 

graph_builder.py

先理解下面这段:

# state.py

from typing import Annotated, Optional, TypedDict from langchain_core.messages import AnyMessage from langgraph.graph.message import add_messages class PlanState(TypedDict): """ 整个 graph 共享的状态。 messages: 对话历史。ToolNode 和 LLM 都依赖它。 draft_plan: LLM 最后生成的计划草稿。 approved: 人工是否批准保存。 final_answer: 最终返回给用户的答案。 """
# 哪些数据需要被多个 node 共享、读取、更新,就放进 PlanState messages: Annotated[list[AnyMessage], add_messages] draft_plan: Optional[str] approved: Optional[bool] final_answer: Optional[str]

  也就是,加装好了弹药 ~

builder = StateGraph(PlanState)

  

进入最重要的 “ 构图”过程~

# graph_builder.py

from
langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import END, START, StateGraph from langgraph.prebuilt import ToolNode from nodes import ( ApprovalNode, PlanningAgentNode, PrepareReviewNode, RejectNode, SaveNode, ) from routers import route_after_agent, route_after_approval from state import PlanState class SmartPlanGraphBuilder: """ 负责构建 SmartPlan Agent 的 graph。 这个类的职责很单一: 把 nodes、tools、routers 组装成一个 LangGraph workflow。 """ def __init__(self, llm, tools, plan_saver): self.llm = llm self.tools = tools self.plan_saver = plan_saver def build(self):
# 我要开始搭建一个 LangGraph 流程图,而这个流程图里所有节点共享的数据结构,叫 PlanState builder
= StateGraph(PlanState) # ----------------------------- # 1. 注册节点 # ----------------------------- builder.add_node( "agent", PlanningAgentNode( llm=self.llm, tools=self.tools, ), ) # ToolNode 是模式 A 的核心: # 它负责执行 LLM 产生的 tool_calls。 builder.add_node( "tools", ToolNode(self.tools), ) builder.add_node( "prepare_review", PrepareReviewNode(), ) builder.add_node( "approval", ApprovalNode(), ) builder.add_node( "save", SaveNode(self.plan_saver), # ---- ----> ) builder.add_node( "reject", RejectNode(), ) # ----------------------------- # 2. 注册边 # ----------------------------- builder.add_edge(START, "agent") # agent 后面有两个可能: # 1. LLM 想调用工具 → tools # 2. LLM 已经完成草稿 → prepare_review builder.add_conditional_edges( "agent", route_after_agent, # ---- ----> { "tools": "tools", "prepare_review": "prepare_review", }, ) # 工具执行完后,必须回到 agent。 # 因为 LLM 需要读取 ToolMessage,然后继续生成答案。 builder.add_edge("tools", "agent") builder.add_edge("prepare_review", "approval") # approval 后面有两个可能: # 1. 人工批准 → save # 2. 人工拒绝 → reject builder.add_conditional_edges( "approval", route_after_approval, # ---- ----> { "save": "save", "reject": "reject", }, ) builder.add_edge("save", END) builder.add_edge("reject", END) # demo 用内存 checkpointer。 # 生产环境应该换成 PostgresSaver 等持久化 checkpointer。 checkpointer = InMemorySaver() return builder.compile(checkpointer=checkpointer)

 

# services.py

from
pathlib import Path class PlanSaver: """ 负责保存计划。 注意: 这个类不是给 LLM 自动调用的 tool。 它只会在人工批准之后,由 save_node 调用。 """ def __init__(self, output_path: str = "saved_plan.txt"): self.output_path = Path(output_path) def save(self, plan: str) -> str: self.output_path.write_text(plan, encoding="utf-8") return str(self.output_path.resolve())

 

# routers.py

from
langgraph.graph import END from state import PlanState def route_after_agent(state: PlanState) -> str: """ agent_node 执行完以后,决定下一步。 如果 LLM 生成了 tool_calls: 说明它想调用工具。 走 tools 节点。 如果没有 tool_calls: 说明 LLM 已经生成了计划草稿。 走 prepare_review 节点。 """ last_message = state["messages"][-1] if getattr(last_message, "tool_calls", None): return "tools" return "prepare_review" def route_after_approval(state: PlanState) -> str: """ approval_node 执行完以后,决定下一步。 如果 approved=True: 去 save_node。 如果 approved=False: 去 reject_node。 """ if state.get("approved"): return "save" return "reject"

 

 

节点定义与实现:nodes.py

from langchain_core.messages import AIMessage
from langgraph.types import interrupt
from state import PlanState


class PlanningAgentNode:
    """
    LLM 节点。

    职责:
    1. 读取 messages。
    2. 让 LLM 判断是否需要调用工具。
    3. 如果需要工具,LLM 会生成 tool_calls。
    4. 如果不需要工具,LLM 会生成最终计划草稿。

    注意:
    这个 node 不执行工具。
    工具由 ToolNode 执行。
    """

    def __init__(self, llm, tools):
        self.llm_with_tools = llm.bind_tools(tools)

    def __call__(self, state: PlanState) -> dict:
        system_message = {
            "role": "system",
            "content": (
                "你是一个计划助手。\n"
                "你可以使用工具查询天气,也可以使用工具做数学计算。\n"
                "如果用户要求安排计划,你应该先调用需要的工具。\n"
                "拿到工具结果后,生成一个清晰的中文计划草稿。\n"
                "不要自己保存计划,保存动作需要人工审批。"
            ),
        }

        response = self.llm_with_tools.invoke(
            [system_message, *state["messages"]]
        )

        return {
            "messages": [response]
        }


class PrepareReviewNode:
    """
    审批准备节点。

    职责:
    1. 读取 LLM 最后一条回答。
    2. 把它保存成 draft_plan。
    3. 后面 approval_node 会让人类审批这个 draft_plan。
    """

    def __call__(self, state: PlanState) -> dict:
        last_message = state["messages"][-1]

        return {
            "draft_plan": last_message.content
        }


class ApprovalNode:
    """
    人工审批节点。

    职责:
    1. 暂停 graph。
    2. 把计划草稿展示给人类。
    3. 等人类批准或拒绝。
    """

    def __call__(self, state: PlanState) -> dict:
        human_decision = interrupt(
            {
                "question": "是否批准保存下面这个计划?",
                "draft_plan": state["draft_plan"],
            }
        )

        if isinstance(human_decision, dict):
            approved = bool(human_decision.get("approved", False))
        else:
            approved = bool(human_decision)

        return {
            "approved": approved
        }


class SaveNode:
    """
    保存节点。

    职责:
    只有人工批准后,才真正保存计划。

    这是模式 B:
    程序员明确决定什么时候执行保存动作。
    """

    def __init__(self, plan_saver):
        self.plan_saver = plan_saver

    def __call__(self, state: PlanState) -> dict:
        saved_path = self.plan_saver.save(state["draft_plan"])

        answer = f"计划已保存:{saved_path}"

        return {
            "final_answer": answer,
            "messages": [AIMessage(content=answer)],
        }


class RejectNode:
    """
    拒绝节点。

    职责:
    如果人工没有批准,就不保存。
    """

    def __call__(self, state: PlanState) -> dict:
        answer = "你没有批准保存,所以计划没有被保存。"

        return {
            "final_answer": answer,
            "messages": [AIMessage(content=answer)],
        }

 

  

 

LangChain 重要版本时间线

版本PyPI 首个对应版本日期官方公告/说明日期重点意义
早期 0.0.x 2022–2023 期间持续演进 LangChain 早期快速迭代阶段,版本号长期停留在 0.0.x
langchain 0.1.0 2024-01-06 2024-01-08 第一个 stable release;官方称其为首个稳定版本,强调 backward compatibility、LCEL、streaming 等。
langchain 0.2.0 2024-05-17 2024-05-20 重要架构调整:langchainlangchain-community 解耦,提升稳定性和安全性。
langchain 0.3.0 2024-09-13 2024-09-16/17 Pydantic 2 支持、工具定义与 chat model utilities 改进;这个时期官方也强调 LangGraph 已成为推荐 agent 构建方式。
langchain 1.0.0 2025-10-17 2025-10-20/22 第一个 major stable release;create_agent 成为新的高层 agent 入口,底层构建在 LangGraph runtime 上。
langchain 1.1.0 2025-11-24 2025-12-02 增强 agent 开发能力:model profiles、summarization middleware、structured output、retry middleware、moderation middleware 等。
langchain 1.2.0 2025-12-15 v1 系列后续稳定演进版本。PyPI release history 显示 1.2.0 于 2025-12-15 发布。
langchain 1.3 2026-05-12 支持 stream_events / astream_eventsversion="v3"

 

LangGraph 重要版本时间线

版本PyPI 首个对应版本日期官方公告/说明日期重点意义
LangGraph 初次发布 2024-01-08 随 LangChain v0.1.0 公告一起宣布:LangGraph 作为“用 graph 创建 language agents”的新库出现。
langgraph v0.1 PyPI 0.1.1:2024-06-22 2024-06-27 官方发布 LangGraph v0.1 与 LangGraph Cloud,强调可靠运行 agent、状态化 workflow、可扩展部署。
langgraph 0.2.0 2024-08-07 2024-08-07 加强 customization,引入新的 checkpointer libraries;围绕 session memory、error recovery、human-in-the-loop 等能力。
langgraph 0.3.0 2025-02-26,但该 PyPI 版本被 yanked 2025-02-27 v0.3 的主题是 prebuilt agents;create_react_agent 等高层抽象拆到 langgraph-prebuilt,并引入 Supervisor、Swarm 等预构建 agent 生态。注意 PyPI 上 0.3.0 被 yanked,原因是缺少 langgraph-prebuilt 依赖。
langgraph 1.0.0 2025-10-17 2025-10-22 第一个 major stable release;官方称其为 durable agent framework 的首个稳定大版本,重点是生产级 durable execution、stateful agent runtime。
langgraph v1.1 2026-03-10 引入 opt-in 的 type-safe streaming / type-safe invoke v2,以及 interrupt/subgraph time travel 修复等。
langgraph v1.2 2026-05-12 2026-05-12 增加更细粒度 node execution 控制:timeouts、error recovery、graceful shutdown;新增 DeltaChannel 和 v3 streaming API。

 

posted @ 2026-05-17 18:52  郝壹贰叁  阅读(33)  评论(0)    收藏  举报