Completed: - Unifuncs DeepSearch API site coverage test (18 medical sites, 9 tier-1 available) - ClinicalTrials.gov dedicated test (4 strategies, English query + depth>=10 works best) - Deep Research V2.0 development plan (5-day phased delivery) - DeepResearch engine capability guide (docs/02-common-capability/) - Test scripts: test-unifuncs-site-coverage.ts, test-unifuncs-clinicaltrials.ts Key findings: - Tier-1 sites: PubMed(28), ClinicalTrials(38), NCBI(18), Scholar(10), Cochrane(4), CNKI(7), SinoMed(9), GeenMedical(5), VIP(1) - Paid databases (WoS/Embase/Scopus/Ovid) cannot be accessed (no credential support) - ClinicalTrials.gov requires English queries with max_depth>=10 Updated: ASL module status doc, system status doc, common capability list Co-authored-by: Cursor <cursoragent@cursor.com>
144 lines
6.9 KiB
Markdown
144 lines
6.9 KiB
Markdown
# **技术设计文档 (TDD):ASL \- 智能文献检索 (Deep Research) MVP 版**
|
||
|
||
**文档版本:** v4.4-Tech (MVP 自然语言确认版)
|
||
|
||
**核心架构:** LLM Requirement Expansion (需求扩写) \+ Postgres-Only (pg-boss) \+ Unifuncs Async API
|
||
|
||
## **🏗️ 1\. 系统数据流向 (Data Flow)**
|
||
|
||
MVP 版本的架构最大程度降低了状态维护的成本,充分利用 Unifuncs 原生支持自然语言查询的能力。
|
||
|
||
1. Client 发送原始简短自然语言 \-\> Node.js 调用 DeepSeek-V3 \-\> 返回**结构化、扩写后的自然语言检索需求(Search Requirements)**(非布尔检索式)。
|
||
2. Client 展示该检索需求(普通文本/Markdown),允许用户直接进行文字修改与补充 \-\> 用户点击执行,发送修改后的 Confirmed Requirement \-\> Node.js 创建 pg-boss 任务。
|
||
3. Worker 启动 \-\> 将用户确认的自然语言需求直接作为 content 传给 Unifuncs 创建任务 \-\> 每 5 秒轮询一次 Unifuncs \-\> 增量日志写入 PostgreSQL。
|
||
4. Client 每 3 秒轮询 Node.js 获取日志 \-\> 渲染 Terminal \-\> 任务完成,渲染结果。
|
||
|
||
## **🗄️ 2\. 数据库设计 (Prisma)**
|
||
|
||
极简表结构,去掉了复杂的澄清记录,只保留原始问题和最终执行的自然语言需求。
|
||
|
||
model AslResearchTask {
|
||
id String @id @default(uuid())
|
||
user\_id String
|
||
|
||
// Step 1 & 2
|
||
original\_query String @db.Text
|
||
target\_sources Json // 选中的数据源,如 \["pubmed.ncbi.nlm.nih.gov"\]
|
||
filters Json // 高级过滤条件
|
||
confirmed\_requirement String? @db.Text // 核心字段:用户复核并修改后的自然语言检索需求
|
||
|
||
// Step 3
|
||
status String // 'draft', 'pending', 'running', 'completed', 'failed'
|
||
unifuncs\_task\_id String? // 外部API的ID
|
||
execution\_logs Json? // 终端日志 \[{type: 'log', text: '...'}, ...\]
|
||
|
||
// Step 4
|
||
result\_list Json?
|
||
synthesis\_report String? @db.Text
|
||
|
||
created\_at DateTime @default(now())
|
||
updated\_at DateTime @updatedAt
|
||
}
|
||
|
||
## **🔌 3\. 核心 API 契约**
|
||
|
||
### **3.1 检索需求扩写接口 (同步, 无状态)**
|
||
|
||
* **POST /api/v1/asl/research/generate-requirement**
|
||
* **处理:** 拦截用户简短输入,拼装 System Prompt,调用系统内置 LLMFactory(DeepSeek-V3)。
|
||
* *System Prompt 示例:* “你是一个医学检索辅助专家。请将用户简短的研究意图,扩写并梳理成一份条理清晰的自然语言检索需求说明。内容包括:1. 核心检索主题;2. 建议包含的专业关键词(中英文);3. 目标人群及干预措施限定;4. 文献类型建议(如 RCT)。输出纯文本,方便用户二次编辑。”
|
||
* *同时:* 创建数据库记录(状态为 draft)。
|
||
* **返回:** { taskId: "uuid", generatedRequirement: "研究主题:他汀类药物...\\n目标人群:...\\n检索要求:..." }
|
||
|
||
### **3.2 任务启动接口 (进入异步队列)**
|
||
|
||
* **PUT /api/v1/asl/research/tasks/:id/execute**
|
||
* **请求体:** { confirmedRequirement: string }
|
||
* **处理:** 1\. 更新 AslResearchTask 的 confirmed\_requirement 字段。
|
||
2\. jobQueue.createJob('deep-research-worker', { taskId: id })
|
||
3\. 更新状态为 pending。
|
||
* **返回:** { success: true }
|
||
|
||
### **3.3 任务状态与日志轮询接口**
|
||
|
||
* **GET /api/v1/asl/research/tasks/:id**
|
||
* **返回:** 包含 status, execution\_logs, 以及(若完成)synthesis\_report 和 result\_list。
|
||
|
||
## **⚙️ 4\. 后台 Worker 逻辑 (Unifuncs 集成)**
|
||
|
||
使用平台现有的 pg-boss 机制。
|
||
|
||
// backend/src/modules/asl/workers/DeepResearchWorker.ts
|
||
|
||
export async function processDeepResearch(job: Job) {
|
||
const taskId \= job.data.taskId;
|
||
const task \= await prisma.aslResearchTask.findUnique({ where: { id: taskId } });
|
||
|
||
// 1\. 发起 Unifuncs 创建任务请求
|
||
const unifuncsPayload \= {
|
||
model: "s2",
|
||
// 💡 核心变更:直接将用户确认的、详细的自然语言需求传给 Unifuncs,由 Unifuncs 自己去理解和拆解检索词
|
||
messages: \[{
|
||
role: "user",
|
||
content: \`请根据以下详细检索需求执行深度研究:\\n${task.confirmed\_requirement}\`
|
||
}\],
|
||
introduction: "你是一名资深的循证医学研究员。请严格遵循用户的检索需求,在指定数据库中执行详尽的深度检索。",
|
||
max\_depth: 25,
|
||
domain\_scope: task.target\_sources,
|
||
// 强制输出格式以分离报告与文献JSON
|
||
output\_prompt: \`
|
||
\<REPORT\_SECTION\>
|
||
\[撰写综合报告\]
|
||
\</REPORT\_SECTION\>
|
||
\<JSON\_LIST\_SECTION\>
|
||
\[输出严格的文献JSON数组\]
|
||
\</JSON\_LIST\_SECTION\>
|
||
\`,
|
||
reference\_style: "link"
|
||
};
|
||
|
||
const createRes \= await unifuncsClient.createTask(unifuncsPayload);
|
||
const unifuncsId \= createRes.data.task\_id;
|
||
|
||
await prisma.aslResearchTask.update({
|
||
where: { id: taskId },
|
||
data: { unifuncs\_task\_id: unifuncsId, status: 'running' }
|
||
});
|
||
|
||
// 2\. 轮询 Unifuncs 状态 (防无限死循环,设置最大重试次数)
|
||
let isCompleted \= false;
|
||
let maxRetries \= 120; // 假设每5秒查一次,最多查10分钟
|
||
|
||
while (\!isCompleted && maxRetries \> 0\) {
|
||
await sleep(5000);
|
||
const queryRes \= await unifuncsClient.queryTask(unifuncsId);
|
||
|
||
// 解析增量日志 reasoning\_content
|
||
const logs \= parseReasoningToLogs(queryRes.data.result?.reasoning\_content);
|
||
|
||
// 更新数据库日志 (覆盖或追加)
|
||
await prisma.aslResearchTask.update({
|
||
where: { id: taskId },
|
||
data: { execution\_logs: logs }
|
||
});
|
||
|
||
if (queryRes.data.status \=== 'completed') {
|
||
isCompleted \= true;
|
||
// 解析提取 \<REPORT\_SECTION\> 和 \<JSON\_LIST\_SECTION\>
|
||
const report \= extractReport(queryRes.data.result.content);
|
||
const list \= extractJsonList(queryRes.data.result.content);
|
||
|
||
await prisma.aslResearchTask.update({
|
||
where: { id: taskId },
|
||
data: { status: 'completed', synthesis\_report: report, result\_list: list }
|
||
});
|
||
}
|
||
maxRetries--;
|
||
}
|
||
}
|
||
|
||
## **🛡️ 5\. 技术优势**
|
||
|
||
1. **零学习成本**:去除了对医生来说晦涩难懂的“布尔逻辑检索式”,整个确认过程完全采用大白话(自然语言),用户审查和修改都极其自然。
|
||
2. **充分发挥 API 威力**:将“拆解关键词、发起搜索、阅读网页”的复杂动作全部下放给专业的 Unifuncs 引擎,本系统架构只做轻量级的“需求扩写”和“流式日志透传”,代码稳定性极高。
|
||
3. **极速上线**:前端页面仅需一个大文本框渲染扩写后的要求,没有任何复杂的 UI 组件开销,是名副其实的 MVP 最优解。 |