feat(iit): Phase 1.5 AI对话集成REDCap真实数据完成

- feat: ChatService集成DeepSeek-V3实现AI对话(390行)
- feat: SessionMemory实现上下文记忆(最近3轮对话,170行)
- feat: 意图识别支持REDCap数据查询(关键词匹配)
- feat: REDCap数据注入LLM(queryRedcapRecord, countRedcapRecords, getProjectInfo)
- feat: 解决LLM幻觉问题(基于真实数据回答,明确system prompt)
- feat: 即时反馈(正在查询...提示)
- test: REDCap查询测试通过(test0102项目,10条记录,ID 7患者详情)
- docs: 创建Phase1.5开发完成记录(313行)
- docs: 更新Phase1.5开发计划(标记完成)
- docs: 更新MVP开发任务清单(Phase 1.5完成)
- docs: 更新模块当前状态(60%完成度)
- docs: 更新系统总体设计文档(v2.6)
- chore: 删除测试脚本(test-redcap-query-for-ai.ts, check-env-config.ts)
- chore: 移除REDCap测试环境变量(REDCAP_TEST_*)

技术亮点:
- AI基于REDCap真实数据对话,不编造信息
- 从数据库读取项目配置,不使用环境变量
- 企业微信端测试通过,用户体验良好

测试通过:
-  查询项目记录总数(10条)
-  查询特定患者详情(ID 7)
-  项目信息查询
-  上下文记忆(3轮对话)
-  即时反馈提示

影响范围:IIT Manager Agent模块
This commit is contained in:
2026-01-03 22:48:10 +08:00
parent 4794640f5d
commit b47079b387
158 changed files with 1273 additions and 110 deletions

View File

@@ -301,5 +301,6 @@ export function getBatchItems<T>(

View File

@@ -337,5 +337,6 @@ runTests().catch((error) => {

View File

@@ -316,5 +316,6 @@ Content-Type: application/json

View File

@@ -252,5 +252,6 @@ export const conflictDetectionService = new ConflictDetectionService();

View File

@@ -202,5 +202,6 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \

View File

@@ -256,5 +256,6 @@ export const streamAIController = new StreamAIController();

View File

@@ -167,3 +167,4 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {
timeout: '1小时',
});

View File

@@ -88,3 +88,4 @@ async function checkProjectConfig() {
// 运行检查
checkProjectConfig().catch(console.error);

View File

@@ -0,0 +1,73 @@
/**
* 检查test0102项目是否在数据库中
*
* 运行方式:
* ```bash
* cd backend
* npm run tsx src/modules/iit-manager/check-test-project-in-db.ts
* ```
*/
import { PrismaClient } from '@prisma/client';
import dotenv from 'dotenv';
dotenv.config();
const prisma = new PrismaClient();
async function main() {
console.log('='.repeat(70));
console.log('🔍 检查test0102项目在数据库中的配置');
console.log('='.repeat(70));
console.log();
try {
// 查询项目
const project = await prisma.iitProject.findFirst({
where: {
redcapProjectId: '16'
}
});
if (project) {
console.log('✅ test0102项目已在数据库中');
console.log();
console.log('📋 项目信息:');
console.log(` 数据库ID: ${project.id}`);
console.log(` 项目名称: ${project.name}`);
console.log(` REDCap项目ID: ${project.redcapProjectId}`);
console.log(` REDCap URL: ${project.redcapUrl}`);
console.log(` API Token: ${project.redcapApiToken.substring(0, 8)}***`);
console.log(` 状态: ${project.status}`);
console.log(` 上次同步: ${project.lastSyncAt || '从未同步'}`);
console.log(` 创建时间: ${project.createdAt}`);
console.log();
console.log('✅ 项目配置完整,可以直接使用!');
console.log();
console.log('🚀 下一步:');
console.log(' 直接运行测试脚本(无需环境变量):');
console.log(' npm run tsx src/modules/iit-manager/test-redcap-query-from-db.ts');
} else {
console.log('❌ test0102项目不在数据库中');
console.log();
console.log('📝 需要先将项目添加到数据库');
console.log();
console.log('💡 解决方案:');
console.log(' 运行插入脚本:');
console.log(' npm run tsx src/modules/iit-manager/insert-test-project.ts');
}
console.log();
console.log('='.repeat(70));
} catch (error: any) {
console.error('❌ 查询失败:', error.message);
console.error(' 请检查数据库连接和表结构');
} finally {
await prisma.$disconnect();
}
}
main();

View File

@@ -242,16 +242,26 @@ export class WechatCallbackController {
const decryptedResult = decrypt(this.encodingAESKey, encryptedMsg);
const decryptedXml = await parseStringPromise(decryptedResult.message) as WechatMessageXml;
// 4. 提取消息内容
const message = this.extractMessage(decryptedXml);
// 4. 检查消息类型
const msgType = decryptedXml.xml.MsgType?.[0];
const event = decryptedXml.xml.Event?.[0];
logger.info('✅ 消息解密成功', {
fromUser: message.fromUser,
msgType: message.msgType,
content: message.content,
msgType,
event,
fromUser: decryptedXml.xml.FromUserName?.[0],
});
// 5. 处理消息并回复
// 5. 处理"进入应用"事件(跳过,不需要回复
if (msgType === 'event' && event === 'enter_agent') {
logger.info('⏭️ 跳过"进入应用"事件');
return;
}
// 6. 提取消息内容
const message = this.extractMessage(decryptedXml);
// 7. 处理消息并回复
await this.processUserMessage(message);
} catch (error: any) {
logger.error('❌ 异步处理消息异常', {
@@ -266,13 +276,13 @@ export class WechatCallbackController {
*/
private extractMessage(xml: WechatMessageXml): UserMessage {
return {
fromUser: xml.xml.FromUserName[0],
toUser: xml.xml.ToUserName[0],
msgType: xml.xml.MsgType[0],
fromUser: xml.xml.FromUserName?.[0] || '',
toUser: xml.xml.ToUserName?.[0] || '',
msgType: xml.xml.MsgType?.[0] || 'text',
content: xml.xml.Content?.[0] || '',
msgId: xml.xml.MsgId[0],
agentId: xml.xml.AgentID[0],
createTime: parseInt(xml.xml.CreateTime[0], 10),
msgId: xml.xml.MsgId?.[0] || '',
agentId: xml.xml.AgentID?.[0] || '',
createTime: parseInt(xml.xml.CreateTime?.[0] || '0', 10),
};
}

View File

@@ -25,6 +25,66 @@ export async function registerIitRoutes(fastify: FastifyInstance) {
};
});
// =============================================
// 根路由(企业微信应用主页,提供简单欢迎页)
// =============================================
fastify.get('/', async (request, reply) => {
reply.type('text/html').send(`
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IIT Manager Agent</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
padding: 20px;
}
.container {
max-width: 500px;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
p {
font-size: 1.2rem;
line-height: 1.6;
opacity: 0.9;
}
.tip {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 10px;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 IIT Manager Agent</h1>
<p>您好我是IIT Manager智能助手</p>
<div class="tip">
<p>💬 请直接在企业微信中发送消息与我对话</p>
<p>我可以帮助您管理临床研究项目</p>
</div>
</div>
</body>
</html>
`);
});
logger.info('Registered route: GET /');
// =============================================
// REDCap Data Entry Trigger Webhook 接收器
// =============================================
@@ -179,6 +239,19 @@ export async function registerIitRoutes(fastify: FastifyInstance) {
// 企业微信回调路由
// =============================================
// 注册text/xml解析器企业微信回调使用此格式
fastify.addContentTypeParser(
'text/xml',
{ parseAs: 'string' },
(req, body, done) => {
// 企业微信发送的是XML字符串直接返回字符串即可
// 在控制器中使用xml2js进行解析
done(null, body);
}
);
logger.info('Registered content parser: text/xml');
// GET: URL验证企业微信配置回调URL时使用
fastify.get(
'/api/v1/iit/wechat/callback',

View File

@@ -17,6 +17,10 @@ import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
import { Message } from '../../../common/llm/adapters/types.js';
import { logger } from '../../../common/logging/index.js';
import { sessionMemory } from '../agents/SessionMemory.js';
import { PrismaClient } from '@prisma/client';
import { RedcapAdapter } from '../adapters/RedcapAdapter.js';
const prisma = new PrismaClient();
/**
* AI对话服务
@@ -44,19 +48,40 @@ export class ChatService {
// 1. 记录用户消息
sessionMemory.addMessage(userId, 'user', userMessage);
// 2. 获取上下文最近2轮对话
// 2. 意图识别(关键词匹配
const { intent, params } = this.detectIntent(userMessage);
logger.info('[ChatService] 意图识别', { userId, intent, params });
// 3. 如果需要查询数据,先执行查询
let toolResult: any = null;
if (intent === 'query_record' && params?.recordId) {
toolResult = await this.queryRedcapRecord(params.recordId);
} else if (intent === 'count_records') {
toolResult = await this.countRedcapRecords();
} else if (intent === 'project_info') {
toolResult = await this.getProjectInfo();
}
// 4. 获取上下文最近2轮对话
const context = sessionMemory.getContext(userId);
logger.info('[ChatService] 处理消息', {
userId,
messageLength: userMessage.length,
hasContext: !!context,
hasToolResult: !!toolResult,
intent,
});
// 3. 构建LLM消息
const messages = this.buildMessages(userMessage, context, userId);
// 5. 构建LLM消息(包含查询结果)
const messages = this.buildMessagesWithData(
userMessage,
context,
toolResult,
userId
);
// 4. 调用LLM复用通用能力层
// 6. 调用LLM复用通用能力层
const response = await this.llm.chat(messages, {
temperature: 0.7,
maxTokens: 500, // 企业微信建议控制输出长度
@@ -66,11 +91,13 @@ export class ChatService {
const aiResponse = response.content;
const duration = Date.now() - startTime;
// 5. 记录AI回复
// 7. 记录AI回复
sessionMemory.addMessage(userId, 'assistant', aiResponse);
logger.info('[ChatService] 对话完成', {
userId,
intent,
hasToolResult: !!toolResult,
duration: `${duration}ms`,
inputTokens: response.usage?.promptTokens,
outputTokens: response.usage?.completionTokens,
@@ -90,67 +117,256 @@ export class ChatService {
}
}
/**
* 构建LLM消息System Prompt + 上下文 + 用户消息
* 简单意图识别(基于关键词
*/
private buildMessages(userMessage: string, context: string, userId: string): Message[] {
private detectIntent(message: string): {
intent: 'query_record' | 'count_records' | 'project_info' | 'general_chat';
params?: any;
} {
const lowerMessage = message.toLowerCase();
// 识别记录查询包含ID号码
const recordIdMatch = message.match(/(?:ID|记录|患者|受试者).*?(\d+)|(\d+).*?(?:入组|数据|信息|情况)/i);
if (recordIdMatch) {
return {
intent: 'query_record',
params: { recordId: recordIdMatch[1] || recordIdMatch[2] }
};
}
// 识别统计查询
if (/(多少|几个|几条|总共|统计).*?(记录|患者|受试者|人)/.test(message) ||
/(记录|患者|受试者|人).*?(多少|几个|几条)/.test(message)) {
return { intent: 'count_records' };
}
// 识别项目信息查询
if (/(项目|研究).*?(名称|信息|情况|怎么样)/.test(message) ||
/什么项目|哪个项目/.test(message)) {
return { intent: 'project_info' };
}
// 默认:普通对话
return { intent: 'general_chat' };
}
/**
* 构建包含数据的LLM消息
*/
private buildMessagesWithData(
userMessage: string,
context: string,
toolResult: any,
userId: string
): Message[] {
const messages: Message[] = [];
// 1. System Prompt定义AI角色和能力
// 1. System Prompt
messages.push({
role: 'system',
content: this.getSystemPrompt(userId),
content: this.getSystemPromptWithData(userId)
});
// 2. 上下文(如果有)
if (context) {
// 2. 如果有工具查询结果注入到System消息
if (toolResult) {
messages.push({
role: 'system',
content: `最近对话上下文\n${context}\n\n请结合上下文理解用户当前问题。`,
content: `REDCap数据查询结果\n${JSON.stringify(toolResult, null, 2)}\n\n请基于以上真实数据回答用户问题。如果数据中包含error字段说明查询失败请友好地告知用户。`
});
}
// 3. 用户消息
// 3. 上下文
if (context) {
messages.push({
role: 'system',
content: `【最近对话上下文】\n${context}\n\n请结合上下文理解用户当前问题。`
});
}
// 4. 用户消息
messages.push({
role: 'user',
content: userMessage,
content: userMessage
});
return messages;
}
/**
* System Prompt定义AI角色
* 新的System Prompt强调基于真实数据
*/
private getSystemPrompt(userId: string): string {
return `你是IIT Manager智能助手负责帮助PIPrincipal Investigator研究负责人管理临床研究项目。
private getSystemPromptWithData(userId: string): string {
return `你是IIT Manager智能助手负责帮助PI管理临床研究项目。
你的身份
- 专业的临床研究助手
- 熟悉IIT研究者发起的临床研究流程
- 了解REDCap电子数据采集系统
重要原则
⚠️ 你**必须基于系统提供的REDCap查询结果**回答问题,**绝对不能编造数据**。
⚠️ 如果系统提供了查询结果请使用这些真实数据如果没有提供明确告知用户需要查询REDCap。
【你的能力】
- 回答研究进展问题(入组情况、数据质控等
- 解答研究方案相关疑问
- 提供数据查询支持
回答研究进展问题(基于REDCap实时数据
✅ 查询患者记录详情
✅ 统计入组人数
✅ 提供项目信息
【回复原则】
1. **基于事实**:只使用系统提供的数据,不编造
2. **简洁专业**控制在150字以内
3. **友好礼貌**:使用"您"称呼PI
4. **引导行动**如需更多信息建议登录REDCap系统
【当前用户】
- 企业微信UserID: ${userId}
【回复原则】
1. 简洁专业控制在200字以内避免冗长
2. 友好礼貌:使用"您"称呼PI
3. 实事求是:不清楚的内容要明确说明
4. 引导行动:提供具体操作建议
【示例对话】
PI: "现在入组多少人了?"
Assistant: "您好根据REDCap系统最新数据当前项目已入组患者XX人。如需查看详细信息请访问REDCap系统或告诉我患者编号。"
现在请开始对话。`;
}
/**
* 查询REDCap特定记录的详细信息
*/
private async queryRedcapRecord(recordId: string): Promise<any> {
try {
// 1. 获取项目配置(从数据库)
const project = await prisma.iitProject.findFirst({
where: { status: 'active' },
select: {
id: true,
name: true,
redcapUrl: true,
redcapApiToken: true,
}
});
if (!project) {
return { error: '未找到活跃项目配置' };
}
// 2. 创建RedcapAdapter
const redcap = new RedcapAdapter(
project.redcapUrl,
project.redcapApiToken
);
// 3. 调用API查询
const records = await redcap.exportRecords({
records: [recordId]
});
// 4. 返回结果
if (records.length === 0) {
return {
error: `未找到记录ID: ${recordId}`,
projectName: project.name
};
}
return {
projectName: project.name,
recordId: recordId,
...records[0] // 返回第一条记录
};
} catch (error: any) {
logger.error('[ChatService] REDCap查询失败', {
recordId,
error: error.message
});
return {
error: `查询失败: ${error.message}`
};
}
}
/**
* 统计REDCap记录总数
*/
private async countRedcapRecords(): Promise<any> {
try {
const project = await prisma.iitProject.findFirst({
where: { status: 'active' },
select: {
name: true,
redcapUrl: true,
redcapApiToken: true,
}
});
if (!project) {
return { error: '未找到活跃项目配置' };
}
const redcap = new RedcapAdapter(
project.redcapUrl,
project.redcapApiToken
);
// 只查询record_id字段最快
const records = await redcap.exportRecords({
fields: ['record_id']
});
// 去重如果是纵向研究同一record_id可能有多行
const uniqueRecordIds = Array.from(
new Set(records.map(r => r.record_id))
);
return {
projectName: project.name,
totalRecords: uniqueRecordIds.length,
recordIds: uniqueRecordIds
};
} catch (error: any) {
logger.error('[ChatService] REDCap统计失败', {
error: error.message
});
return {
error: `统计失败: ${error.message}`
};
}
}
/**
* 获取项目基本信息
*/
private async getProjectInfo(): Promise<any> {
try {
const project = await prisma.iitProject.findFirst({
where: { status: 'active' },
select: {
id: true,
name: true,
description: true,
redcapProjectId: true,
createdAt: true,
lastSyncAt: true,
}
});
if (!project) {
return { error: '未找到活跃项目配置' };
}
return {
projectId: project.id,
projectName: project.name,
description: project.description || '暂无描述',
redcapProjectId: project.redcapProjectId,
lastSyncAt: project.lastSyncAt?.toISOString() || '从未同步',
createdAt: project.createdAt.toISOString(),
};
} catch (error: any) {
logger.error('[ChatService] 项目信息查询失败', {
error: error.message
});
return {
error: `查询失败: ${error.message}`
};
}
}
/**
* 清除用户会话(用于重置对话)
*/
@@ -170,3 +386,4 @@ Assistant: "您好根据REDCap系统最新数据当前项目已入组患
}
}

View File

@@ -153,3 +153,4 @@ testIitDatabase()

View File

@@ -0,0 +1,249 @@
/**
* REDCap查询测试脚本从数据库读取配置
*
* 目的测试基础的REDCap查询功能验证数据格式
* 数据来源:从数据库 iit_schema.projects 表读取项目配置
*
* 运行方式:
* ```bash
* cd backend
* npm run tsx src/modules/iit-manager/test-redcap-query-from-db.ts
* ```
*/
import { RedcapAdapter } from './adapters/RedcapAdapter.js';
import { PrismaClient } from '@prisma/client';
import { logger } from '../../common/logging/index.js';
import dotenv from 'dotenv';
dotenv.config();
const prisma = new PrismaClient();
async function main() {
console.log('='.repeat(70));
console.log('🧪 REDCap查询测试为AI对话准备');
console.log('='.repeat(70));
console.log();
try {
// =============================================
// 1. 从数据库读取test0102项目配置
// =============================================
console.log('📋 从数据库读取项目配置...');
const project = await prisma.iitProject.findFirst({
where: {
OR: [
{ redcapProjectId: '16' },
{ name: { contains: 'test0102' } }
],
status: 'active'
}
});
if (!project) {
console.log('❌ 未找到test0102项目PID: 16');
console.log();
console.log('💡 可能的原因:');
console.log(' 1. 项目未在数据库中');
console.log(' 2. 项目状态不是active');
console.log(' 3. redcapProjectId不是"16"');
console.log();
console.log('📝 解决方法:');
console.log(' 请检查数据库 iit_schema.projects 表');
console.log(' 或联系管理员添加test0102项目配置');
console.log();
process.exit(1);
}
console.log('✅ 项目配置读取成功');
console.log();
console.log('📋 项目信息:');
console.log(` 数据库ID: ${project.id}`);
console.log(` 项目名称: ${project.name}`);
console.log(` REDCap项目ID: ${project.redcapProjectId}`);
console.log(` REDCap URL: ${project.redcapUrl}`);
console.log(` API Token: ${project.redcapApiToken.substring(0, 8)}***`);
console.log(` 状态: ${project.status}`);
console.log();
// =============================================
// 2. 创建RedcapAdapter实例
// =============================================
console.log('📦 创建RedcapAdapter实例...');
const redcap = new RedcapAdapter(
project.redcapUrl,
project.redcapApiToken
);
console.log('✅ Adapter创建成功\n');
// =============================================
// 3. 测试连接
// =============================================
console.log('🔌 测试API连接...');
try {
const isConnected = await redcap.testConnection();
if (isConnected) {
console.log('✅ API连接成功\n');
} else {
console.log('❌ API连接失败');
console.log(' 请检查:');
console.log(' 1. REDCap服务是否启动');
console.log(` 2. URL是否正确: ${project.redcapUrl}`);
console.log(' 3. API Token是否有效');
console.log();
process.exit(1);
}
} catch (error: any) {
console.error('❌ 连接测试失败:', error.message);
process.exit(1);
}
// =============================================
// 4. 查询所有记录获取记录ID列表
// =============================================
console.log('📊 测试1: 查询所有记录获取记录ID列表');
console.log('-'.repeat(70));
const allRecords = await redcap.exportRecords({
fields: ['record_id']
});
// 提取唯一记录ID
const uniqueRecordIds = Array.from(
new Set(allRecords.map((r) => r.record_id || r.record))
);
console.log(`✅ 查询成功`);
console.log(` 总记录数: ${uniqueRecordIds.length}`);
console.log(` 记录ID列表: ${uniqueRecordIds.join(', ')}`);
console.log();
if (uniqueRecordIds.length === 0) {
console.log('⚠️ 项目中没有记录请先在REDCap中创建测试数据');
console.log(' 建议在test0102项目中添加几条测试记录');
console.log();
process.exit(0);
}
// =============================================
// 5. 查询特定记录的详细信息
// =============================================
const testRecordId = uniqueRecordIds[0]; // 使用第一条记录做测试
console.log(`📋 测试2: 查询特定记录的详细信息 (ID: ${testRecordId})`);
console.log('-'.repeat(70));
const specificRecord = await redcap.exportRecords({
records: [testRecordId]
});
if (specificRecord.length > 0) {
console.log('✅ 查询成功');
console.log(` 记录数: ${specificRecord.length}`);
console.log();
console.log('📄 第一条记录的数据结构:');
const firstRecord = specificRecord[0];
const fields = Object.keys(firstRecord);
console.log(`${fields.length} 个字段:`);
fields.slice(0, 10).forEach((field, index) => {
const value = firstRecord[field];
const displayValue = value === '' ? '(空值)' : value;
console.log(` ${(index + 1).toString().padStart(2, ' ')}. ${field.padEnd(30)} = ${displayValue}`);
});
if (fields.length > 10) {
console.log(` ... (还有 ${fields.length - 10} 个字段)`);
}
console.log();
} else {
console.log('❌ 未找到记录');
console.log();
}
// =============================================
// 6. 模拟AI对话场景的查询
// =============================================
console.log('🤖 测试3: 模拟AI对话场景');
console.log('-'.repeat(70));
console.log();
// 场景1: 用户问"有多少条记录?"
console.log('【场景1】用户问"我们系统中已经有几条记录了?"');
const countResult = {
projectName: project.name,
totalRecords: uniqueRecordIds.length,
recordIds: uniqueRecordIds
};
console.log('💾 AI需要的数据:');
console.log(JSON.stringify(countResult, null, 2));
console.log();
console.log('🤖 AI应该回答:');
console.log(` "您好根据REDCap系统记录当前项目${project.name}已有 **${uniqueRecordIds.length}条** 患者数据记录。"`);
console.log();
// 场景2: 用户问特定记录的信息
const demoRecordId = uniqueRecordIds.includes('7') ? '7' : uniqueRecordIds[0];
console.log(`【场景2】用户问"了解Redcap中记录为ID ${demoRecordId}的信息"`);
const recordDetailResult = await redcap.exportRecords({
records: [demoRecordId]
});
console.log('💾 AI需要的数据:');
console.log(JSON.stringify({
projectName: project.name,
recordId: demoRecordId,
data: recordDetailResult[0]
}, null, 2));
console.log();
// 场景3: 用户问"项目名称"
console.log('【场景3】用户问"咱们当前的项目名称是什么?"');
console.log('💾 AI需要的数据:');
console.log(JSON.stringify({
projectName: project.name,
redcapProjectId: project.redcapProjectId,
recordCount: uniqueRecordIds.length,
lastSync: project.lastSyncAt
}, null, 2));
console.log();
console.log('🤖 AI应该回答:');
console.log(` "您好!当前项目名称为 **${project.name}**。如需查看完整方案或项目详情请登录REDCap系统或查阅项目文档。"`);
console.log();
// =============================================
// 测试总结
// =============================================
console.log('='.repeat(70));
console.log('✅ 所有测试完成REDCap查询功能正常');
console.log('='.repeat(70));
console.log();
console.log('📝 测试总结:');
console.log(` 1. ✅ 项目配置从数据库读取成功`);
console.log(` 2. ✅ API连接正常`);
console.log(` 3. ✅ 可以查询所有记录 (${uniqueRecordIds.length} 条)`);
console.log(` 4. ✅ 可以查询特定记录`);
console.log(` 5. ✅ 数据格式符合AI对话需求`);
console.log();
console.log('🚀 下一步:');
console.log(' 将查询功能集成到ChatService让AI能够基于真实数据回答问题');
console.log();
} catch (error: any) {
console.error('❌ 测试失败:', error.message);
console.error(' 错误详情:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
process.exit(0);
}
// 执行测试
main().catch((error) => {
console.error('💥 测试脚本执行失败:', error);
process.exit(1);
});

View File

@@ -223,3 +223,4 @@ export interface CachedProtocolRules {

View File

@@ -402,5 +402,6 @@ SET session_replication_role = 'origin';

View File

@@ -104,5 +104,6 @@ WHERE key = 'verify_test';

View File

@@ -247,5 +247,6 @@ verifyDatabase()

View File

@@ -37,5 +37,6 @@ export {}