Skip to content

实战项目二:研究助理系统

本文将构建一个研究助理系统,它能够搜索资料、整理笔记、构建知识库并生成研究报告。这个项目综合运用了子代理协作、长期记忆和流式输出等核心能力。

项目概述

研究助理系统具备以下核心功能:

  • 多源信息搜索:从网络、文档、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/:存储知识图谱数据

CompositeBackend 路径路由

自定义研究工具

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/:结构化知识条目

## 引用格式
使用 [作者, 年份] 格式,在报告末尾列出完整引用。

小结

本文构建了一个研究助理系统,综合运用了:

  1. 子代理协作:搜索、分析、写作三个专业子代理分工合作
  2. 长期记忆:使用 StoreBackend 实现跨会话的知识持久化
  3. 流式输出:实时显示研究进度和子代理工作状态
  4. 自定义工具:网络搜索、内容提取、笔记保存
  5. 前端集成:使用 useStream Hook 构建交互界面

研究助理系统核心技术概念图

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

读文档、看源码、写代码,理解 AI Agent 本质 🤖