Datawhale 的基本信息
【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents
Datawhale 是一个专注于数据科学与 AI 的开源组织,成立于 2018 年[1]...
核心定位:
- 开源教育平台:提供高质量的 AI 与数据科学学习资源[1]
- 学习者社区:聚集数万名 AI 学习者和从业者[3]
- 知识共享:倡导开源精神,内容完全免费开放[2]
Sources
[1] https://github.com/datawhalechina [2] https://datawhale.club/about [3] https://www.zhihu.com/org/datawhale
执行过程中,系统会实时向前端推送进度信息: ```json { "type": "status", "message": "正在搜索: Datawhale 的基本信息" }{ "type": "status", "message": "正在总结搜索结果..." }{ "type": "task", "task": { "id": 1, "title": "Datawhale 的基本信息", "status": "completed" } }阶段三:报告(Reporting)
报告阶段的目标是整合所有子任务的总结,生成最终报告。系统接收所有子任务的总结 + 研究主题作为输入,输出Markdown 格式的最终报告。报告包含五个部分:标题、概述、各子任务详细分析、总结、参考文献。
报告生成 Agent 会按子任务的逻辑顺序组织内容、开头添加简要概述、合并重复信息、统一 Markdown 格式,并把所有来源引用整理进参考文献区。
四、Agent 系统设计
4.1 三个 Agent 的职责划分
第七章中我们学会了用SimpleAgent构建 Agent,它的设计哲学简单直接:每次调用run()方法,Agent 分析用户问题、决定是否调用工具、返回结果。这对简单任务很有效,但面对深度研究这样的复杂任务,需要采用多 Agent 协作的方式。
本项目设计了三个专职 Agent:
| Agent | 职责 | 设计哲学 |
|---|---|---|
| 研究规划专家(TODO Planner) | 把研究主题分解为 3~5 个子任务 | 类似人类研究者研究前的"头脑风暴" |
| 任务总结专家(Task Summarizer) | 总结搜索结果、提取关键信息 | 类似人类研究者读完文献后的笔记 |
| 报告撰写专家(Report Writer) | 整合所有子任务总结,生成最终报告 | 类似人类研究者完成全部调研后撰写报告 |
Agent 1:研究规划专家(TODO Planner)
其提示词(见 backend/src/prompts.py)要求 Agent:
- 结合研究主题梳理3~5 个最关键的调研任务;
- 每个任务要有明确意图与可执行的检索方向;
- 任务之间应互补、避免重复;
- 必须调用
note工具同步任务信息(这是唯一写入笔记的途径); - 严格以 JSON 格式回复
{"tasks": [...]}。
关键设计点包括:提示词注入当前日期以获取最新信息;明确要求JSON 格式输出便于解析;通过示例帮助 Agent 理解预期输出;强调子任务数量与逻辑关系等约束。
Agent 2:任务总结专家(Task Summarizer)
其提示词(见 backend/src/prompts.py)要求 Agent 基于给定上下文为特定任务生成要点总结,对内容进行详尽且细致的总结,并尽可能多维度(原理、应用、优缺点、工程实践、对比、历史演变等)拓展,梳理3~5 条关键发现,每条发现需说明含义与价值、可引用事实数据。关键设计点:提示词包含任务标题、意图、查询等上下文;明确要求输出包含核心观点、关键数据、来源引用;强调每个观点都要加来源引用;通过示例帮助 Agent 理解输出格式。
Agent 3:报告撰写专家(Report Writer)
其提示词(见 backend/src/prompts.py)采用<REPORT_TEMPLATE>模板,要求报告包含五大部分:
- 背景概览:简述研究主题的重要性与上下文;
- 核心洞见:提炼 3~5 条最重要的结论,标注文献/任务编号;
- 证据与数据:罗列支持性的事实或指标;
- 风险与挑战:分析潜在的问题、限制或待验证的假设;
- 参考来源:按任务列出关键来源条目(标题 + 链接)。
同时要求:报告使用 Markdown、各部分明确分节、禁止添加额外的封面或结语、若某部分信息缺失需说明"暂无相关信息"、输出内容中禁止残留[TOOL_CALL:...]指令。
4.2 ToolAwareSimpleAgent 设计
在深度研究助手中,我们需要记录每个 Agent 的工具调用,用途包括:
- 调试:查看 Agent 调用了哪些工具、传入了什么参数;
- 日志:记录研究过程的全部操作;
- 分析:分析 Agent 的行为模式;
- 进度展示:实时展示 Agent 正在做什么。
SimpleAgent本身不支持工具调用监听,因此需要扩展它。ToolAwareSimpleAgent在SimpleAgent之上增加了tool_call_listener参数——这是一个回调函数,每次调用工具时都会被触发:
from hello_agents import ToolAwareSimpleAgent def tool_listener(call_info): print(f"Agent: {call_info['agent_name']}") print(f"Tool: {call_info['tool_name']}") print(f"Parameters: {call_info['parsed_parameters']}") print(f"Result: {call_info['result']}") agent = ToolAwareSimpleAgent( name="Research Assistant", system_prompt="You are a research assistant", llm=llm, tool_call_listener=tool_listener )ToolAwareSimpleAgent继承自SimpleAgent并重写_execute_tool_call方法:先解析参数、调用父类执行工具,然后通知监听器(agent 名称、工具名、解析后的参数、执行结果)。
在仓库真实实现中,工具调用事件由 backend/src/services/tool_events.py 的ToolCallTracker统一收集,它能从工具参数中推断 task_id(优先取task_id字段,其次从tags中的task_\d+或标题中的"任务 N"匹配),并从note工具结果文本中提取note_id,最终把事件转换为 SSE 负载(type: tool_call、agent、tool、parameters、result、task_id、note_id、note_path)。
4.3 三个 Agent 的协作模式
三个 Agent 是顺序协作关系(多任务执行时也可并行,见下):
- 线性流程:Agent 按固定顺序执行;
- 输入输出清晰:每个 Agent 的输入来自上一个 Agent 的输出;
- 任务间可并行:仓库的流式实现(backend/src/agent.py)中,每个子任务由独立线程(
Thread)执行,多个子任务的搜索与总结可以并发进行,事件通过Queue汇聚后按序 yield。
图 14-6 Agent 协作过程
DeepResearchAgent是整个系统的核心协调器(backend/src/agent.py),它调度三个服务完成完整流程:
def run(self, topic: str) -> SummaryStateOutput: state = SummaryState(research_topic=topic) state.todo_items = self.planner.plan_todo_list(state) # 1. 规划 if not state.todo_items: # 规划失败时的兜底 state.todo_items = [self.planner.create_fallback_task(state)] for task in state.todo_items: self._execute_task(state, task, emit_stream=False) # 2. 执行 report = self.reporting.generate_report(state) # 3. 报告 state.structured_report = report self._persist_final_report(state, report) # 4. 持久化 return SummaryStateOutput(running_summary=report, ...)注意源码中的兜底机制:当规划 Agent 无法生成任务(返回空列表)时,PlanningService.create_fallback_task会创建一个"基础背景梳理"任务,保证研究流程不会中断(backend/src/services/planner.py)。
五、工具系统集成
5.1 SearchTool 多搜索引擎扩展
第七章中实现的基础版SearchTool集成了 Tavily 与 SerpApi。本章进一步扩展,新增 DuckDuckGo、Perplexity、SearXNG 等搜索引擎,并实现 Advanced 模式(多引擎组合搜索)。SearchTool 提供统一的搜索接口,无论使用哪个引擎,调用方式都相同。
引擎选择通过配置文件完成(backend/src/config.py):
class SearchAPI(str, Enum): TAVILY = "tavily" DUCKDUCKGO = "duckduckgo" PERPLEXITY = "perplexity" SEARXNG = "searxng" ADVANCED = "advanced" class Configuration(BaseModel): search_api: SearchAPI = SearchAPI.DUCKDUCKGO # ...# .env SEARCH_API=tavily这样用户只需修改.env文件即可切换搜索引擎,无需改动代码。仓库中的搜索调度实现在 backend/src/services/search.py,它调用全局共享的SearchTool(backend="hybrid"),传入mode="structured"、fetch_full_page、max_results=5、max_tokens_per_source=2000、loop_count等参数。
SearchTool返回的字典包含:
results:搜索结果列表,每条含 title、URL、snippet;backend:实际使用的搜索引擎;answer:AI 生成的答案(仅 Perplexity 返回);notices:通知信息(如 API 限额、错误等)。
去重处理:搜索结果可能包含重复 URL,需要去重(真实实现见 backend/src/utils.py 的deduplicate_and_format_sources,以 URL 为键保留首个来源):
def deduplicate_sources(sources: List[dict]) -> List[dict]: """Remove duplicate URLs""" seen_urls = set() unique_sources = [] for source in sources: if source["url"] not in seen_urls: seen_urls.add(source["url"]) unique_sources.append(source) return unique_sourcesToken 限制:搜索结果可能包含大量文本,需要限制每个来源的 token 数。简单估算规则是 1 token ≈ 4 字符:
def limit_source_tokens(source: dict, max_tokens: int = 2000) -> dict: """Limit the number of tokens for a source""" snippet = source["snippet"] max_chars = max_tokens * 4 if len(snippet) > max_chars: snippet = snippet[:max_chars] + "..." return {**source, "snippet": snippet}5.2 NoteTool 研究进度持久化
深度研究助手使用NoteTool(第九章集成的内置工具)持久化研究进度,支持创建、读取、更新、删除笔记。研究过程中需要记录每个子任务的搜索结果、总结与最终报告,这些信息持久化到磁盘后,可支持:中断后从上次进度继续研究、查看研究全过程操作、分析研究质量与效率。
NoteTool把笔记存储在指定 workspace 目录中,每条笔记是一个 Markdown 文件,文件名即任务 ID,内容包含任务标题、任务意图、搜索查询、搜索结果与总结。生成的文件树:
workspace/ ├── notes/ │ ├── 1.md # 任务 1 的笔记 │ ├── 2.md # 任务 2 的笔记 │ ├── 3.md # 任务 3 的笔记 │ └── ... └── reports/ └── final_report.md # 最终报告在深度研究助手中,用NotesService记录每个子任务的研究进度(仓库真实实现把该逻辑内联进DeepResearchAgent,见 backend/src/agent.py 的_persist_final_report,它会在更新失败时回退为创建):
class NotesService: def __init__(self, workspace: str): self.note_tool = NoteTool(workspace=workspace) def save_task_summary(self, task: TodoItem, search_results: List[dict], summary: str): content = self._format_note_content(task, search_results, summary) self.note_tool.run({ "action": "create", "title": f"Task {task.id}: {task.title}", "content": content, "tags": ["research", "summary"] }) def _format_note_content(self, task, search_results, summary) -> str: content = f"# Task {task.id}: {task.title}\n\n" content += f"## Task Information\n\n- **Intent**: {task.intent}\n- **Query**: {task.query}\n\n" content += f"## Search Results\n\n" for idx, result in enumerate(search_results, start=1): content += f"[{idx}] {result['title']}\nURL: {result['url']}\nSnippet: {result['snippet']}\n\n" content += f"## Summary\n\n{summary}\n" return content值得注意的是,仓库中的 Agent 会主动通过[TOOL_CALL:note:{...}]指令调用 note 工具(见 backend/src/services/notes.py 的build_note_guidance):总结 Agent 在书写总结前先read最新笔记、完成后update增量信息;规划 Agent 创建任务时同步创建笔记;报告 Agent 生成报告前逐个read任务笔记、结束后创建conclusion类型笔记沉淀报告要点。这种"笔记即协作介质"的设计让三个 Agent 可以通过持久化笔记共享上下文,也天然支持断点续研。
5.3 ToolRegistry 工具管理
ToolRegistry是 HelloAgents 框架的工具注册表,用于管理所有工具的注册与调用。在深度研究助手中,用它管理SearchTool与NoteTool:
from hello_agents import ToolAwareSimpleAgent from hello_agents.tools import ToolRegistry, SearchTool, NoteTool # 创建工具 search_tool = SearchTool(backend="hybrid") note_tool = NoteTool(workspace="./workspace/notes") # 创建注册表并注册 registry = ToolRegistry() registry.register_tool(search_tool) registry.register_tool(note_tool) # 创建 Agent agent = ToolAwareSimpleAgent( name="Research Assistant", system_prompt="You are a research assistant", llm=llm, tool_registry=registry )仓库中 backend/src/agent.py 在enable_notes开启时才注册 NoteTool 并传入tool_registry,否则 Agent 不启用工具调用(enable_tool_calling=False),体现配置驱动的灵活性。
工具调用流程:
- Agent 生成指令:如
[TOOL_CALL:search_tool:{"input": "Datawhale 组织", "backend": "tavily"}]; - 解析指令:
ToolRegistry解析指令,提取工具名与参数; - 查找工具:根据工具名找到对应工具;
- 调用工具:调用工具的
run方法并传入参数; - 返回结果:工具返回执行结果;
- 格式化结果:把结果格式化为字符串返回给 Agent。
图 14-7 工具调用过程
六、服务层实现
服务层是连接 Agent 与工具的桥梁,负责具体业务逻辑。四个核心服务分别是:PlanningService、SummarizationService、ReportingService、SearchService。
6.1 规划服务(PlanningService)
PlanningService负责调用研究规划 Agent 分解主题,这是整个研究流程的第一步也是最关键的一步(backend/src/services/planner.py)。
核心职责:
- 构建规划 Prompt:基于研究主题和当前日期构建;
- 调用规划 Agent:生成子任务列表;
- 解析 JSON 响应:从 Agent 回复中提取 JSON 格式的子任务列表;
- 校验子任务格式:确保每个子任务包含 title、intent、query 字段。
JSON 解析与校验是工程重点。Agent 返回的 JSON 可能包含额外文本或格式错误,常见问题与解决方案:
| 常见问题 | 解决方案 |
|---|---|
| 包含额外文本(JSON 前后有解释性文字) | 用正则/边界字符提取 JSON 部分 |
| 格式错误(缺引号、缺逗号) | 多策略解析:先提取 JSON 数组/对象,再尝试整体解析 |
| 缺少必填字段 | 逐条校验 title/intent/query,缺失则抛出异常或回退默认值 |
仓库中的_extract_json_payload采用"找{...}或[...]边界"的策略解析,_extract_tasks还支持从[TOOL_CALL:...]指令中提取任务负载,并支持strip_thinking_tokens预处理(移除<think>推理段,见 backend/src/utils.py)。字段缺失时不会直接失败,而是回退到默认值(title→ "任务N"、intent→ "聚焦主题的关键问题"、query→ 研究主题),保证健壮性。
规划质量评估可以增加评估方法:
def evaluate_plan(self, todo_items: List[TodoItem]) -> dict: score = 100 suggestions = [] if len(todo_items) < 3: score -= 20 suggestions.append("子任务过少,可能遗漏重要信息") elif len(todo_items) > 5: score -= 10 suggestions.append("子任务过多,可能存在冗余") for task in todo_items: if len(task.query.split()) < 2: score -= 10 suggestions.append(f"任务 '{task.title}' 的查询过于简单") return {"score": score, "suggestions": suggestions}好的规划标准:覆盖全面、逻辑清晰、查询精准、数量适中(3~5 个)。
6.2 总结服务(SummarizationService)
SummarizationService负责调用任务总结 Agent,是研究流程的核心环节,直接决定研究质量(backend/src/services/summarizer.py)。
职责:
- 格式化搜索结果:把搜索结果整理为可读文本(编号 + 标题 + URL + 摘要);
- 构建总结 Prompt:基于任务信息与搜索结果构建;
- 调用总结 Agent:生成总结;
- 提取来源引用:从总结中提取来源引用。
class SummarizationService: def __init__(self, llm: HelloAgentsLLM, tool_call_listener=None): self._agent = ToolAwareSimpleAgent( name="Task Summarizer", system_prompt="You are a task summarization expert", llm=llm, tool_call_listener=tool_call_listener ) def summarize_task(self, task: TodoItem, search_results: List[dict]) -> str: formatted_sources = self._format_sources(search_results) prompt = task_summarizer_instructions.format( task_title=task.title, task_intent=task.intent, task_query=task.query, search_results=formatted_sources, ) summary = self._agent.run(prompt) return summary仓库中SummarizationService还提供流式总结(stream_task_summary):逐 chunk 读取agent.stream_run(prompt)的输出,实时过滤<think>推理段、移除[TOOL_CALL:...]指令后把可见文本逐步 yield 给前端,同时通过闭包get_summary()收集完整总结,兼顾实时展示与最终落盘。
6.3 报告生成服务(ReportingService)
ReportingService负责调用报告生成 Agent 整合所有子任务总结,是研究流程的最后一步(backend/src/services/reporter.py)。
职责:
- 格式化子任务总结:把所有子任务总结统一格式(任务编号、标题、意图、总结、来源 URL);
- 构建报告 Prompt:基于研究主题与子任务总结构建;
- 调用报告 Agent:生成最终报告;
- 整理引用:把全部来源引用整理进参考文献区。
class ReportingService: def generate_report(self, research_topic: str, task_summaries) -> str: formatted_summaries = self._format_summaries(task_summaries) prompt = report_writer_instructions.format( research_topic=research_topic, task_summaries=formatted_summaries, ) report = self._agent.run(prompt) return report仓库实现中,报告 Agent 的 Prompt 会注入:每个任务的目标、检索查询、执行状态、任务总结、来源概览,以及可用任务笔记清单(note_id),并要求先逐个read任务笔记再整合信息,必要时创建conclusion笔记沉淀报告要点。
6.4 搜索调度服务(SearchService)
SearchService负责调度搜索引擎、执行搜索并返回结果,是连接 Agent 与 SearchTool 的桥梁(backend/src/services/search.py)。注意这里没有采用 SimpleAgent 直接调用工具的常见形式,而是通过中间层把 SearchTool 的执行结果返回给 Agent,让 Agent 更专注于处理获取到的信息。
职责:
- 调度搜索引擎:根据配置选择引擎;
- 执行搜索:调用 SearchTool;
- 处理结果:去重、限制 token、格式化;
- 错误处理:处理搜索失败场景(异常时记录日志并返回空列表)。
class SearchService: def __init__(self, config: Configuration): self.config = config self.search_tool = SearchTool(backend="hybrid") def search(self, query: str, max_results: int = 5) -> List[dict]: try: raw_response = self.search_tool.run({ "input": query, "backend": self.config.search_api.value, "mode": "structured", "max_results": max_results }) results = raw_response.get("results", []) results = self._deduplicate_sources(results) results = self._limit_source_tokens(results) return results except Exception as e: logger.error(f"Search failed: {query}, error: {e}") return []调度逻辑:读取SEARCH_API配置 → 选择引擎 → 执行搜索 → 去重/限 token/格式化 → 返回结果。
为提升效率、降低成本,还可以为搜索结果增加缓存(MD5 生成缓存键,命中直接返回):
import hashlib, json from pathlib import Path class SearchService: def __init__(self, config): self.config = config self.search_tool = SearchTool(backend="hybrid") self.cache_dir = Path("./cache/search") self.cache_dir.mkdir(parents=True, exist_ok=True) def search(self, query, max_results=5, use_cache=True): cache_key = self._generate_cache_key(query, max_results) cache_file = self.cache_dir / f"{cache_key}.json" if use_cache and cache_file.exists(): return json.load(open(cache_file, "r", encoding="utf-8")) results = self._execute_search(query, max_results) if use_cache and results: json.dump(results, open(cache_file, "w", encoding="utf-8"), ensure_ascii=False, indent=2) return results def _generate_cache_key(self, query, max_results) -> str: content = f"{query}_{max_results}_{self.config.search_api.value}" return hashlib.md5(content.encode()).hexdigest()通过四个核心服务,我们构建了完整的研究流程。各服务各司其职、通过清晰的接口协作,实现了从研究主题到最终报告的自动化。
七、前端交互设计
7.1 全屏模态对话框 UI
深度研究助手采用全屏模态对话框 UI,优点:
- 沉浸式体验:全屏展示,避免干扰,聚焦研究;
- 层次清晰:主页与研究页分离,层级分明;
- 易于关闭:点击关闭按钮或按 ESC 键返回主页;
- 响应式设计:适配不同屏幕尺寸。
全屏模态对话框包含四部分:
- 顶部栏:研究主题 + 关闭按钮;
- 进度区:当前研究进度(规划、执行、报告);
- 内容区:研究结果(Markdown 格式);
- 底部栏:状态信息(如"研究中…"、"已完成")。
图 14-9 全屏模态对话框 UI
对应的 Vue 实现(ResearchModal.vue)核心逻辑:
<template> <div v-if="isOpen" class="modal-overlay" @click.self="close"> <div class="modal-container"> <!-- 顶部栏 --> <div class="modal-header"> <h2>{{ researchTopic }}</h2> <button @click="close" class="close-button">×</button> </div> <!-- 进度区 --> <div class="progress-section"> <div class="progress-bar"> <div class="progress-fill" :style="{ width: progressPercentage + '%' }"></div> </div> <div class="progress-text">{{ progressText }}</div> </div> <!-- 内容区 --> <div class="content-section"> <div v-if="isLoading" class="loading-spinner"> <div class="spinner"></div> <p>研究中,请稍候...</p> </div> <div v-else class="markdown-content" v-html="renderedMarkdown"></div> </div> <!-- 底部栏 --> <div class="modal-footer"> <span class="status-text">{{ statusText }}</span> </div> </div> </div> </template> <script setup lang="ts"> import { ref, computed, watch } from 'vue' import { marked } from 'marked' const props = defineProps<{ isOpen: boolean; researchTopic: string }>() const emit = defineEmits<{ close: [] }>() const isLoading = ref(true) const progressPercentage = ref(0) const progressText = ref('Preparing...') const statusText = ref('Researching...') const markdownContent = ref('') const renderedMarkdown = computed(() => marked(markdownContent.value)) const close = () => emit('close') const handleKeydown = (e: KeyboardEvent) => { if (e.key === 'Escape') close() } watch(() => props.isOpen, (isOpen) => { isOpen ? document.addEventListener('keydown', handleKeydown) : document.removeEventListener('keydown', handleKeydown) }) </script>为适配不同屏幕尺寸,添加媒体查询:
/* 平板设备 */ @media (max-width: 768px) { .modal-container { width: 95vw; height: 95vh; } } /* 手机设备 */ @media (max-width: 480px) { .modal-container { width: 100vw; height: 100vh; border-radius: 0; } .modal-header h2 { font-size: 18px; } }7.2 SSE 实时进度展示
深度研究助手使用SSE(Server-Sent Events)实现实时进度展示。SSE 是服务端推送技术,允许服务器主动向客户端发送数据。
图 14-10 SSE 过程
流程说明:
- 客户端发起请求:向
/research/stream发送请求,携带研究主题; - 服务端建立 SSE 连接:返回
text/event-stream响应; - 服务端推送进度:分阶段推送研究进度(规划 10%、执行 10%~80%、报告 80%~100%);
- 客户端接收进度:监听 SSE 事件、更新 UI;
- 研究完成:服务端推送最终报告并关闭连接。
后端 FastAPI SSE 端点(真实实现见 backend/src/main.py,事件数据通过agent.run_stream()生成):
from fastapi import FastAPI from fastapi.responses import StreamingResponse @app.post("/research/stream") def stream_research(payload: ResearchRequest) -> StreamingResponse: agent = DeepResearchAgent(config=_build_config(payload)) def event_iterator(): for event in agent.run_stream(payload.topic): yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n" return StreamingResponse( event_iterator(), media_type="text/event-stream", headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}, )前端使用 fetch 流式读取 SSE(真实实现见 frontend/src/services/api.ts,它用fetch+ReadableStream+TextDecoder逐段解析data:事件,比EventSource更灵活——可以携带 POST body 并支持 AbortSignal 取消):
// composables/useResearch.ts import { ref } from 'vue' export function useResearch() { const isLoading = ref(false) const progressPercentage = ref(0) const progressText = ref('') const markdownContent = ref('') const error = ref<string | null>(null) const startResearch = (topic: string) => { isLoading.value = true error.value = null const eventSource = new EventSource(`/api/research?topic=${encodeURIComponent(topic)}`) eventSource.onmessage = (event) => { const data = JSON.parse(event.data) switch (data.type) { case 'progress': progressPercentage.value = data.percentage progressText.value = data.text break case 'plan': console.log('规划结果:', data.data) break case 'task_summary': markdownContent.value += `\n\n## Task ${data.task_id}\n\n${data.summary}` break case 'report': markdownContent.value = data.data break case 'error': error.value = data.message eventSource.close() isLoading.value = false break case 'completed': eventSource.close() isLoading.value = false break } } eventSource.onerror = (err) => { console.error('SSE error:', err) error.value = '连接失败,请重试' eventSource.close() isLoading.value = false } } return { isLoading, progressPercentage, progressText, markdownContent, error, startResearch } }在组件中使用:
<script setup lang="ts"> import { useResearch } from '@/composables/useResearch' const { isLoading, progressPercentage, progressText, markdownContent, error, startResearch } = useResearch() const handleStartResearch = (topic: string) => startResearch(topic) </script>7.3 研究结果可视化
研究结果以 Markdown 格式展示,包括标题、段落、列表、引用等元素。使用marked库将 Markdown 转为 HTML 并添加自定义样式:
import { marked } from 'marked' marked.setOptions({ breaks: true, // 支持换行 gfm: true, // 支持 GitHub 风格 Markdown }) const renderedHtml = marked(markdownContent.value)研究报告中包含大量来源引用,需要特殊处理:
## References ### Task 1: Datawhale 的基本信息 - [Datawhale GitHub](https://github.com/datawhalechina) - [Datawhale 官方网站](https://datawhale.club) ### Task 2: Datawhale 的主要项目 - [Hello-Agents 教程](https://github.com/datawhalechina/Hello-Agents)【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/GitHub_Trending/he/hello-agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考