Files
AIclinicalresearch/backend/scripts/test-unifuncs-clinicaltrials.ts
HaHafeng b06daecacd feat(asl): Add Deep Research V2.0 development plan and Unifuncs API site coverage testing
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>
2026-02-22 22:44:41 +08:00

216 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Unifuncs API — ClinicalTrials.gov 专项测试
*
* ClinicalTrials.gov 在上一轮测试中超时,本脚本用多种策略针对性测试
*
* 运行方式:
* cd backend
* npx tsx scripts/test-unifuncs-clinicaltrials.ts
*/
const API_KEY = 'sk-2fNwqUH73elGq0aDKJEM4ReqP7Ry0iqHo4OXyidDe2WpQ9XQ';
const BASE_URL = 'https://api.unifuncs.com/deepsearch/v1';
const POLL_INTERVAL = 10000;
const MAX_WAIT = 900000; // 15 分钟(上次 10 分钟超时,放宽到 15 分钟)
interface TestStrategy {
id: string;
name: string;
query: string;
domainScope: string[];
maxDepth: number;
introduction: string;
}
const STRATEGIES: TestStrategy[] = [
{
id: 'A',
name: '英文查询 + 限定 clinicaltrials.gov',
query: 'Statin therapy for cardiovascular disease prevention, randomized controlled trials, recent 5 years',
domainScope: ['https://clinicaltrials.gov/'],
maxDepth: 10,
introduction: 'You are a clinical research expert. Search ClinicalTrials.gov for relevant clinical trials. Return trial NCT numbers, titles, status, and links.',
},
{
id: 'B',
name: '英文查询 + 不限域名(观察是否自然覆盖 clinicaltrials.gov',
query: 'Find clinical trials on statins for cardiovascular disease prevention registered on ClinicalTrials.gov, list NCT numbers and trial details',
domainScope: [],
maxDepth: 10,
introduction: 'You are a clinical research expert. Focus on finding registered clinical trials from ClinicalTrials.gov. Return NCT numbers, trial titles, status, and direct links to clinicaltrials.gov.',
},
{
id: 'C',
name: '中文查询 + 限定 clinicaltrials.gov + 高深度',
query: '他汀类药物预防心血管疾病的临床试验,请在 ClinicalTrials.gov 上查找注册的 RCT返回 NCT 编号和试验详情',
domainScope: ['https://clinicaltrials.gov/'],
maxDepth: 15,
introduction: '你是临床试验检索专家。请在 ClinicalTrials.gov 上搜索相关临床试验,返回 NCT 编号、试验标题、状态、链接。',
},
{
id: 'D',
name: '简短英文查询 + 限定 clinicaltrials.gov + 低深度',
query: 'statin cardiovascular RCT',
domainScope: ['https://clinicaltrials.gov/'],
maxDepth: 5,
introduction: 'Search ClinicalTrials.gov for statin cardiovascular trials. Return NCT IDs and links.',
},
];
async function createTask(strategy: TestStrategy): Promise<{ taskId: string } | { error: string }> {
const payload: any = {
model: 's2',
messages: [{ role: 'user', content: strategy.query }],
introduction: strategy.introduction,
max_depth: strategy.maxDepth,
reference_style: 'link',
generate_summary: true,
};
if (strategy.domainScope.length > 0) {
payload.domain_scope = strategy.domainScope;
}
try {
const res = await fetch(`${BASE_URL}/create_task`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const json = await res.json() as any;
if (json.code === 0 && json.data?.task_id) {
return { taskId: json.data.task_id };
}
return { error: `API error: ${json.message || JSON.stringify(json)}` };
} catch (err: any) {
return { error: `Request failed: ${err.message}` };
}
}
async function queryTask(taskId: string): Promise<any> {
const params = new URLSearchParams({ task_id: taskId });
const res = await fetch(`${BASE_URL}/query_task?${params.toString()}`, {
headers: { 'Authorization': `Bearer ${API_KEY}` },
});
return res.json();
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
function extractLinks(content: string): { ctLinks: string[]; nctIds: string[]; otherLinks: string[] } {
const urlPattern = /https?:\/\/[^\s)\]"'>]+/gi;
const allUrls = [...new Set(content.match(urlPattern) || [])];
const ctLinks = allUrls.filter(u => u.includes('clinicaltrials.gov'));
const otherLinks = allUrls.filter(u => !u.includes('clinicaltrials.gov'));
const nctPattern = /NCT\d{6,}/gi;
const nctIds = [...new Set(content.match(nctPattern) || [])];
return { ctLinks, nctIds, otherLinks };
}
async function testStrategy(strategy: TestStrategy): Promise<void> {
const startTime = Date.now();
console.log(`\n${'━'.repeat(80)}`);
console.log(`策略 ${strategy.id}: ${strategy.name}`);
console.log(`查询: "${strategy.query}"`);
console.log(`domain_scope: ${strategy.domainScope.length > 0 ? strategy.domainScope.join(', ') : '不限'}`);
console.log(`max_depth: ${strategy.maxDepth}`);
console.log(`${'━'.repeat(80)}`);
const createResult = await createTask(strategy);
if ('error' in createResult) {
console.log(` ❌ 创建失败: ${createResult.error}`);
return;
}
console.log(` → task_id: ${createResult.taskId}`);
const deadline = Date.now() + MAX_WAIT;
let lastProgress = '';
while (Date.now() < deadline) {
await sleep(POLL_INTERVAL);
try {
const json = await queryTask(createResult.taskId) as any;
const data = json.data;
if (!data) continue;
const progress = data.progress ? `${data.progress.current}/${data.progress.total}` : '?';
const stats = data.statistics || {};
const statusLine = `${data.status} (${progress}) 搜索${stats.search_count || 0} 阅读${stats.read_count || 0} 迭代${stats.iterations || 0}`;
if (statusLine !== lastProgress) {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
console.log(` [${elapsed}s] ${statusLine}`);
lastProgress = statusLine;
}
if (data.status === 'completed') {
const content = data.result?.content || '';
const reasoning = data.result?.reasoning_content || '';
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
const { ctLinks, nctIds, otherLinks } = extractLinks(content);
const { ctLinks: rCtLinks, nctIds: rNctIds } = extractLinks(reasoning);
console.log(`\n ✅ 完成 (${elapsed}s)`);
console.log(` ├─ 内容长度: ${content.length} 字符`);
console.log(` ├─ 推理长度: ${reasoning.length} 字符`);
console.log(` ├─ 搜索/阅读: ${stats.search_count || 0}/${stats.read_count || 0}`);
console.log(` ├─ ClinicalTrials 链接 (content): ${ctLinks.length}`);
console.log(` ├─ ClinicalTrials 链接 (reasoning): ${rCtLinks.length}`);
console.log(` ├─ NCT 编号 (content): ${nctIds.length} 个 → ${nctIds.slice(0, 10).join(', ')}${nctIds.length > 10 ? '...' : ''}`);
console.log(` ├─ NCT 编号 (reasoning): ${rNctIds.length}`);
console.log(` ├─ 其他链接: ${otherLinks.length}`);
console.log(` └─ Token: ${JSON.stringify(stats.token_usage || {})}`);
if (ctLinks.length > 0) {
console.log(`\n 📎 ClinicalTrials.gov 链接示例:`);
ctLinks.slice(0, 8).forEach((link, i) => console.log(` ${i + 1}. ${link}`));
}
if (ctLinks.length === 0 && nctIds.length === 0) {
console.log(`\n ⚠️ 内容预览 (前 500 字):`);
console.log(` ${content.slice(0, 500).replace(/\n/g, '\n ')}`);
}
return;
}
if (data.status === 'failed') {
console.log(` ❌ 失败: ${data.result?.content || '未知错误'}`);
return;
}
} catch (err: any) {
// 轮询网络错误,继续重试
}
}
console.log(` ⏰ 超时 (${MAX_WAIT / 1000}s)`);
}
async function main() {
console.log('╔════════════════════════════════════════════════════════════╗');
console.log('║ ClinicalTrials.gov 专项测试 — 4 种策略并行 ║');
console.log('╚════════════════════════════════════════════════════════════╝');
console.log(`超时: ${MAX_WAIT / 1000}s | 轮询: ${POLL_INTERVAL / 1000}s`);
// 并行执行所有策略
await Promise.all(STRATEGIES.map(s => testStrategy(s)));
console.log(`\n${'═'.repeat(80)}`);
console.log('所有策略测试完成!');
console.log(`${'═'.repeat(80)}`);
}
main().catch(err => {
console.error('脚本执行失败:', err);
process.exit(1);
});