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>
This commit is contained in:
2026-02-23 13:21:52 +08:00
parent b06daecacd
commit 8f06d4f929
39 changed files with 5605 additions and 417 deletions

View File

@@ -0,0 +1,72 @@
# **Deep Research V2.0 开发计划审查与优化建议书**
**审查对象:** 07-Deep Research V2.0 开发计划.md
**审查基准:** V4.1 PRD、Unifuncs API 官方文档、系统高可用性架构标准
**审查结论:** 整体架构设计pg-boss \+ 异步轮询 \+ 单页瀑布流)非常优秀。但在**容错机制、数据解析提取、前端状态管理、以及终端 UX 细节**上存在明显隐患,需进行针对性修正。
## **🔴 一、 核心高危风险与修正建议 (Critical Risks & Fixes)**
### **1\. Unifuncs 轮询逻辑的“脆弱性” (Worker 改造部分)**
* **原计划缺陷:** 在 deepResearchV2Worker.ts 的伪代码中,轮询逻辑简单地使用了 await unifuncsClient.queryTask()。如果在长达 15 分钟的轮询中,发生了一次短暂的网络抖动或 Unifuncs 网关返回了 502/504整个 try-catch 就会抛出异常,导致这个耗费了大量时间的长任务直接 failed。
* **修正方案:**
在 Worker 的 while 循环内部,**必须包裹 try-catch 并引入重试机制Exponential Backoff**。遇到网络请求失败时,不能直接退出,而是应该记录 warning 并 continue 等待下一次 5s 后的轮询,连续失败超过 5 次才判定任务崩溃。
### **2\. 输出解析的“幻觉陷阱” (JSON Parsing)**
* **原计划缺陷:** 计划中提到使用 extractSection 提取 \<JSON\_LIST\_SECTION\> 后,直接调用 safeParseJsonList。但在实际的大模型输出中即便你规定了 XML 标签,模型极大概率会在标签内输出 Markdown 代码块,例如:
\<JSON\_LIST\_SECTION\>
\`\`\`json
\[{"title": "..."}\]
\</JSON\_LIST\_SECTION\>
如果直接 \`JSON.parse()\` 必定报错崩溃。
* **修正方案:**
utils/resultParser.ts 中的 safeParseJsonList 方法**必须**包含预处理逻辑:
1. 使用正则清洗掉可能存在的 json\` 和 首尾包裹符。
2. 增加**兜底方案 (Fallback)**:如果正则和清洗均宣告失败,调用一次系统自带的 DeepSeek-V3传入提取出的坏字符串要求其强制吐出标准 JSON Array利用 response\_format: { type: "json\_object" }),确保 Step 4 的表格必定能渲染。
## **🟡 二、 前端架构与交互体验优化 (Frontend UX/Architecture)**
### **1\. 终端日志自动滚动的“反人类”交互 (AgentTerminal)**
* **原计划缺陷:** 计划中提到“新日志出现时 auto-scroll 到底部”。如果用户在长达 5 分钟的执行过程中,向上滚动滚轮想查看之前的思考日志,此时新日志一推过来,页面又被强制拉回到底部,这种体验非常“反人类”。
* **修正方案:**
修改 Auto-scroll 触发逻辑:前端通过 ref 判断当前滚动条位置,**只有当用户处于滚动条最底部(或距离底部 \< 50px收到新日志才自动滚动到底部**。如果用户已经向上滚动阅读历史,则只在容器内部静默追加日志,不改变视口位置(可浮现一个“↓ 有新日志”的提示小红点)。
### **2\. 前端状态管理的“螺旋地狱” (State Management)**
* **原计划缺陷:** 计划在 6.2 节中明确写道“页面级状态useState 即可,无需 Zustand”。
这是一个有 4 个复杂步骤Landing \-\> Setup \-\> HITL \-\> Terminal \-\> Results的单页应用。如果将 taskId、query、logs、step 全塞在父组件的 useState 里,会导致极其严重的**属性透传Prop Drilling和父组件频繁全量重渲染**。
* **修正方案:**
坚决反对在此场景下仅使用 useState。建议使用现有的 @tanstack/react-query 缓存状态,或者引入极其轻量的 Context API / Zustand。特别是终端日志Logs频繁追加时不能让整个大页面跟着一起重绘终端组件应当是局部渲染的。
## **🟢 三、 业务细节与数据闭环补充 (Business Details)**
### **1\. Word 导出功能的缺失逻辑 (Word Export)**
* **原计划缺陷:** 提到“报告 \+ 文献表格 → 完整 Markdown → Pandoc 转 Word”。但 result\_list 存的是 JSON ArrayPandoc 不认识 JSON。
* **修正方案:**
在 services/wordExportService.ts 中,需要明确增加一步\*\*“JSON to Markdown Table”\*\*的转化方法。
即把 JSON 数组转化为:
| 文献标题 | 期刊 | 发表年份 | 链接 |
|---|---|---|---|
| Efficacy of... | Lancet | 2023 | \[PubMed\](...) |
再将这个生成的 Markdown Table 拼接到 synthesis\_report 的末尾,最后统一送给 Pandoc 渲染。
### **2\. 第一步“需求扩写”的成本追踪漏洞**
* **原计划缺陷:**
数据库 Schema 只增加了 Unifuncs 任务的 tokenUsage 记录。但 Step 1 中,调用本地 DeepSeek-V3 把一句话扩写成几百字的“自然语言指令书”,也是要消耗 Token 的。
* **修正方案:**
在 AslResearchTask 模型中,应将 tokenUsage 分离或扩展为能够记录两次消耗的结构。例如internal\_token\_usage (记录扩写的成本) 和 external\_token\_usage (记录 Unifuncs 成本),以实现精细化的财务核算。
## **📝 最终评审结论**
请开发团队长仔细阅读上述 6 条修正建议,并同步更新到研发 Task 列表中。
特别是在\*\*【JSON解析防崩溃】**和**【终端条件自动滚动】\*\*这两点上,直接决定了本次 V2.0 交付的“工业级质感”。只要把控好这几个细节,原有的技术路线完全可以跑通,且效果会非常惊艳!

View File

@@ -0,0 +1,114 @@
# **Deep Research 需求扩写 Prompt 设计与检索维度指南**
**设计者:** 医学信息官 (MIO)
**应用场景:** ASL Deep Research V2.0 (MVP) \- Step 1 需求转化阶段
**底层引擎:** 本地 DeepSeek-V3 / Qwen-Max (负责扩写) \-\> Unifuncs (负责执行)
## **一、 文献检索维度的解构 (MIO 视角)**
要把用户的“一句话”扩写成一份严谨的检索指令书LLM 必须脑补并结构化以下维度。这些维度将直接决定 Unifuncs 引擎去哪些网页、抓取什么关键词、过滤什么条件。
### **🚨 1\. 必须项 (Mandatory Fields \- 决定检索成败)**
如果用户没提LLM 必须根据医学常识自动补齐其合理范围;如果补不齐,应设置默认的最优标准。
1. **核心疾病/目标人群 (Population):** 必须明确适应症。*(例如用户说“他汀”LLM必须根据常识扩写出可能针对的“高脂血症”或“心血管疾病预防”人群)*。
2. **核心干预措施 (Intervention):** 具体的药物、疗法或器械。
3. **目标文献类型 (Study Design \- 极度重要):** 在循证医学中文献类型决定了证据等级。如果用户没说LLM 默认必须限定为:**高质量的随机对照试验 (RCT)、前瞻性队列研究、系统综述与 Meta 分析**。绝不能让引擎去抓取个案报道 (Case Report) 或动物实验。
4. **获取约束 (Access Rule):** 这是我们系统的硬杠杠。必须强制要求:**仅限开放获取 (Open Access) 且包含完整 PDF 链接的文献**。
### **💡 2\. 可选项 (Optional Fields \- 提升查准率)**
用户如果有提及要强化如果没提及LLM 可以适当发散或留白。
1. **对照组 (Comparison):** 安慰剂 (Placebo) 还是标准疗法 (Standard of Care)。
2. **特定结局指标 (Outcomes):** 比如 OS (总生存期)、MACE (主要心血管不良事件)、AEs (不良反应)。
3. **同义词与 MeSH 词扩展 (Synonyms Expansion):** 这是体现“专业性”的核心!用户可能只输入了 "心衰"LLM 必须在指令书里附带提供 "Heart Failure", "HFrEF", "HFpEF" 等检索词,供 Unifuncs 扩大查全率。
## **二、 核心 System Prompt 设计 (可以直接写入代码)**
请将以下 Prompt 部署到 services/requirementExpansionService.ts 中。
\# Role
你是一名受过严谨训练的“临床医学信息专家Medical Information Officer”与循证医学图书管理员。
\# Task
用户的输入往往是口语化且不完整的临床检索诉求。你的任务是将用户简短的一句话,扩写、翻译并结构化为一份《深度检索指令书》。
这份指令书将直接发送给另一个拥有自主上网能力的 AI 深搜引擎Agent去执行。因此你的语言必须清晰、专业、逻辑严密并利用你的医学知识为用户补全隐含的专业要求。
\# Instructions & Rules
1\. \*\*理解与补全 (PICOS)\*\* 基于用户的输入,识别 P (人群)、I (干预)、C (对照)、O (结局)、S (研究类型)。
2\. \*\*强制文献质量过滤:\*\* 如果用户没有指定文献类型,你必须强制添加:“优先获取 随机对照试验(RCT)、前瞻性队列研究、系统综述与Meta分析排除动物实验、体外研究、个案报道及非英文文献”。
3\. \*\*强制数据可及性:\*\* 必须在指令中强调:“只提取开放获取 (Open Access) 且附带有效 PDF 全文下载链接的文献”。
4\. \*\*扩展专业检索词 (MeSH)\*\* 利用你的医学知识,将用户的中文或俗称,扩展为标准的英文医学主题词 (MeSH) 和常见缩写,写在【扩展检索词库】中,指导深搜引擎扩大召回率。
\# Output Format (严格遵循以下 Markdown 结构,不要输出额外废话)
请帮我执行一次深度的医学文献检索。以下是具体的检索要求:
【核心检索主题】
(一句话总结用户意图例如评估XXX在YYY中的疗效与安全性)
【目标人群与干预措施】
\- 人群 (Population)(详细描述疾病阶段、特征,如无明确要求则合理推断)
\- 干预 (Intervention / Comparison)(具体的药物、疗法及可能的对照)
【关注的结局指标 (Outcomes)】
(列出该领域常见的核心指标,如 OS, PFS, 并发症发生率等。如果用户指定了则用用户的)
【目标文献类型与质量要求】
\- 必须是:随机对照试验 (RCT)、队列研究、系统综述与 Meta 分析。
\- 强制排除:动物实验、体外细胞研究、个案报道。
\- 强制获取要求:必须是开放获取 (Open Access) 的文献,且必须能获取 PDF 全文或完整 XML 数据。对于需要付费墙 (Paywall) 的文献请予以忽略。
【扩展检索词库 (供搜索引擎参考)】
(列出 5-8 个核心的英文专业词汇、MeSH词或缩写用逗号分隔。例如Hydroxymethylglutaryl-CoA Reductase Inhibitors, Heart Failure, MACE...)
## **三、 效果演示 (Before vs After)**
你可以看到,经过这个 Prompt 处理后,原本干瘪的需求变得极其丰满,且直接指导了 Unifuncs 引擎该怎么做。
### **🧑‍⚕️ 医生的粗略输入 (前端传入)**
"替雷利珠单抗治疗非小细胞肺癌只要带PDF的"
### **🤖 LLM 扩写后的指令书 (前端 Step 2 展示给用户确认)**
请帮我执行一次深度的医学文献检索。以下是具体的检索要求:
**【核心检索主题】**
评估替雷利珠单抗Tislelizumab在治疗非小细胞肺癌NSCLC中的疗效与安全性。
**【目标人群与干预措施】**
* **人群 (Population)** 晚期、局部晚期或转移性非小细胞肺癌患者(可包含一线或二线治疗人群)。
* **干预 (Intervention / Comparison)** 替雷利珠单抗单药或联合化疗,对照组可为标准含铂双药化疗或安慰剂。
**【关注的结局指标 (Outcomes)】**
重点关注总生存期 (OS)、无进展生存期 (PFS)、客观缓解率 (ORR) 以及免疫相关不良事件 (irAEs) 的发生率。最好能提取到 HR 值及 95% CI。
**【目标文献类型与质量要求】**
* 必须是:随机对照试验 (RCT)、前瞻性队列研究、系统综述与 Meta 分析。
* 强制排除:动物实验、体外细胞研究、个案报道。
* 强制获取要求:必须是开放获取 (Open Access) 的文献,且必须能获取 PDF 全文或完整 XML 数据。对于需要付费墙 (Paywall) 的文献请予以忽略。
**【扩展检索词库 (供搜索引擎参考)】**
Tislelizumab, BGB-A317, Non-Small Cell Lung Cancer, NSCLC, Carcinoma, Non-Small-Cell Lung (MeSH), PD-1 Inhibitors, Overall Survival.
## **四、 为什么这种设计对 Unifuncs API 特别有效?**
Unifuncs 是一个基于大模型推理的 Agent。如果你只给它扔一句 "替雷利珠单抗治疗非小细胞肺癌",它会像个无头苍蝇一样在网上随便搜几个百度百科或知乎的新闻来交差。
但当你把上面这篇**充斥着 MeSH词、明确排除了动物实验、明确要求找 RCT、并且要求带有 PDF** 的大白话发给 Unifuncs 的 messages 时:
1. Unifuncs 会直接把 Tislelizumab AND NSCLC AND Randomized Controlled Trial 作为 PubMed 的搜索词。
2. 它在阅读网页时,看到没有 PDF 下载链接的,会自动根据指令里的【强制获取要求】执行 Discard放弃动作。
3. 它在总结时,会自动围绕【关注的结局指标】去撰写你的 synthesis\_report。
这就是大模型时代\*\*“用魔法(本地大模型)打败魔法(搜索大模型)”\*\*的最佳实践!