在当今快速发展的AI领域,单一的大语言模型(LLM)已经难以满足复杂、多步骤的业务需求。多智能体(Multi-Agent)系统应运而生,它通过将多个专用的子智能体(sub-agent)组合起来,协同完成一项复杂任务,从而显著提升了系统的鲁棒性、可扩展性和问题解决能力。
本文将以一个智能工厂设备运维助手 的实际场景为例,深入探讨如何使用强大的图计算框架 LangGraph 从零开始构建一个功能完备的多Agent系统。我们将覆盖从基础架构设计、状态与记忆管理、人机回环(Human-in-the-Loop),到最终使用开源可观测性平台 LangFuse 进行系统评测的全过程。
这不仅是一篇技术教程,更希望为读者在构建复杂Agent系统时提供一套可复现的开发经验和设计思路。
系统原型展现
为了增加读者的感性认识,先把本系统实现的界面展示一下,方便大家理解后面的拆解。
- Web界面:

- 命令行界面:


- LangFuse跟踪评估界面:

1. 场景设定与架构设计
场景 :假设我们正在为一个现代化智能工厂构建一个AI运维助手。该助手需要处理一线工程师的自然语言请求,例如查询设备状态、分析故障原因,并自动创建维保任务。
为了实现这个目标,我们设计一个基于"主管(Supervisor)"模式的多Agent架构。该架构包含:
* 设备状态查询Agent (Machine Status Agent) :负责连接工厂的物联网(IoT)数据库,查询设备的实时运行参数,如温度、压力、转速等。
* 维保任务调度Agent (Maintenance Scheduler Agent) :负责与工单系统(Ticketing System)交互,根据故障信息创建、查询和更新维保任务。
* 主管Agent (Supervisor) :作为总协调者,负责理解用户意图,并将任务路由给合适的子Agent。它不直接执行工具,而是"指挥"其他Agent工作。
整体工作流程如下:

2. 环境准备与核心概念
在开始编码前,我们需要安装必要的库并设置环境变量。
Bash
pip install langchain langchain-openai langgraph langfuse typing_extensions
在您的项目根目录创建一个 .env 文件,用于存放API密钥:
python
# .env file
OPENAI_API_KEY="your-openai-api-key"
# LangFuse credentials
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_HOST="https://cloud.langfuse.com" # 或者你的自托管地址
LangGraph的核心概念
* 状态 (State) :是数据在图中流动的核心载体。它通常是一个字典或TypedDict,每个节点都可以读取和修改它。这构成了Agent的"记忆"基础。
* 节点 (Node) :是图中的基本计算单元。它可以是一个函数或一个Runnable对象,接收当前的状态作为输入,并返回一个更新后的状态字典。
* 边 (Edge) :连接不同的节点,定义了工作流的走向。LangGraph支持常规边(固定流向)和条件边(根据当前状态动态决定下一个节点)。
短期记忆与长期记忆
* 短期记忆 (Short-Term Memory) :用于维持单次对话的上下文。在LangGraph中,这通常由checkpointer(检查点)机制实现,它能保存和恢复图的每一步状态。
* 长期记忆 (Long-Term Memory) :用于跨越多轮对话,持久化存储用户信息或偏好。我们将使用一个外部存储(如数据库)来实现这一点。
首先初始化这些组件:
Python
# main.py
import os
from dotenv import load_dotenv
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.store.memory import InMemoryStore
# 加载环境变量
load_dotenv()
# 短期记忆: 使用SQLite作为检查点后端
# 这使得对话可以被中断和恢复
memory = SqliteSaver.from_conn_string(":memory:")
# 长期记忆: 使用内存存储作为示例
# 在生产环境中,你可以替换为Redis, Postgres等
long_term_memory_store = InMemoryStore()
3. 构建第一个子Agent:设备状态查询
我们将从零开始构建一个完整的ReAct(Reasoning and Acting)风格的Agent。
3.1 定义状态 (State)
状态是所有Agent共享的数据结构。
Python
from typing import TypedDict, Annotated, List
from langgraph.graph.message import AnyMessage, add_messages
class AgentState(TypedDict):
# 对话历史,add_messages会自动聚合消息
messages: Annotated[list[AnyMessage], add_messages]
# 从长期记忆中加载的设备历史故障信息
device_history: str
# 当前操作的设备ID
device_id: str
# 防止无限循环的计数器
remaining_steps: int
3.2 定义工具 (Tools)
工具是Agent与外部世界交互的桥梁。这里我们模拟一个数据库查询函数。
Python
from langchain_core.tools import tool
import random
# 模拟的设备数据库
DEVICE_DB = {
"C-101": {"name": "1号数控车床", "status": "正常", "temp": 65.5, "pressure": 1.2},
"P-205": {"name": "5号加压泵", "status": "告警", "temp": 92.1, "pressure": 2.5},
}
@tool
def get_device_status(device_id: str) -> dict:
"""根据设备ID查询其详细运行状态。"""
print(f"--- 工具调用: get_device_status, device_id={device_id} ---")
return DEVICE_DB.get(device_id, {"error": "未找到设备"})
@tool
def find_high_temp_devices() -> list[str]:
"""查询所有温度超过阈值(80度)的设备。"""
print(f"--- 工具调用: find_high_temp_devices ---")
high_temp_list = []
for device_id, data in DEVICE_DB.items():
if data["temp"] > 80.0:
high_temp_list.append(device_id)
return high_temp_list
# 将工具打包
status_tools = [get_device_status, find_high_temp_devices]
3.3 定义节点 (Nodes)
一个ReAct Agent至少需要两个节点:一个用于思考和调用工具的Agent节点 ,另一个用于执行工具的工具节点。
Python
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from langgraph.prebuilt import ToolNode
# 绑定工具到LLM,这样LLM才知道有哪些工具可用
llm = ChatOpenAI(model="gpt-4-turbo")
llm_with_status_tools = llm.bind_tools(status_tools)
# 1. Agent节点
def status_agent_node(state: AgentState):
"""设备状态查询Agent的核心逻辑节点"""
prompt = """
你是一个专业的设备状态分析员。你的职责是根据用户的请求,使用工具查询设备的实时状态。
如果用户没有提供明确的设备ID,但描述了问题(如"哪个设备温度最高"),请选择合适的工具进行查询。
请直接、简洁地回答问题。
"""
# 将系统提示和当前对话历史传给LLM
response = llm_with_status_tools.invoke(
[SystemMessage(content=prompt)] + state["messages"]
)
return {"messages": [response]}
# 2. 工具节点
# ToolNode是LangGraph预置的,可以方便地执行工具调用
status_tool_node = ToolNode(status_tools)
3.4 组装图 (Graph Assembly)
现在,我们将节点和边组合成一个可执行的图。

Python
from langgraph.graph import StateGraph, START, END
def should_continue(state: AgentState):
"""条件边:判断是继续调用工具还是结束"""
if last_message := state["messages"][-1]:
if last_message.tool_calls:
return "continue"
return "end"
# 创建图
graph_builder = StateGraph(AgentState)
# 添加节点
graph_builder.add_node("status_agent", status_agent_node)
graph_builder.add_node("status_tool_executor", status_tool_node)
# 定义工作流的入口
graph_builder.add_edge(START, "status_agent")
# 添加条件边
graph_builder.add_conditional_edges(
"status_agent",
should_continue,
{
"continue": "status_tool_executor",
"end": END,
},
)
# 工具执行后返回Agent节点继续思考
graph_builder.add_edge("status_tool_executor", "status_agent")
# 编译图
status_agent_graph = graph_builder.compile(checkpointer=memory)
4. 构建第二个子Agent:维保任务调度
为了展示不同的构建方式,我们将使用LangGraph的预构建函数 create_react_agent 来快速创建维保任务调度Agent。
Python
from langgraph.prebuilt import create_react_agent
@tool
def create_maintenance_ticket(device_id: str, issue_description: str) -> dict:
"""为指定设备创建维保工单。"""
print(f"--- 工具调用: create_maintenance_ticket, device_id={device_id} ---")
ticket_id = random.randint(1000, 9999)
return {"status": "成功", "ticket_id": ticket_id, "message": f"已为设备 {device_id} 创建工单 {ticket_id}。"}
maintenance_tools = [create_maintenance_ticket]
maintenance_agent_prompt = """
你是一名维保任务调度员。你的任务是根据分析员的报告,为出现问题的设备创建维保工单。
在创建工单时,必须提供清晰的设备ID和问题描述。
"""
# 使用预构建函数快速创建Agent
maintenance_agent = create_react_agent(
llm,
tools=maintenance_tools,
messages_modifier=maintenance_agent_prompt,
checkpointer=memory,
)
5. 核心大脑:构建Supervisor
Supervisor负责接收用户请求,并将其路由给上述两个子Agent。

Python
from langchain_core.tools import tool
from typing import Literal
# Supervisor需要特殊的"工具"来将任务路由给子Agent
# 注意:这里的工具函数体是空的,因为Supervisor的LLM输出ToolCall本身就是路由指令
@tool
def status_agent_router(query: str):
"""将有关设备状态、温度、压力等实时数据的查询路由到此。"""
pass
@tool
def maintenance_agent_router(device_id: str, issue: str):
"""将有关创建维保任务、安排检修的请求路由到此。"""
pass
# 定义一个包含所有成员及其路由工具的字典
members = {
"status_agent": {"agent": status_agent_graph, "router": status_agent_router},
"maintenance_agent": {"agent": maintenance_agent, "router": maintenance_agent_router},
}
supervisor_prompt = """
你是一个智能工厂的运维主管。你的团队有以下成员:
- status_agent: 负责查询设备实时状态。
- maintenance_agent: 负责创建维保工单。
根据用户的请求,决定下一步应该由哪个成员来处理。
如果一个成员完成后还有后续步骤,请继续路由到下一个合适的成员。
如果所有任务都已完成,或者你不确定下一步做什么,请直接回复用户。
"""
# Supervisor的核心逻辑
def supervisor_node(state: AgentState):
# ... (省略了完整的Supervisor实现细节,核心是调用LLM决定下一个Agent)
# 实际实现会更复杂,需要解析LLM的ToolCall来决定路由
# 为了简化,我们假设它能返回下一个agent的名字或"END"
# 在实际的LangGraph中,这通常通过一个专门的`MessageRouter`或`ToolRouter`实现
# 这里我们用一个简化的概念表示
print("--- 主管决策中 ---")
# ...
# 返回下一个节点的名称
pass
注意:一个完整的Supervisor实现较为复杂,LangGraph官方文档提供了 create_supervisor 预构建函数,它能极大地简化这个过程。为了篇幅,这里我们仅展示其概念,具体实现细节可以看实现源代码。
6. 引入"人"的智慧:Human-in-the-Loop
在工业场景中,某些关键操作(如停机检修)需要人工确认。我们可以通过LangGraph的interrupt功能实现这一点。

Python
from langgraph.errors import GraphInterrupt
from langgraph.graph import StateGraph
def confirmation_node(state: AgentState):
"""检查是否需要人工确认"""
# 假设state中包含了任务的关键性信息
if state.get("is_critical_maintenance"):
# 抛出中断,图会在这里暂停
raise GraphInterrupt("关键维保任务需要停机,请输入'yes'确认:")
return
def human_input_node(state: AgentState):
"""一个虚拟节点,用于接收中断后的输入"""
pass
# ... 在你的图中加入中断逻辑 ...
# workflow.add_node("confirmation", confirmation_node)
# workflow.add_conditional_edges(
# "previous_node",
# lambda state: "confirmation" if state.get("is_critical_maintenance") else "next_node"
# )
# workflow.add_edge("confirmation", "human_input_node")
当图执行到confirmation_node并抛出GraphInterrupt时,程序会暂停。你可以捕获这个中断,获取提示信息展示给用户,然后将用户的输入重新invoke回图中,从而继续执行。
7. 赋予Agent"记忆":长期记忆集成
为了让Agent"记住"一台设备的历史故障,我们需要集成长期记忆。

Python
# 节点1: 加载记忆
def load_memory_node(state: AgentState):
device_id = state.get("device_id")
if device_id:
# 从长期记忆存储中获取数据
history = long_term_memory_store.get(f"device:{device_id}:history")
return {"device_history": str(history or "无历史故障记录")}
return {}
# 节点2: 创建/更新记忆
def create_memory_node(state: AgentState):
# 分析整个对话,判断是否产生了新的故障记录
# ... LLM分析逻辑 ...
new_fault_detected = True # 假设分析后发现新故障
if new_fault_detected:
device_id = state.get("device_id")
issue = "温度超过100度"
# 更新长期记忆
long_term_memory_store.put(f"device:{device_id}:history", issue)
return {}
# 将这两个节点整合进你的主工作流图的开始和结束位置
8. 效果如何?使用LangFuse进行测试评估
构建完复杂的系统后,如何科学地评估其表现至关重要。我们将使用开源的 LangFuse 平台。
8.1 LangFuse设置与代码集成
首先,初始化LangFuse客户端。
Python
from langfuse import Langfuse
from langfuse.decorators import traceable
# 初始化Langfuse
langfuse = Langfuse()
使用 @traceable 装饰器可以非常方便地追踪整个Agent的执行过程。
Python
@traceable(name="smart_factory_agent_run")
def run_agent(question: str):
# 这里是调用你的最终版Agent图的代码
# config = {"configurable": {"thread_id": "some_unique_id"}}
# response = final_graph.invoke({"messages": [HumanMessage(content=question)]}, config)
# return response
# 为了演示,我们返回一个模拟结果
print(f"Agent is processing: {question}")
return {"output": "工单 #8848 已为设备 C-101 创建,问题:温度异常。"}
当你运行 run_agent("...") 时,完整的调用链、输入输出、耗时、Token消耗等信息都会被自动发送到你的LangFuse项目中。
8.2 创建评估数据集
在LangFuse中,我们可以创建一个数据集来存放测试用例。
Python
# 在Langfuse UI中创建一个名为 "factory-agent-test" 的数据集,
# 或者使用SDK创建:
langfuse.create_dataset(name="factory-agent-test")
# 为数据集添加测试用例
langfuse.create_example(
dataset_name="factory-agent-test",
input={"question": "1号车床温度有点高,是什么情况?"},
expected_output="1号数控车床当前温度为65.5度,状态正常。"
)
langfuse.create_example(
dataset_name="factory-agent-test",
input={"P-205加压泵告警了,帮我安排检修。"},
expected_output="已为设备 P-205 创建维保工单" # 预期输出可以模糊一些,用于后续LLM评估
)
8.3 定义评估标准 (Evaluator)
我们可以使用一个强大的LLM作为"考官",来评估Agent的回答质量。
Python
from langfuse.model import CreateScore
from langfuse.evaluation import Evaluation
# 使用LLM作为评估者
@traceable(name="correctness_evaluator")
def correctness_evaluator(output, expected_output):
system_prompt = """
你是一个严格的评估员。你的任务是判断"实际输出"是否在事实上与"预期输出"一致。
你需要给出'1'(完全一致或更好)或'0'(不一致或错误)的分数,并提供简短的评估理由。
"""
human_prompt = f"""
[预期输出]: {expected_output}
[实际输出]: {output}
请严格按照预期输出进行评估。如果实际输出包含了预期输出的核心信息,即可判为正确。
"""
response = llm.invoke([
SystemMessage(content=system_prompt),
HumanMessage(content=human_prompt)
]).content
# 简单的解析逻辑
score = 1 if '1' in response else 0
reason = response
return CreateScore(name="correctness", value=score, comment=reason)
8.4 运行评估
LangFuse提供了便捷的工具来对数据集中的每一项运行你的Agent,并用评估函数打分。
Python
evaluation = Evaluation(
name="agent-v1-eval",
dataset_name="factory-agent-test",
model=run_agent, # 你的Agent执行函数
scorers=[correctness_evaluator] # 评估函数列表
)
# 运行评估任务
# 这会遍历数据集,执行agent,然后用scorer打分
evaluation_job = evaluation.run()
print("评估完成!请前往LangFuse仪表板查看详细结果。")
评估完成后,你可以在LangFuse的UI上看到每个测试用例的详细执行Trace、最终得分、评估理由、以及整体的平均分、延迟、成本等聚合指标。这为你迭代和优化Agent提供了强有力的数据支持。
总结
本文通过一个智能工厂运维助手的实例,完整地展示了使用LangGraph构建一个包含 主管-下属架构、短期/长期记忆、人机回环 的复杂多Agent系统的全过程。 此外,我们引入了开源可观测性平台LangFuse ,演示了如何通过追踪、数据集和评估器对我们构建的复杂系统进行科学、量化的评估。这形成了一个从"开发"到"运维"的闭环,是构建生产级AI应用不可或缺的一环。