feat(asl): Add DeepSearch smart literature retrieval MVP

Features:
- Integrate unifuncs DeepSearch API (OpenAI compatible protocol)
- SSE real-time streaming for AI thinking process display
- Natural language input, auto-generate PubMed search strategy
- Extract and display PubMed literature links
- Database storage for task records (asl_research_tasks)

Backend:
- researchService.ts - Core business logic with SSE streaming
- researchController.ts - SSE stream endpoint
- researchWorker.ts - Async task worker (backup mode)
- schema.prisma - AslResearchTask model

Frontend:
- ResearchSearch.tsx - Search page with unified content stream
- ResearchSearch.css - Styling (unifuncs-inspired simple design)
- ASLLayout.tsx - Enable menu item
- api/index.ts - Add research API functions

API Endpoints:
- POST /api/v1/asl/research/stream - SSE streaming search
- POST /api/v1/asl/research/tasks - Async task creation
- GET /api/v1/asl/research/tasks/:taskId/status - Task status

Documentation:
- Development record for DeepSearch integration
- Update ASL module status (v1.5)
- Update system status (v3.7)

Known limitations:
- SSE mode, task interrupts when leaving page
- Cost ~0.3 RMB per search (unifuncs API)
This commit is contained in:
2026-01-18 19:15:55 +08:00
parent 57fdc6ef00
commit 1ece9a4ae8
20 changed files with 2052 additions and 16 deletions

View File

@@ -0,0 +1,120 @@
/**
* unifuncs DeepSearch API 快速验证脚本
*
* 运行方式:
* cd backend
* npx tsx scripts/test-unifuncs-deepsearch.ts
*/
import OpenAI from 'openai';
// ========== 配置 ==========
const UNIFUNCS_API_KEY = 'sk-2fNwqUH73elGq0aDKJEM4ReqP7Ry0iqHo4OXyidDe2WpQ9XQ';
const UNIFUNCS_BASE_URL = 'https://api.unifuncs.com/deepsearch/v1';
// ========== 测试用例 ==========
const TEST_QUERIES = [
// 简单测试
'糖尿病 SGLT2抑制剂 心血管 RCT',
// 复杂临床问题
// '乳腺癌免疫治疗最新系统综述近3年的研究进展',
];
// ========== 主函数 ==========
async function testDeepSearch() {
console.log('🚀 unifuncs DeepSearch API 验证测试\n');
console.log('=' .repeat(60));
const client = new OpenAI({
baseURL: UNIFUNCS_BASE_URL,
apiKey: UNIFUNCS_API_KEY,
});
for (const query of TEST_QUERIES) {
console.log(`\n📝 测试查询: "${query}"\n`);
console.log('-'.repeat(60));
try {
const startTime = Date.now();
// 方式1: 流式响应(推荐用于验证)
const stream = await client.chat.completions.create({
model: 's2',
messages: [{ role: 'user', content: query }],
stream: true,
// @ts-ignore - unifuncs 扩展参数
introduction: '你是一名专业的临床研究文献检索专家,请在 PubMed 中检索相关文献。输出每篇文献的 PMID、标题、作者、期刊、发表年份、研究类型。',
max_depth: 10, // 验证时用较小的深度,加快速度
domain_scope: ['https://pubmed.ncbi.nlm.nih.gov/'],
domain_blacklist: ['wanfang.com', 'cnki.net'],
reference_style: 'link',
} as any);
let thinking = false;
let thinkingContent = '';
let responseContent = '';
console.log('📡 流式响应中...\n');
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
// 处理思考过程 (reasoning_content)
if ((delta as any)?.reasoning_content) {
if (!thinking) {
console.log('💭 [思考过程]');
thinking = true;
}
const content = (delta as any).reasoning_content;
thinkingContent += content;
process.stdout.write(content);
}
// 处理正式回答 (content)
else if (delta?.content) {
if (thinking) {
console.log('\n\n📄 [检索结果]');
thinking = false;
}
responseContent += delta.content;
process.stdout.write(delta.content);
}
}
const endTime = Date.now();
const duration = ((endTime - startTime) / 1000).toFixed(2);
console.log('\n\n' + '='.repeat(60));
console.log(`✅ 测试完成!耗时: ${duration}`);
console.log(`📊 思考过程长度: ${thinkingContent.length} 字符`);
console.log(`📊 回答内容长度: ${responseContent.length} 字符`);
// 尝试提取 PMID
const pmidMatches = responseContent.match(/PMID[:\s]*(\d+)/gi) || [];
const pubmedLinks = responseContent.match(/pubmed\.ncbi\.nlm\.nih\.gov\/(\d+)/gi) || [];
const totalPmids = new Set([
...pmidMatches.map(m => m.replace(/PMID[:\s]*/i, '')),
...pubmedLinks.map(m => m.replace(/pubmed\.ncbi\.nlm\.nih\.gov\//i, '')),
]);
console.log(`📚 检索到的文献数量: ${totalPmids.size}`);
if (totalPmids.size > 0) {
console.log(`📚 PMID 列表: ${[...totalPmids].slice(0, 10).join(', ')}${totalPmids.size > 10 ? '...' : ''}`);
}
} catch (error: any) {
console.error('\n❌ 测试失败:', error.message);
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应数据:', error.response.data);
}
}
}
console.log('\n' + '='.repeat(60));
console.log('🏁 所有测试完成!');
}
// ========== 运行 ==========
testDeepSearch().catch(console.error);