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>
This commit is contained in:
215
backend/scripts/test-unifuncs-clinicaltrials.ts
Normal file
215
backend/scripts/test-unifuncs-clinicaltrials.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
374
backend/scripts/test-unifuncs-site-coverage.ts
Normal file
374
backend/scripts/test-unifuncs-site-coverage.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Unifuncs API 网站覆盖能力测试脚本
|
||||
*
|
||||
* 测试 unifuncs DeepSearch 对中国医生常用医学期刊网站的搜索能力
|
||||
* 使用异步模式(create_task + query_task)并行测试所有站点
|
||||
*
|
||||
* 运行方式:
|
||||
* cd backend
|
||||
* npx tsx scripts/test-unifuncs-site-coverage.ts
|
||||
*/
|
||||
|
||||
// ========== 配置 ==========
|
||||
const API_KEY = 'sk-2fNwqUH73elGq0aDKJEM4ReqP7Ry0iqHo4OXyidDe2WpQ9XQ';
|
||||
const BASE_URL = 'https://api.unifuncs.com/deepsearch/v1';
|
||||
const MAX_DEPTH = 5; // 测试用低深度,加快速度
|
||||
const POLL_INTERVAL = 10000; // 10s 轮询间隔
|
||||
const MAX_WAIT = 600000; // 单任务最长等待 10 分钟
|
||||
const QUERY = '他汀类药物预防心血管疾病的随机对照试验和Meta分析,近5年高质量研究';
|
||||
|
||||
// ========== 测试站点列表 ==========
|
||||
interface TestSite {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
priority: 'top7' | 'other';
|
||||
category: 'english' | 'chinese';
|
||||
}
|
||||
|
||||
const TEST_SITES: TestSite[] = [
|
||||
// ── Top 7 最关注 ──
|
||||
{ id: 1, name: 'PubMed', url: 'https://pubmed.ncbi.nlm.nih.gov/', priority: 'top7', category: 'english' },
|
||||
{ id: 2, name: 'ClinicalTrials.gov', url: 'https://clinicaltrials.gov/', priority: 'top7', category: 'english' },
|
||||
{ id: 3, name: '中华医学期刊网', url: 'https://medjournals.cn/', priority: 'top7', category: 'chinese' },
|
||||
{ id: 4, name: '中国知网 CNKI', url: 'https://www.cnki.net/', priority: 'top7', category: 'chinese' },
|
||||
{ id: 5, name: '万方数据', url: 'https://www.wanfangdata.com.cn/', priority: 'top7', category: 'chinese' },
|
||||
{ id: 6, name: '维普 VIP', url: 'https://www.cqvip.com/', priority: 'top7', category: 'chinese' },
|
||||
{ id: 7, name: '中国临床试验注册中心', url: 'http://www.chictr.org.cn/', priority: 'top7', category: 'chinese' },
|
||||
// ── 其他常用 ──
|
||||
{ id: 8, name: 'CBM/SinoMed', url: 'http://www.sinomed.ac.cn/', priority: 'other', category: 'chinese' },
|
||||
{ id: 9, name: 'Web of Science', url: 'https://www.webofscience.com/', priority: 'other', category: 'english' },
|
||||
{ id: 10, name: 'Embase', url: 'http://www.embase.com/', priority: 'other', category: 'english' },
|
||||
{ id: 11, name: 'Cochrane Library', url: 'https://www.cochranelibrary.com/', priority: 'other', category: 'english' },
|
||||
{ id: 12, name: 'Google Scholar', url: 'https://scholar.google.com/', priority: 'other', category: 'english' },
|
||||
{ id: 13, name: 'Ovid', url: 'http://ovidsp.ovid.com/', priority: 'other', category: 'english' },
|
||||
{ id: 14, name: 'Scopus', url: 'https://www.scopus.com/', priority: 'other', category: 'english' },
|
||||
{ id: 15, name: '中国中医药数据库', url: 'https://cintmed.cintcm.cn/', priority: 'other', category: 'chinese' },
|
||||
{ id: 16, name: 'GeenMedical', url: 'https://www.geenmedical.com/', priority: 'other', category: 'english' },
|
||||
{ id: 17, name: 'NSTL 国家科技图书文献中心', url: 'https://www.nstl.gov.cn/', priority: 'other', category: 'chinese' },
|
||||
{ id: 18, name: 'NCBI (全站)', url: 'https://www.ncbi.nlm.nih.gov/', priority: 'other', category: 'english' },
|
||||
];
|
||||
|
||||
// ========== 结果结构 ==========
|
||||
interface TaskResult {
|
||||
site: TestSite;
|
||||
taskId: string | null;
|
||||
status: 'success' | 'failed' | 'timeout' | 'create_error';
|
||||
searchCount: number;
|
||||
readCount: number;
|
||||
iterations: number;
|
||||
contentLength: number;
|
||||
reasoningLength: number;
|
||||
referencesFound: number; // 在 content 中找到的该站点链接数
|
||||
otherLinksFound: number; // 找到的其他链接数
|
||||
durationSec: number;
|
||||
errorMessage: string;
|
||||
sampleLinks: string[]; // 找到的前 5 个链接
|
||||
}
|
||||
|
||||
// ========== API 封装 ==========
|
||||
|
||||
async function createTask(site: TestSite): Promise<{ taskId: string } | { error: string }> {
|
||||
const payload = {
|
||||
model: 's2',
|
||||
messages: [{ role: 'user', content: QUERY }],
|
||||
introduction: '你是一名专业的临床研究文献检索专家。请在指定数据库中尽可能多地检索相关文献,输出每篇文献的标题、作者、年份、链接。',
|
||||
max_depth: MAX_DEPTH,
|
||||
domain_scope: [site.url],
|
||||
reference_style: 'link',
|
||||
generate_summary: true,
|
||||
};
|
||||
|
||||
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 返回错误: ${json.message || JSON.stringify(json)}` };
|
||||
} catch (err: any) {
|
||||
return { error: `请求失败: ${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 extractDomainLinks(content: string, siteUrl: string): string[] {
|
||||
const domain = new URL(siteUrl).hostname.replace('www.', '');
|
||||
const urlPattern = /https?:\/\/[^\s)\]"'>]+/gi;
|
||||
const allUrls = content.match(urlPattern) || [];
|
||||
return [...new Set(allUrls.filter(u => u.includes(domain)))];
|
||||
}
|
||||
|
||||
function extractAllLinks(content: string): string[] {
|
||||
const urlPattern = /https?:\/\/[^\s)\]"'>]+/gi;
|
||||
return [...new Set(content.match(urlPattern) || [])];
|
||||
}
|
||||
|
||||
// ========== 单站点完整流程 ==========
|
||||
|
||||
async function testSingleSite(site: TestSite): Promise<TaskResult> {
|
||||
const startTime = Date.now();
|
||||
const baseResult: TaskResult = {
|
||||
site,
|
||||
taskId: null,
|
||||
status: 'failed',
|
||||
searchCount: 0,
|
||||
readCount: 0,
|
||||
iterations: 0,
|
||||
contentLength: 0,
|
||||
reasoningLength: 0,
|
||||
referencesFound: 0,
|
||||
otherLinksFound: 0,
|
||||
durationSec: 0,
|
||||
errorMessage: '',
|
||||
sampleLinks: [],
|
||||
};
|
||||
|
||||
// 1. 创建任务
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} → 创建任务...`);
|
||||
const createResult = await createTask(site);
|
||||
|
||||
if ('error' in createResult) {
|
||||
baseResult.status = 'create_error';
|
||||
baseResult.errorMessage = createResult.error;
|
||||
baseResult.durationSec = (Date.now() - startTime) / 1000;
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} ✗ 创建失败: ${createResult.error}`);
|
||||
return baseResult;
|
||||
}
|
||||
|
||||
baseResult.taskId = createResult.taskId;
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} → task_id: ${createResult.taskId}`);
|
||||
|
||||
// 2. 轮询直到完成
|
||||
const deadline = Date.now() + MAX_WAIT;
|
||||
let lastStatus = '';
|
||||
|
||||
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 currentStatus = data.status;
|
||||
if (currentStatus !== lastStatus) {
|
||||
const progress = data.progress ? `${data.progress.current}/${data.progress.total}` : '?';
|
||||
const stats = data.statistics
|
||||
? `搜索${data.statistics.search_count || 0} 阅读${data.statistics.read_count || 0}`
|
||||
: '';
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} → ${currentStatus} (${progress}) ${stats}`);
|
||||
lastStatus = currentStatus;
|
||||
}
|
||||
|
||||
if (currentStatus === 'completed') {
|
||||
const content = data.result?.content || '';
|
||||
const reasoning = data.result?.reasoning_content || '';
|
||||
const stats = data.statistics || {};
|
||||
|
||||
const siteLinks = extractDomainLinks(content, site.url);
|
||||
const allLinks = extractAllLinks(content);
|
||||
|
||||
baseResult.status = 'success';
|
||||
baseResult.contentLength = content.length;
|
||||
baseResult.reasoningLength = reasoning.length;
|
||||
baseResult.searchCount = stats.search_count || 0;
|
||||
baseResult.readCount = stats.read_count || 0;
|
||||
baseResult.iterations = stats.iterations || 0;
|
||||
baseResult.referencesFound = siteLinks.length;
|
||||
baseResult.otherLinksFound = allLinks.length - siteLinks.length;
|
||||
baseResult.sampleLinks = siteLinks.slice(0, 5);
|
||||
baseResult.durationSec = (Date.now() - startTime) / 1000;
|
||||
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} ✓ 完成 | 站内链接:${siteLinks.length} 其他链接:${allLinks.length - siteLinks.length} | ${baseResult.durationSec.toFixed(0)}s`);
|
||||
return baseResult;
|
||||
}
|
||||
|
||||
if (currentStatus === 'failed') {
|
||||
baseResult.status = 'failed';
|
||||
baseResult.errorMessage = data.result?.content || '任务失败';
|
||||
baseResult.durationSec = (Date.now() - startTime) / 1000;
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} ✗ 失败: ${baseResult.errorMessage.slice(0, 80)}`);
|
||||
return baseResult;
|
||||
}
|
||||
} catch (err: any) {
|
||||
// 轮询中的网络错误,继续重试
|
||||
}
|
||||
}
|
||||
|
||||
// 超时
|
||||
baseResult.status = 'timeout';
|
||||
baseResult.errorMessage = `超时(${MAX_WAIT / 1000}s)`;
|
||||
baseResult.durationSec = (Date.now() - startTime) / 1000;
|
||||
console.log(` [${site.id.toString().padStart(2)}] ${site.name} ⏰ 超时`);
|
||||
return baseResult;
|
||||
}
|
||||
|
||||
// ========== 结果报告 ==========
|
||||
|
||||
function printReport(results: TaskResult[]) {
|
||||
console.log('\n');
|
||||
console.log('='.repeat(100));
|
||||
console.log(' Unifuncs DeepSearch API 网站覆盖能力测试报告');
|
||||
console.log('='.repeat(100));
|
||||
console.log(`测试时间: ${new Date().toISOString()}`);
|
||||
console.log(`测试查询: "${QUERY}"`);
|
||||
console.log(`配置: max_depth=${MAX_DEPTH}, poll_interval=${POLL_INTERVAL / 1000}s`);
|
||||
console.log('');
|
||||
|
||||
// ── Top 7 结果 ──
|
||||
console.log('━'.repeat(100));
|
||||
console.log(' ★ Top 7 最关注站点');
|
||||
console.log('━'.repeat(100));
|
||||
printTable(results.filter(r => r.site.priority === 'top7'));
|
||||
|
||||
// ── 其他结果 ──
|
||||
console.log('');
|
||||
console.log('━'.repeat(100));
|
||||
console.log(' 其他常用站点');
|
||||
console.log('━'.repeat(100));
|
||||
printTable(results.filter(r => r.site.priority === 'other'));
|
||||
|
||||
// ── 汇总 ──
|
||||
console.log('');
|
||||
console.log('━'.repeat(100));
|
||||
console.log(' 汇总统计');
|
||||
console.log('━'.repeat(100));
|
||||
|
||||
const successSites = results.filter(r => r.status === 'success' && r.referencesFound > 0);
|
||||
const reachableSites = results.filter(r => r.status === 'success');
|
||||
const failedSites = results.filter(r => r.status !== 'success');
|
||||
|
||||
console.log(` 可搜索并返回站内链接: ${successSites.length}/${results.length} 个站点`);
|
||||
console.log(` 可到达但无站内链接: ${reachableSites.length - successSites.length} 个站点`);
|
||||
console.log(` 不可用/失败/超时: ${failedSites.length} 个站点`);
|
||||
console.log('');
|
||||
|
||||
if (successSites.length > 0) {
|
||||
console.log(' ✅ 确认可搜索的站点:');
|
||||
for (const r of successSites) {
|
||||
console.log(` - ${r.site.name} (${r.site.url}) → ${r.referencesFound} 个站内链接`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
const noLinkSites = reachableSites.filter(r => r.referencesFound === 0);
|
||||
if (noLinkSites.length > 0) {
|
||||
console.log(' ⚠️ 任务完成但无站内链接(可能搜索到了但链接指向其他站点):');
|
||||
for (const r of noLinkSites) {
|
||||
console.log(` - ${r.site.name} (${r.site.url}) → 其他链接 ${r.otherLinksFound} 个`);
|
||||
if (r.sampleLinks.length === 0) {
|
||||
const allLinks = r.otherLinksFound;
|
||||
console.log(` 内容长度: ${r.contentLength} 字符, 搜索${r.searchCount}次, 阅读${r.readCount}次`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
if (failedSites.length > 0) {
|
||||
console.log(' ❌ 不可用站点:');
|
||||
for (const r of failedSites) {
|
||||
console.log(` - ${r.site.name} (${r.site.url}) → ${r.status}: ${r.errorMessage.slice(0, 100)}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(100));
|
||||
|
||||
// ── 输出可用于前端配置的 JSON ──
|
||||
console.log('');
|
||||
console.log('前端可用数据源配置(可直接用于 V2.0 数据源选择):');
|
||||
console.log('');
|
||||
const configList = results.map(r => ({
|
||||
name: r.site.name,
|
||||
url: r.site.url,
|
||||
category: r.site.category,
|
||||
available: r.status === 'success' && r.referencesFound > 0,
|
||||
reachable: r.status === 'success',
|
||||
siteLinksFound: r.referencesFound,
|
||||
searchCount: r.searchCount,
|
||||
readCount: r.readCount,
|
||||
}));
|
||||
console.log(JSON.stringify(configList, null, 2));
|
||||
}
|
||||
|
||||
function printTable(results: TaskResult[]) {
|
||||
const header = ' 序号 | 状态 | 站点名称 | 站内链接 | 其他链接 | 搜索/阅读 | 耗时 | 说明';
|
||||
console.log(header);
|
||||
console.log(' ' + '-'.repeat(header.length - 2));
|
||||
|
||||
for (const r of results) {
|
||||
const statusIcon =
|
||||
r.status === 'success' && r.referencesFound > 0 ? '✅' :
|
||||
r.status === 'success' ? '⚠️' :
|
||||
r.status === 'timeout' ? '⏰' : '❌';
|
||||
|
||||
const note = r.status !== 'success'
|
||||
? r.errorMessage.slice(0, 25)
|
||||
: r.referencesFound > 0
|
||||
? r.sampleLinks[0]?.slice(0, 35) || ''
|
||||
: `内容${r.contentLength}字`;
|
||||
|
||||
console.log(
|
||||
` ${r.site.id.toString().padStart(4)} | ${statusIcon} | ` +
|
||||
`${(r.site.name).padEnd(24)} | ` +
|
||||
`${r.referencesFound.toString().padStart(8)} | ` +
|
||||
`${r.otherLinksFound.toString().padStart(8)} | ` +
|
||||
`${r.searchCount}/${r.readCount}`.padStart(9) + ' | ' +
|
||||
`${r.durationSec.toFixed(0).padStart(5)}s | ` +
|
||||
note
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 主入口 ==========
|
||||
|
||||
async function main() {
|
||||
console.log('╔════════════════════════════════════════════════════════════╗');
|
||||
console.log('║ Unifuncs DeepSearch API — 医学网站覆盖能力测试 ║');
|
||||
console.log('╚════════════════════════════════════════════════════════════╝');
|
||||
console.log('');
|
||||
console.log(`查询: "${QUERY}"`);
|
||||
console.log(`站点数: ${TEST_SITES.length} | max_depth: ${MAX_DEPTH} | 超时: ${MAX_WAIT / 1000}s`);
|
||||
console.log(`API: ${BASE_URL}`);
|
||||
console.log('');
|
||||
console.log('并行创建所有任务...\n');
|
||||
|
||||
// 并行创建所有任务并执行
|
||||
const promises = TEST_SITES.map(site => testSingleSite(site));
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// 输出报告
|
||||
printReport(results);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('脚本执行失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user