Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/04-开发计划/07-Deep Research V2.0 开发计划.md
HaHafeng 8f06d4f929 feat(asl): Complete Deep Research V2.0 core development
Backend:
- Add SSE streaming client (unifuncsSseClient) replacing async polling
- Add paragraph-based reasoning parser with mergeConsecutiveThinking
- Add requirement expansion service (DeepSeek-V3 PICOS+MeSH)
- Add Word export service with Pandoc, inline hyperlinks, reference link expansion
- Add deep research V2 worker with 2s log flush and Chinese source prompt
- Add 5 curated data sources config (PubMed/ClinicalTrials/Cochrane/CNKI/MedJournals)
- Add 4 API endpoints (generate-requirement/tasks/task-status/export-word)
- Update Prisma schema with 6 new V2.0 fields on AslResearchTask
- Add DB migration for V2.0 fields
- Simplify ASL_DEEP_RESEARCH_EXPANSION prompt (remove strategy section)

Frontend:
- Add waterfall-flow DeepResearchPage (phase 0-4 progressive reveal)
- Add LandingView, SetupPanel, StrategyConfirm, AgentTerminal, ResultsView
- Add react-markdown + remark-gfm for report rendering
- Add custom link component showing visible URLs after references
- Add useDeepResearchTask polling hook
- Add deep research TypeScript types

Tests:
- Add E2E test, smoke test, and Chinese data source test scripts

Docs:
- Update ASL module status (v2.0 - core features complete)
- Update system status (v6.1 - ASL V2.0 milestone)
- Update Unifuncs DeepSearch API guide (v2.0 - SSE mode + Chinese source results)
- Update module auth specification (test script guidelines)
- Update V2.0 development plan

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 13:21:52 +08:00

39 KiB
Raw Permalink Blame History

Deep Research V2.0 开发计划

文档版本: v1.1
创建日期: 2026-02-22
维护者: 开发团队
前置文档: PRD V4.1 / 原型图 V4.2 / 技术设计 V4.1
预计工期: 5 天
核心理念: 单页瀑布流 + 自然语言需求扩写 + 异步执行 + 务实结果展示
v1.1 更新: 融入审查建议Worker 重试、JSON 防崩溃、条件滚动、MeSH 扩展、Prompt 管理、数据源精简)


1. 升级概述

1.1 V1.x → V2.0 变化总结

维度 V1.x (当前) V2.0 (目标)
交互模式 单输入框 → 直接搜索 四步瀑布流 Landing → 配置 → HITL 确认 → 终端 → 结果
需求理解 用户原文直传 unifuncs 内置 LLM 需求扩写 + 用户人工核验修改
API 协议 OpenAI 兼容SSE 流式) Unifuncs 异步模式create_task + query_task 轮询)
执行展示 混合文字流(打字机效果) 暗黑终端 + 分类结构化日志(每 3-5s 弹出一条)
结果展示 PubMed 链接列表 综合报告Markdown+ 文献清单表格 + Word 导出
可靠性 离开页面任务丢失 pg-boss 队列,离开页面任务继续,回来可恢复

1.2 设计决策记录

决策 选择 理由
SSE vs 异步 异步模式 Deep Research 任务 3-10 分钟SSE 连接不稳定;异步模式用户可离开回来,可靠性远高于 SSE
异步下的实时性 Worker 5s 轮询 + 前端 3s 轮询 用户每 3-5s 看到一条新日志,对分钟级 Agent 任务来说体验自然,比逐字流更适合终端 UI
结果展示复杂度 报告 + 表格,不做图表看板 研究人员要的是内容本身(综合报告 + 文献清单图表是锦上添花非刚需MVP 不做
Word 导出 复用 Pandoc Protocol Agent 已验证 Pandoc → Word 方案,零额外依赖
需求扩写 Prompt Prompt 管理服务(运营端可配) 使用 ASL_DEEP_RESEARCH_EXPANSION,运营管理端可在线调优,代码中写兜底 Fallback
数据源范围 精简为 5 个3英文+2中文 基于 18 站实测结果精选PubMed 默认勾选ClinicalTrials 标记需英文查询
状态管理 React Query + useState 服务端状态用 useQuery 轮询(自带缓存+去重),页面步骤用 useState不引入 Zustand

2. 系统数据流

┌──────────────────────────────────────────────────────────────────────┐
│ Step 1-2: 需求扩写(同步,本系统内部)                                  │
│                                                                      │
│  前端 Landing/Setup ──POST──→ Node.js ──LLMFactory──→ DeepSeek-V3   │
│                     original_query    "需求扩写Prompt"                │
│                                                                      │
│  返回taskId + generatedRequirement结构化自然语言检索指令书        │
│  前端展示指令书,用户可编辑修改                                        │
└──────────────────────────────────────────────────────────────────────┘
                                    ↓ 用户确认
┌──────────────────────────────────────────────────────────────────────┐
│ Step 3: 异步执行pg-boss + Unifuncs 异步 API                       │
│                                                                      │
│  前端 ──PUT──→ Node.js ──pg-boss push──→ Worker                     │
│       confirmed_requirement                                          │
│                                                                      │
│  Worker:                                                             │
│    1. POST unifuncs/v1/create_task传入 confirmed_requirement      │
│    2. 每 5s GET unifuncs/v1/query_task                               │
│    3. 解析 reasoning_content → 增量日志写 DB (execution_logs)         │
│    4. 完成后解析 content → synthesis_report + result_list            │
│                                                                      │
│  前端每 3s GET /tasks/:id → 渲染 execution_logs 到暗黑终端            │
└──────────────────────────────────────────────────────────────────────┘
                                    ↓ status === 'completed'
┌──────────────────────────────────────────────────────────────────────┐
│ Step 4: 结果展示(读 DB 渲染)                                        │
│                                                                      │
│  终端折叠 → 白底结果区展开                                            │
│  ├── ✅ 完成横幅(一行 + 导出 Word 按钮)                              │
│  ├── 📄 AI 综合报告synthesis_report → Markdown 渲染)               │
│  └── 📋 文献清单表格result_list → Ant Design Table                │
└──────────────────────────────────────────────────────────────────────┘

3. 数据库 Schema 变更

在现有 AslResearchTask 基础上新增 6 个字段,不删除任何现有字段(向后兼容)。

model AslResearchTask {
  // ── 现有字段(保留不动)──────────────────────────
  id               String   @id @default(uuid())
  projectId        String   @map("project_id")
  userId           String   @map("user_id")
  query            String                           // 原始粗略输入Step 1
  filters          Json?                            // 高级筛选配置
  externalTaskId   String?  @map("external_task_id") // unifuncs task_id
  status           String   @default("pending")
  errorMessage     String?  @map("error_message")
  resultCount      Int?     @map("result_count")
  rawResult        String?  @map("raw_result") @db.Text
  reasoningContent String?  @map("reasoning_content") @db.Text
  literatures      Json?
  tokenUsage       Json?    @map("token_usage")
  searchCount      Int?     @map("search_count")
  readCount        Int?     @map("read_count")
  iterations       Int?
  createdAt        DateTime @default(now()) @map("created_at")
  updatedAt        DateTime @updatedAt @map("updated_at")
  completedAt      DateTime? @map("completed_at")

  // ── V2.0 新增字段 ──────────────────────────────
  targetSources         Json?    @map("target_sources")          // 选中的数据源 ["pubmed.ncbi.nlm.nih.gov", ...]
  confirmedRequirement  String?  @map("confirmed_requirement") @db.Text  // 用户核验后的自然语言检索指令书
  aiIntentSummary       Json?    @map("ai_intent_summary")       // AI提炼的结构化摘要左侧卡片用
  executionLogs         Json?    @map("execution_logs")          // 终端日志数组 [{type, title, text, timestamp}]
  synthesisReport       String?  @map("synthesis_report") @db.Text // AI综合报告Markdown
  resultList            Json?    @map("result_list")             // 结构化文献元数据列表

  // ── 索引(保留现有)────────────────────────────
  @@index([projectId], map: "idx_research_tasks_project_id")
  @@index([userId], map: "idx_research_tasks_user_id")
  @@index([status], map: "idx_research_tasks_status")
  @@index([createdAt], map: "idx_research_tasks_created_at")
  @@map("research_tasks")
  @@schema("asl_schema")
}

Status 枚举扩展:

状态 含义 触发时机
draft 需求已扩写,等待用户确认 POST /generate-requirement
pending 用户已确认,等待 Worker 拾取 PUT /tasks/:id/execute
running Worker 已创建 unifuncs 任务,轮询中 Worker 内部
completed unifuncs 完成,结果已解析入库 Worker 内部
failed 执行失败 Worker 内部

迁移命令:

npx prisma migrate dev --name add_deep_research_v2_fields

4. 需求扩写 Prompt 设计

4.1 设计原则

需求扩写 Prompt 通过 Prompt 管理服务ASL_DEEP_RESEARCH_EXPANSION)进行管理,运营端可在线调优,代码内置 Fallback 兜底。

原则 说明
PICOS 结构化 引导 LLM 按 Population-Intervention-Comparison-Outcome-Study Design 拆解用户模糊需求
MeSH 同义词扩展 自动补充专业 MeSH 术语(如 "他汀" → "Statins, Hydroxymethylglutaryl-CoA Reductase Inhibitors"
默认高质量研究设计 若用户未指定,默认偏向 RCT、SR/MA、Cohort Study
自然语言对话风格 输出"像资深医学信息官写给检索助手的一封邮件",方便 HITL 编辑
数据源感知 Prompt 接收用户选择的数据源列表ClinicalTrials.gov 时自动生成英文指令段
不硬编码约束 不强制 Open Access / 不排除非英文文献 — 这些由用户在配置面板自主选择

4.2 Prompt 模板结构Fallback

// backend/src/common/prompt/prompt.fallbacks.ts新增
const ASL_DEEP_RESEARCH_EXPANSION = `
你是一位经验丰富的医学信息官Medical Information Officer
擅长将研究者的模糊想法转化为精准的文献检索需求指令。

## 任务
根据用户输入的粗略研究想法,生成一份结构化的深度文献检索指令书。

## 输出规则
1. **自然语言风格**:像写邮件一样,口语化但专业,方便研究者阅读和编辑
2. **PICOS 拆解**:明确 Population / Intervention / Comparison / Outcome / Study Design
3. **MeSH 扩展**:为关键术语补充 MeSH 同义词(用括号标注英文 MeSH 术语)
4. **研究设计偏好**:若用户未指定,默认优先 RCT、Systematic Review/Meta-Analysis、Cohort Study
5. **数据源适配**:根据用户选择的数据源列表调整语言和策略
   - 若包含 ClinicalTrials.gov → 追加一段英文检索指令
   - 若包含中文数据源CNKI/中华医学期刊网)→ 保留中文检索关键词
6. **不得自行添加约束**:不要擅自限定"仅开放获取"或"仅英文文献"

## 用户输入
- 研究想法:{{originalQuery}}
- 选择的数据源:{{targetSources}}
- 时间范围:{{yearRange}}
- 目标数量:{{targetCount}}

## 输出格式
请同时输出两部分:
### Part 1: 自然语言检索指令书
(可编辑的完整检索需求描述)

### Part 2: 结构化摘要JSON
\`\`\`json
{
  "objective": "...",
  "population": "...",
  "intervention": "...",
  "comparison": "...",
  "outcome": "...",
  "studyDesign": ["RCT", "Meta-analysis", ...],
  "meshTerms": ["term1", "term2", ...],
  "condition": "..."
}
\`\`\`
`;

4.3 Prompt 管理集成

层级 说明
运营端 Prompt 管理界面 → ASL_DEEP_RESEARCH_EXPANSION → 可在线编辑、版本管理、A/B 测试
代码 Fallback prompt.fallbacks.ts 写入默认模板,数据库无记录时自动使用
调用方式 promptService.getPrompt('ASL_DEEP_RESEARCH_EXPANSION') → 填充变量 → LLMFactory 调用

5. API 契约

5.1 需求扩写(同步)

POST /api/v1/asl/research/generate-requirement

// 请求
{ 
  originalQuery: string,       // "他汀预防心血管疾病要能下载PDF的"
  targetSources: string[],     // 从精选数据源列表中选择(见 5.1.1
  filters: {
    yearRange?: string,        // "2010至今" | "过去5年" | "不限"
    targetCount?: string,      // "~100篇" | "全面检索"
    requireOpenAccess?: boolean // true
  }
}

// 响应
{
  success: true,
  data: {
    taskId: "uuid",                     // 已创建DB记录status=draft
    generatedRequirement: "请帮我执行一次深度的医学文献检索...",  // LLM扩写结果
    intentSummary: {                    // PICOS + MeSH 结构化摘要
      objective: "为Meta分析构建测试语料库",
      population: "心血管疾病高危患者",
      intervention: "他汀类药物 (Statins, HMG-CoA Reductase Inhibitors)",
      comparison: "安慰剂/未使用他汀",
      outcome: "主要不良心血管事件 (MACE) 发生率",
      studyDesign: ["RCT", "Meta-analysis", "Cohort"],
      meshTerms: ["Hydroxymethylglutaryl-CoA Reductase Inhibitors", "Cardiovascular Diseases", "Primary Prevention"],
      condition: "心血管疾病 (CVD)"
    }
  }
}

5.1.1 精选数据源配置

基于 Unifuncs API 18 站实测结果精选的 5 个数据源:

类别 数据源 domain_scope 值 默认 备注
🌍 英文 PubMed https://pubmed.ncbi.nlm.nih.gov/ 默认勾选 一级可用,核心数据源
🌍 英文 ClinicalTrials.gov https://clinicaltrials.gov/ ☐ 可选 ⚠️ 前端提示"需英文查询"
🌍 英文 Cochrane Library https://www.cochranelibrary.com/ ☐ 可选 一级可用,系统综述金标准
🇨🇳 中文 中国知网 CNKI https://www.cnki.net/ ☐ 可选 二级可达,中文文献
🇨🇳 中文 中华医学期刊网 https://medjournals.cn/ ☐ 可选 二级可达,中文文献

实现要点:

  • 调用 promptService.getPrompt('ASL_DEEP_RESEARCH_EXPANSION') 获取 Prompt无数据库记录时走 Fallback
  • 填充变量 {{originalQuery}}{{targetSources}}{{yearRange}}{{targetCount}}
  • 调用 LLMFactory.getAdapter('deepseek-v3') 执行扩写
  • 解析 LLM 输出Part 1 → generatedRequirementPart 2 JSON → intentSummary
  • 创建 DB 记录status = draft

5.2 启动执行(进入异步队列)

PUT /api/v1/asl/research/tasks/:id/execute

// 请求
{
  confirmedRequirement: string  // 用户核验修改后的最终指令书
}

// 响应
{ success: true }

实现要点:

  • 更新 DB 的 confirmed_requirementtarget_sources
  • jobQueue.push('asl_deep_research_v2', { taskId }) 推入 pg-boss
  • status 更新为 pending

5.3 任务状态与日志轮询

GET /api/v1/asl/research/tasks/:id

// 响应
{
  success: true,
  data: {
    taskId: "uuid",
    status: "running",                  // draft/pending/running/completed/failed
    executionLogs: [                    // 终端日志(增量)
      { type: "think", title: "任务理解", text: "已收到检索需求...", ts: "..." },
      { type: "action", title: "Search", text: "executing search across PubMed...", ts: "..." },
      { type: "done", title: "搜索轮次完成", text: "", ts: "..." },
    ],
    progress: { current: 60, total: 100 },
    // 仅 completed 时有:
    synthesisReport: "## 研究背景\n他汀类药物...",
    resultList: [
      { title: "...", authors: "...", journal: "...", year: 2010, type: "Meta-analysis", pmid: "...", doi: "...", pdfStatus: "OA" },
    ],
    resultCount: 103,
    errorMessage: null
  }
}

5.4 Word 导出

GET /api/v1/asl/research/tasks/:id/export-word

  • 读取 DB 的 synthesis_reportMarkdownresult_listJSON
  • 拼接为完整 Markdown报告 + 文献清单表格)
  • 调用 Pandoc 转 Word
  • 返回 .docx 文件流

5.5 路由汇总

方法 路径 说明 新增/改造
POST /research/generate-requirement 需求扩写 新增
PUT /research/tasks/:id/execute 启动执行 新增
GET /research/tasks/:id 状态+日志+结果 改造
GET /research/tasks/:id/export-word Word 导出 新增
POST /research/stream V1 SSE保留兼容 不动
POST /research/tasks V1 异步创建(保留) 不动

6. 后台 Worker 逻辑

6.1 核心流程(伪代码)

// backend/src/modules/asl/workers/deepResearchV2Worker.ts

export async function processDeepResearchV2(job: Job) {
  const { taskId } = job.data;
  const task = await prisma.aslResearchTask.findUnique({ where: { id: taskId } });

  // 1. 调用 Unifuncs 创建异步任务
  const unifuncsPayload = {
    model: "s2",
    messages: [{
      role: "user",
      content: `请根据以下详细检索需求执行深度研究:\n${task.confirmedRequirement}`
    }],
    introduction: buildIntroduction(),
    max_depth: 25,
    domain_scope: task.targetSources || ["https://pubmed.ncbi.nlm.nih.gov/"],
    domain_blacklist: ["wanfang.com", "cnki.net"],
    output_prompt: buildOutputPrompt(),
    reference_style: "link",
    generate_summary: true,
  };

  const createRes = await unifuncsClient.createTask(unifuncsPayload);
  const unifuncsTaskId = createRes.data.task_id;

  await prisma.aslResearchTask.update({
    where: { id: taskId },
    data: { externalTaskId: unifuncsTaskId, status: 'running' }
  });

  // 2. 轮询 Unifuncs 直到完成(含指数退避重试)
  let previousReasoning = '';
  const MAX_POLLS = 180;       // 最多 15 分钟180 × 5s
  let consecutiveErrors = 0;   // 连续错误计数
  const MAX_CONSECUTIVE_ERRORS = 5;

  for (let i = 0; i < MAX_POLLS; i++) {
    await sleep(5000);

    let data: any;
    try {
      const queryRes = await unifuncsClient.queryTask(unifuncsTaskId);
      data = queryRes.data;
      consecutiveErrors = 0;  // 成功后重置
    } catch (err) {
      consecutiveErrors++;
      logger.warn(`Unifuncs query_task 第 ${consecutiveErrors} 次失败`, { taskId, error: err.message });

      if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
        throw new Error(`Unifuncs 连续 ${MAX_CONSECUTIVE_ERRORS} 次查询失败: ${err.message}`);
      }

      // 指数退避2s → 4s → 8s → 16s → 32s
      const backoffMs = Math.min(2000 * Math.pow(2, consecutiveErrors - 1), 32000);
      await sleep(backoffMs);
      continue;
    }

    // 解析增量日志
    const currentReasoning = data.result?.reasoning_content || '';
    if (currentReasoning.length > previousReasoning.length) {
      const increment = currentReasoning.slice(previousReasoning.length);
      const newLogs = parseReasoningToLogs(increment);
      await appendExecutionLogs(taskId, newLogs);
      previousReasoning = currentReasoning;
    }

    // 同步进度
    if (data.progress) {
      // progress 信息可通过 executionLogs 的最后一条体现
    }

    // 检查完成
    if (data.status === 'completed') {
      const content = data.result?.content || '';
      const report = extractSection(content, 'REPORT_SECTION');
      const jsonList = extractSection(content, 'JSON_LIST_SECTION');
      const parsedList = safeParseJsonList(jsonList);  // 防崩溃 JSON 解析,见 6.4

      await prisma.aslResearchTask.update({
        where: { id: taskId },
        data: {
          status: 'completed',
          rawResult: content,
          reasoningContent: currentReasoning,
          synthesisReport: report || content,
          resultList: parsedList,
          resultCount: parsedList?.length || 0,
          tokenUsage: data.statistics?.token_usage,
          searchCount: data.statistics?.search_count,
          readCount: data.statistics?.read_count,
          iterations: data.statistics?.iterations,
          completedAt: new Date(),
        }
      });
      return;
    }

    if (data.status === 'failed') {
      throw new Error(data.result?.content || 'Unifuncs 任务失败');
    }
  }

  throw new Error('任务超时15分钟');
}

v1.1 新增 — 轮询韧性设计:

机制 策略 说明
瞬态失败重试 指数退避 2s → 4s → 8s → 16s → 32s 网络抖动、Unifuncs 临时不可用时自动恢复
连续失败阈值 MAX_CONSECUTIVE_ERRORS = 5 连续 5 次查询全失败才标记任务 failed
成功后重置 consecutiveErrors = 0 中间穿插成功不累计

6.2 日志解析逻辑

function parseReasoningToLogs(increment: string): LogEntry[] {
  const logs: LogEntry[] = [];
  const lines = increment.split('\n').filter(l => l.trim());

  for (const line of lines) {
    if (line.includes('搜索') || line.includes('search') || line.includes('Search')) {
      logs.push({ type: 'action', title: 'Search', text: line.trim(), ts: new Date().toISOString() });
    } else if (line.includes('阅读') || line.includes('read') || line.includes('Read')) {
      logs.push({ type: 'action', title: 'Read', text: line.trim(), ts: new Date().toISOString() });
    } else if (line.includes('完成') || line.includes('成功') || line.includes('OK')) {
      logs.push({ type: 'done', title: '阶段完成', text: line.trim(), ts: new Date().toISOString() });
    } else if (line.includes('汇总') || line.includes('总结') || line.includes('发现')) {
      logs.push({ type: 'summary', title: '阶段总结', text: line.trim(), ts: new Date().toISOString() });
    } else if (line.trim().length > 10) {
      logs.push({ type: 'think', title: 'Thinking', text: line.trim(), ts: new Date().toISOString() });
    }
  }
  return logs;
}

6.3 output_prompt 设计

function buildOutputPrompt(): string {
  return `请严格按照以下格式输出结果:

<REPORT_SECTION>
[此处撰写深度综合研究报告,使用 Markdown 格式,包括:
- 研究背景与目的
- 核心发现与共识
- 分歧点与研究空白
- 参考文献列表带编号和PubMed链接]
</REPORT_SECTION>

<JSON_LIST_SECTION>
[此处输出文献元数据的严格 JSON 数组,每条包含:
{"title":"...", "authors":"...", "journal":"...", "year":2024, "type":"RCT|Meta-analysis|Cohort|SR", "pmid":"...", "doi":"...", "pdfStatus":"OA|Restricted", "url":"https://pubmed.ncbi.nlm.nih.gov/..."}]
</JSON_LIST_SECTION>`;
}

6.4 JSON 解析防崩溃v1.1 新增)

LLM 输出的 JSON 常携带 ```json 代码围栏或尾部逗号,直接 JSON.parse 会崩溃。

function safeParseJsonList(raw: string | null): any[] | null {
  if (!raw) return null;

  // Step 1: 去除 Markdown 代码围栏
  let cleaned = raw.replace(/```json\s*/gi, '').replace(/```\s*/g, '');

  // Step 2: 去除尾部逗号(数组/对象末尾的 ,] 或 ,}
  cleaned = cleaned.replace(/,\s*([}\]])/g, '$1');

  // Step 3: 尝试解析
  try {
    const parsed = JSON.parse(cleaned);
    return Array.isArray(parsed) ? parsed : [parsed];
  } catch (e) {
    logger.warn('JSON 解析失败,尝试正则提取', { error: e.message });
    // Step 4: 降级 — 尝试逐行提取 JSON 对象
    const objects: any[] = [];
    const regex = /\{[^{}]*\}/g;
    let match;
    while ((match = regex.exec(cleaned)) !== null) {
      try {
        objects.push(JSON.parse(match[0]));
      } catch { /* 跳过无法解析的单条 */ }
    }
    return objects.length > 0 ? objects : null;
  }
}

防崩溃策略总结:

层级 处理 覆盖场景
L1 去除 ```json 围栏 LLM 习惯性包裹代码块
L2 去除尾部逗号 [{...}, {...},][{...}, {...}]
L3 标准 JSON.parse 正常路径
L4 正则逐条提取 JSON 结构被破坏但单条仍有效
L5 返回 null 彻底无法解析,前端降级展示报告

7. 前端组件设计

7.1 页面结构

frontend-v2/src/modules/asl/pages/
└── DeepResearchPage.tsx            # V2.0 主页面(替代 ResearchSearch.tsx

frontend-v2/src/modules/asl/components/
├── deep-research/
│   ├── LandingView.tsx             # Landing 大搜索框
│   ├── SetupPanel.tsx              # Step 1: 配置面板
│   ├── StrategyConfirm.tsx         # Step 2: HITL 策略确认(左右分栏)
│   ├── AgentTerminal.tsx           # Step 3: 暗黑执行终端
│   └── ResultsView.tsx             # Step 4: 结果展示(报告+表格)

7.2 状态管理

设计决策: React Query + useState不引入 Zustandv1.1 确认)

状态类型 工具 理由
服务端数据(任务状态、日志、结果) @tanstack/react-query 自带缓存、去重、条件轮询refetchInterval完美匹配轮询场景
页面步骤流转 useState 仅 5 个步骤的 FSM组件树内部流转无需全局状态
组件间共享(如 taskId props drilling / React.memo 组件层级仅 2-3 层prop 传递足够,不需要 Context/Zustand
// 页面级状态useState 即可,不引入 Zustand
interface DeepResearchState {
  currentStep: 'landing' | 'setup' | 'strategy' | 'terminal' | 'results';
  taskId: string | null;
  originalQuery: string;
  generatedRequirement: string;
  intentSummary: IntentSummary | null;
  isGenerating: boolean;  // 需求扩写中
}

// PICOS + MeSH 结构化摘要v1.1 新增)
interface IntentSummary {
  objective: string;
  population: string;
  intervention: string;
  comparison: string;
  outcome: string;
  studyDesign: string[];
  meshTerms: string[];
  condition: string;
}

7.3 各组件核心逻辑

LandingViewLanding 大搜索框)

  • 居中大输入框 + "开始研究"按钮 + 推荐预置词
  • 点击后携带输入值,平滑过渡到 SetupPanel
  • 参考原型图 V4.2 的 #landing-view 部分

SetupPanelStep 1: 配置) (v1.1 数据源更新)

  • 继承 Landing 输入值到 textarea
  • 数据源 Checkbox基于实测精选 5 个,见 5.1.1
    • 🌍 英文数据源
      • PubMed默认勾选不可取消
      • ClinicalTrials.gov — ⚠️ 旁标橙色提示:"该站点需要英文查询,系统将自动为此数据源生成英文检索指令"
      • Cochrane Library
    • 🇨🇳 中文数据源
      • 中国知网 CNKI
      • 中华医学期刊网
  • 高级过滤年份下拉、目标数量、OA 偏好 — 注意是偏好非强制)
  • 点击"解析并生成检索需求书" → POST /generate-requirement
  • Loading 后平滑展开 Step 2

StrategyConfirmStep 2: HITL 确认) (v1.1 PICOS + MeSH)

  • 左侧 1/3AI 意图提炼卡片只读PICOS 结构化展示)
    • 🎯 研究目标:objective
    • 👥 研究人群:population
    • 💊 干预措施:intervention(含 MeSH 英文术语)
    • ⚖️ 对照组:comparison
    • 📊 结局指标:outcome
    • 📋 研究设计:studyDesign Tag 列表
    • 🏷️ MeSH 术语:meshTerms 小标签展示
  • 右侧 2/3可编辑 textarea内容为 generatedRequirement,自然语言对话风格)
  • 提示文案:"这是 AI 以医学信息官的视角为您扩写的检索需求,您可以像写邮件一样直接编辑修改"
  • 点击"确认需求,启动 Deep Research" → PUT /execute

AgentTerminalStep 3: 暗黑终端) (v1.1 条件滚动)

  • 暗色背景bg-slate-900固定高度 550px内部滚动
  • 顶部状态栏:红/黄/绿圆点 + "Running" 脉冲指示灯
  • 日志渲染:
    • think → 紫色 + 🧠 图标
    • action → 蓝色 + 💻 图标
    • done → 绿色 + 图标
    • summary → 黄色 + 📋 图标
  • 轮询逻辑:useQuery + refetchInterval: 3000running 时启用)
  • 条件自动滚动v1.1 新增):
    • 用户未手动上滚时 → 新日志自动滚到底部
    • 用户已手动上滚查看历史 → 停止自动滚动,避免打断阅读
    • 实现:onScroll 检测 scrollTop + clientHeight < scrollHeight - thresholduserScrolled flag
  • 完成后状态灯变灰 "Finished",终端可折叠

ResultsViewStep 4: 结果)

  • 白色背景,与终端形成视觉分界
  • 完成横幅(一行):文献数 + 耗时 + "导出 Word" 按钮
  • AI 综合报告区:react-markdown 渲染 synthesisReport,可折叠,默认展开
  • 文献清单表格Ant Design Table
    • 列:标题(可点击跳转 PubMed、期刊、年份、类型 Tag、PDF 状态
    • 支持简单搜索过滤
    • 分页(前端分页即可,数据量 ~100 条)
    • 降级展示:若 resultList 为 nullJSON 解析失败),隐藏表格,仅展示综合报告

7.4 轮询 Hook

// hooks/useDeepResearchTask.ts
function useDeepResearchTask(taskId: string | null) {
  return useQuery({
    queryKey: ['deep-research-task', taskId],
    queryFn: () => apiClient.get(`/api/v1/asl/research/tasks/${taskId}`),
    enabled: !!taskId,
    refetchInterval: (query) => {
      const status = query.state.data?.data?.status;
      return (status === 'pending' || status === 'running') ? 3000 : false;
    },
  });
}

8. 复用清单(不重复造轮子)

能力 来源 用法
LLM 调用 common/llm/LLMFactory DeepSeek-V3 需求扩写
Prompt 管理 common/prompt/promptService ASL_DEEP_RESEARCH_EXPANSION Prompt 获取(运营端可配 + 代码 Fallback
pg-boss 队列 common/jobs/jobQueue Worker 注册与任务推送
日志服务 common/logging/logger 全程结构化日志
认证中间件 common/auth/authenticate 所有 API 路由
Prisma 全局实例 config/database 数据库操作
Word 导出 PandocPython 微服务) 复用 Protocol Agent 验证的方案
DeepResearch 引擎 common/deepresearch/ Unifuncs API 封装create_task / query_task
前端 API Client common/api/axios 带认证的请求
前端布局 ASLLayout.tsx 左侧导航

9. 分阶段开发计划

Phase 1: 数据库 + Prompt 管理 + 需求扩写Day 1

目标: 用户输入粗略想法 → AI 按 PICOS 框架扩写为结构化指令书 → 用户可编辑修改

任务 文件 说明
Schema 迁移 prisma/schema.prisma 新增 6 个字段,prisma migrate dev
Prompt Fallback common/prompt/prompt.fallbacks.ts 新增 ASL_DEEP_RESEARCH_EXPANSION 兜底模板PICOS + MeSH
需求扩写服务 services/requirementExpansionService.ts 新建,调用 promptService → LLMFactory → 解析 Part 1/Part 2 输出
扩写 API controllers/researchController.ts 新增 POST /generate-requirement
启动 API controllers/researchController.ts 新增 PUT /tasks/:id/execute
状态 API 改造 controllers/researchController.ts 改造 GET /tasks/:id,返回新字段(含 PICOS intentSummary
数据源配置 config/dataSources.ts 新建,定义 5 个精选数据源常量domain、label、默认状态、备注
路由注册 routes/index.ts 注册新端点

验收标准:

  • POST /generate-requirement 返回 PICOS 结构化摘要 + 自然语言指令书
  • Prompt 从数据库加载,无记录时自动走 Fallback
  • PUT /tasks/:id/execute 成功推入 pg-boss 队列
  • GET /tasks/:id 返回含新字段的完整数据
  • 选择 ClinicalTrials.gov 时,扩写结果包含英文检索指令段

Phase 2: Worker 改造 — Unifuncs 异步模式Day 2

目标: Worker 使用 create_task + query_task 轮询(含指数退避),增量日志写入 DB

任务 文件 说明
Unifuncs 异步客户端 services/unifuncsAsyncClient.ts 新建,封装 create_task / query_task
V2 Worker workers/deepResearchV2Worker.ts 新建,轮询 + 指数退避重试 + 日志解析 + 结果切割
日志解析器 utils/reasoningParser.ts 新建reasoning_content → 结构化日志
结果解析器 utils/resultParser.ts 新建XML 标签切割 + safeParseJsonList 防崩溃解析
Worker 注册 workers/researchWorker.ts 注册新 Worker asl_deep_research_v2

验收标准:

  • Worker 成功调用 unifuncs create_task
  • 轮询期间 execution_logs 持续增量更新
  • 完成后 synthesis_report 和 result_list 正确入库
  • 韧性测试:模拟单次 query_task 失败 → 指数退避后自动恢复
  • JSON 防崩溃LLM 输出带 ```json 围栏 → safeParseJsonList 正确解析
  • 超时保护15 分钟)和错误处理正常

Phase 3: 前端 — Landing + 配置 + HITL 确认Day 3

目标: 完成 Step 1-2 的前端交互瀑布流渐进展开PICOS 结构化展示

任务 文件 说明
主页面骨架 pages/DeepResearchPage.tsx 新建useState 管理瀑布流步骤
Landing 组件 components/deep-research/LandingView.tsx 大搜索框 + 推荐预置词
配置面板 components/deep-research/SetupPanel.tsx 精选 5 数据源 Checkbox(含 ClinicalTrials.gov 英文提示)+ 高级过滤
HITL 确认 components/deep-research/StrategyConfirm.tsx 左侧 PICOS + MeSH 卡片 + 右侧可编辑 textarea
API 函数 api/index.ts 新增 generateRequirement / executeTask
路由注册 pages/index.tsx 新增 V2 路由

验收标准:

  • Landing 输入 → Step 1 配置面板流畅过渡
  • 数据源显示 5 个选项PubMed 默认勾选ClinicalTrials.gov 标注英文提示
  • 点击"生成需求书" → Loading → Step 2 展开
  • Step 2 左侧 PICOS 结构化摘要 + MeSH 术语标签正确展示
  • Step 2 右侧 textarea 显示自然语言对话风格的检索指令书,可编辑
  • 点击"启动 Deep Research" → 进入 Step 3

Phase 4: 前端 — 终端 + 结果展示Day 4

目标: 完成 Step 3-4终端实时日志条件滚动+ 结果报告/表格(含降级)

任务 文件 说明
暗黑终端 components/deep-research/AgentTerminal.tsx 日志渲染 + 条件 auto-scroll + 状态灯
结果视图 components/deep-research/ResultsView.tsx 横幅 + 报告 + 文献表格 + 降级展示
轮询 Hook hooks/useDeepResearchTask.ts React Query 3s 轮询running 时启用
终端样式 CSS / Tailwind 暗色主题 + 日志类型着色

验收标准:

  • 终端日志按类型着色,未手动滚动时 auto-scroll手动上滚时暂停
  • 完成后终端折叠,结果区展开
  • 综合报告 Markdown 渲染正确
  • 文献清单表格展示(标题可点击跳转 PubMed
  • 降级验证resultList 为 null 时,隐藏表格仅展示报告
  • 全流程端到端联调通过

Phase 5: Word 导出 + 收尾Day 5

目标: Word 导出功能 + 全流程打磨 + 测试

任务 文件 说明
Word 导出 API controllers/researchController.ts GET /tasks/:id/export-word
Markdown 拼接 services/wordExportService.ts 报告 + 文献表格 → 完整 Markdown
Pandoc 调用 复用 Python 微服务 Markdown → .docx
前端导出按钮 ResultsView.tsx 下载 Word 文件
全流程测试 手动 + 脚本 端到端验证
文档更新 模块状态文档 更新 ASL 模块当前状态

验收标准:

  • 点击"导出 Word" → 下载包含报告和文献清单的 .docx
  • 全流程Landing → 配置 → 扩写 → 确认 → 执行 → 日志 → 结果 → 导出
  • 离开页面回来,能恢复查看正在执行/已完成的任务
  • 错误情况处理unifuncs 超时、API 报错、网络中断)

10. 验收标准总览

功能验收

  • Landing 引导:用户输入粗略想法 → 进入配置
  • 需求扩写AI 自动扩写为结构化自然语言指令书
  • HITL 核验:用户可直接编辑修改指令书
  • 异步执行pg-boss 队列,离开页面任务不中断
  • 终端日志:每 3-5s 弹出一条结构化日志
  • 综合报告Markdown 渲染,内容来自 Unifuncs 输出
  • 文献清单:表格展示,标题可跳转 PubMed
  • Word 导出:一键导出报告 + 文献清单

非功能验收

  • V1.x SSE 端点保留不动(向后兼容)
  • 所有 API 经过 authenticate 中间件
  • 日志使用 logger(非 console.log
  • 无硬编码配置API Key 来自环境变量)
  • 数据库变更通过 Prisma migrate非 db push

11. 风险与应对

风险 概率 影响 应对措施
Unifuncs 异步模式下 reasoning_content 不增量更新 终端日志为空 降级方案:只显示 progress.message
output_prompt XML 标签分割不可靠 报告和列表无法分离 降级方案:整体作为报告展示,文献从 PubMed 链接提取
LLM 输出 JSON 格式不规范 文献列表解析失败 safeParseJsonList 四层防崩溃(围栏清理 → 尾逗号 → 标准解析 → 正则逐条),见 6.4
Unifuncs query_task 瞬态失败 轮询中断 指数退避重试2s→32s连续 5 次失败才标记 failed见 6.1
Unifuncs 长任务超时 任务失败 MAX_POLLS=18015分钟超时标记 failed用户可重试
ClinicalTrials.gov 中文查询失败 临床试验检索无结果 Prompt 自动为该数据源生成英文检索指令段,前端标注提示
Pandoc Word 导出在 SAE 不可用 导出失败 降级方案:导出为 Markdown 文件
Prompt 管理服务不可用 需求扩写失败 代码内置 Fallback 模板,数据库无记录时自动使用

附录v1.1 更新变更记录

变更项 章节 说明
Prompt 管理集成 §4新增 需求扩写 Prompt 通过 Prompt 管理服务配置,含 PICOS + MeSH 扩展
精选数据源 §5.1.1(新增) 基于 18 站实测精选 5 个数据源3英文+2中文
指数退避重试 §6.1(更新) Worker 轮询增加瞬态失败指数退避2s→32s
JSON 防崩溃 §6.4(新增) safeParseJsonList 四层解析策略
PICOS 摘要 §7.2, §7.3(更新) IntentSummary 扩展为 PICOS + MeSH 结构
条件自动滚动 §7.3(更新) AgentTerminal 手动上滚时暂停 auto-scroll
状态管理确认 §1.2, §7.2(更新) 确认 React Query + useState不引入 Zustand
降级展示 §7.3(更新) ResultsView 在 resultList=null 时仅展示报告

文档维护者: 开发团队
最后更新: 2026-02-22
文档状态: v1.1 方案确认(含审查建议),待开发启动