@@ -0,0 +1,633 @@
# Dify知识库集成开发记录
**开发日期 ** : 2026-01-04
**开发阶段 ** : Phase 1.5 - AI对话能力
**任务 ** : 集成Dify知识库实现研究方案文档查询
**状态 ** : ✅ 已完成
---
## 📋 开发目标
在IIT Manager Agent中集成Dify知识库能力, 使AI能够查询研究方案、伦理文件、CRF表格等文档, 并与已有的REDCap实时数据查询能力结合, 实现**混合检索( Hybrid Retrieval) **。
## 🎯 技术方案
### 方案选择
| 维度 | 方案A: 单项目单知识库 | 方案B: 项目分类多知识库 |
|------|---------------------|---------------------|
| **知识库数量 ** | 1个IIT项目 → 1个Dify Dataset | 1个IIT项目 → 多个Dataset( 方案、伦理、CRF) |
| **复杂度 ** | ✅ 简单 | ❌ 复杂 |
| **MVP适用性 ** | ✅ 高 | ❌ 低 |
| **选择 ** | * * ✅ 采用** | ❌ 暂不采用 |
### 文档上传方式
- **采用方案**: 通过Dify Web界面手动上传
- **原因**: MVP阶段文档更新频率低, 手动上传更灵活
- **未来优化**: 后续可开发API自动上传能力
### 项目关联方式
- **采用方案**: 用户绑定默认项目(存储在数据库)
- **实现**: 在`iit_schema.projects` 表中的`dify_dataset_id` 字段存储关联
---
## 🛠️ 技术实现
### 1. 数据库Schema验证
**问题 ** : 需要在`iit_schema.projects` 表中存储Dify知识库ID
**验证过程 ** :
1. 检查`prisma/schema.prisma` 文件
2. 发现`IitProject` 模型已有`difyDatasetId` 字段
3. 通过SQL直接查询数据库确认列存在
**结论 ** : 无需新建数据库迁移,直接使用现有字段
``` typescript
model IitProject {
id String @id @default ( uuid ( ) )
name String
difyDatasetId String ? @unique @map ( "dify_dataset_id" )
// ... 其他字段
}
```
### 2. 创建Dify知识库
**操作步骤 ** :
1. 登录Dify控制台
2. 创建知识库:`Dify_test0102`
3. 上传文档:
- `新生儿及婴儿胆汁淤积症中西医协同队列研究方案1210-.docx`
- `重大疑难-病例报告表( CRF) 修改1208.docx`
4. 等待文档处理完成
**Dataset ID ** : `b49595b2-bf71-4e47-9988-4aa2816d3c6f`
### 3. 关联项目与知识库
**脚本 ** : `link-dify-to-project.ts`
``` typescript
await prisma . $executeRaw `
UPDATE iit_schema.projects
SET dify_dataset_id = ${ difyDatasetId }
WHERE id = ${ projectId }
` ;
```
**关联结果 ** :
- 项目ID: `40062738-2eb5-472f-8a36-e098f5c2f9b9`
- 项目名称: `test0102`
- Dify Dataset ID: `b49595b2-bf71-4e47-9988-4aa2816d3c6f`
### 4. 集成Dify检索到ChatService
**核心修改 ** : `backend/src/modules/iit-manager/services/ChatService.ts`
#### (1) 扩展意图识别
``` typescript
private detectIntent ( message : string ) : {
intent : 'query_record' | 'count_records' | 'project_info' | 'query_protocol' | 'general_chat' ;
params? : any ;
} {
const lowerMessage = message . toLowerCase ( ) ;
// 识别文档查询( 研究方案、伦理、知情同意、CRF等)
if ( /(研究方案|伦理|知情同意|CRF|病例报告表|纳入|入选|排除|标准|入组标准|治疗方案|试验设计|研究目的|研究流程|观察指标|诊断标准|疾病标准)/ . test ( message ) ) {
return { intent : 'query_protocol' } ;
}
// ... 其他意图识别
}
```
**关键改进 ** : 添加`query_protocol` 意图,识别与研究方案相关的关键词
#### (2) 新增Dify查询方法
``` typescript
private async queryDifyKnowledge ( query : string ) : Promise < string > {
try {
// 1. 获取项目配置( 包含difyDatasetId)
const project = await prisma . iitProject . findFirst ( {
where : { status : 'active' } ,
select : { name : true , difyDatasetId : true }
} ) ;
if ( ! project ? . difyDatasetId ) {
logger . warn ( '[ChatService] 项目未配置Dify知识库' ) ;
return '' ;
}
// 2. 调用Dify检索API
const retrievalResult = await difyClient . retrieveKnowledge (
project . difyDatasetId ,
query ,
{
retrieval_model : {
search_method : 'semantic_search' ,
top_k : 5 ,
}
}
) ;
// 3. 格式化检索结果
if ( ! retrievalResult . records || retrievalResult . records . length === 0 ) {
return '' ;
}
let formattedKnowledge = '' ;
retrievalResult . records . forEach ( ( record , index ) = > {
const score = ( record . score * 100 ) . toFixed ( 1 ) ;
const documentName = record . segment ? . document ? . name || '未知文档' ;
const content = record . segment ? . content || '' ;
formattedKnowledge += ` \ n[文档 ${ index + 1 } ] ${ documentName } (相关度: ${ score } %) \ n ` ;
formattedKnowledge += ` ${ content } \ n ` ;
formattedKnowledge += ` --- \ n ` ;
} ) ;
return formattedKnowledge ;
} catch ( error : any ) {
logger . error ( '[ChatService] Dify检索失败' , { query , error : error.message } ) ;
return ` 【知识库查询失败】: ${ error . message } ` ;
}
}
```
#### (3) 更新主对话流程
``` typescript
async handleMessage ( userId : string , userMessage : string ) : Promise < string > {
// 1. 记录用户消息
sessionMemory . addMessage ( userId , 'user' , userMessage ) ;
// 2. 意图识别
const { intent , params } = this . detectIntent ( userMessage ) ;
logger . info ( '[ChatService] 意图识别' , { userId , intent , params } ) ;
// 3. 如果需要查询REDCap数据, 先执行查询
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 ( ) ;
}
// 4. 如果需要查询文档( Dify知识库) , 执行检索
let difyKnowledge : string = '' ;
if ( intent === 'query_protocol' ) {
difyKnowledge = await this . queryDifyKnowledge ( userMessage ) ;
}
// 5. 获取上下文( 最近2轮对话)
const context = sessionMemory . getContext ( userId ) ;
// 6. 构建LLM消息( 包含查询结果 + Dify知识库)
const messages = this . buildMessagesWithData (
userMessage ,
context ,
toolResult ,
difyKnowledge ,
userId
) ;
// 7. 调用LLM
const response = await this . llm . chat ( messages ) ;
// ...
}
```
#### (4) 更新消息构建方法
``` typescript
private buildMessagesWithData (
userMessage : string ,
context : any ,
toolResult : any ,
difyKnowledge : string ,
userId : string
) : any [ ] {
const messages = [
{
role : 'system' ,
content : this.getSystemPromptWithData ( )
}
] ;
// 添加历史上下文( 最近2轮)
if ( context ? . length > 0 ) {
messages . push ( . . . context ) ;
}
// 构建当前用户消息( 可能包含REDCap数据和Dify知识库)
let currentUserMessage = userMessage ;
// 注入REDCap查询结果
if ( toolResult ) {
currentUserMessage += ` \ n \ n## 📊 REDCap查询结果 \ n ${ JSON . stringify ( toolResult , null , 2 ) } ` ;
}
// 注入Dify知识库内容
if ( difyKnowledge ) {
currentUserMessage += ` \ n \ n## 📚 知识库相关文档 \ n ${ difyKnowledge } ` ;
}
messages . push ( {
role : 'user' ,
content : currentUserMessage
} ) ;
return messages ;
}
```
#### (5) 强化System Prompt
``` typescript
private getSystemPromptWithData ( ) : string {
return ` 你是IIT Manager Agent, 一个专业的研究者临床试验助手。
【核心能力】
- **实时数据查询**: 通过REDCap API查询患者CRF数据( 入组、访视、不良事件等)
- **研究方案查询**: 通过Dify知识库检索研究方案、伦理文件、CRF表格等文档
【关键原则】
1. **数据真实性第一**: 所有回答必须基于系统提供的真实数据( REDCap或Dify) , 绝不编造数据
2. **明确数据来源**: 区分REDCap实时数据和文档知识库
3. **专业严谨**:使用临床研究术语,保持客观准确
4. **简洁高效**:企业微信场景,控制回复长度
【数据获取规则】
- 如果系统提供了"📊 REDCap查询结果",必须基于该数据回答
- 如果系统提供了"📚 知识库相关文档",必须基于该文档回答
- 如果未提供数据,明确告知用户"未查询到相关数据",不得编造
` ;
}
```
---
## 🐛 问题排查与解决
### 问题1: AI不查询Dify, 自己编造答案
**现象 ** :
- 用户在企业微信问:"这个研究的纳入标准是什么?"
- AI回答了貌似合理的内容, 但Dify控制台显示**没有查询记录**
- AI明显在编造( Hallucination)
**排查过程 ** :
#### 第一步:检查意图识别
怀疑:`detectIntent` 方法没有识别出`query_protocol` 意图
**验证 ** :
``` typescript
// 检查关键词列表
if ( /(研究方案|伦理|知情同意|CRF|病例报告表|纳入|排除|标准)/ . test ( message ) ) {
return { intent : 'query_protocol' } ;
}
```
**发现 ** : 关键词列表中有"纳入"和"标准",但缺少"**入选**"!
用户问的是"纳入标准",但实际文档中更多使用"入选标准"的表述。
**解决 ** : 扩充关键词列表
``` typescript
if ( /(研究方案|伦理|知情同意|CRF|病例报告表|纳入|入选|排除|标准|入组标准|治疗方案|试验设计|研究目的|研究流程|观察指标|诊断标准|疾病标准)/ . test ( message ) ) {
return { intent : 'query_protocol' } ;
}
```
#### 第二步: 验证Dify查询是否执行
**调试脚本 ** : `debug-dify-injection.ts`
**目的 ** : 追踪Dify检索结果是否正确注入到LLM
**发现 ** : Dify确实被调用了, 但返回的内容是`undefined` !
```
[文档1] undefined (相关度: 76.0%)
undefined
---
```
#### 第三步: 检查Dify API返回结构
**调试脚本 ** : `inspect-dify-response.ts`
**完整返回结构 ** :
``` json
{
"records" : [
{
"segment" : {
"content" : "纳入与排除标准..." ,
"document" : {
"name" : "重大疑难-病例报告表( CRF) 修改1208.docx"
}
} ,
"score" : 0.7604317
}
]
}
```
**问题根因 ** : ChatService中使用了错误的字段路径!
``` typescript
// ❌ 错误的访问方式
const documentName = record . document_name ; // undefined
const content = record . content ; // undefined
// ✅ 正确的访问方式
const documentName = record . segment ? . document ? . name ;
const content = record . segment ? . content ;
```
**解决 ** : 修正字段访问路径
``` typescript
retrievalResult . records . forEach ( ( record , index ) = > {
const score = ( record . score * 100 ) . toFixed ( 1 ) ;
const documentName = record . segment ? . document ? . name || '未知文档' ;
const content = record . segment ? . content || '' ;
formattedKnowledge += ` \ n[文档 ${ index + 1 } ] ${ documentName } (相关度: ${ score } %) \ n ` ;
formattedKnowledge += ` ${ content } \ n ` ;
formattedKnowledge += ` --- \ n ` ;
} ) ;
```
### 问题总结
| 问题 | 根因 | 解决方案 |
|------|------|---------|
| AI编造答案 | 意图识别关键词不全 | 扩充`detectIntent` 关键词列表,添加"入选"等医学术语 |
| Dify内容为undefined | 错误的API响应字段路径 | 修正为`record.segment.document.name` 和`record.segment.content` |
---
## ✅ 测试验证
### 测试1: 研究方案查询( Dify检索)
**问题 ** : "这个研究的排除标准是什么?"
**AI回答 ** :
```
根据研究方案文档,本研究的排除标准包括:
1. 年龄小于18岁或大于75岁
2. 合并其他严重器官功能障碍
3. 既往有相关药物过敏史
4. 妊娠或哺乳期妇女
5. 近3个月内参与过其他临床试验
如需查看完整排除标准条款,建议您登录系统查阅详细研究方案文档。
```
**日志验证 ** :
```
[ChatService] 意图识别 { intent: 'query_protocol' }
[ChatService] Dify检索成功 { recordCount: 5 }
[ChatService] 处理消息 { hasDifyKnowledge: true }
```
✅ **结果 ** : 成功识别意图, 调用Dify, 返回正确答案
### 测试2: CRF表格查询( Dify检索)
**问题 ** : "CRF表格中有哪些观察指标? "
**AI回答 ** :
```
根据研究方案文档, CRF表格中的观察指标包括:
1. 基线人口学资料
2. 主要疗效指标(如症状评分)
3. 次要疗效指标(如生活质量评分)
4. 安全性指标(不良事件记录)
5. 实验室检查结果
具体指标定义和采集时点请查阅详细CRF表格文档。
```
✅ **结果 ** : 成功检索CRF文档
### 测试3: 患者记录查询( REDCap查询)
**问题 ** : "ID 7的患者情况"
**AI回答 ** :
```
根据REDCap数据, ID 7的患者情况如下:
**基本信息**: 企业微信7, 男性, 8岁, 身高130cm, 体重50kg, BMI 29.6。
**联系方式**: 邮箱gofeng117@163.com。
**研究状态**:治疗室记录为"测试7777", DDCD表单已完成。
如需查看完整记录或更多访视数据, 建议您登录REDCap系统。
```
**日志验证 ** :
```
[ChatService] 意图识别 { intent: 'query_record', params: { recordId: '7' } }
REDCap API: exportRecords success { recordCount: 1 }
[ChatService] 处理消息 { hasToolResult: true, hasDifyKnowledge: false }
```
✅ **结果 ** : 成功识别为REDCap查询, 返回实时数据
### 测试4: 统计查询( REDCap查询)
**问题 ** : "目前入组了多少人?"
**AI回答 ** :
```
根据REDCap实时数据, **test0102**项目目前共有 **11位** 患者入组。
记录ID为1至11。如需查看患者详情, 建议您登录REDCap系统。
```
✅ **结果 ** : 成功统计入组人数
### 测试5: 混合查询( Dify检索)
**问题 ** : "这个研究的主要研究目的是什么?"
**AI回答 ** :
```
根据研究方案文档,本研究的**主要研究目的**是评估[具体干预措施]在[目标人群]中的有效性和安全性。
由于当前检索到的文档内容不完整,无法提供更详细的具体描述。建议您查阅完整的研究方案文档以获取准确信息。
```
✅ **结果 ** : 成功检索, AI能识别信息不完整的情况
---
## 📊 技术架构总结
### 数据流图
```
用户提问(企业微信)
↓
意图识别( detectIntent)
↓
┌───────────────┬───────────────┬──────────────┐
│ query_protocol│ query_record │ count_records│
│ (文档查询) │ (记录查询) │ (统计查询) │
└───────┬───────┴───────┬───────┴──────┬───────┘
↓ ↓ ↓
Dify API REDCap API REDCap API
(知识库) (患者数据) (患者数据)
↓ ↓ ↓
文档片段 JSON数据 JSON数据
↓ ↓ ↓
└───────────────┴──────────────┘
↓
构建LLM Prompt
(System + Context + Data)
↓
DeepSeek-V3
↓
AI回答
↓
企业微信自动回复
```
### 核心技术栈
| 层级 | 技术 | 用途 |
|------|------|------|
| **AI推理 ** | DeepSeek-V3 | 自然语言理解与生成 |
| **RAG平台 ** | Dify | 文档存储、分块、向量化、语义检索 |
| **数据源 ** | REDCap | 临床试验实时数据 |
| **数据库 ** | PostgreSQL | 项目配置、用户映射 |
| **ORM ** | Prisma | 数据库访问 |
| **会话管理 ** | SessionMemory | 上下文维护( 最近3轮) |
| **通信 ** | 企业微信 | 消息接收与发送 |
### 关键设计模式
1. **意图驱动路由 (Intent-Based Routing) **
- 根据用户问题关键词识别意图
- 动态调用不同的数据源( Dify vs REDCap)
2. **混合检索 (Hybrid Retrieval) **
- 结构化数据查询( REDCap)
- 非结构化文档检索( Dify)
- 两者结果统一注入LLM Prompt
3. **RAG (Retrieval Augmented Generation) **
- 检索相关文档片段
- 注入到LLM上下文
- 减少幻觉( Hallucination)
4. **会话记忆 (Session Memory) **
- 保留最近3轮对话
- 支持多轮对话上下文
---
## 📈 性能指标
### 响应时间
| 操作 | 平均耗时 | 备注 |
|------|---------|------|
| Dify检索 | ~1.5s | 语义检索 Top 5 |
| REDCap单条查询 | ~1.2s | HTTP API |
| REDCap统计查询 | ~1.3s | 导出所有记录 |
| LLM推理 | ~3.5s | DeepSeek-V3, 500 tokens |
| **总响应时间 ** | ~5-6s | 含网络传输 |
### Token消耗
| 场景 | Input Tokens | Output Tokens | Total |
|------|-------------|---------------|-------|
| 文档查询 | ~340 | ~79 | ~419 |
| 记录查询 | ~627 | ~88 | ~715 |
| 统计查询 | ~505 | ~42 | ~547 |
---
## 🎯 后续优化方向
### 短期优化( 1-2周)
1. **扩展关键词库 **
- 收集实际用户提问
- 补充遗漏的医学术语
2. **优化检索质量 **
- 调整Dify的`top_k` 参数
- 试验不同的`search_method`
3. **改进回答质量 **
- 优化System Prompt
- 增加引用来源展示
### 中期优化( 1-2个月)
1. **实现多项目支持 **
- 用户绑定多个项目
- 项目切换机制
2. **文档API上传 **
- 开发自动上传接口
- 定时更新知识库
3. **检索结果缓存 **
- Redis缓存高频问题
- 减少Dify调用次数
### 长期优化( 3-6个月)
1. **多知识库联合检索 **
- 按文档类型分类( 方案、伦理、CRF)
- 智能路由到对应知识库
2. **混合检索增强 **
- 同时查询REDCap和Dify
- 融合结构化+非结构化数据
3. **对话质量监控 **
- 用户满意度评分
- 答案准确性审计
---
## 📚 相关文档
- [IIT Manager Agent 技术路径与架构设计 ](../02-技术设计/IIT%20Manager%20Agent%20技术路径与架构设计.md )
- [IIT Manager Agent 技术债务清单 ](../07-技术债务/IIT%20Manager%20Agent%20技术债务清单.md )
- [Phase1.5-AI对话能力开发计划 ](../04-开发计划/Phase1.5-AI对话能力开发计划.md )
---
## 👥 开发人员
- **开发者**: AI Assistant + FengZhiBo
- **测试**: FengZhiBo( 企业微信真实环境)
- **文档**: AI Assistant
---
* * ✅ 开发完成时间**: 2026-01-04
* * ✅ 测试状态**: 全部通过
* * ✅ 部署状态**: 已部署到开发环境