导语:欢迎来到我们第二周的毕业项目!在过去几天里,我们深入探索了 LangGraph 的世界,学习了如何用图的思维构建状态机、实现智能路由、持久化记忆、乃至人机与多智能体协作。现在,是时候将所有这些碎片化的知识拼凑起来,打造一个真正强大、完整、实用的应用了。在本项目中,我们将从零到一,创建并配置一个名为 DeepResearch 的深度研究助手。这个系统将模拟一个由“主管”和多个“研究员”组成的 AI 研究团队,能够对任意给定主题进行分工、并行搜索、并最终汇总成一份结构化的研究报告。这不仅是对本周所学知识的终极考验,更是一个可以作为你个人作品集亮点的、准生产级的 Agentic AI 项目。
目录
- 项目愿景:打造一个自动化 AI 研究团队
- 告别手动搜索:DeepResearch 要解决什么问题?
- 团队架构:一个“主管”(Supervisor)+ 多个“研究员”(Workers)
- 工作流程:规划 -> 并行研究 -> 汇总报告
- 架构设计:绘制 DeepResearch 的蓝图
- 状态定义(
AgentState):我们需要追踪哪些信息?(任务、计划、小组成员、历史消息等) - 节点规划:
Supervisor: 核心大脑,负责任务分解和工作流管理。Worker_Nodes: 并行执行单元,每个 Worker 都是一个独立的 ReAct 风格 Agent。Aggregator_Node: 结果汇总节点。
- 图的拓扑结构:一个动态的、层级化的协作图。
- Mermaid 图:可视化 DeepResearch 的完整工作流。
- 状态定义(
- 第一步:构建“研究员” Worker Agent
- 目标:创建一个可以执行搜索任务的、可复用的 ReAct Agent。
- 工具定义:使用
TavilySearchResults。 - Agent 创建:使用
create_openai_tools_agent创建一个标准的Runnable。 - 封装成函数:将其封装成一个接收
messages和sender_name的节点函数。
- 第二步:构建“主管” Supervisor Agent
- 核心职责:任务分解、成员分配、流程控制。
- Supervisor Prompt 设计:赋予其“项目经理”的人格和决策逻辑。
- 输出结构化:使用 Function Calling 让 Supervisor 输出结构化的计划和决策。
- 第三步:用 LangGraph 组装团队
- 定义图、节点和边,实现动态的 Worker 分配和调用。
- 使用
functools.partial动态创建多个 Worker 节点。 - 实现
router函数,根据 Supervisor 的决策,将任务分发给所有 Workers 或进行汇总。
- 第四步:接入持久化记忆
- 使用
SqliteSaver为我们的研究任务提供断点续传能力。 - 为每个研究任务分配一个唯一的
thread_id。
- 使用
- 完整代码与运行指南
- 整合所有代码片段。
- 运行 DeepResearch Agent,对一个复杂主题(如“AGI 的最新进展”)进行深度研究。
- 观察控制台输出,理解 AI 团队是如何协同工作的。
- 项目总结与展望
- DeepResearch 的优点与局限性。
- 如何进一步扩展?(增加更多类型的 Worker、引入人类反馈环、优化报告生成等)
- 你已经具备了构建复杂多智能体系统的能力!
1. 项目愿景:打造一个自动化 AI 研究团队
我们每天都会花费大量时间在信息检索上:为了写一份报告、学习一项新技术、或者了解一个行业动态。这个过程通常是繁琐的、重复的:打开搜索引擎、输入关键词、浏览多个网页、复制粘贴、总结归纳。
DeepResearch 的愿景,就是将这个过程自动化。我们希望创建一个 AI 系统,你只需要给它一个研究主题,它就能像一个训练有素的人类研究团队一样,自主地完成所有工作,并最终给你一份高质量的研究报告。
团队架构
一个高效的人类研究团队通常包含两种角色:
- 项目主管 (Supervisor):负责理解总体目标,将大任务分解为多个可执行的小任务,并将任务分配给合适的团队成员。
- 研究员 (Researcher/Worker):负责执行具体的小任务,比如“搜索 A 公司的财报”、“查找 B 技术的实现原理”等。
我们的 DeepResearch 系统将完美地复刻这种架构。
工作流程
- 规划 (Plan):用户提出一个复杂的研究主题(如“分析苹果 Vision Pro 的市场前景”)。
SupervisorAgent 首先介入,将这个主题分解为一系列具体的子问题,例如:“Vision Pro 的核心技术是什么?”、“当前市场上有哪些竞争对手?”、“媒体和用户的初步评价如何?”、“它的定价策略和目标用户是谁?”。 - 并行研究 (Parallel Research):
Supervisor将这些子问题作为独立的任务,同时分配给多个WorkerAgent。每个Worker都是一个具备网络搜索能力的独立研究员。 - 汇总报告 (Aggregate):当所有的
WorkerAgent 都完成了自己的研究任务后,一个Aggregator节点(或者由Supervisor亲自担任)会收集所有子问题的答案,并将它们整合成一份结构清晰、逻辑连贯的最终研究报告。
2. 架构设计:绘制 DeepResearch 的蓝图
状态定义 (AgentState)
我们的 State 需要比之前更复杂,以追踪整个团队的工作进展。
from typing import TypedDict, Annotated, List, Optional
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
task: str # 用户的初始研究任务
plan: List[str] # 主管分解的任务计划列表
messages: Annotated[List[BaseMessage], operator.add]
team_members: List[str] # 团队成员列表 (e.g., "Researcher", "Coder")
# 用于存储每个 worker 的结果,key 是 worker 的名字
worker_outputs: Annotated[dict, operator.ior_]
next: str # 下一个要执行的节点名
节点规划
supervisor: 接收初始task,生成plan,并决定下一步是分派任务 (dispatch) 还是汇总 (aggregate)。research_worker: 一个可复用的节点函数,可以根据分配给它的具体任务执行研究。aggregator: 收集所有worker_outputs并生成最终报告。
图的拓扑结构
这是一个层级与并行结合的结构。
Mermaid 图:可视化 DeepResearch 的完整工作流
graph TD
A[Start] --> B(Supervisor: 接收任务, 制定计划);
B --> C{Router: 是否需要研究?};
C -- Yes --> D[并行执行: dispatch_to_workers];
subgraph Workers
D1(Worker 1: 研究子问题 1);
D2(Worker 2: 研究子问题 2);
D3(...);
end
D --> D1;
D --> D2;
D --> D3;
D1 --> E(Supervisor: 收集结果);
D2 --> E;
D3 --> E;
E --> F(Aggregator: 汇总报告);
C -- No / All Done --> F;
F --> G[End];
3. 第一步:构建“研究员” Worker Agent
Worker 是我们团队的基石。它应该是一个功能单一、性能可靠的 ReAct 风格 Agent。我们将创建一个通用的函数,它可以实例化并运行一个具备搜索能力的 Worker。
# deep_research.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_community.tools.tavily_search import TavilySearchResults
# 1. 定义 Worker Agent 的工具
worker_tools = [TavilySearchResults(max_results=4)]
# 2. 定义 Worker Agent 的 Prompt
worker_prompt = ChatPromptTemplate.from_messages([
("system", "You are a research assistant. You must use the provided tools to answer the user's question."),
("user", "{input}")
])
# 3. 创建 Worker Agent 的 Runnable
def create_worker_agent_runnable():
llm = ChatOpenAI(model="deepseek-chat")
agent = create_openai_tools_agent(llm, worker_tools, worker_prompt)
return AgentExecutor(agent=agent, tools=worker_tools, verbose=True)
# 4. 封装成一个可供 LangGraph 调用的节点函数
def worker_node(state: AgentState, agent_runnable, worker_name: str):
# 根据 worker_name 从 plan 中找到分配给自己的任务
# 为了简化,我们假设 plan 就是一个任务列表,按顺序分配
# 现实中,这里的任务分配会更复杂
task = state['plan'][state['team_members'].index(worker_name)]
# 运行 ReAct Agent
result = agent_runnable.invoke({"input": task})
# 返回结果,用 worker_name 作为 key
return {"worker_outputs": {worker_name: result['output']}}
4. 第二步:构建“主管” Supervisor Agent
Supervisor 是团队的大脑。它的核心是 Prompt,我们需要在这个 Prompt 中赋予它分解任务和管理团队的能力。
# deep_research.py (续)
from langchain_core.pydantic_v1 import BaseModel, Field
# 1. 定义 Supervisor 的输出数据结构
class Plan(BaseModel):
"""A plan to complete a research task, broken down into simple steps."""
steps: List[str] = Field(description="A list of simple, actionable steps to research.")
# 2. 创建 Supervisor 的 Prompt
supervisor_prompt = ChatPromptTemplate.from_messages([
("system", """You are a supervisor of a team of AI research agents.
Given a user's research request, your job is to create a detailed, step-by-step plan to address it.
Each step in the plan will be executed by a dedicated researcher.
Do not try to answer the question yourself, only create the plan."""),
("user", "Research task: {task}")
])
# 3. 创建 Supervisor 的 Runnable
llm = ChatOpenAI(model="gpt-4-turbo") # 主管建议使用更强大的模型
structured_llm = llm.with_structured_output(Plan)
supervisor_runnable = supervisor_prompt | structured_llm
5. 第三步:用 LangGraph 组装团队
这是最关键的一步,我们将使用 LangGraph 将 Supervisor 和多个 Worker 组装成一个协同工作的图。
# deep_research.py (续)
import functools
from langgraph.graph import StatefulGraph, END
# --- 1. 创建节点 ---
# Supervisor 节点
def supervisor_node(state: AgentState):
print("--- Calling Supervisor Node ---")
plan = supervisor_runnable.invoke({"task": state['task']})
print(f"Plan: {plan.steps}")
# 假设有多少步,就创建多少个 worker
team_members = [f"Researcher_{i+1}" for i in range(len(plan.steps))]
return {
"plan": plan.steps,
"team_members": team_members,
"next": "run_workers" # 决定下一步走向
}
# Worker 节点 (使用 functools.partial 动态创建)
# 我们为每个 team_member 创建一个独立的、可调用的节点
worker_runnables = {name: create_worker_agent_runnable() for name in ["Researcher_1", "Researcher_2", "Researcher_3"]} # 预先创建
worker_nodes = []
for member in ["Researcher_1", "Researcher_2", "Researcher_3"]:
node = functools.partial(worker_node, agent_runnable=worker_runnables[member], worker_name=member)
worker_nodes.append(node)
# 聚合节点
def aggregator_node(state: AgentState):
print("--- Calling Aggregator Node ---")
all_outputs = "\n\n".join(state['worker_outputs'].values())
# 最后调用一次 LLM 来生成最终报告
final_prompt = ChatPromptTemplate.from_messages([
("system", "You are a senior research analyst. Your task is to synthesize the research findings from your team into a coherent, well-structured final report."),
("user", "Research Task: {task}\n\nTeam Findings:\n{findings}")
])
final_report_runnable = final_prompt | llm
final_report = final_report_runnable.invoke({
"task": state['task'],
"findings": all_outputs
})
return {"messages": [final_report]}
# --- 2. 定义图 ---
workflow = StatefulGraph(AgentState)
workflow.add_node("supervisor", supervisor_node)
workflow.add_node("aggregator", aggregator_node)
# 将多个并行节点作为一个整体添加到图中
for i, worker_node_func in enumerate(worker_nodes):
workflow.add_node(f"worker_{i+1}", worker_node_func)
# --- 3. 定义边 ---
workflow.set_entry_point("supervisor")
# 从 supervisor 分发到所有 workers
# LangGraph 会自动等待所有并行分支完成后再继续
for i in range(len(worker_nodes)):
workflow.add_edge("supervisor", f"worker_{i+1}")
# 所有 workers 完成后,走向 aggregator
for i in range(len(worker_nodes)):
workflow.add_edge(f"worker_{i+1}", "aggregator")
workflow.add_edge("aggregator", END)
# 编译图
app = workflow.compile()
(这是一个简化的拓扑。在真实的 LangGraph 中,并行执行和聚合的定义会使用更高级的 add_node 和条件边逻辑,但核心思想是相同的:从一个点分叉,再汇聚到一个点。)
6. 第四步:接入持久化记忆
对于可能耗时数分钟甚至数小时的深度研究任务,持久化是必须的。
# deep_research.py (续)
from langgraph.checkpoint.sqlite import SqliteSaver
memory_saver = SqliteSaver.from_conn_string("sqlite:///deep_research.sqlite")
# 传入 checkpointer 编译
app = workflow.compile(checkpointer=memory_saver)
7. 完整代码与运行指南
将以上所有代码整合到一个 deep_research.py 文件中。然后,在文件末尾添加运行代码:
# deep_research.py (末尾)
import uuid
# 为这次研究任务创建一个唯一的 ID
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
task = "Analyze the market landscape and future trends for AI-powered code generation tools."
# 开始运行
inputs = {"task": task, "messages": []}
for output in app.stream(inputs, config):
for key, value in output.items():
print(f"--- Output from node '{key}' ---")
print(value)
print("\n")
在终端运行 python deep_research.py。你将看到一场 AI 团队协作的大戏:
- Supervisor 首先输出它制定的研究计划。
- 然后,多个 Researcher 节点开始并行工作,它们的
verbose=True日志会交错出现。 - 当所有 Researcher 都完成后,Aggregator 节点被触发。
- 最后,你会得到一份由 AI 团队为你精心准备的、关于“AI 代码生成工具市场”的深度研究报告。
- 所有中间状态都被保存在
deep_research.sqlite文件中。你可以随时中断,然后用相同的thread_id恢复任务。
8. 项目总结与展望
DeepResearch 的优点与局限性
- 优点:
- 自动化:将繁琐的研究工作流完全自动化。
- 效率:通过并行处理,大大缩短了研究时间。
- 结构化:通过“规划-执行-汇总”的模式,保证了研究的广度和深度。
- 可扩展:可以轻易地增加更多、更专业的 Worker Agent。
- 局限性:
- 成本较高:每个 Agent 的运行和最终的汇总都需要多次 LLM 调用。
- 稳定性:任意一个 Worker Agent 的失败都可能影响最终结果的质量。
- 规划质量:最终报告的质量高度依赖于 Supervisor 最初制定的计划的质量。
如何进一步扩展?
- 增加 Worker 类型:加入一个
code_execution_agent来验证生成的代码,或者一个chart_generation_agent来生成数据图表。 - 引入人类反馈环:在 Supervisor 制定完计划后,或者在 Aggregator 生成最终报告前,加入一个“人类批准”节点,让人类专家来审核和修正。
- 迭代式研究:让 Supervisor 在看到第一轮的研究结果后,能够决定是否需要进行第二轮、更深入的研究,形成一个更大的研究循环。
通过完成 DeepResearch 项目,你已经不再只是一个 Agent 的使用者,你已经成为一个能够设计、组织和指挥 AI 团队协同作战的“AI 架构师”。你所掌握的,是构建下一代复杂、智能应用系统的核心能力。