主题
实战项目二:研究助理系统
本文将构建一个研究助理系统,它能够搜索资料、整理笔记、构建知识库并生成研究报告。这个项目综合运用了子代理协作、长期记忆和流式输出等核心能力。
项目概述
研究助理系统具备以下核心功能:
- 多源信息搜索:从网络、文档、API 等多渠道获取信息
- 自动笔记整理:将搜索结果整理成结构化笔记
- 知识库构建:持久化存储研究发现,支持跨会话访问
- 研究报告生成:根据收集的资料生成完整的研究报告
- 历史研究记录:记录研究历史,支持后续查阅和引用

技术架构
研究助理系统
├── 主代理 (ResearchDirector)
│ ├── 研究规划
│ ├── 任务协调
│ └── 报告整合
├── 搜索子代理 (Searcher)
│ ├── 网络搜索
│ └── 文档检索
├── 分析子代理 (Analyzer)
│ ├── 内容分析
│ └── 关键信息提取
├── 写作子代理 (Writer)
│ ├── 笔记整理
│ └── 报告撰写
└── 记忆存储 (MemoryStore)
├── 研究笔记
├── 知识图谱
└── 历史记录
项目初始化
目录结构
research-assistant/
├── src/
│ ├── agents/
│ │ ├── main-agent.ts
│ │ └── subagents/
│ │ ├── searcher.ts
│ │ ├── analyzer.ts
│ │ └── writer.ts
│ ├── backends/
│ │ └── research-backend.ts
│ ├── tools/
│ │ └── research-tools.ts
│ └── index.ts
├── package.json
└── tsconfig.json安装依赖
bash
npm init -y
npm install @langchain/langgraph-agents @langchain/anthropic @langchain/community zod
npm install -D typescript @types/node后端配置:支持长期记忆
typescript
// src/backends/research-backend.ts
import {
CompositeBackend,
StateBackend,
StoreBackend,
} from "@langchain/langgraph-agents/backends";
export function createResearchBackend() {
return (rt: any) =>
new CompositeBackend(
new StateBackend(rt),
{
"/memories/": new StoreBackend(rt),
"/notes/": new StoreBackend(rt),
"/reports/": new StoreBackend(rt),
"/knowledge/": new StoreBackend(rt),
}
);
}关键点:
/memories/:存储研究记忆和偏好/notes/:存储研究笔记/reports/:存储生成的报告/knowledge/:存储知识图谱数据

自定义研究工具
typescript
// src/tools/research-tools.ts
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
export const webSearchTool = new TavilySearchResults({
maxResults: 5,
apiKey: process.env.TAVILY_API_KEY,
});
export const fetchUrlTool = tool(
async ({ url }) => {
try {
const response = await fetch(url);
const html = await response.text();
const textContent = html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
.replace(/<[^>]+>/g, " ")
.replace(/\s+/g, " ")
.trim()
.slice(0, 10000);
return textContent;
} catch (error: any) {
return `获取失败: ${error.message}`;
}
},
{
name: "fetch_url",
description: "获取网页内容并转换为文本",
schema: z.object({
url: z.string().url().describe("要获取的 URL"),
}),
}
);
export const extractKeyPointsTool = tool(
async ({ content, topic }) => {
const lines = content.split(/[.。!!??\n]/).filter((l) => l.trim());
const relevant = lines.filter(
(line) =>
line.toLowerCase().includes(topic.toLowerCase()) || line.length > 50
);
return relevant.slice(0, 10).join("\n\n");
},
{
name: "extract_key_points",
description: "从内容中提取与主题相关的关键点",
schema: z.object({
content: z.string().describe("要分析的内容"),
topic: z.string().describe("研究主题"),
}),
}
);
export const saveNoteTool = tool(
async ({ title, content, tags }, config) => {
const timestamp = new Date().toISOString().split("T")[0];
const filename = `/notes/${timestamp}-${title.replace(/\s+/g, "-")}.md`;
const noteContent = `# ${title}
**日期**: ${timestamp}
**标签**: ${tags.join(", ")}
---
${content}
`;
return { filename, content: noteContent };
},
{
name: "save_note",
description: "保存研究笔记到知识库",
schema: z.object({
title: z.string().describe("笔记标题"),
content: z.string().describe("笔记内容"),
tags: z.array(z.string()).describe("标签列表"),
}),
}
);
子代理定义
搜索子代理
typescript
// src/agents/subagents/searcher.ts
import { SubAgent } from "@langchain/langgraph-agents";
import { webSearchTool, fetchUrlTool } from "../../tools/research-tools";
export const searcherSubagent: SubAgent = {
name: "searcher",
description: "执行信息搜索任务。能够进行网络搜索和获取网页内容。",
systemPrompt: `你是一个专业的信息搜索专家。你的任务是:
1. 根据研究主题进行全面的网络搜索
2. 获取相关网页的详细内容
3. 筛选高质量、可靠的信息源
4. 记录所有来源以便引用
搜索策略:
- 使用多个关键词组合
- 优先选择权威来源(学术网站、官方文档、知名媒体)
- 注意信息的时效性
- 获取多个角度的观点
输出时请包含:
- 来源 URL
- 关键发现
- 信息可靠性评估`,
tools: [webSearchTool, fetchUrlTool],
};分析子代理
typescript
// src/agents/subagents/analyzer.ts
import { SubAgent } from "@langchain/langgraph-agents";
import { extractKeyPointsTool } from "../../tools/research-tools";
export const analyzerSubagent: SubAgent = {
name: "analyzer",
description: "分析和处理搜索结果。提取关键信息,识别模式和趋势。",
systemPrompt: `你是一个研究分析专家。你的任务是:
1. 分析搜索结果的质量和相关性
2. 提取关键信息和数据点
3. 识别信息之间的关联和模式
4. 评估信息的可靠性和一致性
5. 总结主要发现
分析框架:
- 主要观点是什么?
- 有哪些支持证据?
- 存在哪些争议或不一致?
- 信息缺口在哪里?
输出结构化的分析结果,便于后续整理。`,
tools: [extractKeyPointsTool],
};写作子代理
typescript
// src/agents/subagents/writer.ts
import { SubAgent } from "@langchain/langgraph-agents";
import { saveNoteTool } from "../../tools/research-tools";
export const writerSubagent: SubAgent = {
name: "writer",
description: "撰写研究笔记和报告。将分析结果整理成结构化文档。",
systemPrompt: `你是一个专业的研究写作专家。你的任务是:
1. 将分析结果整理成清晰的笔记
2. 撰写结构化的研究报告
3. 确保引用准确
4. 保持学术写作风格
写作原则:
- 结构清晰,逻辑连贯
- 观点有据可查
- 语言准确专业
- 适当使用图表和列表
报告结构:
1. 摘要
2. 背景介绍
3. 主要发现
4. 分析讨论
5. 结论建议
6. 参考来源`,
tools: [saveNoteTool],
};
主代理配置
typescript
// src/agents/main-agent.ts
import { createDeepAgent } from "@langchain/langgraph-agents";
import { createResearchBackend } from "../backends/research-backend";
import { searcherSubagent } from "./subagents/searcher";
import { analyzerSubagent } from "./subagents/analyzer";
import { writerSubagent } from "./subagents/writer";
export function createResearchAssistant() {
return createDeepAgent({
model: "claude-sonnet-4-20250514",
name: "research-assistant",
systemPrompt: `你是一个研究助理总监,负责协调研究任务。
你的职责:
1. 理解用户的研究需求
2. 制定研究计划
3. 协调子代理执行任务
4. 整合研究结果
可用的子代理:
- searcher: 信息搜索
- analyzer: 内容分析
- writer: 撰写报告
工作流程:
1. 分析研究问题,拆解为具体任务
2. 使用 searcher 收集信息
3. 使用 analyzer 分析结果
4. 使用 writer 整理输出
始终保存重要发现到 /notes/ 目录,便于后续引用。
查阅 /memories/research-history.md 了解之前的研究记录。`,
subagents: [searcherSubagent, analyzerSubagent, writerSubagent],
backend: createResearchBackend(),
});
}
使用示例
基础研究任务
typescript
// src/index.ts
import { createResearchAssistant } from "./agents/main-agent";
async function basicResearch() {
const agent = createResearchAssistant();
const result = await agent.invoke({
messages: [
{
role: "human",
content: "研究一下 2024 年大语言模型的最新发展趋势",
},
],
});
console.log(result.messages.at(-1)?.content);
}
basicResearch();深度研究任务
typescript
async function deepResearch() {
const agent = createResearchAssistant();
const result = await agent.invoke({
messages: [
{
role: "human",
content: `
请帮我进行一项深度研究:
主题:AI Agent 的技术架构和应用场景
研究要求:
1. 搜索相关的技术文章和论文
2. 分析主流的 Agent 框架(如 LangGraph、AutoGPT 等)
3. 总结核心技术组件
4. 整理实际应用案例
5. 生成一份完整的研究报告
请将研究笔记保存到知识库,并生成最终报告。
`,
},
],
});
console.log(result.messages.at(-1)?.content);
}利用历史研究
typescript
async function continuedResearch() {
const agent = createResearchAssistant();
const result = await agent.invoke({
messages: [
{
role: "human",
content: `
基于之前关于 AI Agent 的研究,我想进一步了解:
1. 查看我们之前的研究笔记(在 /notes/ 目录)
2. 补充关于"多代理协作"的内容
3. 更新研究报告
请保持与之前研究的一致性。
`,
},
],
});
console.log(result.messages.at(-1)?.content);
}流式输出:实时研究进度
typescript
async function streamingResearch() {
const agent = createResearchAssistant();
const messages = [
{
role: "human",
content: "研究 RAG(检索增强生成)技术的最新进展",
},
];
console.log("🔬 开始研究...\n");
for await (const [namespace, chunk] of await agent.stream(
{ messages },
{ streamMode: ["updates", "messages", "custom"], subgraphs: true }
)) {
const isMainAgent = namespace.length === 0;
const source = isMainAgent ? "研究总监" : getSubagentName(namespace);
if (chunk.type === "AIMessageChunk") {
if (chunk.content) {
process.stdout.write(chunk.content);
}
if (chunk.tool_calls?.length > 0) {
for (const tc of chunk.tool_calls) {
console.log(`\n[${source}] 🔧 ${tc.name}`);
}
}
}
if (chunk.type === "progress") {
console.log(`\n[${source}] 📊 ${chunk.stage}: ${chunk.progress}%`);
}
}
console.log("\n\n✅ 研究完成");
}
function getSubagentName(namespace: string[]): string {
const map: Record<string, string> = {
searcher: "搜索员",
analyzer: "分析师",
writer: "撰稿人",
};
const name = namespace.find((s) => s.startsWith("tools:"))?.split(":")[1];
return map[name || ""] || name || "子代理";
}
前端集成示例
tsx
import { useStream } from "@langchain/langgraph-sdk/react";
import { useState } from "react";
function ResearchApp() {
const [topic, setTopic] = useState("");
const stream = useStream({
assistantId: "research-assistant",
apiUrl: "http://localhost:2024",
filterSubagentMessages: true,
});
const startResearch = async () => {
await stream.submit({
messages: [{ role: "human", content: `研究主题:${topic}` }],
});
};
return (
<div className="research-app">
<header>
<h1>🔬 AI 研究助理</h1>
<div className="search-bar">
<input
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="输入研究主题..."
/>
<button onClick={startResearch} disabled={stream.isLoading}>
开始研究
</button>
</div>
</header>
<main>
<section className="research-progress">
{stream.activeSubagents.length > 0 && (
<div className="subagent-status">
<h3>研究进度</h3>
{stream.activeSubagents.map((id) => {
const sub = stream.subagents[id];
return (
<div key={id} className="subagent-card">
<span className="name">{id}</span>
<span className="status">{sub?.status}</span>
{sub?.toolCalls.length > 0 && (
<div className="tools">
{sub.toolCalls.map((tc, i) => (
<span key={i} className="tool">{tc.name}</span>
))}
</div>
)}
</div>
);
})}
</div>
)}
</section>
<section className="research-results">
<h3>研究结果</h3>
{stream.messages.map((msg, i) => (
<div key={i} className={`message ${msg.role}`}>
{msg.content}
</div>
))}
</section>
</main>
</div>
);
}记忆系统配置
创建 ~/.deepagents/research-assistant/AGENTS.md:
markdown
# 研究助理配置
## 研究偏好
- 优先使用学术来源
- 关注最近 2 年的内容
- 中英文资料都接受
## 输出格式
- 报告使用 Markdown 格式
- 包含目录结构
- 所有引用需标注来源
## 知识库组织
- /notes/:按日期和主题组织笔记
- /reports/:完整研究报告
- /knowledge/:结构化知识条目
## 引用格式
使用 [作者, 年份] 格式,在报告末尾列出完整引用。小结
本文构建了一个研究助理系统,综合运用了:
- 子代理协作:搜索、分析、写作三个专业子代理分工合作
- 长期记忆:使用 StoreBackend 实现跨会话的知识持久化
- 流式输出:实时显示研究进度和子代理工作状态
- 自定义工具:网络搜索、内容提取、笔记保存
- 前端集成:使用 useStream Hook 构建交互界面

下一篇文章,我们将构建一个安全代码执行平台,展示沙箱系统和人机协作的综合应用。