LangGraph 初见
使用 LangGraph 和 LangSmith 构建高级 AI 代理:一个多智能体研究系统实战
摘要
本文档详细阐述了如何利用 LangChain 生态中的 LangGraph 和 LangSmith 构建一个高级、有状态、可循环的 AI 代理系统。传统的线性链式(Chains)调用在处理需要规划、执行、反思和条件判断的复杂任务时能力有限。为了解决此问题,我们通过一个多智能体研究系统的案例,展示如何使用 LangGraph 将代理的行为建模为显式的状态图(State Graph),并利用 LangSmith 对其进行端到端的追踪、调试和评估,实现生产级的可靠性与可观测性。
1. 核心概念
在进入实战编码前,理解以下两个核心组件至关重要。
1.1 LangGraph:可控的 AI 工作流
LangGraph 是一个用于构建有状态、多智能体应用的库。它将传统代理(Agent)中隐式的、有时混沌的思维循环,转变为显式的、可控的状态机。其核心思想是将应用构建为一个图(Graph)。
State (状态):一个在图中所有节点间传递和更新的中心化对象。通常定义为一个 Python
TypedDict
或 PydanticBaseModel
,作为系统唯一的“事实来源”(Single Source of Truth)。Nodes (节点):图中的工作单元。每个节点是一个 Python 函数,接收当前状态,执行任务(如调用 LLM、执行工具),并返回对状态的更新。
Edges (边):定义节点之间的流转逻辑。最关键的是 Conditional Edges (条件边),它允许我们根据当前状态,动态地决定下一个要执行的节点,从而实现循环、分支等复杂逻辑。
1.2 LangSmith:AI 应用的可观测性
LangSmith 是一个专为 LLM 应用打造的可观测性(Observability)平台,功能类似于传统后端的 DataDog 或 Jaeger。它解决了 LLM 应用调试困难、行为难以预测的痛点。
Tracing (链路追踪):自动捕获并可视化应用执行的每一步,包括 LLM 调用、工具使用、提示(Prompt)详情、Token 消耗等。
Monitoring (监控):提供对应用在生产环境中的延迟、成本、错误率和用户反馈的宏观监控。
Evaluation (评估):允许开发者创建数据集,对应用的不同版本进行自动化评估,以量化模型或提示变更带来的效果。
2. 系统架构与设计
我们构建的研究系统将模拟一个研究团队的工作流程,该流程被建模为一个状态图。
2.1 工作流程
Planner (规划师):接收用户提出的宏观研究课题。
Executor (执行者):使用工具(如搜索引擎)对具体子问题进行研究。
Decider (决策者):判断研究是否完成。如果未完成,流程循环回到 Executor;如果已完成,流程进入下一步。
Synthesizer (综合者):汇总所有研究资料,生成最终报告。
2.2 状态图 (Mermaid)
代码段
graph TD
A[Planner] --> B[Executor];
B --> C{Should Continue?};
C -- Yes --> B;
C -- No --> D[Synthesizer];
D --> E[END];
3. Step-by-Step 实现
以下是完整的代码实现步骤。
步骤 1: 环境设置
安装所有必要的依赖库,并配置环境变量。
Python
# 安装依赖库
!pip install -qU langchain langgraph langchain_openai tavily-python langsmith
# 导入并设置环境变量
import os
# os.environ["OPENAI_API_KEY"] = "sk-..."
# os.environ["LANGSMITH_API_KEY"] = "ls__..."
# os.environ["TAVILY_API_KEY"] = "tvly-..."
# os.environ["OPENAI_API_BASE"] = "YOUR_API_BASE_URL" # 如有需要
# 开启 LangSmith 追踪
os.environ["LANGCHAIN_TRACING_V2"] = "true"
# 为项目命名
os.environ["LANGCHAIN_PROJECT"] = "Multi-Agent Research System"
步骤 2: 定义状态与工具
定义图的中心化 State
对象和代理需要使用的 Tool
。
Python
from typing import TypedDict, List
from langchain_community.tools.tavily_search import TavilySearchResults
# 定义将在整个图中流转的状态对象
class ResearchState(TypedDict):
"""
表示研究任务状态的 TypedDict。
"""
topic: str
plan: List[str]
researched_questions: List[str]
research_data: List[dict]
report: str
# 实例化工具。Tavily 是一个为 AI 优化的搜索引擎。
# max_results 控制每次搜索返回的结果数量。
tavily_tool = TavilySearchResults(max_results=3)
tools = [tavily_tool]
步骤 3: 实现图节点
为系统中的每个角色(Planner, Executor, Synthesizer)创建对应的节点函数。
Python
import re
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 统一定义 LLM 实例,便于管理和复用
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 节点 1: Planner
planner_prompt = ChatPromptTemplate.from_template(
"""你是一个专业的研究规划师。
你的任务是根据给定的研究主题,生成一个由3到5个具体、可搜索的子问题组成的列表,这个列表将作为后续研究的计划。
请确保问题是独立的,并且能够通过网络搜索找到答案。
研究主题: {topic}
请以无序列表(使用-或*)的格式返回你的计划。"""
)
def planner_node(state: ResearchState) -> dict:
"""接收用户主题,生成研究计划。"""
print("--- 节点: 规划师 ---")
topic = state["topic"]
planner_chain = planner_prompt | llm | StrOutputParser()
plan_string = planner_chain.invoke({"topic": topic})
plan = [item.strip() for item in re.findall(r'[-*]\s*(.*)', plan_string)]
print(f"研究计划生成完毕: {plan}")
return {"plan": plan}
# 节点 2: Executor
def executor_node(state: ResearchState) -> dict:
"""执行研究计划中的下一个问题。"""
print("--- 节点: 执行者 ---")
plan = state["plan"]
researched_questions = state["researched_questions"]
question_to_research = plan[len(researched_questions)]
print(f"正在研究问题: {question_to_research}")
research_result = tavily_tool.invoke({"query": question_to_research})
newly_researched = researched_questions + [question_to_research]
new_data = state["research_data"] + [{"question": question_to_research, "answer": research_result}]
print("研究完成,数据已更新。")
return {"researched_questions": newly_researched, "research_data": new_data}
# 节点 3: Synthesizer
synthesizer_prompt = ChatPromptTemplate.from_template(
"""你是一位专业的研究报告撰写员。
你的任务是根据下面的研究主题和收集到的数据,撰写一份全面、流畅、结构清晰的研究报告。
报告应该很好地综合所有信息,而不仅仅是罗列数据。
研究主题: {topic}
研究数据:\n---\n{research_data}\n---
请生成最终的研究报告。"""
)
def synthesizer_node(state: ResearchState) -> dict:
"""综合所有研究数据,生成最终报告。"""
print("--- 节点: 综合者 ---")
topic = state["topic"]
research_data_str = "\n\n".join(
[f"问题: {item['question']}\n答案: {item['answer']}" for item in state["research_data"]]
)
synthesizer_chain = synthesizer_prompt | llm | StrOutputParser()
report = synthesizer_chain.invoke({"topic": topic, "research_data": research_data_str})
print("报告生成完毕。")
return {"report": report}
步骤 4: 组装与编译图
将节点连接起来,定义工作流的逻辑,特别是实现循环的条件边。
Python
from langgraph.graph import StateGraph, END
# 定义决策函数
def should_continue(state: ResearchState) -> str:
"""决策节点:判断是否需要继续研究。"""
print("--- 节点: 决策 ---")
if len(state["plan"]) == len(state["researched_questions"]):
print("决策: 所有问题研究完毕,准备生成报告。")
return "end"
else:
print("决策: 计划尚未完成,继续研究。")
return "continue"
# 实例化图并绑定 State 对象
graph = StateGraph(ResearchState)
# 添加节点
graph.add_node("planner", planner_node)
graph.add_node("executor", executor_node)
graph.add_node("synthesizer", synthesizer_node)
# 设置入口点
graph.set_entry_point("planner")
# 添加边
graph.add_edge("planner", "executor")
graph.add_conditional_edges(
"executor",
should_continue,
{
"continue": "executor",
"end": "synthesizer"
}
)
graph.add_edge("synthesizer", END)
# 编译图,生成可执行的应用
app = graph.compile()
步骤 5: 运行与观测
使用 app.stream()
运行应用,并展示如何在 LangSmith 中进行分析。
5.1 运行代码
Python
# 定义研究主题和初始状态
topic = "分析一下2025年AI芯片市场的主要趋势、关键参与者和面临的挑战"
initial_state = {
"topic": topic,
"plan": [], "researched_questions": [], "research_data": [], "report": ""
}
# 流式运行并打印日志
final_state = {}
print(f"🚀 开始研究主题: {topic}\n")
for chunk in app.stream(initial_state, {"recursion_limit": 10}):
node_name = list(chunk.keys())[0]
print(f"--- 节点 '{node_name}' 已执行 ---")
final_state = chunk[node_name]
print("\n\n✅ 研究流程结束!")
print("\n\n--- 最终研究报告 ---")
print(final_state.get("report", "报告生成失败。"))
5.2 运行日志示例
Plaintext
--- 节点: 规划师 ---
研究计划生成完毕: [...]
--- 节点 'planner' 已执行 ---
--- 节点: 执行者 ---
正在研究问题: ...
--- 节点: 决策 ---
决策: 计划尚未完成,继续研究。
--- 节点 'executor' 已执行 ---
... (循环执行) ...
--- 节点: 决策 ---
决策: 所有问题研究完毕,准备生成报告。
--- 节点 'executor' 已执行 ---
--- 节点: 综合者 ---
报告生成完毕。
--- 节点 'synthesizer' 已执行 ---
✅ 研究流程结束!
5.3 在 LangSmith 中分析
在 LangSmith 平台对应的项目中,可以找到本次运行的完整 Trace。通过 Trace 视图,可以获得以下关键洞见:
可视化流程: 直观地看到图的执行路径,包括循环和分支,与设计的状态图完全对应。
输入/输出详情: 点击任一 LLM 节点(如
planner
),可以审查其完整的输入 Prompt、模型参数、原始输出、Token 消耗和延迟。工具调用分析: 展开
executor
节点,可以清晰地看到其内部对TavilySearchResults
工具的调用,包括查询的子问题和工具返回的原始数据。逻辑决策透明化:
should_continue
决策步骤清晰可见,使得复杂的控制流逻辑不再是黑盒。
4. 结论
通过本案例,我们成功构建并演示了一个基于 LangGraph 的高级 AI 代理。其核心优势在于将代理的内部工作流显式化、结构化,通过状态图的方式极大地增强了系统的可控性、可预测性和可调试性。结合 LangSmith 平台提供的强大可观测能力,开发者可以构建、调试并维护真正达到生产级别的复杂 AI 应用。
5. 未来探索方向
错误处理: 为图的节点增加异常捕获和重试逻辑。
工具集扩展: 引入更多工具(如代码解释器、数据库查询工具),并让代理动态选择使用。
自我修正循环: 增加一个“审稿”(Critique)节点,对
Synthesizer
生成的报告进行评估和反馈,形成一个内部的自我优化循环。
Last updated