From a62332f1c43a1378fc9894c9c740aeda2c15985a Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 5 May 2026 19:53:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai):=20AI=20=E5=88=86=E6=9E=90=E9=A2=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=20+=20prompt=20=E9=9D=9E=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 四个 SSE 端点增加数据完整性校验:items/sections 为空时返回 400 - 迁移 000123 更新全部 prompt system_prompt:明确非对话、输出结构化结果 - 前端用户看到的是分析结论,不再收到"请补充数据"的对话式回复 --- crates/erp-ai/src/handler/mod.rs | 21 ++++ crates/erp-server/migration/src/lib.rs | 2 + ...23_update_ai_prompts_system_instruction.rs | 112 ++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 crates/erp-server/migration/src/m20260505_000123_update_ai_prompts_system_instruction.rs diff --git a/crates/erp-ai/src/handler/mod.rs b/crates/erp-ai/src/handler/mod.rs index 058480d..1dc5dec 100644 --- a/crates/erp-ai/src/handler/mod.rs +++ b/crates/erp-ai/src/handler/mod.rs @@ -42,6 +42,13 @@ where .health_provider .get_lab_report(ctx.tenant_id, report_id) .await?; + + if lab_dto.items.is_empty() { + return Err(erp_core::error::AppError::Validation( + "化验报告缺少检查项目数据,无法进行 AI 分析。请先录入完整的化验指标。".into(), + )); + } + let sanitized_data = state.analysis.sanitizer.sanitize_lab_report(&lab_dto)?; let prompt = state @@ -112,6 +119,13 @@ where .health_provider .get_trend_analysis_data(ctx.tenant_id, patient_id, &metrics, &range) .await?; + + if trend_data.metrics.is_empty() { + return Err(erp_core::error::AppError::Validation( + "患者在选定时间段内无体征监测数据,无法进行趋势分析。".into(), + )); + } + let sanitized_data = state.analysis.sanitizer.sanitize_trend_analysis(&trend_data)?; let prompt = state @@ -223,6 +237,13 @@ where .health_provider .get_full_report(ctx.tenant_id, report_id) .await?; + + if report_dto.sections.is_empty() { + return Err(erp_core::error::AppError::Validation( + "健康报告缺少内容数据,无法生成摘要。请先完善报告内容。".into(), + )); + } + let sanitized_data = state .analysis .sanitizer diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 00407c8..b9d5b58 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -122,6 +122,7 @@ mod m20260505_000119_enable_pgvector; mod m20260505_000120_create_ai_knowledge_rules; mod m20260505_000121_create_ai_knowledge_references; mod m20260505_000122_create_ai_knowledge_guides; +mod m20260505_000123_update_ai_prompts_system_instruction; pub struct Migrator; @@ -251,6 +252,7 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000120_create_ai_knowledge_rules::Migration), Box::new(m20260505_000121_create_ai_knowledge_references::Migration), Box::new(m20260505_000122_create_ai_knowledge_guides::Migration), + Box::new(m20260505_000123_update_ai_prompts_system_instruction::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260505_000123_update_ai_prompts_system_instruction.rs b/crates/erp-server/migration/src/m20260505_000123_update_ai_prompts_system_instruction.rs new file mode 100644 index 0000000..d16b5c7 --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000123_update_ai_prompts_system_instruction.rs @@ -0,0 +1,112 @@ +//! 更新所有 AI 分析 prompt 的 system_prompt — 强调这是系统自动分析,非对话 + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + + // 化验单解读 — 强化系统指令 + let sys_lab = esc(r#"你是一名专业的医学检验解读助手。这是由健康管理系统自动触发的分析任务,不是对话。 + +请直接输出结构化的分析结果,格式如下: + +## 指标分析 +逐项列出每个指标的数值、是否在正常范围、简要说明。 + +## 异常指标 +列出所有异常指标,说明可能的原因(不作为诊断)。 + +## 健康建议 +给出 2-3 条切实可行的后续行动建议。 + +## 总体评估 +用一句话总结健康状况。 + +要求: +1. 直接输出结果,不要寒暄或询问 +2. 使用通俗易懂的语言 +3. 异常指标要重点标注"#); + + // 趋势分析 — 保持已有的 v2 格式,只加非对话指令前缀 + let sys_trend = esc(r#"你是一名健康数据分析专家。你将收到经过预处理的结构化统计摘要数据,包括线性回归趋势、异常检测结果等。 +这是由健康管理系统自动触发的分析任务,不是对话。请直接输出结构化的分析结果。 + +要求: +1. **趋势判断** — 基于回归斜率(slope)和R²判断指标趋势(上升/下降/稳定),注意R²较低时趋势不确定性大 +2. **异常预警** — 重点分析被检测为异常的数据点,说明偏离程度和可能的原因 +3. **综合分析** — 考虑各指标间的关联性(如血压和体重、血糖和心率) +4. **临床建议** — 给出切实可行的健康管理建议,不替代医生诊断 +5. **风险评级** — 对整体健康风险给出低/中/高评估并说明理由 +6. **关注重点** — 用简洁的语言总结最需要关注的 2-3 个问题"#); + + // 体检方案 — 加非对话指令 + let sys_checkup = esc(r#"你是一名健康管理顾问。这是由健康管理系统自动触发的分析任务,不是对话。 +请直接输出个性化的体检方案,格式如下: + +## 推荐检查项目 +按优先级列出,每项说明目的、建议频率。 + +## 重点关注的健康风险 +基于患者情况列出 2-3 个需要特别关注的健康风险。 + +## 生活方式建议 +给出 3-5 条切实可行的日常健康管理建议。 + +要求: +1. 直接输出结果,不要寒暄或询问 +2. 基于患者年龄、性别、既往病史推荐 +3. 按优先级排序"#); + + // 报告摘要 — 加非对话指令 + let sys_summary = esc(r#"你是一名医疗报告摘要撰写专家。这是由健康管理系统自动触发的分析任务,不是对话。 +请直接输出结构化的报告摘要,格式如下: + +## 关键发现 +列出报告中的主要发现。 + +## 异常项目 +列出所有异常项目及其严重程度。 + +## 结论 +简明的总体结论。 + +## 行动建议 +具体的后续步骤建议。 + +要求: +1. 直接输出结果,不要寒暄或询问 +2. 控制在 500 字以内 +3. 语言简洁专业"#); + + for (name, sys) in [ + ("lab_report_interpretation", sys_lab), + ("health_trend_analysis", sys_trend), + ("personalized_checkup_plan", sys_checkup), + ("report_summary_generation", sys_summary), + ] { + db.execute(sea_orm::Statement::from_string( + sea_orm::DatabaseBackend::Postgres, + format!( + "UPDATE ai_prompt SET system_prompt = '{sys}', version = version + 1, updated_at = NOW() WHERE name = '{name}' AND is_active = true" + ), + )) + .await?; + } + + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + // prompt 更新不做回滚,保持最新版本 + Ok(()) + } +} + +fn esc(s: &str) -> String { + s.replace('\'', "''") +}