Files
AIclinicalresearch/backend/test-tool-c-day3.mjs
HaHafeng dfc0fe0b9a feat(pkb): Integrate pgvector and create Dify replacement plan
Summary:
- Migrate PostgreSQL to pgvector/pgvector:pg15 Docker image
- Successfully install and verify pgvector 0.8.1 extension
- Create comprehensive Dify-to-pgvector migration plan
- Update PKB module documentation with pgvector status
- Update system documentation with pgvector integration

Key changes:
- docker-compose.yml: Switch to pgvector/pgvector:pg15 image
- Add EkbDocument and EkbChunk data model design
- Design R-C-R-G hybrid retrieval architecture
- Add clinical data JSONB fields (pico, studyDesign, regimen, safety, criteria, endpoints)
- Create detailed 10-day implementation roadmap

Documentation updates:
- PKB module status: pgvector RAG infrastructure ready
- System status: pgvector 0.8.1 integrated
- New: Dify replacement development plan (01-Dify替换为pgvector开发计划.md)
- New: Enterprise medical knowledge base solution V2

Tested: PostgreSQL with pgvector verified, frontend and backend functionality confirmed
2026-01-20 00:00:58 +08:00

401 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
/**
* Tool C Day 3 测试脚本
*
* 测试内容:
* 1. 10个Few-shot示例场景测试
* 2. AI自我修正机制测试重试
* 3. 端到端测试
*
* 前提:
* - 需要先创建一个Session使用Day 2的upload接口
* - 需要Python服务运行端口8000
* - 需要后端服务运行端口3000
*
* 执行方式node test-tool-c-day3.mjs
*/
import axios from 'axios';
import FormData from 'form-data';
import * as XLSX from 'xlsx';
const BASE_URL = 'http://localhost:3000';
const API_PREFIX = '/api/v1/dc/tool-c';
let testSessionId = null;
// ==================== 辅助函数 ====================
function printSection(title) {
console.log('\n' + '='.repeat(70));
console.log(` ${title}`);
console.log('='.repeat(70) + '\n');
}
function printSuccess(message) {
console.log('✅ ' + message);
}
function printError(message) {
console.log('❌ ' + message);
}
function printInfo(message) {
console.log(' ' + message);
}
// ==================== 准备测试Session ====================
async function createTestSession() {
printSection('准备创建测试Session');
try {
// 创建测试Excel数据
const testData = [
{ patient_id: 'P001', name: '张三', age: 25, gender: '男', diagnosis: '感冒', sbp: 120, dbp: 80, weight: 70, height: 175, BMI: '', creatinine: '>100', check_date: '2024-01-01' },
{ patient_id: 'P002', name: '李四', age: 65, gender: '女', diagnosis: '高血压', sbp: 150, dbp: 95, weight: 65, height: 160, BMI: '', creatinine: '<0.1', check_date: '2024-01-05' },
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 135, dbp: 85, weight: 80, height: 170, BMI: '', creatinine: '85', check_date: '2024-01-03' },
{ patient_id: 'P004', name: '赵六', age: 70, gender: '女', diagnosis: '冠心病', sbp: 160, dbp: 100, weight: 60, height: 155, BMI: '', creatinine: '120', check_date: '2024-01-10' },
{ patient_id: 'P005', name: '钱七', age: 35, gender: '男', diagnosis: '胃炎', sbp: 110, dbp: 70, weight: 75, height: 180, BMI: '', creatinine: '-', check_date: '2024-01-08' },
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 138, dbp: 88, weight: 82, height: 170, BMI: '', creatinine: '88', check_date: '2024-01-12' }, // 重复ID日期更新
];
// 生成Excel
const ws = XLSX.utils.json_to_sheet(testData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
const excelBuffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
// 上传创建Session
const form = new FormData();
form.append('file', excelBuffer, {
filename: 'test-medical-data-day3.xlsx',
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/sessions/upload`,
form,
{ headers: form.getHeaders(), timeout: 10000 }
);
if (response.data.success) {
testSessionId = response.data.data.sessionId;
printSuccess(`Session创建成功: ${testSessionId}`);
printInfo(`数据: ${testData.length}行 x ${Object.keys(testData[0]).length}`);
return true;
} else {
printError('Session创建失败');
return false;
}
} catch (error) {
printError('Session创建异常: ' + error.message);
return false;
}
}
// ==================== AI测试函数 ====================
async function testAIGenerate(testName, userMessage, shouldSucceed = true) {
printSection(`测试: ${testName}`);
try {
printInfo(`用户需求: ${userMessage}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/ai/generate`,
{
sessionId: testSessionId,
message: userMessage
},
{ timeout: 30000 } // AI调用可能需要较长时间
);
if (response.data.success) {
printSuccess('AI生成代码成功');
console.log('\n生成的代码:');
console.log('```python');
console.log(response.data.data.code);
console.log('```\n');
console.log('解释:', response.data.data.explanation);
console.log('MessageID:', response.data.data.messageId);
return { success: true, data: response.data.data };
} else {
if (shouldSucceed) {
printError('AI生成失败: ' + response.data.error);
} else {
printInfo('预期失败: ' + response.data.error);
}
return { success: false, error: response.data.error };
}
} catch (error) {
printError('AI生成异常: ' + (error.response?.data?.error || error.message));
return { success: false, error: error.message };
}
}
async function testAIProcess(testName, userMessage) {
printSection(`测试(一步到位): ${testName}`);
try {
printInfo(`用户需求: ${userMessage}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/ai/process`,
{
sessionId: testSessionId,
message: userMessage,
maxRetries: 3
},
{ timeout: 60000 } // 带重试可能需要更长时间
);
if (response.data.success) {
printSuccess(`执行成功${response.data.data.retryCount > 0 ? `(重试${response.data.data.retryCount}次)` : ''}`);
console.log('\n生成的代码:');
console.log('```python');
console.log(response.data.data.code);
console.log('```\n');
console.log('解释:', response.data.data.explanation);
if (response.data.data.executeResult.success) {
printSuccess('代码执行成功');
console.log('数据预览前5行:');
console.log(JSON.stringify(response.data.data.executeResult.newDataPreview?.slice(0, 5), null, 2));
}
return { success: true, data: response.data.data };
} else {
printError('处理失败: ' + response.data.error);
return { success: false, error: response.data.error };
}
} catch (error) {
printError('处理异常: ' + (error.response?.data?.error || error.message));
return { success: false, error: error.message };
}
}
// ==================== 主测试函数 ====================
async function runAllTests() {
console.log('\n' + '🚀'.repeat(35));
console.log(' Tool C Day 3 测试 - AI代码生成');
console.log('🚀'.repeat(35));
const results = {};
try {
// 0. 准备测试Session
const sessionCreated = await createTestSession();
if (!sessionCreated) {
printError('测试Session创建失败无法继续');
return;
}
await new Promise(resolve => setTimeout(resolve, 2000));
// ==================== 10个Few-shot示例测试 ====================
// 测试1: 统一缺失值标记
let result = await testAIProcess(
'示例1: 统一缺失值标记',
'把所有代表缺失的符号(-、不详、NA、N/A统一替换为标准空值'
);
results['示例1-缺失值'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试2: 数值列清洗
result = await testAIProcess(
'示例2: 数值列清洗',
'把creatinine列里的非数字符号去掉<0.1按0.05处理,转为数值类型'
);
results['示例2-数值清洗'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试3: 分类变量编码
result = await testAIProcess(
'示例3: 分类变量编码',
'把gender列转为数字男=1女=0'
);
results['示例3-编码'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试4: 连续变量分箱
result = await testAIProcess(
'示例4: 连续变量分箱',
'把age列按18岁、60岁分为未成年、成年、老年三组'
);
results['示例4-分箱'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试5: BMI计算
result = await testAIProcess(
'示例5: BMI计算',
'根据weight和height计算BMI并标记BMI≥28为肥胖'
);
results['示例5-BMI'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试6: 条件筛选
result = await testAIProcess(
'示例6: 条件筛选',
'筛选出年龄≥60岁、且sbp≥140的患者'
);
results['示例6-筛选'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试7: 智能去重
result = await testAIProcess(
'示例7: 智能去重',
'按patient_id去重保留check_date最新的记录'
);
results['示例7-去重'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试8: 中位数填补(简化版,跳过多重插补)
result = await testAIProcess(
'示例8: 缺失值填补',
'用age列的中位数填补age列的缺失值'
);
results['示例8-填补'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试9: 统计汇总
result = await testAIProcess(
'示例9: 统计汇总',
'按diagnosis分组统计每个诊断的平均年龄和患者数量'
);
results['示例9-统计'] = result.success;
await new Promise(resolve => setTimeout(resolve, 3000));
// 测试10: 复杂计算
result = await testAIProcess(
'示例10: 复杂计算',
'根据sbp判断血压分类正常(<140)、高血压I级(140-159)、高血压II级(≥160)'
);
results['示例10-分类'] = result.success;
// ==================== 对话历史测试 ====================
printSection('测试: 获取对话历史');
try {
const historyResponse = await axios.get(
`${BASE_URL}${API_PREFIX}/ai/history/${testSessionId}?limit=5`
);
if (historyResponse.data.success) {
printSuccess(`获取历史成功: ${historyResponse.data.data.count}`);
results['对话历史'] = true;
} else {
printError('获取历史失败');
results['对话历史'] = false;
}
} catch (error) {
printError('获取历史异常: ' + error.message);
results['对话历史'] = false;
}
} catch (error) {
printError('测试过程中发生异常: ' + error.message);
console.error(error);
}
// 汇总结果
printSection('测试结果汇总');
let passed = 0;
let total = 0;
for (const [testName, result] of Object.entries(results)) {
total++;
if (result) {
passed++;
console.log(`${testName.padEnd(20)}: ✅ 通过`);
} else {
console.log(`${testName.padEnd(20)}: ❌ 失败`);
}
}
console.log('\n' + '-'.repeat(70));
console.log(`总计: ${passed}/${total} 通过 (${((passed/total)*100).toFixed(1)}%)`);
console.log('-'.repeat(70));
if (passed === total) {
console.log('\n🎉 所有测试通过Day 3 AI功能完成\n');
} else if (passed >= total * 0.7) {
console.log(`\n⚠️ 有 ${total - passed} 个测试失败但通过率≥70%,基本可用\n`);
} else {
console.log(`\n❌ 通过率过低,需要调试\n`);
}
}
// 执行测试
runAllTests()
.then(() => {
console.log('测试完成');
process.exit(0);
})
.catch((error) => {
console.error('测试失败:', error);
process.exit(1);
});