feat(iit): harden QC pipeline consistency and release artifacts
Implement IIT quality workflow hardening across eQuery deduplication, guard metadata validation, timeline/readability improvements, and chat evidence fallbacks, then synchronize release and development documentation for deployment handoff. Includes migration/scripts for open eQuery dedupe guards, orchestration/status semantics, report/tool readability fixes, and updated module status plus deployment checklist. Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# 请将 PDF 样本文件放在此目录
|
||||
|
||||
## 建议准备:
|
||||
- sample-01-rct.pdf
|
||||
- sample-02-cohort.pdf
|
||||
- sample-03-with-tables.pdf
|
||||
- sample-04-with-formulas.pdf
|
||||
- sample-05-chinese.pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,3 +7,15 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# IIT Manager Agent模块 - 当前状态与开发指南
|
||||
|
||||
> **文档版本:** v3.2
|
||||
> **文档版本:** v3.3
|
||||
> **创建日期:** 2026-01-01
|
||||
> **维护者:** IIT Manager开发团队
|
||||
> **最后更新:** 2026-03-02 **数据一致性修复 + 项目隔离 + 管理端配置流重设计 + 中文显示名!**
|
||||
> **最后更新:** 2026-03-08 **IIT 回归修复收口(事件名中文化、D1 规则名友好显示、AI 证据兜底)**
|
||||
> **重大里程碑:**
|
||||
> - **2026-03-08:IIT 回归修复收口!** 实时工作流事件名友好化(含兜底)、D1 不合规条目显示规则名称、AI 对话新增“证据不为空”补齐逻辑
|
||||
> - **2026-03-02:QC 数据一致性修复!** AI 时间线 + 警告详情 统一从 qc_field_status(SSOT)读取,与热力图数据一致
|
||||
> - **2026-03-02:字段/事件中文显示名!** LEFT JOIN field_metadata + qc_event_status,消除 REDCap 技术标识符
|
||||
> - **2026-03-02:警告详情可查看!** 新增 field-issues 分页 API + ReportsPage 严重问题/警告数字可点击弹出详情 Modal
|
||||
@@ -66,7 +67,7 @@ CRA Agent 是一个**替代 CRA 岗位的自主 AI Agent**,而非辅助 CRA
|
||||
- AI能力:DeepSeek/Qwen + 自研 RAG(pgvector)+ LLM Tool Use
|
||||
|
||||
### 当前状态
|
||||
- **开发阶段**:**V3.2 数据一致性 + 项目隔离 + 管理端重设计 + 中文显示名 → 待部署验证**
|
||||
- **开发阶段**:**V3.3 回归修复收口(可读性 + 证据兜底)→ 待部署验证**
|
||||
- **V3.2 数据一致性 + 项目隔离已完成**(2026-03-02):
|
||||
- AI 时间线改为从 qc_field_status(SSOT)聚合,与风险热力图数据一致
|
||||
- 新增 field-issues 分页查询 API(支持按维度/严重程度/受试者筛选)
|
||||
@@ -152,6 +153,18 @@ CRA Agent 是一个**替代 CRA 岗位的自主 AI Agent**,而非辅助 CRA
|
||||
- iitBatchController 执行 QcExecutor.executeBatch 后调用 DailyQcOrchestrator.orchestrate
|
||||
- 返回 equeriesCreated 计数
|
||||
|
||||
#### ✅ 已完成功能(V3.3 回归修复收口 - 2026-03-08)
|
||||
- ✅ **AI 实时工作流事件名友好化补强**:
|
||||
- `getTimeline` 增加事件标签聚合映射(`qc_event_status` + `cachedRules.eventLabels`)
|
||||
- 无标签时采用 `访视(eventId)` 兜底,避免裸露技术 ID
|
||||
- ✅ **D1 筛选入选表“不合规条目”友好显示**:
|
||||
- 前端由 `ruleId` 改为优先显示 `ruleName`
|
||||
- ✅ **AI 对话“证据为空”结构性兜底**:
|
||||
- 对话编排层改为“只要回答中出现 `证据:` 且无条目,即强制补齐”
|
||||
- 新增项目级 summary 证据抽取(健康度、总受试者、严重/警告问题数、通过率)
|
||||
- ⚠️ **当前风险提示**:
|
||||
- 用户反馈“问题仍在”,需在线上运行态做最终复核(可能涉及服务未重启或旧进程实例)
|
||||
|
||||
#### ✅ 已完成功能(GCP 业务报表 + AI 时间线 + Bug 修复 - 2026-03-01)
|
||||
- ✅ **GCP 标准报表(阶段 A 4 张)**:
|
||||
- D1 筛选入选表(getEligibilityReport:record_summary 全量 + qc_field_status D1 叠加)
|
||||
@@ -796,11 +809,24 @@ npx ts-node src/modules/iit-manager/test-wechat-push.ts
|
||||
### 9.4 开发记录文档
|
||||
|
||||
- [V1.1更新完成报告](./06-开发记录/V1.1更新完成报告.md) - 技术方案更新记录
|
||||
- [2026-03-08-IIT-CRA-回归修复与发布收口记录](./06-开发记录/2026-03-08-IIT-CRA-回归修复与发布收口记录.md) - 今日收口与发布前状态
|
||||
|
||||
---
|
||||
|
||||
## 🔄 十、更新日志
|
||||
|
||||
### 2026-03-08:IIT 回归修复收口(可读性 + AI 证据兜底) ⚙️
|
||||
|
||||
**完成内容**:
|
||||
- ✅ AI 实时工作流详情事件名增强:数据库标签 + 规则缓存标签 + 可读兜底
|
||||
- ✅ D1 受试者逐条判定“不合规条目”改为规则名称优先显示
|
||||
- ✅ AI 对话“结论有、证据空”补齐逻辑加强(覆盖项目级问句)
|
||||
- ✅ 文档同步:新增当日开发记录,更新模块状态与待部署清单
|
||||
|
||||
**状态结论**:
|
||||
- 代码侧修复已完成并通过静态检查
|
||||
- 仍需部署后结合线上进程状态完成最终验收
|
||||
|
||||
### 2026-01-04:Dify知识库集成完成 - 混合检索实现 ✅
|
||||
|
||||
**完成内容**:
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# 2026-03-08 IIT CRA 回归修复与发布收口记录
|
||||
|
||||
> 日期:2026-03-08
|
||||
> 范围:IIT Manager Agent(IIT 质控驾驶舱 + 报告与关键事件 + AI 对话助手)
|
||||
> 关联项目:`原发性痛经0302`(projectId: `1d80f270-6a02-4b58-9db3-6af176e91f3c`)
|
||||
|
||||
---
|
||||
|
||||
## 1. 今日目标
|
||||
|
||||
围绕用户反馈的三类问题做回归修复并准备发布收口:
|
||||
|
||||
1. AI 实时工作流详情中事件名仍出现技术 ID。
|
||||
2. D1 筛选入选表中“不合规条目”显示 `rule_xxx`,不可读。
|
||||
3. AI 对话助手出现“结论有、证据空”的回答。
|
||||
|
||||
---
|
||||
|
||||
## 2. 今日已完成变更
|
||||
|
||||
### 2.1 AI 实时工作流事件名中文化增强
|
||||
|
||||
- 文件:`backend/src/modules/admin/iit-projects/iitQcCockpitController.ts`
|
||||
- 变更:
|
||||
- 新增 `buildEventLabelMap()`:聚合 `qc_event_status.event_label`,并兼容 `cachedRules.eventLabels`。
|
||||
- 新增 `fallbackEventLabel()`:无标签时以 `访视(eventId)` 兜底,避免裸技术 ID。
|
||||
- 在 `getTimeline` 组装 issue 时,`eventLabel` 采用“数据库标签 -> 规则缓存标签 -> 友好兜底”的顺序。
|
||||
|
||||
### 2.2 D1 不合规条目友好显示
|
||||
|
||||
- 文件:`frontend-v2/src/modules/iit/components/reports/EligibilityTable.tsx`
|
||||
- 变更:
|
||||
- 前端构建 `ruleId -> ruleName` 映射。
|
||||
- “不合规条目”列改为优先显示规则名称,缺失时才回退 `ruleId`。
|
||||
|
||||
### 2.3 AI 对话证据块兜底修复
|
||||
|
||||
- 文件:`backend/src/modules/iit-manager/services/ChatOrchestrator.ts`
|
||||
- 变更:
|
||||
- 调整证据补齐触发条件:不再仅限“患者类问题”;只要出现 `证据:` 且无明细,就尝试自动补齐。
|
||||
- 扩展证据抽取:支持项目级 summary(`healthScore`、`healthGrade`、`totalRecords`、`criticalIssues`、`warningIssues`、`passRate`)与患者级细节共同兜底。
|
||||
|
||||
---
|
||||
|
||||
## 3. 结果与现状
|
||||
|
||||
### 已确认改善
|
||||
|
||||
- 实时工作流详情中的事件名显示链路已具备中文化能力与可读兜底。
|
||||
- D1 表格“不合规条目”已由规则 ID 显示改为规则名称优先。
|
||||
- AI 对话“证据为空”逻辑已在编排层增加强制补齐策略。
|
||||
|
||||
### 仍待现场验证
|
||||
|
||||
- 用户最新反馈“问题依然存在”,说明线上/当前运行进程可能仍受以下因素影响:
|
||||
1. 服务进程未重启,旧实例(缓存 orchestrator)未加载新逻辑;
|
||||
2. 生产/测试环境并非本地当前分支镜像;
|
||||
3. 某些对话路径绕过了已补齐分支(需加会话级 trace 验证)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 根因判断(针对“证据为空”)
|
||||
|
||||
本次定位到的核心根因是:
|
||||
|
||||
- 旧逻辑把证据补齐限制在“患者类意图”,导致“最新质控报告怎么样”这类项目级问句可能跳过补齐分支;
|
||||
- 当 LLM 输出了 `结论 + 证据:` 但未附条目时,前端看到的就是空证据块。
|
||||
|
||||
本次已在编排层做“证据头存在即补齐”的保护,属于结构性修复方向。
|
||||
|
||||
---
|
||||
|
||||
## 5. 待办与明日计划
|
||||
|
||||
1. 以线上真实环境复现同一问句:`最新质控报告怎么样?`,抓取完整请求链路日志。
|
||||
2. 在 `ChatOrchestrator` 增加一次性 debug 日志(tool payload 摘要 + 证据补齐命中标记),用于确认分支命中率。
|
||||
3. 对“证据模板”加最低保障:当工具返回成功且 answer 含 `证据:` 时,至少输出 1 条结构化证据(不允许空块)。
|
||||
4. 完成本轮发布后,补一份“AI 问答回归用例清单(项目级/患者级/维度级)”纳入日常回归。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联文档
|
||||
|
||||
- 技术评审与回归看板:
|
||||
`docs/03-业务模块/IIT Manager Agent/09-技术评审报告/2026-03-08-IIT-CRA-最小复现项目对账结果-Phase1-原发性痛经0302.md`
|
||||
- 模块总览与开发指南:
|
||||
`docs/03-业务模块/IIT Manager Agent/00-模块当前状态与开发指南.md`
|
||||
- 待部署变更清单:
|
||||
`docs/05-部署文档/03-待部署变更清单.md`
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# IIT / CRA Agent 指标口径与 SSOT 对照表(Phase 0)
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 创建日期:2026-03-08
|
||||
> 适用范围:IIT 业务端大盘 / 报告与关键事件 / AI 实时工作流水 / AI 对话
|
||||
> 目标:先统一“同名指标”的定义和数据源,再进入修复,避免反复打补丁
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与问题定义
|
||||
|
||||
当前线上/测试反馈的核心不是单点报错,而是同一指标在多个页面口径不一致,例如:
|
||||
|
||||
- 大盘通过率与趋势通过率不一致
|
||||
- 报告页健康分与大盘健康分不一致
|
||||
- 时间线问题数与待处理 eQuery 不一致
|
||||
- 报告中 D1/D2/D3D4/D6 与下方维度评分对不上
|
||||
|
||||
这些问题在工程上属于**口径漂移(metric drift)**,必须先冻结 SSOT(Single Source of Truth),再排查数据同步、规则执行、前端展示。
|
||||
|
||||
---
|
||||
|
||||
## 2. 指标分层模型(必须统一)
|
||||
|
||||
IIT 指标按 4 层定义,禁止跨层混算:
|
||||
|
||||
1. **事实层(Raw Facts)**:REDCap 原始记录与事件数据
|
||||
2. **执行层(QC Results)**:`qc_field_status` / `qc_event_status` 的规则执行结果
|
||||
3. **聚合层(Project Stats)**:`iit_qc_project_stats`、`iit_record_summary` 的聚合快照
|
||||
4. **呈现层(UI/API)**:Dashboard/Reports/AiStream/AiChat
|
||||
|
||||
约束:
|
||||
|
||||
- 呈现层不允许重新发明计算公式,只消费聚合层或执行层
|
||||
- 同一名称指标只能有一个“主口径”
|
||||
- 不同用途的指标必须显式命名(例如“按受试者通过率”与“按事件通过率”)
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心指标 SSOT 对照表(冻结版)
|
||||
|
||||
## 3.1 项目健康度与通过率
|
||||
|
||||
| 指标名 | 业务定义 | SSOT 来源 | 当前代码入口 | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| healthScore | 健康度总分(0-100) | `iit_qc_project_stats.health_score` | `iitQcCockpitService.getStats()` | 不允许前端 fallback 推导 |
|
||||
| healthGrade | 健康度等级 | `iit_qc_project_stats.health_grade` | `iitQcCockpitService.getStats()` | 同上 |
|
||||
| passRate (大盘) | 按受试者通过率 | `passed_records / total_records` | `iitQcCockpitService.getStats()` | 保留 1 位小数 |
|
||||
| passRate (趋势) | 按日志条目通过率(旧) | `iit_qc_logs` 分组计算 | `iitQcCockpitController.getTrend()` | 与大盘不是同一口径,必须重命名或替换 |
|
||||
|
||||
结论:
|
||||
|
||||
- 当前“通过率”至少有两种口径,UI 未标注,必然引发“100% vs 33%”类问题。
|
||||
|
||||
## 3.2 D1/D2/D3D4/D6 报表
|
||||
|
||||
| 报表 | 业务定义 | SSOT 来源 | 当前查询入口 |
|
||||
|---|---|---|---|
|
||||
| D1 筛选入选 | 入排规则是否通过 | `qc_field_status`(D1) + `record_summary`(受试者全集) | `iitQcCockpitService.getEligibilityReport()` |
|
||||
| D2 完整性 | 缺失字段率 | `qc_field_status`(D2) | `iitQcCockpitService.getCompletenessReport()` |
|
||||
| D3/D4 质疑跟踪 | eQuery 生命周期 | `iit_equeries` | `iitQcCockpitService.getEqueryLogReport()` |
|
||||
| D6 方案偏离 | 访视超窗等偏离 | `qc_field_status`(D6) | `iitQcCockpitService.getDeviationReport()` |
|
||||
|
||||
结论:
|
||||
|
||||
- D 类报表与大盘维度评分来自不同聚合路径时,必须明确“时点一致性”(同一批次同一时刻)。
|
||||
|
||||
## 3.3 AI 实时工作流水
|
||||
|
||||
| 指标 | 业务定义 | SSOT 来源 | 当前入口 | 风险 |
|
||||
|---|---|---|---|---|
|
||||
| timeline total | 时间线受试者总数 | `qc_field_status` 聚合 + pass 补充 | `iitQcCockpitController.getTimeline()` | 与 eQuery 总数不是同一概念 |
|
||||
| red/yellow 问题数 | 每受试者 FAIL/WARNING 汇总 | `qc_field_status` | `getTimeline()` | 应与 field-issues 可对账 |
|
||||
| 事件中文名 | event display label | `qc_event_status.event_label` | `getTimeline()` LEFT JOIN | 空值时会回退技术名 |
|
||||
|
||||
## 3.4 AI 对话
|
||||
|
||||
| 语义工具 | 主要数据源 | 适用问题 | 当前实现 |
|
||||
|---|---|---|---|
|
||||
| `read_report` | `QcReportService` 缓存报告 | 通过率、问题统计、趋势摘要 | `ToolsService` |
|
||||
| `look_up_data` | REDCap 原始数据 | 单患者字段值、原始记录核查 | `ToolsService` |
|
||||
| `check_quality` | `QcExecutor` 实时执行 | 用户明确要求“重跑质控” | `ToolsService` |
|
||||
| `search_knowledge` | 项目知识库(RAG) | 方案文本、入排标准文本 | `ToolsService` |
|
||||
|
||||
结论:
|
||||
|
||||
- 对话错误不等于“模型幻觉”,优先排查是否选错工具或工具查询不完整。
|
||||
|
||||
---
|
||||
|
||||
## 4. 现存口径漂移点(已识别)
|
||||
|
||||
1. **趋势口径漂移**
|
||||
- 趋势接口仍从 `iit_qc_logs` 计算通过率;大盘通过率来自 `iit_qc_project_stats`。
|
||||
- 导致“卡片 100%,趋势 33%”。
|
||||
|
||||
2. **健康分 fallback 风险**
|
||||
- 大盘前端在 `healthScore` 缺失时回退为 `Math.round(passRate)`。
|
||||
- 数据未准备好时会把通过率误当健康分,造成“待处理质疑很多但仍 100 分”。
|
||||
|
||||
3. **报告缓存时点风险**
|
||||
- `read_report` 优先读缓存报告(默认有效期 24h),若未及时刷新会滞后于大盘/流水。
|
||||
|
||||
4. **“总数”语义混淆**
|
||||
- 时间线问题数、eQuery 待处理数、D3D4 报表数本质是不同对象,UI 文案未区分。
|
||||
|
||||
5. **展示字段名兼容风险**
|
||||
- 若 `eventLabel/fieldLabel` 空,前端回退技术字段名;用户会误判为“事件名错误”。
|
||||
|
||||
---
|
||||
|
||||
## 5. 冻结规则(修复期强制执行)
|
||||
|
||||
1. 页面上所有“通过率”必须标注口径:
|
||||
- 按受试者(record)
|
||||
- 按事件(record-event)
|
||||
- 按规则检查条目(field-level checks)
|
||||
|
||||
2. 健康度评分只允许来自 `healthScoreEngine` 落库结果,不允许前端推算。
|
||||
|
||||
3. 报告页、大盘、AI 对话必须显示“统计快照时间”,便于用户识别时点差异。
|
||||
|
||||
4. UI 文案新增说明:
|
||||
- “待处理质疑”来自 `iit_equeries`
|
||||
- “时间线问题总数”来自 `qc_field_status`
|
||||
|
||||
5. 涉及 D1/D2/D3D4/D6 的变更,必须附带“口径回归测试”记录。
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 1 进入条件
|
||||
|
||||
满足以下条件后,才进入根因排查与代码修复:
|
||||
|
||||
- [ ] 本文“SSOT 对照表”已被团队确认(产品/研发/质控)
|
||||
- [ ] 每个页面核心指标已标注口径
|
||||
- [ ] 已选定“最小复现项目”与冻结时间窗口
|
||||
|
||||
Phase 1 执行手册见:
|
||||
`docs/03-业务模块/IIT Manager Agent/09-技术评审报告/2026-03-08-IIT-CRA-最小复现项目对账执行手册-Phase1.md`
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# IIT / CRA Agent 最小复现项目对账执行手册(Phase 1)
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 创建日期:2026-03-08
|
||||
> 目标:用最小复现项目定位根因,不做盲修
|
||||
> 适用问题:报告与事件不一致、AI 实时流水不一致、AI 对话结论错误
|
||||
|
||||
---
|
||||
|
||||
## 1. 执行原则
|
||||
|
||||
1. **先对账,后修复**:每个异常必须先确认“错在数据、规则、聚合、还是展示”。
|
||||
2. **单项目封闭验证**:只使用“纳入排除标准测试”项目,不混其他项目。
|
||||
3. **同一时间窗口**:所有 API / SQL 在同一时段采样,避免时间漂移。
|
||||
4. **一条问题一条证据链**:必须保留“输入 -> 处理中间态 -> 输出”。
|
||||
|
||||
---
|
||||
|
||||
## 2. 最小复现项目准备
|
||||
|
||||
项目:`纳入排除标准测试`
|
||||
固定对象:
|
||||
|
||||
- 患者:`2 / 3 / 4`(至少包含不完整录入与违规样本)
|
||||
- 事件:筛选期 + 第一次月经周期结束时(若项目定义如此)
|
||||
- 规则:D1/D2/D3/D4/D6 至少各有 1 条命中场景
|
||||
|
||||
冻结窗口:
|
||||
|
||||
- 执行前停止自动批任务(避免数据持续变化)
|
||||
- 手动执行一次“一键全量质控”,记录触发时间 T0
|
||||
- 所有采样以 T0 后 1-3 分钟为准
|
||||
|
||||
---
|
||||
|
||||
## 3. 四层对账流程(按顺序)
|
||||
|
||||
## 3.1 事实层(REDCap 是否联通)
|
||||
|
||||
目标:确认“源头有数据”。
|
||||
|
||||
检查项:
|
||||
|
||||
- REDCap 中是否存在目标患者/事件数据
|
||||
- `record_summary` 是否包含对应 `record_id`
|
||||
- `SyncManager` 最近同步时间是否晚于 T0
|
||||
|
||||
判定:
|
||||
|
||||
- REDCap 有、平台无 -> 联通/同步问题
|
||||
- REDCap 无 -> 上游录入问题,不是质控引擎问题
|
||||
|
||||
## 3.2 执行层(规则是否正确执行)
|
||||
|
||||
目标:确认“规则判断是否符合临床预期”。
|
||||
|
||||
检查项:
|
||||
|
||||
- `qc_field_status` 是否有该患者对应 D1/D2/D3/D6 记录
|
||||
- `status/severity/message/actual_value/expected_value` 是否合理
|
||||
- `qc_event_status` 是否与字段级状态一致
|
||||
|
||||
判定:
|
||||
|
||||
- 执行结果本身错 -> 规则定义/引擎问题
|
||||
- 执行结果正确、展示错 -> 聚合/前端问题
|
||||
|
||||
## 3.3 聚合层(报表和大盘是否同口径)
|
||||
|
||||
目标:确认“同名指标是否来自同一口径与同一快照”。
|
||||
|
||||
检查项:
|
||||
|
||||
- 大盘:`getQcCockpitData`
|
||||
- 报告:`getQcReport` / `refreshQcReport`
|
||||
- 趋势:`getTrend`
|
||||
- D1/D2/D3D4/D6:各报表 API
|
||||
|
||||
重点看:
|
||||
|
||||
- 通过率口径(按受试者 vs 按日志)
|
||||
- 健康分是否来自 `healthScore` 真实值
|
||||
- 报告缓存是否过期/未刷新
|
||||
|
||||
## 3.4 对话层(AI 工具链路是否选对)
|
||||
|
||||
目标:确认“AI 回答错误是查询错还是推理错”。
|
||||
|
||||
检查项:
|
||||
|
||||
- 问题触发了哪个工具(`read_report` / `look_up_data` / `check_quality` / `search_knowledge`)
|
||||
- 工具返回数据是否完整(如实验室检查字段是否缺失)
|
||||
- 最终回答是否与工具结果一致
|
||||
|
||||
判定:
|
||||
|
||||
- 工具返回错 -> 数据查询/映射/项目隔离问题
|
||||
- 工具返回对、回答错 -> Prompt/回答策略问题
|
||||
|
||||
---
|
||||
|
||||
## 4. 针对当前三大类问题的定位矩阵
|
||||
|
||||
## 4.1 第一大类:报告与关键事件
|
||||
|
||||
| 现象 | 优先怀疑 | 第一检查点 |
|
||||
|---|---|---|
|
||||
| D1/D2/D3D4/D6 与维度评分不一致 | 聚合口径漂移 | `getStats()` vs 各报表 SQL |
|
||||
| 质控 0 个受试者 | 执行未落库或 projectId 错 | `qc_field_status` 是否有该项目数据 |
|
||||
| 待处理 252 但健康分 100 | 健康分 fallback / 评分权重缺陷 | 前端 `healthScore` fallback、HealthScoreEngine 维度权重 |
|
||||
| 上方通过率 100,下方趋势 33 | 趋势口径不同 | `getTrend()` 当前读 `iit_qc_logs` |
|
||||
| 质控完成无热力图 | `qc_event_status` 空或事件列构建失败 | `getHeatmapData()` 查询结果 |
|
||||
| 执行摘要无信息 | 报告缓存旧 / 生成失败 | `iit_qc_reports` 最新记录 + refresh |
|
||||
| 报告分 64 vs 大盘 100 | 报告快照与实时不一致 | `report.generatedAt` vs cockpit now |
|
||||
| D1 无数据 | D1 规则缺失或 rule_category 错 | `qc_field_status where D1` |
|
||||
| D2 事件数=1 且明细异常 | D2 统计规则/activeEvents定义不清 | `getCompletenessReport()` 的 byRecordEvent |
|
||||
| 已回复仍显示 0 | 状态机未推进或统计口径错 | `iit_equeries.status` 分布 |
|
||||
| 无“重开质疑”操作 | 前端缺动作入口(后端有状态) | EQueryPage 操作列 |
|
||||
|
||||
## 4.2 第二大类:AI 实时工作流水
|
||||
|
||||
| 现象 | 优先怀疑 | 第一检查点 |
|
||||
|---|---|---|
|
||||
| 流水问题总数 444 vs 待处理 252 | 指标对象不同 | 时间线来自 `qc_field_status`,待处理来自 `iit_equeries` |
|
||||
| 事件编码生成逻辑不明 | 事件标签映射缺失 | `eventLabel` 来源:`qc_event_status` / fallback |
|
||||
| 日期筛选按钮无效 | API 过滤未生效或前端未传 | `getTimeline(date=YYYY-MM-DD)` 返回是否变化 |
|
||||
|
||||
## 4.3 第三大类:AI 对话助手
|
||||
|
||||
| 现象 | 优先怀疑 | 第一检查点 |
|
||||
|---|---|---|
|
||||
| 已签署知情显示 0 | 工具选路错误或字段未映射 | `look_up_data` 查询字段覆盖 |
|
||||
| 3号患者严重问题被说成无问题 | read_report 缓存滞后或筛选逻辑错 | `QcReportService` 缓存时间与 issue 列表 |
|
||||
| 总体通过率异常(分项非0,总体0) | 聚合口径错误 | 报告 summary.passRate 公式 |
|
||||
| 查询患者2信息不全 | 工具默认返回字段不完整 | `look_up_data` 默认 data 结构 |
|
||||
| 4号患者访视名错误/状态描述偏差 | eventLabel 回退技术ID + 业务叙述模板粗糙 | `eventLabel` 链路与回答模板 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 执行清单(逐项打勾)
|
||||
|
||||
## 5.1 一次完整排查(建议 2-3 小时)
|
||||
|
||||
- [ ] 执行 `batch-qc`,记录 T0、返回 `totalRecords/totalEventCombinations/passRate`
|
||||
- [ ] 拉取驾驶舱:`qc-cockpit`
|
||||
- [ ] 拉取报告:`qc-cockpit/report`(先读缓存,再 refresh)
|
||||
- [ ] 拉取趋势:`qc-cockpit/trend`
|
||||
- [ ] 拉取时间线:`qc-cockpit/timeline`
|
||||
- [ ] 拉取 D1/D2/D3D4/D6 报表
|
||||
- [ ] 拉取 `field-issues`(critical + warning)
|
||||
- [ ] 拉取 eQuery stats 与 list
|
||||
- [ ] 对 5 条 AI 问答样例做工具链路记录
|
||||
|
||||
## 5.2 SQL 对账模板(只读)
|
||||
|
||||
> 以下为只读核对 SQL,请在测试库或只读会话执行。
|
||||
|
||||
```sql
|
||||
-- 1) 项目级统计快照
|
||||
SELECT project_id, total_records, passed_records, failed_records, warning_records,
|
||||
health_score, health_grade, d1_pass_rate, d2_pass_rate, d3_pass_rate, d5_pass_rate, d6_pass_rate, d7_pass_rate
|
||||
FROM iit_schema.qc_project_stats
|
||||
WHERE project_id = :project_id;
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 2) 字段级问题总量(SSOT)
|
||||
SELECT status, severity, rule_category, COUNT(*) AS cnt
|
||||
FROM iit_schema.qc_field_status
|
||||
WHERE project_id = :project_id
|
||||
GROUP BY status, severity, rule_category
|
||||
ORDER BY rule_category, status, severity;
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 3) 事件级状态
|
||||
SELECT record_id, event_id, event_label, status, fields_total, fields_passed, fields_failed, fields_warning
|
||||
FROM iit_schema.qc_event_status
|
||||
WHERE project_id = :project_id
|
||||
ORDER BY record_id, event_id;
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 4) eQuery 状态分布
|
||||
SELECT status, COUNT(*) AS cnt
|
||||
FROM iit_schema.iit_equeries
|
||||
WHERE project_id = :project_id
|
||||
GROUP BY status
|
||||
ORDER BY status;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 修复优先级建议(按风险)
|
||||
|
||||
P0(先修):
|
||||
|
||||
1. 通过率口径统一(大盘/趋势/报告明确区分)
|
||||
2. 健康分来源统一(禁前端 fallback 推算)
|
||||
3. 报告缓存刷新策略(批量质控后强制刷新并透出快照时间)
|
||||
4. 时间线与 eQuery 统计文案区分(避免同名误读)
|
||||
|
||||
P1(随后):
|
||||
|
||||
1. EQuery “重开”前端操作入口(后端状态机已支持)
|
||||
2. D2“活跃事件/缺失字段”定义可视化说明
|
||||
3. AI 对话输出模板升级(访视表格化 + 证据引用)
|
||||
|
||||
P2(优化):
|
||||
|
||||
1. 工具调用 trace 可视化(问题 -> 工具 -> 结果 -> 回答)
|
||||
2. 指标字典落地到接口 schema,前后端共享类型
|
||||
|
||||
---
|
||||
|
||||
## 7. 本阶段交付物要求
|
||||
|
||||
完成 Phase 1 后,必须输出:
|
||||
|
||||
1. `问题-根因对照表`(你列出的每条问题都要有结论)
|
||||
2. `证据包`(API 返回 + SQL 结果 + 截图)
|
||||
3. `修复方案分批计划`(P0/P1/P2 + 影响面 + 回归用例)
|
||||
|
||||
没有这三项,不进入大规模代码修改。
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
# IIT / CRA Agent 最小复现项目对账结果(Phase 1)
|
||||
|
||||
> 执行日期:2026-03-08
|
||||
> 环境:本地开发环境(backend + postgres docker + local REDCap)
|
||||
> 目标:验证“报告/大盘/流水/对话”异常是否来自 REDCap 联通、规则执行、聚合链路或展示口径。
|
||||
|
||||
---
|
||||
|
||||
## 1. 本次对账对象
|
||||
|
||||
- IIT 项目名称:`原发性痛经0302`
|
||||
- IIT 项目 ID:`1d80f270-6a02-4b58-9db3-6af176e91f3c`
|
||||
- 项目 REDCap PID:`18`
|
||||
- 项目 REDCap URL:`http://localhost:8080`
|
||||
- 数据库:`ai_clinical_research`(`ai-clinical-postgres`)
|
||||
|
||||
项目映射校验结果:
|
||||
|
||||
- `iit_schema.projects` 中存在且唯一:
|
||||
- `id=1d80f270-6a02-4b58-9db3-6af176e91f3c`
|
||||
- `name=原发性痛经0302`
|
||||
- `redcap_project_id=18`
|
||||
- `status=active`
|
||||
|
||||
---
|
||||
|
||||
## 2. 第一轮 SQL 证据(核心)
|
||||
|
||||
### 2.1 聚合结果与原始质控事实明显不一致
|
||||
|
||||
- `iit_schema.qc_project_stats`(该项目):
|
||||
- `total_records=0`
|
||||
- `passed_records=0`
|
||||
- `failed_records=0`
|
||||
- `warning_records=0`
|
||||
- `health_score=82.3`
|
||||
- `d1_pass_rate=41.2`, `d2=100`, `d3=100`, `d5=100`, `d6=69.6`, `d7=100`
|
||||
- `iit_schema.record_summary`(该项目):
|
||||
- `COUNT(DISTINCT record_id)=0`
|
||||
- `iit_schema.qc_field_status`(该项目):
|
||||
- `COUNT(*)=713`
|
||||
- `COUNT(DISTINCT record_id)=12`
|
||||
- 存在 FAIL/WARNING/PASS 混合状态,且 D1/D2/D3/D5/D6 均有数据
|
||||
- `iit_schema.qc_event_status`(该项目):
|
||||
- `COUNT(*)=25`
|
||||
- `iit_schema.equery`(该项目):
|
||||
- `pending=262`
|
||||
|
||||
判定:`qc_field_status/qc_event_status/equery` 已有大量有效数据,但 `record_summary` 为空,导致项目级统计口径断裂。
|
||||
|
||||
### 2.2 历史项目残留与项目隔离异常迹象
|
||||
|
||||
- `record_summary` 总表仅有 14 行,且全部属于旧项目 ID:`test0102-pd-study`
|
||||
- 当前项目(UUID)在 `record_summary` 无行
|
||||
|
||||
判定:当前项目未建立或未插入 `record_summary` 基础行,后续“仅 UPDATE 的聚合语句”无法生效。
|
||||
|
||||
---
|
||||
|
||||
## 3. 第一轮 API 证据(核心)
|
||||
|
||||
使用管理员账号登录后,调用 `/api/v1/admin/iit-projects/:projectId/...`。
|
||||
|
||||
### 3.1 Dashboard / Report / Trend 三口径冲突
|
||||
|
||||
- `qc-cockpit` 返回:
|
||||
- `qualityScore=82`
|
||||
- `totalRecords=0`
|
||||
- `passRate=100`
|
||||
- `criticalCount=137`
|
||||
- `queryCount=262`
|
||||
- `qc-cockpit/report` 返回:
|
||||
- `totalRecords=0`
|
||||
- `criticalIssues=156`
|
||||
- `warningIssues=284`
|
||||
- `passRate=0`
|
||||
- `qc-cockpit/trend` 返回:
|
||||
- 最新点:`total=148`, `passed=48`, `passRate=32`
|
||||
|
||||
判定:同一项目同一时间,`passRate` 至少出现 `100 / 0 / 32` 三个版本,属于 P0 级口径漂移。
|
||||
|
||||
### 3.2 时间线日期筛选无效(复现)
|
||||
|
||||
调用 `qc-cockpit/timeline`:
|
||||
|
||||
- 不带日期:`total=12`
|
||||
- `startDate=2026-03-01&endDate=2026-03-02`:`total=12`
|
||||
- `startDate=2026-03-08&endDate=2026-03-08`:`total=12`
|
||||
|
||||
判定:前端传 `startDate/endDate` 未在后端生效,日期筛选失效可稳定复现。
|
||||
|
||||
---
|
||||
|
||||
## 4. 对话层冒烟(第一轮)
|
||||
|
||||
调用 `/api/v1/iit/chat` 的典型问答可得到答复,但存在“解释与口径不稳定”的风险:
|
||||
|
||||
- “签署知情人数”问题:答复给出“表单通过数 10/12”,但承认无法直接给出人数。
|
||||
- “最新质控报告”问题:引用了健康分、严重问题、警告问题、待处理 eQuery,整体和 `qc_report` 可对齐。
|
||||
- “患者访视次数”问题:返回“第2次访视”,但需和 REDCap 事件表进一步逐条对账(Phase 2 再做字段级真值核验)。
|
||||
|
||||
判定:对话层主要风险当前不是“工具不可用”,而是上游口径漂移会向下传导。
|
||||
|
||||
---
|
||||
|
||||
## 5. 根因初判(带代码入口)
|
||||
|
||||
### 根因 A(P0):`record_summary` 聚合是“仅 UPDATE”,无 UPSERT
|
||||
|
||||
- 代码入口:`backend/src/modules/iit-manager/engines/QcAggregator.ts`
|
||||
- `aggregateRecordSummary()` 当前逻辑:
|
||||
- 只执行 `UPDATE iit_schema.record_summary rs ... FROM agg`
|
||||
- 依赖 `rs` 预先存在
|
||||
- 当项目没有预写 `record_summary` 行时:
|
||||
- 聚合更新行数为 0
|
||||
- `HealthScoreEngine.queryRecordStats()` 基于 `record_summary` 得到 `totalRecords=0`
|
||||
- Dashboard/Report 中基于该数据的统计出现“0记录 + 非0问题”的冲突
|
||||
|
||||
### 根因 B(P0):时间线接口参数约定不一致
|
||||
|
||||
- 代码入口:`backend/src/modules/admin/iit-projects/iitQcCockpitController.ts`
|
||||
- `getTimeline` 仅读取 `query.date`,未处理 `startDate/endDate`
|
||||
- 前端使用区间筛选时,后端实际不生效
|
||||
|
||||
### 根因 C(P1):同名指标多来源并行,无统一冻结口径
|
||||
|
||||
- `passRate` 在 cockpit/report/trend 来源不一致
|
||||
- 导致用户看到“同一页不同值”的信任问题
|
||||
|
||||
---
|
||||
|
||||
## 6. 与三大类问题的对应关系(第一轮)
|
||||
|
||||
- 第一大类(报告/关键事件):
|
||||
- 已确认强相关于根因 A + 根因 C
|
||||
- 第二大类(AI 实时工作流水):
|
||||
- 已确认日期筛选问题由根因 B 直接导致
|
||||
- 总问题数差异与根因 C 强相关
|
||||
- 第三大类(AI 对话助手):
|
||||
- 目前更像“上游口径漂移传导”而非对话工具不可用
|
||||
- 仍需在 Phase 2 做患者级真值核验
|
||||
|
||||
---
|
||||
|
||||
## 7. 下一步(建议立即执行)
|
||||
|
||||
1. P0 修复 `QcAggregator.aggregateRecordSummary`:改为 UPSERT(或先补齐 record_summary 基础行再 UPDATE)。
|
||||
2. P0 修复 `getTimeline`:支持 `startDate/endDate`,并与 `date` 兼容。
|
||||
3. P0 统一 `passRate` 口径(按 Phase0 SSOT),至少先让 cockpit/report/trend 展示同一主口径。
|
||||
4. 修复后回归本项目 3 个关键断言:
|
||||
- `record_summary` 记录数应为 `12`
|
||||
- Dashboard/Report/Trend 的主通过率在同一时间窗内一致
|
||||
- 时间线区间筛选对 `2026-03-08` 返回 `0`(当前数据下)
|
||||
|
||||
---
|
||||
|
||||
## 8. 第二轮回归看板(18 问题闭环状态)
|
||||
|
||||
> 执行日期:2026-03-08(第二轮)
|
||||
> 结论口径:
|
||||
> - `已闭环`:问题已修复并复测通过
|
||||
> - `部分闭环`:核心问题缓解,但仍有体验/口径尾项
|
||||
> - `未闭环`:仍存在明确缺陷或未完成验证
|
||||
|
||||
### 8.1 第一大类:报告与关键事件(11 项)
|
||||
|
||||
| # | 问题 | 当前状态 | 证据 / 说明 |
|
||||
|---|---|---|---|
|
||||
| 1 | D1/D2/D3D4/D6 与维度评分不一致 | **部分闭环** | 主通过率口径已统一;但 D2 的 `eventsChecked=1` 仍需继续核验业务定义。 |
|
||||
| 2 | 质控 0 个受试者 | **已闭环** | `record_summary` 已恢复 `12` 条,`qc_project_stats.total_records=12`。 |
|
||||
| 3 | 健康分与问题规模冲突(如健康分高但问题多) | **部分闭环** | 评分与问题计数可同时存在(不同公式);需补“评分解释文案”避免误读。 |
|
||||
| 4 | 上方通过率 100 / 下方趋势 33 | **已闭环** | 当前 cockpit/report/trend 均为 `passRate=0`(同一快照)。 |
|
||||
| 5 | 质控完成无热力图 | **已闭环** | cockpit 返回 heatmap `rows/columns` 非空。 |
|
||||
| 6 | 执行摘要无信息 | **已闭环** | report summary 可稳定返回 `totalRecords/criticalIssues/warningIssues/pendingQueries`。 |
|
||||
| 7 | 报告分数 vs 大盘分数冲突 | **部分闭环** | 主指标已一致;健康分展示仍建议增加“更新时间+公式说明”。 |
|
||||
| 8 | D1 无数据 | **已闭环** | D1 报表已恢复:`eligible=0, ineligible=11, incomplete=1`。 |
|
||||
| 9 | D2 事件数=1、明细异常 | **部分闭环** | 已修复 `eventsChecked=5`(按活跃事件);同时新增 `d2EventsChecked=1` 标识 D2 覆盖事件数,避免误读。 |
|
||||
| 10 | 已回复仍显示 0 | **已闭环** | 已执行 `pending -> responded -> closed(ai_review)` 实流回归,`equeries/stats` 与 D3D4 报表最终一致。 |
|
||||
| 11 | 无“重开质疑”操作 | **未闭环** | 前端仍缺 reopen 操作入口(后端状态机支持)。 |
|
||||
|
||||
### 8.2 第二大类:AI 实时工作流水(3 项)
|
||||
|
||||
| # | 问题 | 当前状态 | 证据 / 说明 |
|
||||
|---|---|---|---|
|
||||
| 12 | 流水问题总数 vs 待处理总数不一致 | **部分闭环** | 已在口径上解释为“字段问题数 vs eQuery 数”;需在 UI 文案继续强化区分。 |
|
||||
| 13 | 事件编码生成逻辑不明 | **部分闭环** | 已补齐后端 `event_id -> event_label` 统一映射(报表/对话可显示“筛选期”等);仍保留 event_id 作为证据辅助。 |
|
||||
| 14 | 日期筛选按钮无效 | **已闭环** | timeline 支持 `startDate/endDate`,`2026-03-01~03-02` 返回 `0`。 |
|
||||
|
||||
### 8.3 第三大类:AI 对话助手(5 项)
|
||||
|
||||
| # | 问题 | 当前状态 | 证据 / 说明 |
|
||||
|---|---|---|---|
|
||||
| 15 | 已签署知情显示 0 | **已闭环** | 回归问答返回 `11/12`(签署率 91.7%)。 |
|
||||
| 16 | 3号患者被误判为无问题 | **已闭环** | 回归问答已返回“不符合”并列出 rule 证据。 |
|
||||
| 17 | 总体通过率答复异常 | **已闭环** | 回归问答返回 `0%`,并给出公式 `passedRecords/totalRecords`。 |
|
||||
| 18 | 患者2信息不全 / 患者4访视描述偏差 | **已闭环** | 患者2信息完整度提升;患者4访视描述已优先输出业务事件名(保留 event_id 仅作证据)。 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 严重未闭环问题(当前 P0/P1)
|
||||
|
||||
### P0(必须优先清零)
|
||||
|
||||
1. ~~**D2 统计口径残留问题**:`eventsChecked=1` 与多事件现实不一致。~~(已完成:`eventsChecked` 修复 + `d2EventsChecked` 解释字段)
|
||||
2. ~~**“已回复仍为0”缺少场景化回归**:需要构造 responded/reviewing/closed 数据流转验证。~~(已完成:状态流转回归通过)
|
||||
|
||||
### P1(应在下一轮完成)
|
||||
|
||||
1. ~~**事件标签可读性**:technical event_id 需稳定映射到 eventLabel(报表 + 对话统一)。~~(已完成第一阶段:后端统一映射,前端显示可读事件名)
|
||||
2. **EQuery 重开操作入口**:前端补 `reopen` 按钮与状态回流。
|
||||
3. **评分解释文案**:健康分与问题数量并存时给出公式与更新时间提示。
|
||||
|
||||
---
|
||||
|
||||
## 10. 下一步执行计划(建议)
|
||||
|
||||
1. **P0-A:D2 口径修复**
|
||||
- 明确 activeEvents 定义(按患者真实到访事件)
|
||||
- 修正 `getCompletenessReport()` 的 `eventsChecked` 计算
|
||||
- 回归断言:`eventsChecked` 与 `qc_event_status` 的 D2 事件集合一致
|
||||
|
||||
2. **P0-B:eQuery 状态流转回归**
|
||||
- 构造一条 `pending -> responded -> reviewing/closed` 流程
|
||||
- 验证:eQuery 列表、stats、D3D4 报表、对话回答四处一致
|
||||
|
||||
3. **P1:事件标签统一**
|
||||
- 补全 `event_id -> event_label` 映射策略(优先 metadata,其次兜底格式化)
|
||||
- 前端与 AI 对话统一使用 `eventLabel`,避免技术 ID 直出
|
||||
|
||||
---
|
||||
|
||||
## 11. 本轮执行证据(新增)
|
||||
|
||||
### 11.1 D2 口径修复结果
|
||||
|
||||
- 接口:`GET /api/v1/admin/iit-projects/:projectId/qc-cockpit/report/completeness`
|
||||
- 修复后结果:
|
||||
- `eventsChecked=5`(项目活跃事件数)
|
||||
- `d2EventsChecked=1`(D2 当前覆盖事件数)
|
||||
- 受试者样例 `recordId=1`:`activeEvents=5`,`d2CoveredEvents=1`
|
||||
|
||||
### 11.2 eQuery 状态流转回归结果
|
||||
|
||||
- 操作:选取一条 pending eQuery,调用 `POST /equeries/:id/respond`
|
||||
- 实际状态流:
|
||||
- `pending -> responded -> closed (ai_review)`(异步作业触发)
|
||||
- 最终一致性(等待异步稳定后):
|
||||
- `GET /equeries/stats`: `pending=261, closed=1, responded=0`
|
||||
- `GET /qc-cockpit/report/equery-log`: 同步为 `pending=261, closed=1, responded=0`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 12. eQuery 专项 P0 修复(2026-03-08 夜间增量)
|
||||
|
||||
### 12.1 已落地代码改动
|
||||
|
||||
1. **eQuery 生成去重策略升级(后端)**
|
||||
- 文件:`backend/src/modules/iit-manager/services/DailyQcOrchestrator.ts`
|
||||
- 从原来的 `recordId + fieldName` 去重,升级为 `recordId + eventId + ruleName` 去重。
|
||||
- 目的:减少“同一业务问题因不同字段重复建单”(对应问题 9)。
|
||||
|
||||
2. **高噪音规则抑制(后端)**
|
||||
- 文件:`backend/src/modules/iit-manager/services/DailyQcOrchestrator.ts`
|
||||
- 新增 `shouldSuppressIssue()`,当前先抑制三类已确认噪音:
|
||||
- `不良事件记录与知情同意状态一致性检查`(缺上下文时高频误报)
|
||||
- `所有纳入标准完整性检查` 且实际值为全 `1,1,...` 的错误告警
|
||||
- `访视日期早于知情同意书签署日期` 但两日期相同的误报文本
|
||||
|
||||
3. **eQuery 文案可读性增强(后端)**
|
||||
- 文件:`backend/src/modules/iit-manager/services/DailyQcOrchestrator.ts`
|
||||
- 新增 `normalizeQueryText()`:
|
||||
- 清理 `(标准: [object Object])`
|
||||
- 去掉 markdown `**` 噪音
|
||||
- 对“入组状态与排除标准冲突检查”改写为业务可读提示语
|
||||
|
||||
4. **eQuery 上下文字段补齐(后端)**
|
||||
- 文件:`backend/src/modules/iit-manager/services/DailyQcOrchestrator.ts`
|
||||
- 创建 eQuery 时补写 `eventId/formName`,并在 `expectedAction` 中带出事件上下文。
|
||||
- 目的:缓解“详情中表单/访视点显示不全(-)”。
|
||||
|
||||
5. **规则消息层通用序列化修复(后端)**
|
||||
- 文件:
|
||||
- `backend/src/modules/iit-manager/engines/HardRuleEngine.ts`
|
||||
- `backend/src/modules/iit-manager/engines/SkillRunner.ts`
|
||||
- 新增逻辑字面量与实际值格式化,避免 `expectedValue`/`actualValue` 落成 `[object Object]` 或不可读数组。
|
||||
|
||||
6. **eQuery 列表可用性增强(前端)**
|
||||
- 文件:`frontend-v2/src/modules/iit/pages/EQueryPage.tsx`
|
||||
- 新增:
|
||||
- 质疑序号列
|
||||
- 访视点列(eventId)
|
||||
- 按严重程度过滤
|
||||
- 按受试者号过滤
|
||||
- 前端默认排序:按受试者号升序、同受试者按创建时间降序
|
||||
|
||||
### 12.2 状态评估(对应 14 条中的关键项)
|
||||
|
||||
- **部分闭环(需复测)**
|
||||
- #4 排序混乱 / 建议增加序号:已实现前端排序+序号
|
||||
- #9 同一目的重复记录:已增强去重键
|
||||
- #10/#14 事件/表单显示不足:新单已补上下文,历史单仍需数据回填
|
||||
- #12 告警文案与全 1 误报:已在派单前抑制
|
||||
|
||||
- **仍需继续(下一轮 P0)**
|
||||
- #3/#7/#11 的“规则本体逻辑”仍建议在规则配置层做根因修正(当前先做了派单层防噪)
|
||||
- #5 纳入标准检查覆盖不全:仍需补齐规则集合与事件适用范围
|
||||
- #13 缺少访视定位的业务解释:需在报表与详情页增加 eventLabel 映射显示
|
||||
|
||||
### 12.3 第二批 P0(规则执行层护栏)已落地
|
||||
|
||||
1. **规则执行层新增业务护栏(HardRuleEngine / SkillRunner 双端一致)**
|
||||
- 文件:
|
||||
- `backend/src/modules/iit-manager/engines/HardRuleEngine.ts`
|
||||
- `backend/src/modules/iit-manager/engines/SkillRunner.ts`
|
||||
- 新增 `forcePassByBusinessGuard()`,在规则命中前做语义纠偏:
|
||||
- `访视日期早于知情同意书签署日期`:当访视日期 >= 签署日期(含同日)直接判通过
|
||||
- `SF-MPQ和CMSS评估日期与访视日期不一致`:存在缺失值时不判“不一致”
|
||||
- `所有纳入标准完整性检查`:实际值全 1 时直接判通过
|
||||
- `入组状态与排除标准冲突检查`:实际值全 0 时直接判通过
|
||||
|
||||
2. **D2 缺失告警事件名可读化**
|
||||
- 文件:`backend/src/modules/iit-manager/engines/CompletenessEngine.ts`
|
||||
- 告警文案从技术事件码切换为 `eventLabel`(如“筛选期”),减少 `65a64dbbd9_arm_1` 直接暴露。
|
||||
|
||||
> 注:以上为执行层兜底,能立即压误报;下一步仍建议在规则配置(skill.config.rules)层做同名规则逻辑重构,避免长期依赖护栏。
|
||||
|
||||
### 12.4 同步完成 P1:eQuery 重开入口
|
||||
|
||||
- 后端新增 `POST /api/v1/admin/iit-projects/:projectId/equeries/:equeryId/reopen`
|
||||
- 文件:
|
||||
- `backend/src/modules/admin/iit-projects/iitEqueryService.ts`
|
||||
- `backend/src/modules/admin/iit-projects/iitEqueryController.ts`
|
||||
- `backend/src/modules/admin/iit-projects/iitEqueryRoutes.ts`
|
||||
- 状态流:`closed -> reopened`
|
||||
|
||||
- 前端已接入“重开”按钮
|
||||
- 文件:
|
||||
- `frontend-v2/src/modules/iit/api/iitProjectApi.ts`
|
||||
- `frontend-v2/src/modules/iit/pages/EQueryPage.tsx`
|
||||
- 仅在 `closed` 状态显示“重开”操作,执行后刷新列表。
|
||||
|
||||
---
|
||||
|
||||
## 13. 执行验证与卡点结论(2026-03-08 晚)
|
||||
|
||||
### 13.1 复跑验证结论
|
||||
|
||||
- 脚本:`backend/scripts/run_iit_qc_once.ts`
|
||||
- 项目:`1d80f270-6a02-4b58-9db3-6af176e91f3c`(原发性痛经0302)
|
||||
- 结果(成功):
|
||||
- `batch.totalRecords=12`
|
||||
- `batch.totalEvents=37`
|
||||
- `batch.fieldStatusWrites=1015`
|
||||
- `orchestrate.pushSent=true`
|
||||
- `orchestrate.equeriesCreated=0`
|
||||
|
||||
### 13.2 “卡在哪里”的根因
|
||||
|
||||
- 本次并非主流程卡死,`QcExecutor -> DailyQcOrchestrator` 已完整跑通并返回结果。
|
||||
- 之前观察到的告警根因是:
|
||||
- 审计日志 SQL 使用了旧列 `action`
|
||||
- 当前表 `iit_schema.audit_logs` 实际列为 `action_type`
|
||||
- 已修复后复跑,相关 `audit_logs.action` 报错未再出现。
|
||||
|
||||
### 13.3 历史 eQuery 上下文回填结果
|
||||
|
||||
- 脚本:`backend/scripts/backfill_equery_context.ts`
|
||||
- 回填结果:
|
||||
- `missingBefore=262`
|
||||
- `updatedRows=203`
|
||||
- `missingAfter=59`
|
||||
|
||||
### 13.4 剩余 59 条未回填原因
|
||||
|
||||
- 脚本:`backend/scripts/analyze_missing_equery_context.ts`
|
||||
- 统计:`B_RULE_MATCH_FIELD_MISMATCH = 59`(100%)
|
||||
- 含义:
|
||||
- 能匹配到同受试者 + 同规则名
|
||||
- 但匹配不到同字段名(历史 eQuery 以 `exclusion_criteria2/3/4/5` 多字段拆条,现有 `qc_field_status` 粒度/字段记录不一致)
|
||||
- 代表样例:`category=入组状态与排除标准冲突检查` 且 `event_id/form_name` 为空。
|
||||
|
||||
### 13.5 容错回填(二阶段)执行结果
|
||||
|
||||
- 已升级脚本:`backend/scripts/backfill_equery_context.ts`
|
||||
- Phase 1:严格匹配(`record + rule + field`)
|
||||
- Phase 2:容错匹配(`record + rule`,取最近 QC 事件/表单)
|
||||
- 本次执行结果:
|
||||
- `missingBefore=59`
|
||||
- `strictUpdatedRows=0`
|
||||
- `relaxedUpdatedRows=59`
|
||||
- `missingAfter=0`
|
||||
- 复核脚本:`backend/scripts/analyze_missing_equery_context.ts`
|
||||
- `reasonStats=[]`
|
||||
- `sample=[]`
|
||||
- 结论:历史 eQuery 的 `event_id/form_name` 缺失已清零。
|
||||
|
||||
---
|
||||
|
||||
## 14. 新增端到端脚本(四流程一体)
|
||||
|
||||
- 脚本:`backend/scripts/e2e_iit_full_flow.ts`
|
||||
- 运行方式:
|
||||
- `npx tsx scripts/e2e_iit_full_flow.ts 1d80f270-6a02-4b58-9db3-6af176e91f3c`
|
||||
- 可选:`--with-chat`(增加 LLM 问答链路)
|
||||
|
||||
### 14.1 覆盖的 4 个流程
|
||||
|
||||
1. REDCap 结构与真实数据拉取(metadata/events/form-event/records-by-event)
|
||||
2. 规则配置加载与覆盖校验(`qc_process` 活跃规则)
|
||||
3. 执行质控与报告编排(`QcExecutor` + `DailyQcOrchestrator`)
|
||||
4. 多消费端一致性校验(Cockpit / Report / Tools 的通过率一致)
|
||||
|
||||
### 14.2 本次执行结果(通过)
|
||||
|
||||
- Stage1_REDCap:通过
|
||||
- `metadata=74`, `events=5`, `formEventMapping=19`, `recordEventRows=37`, `uniqueRecords=12`
|
||||
- Stage2_Rules:通过
|
||||
- `ruleCount=79`, `multiFieldRules=29`, `categories={D1,D3,D5,D6}`
|
||||
- Stage3_Execution:通过
|
||||
- `totalRecords=12`, `totalEvents=37`, `fieldStatusWrites=1015`
|
||||
- DB:`qc_field_status=713`, `qc_event_status=25`, `record_summary=12`
|
||||
- Stage4_Consumption:通过
|
||||
- `reportPassRate=0`, `cockpitPassRate=0`, `toolPassRate=0`(一致)
|
||||
|
||||
---
|
||||
|
||||
## 15. 元数据驱动护栏落地(去硬编码)
|
||||
|
||||
### 15.1 执行内核收敛
|
||||
|
||||
- 已完成:`SkillRunner` 的 HARD_RULE 执行改为单路径复用 `HardRuleEngine.executeWithRules()`。
|
||||
- 价值:删除重复实现,避免“同一规则两套行为”。
|
||||
|
||||
### 15.2 guardType 元数据写回(项目级)
|
||||
|
||||
- 脚本:`backend/scripts/suggest_guard_types_for_project.ts`
|
||||
- 运行:`npx tsx scripts/suggest_guard_types_for_project.ts 1d80f270-6a02-4b58-9db3-6af176e91f3c --apply`
|
||||
- 结果:`updated=5`
|
||||
- `date_not_before_or_equal` ×1
|
||||
- `pass_if_exclusion_all_zero` ×1
|
||||
- `pass_if_all_ones` ×1
|
||||
- `skip_if_any_missing` ×2
|
||||
|
||||
### 15.3 回归脚本拆分(职责清晰)
|
||||
|
||||
- 引擎机制 smoke:`backend/scripts/regression_hardrule_guards.ts`(可写死样例)
|
||||
- 项目动态回归:`backend/scripts/regression_hardrule_guards_by_project.ts`(从 `qc_process` 读取规则)
|
||||
- 项目动态回归结果:`skipped=[]`,核心 guard 断言全部通过。
|
||||
|
||||
### 15.4 复跑端到端验证
|
||||
|
||||
- 脚本:`backend/scripts/e2e_iit_full_flow.ts`
|
||||
- 结果:Stage1~Stage4 全部通过(口径一致性维持)。
|
||||
- 备注:本次编排阶段 `pushSent=false`(非主流程阻断项),不影响质控执行与报告一致性断言。
|
||||
|
||||
---
|
||||
|
||||
## 16. 守护门禁与兼容开关(新增)
|
||||
|
||||
### 16.1 guardType 门禁脚本
|
||||
|
||||
- 新增:`backend/scripts/validate_guard_types_for_project.ts`
|
||||
- 用法:
|
||||
- 检查:`npx tsx scripts/validate_guard_types_for_project.ts <projectId>`
|
||||
- 严格失败:`npx tsx scripts/validate_guard_types_for_project.ts <projectId> --strict`
|
||||
- 本项目执行结果(strict):`missingCount=0`, `mismatchCount=0`
|
||||
|
||||
### 16.2 E2E 增加严格 guard 覆盖断言
|
||||
|
||||
- 脚本:`backend/scripts/e2e_iit_full_flow.ts`
|
||||
- 新增参数:
|
||||
- `--strict-guards`
|
||||
- 或环境变量 `E2E_REQUIRE_GUARD_TYPES=1`
|
||||
- 严格模式下,Stage2 会校验 guardType 候选规则的覆盖率(未覆盖即 fail)。
|
||||
|
||||
### 16.3 兼容兜底可控下线
|
||||
|
||||
- 文件:`backend/src/modules/iit-manager/engines/HardRuleEngine.ts`
|
||||
- 新增开关:`IIT_GUARD_LEGACY_NAME_FALLBACK`
|
||||
- 默认开启(兼容历史规则名)
|
||||
- 设为 `0` 可关闭规则名兜底,仅按 `metadata.guardType` 生效
|
||||
- 目的:支持“先迁移配置,再逐步下线兼容逻辑”的平滑策略。
|
||||
|
||||
|
||||
|
||||
55
docs/04-开发规范/13-Cursor防丢失与保存规范.md
Normal file
55
docs/04-开发规范/13-Cursor防丢失与保存规范.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Cursor 防丢失与保存规范
|
||||
|
||||
> 目的:降低“已编辑但未落盘”的风险,避免文档/代码丢失。
|
||||
> 适用范围:本仓库所有开发成员。
|
||||
|
||||
---
|
||||
|
||||
## 1. 强制设置(已建议统一)
|
||||
|
||||
- 开启自动保存:`files.autoSave = afterDelay`
|
||||
- 自动保存延迟:`files.autoSaveDelay = 800`
|
||||
- 开启热退出恢复:`files.hotExit = onExitAndWindowClose`
|
||||
- 重启自动恢复窗口:`window.restoreWindows = all`
|
||||
|
||||
---
|
||||
|
||||
## 2. 每日开发最小动作
|
||||
|
||||
1. 启动开发前先执行一次 `Ctrl+K S`(全部保存)
|
||||
2. 每次运行脚本/切分支前执行一次 `Ctrl+K S`
|
||||
3. 每 30-60 分钟做一次本地 checkpoint(Git 提交)
|
||||
4. 收工前执行:
|
||||
- `Ctrl+K S`
|
||||
- `git status`
|
||||
- 必要时提交 `WIP`(仅本地)
|
||||
|
||||
---
|
||||
|
||||
## 3. 文档类文件专项要求
|
||||
|
||||
- 长文档编辑时,至少每 10 分钟手动 `Ctrl+S` 一次
|
||||
- 批量修改文档后,立即执行 `git status` 确认改动被识别
|
||||
- 对关键文档(部署/规范/评审报告)建议单次编辑后立即提交
|
||||
|
||||
---
|
||||
|
||||
## 4. 故障应急(出现“疑似丢失”时)
|
||||
|
||||
1. 先不要继续编辑,先 `Ctrl+K S`
|
||||
2. 查看 `git status` 是否有改动痕迹
|
||||
3. 查看文件时间戳是否更新
|
||||
4. 如果未落盘,优先检查:
|
||||
- 是否在另一个 Cursor 窗口编辑了同名文件
|
||||
- 是否被同步盘/杀毒软件锁文件
|
||||
- 是否发生异常退出(查看恢复提示)
|
||||
|
||||
---
|
||||
|
||||
## 5. 发布前防丢失检查
|
||||
|
||||
- [ ] 所有编辑器标签页已保存
|
||||
- [ ] `git status` 与预期一致
|
||||
- [ ] 核心文档改动已提交到仓库
|
||||
- [ ] 迁移/部署类文档已更新(如适用)
|
||||
|
||||
232
docs/05-每日进度/Day21-22-知识库前端开发与问题修复.md
Normal file
232
docs/05-每日进度/Day21-22-知识库前端开发与问题修复.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Day 21-22:知识库前端开发与问题修复
|
||||
|
||||
> **日期:** 2025-10-11
|
||||
> **状态:** ✅ 已完成
|
||||
> **里程碑:** 里程碑1 - Week 3
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
完成知识库前端页面开发,并解决前后端集成过程中遇到的3个关键问题。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成的功能
|
||||
|
||||
### 1. 知识库前端页面(已完成)
|
||||
- ✅ 知识库列表页面
|
||||
- ✅ 知识库详情页面
|
||||
- ✅ 文档上传组件
|
||||
- ✅ 文档列表显示
|
||||
- ✅ 文档状态管理
|
||||
|
||||
---
|
||||
|
||||
## 🐛 发现并修复的问题
|
||||
|
||||
### 问题1:删除知识库失败 - CORS错误
|
||||
|
||||
**现象:**
|
||||
```
|
||||
Access to XMLHttpRequest at 'http://localhost:3001/api/v1/knowledge-bases/xxx'
|
||||
has been blocked by CORS policy: Method DELETE is not allowed by
|
||||
Access-Control-Allow-Methods in preflight response.
|
||||
```
|
||||
|
||||
**原因:**
|
||||
后端CORS配置不完整,没有明确允许DELETE方法
|
||||
|
||||
**修复:**
|
||||
```typescript
|
||||
// backend/src/index.ts
|
||||
await fastify.register(cors, {
|
||||
origin: true,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'],
|
||||
exposedHeaders: ['Content-Range', 'X-Content-Range'],
|
||||
maxAge: 600,
|
||||
preflightContinue: false,
|
||||
});
|
||||
```
|
||||
|
||||
**验收:** ✅ 删除知识库成功,无CORS错误
|
||||
|
||||
---
|
||||
|
||||
### 问题2:编辑知识库失败 - CORS错误
|
||||
|
||||
**现象:**
|
||||
```
|
||||
Access to XMLHttpRequest at 'http://localhost:3001/api/v1/knowledge-bases/xxx'
|
||||
has been blocked by CORS policy: Method PUT is not allowed by
|
||||
Access-Control-Allow-Methods in preflight response.
|
||||
```
|
||||
|
||||
**原因:**
|
||||
与问题1相同,CORS配置没有明确允许PUT方法
|
||||
|
||||
**修复:**
|
||||
同上,统一修复CORS配置
|
||||
|
||||
**验收:** ✅ 编辑知识库成功,保存正常
|
||||
|
||||
---
|
||||
|
||||
### 问题3:文件上传无响应
|
||||
|
||||
**现象:**
|
||||
- 点击上传按钮完全没有反应
|
||||
- 前端无错误提示
|
||||
- 后端无请求日志
|
||||
- 浏览器控制台无日志输出
|
||||
|
||||
**排查过程:**
|
||||
|
||||
#### 步骤1:添加调试日志
|
||||
在前端组件中添加详细的console.log,发现:
|
||||
- `beforeUpload` 被调用 ✅
|
||||
- `customRequest` 没有被调用 ❌
|
||||
|
||||
#### 步骤2:发现浏览器缓存问题
|
||||
- 浏览器Sources中显示两个同名文件
|
||||
- 实际运行的是旧版本代码
|
||||
- 清除缓存后解决
|
||||
|
||||
#### 步骤3:发现组件被禁用
|
||||
调试日志显示:
|
||||
```javascript
|
||||
{
|
||||
disabled: true,
|
||||
组件是否被禁用: true
|
||||
}
|
||||
```
|
||||
|
||||
**原因1:** 组件被 `disabled={loading}` 禁用,loading状态一直为true
|
||||
|
||||
**修复1:**
|
||||
```typescript
|
||||
// frontend/src/pages/KnowledgePage.tsx
|
||||
<DocumentUpload
|
||||
disabled={false} // 临时改为false,后续优化loading状态管理
|
||||
...
|
||||
/>
|
||||
```
|
||||
|
||||
**原因2:** `beforeUpload` 返回 `false` 阻止了 `customRequest` 执行
|
||||
|
||||
根据Ant Design Upload组件的机制:
|
||||
- `return false` → 完全阻止上传,包括customRequest
|
||||
- `return Upload.LIST_IGNORE` → 忽略该文件
|
||||
- 不返回任何值(undefined)→ 允许customRequest执行
|
||||
|
||||
**修复2:**
|
||||
```typescript
|
||||
// frontend/src/components/knowledge/DocumentUpload.tsx
|
||||
const beforeUpload = (file: File) => {
|
||||
// 验证逻辑...
|
||||
|
||||
// 不返回任何值,让 customRequest 处理上传
|
||||
// 之前: return false; ❌
|
||||
};
|
||||
```
|
||||
|
||||
**验收:** ✅ 文件上传成功,能看到上传进度,后端正确接收文件
|
||||
|
||||
---
|
||||
|
||||
## 🔧 技术要点总结
|
||||
|
||||
### 1. CORS配置要点
|
||||
- 必须明确列出所有需要的HTTP方法
|
||||
- 开发环境可以 `origin: true` 允许所有来源
|
||||
- 生产环境应该指定具体的域名列表
|
||||
- `maxAge` 可以减少preflight请求频率
|
||||
|
||||
### 2. Ant Design Upload组件要点
|
||||
- `beforeUpload` 返回值决定是否继续上传
|
||||
- `false` 或 `Upload.LIST_IGNORE` → 阻止上传
|
||||
- `undefined`(不返回)→ 允许customRequest
|
||||
- `true` → 默认上传行为(需要action)
|
||||
- 使用 `customRequest` 可以完全控制上传逻辑
|
||||
- `disabled` 属性会阻止所有交互
|
||||
|
||||
### 3. 前端缓存问题处理
|
||||
- 开发时遇到代码不更新,优先考虑缓存问题
|
||||
- 解决方案:
|
||||
1. 使用无痕模式测试
|
||||
2. 清除浏览器缓存(Ctrl+Shift+Delete)
|
||||
3. 删除 `node_modules/.vite` 和 `dist` 文件夹
|
||||
4. 硬刷新(Ctrl+F5)
|
||||
|
||||
### 4. 调试技巧
|
||||
- 在关键位置添加console.log
|
||||
- 使用浏览器Sources查看实际运行的代码
|
||||
- 检查组件状态(props、state)
|
||||
- 对比文件内容和浏览器中的代码
|
||||
|
||||
---
|
||||
|
||||
## 📊 成果物
|
||||
|
||||
### 后端
|
||||
- `backend/src/index.ts` - 完整的CORS配置
|
||||
- `backend/src/controllers/documentController.ts` - 文档上传日志增强
|
||||
|
||||
### 前端
|
||||
- `frontend/src/pages/KnowledgePage.tsx` - 知识库管理主页面
|
||||
- `frontend/src/components/knowledge/DocumentUpload.tsx` - 文档上传组件(已清理调试日志)
|
||||
- `frontend/src/components/knowledge/KnowledgeBaseList.tsx` - 知识库列表组件
|
||||
- `frontend/src/components/knowledge/DocumentList.tsx` - 文档列表组件
|
||||
|
||||
---
|
||||
|
||||
## 🎯 下一步计划
|
||||
|
||||
### Day 23-24:知识库检索 + @引用功能 ⭐⭐⭐
|
||||
这是里程碑1的核心功能!
|
||||
|
||||
**任务:**
|
||||
1. 实现知识库检索API(调用Dify)
|
||||
2. 前端实现 `@知识库` 触发器
|
||||
3. 对话中集成知识库检索
|
||||
4. AI回答中显示溯源引用
|
||||
|
||||
**验收标准:**
|
||||
- ✅ 能在对话输入框输入 `@` 触发知识库选择
|
||||
- ✅ 选择知识库后能检索相关内容
|
||||
- ✅ AI回答包含明确的引用来源(如:`[📄 文献.pdf]`)
|
||||
- ✅ 基于知识库的回答质量可接受
|
||||
|
||||
---
|
||||
|
||||
## 💡 经验教训
|
||||
|
||||
### 1. 问题排查要系统化
|
||||
- ❌ 不要反复确认"服务是否启动"
|
||||
- ✅ 应该分析根本原因:代码逻辑?配置?缓存?
|
||||
|
||||
### 2. 缓存问题很常见
|
||||
- 前端开发时,缓存是高频问题
|
||||
- 建立清除缓存的标准流程
|
||||
- 优先使用无痕模式验证
|
||||
|
||||
### 3. 添加日志要有策略
|
||||
- 关键节点添加日志
|
||||
- 完成后及时清理,避免污染
|
||||
- 保留error级别的日志
|
||||
|
||||
### 4. CORS配置要完整
|
||||
- 一次性配置所有可能用到的HTTP方法
|
||||
- 避免后续反复修改
|
||||
|
||||
---
|
||||
|
||||
**文档维护:** 2025-10-11
|
||||
**作者:** AI助手 + 开发者
|
||||
**Git提交:** feat(frontend): Day 21-22 - 知识库前端开发完成,修复3个关键问题
|
||||
|
||||
|
||||
|
||||
|
||||
420
docs/05-每日进度/Day23-24-知识库检索与@引用功能完成.md
Normal file
420
docs/05-每日进度/Day23-24-知识库检索与@引用功能完成.md
Normal file
@@ -0,0 +1,420 @@
|
||||
# Day 23-24:知识库检索 + @引用功能完成 ✅
|
||||
|
||||
**开发时间**: Day 23-24
|
||||
**开发人员**: AI助手
|
||||
**任务状态**: ✅ 已完成(里程碑1核心功能完成!)
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
实现对话中引用知识库的完整功能,用户可以通过 `@知识库` 引用已上传的文献,AI基于文献内容进行精准回答。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已完成功能
|
||||
|
||||
### 1. 知识库检索API(后端)
|
||||
**文件**: `backend/src/services/knowledgeBaseService.ts`
|
||||
|
||||
- ✅ Day 20已实现Dify检索API集成
|
||||
- ✅ 支持语义检索,返回最相关的文档片段
|
||||
- ✅ 返回相似度分数,便于质量评估
|
||||
|
||||
**API接口**:
|
||||
```
|
||||
GET /api/v1/knowledge-bases/:id/search?query=骨质疏松&top_k=3
|
||||
```
|
||||
|
||||
**返回数据**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"query": { "content": "骨质疏松" },
|
||||
"records": [
|
||||
{
|
||||
"segment": {
|
||||
"id": "xxx",
|
||||
"content": "相关文档内容...",
|
||||
"document_id": "xxx"
|
||||
},
|
||||
"score": 0.85
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 前端@知识库选择器
|
||||
**文件**: `frontend/src/pages/AgentChatPage.tsx`
|
||||
|
||||
**核心改动**:
|
||||
```typescript
|
||||
// 1. 引入知识库Store
|
||||
import { useKnowledgeBaseStore } from '../stores/useKnowledgeBaseStore'
|
||||
|
||||
// 2. 加载知识库列表
|
||||
const { knowledgeBases, fetchKnowledgeBases } = useKnowledgeBaseStore()
|
||||
|
||||
useEffect(() => {
|
||||
fetchKnowledgeBases()
|
||||
}, [])
|
||||
|
||||
// 3. 传递给MessageInput组件
|
||||
<MessageInput
|
||||
onSend={handleSendMessage}
|
||||
loading={sending}
|
||||
knowledgeBases={knowledgeBases} // ✅ 知识库列表
|
||||
placeholder={`向${agent.name}提问...`}
|
||||
/>
|
||||
```
|
||||
|
||||
**UI功能**:
|
||||
- ✅ 点击"@知识库"按钮弹出下拉菜单
|
||||
- ✅ 显示用户所有知识库列表
|
||||
- ✅ 支持多选知识库(蓝色标签显示)
|
||||
- ✅ 可移除已选择的知识库
|
||||
|
||||
---
|
||||
|
||||
### 3. 对话集成知识库检索(后端)
|
||||
**文件**: `backend/src/services/conversationService.ts`
|
||||
|
||||
**核心实现**:
|
||||
```typescript
|
||||
// 1. 导入知识库服务
|
||||
import * as knowledgeBaseService from './knowledgeBaseService.js';
|
||||
|
||||
// 2. 发送消息时检索知识库
|
||||
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
||||
const knowledgeResults: string[] = [];
|
||||
|
||||
// 对每个知识库进行检索
|
||||
for (const kbId of knowledgeBaseIds) {
|
||||
const searchResult = await knowledgeBaseService.searchKnowledgeBase(
|
||||
userId,
|
||||
kbId,
|
||||
content, // 用户问题作为检索query
|
||||
3 // 每个知识库返回3个最相关段落
|
||||
);
|
||||
|
||||
// 格式化检索结果
|
||||
if (searchResult.records && searchResult.records.length > 0) {
|
||||
const kbInfo = await prisma.knowledgeBase.findUnique({
|
||||
where: { id: kbId },
|
||||
select: { name: true },
|
||||
});
|
||||
|
||||
knowledgeResults.push(
|
||||
`【知识库:${kbInfo?.name || '未命名'}】\n` +
|
||||
searchResult.records
|
||||
.map((record: any, index: number) => {
|
||||
const score = (record.score * 100).toFixed(1);
|
||||
return `${index + 1}. [相关度${score}%] ${record.segment.content}`;
|
||||
})
|
||||
.join('\n\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (knowledgeResults.length > 0) {
|
||||
knowledgeBaseContext = knowledgeResults.join('\n\n---\n\n');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**工作流程**:
|
||||
1. 用户选择知识库并发送问题
|
||||
2. 后端对每个知识库调用Dify检索API
|
||||
3. 获取最相关的文档片段(top_k=3)
|
||||
4. 格式化检索结果(包含知识库名称和相关度)
|
||||
5. 将检索结果注入到LLM上下文
|
||||
6. LLM基于文献内容生成回答
|
||||
|
||||
---
|
||||
|
||||
### 4. 上下文组装优化
|
||||
**文件**: `backend/src/services/conversationService.ts` - `assembleContext()`
|
||||
|
||||
**智能上下文注入**:
|
||||
```typescript
|
||||
// 第一条消息:使用完整模板(包含项目背景 + 知识库上下文)
|
||||
if (isFirstMessage) {
|
||||
userPromptContent = agentService.renderUserPrompt(agentId, {
|
||||
projectBackground,
|
||||
userInput,
|
||||
knowledgeBaseContext,
|
||||
});
|
||||
}
|
||||
// 后续消息:只发送用户输入 + 知识库上下文
|
||||
else {
|
||||
if (knowledgeBaseContext) {
|
||||
userPromptContent = `${userInput}\n\n## 参考文献(来自知识库)\n${knowledgeBaseContext}`;
|
||||
} else {
|
||||
userPromptContent = userInput;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- ✅ 节省token消耗(避免重复发送项目背景)
|
||||
- ✅ 动态注入知识库内容(只在需要时添加)
|
||||
- ✅ 保持对话上下文连贯性
|
||||
|
||||
---
|
||||
|
||||
## 🔄 完整工作流程
|
||||
|
||||
```
|
||||
用户操作流程:
|
||||
1. 进入智能体对话页面
|
||||
2. 点击"@知识库"按钮
|
||||
3. 选择一个或多个知识库
|
||||
4. 输入问题(如"AI在临床研究中有哪些应用?")
|
||||
5. 点击发送
|
||||
|
||||
系统处理流程:
|
||||
[前端] 发送消息 + knowledgeBaseIds[]
|
||||
↓
|
||||
[后端] 接收消息请求
|
||||
↓
|
||||
[后端] 对每个知识库调用Dify检索API
|
||||
↓
|
||||
[Dify] 语义检索返回最相关文档片段
|
||||
↓
|
||||
[后端] 格式化检索结果(知识库名 + 相关度 + 内容)
|
||||
↓
|
||||
[后端] 组装上下文:系统提示 + 历史消息 + 用户问题 + 文献内容
|
||||
↓
|
||||
[LLM] DeepSeek-V3基于文献生成回答
|
||||
↓
|
||||
[后端] 流式返回AI回答
|
||||
↓
|
||||
[前端] 实时显示流式输出
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心技术亮点
|
||||
|
||||
### 1. RAG(检索增强生成)完整实现
|
||||
- ✅ 用户问题 → 语义检索 → 相关文档 → LLM生成
|
||||
- ✅ 提高AI回答的准确性和可信度
|
||||
- ✅ 支持引用来源追溯
|
||||
|
||||
### 2. 多知识库联合检索
|
||||
- ✅ 支持同时选择多个知识库
|
||||
- ✅ 分别检索后合并结果
|
||||
- ✅ 标注知识库来源
|
||||
|
||||
### 3. 相关度评分展示
|
||||
- ✅ Dify返回0-1的相似度分数
|
||||
- ✅ 转换为百分比展示(如"相关度85.3%")
|
||||
- ✅ 帮助用户评估引用质量
|
||||
|
||||
### 4. 错误容错机制
|
||||
- ✅ 单个知识库检索失败不影响其他知识库
|
||||
- ✅ 检索失败不阻断对话(降级为无文献回答)
|
||||
- ✅ 详细的错误日志记录
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流示例
|
||||
|
||||
**用户输入**:
|
||||
```
|
||||
问题: "AI在临床研究中有哪些应用?"
|
||||
选择知识库: ["我的研究文献"]
|
||||
```
|
||||
|
||||
**检索结果(注入LLM上下文)**:
|
||||
```
|
||||
## 参考文献(来自知识库)
|
||||
|
||||
【知识库:我的研究文献】
|
||||
|
||||
1. [相关度92.3%] AI临床研究文献解决方案主要包括以下几个方向:
|
||||
- 智能诊断:利用深度学习分析医学影像...
|
||||
- 药物研发:通过AI预测药物分子结构...
|
||||
|
||||
2. [相关度87.5%] 在临床试验设计中,AI可以优化患者招募...
|
||||
|
||||
3. [相关度81.2%] AI辅助的临床决策支持系统能够...
|
||||
```
|
||||
|
||||
**LLM回答**(基于检索内容):
|
||||
```
|
||||
根据您上传的文献,AI在临床研究中主要有以下应用:
|
||||
|
||||
1. **智能诊断**: 利用深度学习分析医学影像,可以提高诊断准确率...
|
||||
2. **药物研发**: 通过AI预测药物分子结构,加速新药研发...
|
||||
3. **临床试验优化**: AI可以优化患者招募流程...
|
||||
...
|
||||
|
||||
📄 以上内容来自您的知识库"我的研究文献"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试建议
|
||||
|
||||
### 1. 基础功能测试
|
||||
- [ ] 点击"@知识库"能否正常显示知识库列表
|
||||
- [ ] 选择知识库后是否出现蓝色标签
|
||||
- [ ] 能否移除已选择的知识库
|
||||
- [ ] 发送消息后AI是否基于文献回答
|
||||
|
||||
### 2. 多知识库测试
|
||||
- [ ] 同时选择2-3个知识库
|
||||
- [ ] 验证AI回答是否整合多个来源
|
||||
|
||||
### 3. 相关性测试
|
||||
- [ ] 问与文献相关的问题(应精准回答)
|
||||
- [ ] 问与文献无关的问题(应说明文献中无相关内容)
|
||||
|
||||
### 4. 边界情况测试
|
||||
- [ ] 知识库为空时的处理
|
||||
- [ ] 不选择知识库的普通对话
|
||||
- [ ] 检索失败时的降级处理
|
||||
|
||||
---
|
||||
|
||||
## 📁 涉及文件清单
|
||||
|
||||
### 后端修改
|
||||
- `backend/src/services/conversationService.ts` - 集成知识库检索
|
||||
- 添加 `knowledgeBaseService` 导入
|
||||
- 实现检索逻辑(流式和非流式)
|
||||
- 格式化检索结果注入上下文
|
||||
|
||||
### 前端修改
|
||||
- `frontend/src/pages/AgentChatPage.tsx` - 加载知识库列表
|
||||
- 引入 `useKnowledgeBaseStore`
|
||||
- 添加 `fetchKnowledgeBases()` 调用
|
||||
- 传递 `knowledgeBases` 给 `MessageInput`
|
||||
|
||||
### 前端已有组件(Day 18-19已实现)
|
||||
- `frontend/src/components/chat/MessageInput.tsx` - @知识库UI
|
||||
- `frontend/src/stores/useKnowledgeBaseStore.ts` - 知识库状态管理
|
||||
|
||||
---
|
||||
|
||||
## 🎉 里程碑1 - 完成度评估
|
||||
|
||||
### ✅ 已完成核心功能(100%)
|
||||
1. ✅ 用户认证与项目管理
|
||||
2. ✅ 12个AI智能体配置与调用
|
||||
3. ✅ 多轮对话上下文管理
|
||||
4. ✅ 流式输出(打字机效果)
|
||||
5. ✅ 模型切换(DeepSeek-V3/Qwen3-72b/Gemini-Pro)
|
||||
6. ✅ 个人知识库管理(创建/上传/删除)
|
||||
7. ✅ @知识库检索与RAG集成 ⭐ **今日完成**
|
||||
|
||||
### 🚀 下一步工作
|
||||
|
||||
**里程碑2预览**(预计2-3天):
|
||||
1. 项目协作功能(成员管理、权限控制)
|
||||
2. 对话历史管理(查看、搜索、导出)
|
||||
3. AI回答评价与反馈
|
||||
4. 引用溯源优化(点击引用查看原文)
|
||||
|
||||
---
|
||||
|
||||
## 💡 技术收获
|
||||
|
||||
### 1. RAG系统设计经验
|
||||
- 检索质量直接影响AI回答质量
|
||||
- top_k参数需要平衡相关性和上下文长度
|
||||
- 多知识库检索需要合并策略
|
||||
|
||||
### 2. LLM上下文管理
|
||||
- 第一条消息注入完整背景
|
||||
- 后续消息动态添加知识库内容
|
||||
- 控制token消耗同时保持连贯性
|
||||
|
||||
### 3. 错误处理最佳实践
|
||||
- 外部API调用必须有容错
|
||||
- 降级策略保证基础功能可用
|
||||
- 详细日志便于问题排查
|
||||
|
||||
---
|
||||
|
||||
## 📝 用户测试指南
|
||||
|
||||
### 前置条件
|
||||
1. 确保Dify服务运行正常
|
||||
2. 已创建知识库并上传至少1个文档
|
||||
3. 文档已完成索引(Dify后台显示"已完成")
|
||||
|
||||
### 测试步骤
|
||||
|
||||
**Step 1: 清空浏览器缓存**
|
||||
```
|
||||
1. 按 Ctrl+F5 硬刷新页面
|
||||
2. 或使用无痕模式访问 http://localhost:3000
|
||||
```
|
||||
|
||||
**Step 2: 进入对话页面**
|
||||
```
|
||||
1. 访问首页
|
||||
2. 选择任意智能体(推荐"话题评估专家")
|
||||
```
|
||||
|
||||
**Step 3: 使用@知识库**
|
||||
```
|
||||
1. 点击输入框下方的"@知识库"按钮
|
||||
2. 从下拉菜单选择知识库(如"我的研究文献")
|
||||
3. 看到蓝色标签显示已选择
|
||||
4. 输入问题,例如:
|
||||
- "AI在临床研究中有哪些应用?"
|
||||
- "这篇文献的主要结论是什么?"
|
||||
- "请总结文献中的研究方法"
|
||||
5. 点击发送
|
||||
```
|
||||
|
||||
**Step 4: 观察AI回答**
|
||||
```
|
||||
✅ 正常情况:
|
||||
- AI回答与文档内容高度相关
|
||||
- 引用文档中的具体信息
|
||||
- 回答比不@知识库更精准
|
||||
|
||||
❌ 异常情况请反馈:
|
||||
- AI回答完全不相关
|
||||
- 提示"检索失败"
|
||||
- 页面报错
|
||||
```
|
||||
|
||||
**Step 5: 查看后端日志**
|
||||
```
|
||||
后端控制台应能看到:
|
||||
- 检索知识库的日志
|
||||
- 返回的相关文档数量
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 已知问题
|
||||
|
||||
无
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- [Day 18: Dify部署完成](./Day18-Dify部署完成.md)
|
||||
- [Day 19-20: 知识库API完成](./Day19-20-知识库API完成.md)
|
||||
- [Day 21-22: 知识库前端开发与问题修复](./Day21-22-知识库前端开发与问题修复.md)
|
||||
- [产品需求文档](../00-项目概述/产品需求文档\(PRD\).md)
|
||||
- [开发里程碑](../04-开发计划/开发里程碑.md)
|
||||
|
||||
---
|
||||
|
||||
**文档创建时间**: 2025-10-11
|
||||
**最后更新**: 2025-10-11
|
||||
|
||||
|
||||
|
||||
598
docs/05-每日进度/Day25-智能问答功能完成.md
Normal file
598
docs/05-每日进度/Day25-智能问答功能完成.md
Normal file
@@ -0,0 +1,598 @@
|
||||
# Day 25:智能问答功能完成 ✅
|
||||
|
||||
**开发时间**: Day 25
|
||||
**开发人员**: AI助手
|
||||
**任务状态**: ✅ 已完成
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
实现无项目、无智能体概念的纯AI对话功能,支持可选的@知识库引用。用户可以像在ChatGPT官网一样自由对话,同时可以通过@知识库引用个人文献获得更精准的回答。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已完成功能
|
||||
|
||||
### 1. 数据库Schema扩展
|
||||
|
||||
**文件**: `backend/prisma/schema.prisma`
|
||||
|
||||
新增两个数据表:
|
||||
|
||||
#### GeneralConversation(通用对话表)
|
||||
```prisma
|
||||
model GeneralConversation {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
title String
|
||||
modelName String? @map("model_name")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
messages GeneralMessage[]
|
||||
}
|
||||
```
|
||||
|
||||
#### GeneralMessage(通用消息表)
|
||||
```prisma
|
||||
model GeneralMessage {
|
||||
id String @id @default(uuid())
|
||||
conversationId String @map("conversation_id")
|
||||
role String
|
||||
content String @db.Text
|
||||
model String?
|
||||
metadata Json?
|
||||
tokens Int?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
conversation GeneralConversation @relation(...)
|
||||
}
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 与项目对话完全独立
|
||||
- ✅ 不依赖智能体配置
|
||||
- ✅ 支持元数据存储(如知识库IDs)
|
||||
|
||||
---
|
||||
|
||||
### 2. 后端API实现
|
||||
|
||||
**文件**: `backend/src/controllers/chatController.ts`
|
||||
|
||||
#### API接口列表
|
||||
|
||||
| 接口 | 方法 | 功能 | 参数 |
|
||||
|------|------|------|------|
|
||||
| `/api/v1/chat/stream` | POST | 发送消息(流式) | content, modelType, knowledgeBaseIds?, conversationId? |
|
||||
| `/api/v1/chat/conversations` | GET | 获取对话列表 | - |
|
||||
| `/api/v1/chat/conversations/:id` | DELETE | 删除对话 | id |
|
||||
|
||||
#### 核心实现逻辑
|
||||
|
||||
```typescript
|
||||
async sendMessageStream(request, reply) {
|
||||
// 1. 获取或创建对话(无需项目/智能体)
|
||||
if (conversationId) {
|
||||
// 续接已有对话
|
||||
} else {
|
||||
// 创建新对话
|
||||
conversation = await prisma.generalConversation.create({
|
||||
userId,
|
||||
title: content.substring(0, 50),
|
||||
modelName: modelType,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 检索知识库(如果有)
|
||||
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
||||
for (const kbId of knowledgeBaseIds) {
|
||||
const searchResult = await knowledgeBaseService.searchKnowledgeBase(
|
||||
userId, kbId, content, 3
|
||||
);
|
||||
// 格式化检索结果
|
||||
knowledgeBaseContext += formatResults(searchResult);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 组装上下文(极简)
|
||||
const messages = [
|
||||
{ role: 'system', content: '你是一个专业、友好的AI助手...' },
|
||||
...historyMessages, // 最近20条
|
||||
{
|
||||
role: 'user',
|
||||
content: knowledgeBaseContext
|
||||
? `${content}\n\n## 参考资料\n${knowledgeBaseContext}`
|
||||
: content
|
||||
}
|
||||
];
|
||||
|
||||
// 4. 流式调用LLM
|
||||
for await (const chunk of adapter.chatStream(messages)) {
|
||||
reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
||||
}
|
||||
|
||||
// 5. 保存消息到数据库
|
||||
}
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ✅ 无需agentId和projectId
|
||||
- ✅ 自动管理对话历史
|
||||
- ✅ 集成知识库检索
|
||||
- ✅ 极简的System Prompt
|
||||
|
||||
---
|
||||
|
||||
### 3. 前端页面实现
|
||||
|
||||
**文件**: `frontend/src/pages/ChatPage.tsx`
|
||||
|
||||
**核心特性**:
|
||||
- ✅ 完全独立的对话页面
|
||||
- ✅ 复用现有组件(MessageList, MessageInput, ModelSelector)
|
||||
- ✅ 支持@知识库功能
|
||||
- ✅ 自动创建和续接对话
|
||||
|
||||
**页面结构**:
|
||||
```tsx
|
||||
<ChatPage>
|
||||
<Header>
|
||||
💬 智能问答 | [模型选择器]
|
||||
</Header>
|
||||
|
||||
<MessageArea>
|
||||
{messages.length === 0 ? (
|
||||
<EmptyState>
|
||||
💬 与AI自由对话
|
||||
直接提问,或使用@知识库引用文献
|
||||
</EmptyState>
|
||||
) : (
|
||||
<MessageList />
|
||||
)}
|
||||
</MessageArea>
|
||||
|
||||
<MessageInput
|
||||
knowledgeBases={knowledgeBases}
|
||||
onSend={(content, kbIds) => sendMessage(...)}
|
||||
/>
|
||||
</ChatPage>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 前端API封装
|
||||
|
||||
**文件**: `frontend/src/api/chatApi.ts`
|
||||
|
||||
**接口方法**:
|
||||
```typescript
|
||||
// 发送消息(流式)
|
||||
sendMessageStream(
|
||||
data: {
|
||||
content: string,
|
||||
modelType: string,
|
||||
knowledgeBaseIds?: string[],
|
||||
conversationId?: string
|
||||
},
|
||||
onChunk: (content) => void,
|
||||
onComplete: (conversationId) => void,
|
||||
onError: (error) => void
|
||||
)
|
||||
|
||||
// 获取对话列表
|
||||
getConversations(): Promise<GeneralConversation[]>
|
||||
|
||||
// 删除对话
|
||||
deleteConversation(id: string): Promise<void>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 路由和导航
|
||||
|
||||
**修改文件**:
|
||||
- `frontend/src/App.tsx` - 添加 `/chat` 路由
|
||||
- `frontend/src/layouts/MainLayout.tsx` - 添加"智能问答"菜单项
|
||||
|
||||
**导航顺序**:
|
||||
1. 🏠 首页
|
||||
2. 💬 **智能问答** ⭐ 新增
|
||||
3. 🧪 智能体
|
||||
4. 📁 知识库管理
|
||||
5. 📜 历史记录
|
||||
|
||||
---
|
||||
|
||||
## 🔄 完整工作流程
|
||||
|
||||
### 场景1:纯对话(无知识库)
|
||||
|
||||
```
|
||||
用户访问 /chat
|
||||
↓
|
||||
空白对话界面
|
||||
↓
|
||||
输入:"介绍一下自己"
|
||||
↓
|
||||
[后端] 创建新对话
|
||||
↓
|
||||
[后端] 组装上下文:
|
||||
- System Prompt: "你是一个专业的AI助手"
|
||||
- User: "介绍一下自己"
|
||||
↓
|
||||
[LLM] DeepSeek-V3 回答
|
||||
↓
|
||||
[前端] 流式显示回答
|
||||
```
|
||||
|
||||
### 场景2:基于知识库对话
|
||||
|
||||
```
|
||||
用户访问 /chat
|
||||
↓
|
||||
点击 @知识库 → 选择"骨质疏松知识库"
|
||||
↓
|
||||
输入:"这个知识库讲的是什么?"
|
||||
↓
|
||||
[后端] 调用Dify检索API
|
||||
↓
|
||||
[后端] 检索到3条相关文档片段
|
||||
↓
|
||||
[后端] 组装上下文:
|
||||
- System Prompt
|
||||
- User: "这个知识库讲的是什么?"
|
||||
- 参考资料: 【知识库:骨质疏松知识库】...
|
||||
↓
|
||||
[LLM] 基于文档内容回答
|
||||
↓
|
||||
[前端] 显示基于文献的精准回答
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 技术亮点
|
||||
|
||||
### 1. 极简架构
|
||||
- 无需项目背景
|
||||
- 无需智能体配置
|
||||
- 直达核心:用户 ↔ AI ↔ 知识库
|
||||
|
||||
### 2. 灵活的知识库集成
|
||||
- 完全可选(不@知识库 = 纯对话)
|
||||
- 多知识库支持(同时选择多个)
|
||||
- 实时检索(每次发送时检索最新内容)
|
||||
|
||||
### 3. 代码复用率高
|
||||
- ✅ 复用 MessageList 组件
|
||||
- ✅ 复用 MessageInput 组件
|
||||
- ✅ 复用 ModelSelector 组件
|
||||
- ✅ 复用 knowledgeBaseService
|
||||
- ✅ 复用 LLM适配器
|
||||
|
||||
### 4. 独立的数据隔离
|
||||
- 通用对话存储在独立的表
|
||||
- 不影响项目对话数据
|
||||
- 便于后续统计和分析
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流示例
|
||||
|
||||
**请求示例**:
|
||||
```json
|
||||
POST /api/v1/chat/stream
|
||||
{
|
||||
"content": "这个知识库讲的是什么?",
|
||||
"modelType": "deepseek-v3",
|
||||
"knowledgeBaseIds": ["7d1e08ae-7a40-4e62-8654-bb631dc47293"]
|
||||
}
|
||||
```
|
||||
|
||||
**知识库检索**:
|
||||
```
|
||||
🔍 检索 → Dify API
|
||||
↓
|
||||
返回3条记录(相关度50.8%, 46.3%, 43.9%)
|
||||
↓
|
||||
格式化:
|
||||
【知识库:骨质疏松知识库】
|
||||
1. [相关度50.8%] 文档上传与处理:支持在知识库...
|
||||
2. [相关度46.3%] AI科研助手产品需求文档(PRD)
|
||||
3. [相关度43.9%] 知识库融合对话功能...
|
||||
```
|
||||
|
||||
**发送给LLM**:
|
||||
```
|
||||
System: 你是一个专业、友好的AI助手...
|
||||
|
||||
User: 这个知识库讲的是什么?
|
||||
|
||||
## 参考资料(来自知识库)
|
||||
【知识库:骨质疏松知识库】
|
||||
1. [相关度50.8%] 文档上传与处理...
|
||||
...
|
||||
```
|
||||
|
||||
**AI回答**:
|
||||
```
|
||||
根据您的知识库内容,这是一份关于"AI科研助手"的产品需求文档(PRD)。
|
||||
|
||||
主要内容包括:
|
||||
1. 文档管理功能...
|
||||
2. 知识库融合对话功能...
|
||||
...
|
||||
|
||||
【文献来源】骨质疏松知识库
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 已知问题
|
||||
|
||||
### 1. 问题描述不当导致检索质量差
|
||||
**现象**:问"这个文档里有什么?"检索到的内容相关度低
|
||||
|
||||
**原因**:
|
||||
- 问题太宽泛,语义模糊
|
||||
- Dify检索需要具体的关键词或概念
|
||||
|
||||
**建议**:
|
||||
- 使用具体问题:"骨质疏松的治疗方法有哪些?"
|
||||
- 使用文档中的关键词提问
|
||||
|
||||
### 2. 英文文档 vs 中文问题
|
||||
**现象**:阿尔兹海默症知识库检索到英文片段
|
||||
|
||||
**原因**:
|
||||
- 文档是英文的
|
||||
- 中文问题匹配度相对较低
|
||||
|
||||
**建议**:
|
||||
- 使用英文提问
|
||||
- 或者在问题中包含文档中的专业术语
|
||||
|
||||
---
|
||||
|
||||
## 📁 涉及文件清单
|
||||
|
||||
### 后端新增
|
||||
- `backend/src/controllers/chatController.ts` - 通用对话Controller
|
||||
- `backend/src/routes/chatRoutes.ts` - 通用对话路由
|
||||
- `backend/prisma/schema.prisma` - 新增通用对话表
|
||||
- `backend/migrations/add_general_chat.sql` - 数据库迁移SQL
|
||||
|
||||
### 后端修改
|
||||
- `backend/src/index.ts` - 注册chatRoutes
|
||||
|
||||
### 前端新增
|
||||
- `frontend/src/pages/ChatPage.tsx` - 智能问答页面
|
||||
- `frontend/src/api/chatApi.ts` - 通用对话API封装
|
||||
|
||||
### 前端修改
|
||||
- `frontend/src/App.tsx` - 添加 /chat 路由
|
||||
- `frontend/src/layouts/MainLayout.tsx` - 添加"智能问答"菜单项
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试验证
|
||||
|
||||
### ✅ 已验证功能
|
||||
|
||||
1. **纯对话功能**
|
||||
- ✅ 无需项目/智能体
|
||||
- ✅ 直接与AI对话
|
||||
- ✅ 流式输出正常
|
||||
- ✅ 模型切换正常
|
||||
|
||||
2. **@知识库功能**
|
||||
- ✅ 下拉菜单选择知识库
|
||||
- ✅ 检索功能正常(调用Dify API)
|
||||
- ✅ 知识库内容成功注入到AI上下文
|
||||
- ✅ AI基于知识库内容回答
|
||||
|
||||
3. **对话历史**
|
||||
- ✅ 自动创建对话
|
||||
- ✅ 续接已有对话
|
||||
- ✅ 上下文连贯(最近20条消息)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键技术实现
|
||||
|
||||
### 1. 无依赖的对话架构
|
||||
|
||||
**传统模式(项目对话)**:
|
||||
```
|
||||
用户 → 选择项目 → 选择智能体 → 对话
|
||||
依赖:projectId + agentId
|
||||
```
|
||||
|
||||
**智能问答模式**:
|
||||
```
|
||||
用户 → 对话
|
||||
依赖:无
|
||||
```
|
||||
|
||||
### 2. 知识库检索集成
|
||||
|
||||
```typescript
|
||||
// 检索知识库
|
||||
if (knowledgeBaseIds && knowledgeBaseIds.length > 0) {
|
||||
for (const kbId of knowledgeBaseIds) {
|
||||
const searchResult = await knowledgeBaseService.searchKnowledgeBase(
|
||||
userId, kbId, content, 3
|
||||
);
|
||||
// 格式化并追加到上下文
|
||||
}
|
||||
}
|
||||
|
||||
// 组装最终Prompt
|
||||
const userContent = knowledgeBaseContext
|
||||
? `${content}\n\n## 参考资料(来自知识库)\n${knowledgeBaseContext}`
|
||||
: content;
|
||||
```
|
||||
|
||||
### 3. 对话续接机制
|
||||
|
||||
**首次发送**:
|
||||
```json
|
||||
{ "content": "你好", "modelType": "deepseek-v3" }
|
||||
→ 创建新对话,返回 conversationId
|
||||
```
|
||||
|
||||
**后续发送**:
|
||||
```json
|
||||
{
|
||||
"content": "继续聊",
|
||||
"modelType": "deepseek-v3",
|
||||
"conversationId": "xxx"
|
||||
}
|
||||
→ 续接对话,加载历史消息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 设计亮点
|
||||
|
||||
### 1. 用户体验优化
|
||||
|
||||
**问题**:传统智能体模式需要选择项目和智能体,流程复杂
|
||||
**解决**:智能问答直达对话,0步骤开始
|
||||
|
||||
**问题**:用户可能不知道如何使用知识库
|
||||
**解决**:@知识库完全可选,不影响基础使用
|
||||
|
||||
### 2. 架构清晰性
|
||||
|
||||
```
|
||||
应用架构:
|
||||
┌─────────────────────────┐
|
||||
│ 项目-智能体模式 │
|
||||
│ - 结构化的研究流程 │
|
||||
│ - 专业领域AI │
|
||||
│ - 项目背景上下文 │
|
||||
└─────────────────────────┘
|
||||
|
||||
┌─────────────────────────┐
|
||||
│ 智能问答模式 │
|
||||
│ - 自由对话 │
|
||||
│ - 通用AI助手 │
|
||||
│ - 可选知识库辅助 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
两种模式互不干扰,满足不同场景需求。
|
||||
|
||||
### 3. 代码复用率
|
||||
|
||||
**新增代码**:~400行
|
||||
**复用代码**:~2000行(组件、服务、适配器)
|
||||
**复用率**:83%
|
||||
|
||||
---
|
||||
|
||||
## 📝 用户使用指南
|
||||
|
||||
### 快速开始
|
||||
|
||||
1. 访问 `http://localhost:3000`
|
||||
2. 点击左侧导航"💬 智能问答"
|
||||
3. 输入问题,开始对话
|
||||
|
||||
### 使用@知识库
|
||||
|
||||
1. 点击输入框下方的"@知识库"按钮
|
||||
2. 选择一个或多个知识库
|
||||
3. 输入问题(建议具体问题,如:"治疗方法有哪些?")
|
||||
4. 点击发送
|
||||
5. AI会基于知识库内容回答
|
||||
|
||||
### 最佳实践
|
||||
|
||||
**✅ 推荐的问题类型**:
|
||||
- 具体问题:"骨质疏松的病因是什么?"
|
||||
- 概念解释:"什么是阿尔兹海默症?"
|
||||
- 信息提取:"文献中提到了哪些治疗方法?"
|
||||
|
||||
**❌ 不推荐的问题**:
|
||||
- 太宽泛:"这个文档有什么?"(检索效果差)
|
||||
- 无关问题:"今天天气怎么样?"(浪费检索资源)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 与Day 23-24的关系
|
||||
|
||||
**Day 23-24**:在项目智能体对话中实现@知识库
|
||||
- ✅ 功能完整
|
||||
- ⚠️ 但受限于智能体角色(如"选题评价"会忽略知识库内容)
|
||||
|
||||
**Day 25**:独立的智能问答
|
||||
- ✅ 无角色限制
|
||||
- ✅ 专注于基于知识库回答问题
|
||||
- ✅ 提供最纯粹的测试环境
|
||||
|
||||
---
|
||||
|
||||
## 🎉 里程碑1 - 100%完成!
|
||||
|
||||
### 核心功能清单
|
||||
|
||||
1. ✅ 用户认证与项目管理
|
||||
2. ✅ 12个AI智能体配置与调用
|
||||
3. ✅ 多轮对话上下文管理
|
||||
4. ✅ 流式输出(打字机效果)
|
||||
5. ✅ 模型切换(DeepSeek-V3/Qwen3/Gemini)
|
||||
6. ✅ 个人知识库管理
|
||||
7. ✅ @知识库检索与RAG集成(Day 23-24)
|
||||
8. ✅ **智能问答功能**(Day 25)⭐ 今日完成
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步规划
|
||||
|
||||
### 里程碑2预览(预计3-4天)
|
||||
|
||||
1. **对话历史增强**
|
||||
- 对话列表展示
|
||||
- 搜索和筛选
|
||||
- 导出为Markdown
|
||||
|
||||
2. **引用溯源优化**
|
||||
- 点击引用查看原文
|
||||
- 高亮显示相关片段
|
||||
- 文档来源追踪
|
||||
|
||||
3. **项目协作功能**
|
||||
- 成员管理
|
||||
- 权限控制
|
||||
- 共享知识库
|
||||
|
||||
---
|
||||
|
||||
## 📊 技术收获
|
||||
|
||||
### 1. 架构设计
|
||||
- 通过"通用对话"补充"项目对话"的不足
|
||||
- 两种模式并存,互不干扰
|
||||
- 代码高度复用
|
||||
|
||||
### 2. API设计
|
||||
- RESTful风格
|
||||
- 可选参数灵活性(conversationId?, knowledgeBaseIds?)
|
||||
- 流式输出性能优化
|
||||
|
||||
### 3. 前端组件化
|
||||
- MessageList、MessageInput高度解耦
|
||||
- 易于在不同场景复用
|
||||
- Props设计合理
|
||||
|
||||
---
|
||||
|
||||
**文档创建时间**: 2025-10-11
|
||||
**最后更新**: 2025-10-11
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
| DB-3 | ReviewTask 表新增 `error_details` JSONB 字段(存储 Skill 级失败详情) | `prisma/migrations/20260307_add_error_details_to_review_task/migration.sql` | 高 | 支持 partial_completed 状态,记录每个失败/超时 Skill 的名称和原因 |
|
||||
| DB-4 | SSA execution_mode 默认值改为 `agent` + 已有 session 全部更新 | `prisma/migrations/20260308_default_agent_mode/migration.sql` | 高 | ALTER DEFAULT + UPDATE 旧数据;QPER UI 入口已移除 |
|
||||
| DB-5 | SSA Agent Prompt 种子数据(SSA_AGENT_PLANNER / SSA_AGENT_CODER) | `prisma/seed-ssa-agent-prompts.ts` | 高 | 部署后执行 `npx tsx prisma/seed-ssa-agent-prompts.ts`;幂等可重复执行 |
|
||||
| DB-6 | IIT eQuery open 集合去重护栏(历史收敛 + open 唯一索引) | `prisma/migrations/20260308_add_iit_equery_open_dedupe_guard/migration.sql` | 高 | 先自动将历史重复 open eQuery 收敛为 `auto_closed`,再建立部分唯一索引防止未来重复 |
|
||||
|
||||
### 后端变更 (Node.js)
|
||||
|
||||
@@ -35,6 +36,8 @@
|
||||
| BE-8 | SSA Agent 通道体验优化(方案 B 左右职责分离 + 10 项 Bug 修复) | `ChatHandlerService.ts`, `AgentCoderService.ts`, `chat.routes.ts` | 重新构建镜像 | 视线牵引 Prompt + maxTokens 8000 + 重试流式生成 + consoleOutput 类型防御 + Prompt 铁律 + parseCode 健壮化 |
|
||||
| BE-9 | Phase 5A:CoderAgent 防错护栏(4 项改动) | `AgentCoderService.ts`, `TokenTruncationService.ts`, `chat.routes.ts` | 重新构建镜像 | XML 标签提取 + 防御性编程 Prompt + 高保真 Schema 注入 + token 配额 2500 + 后端强制 Agent 模式 |
|
||||
| BE-10 | SSA Agent 核心 Prompt 接入运营管理端(PlannerAgent + CoderAgent) | `AgentPlannerService.ts`, `AgentCoderService.ts`, `prompt.fallbacks.ts` | 重新构建镜像 | 硬编码 → `PromptService.get()` 动态加载;三级容灾:DB → 缓存 → fallback;需先完成 DB-5 |
|
||||
| BE-11 | IIT eQuery 幂等写入 + 安全去重工具脚本 | `iitEqueryService.ts`, `scripts/dedupe_open_equeries.ts`, `package.json` | 重新构建镜像 | `createBatch` 改为 `ON CONFLICT DO NOTHING`(open 集合),新增 `npx tsx scripts/dedupe_open_equeries.ts <projectId> [--apply]` |
|
||||
| BE-12 | IIT 实时工作流事件名称友好化兜底 + AI 对话证据块强制补齐 | `iitQcCockpitController.ts`, `ChatOrchestrator.ts` | 重新构建镜像 | 时间线事件名采用 event_label/cachedRules/fallback 三层映射;回答含“证据:”时若无明细则自动补齐,避免空证据块 |
|
||||
|
||||
### 前端变更
|
||||
|
||||
@@ -48,6 +51,7 @@
|
||||
| FE-6 | RVW 前端支持 partial_completed 状态(部分完成) | `TaskDetail.tsx`, `TaskTable.tsx`, `rvw/types/index.ts` | 重新构建镜像 | 琥珀色警告横幅展示失败模块详情,列表页显示"部分完成"标签,支持查看已完成模块的报告 |
|
||||
| FE-7 | SSA Agent 通道体验优化(方案 B + 动态 UI) | `AgentCodePanel.tsx`, `SSAChatPane.tsx`, `SSAWorkspacePane.tsx`, `SSACodeModal.tsx`, `useSSAChat.ts`, `ssaStore.ts`, `ssa.css` | 重新构建镜像 | 左右职责分离 + JWT 刷新 + 重试代码展示 + 错误信息展示 + 进度条同步 + 导出/查看代码按钮恢复 + ExecutingProgress 组件 |
|
||||
| FE-8 | SSA 默认 Agent 模式 + 查看代码修复 + 分析历史卡片 | `SSAChatPane.tsx`, `SSAWorkspacePane.tsx`, `useSSAChat.ts`, `ssaStore.ts` | 重新构建镜像 | 移除 ModeToggle + 默认 agent + 查看代码走 Modal + 分析完成后对话插入可点击结果卡片 + ChatIntentType 扩展 system |
|
||||
| FE-9 | IIT D1 筛选入选表“不合规条目”规则名称友好显示 | `EligibilityTable.tsx` | 重新构建镜像 | 不合规条目由 ruleId 显示改为 ruleName 优先,减少技术标识符暴露 |
|
||||
|
||||
### Python 微服务变更
|
||||
|
||||
|
||||
Reference in New Issue
Block a user