[langgraph] Why Is It
必要性
Agent ≠ prompt
Agent = 系统
而系统一定需要:
-
- execution(LangGraph)
- observability(LangSmith)
- deployment(LangSmith)
- iteration loop(LangSmith)
----> 该上 LangGraph,如果你开始出现这些需求:
-
-
我需要明确状态 State
-
我需要多个节点和分支
-
我需要失败后恢复
-
我需要长时间运行
-
我需要人工审批
-
我需要把多个 agent 组装成系统
-
我不想被高层默认逻辑束缚
-
我需要可控的 orchestration
-
这些正是 LangGraph 官方主打能力。
----> 给你两个最清晰的对比例子
场景 1:客服 FAQ + 查订单
用户问:“我的订单到哪了?如果延迟了就帮我解释一下。”
这类通常是:
-
-
查订单 API
-
拼接解释
-
可能再调一个退款政策 tool
-
LangChain 更合适。
因为核心目标是快速构建,流程不复杂。
场景 2:企业级故障排查 agent
任务:
-
先读取告警
-
再读日志
-
判断是网络/数据库/应用层问题
-
如果证据不足,走额外诊断分支
-
如果风险高,暂停并通知值班工程师
-
等人工确认后继续
-
最后生成 RCA 摘要
-
全流程需要 trace、state、checkpoint
-
LangGraph 更合适。
因为这已经不是“一个 agent 调几个 tool”的问题了,而是一个真正的状态化 workflow。
----> 最后帮你压成一句最容易记住的话
LangChain 不是“不适合 agent”,而是更适合“先把 agent 做出来”。
LangGraph 不是“替代 LangChain”,而是更适合“把 agent 做成可控、可恢复、可上线的系统”。
实践出真知
一天可以快速过一遍的课程:LangGraph大模型全套教程,从入门到企业级实战
其他参考:2026吃透LangGraph全套实战教程,15天学完,让你少走99%弯路【通俗易懂,附课件代码资料】
LangChain的写法
import datetime from langchain.tools import tool from config.load_key import load_key from langchain_community.chat_models import ChatTongyi # 构建阿里云百炼大模型客户端 llm = ChatTongyi( model="qwen-plus", api_key=load_key("BAILIAN_API_KEY"), ) # 定义工具 注意要添加注释 @tool def get_current_date(): """获取今天日期""" return datetime.datetime.today().strftime("%Y-%m-%d") # 大模型绑定工具 llm_with_tools = llm.bind_tools([get_current_date]) # 工具容器 all_tools = { "get_current_date": get_current_date } # 把所有消息存到一起 query = "今天是几月几号" messages = [query] # 询问大模型。大模型会判断需要调用工具,并返回一个工具调用请求 ai_msg = llm_with_tools.invoke(messages) # 无法给出直接的答案,但会选择调用工具
print(ai_msg) # this is a dict messages.append(ai_msg) # 打印需要调用的工具 print(ai_msg.tool_calls) if ai_msg.tool_calls: for tool_call in ai_msg.tool_calls: # 遍历 llm 认为需要的工具集 selected_tool = all_tools[tool_call["name"].lower()] tool_msg = selected_tool.invoke(tool_call) ----> 理解如下 messages.append(tool_msg) ----> 这本身就是ReAct的过程!! # 再次调用大模型 result = llm_with_tools.invoke(messages).content print(result)
StructuredTool.invoke(tool_call)??
可以把它理解成:
class StructuredTool: def invoke(self, tool_call): args = tool_call["args"] result = self.func(**args) return ToolMessage( content=str(result), tool_call_id=tool_call["id"] )
结合下面,也就理解了。
content='' additional_kwargs={ 'tool_calls': [ { 'index': 0, 'id': 'call_45f2c0e22294476bb7783d', 'type': 'function', 'function': { 'name': 'get_current_date', 'arguments': '{}' } } ] } tool_calls=[ { 'name': 'get_current_date', 'args': {}, 'id': 'call_45f2c0e22294476bb7783d', 'type': 'tool_call' } ]
LangGraph的写法
# 用 LangGraph 创建 ReAct Agent agent = create_react_agent( model=llm, tools=[get_current_date], prompt="You are a helpful assistant", ) # 调用 Agent result = agent.invoke( { "messages": [ { "role": "user", "content": "今天是几月几号", } ] } )
好吧。这里像极了之前的MCP学习过程中的实现方式。
建议正式项目:MCP Server + LangGraph。
""" mcp_server.py 这是 MCP Server,也就是“工具服务层”。 它的职责: 1. 定义工具 2. 通过 MCP 协议暴露工具 3. 让外部 Agent client 可以发现和调用这些工具 注意: - 这里没有 LLM - 这里没有 LangGraph - 这里没有 Agent loop - 这里不决定什么时候调用工具 这就是“工具层标准化”的核心。 """ import datetime from pathlib import Path from mcp.server.fastmcp import FastMCP mcp = FastMCP("date-demo-server") @mcp.tool() def get_current_date() -> str: """ 获取今天日期。 Returns: str: 今天日期,格式为 YYYY-MM-DD。 """ return datetime.datetime.today().strftime("%Y-%m-%d") @mcp.tool() def save_user_note(note: str) -> str: """ 把一条 note 保存到本地 demo_notes.txt 文件。 Args: note: 要保存的文本内容。 Returns: str: 保存结果。 这个工具是故意放进来的。 因为它有“写入副作用”,非常适合演示第 4 层里的 human approval。 """ output_path = Path("demo_notes.txt") with output_path.open("a", encoding="utf-8") as f: f.write(note + "\n") return f"Saved note to {output_path.resolve()}" if __name__ == "__main__": # stdio 模式: # client 会把这个 server.py 当成子进程启动。 mcp.run(transport="stdio")
""" client.py 这个文件是 Agent Client。 它的职责是: 1. 创建大模型 llm 2. 通过 MCP Client 连接 server.py 3. 从 MCP Server 加载 tools 4. 把 tools 交给 LangChain / LangGraph Agent 5. 让 Agent 自动完成: LLM 判断 -> tool call -> 执行工具 -> observation -> LLM 最终回答 和你之前手写版本最大的区别: 之前: 你自己维护 all_tools 你自己判断 ai_msg.tool_calls 你自己 selected_tool.invoke(...) 你自己 messages.append(tool_msg) 你自己再次 llm.invoke(...) 现在: MCP Server 管工具暴露 MultiServerMCPClient 管工具加载 create_agent / LangGraph runtime 管 ReAct 循环 """ import asyncio import sys from pathlib import Path from config.load_key import load_key from langchain_community.chat_models import ChatTongyi from langchain_mcp_adapters.client importMultiServerMCPClient from langchain.agents import create_agent async def main() -> None: """ 主流程: 1. 创建 LLM 2. 创建 MCP client 3. 加载 MCP tools 4. 创建 Agent 5. invoke Agent """ # ------------------------------------------------------------ # 1. 构建阿里云百炼大模型客户端 # ------------------------------------------------------------ llm = ChatTongyi( model="qwen-plus", api_key=load_key("BAILIAN_API_KEY"), ) # ------------------------------------------------------------ # 2. 找到 server.py 的绝对路径 # ------------------------------------------------------------ current_dir = Path(__file__).resolve().parent server_path = current_dir / "server.py" # ------------------------------------------------------------ # 3. 创建 MCP Client # ------------------------------------------------------------ # 这里的 date_server 是你给这个 MCP server 起的名字。 # # transport="stdio": # 表示 client.py 会自动启动 server.py 子进程。 # # command=sys.executable: # 表示使用当前 Python 环境运行 server.py。 # 这样比直接写 "python" 更稳,因为它能确保使用同一个 venv。 # # args=[str(server_path)]: # 表示要启动的 MCP Server 文件。 # ------------------------------------------------------------ mcp_client = MultiServerMCPClient( { "date_server": { "transport": "stdio", "command": sys.executable, "args": [str(server_path)], } } ) # ------------------------------------------------------------ # 4. 从 MCP Server 加载工具 # ------------------------------------------------------------ # 这一步之后,server.py 里的 get_current_date 会变成 # LangChain / LangGraph Agent 可以调用的 tool。 # ------------------------------------------------------------ tools = await mcp_client.get_tools() print("Loaded MCP tools:") for tool in tools: print(f"- {tool.name}: {tool.description}") # ------------------------------------------------------------ # 5. 创建 Agent # ------------------------------------------------------------ # 这里的 create_agent 是当前 LangChain 官方推荐的高级 agent 创建方式。 # 它内部会处理类似 ReAct 的循环: # # User Message # -> LLM # -> tool_calls # -> MCP tool execution # -> ToolMessage / Observation # -> LLM # -> Final Answer # # 所以你不需要再手写: # if ai_msg.tool_calls: # selected_tool.invoke(...) # messages.append(tool_msg) # ------------------------------------------------------------ agent = create_agent( model=llm, tools=tools, system_prompt=( "You are a helpful assistant. " "When the user asks about today's date, use the available date tool." ), ) # ------------------------------------------------------------ # 6. 调用 Agent # ------------------------------------------------------------ result = await agent.ainvoke( { "messages": [ { "role": "user", "content": "今天是几月几号?", } ] } ) # ------------------------------------------------------------ # 7. 打印完整 message 流,方便你观察 ReAct 过程 # ------------------------------------------------------------ print("\n========== Full Agent Messages ==========") for message in result["messages"]: print(message) print("----------------------------------------") # ------------------------------------------------------------ # 8. 打印最终答案 # ------------------------------------------------------------ final_message = result["messages"][-1] print("\n========== Final Answer ==========") print(final_message.content) if __name__ == "__main__": asyncio.run(main())
版本一:第 3 层 MCP Server + LangGraph ReAct Agent
""" layer3_client_react_agent.py 第 3 层示范: MCP Server + LangGraph prebuilt ReAct Agent 这个版本的目标: 1. 工具不再写在 client 里,而是来自 MCP Server 2. LangGraph 的 create_react_agent 负责 ReAct loop 3. 你不再手写 if ai_msg.tool_calls 4. 你不再手写 selected_tool.invoke(...) 5. 你不再手写 messages.append(tool_msg) 这就是: 工具层标准化 + agent runtime 工程化 """ import asyncio import sys from pathlib import Path from config.load_key import load_key from langchain_community.chat_models import ChatTongyi from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent async def main() -> None: # ------------------------------------------------------------ # 1. 创建 LLM # ------------------------------------------------------------ llm = ChatTongyi( model="qwen-plus", api_key=load_key("BAILIAN_API_KEY"), ) # ------------------------------------------------------------ # 2. 定位 MCP Server # ------------------------------------------------------------ current_dir = Path(__file__).resolve().parent server_path = current_dir / "mcp_server.py" # ------------------------------------------------------------ # 3. 创建 MCP Client # ------------------------------------------------------------ # MultiServerMCPClient 可以连接多个 MCP Server。 # # 这里我们只连接一个: # date_demo_server # # transport="stdio": # client 会自动启动 mcp_server.py 子进程。 # ------------------------------------------------------------ mcp_client = MultiServerMCPClient( { "date_demo_server": { "transport": "stdio", "command": sys.executable, "args": [str(server_path)], } } ) # ------------------------------------------------------------ # 4. 从 MCP Server 加载 tools # ------------------------------------------------------------ # 这一步是第 3 层的关键: # # 原来: # tools = [get_current_date] # # 现在: # tools = await mcp_client.get_tools() # # 工具来源变了。 # 工具从“本地函数”变成了“标准化 MCP 工具服务”。 # ------------------------------------------------------------ tools = await mcp_client.get_tools() print("Loaded MCP tools:") for tool in tools: print(f"- {tool.name}: {tool.description}") # ------------------------------------------------------------ # 5. 创建 LangGraph ReAct Agent # ------------------------------------------------------------ # 这个 agent 内部会自动执行: # # LLM -> tool_calls -> Tool execution -> ToolMessage -> LLM # # 这正是你原来手写的 ReAct loop。 # ------------------------------------------------------------ agent = create_react_agent( model=llm, tools=tools, prompt=( "You are a helpful assistant. " "When the user asks today's date, call get_current_date. " "Answer in Chinese." ), ) # ------------------------------------------------------------ # 6. 调用 Agent # ------------------------------------------------------------ result = await agent.ainvoke( { "messages": [ { "role": "user", "content": "今天是几月几号?", } ] } ) # ------------------------------------------------------------ # 7. 打印完整消息流,方便你观察 ReAct 过程 # ------------------------------------------------------------ print("\n========== Full Messages ==========") for msg in result["messages"]: print(msg) print("-----------------------------------") print("\n========== Final Answer ==========") print(result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main())
版本二:第 4 层 MCP Server + 自定义 StateGraph
自定义:
- State
- Node
- Edge
- Conditional Edge
- Checkpoint
- Human Approval
- Memory
创造一个“节点通过共享 state 通信”的图;每个 node 读入当前 state,并返回一部分 state update。

注意的知识点:
graph.ainvoke(...)启动的是整个 graph。
它默认不会在每个 node 结束后返回。
它只会在两种情况下返回:第一,整个 graph 跑到END;第二,中途遇到interrupt()。
所以 first_result 之所以“偏偏”在 approval_node 返回,不是因为 approval_node 是特殊节点,而是因为:approval_node 里面调用了 interrupt(...)
而前面的 date_agent_node、writer_agent_node 都只是普通 return,没有 interrupt。
""" layer4_client_custom_stategraph.py 第 4 层示范: MCP Server + 自定义 LangGraph StateGraph + 多节点 workflow + checkpoint / short-term memory + human approval 这个版本的重点不是“代码更短”,而是“控制力更强”。 你不再把所有事情交给一个 prebuilt ReAct Agent。 你开始显式设计业务流程: date_agent_node ↓ writer_agent_node ↓ 是否需要人工审批? ↓ yes approval_node ↓ save_node ↓ final_node 这就是生产级复杂 agent 系统的雏形。 """ import asyncio import sys from pathlib import Path from typing import Annotated, Any, Optional, TypedDict from config.load_key import load_key from langchain_core.messages import AIMessage, AnyMessage, HumanMessage from langchain_community.chat_models import ChatTongyi from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.graph import END, START, StateGraph from langgraph.graph.message import add_messages from langgraph.checkpoint.memory import InMemorySaver from langgraph.types import Command, interrupt # ============================================================ # 1. 定义 Graph State # ============================================================ # 这是第 4 层最重要的思想之一: # # 你不再只维护 messages。 # 你开始维护“业务状态”。 # # messages: # 对话历史 # # current_date: # MCP 工具返回的日期 # # draft_answer: # writer_agent_node 生成的草稿 # # requires_approval: # 是否需要人工审批 # # approved: # 人工是否批准 # # final_answer: # 最终输出 # ============================================================ class DemoState(TypedDict): messages: Annotated[list[AnyMessage], add_messages] current_date: Optional[str] draft_answer: Optional[str] requires_approval: bool approved: Optional[bool] final_answer: Optional[str] def get_latest_user_text(state: DemoState) -> str: """ 从 state["messages"] 中取出最新用户输入。 """ for msg in reversed(state["messages"]): if isinstance(msg, HumanMessage): return msg.content # 有些情况下 message 可能是 dict-like。 if getattr(msg, "type", None) == "human": return msg.content return "" def find_tool_by_name(tools: list[Any], expected_name: str): """ 从 MCP 加载出来的 tools 里找指定工具。 有些工具适配层可能会对 tool name 做轻微包装, 所以这里写得稍微稳一点。 """ for tool in tools: if tool.name == expected_name: return tool for tool in tools: if tool.name.endswith(expected_name): return tool available = [tool.name for tool in tools] raise RuntimeError( f"Cannot find tool {expected_name}. Available tools: {available}" ) async def build_graph(): """ 创建并编译自定义 StateGraph。 """ # ------------------------------------------------------------ # 1. 创建 LLM # ------------------------------------------------------------ llm = ChatTongyi( model="qwen-plus", api_key=load_key("BAILIAN_API_KEY"), ) # ------------------------------------------------------------ # 2. 连接 MCP Server # ------------------------------------------------------------ current_dir = Path(__file__).resolve().parent server_path = current_dir / "mcp_server.py" mcp_client = MultiServerMCPClient( { "date_demo_server": { "transport": "stdio", "command": sys.executable, "args": [str(server_path)], } } ) tools = await mcp_client.get_tools() get_current_date_tool = find_tool_by_name(tools, "get_current_date") save_user_note_tool = find_tool_by_name(tools, "save_user_note") # ============================================================ # 2. 定义 Nodes # ============================================================ async def date_agent_node(state: DemoState) -> dict: """ Agent 1:日期工具代理。 这个节点只负责一件事: 调用 MCP Server 里的 get_current_date 工具。 注意: 它不是直接调用本地 Python 函数。 它是通过 MCP adapter 调用 MCP 工具。 """ current_date = await get_current_date_tool.ainvoke({}) return { "current_date": current_date, } async def writer_agent_node(state: DemoState) -> dict: """ Agent 2:回答生成代理。 这个节点负责把工具结果转成自然语言回答。 这里为了演示“多 agent workflow”,我们把: 获取日期 生成回答 拆成两个不同 node。 正式项目里,你可以继续拆: planner_agent data_agent report_agent reviewer_agent executor_agent """ user_text = get_latest_user_text(state) requires_approval = any( keyword in user_text for keyword in ["保存", "记录", "写入", "save", "store"] ) response = await llm.ainvoke( [ { "role": "system", "content": ( "你是一个中文助手。" "你需要基于工具返回的日期,给用户一个简洁自然的回答。" ), }, { "role": "user", "content": ( f"用户原始问题:{user_text}\n" f"工具返回的日期:{state['current_date']}\n\n" "请生成回答草稿。" ), }, ] ) return { "draft_answer": response.content, "requires_approval": requires_approval, } def approval_node(state: DemoState) -> dict: """ 人工审批节点。 这个节点体现第 4 层的核心生产能力: 在执行有副作用的动作之前,暂停 graph,等待人工确认。 注意: - interrupt 之前不要做不可逆副作用 - 真正的 save_user_note 在 save_node 里执行 """ human_decision = interrupt( { "question": "检测到用户请求保存/写入内容,是否批准调用 save_user_note 工具?", "draft_answer": state["draft_answer"], "current_date": state["current_date"], "expected_tool": "save_user_note", } ) # resume 之后,human_decision 会变成 Command(resume=...) 传入的值。 if isinstance(human_decision, dict): approved = bool(human_decision.get("approved", False)) # <---- else: approved = bool(human_decision) return { "approved": approved, } async def save_node(state: DemoState) -> dict: """ 写入节点。 只有人工批准后,才允许调用 save_user_note。 """ if not state.get("approved"): return { "final_answer": ( f"{state['draft_answer']}\n\n" "保存操作未执行,因为人工审批未通过。" ) } save_result = await save_user_note_tool.ainvoke( { "note": f"Date answer: {state['draft_answer']}" } ) return { "final_answer": ( f"{state['draft_answer']}\n\n" f"保存操作已执行:{save_result}" ) } def final_node(state: DemoState) -> dict: """ 最终输出节点。 把 final_answer 或 draft_answer 放回 messages。 """ answer = state.get("final_answer") or state.get("draft_answer") or "没有生成答案。" return { "messages": [ AIMessage(content=answer) ] } # ============================================================ # 3. 定义条件路由 # ============================================================ def route_after_writer(state: DemoState) -> str: """ writer_agent_node 之后决定走哪里。 如果需要保存/写入,就进入 approval_node。 如果只是普通查询日期,就直接 final_node。 """ if state.get("requires_approval"): return "approval" return "final" # ============================================================ # 4. 创建 StateGraph # ============================================================ builder = StateGraph(DemoState) builder.add_node("date_agent", date_agent_node) builder.add_node("writer_agent", writer_agent_node) builder.add_node("approval", approval_node) builder.add_node("save", save_node) builder.add_node("final", final_node) builder.add_edge(START, "date_agent") builder.add_edge("date_agent", "writer_agent") builder.add_conditional_edges( "writer_agent", route_after_writer, { "approval": "approval", "final": "final", }, ) builder.add_edge("approval", "save") builder.add_edge("save", "final") builder.add_edge("final", END) # ------------------------------------------------------------ # 5. Checkpointer # ------------------------------------------------------------ # InMemorySaver 是 demo 用。 # # 生产环境不要只用内存。 # 生产环境应该使用数据库型 checkpointer,比如 PostgresSaver。 # ------------------------------------------------------------ checkpointer = InMemorySaver() graph = builder.compile(checkpointer=checkpointer) return graph async def main() -> None: graph = await build_graph() # thread_id 是 checkpoint / memory 的关键。 # # 同一个 thread_id: # 可以恢复同一个 graph 执行状态 # 可以保留 thread-level short-term memory # # 换一个 thread_id: # 就是一个新的会话 / 新的流程实例 config = { "configurable": { "thread_id": "date-demo-thread-001" } } # 这个 query 故意包含“保存”,所以会触发 human approval。 user_query = "今天是几月几号?请保存这个结果。" print("\n========== First Run ==========") first_result = await graph.ainvoke( { "messages": [ HumanMessage(content=user_query) ], "current_date": None, "draft_answer": None, "requires_approval": False, "approved": None, "final_answer": None, }, config=config, ) # 如果图在 approval_node 暂停,会返回 __interrupt__。 if "__interrupt__" in first_result: print("\n========== Human Approval Required ==========") print(first_result["__interrupt__"]) user_input = input("\nApprove save_user_note? Type y/n: ").strip().lower() approved = user_input in {"y", "yes"} print("\n========== Resume Graph ==========") final_result = await graph.ainvoke( Command( resume={ "approved": approved } ), config=config, ) else: final_result = first_result print("\n========== Final Messages ==========") for msg in final_result["messages"]: print(msg) print("-----------------------------------") print("\n========== Final Answer ==========") print(final_result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main())

浙公网安备 33010602011771号