From 7edf1ed1d3be7d12ca8574210879884ba7ef2c63 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 19 May 2026 10:41:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20Agent=20Tool=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=20=E2=80=94=20QueryPatientProfile=20+=20DisplayHint=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=203=20=E5=8F=98=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QueryPatientProfileTool: 查询患者档案摘要(年龄/性别/慢性病/用药/家族史) - DisplayHint 新增 TrendChart/InsightCard/PatientProfile 变体 - 沙箱: Patient + MedicalStaff 添加 query_patient_profile --- crates/erp-ai/src/agent/sandbox.rs | 2 + crates/erp-ai/src/agent/tool.rs | 14 +++ crates/erp-ai/src/agent/tools/mod.rs | 2 + .../src/agent/tools/query_patient_profile.rs | 107 ++++++++++++++++++ crates/erp-ai/src/handler/chat_handler.rs | 2 + 5 files changed, 127 insertions(+) create mode 100644 crates/erp-ai/src/agent/tools/query_patient_profile.rs diff --git a/crates/erp-ai/src/agent/sandbox.rs b/crates/erp-ai/src/agent/sandbox.rs index f22c3da..b554721 100644 --- a/crates/erp-ai/src/agent/sandbox.rs +++ b/crates/erp-ai/src/agent/sandbox.rs @@ -45,6 +45,7 @@ pub fn get_sandbox_config(role: &UserRole) -> SandboxConfig { "query_patient_lab_reports".into(), "query_patient_medications".into(), "search_medical_knowledge".into(), + "query_patient_profile".into(), ]), system_prompt_suffix: PATIENT_PROMPT_SUFFIX, output_filter: OutputFilter { @@ -61,6 +62,7 @@ pub fn get_sandbox_config(role: &UserRole) -> SandboxConfig { "query_patient_appointments".into(), "query_patient_medications".into(), "search_medical_knowledge".into(), + "query_patient_profile".into(), ]), system_prompt_suffix: MEDICAL_STAFF_PROMPT_SUFFIX, output_filter: OutputFilter { diff --git a/crates/erp-ai/src/agent/tool.rs b/crates/erp-ai/src/agent/tool.rs index 5813d92..faa5fc4 100644 --- a/crates/erp-ai/src/agent/tool.rs +++ b/crates/erp-ai/src/agent/tool.rs @@ -51,5 +51,19 @@ pub enum DisplayHint { level: String, message: String, }, + TrendChart { + metrics: Vec, + period: String, + summary: String, + }, + InsightCard { + title: String, + severity: String, + items: Vec, + }, + PatientProfile { + chronic_conditions: Vec, + medication_count: usize, + }, Text, } diff --git a/crates/erp-ai/src/agent/tools/mod.rs b/crates/erp-ai/src/agent/tools/mod.rs index ef8175e..2e8b6fa 100644 --- a/crates/erp-ai/src/agent/tools/mod.rs +++ b/crates/erp-ai/src/agent/tools/mod.rs @@ -3,11 +3,13 @@ pub mod query_appointments; pub mod query_lab_reports; pub mod query_medications; +pub mod query_patient_profile; pub mod query_vitals; pub mod search_medical_knowledge; pub use query_appointments::QueryAppointmentsTool; pub use query_lab_reports::QueryLabReportsTool; pub use query_medications::QueryMedicationsTool; +pub use query_patient_profile::QueryPatientProfileTool; pub use query_vitals::QueryPatientVitalsTool; pub use search_medical_knowledge::SearchMedicalKnowledgeTool; diff --git a/crates/erp-ai/src/agent/tools/query_patient_profile.rs b/crates/erp-ai/src/agent/tools/query_patient_profile.rs new file mode 100644 index 0000000..9439176 --- /dev/null +++ b/crates/erp-ai/src/agent/tools/query_patient_profile.rs @@ -0,0 +1,107 @@ +use async_trait::async_trait; + +use crate::agent::tool::{AgentTool, DisplayHint, ToolContext, ToolResult}; + +/// 查询患者档案摘要(年龄/性别/慢性病/用药/家族史) +pub struct QueryPatientProfileTool; + +#[async_trait] +impl AgentTool for QueryPatientProfileTool { + fn name(&self) -> &str { + "query_patient_profile" + } + + fn description(&self) -> &str { + "查询患者健康档案摘要,包括年龄组、性别、慢性疾病、当前用药、家族病史和最近体检日期。" + } + + fn parameters_schema(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": {} + }) + } + + async fn execute(&self, ctx: &ToolContext, _params: serde_json::Value) -> ToolResult { + let patient_id = match ctx.patient_id { + Some(id) => id, + None => { + return ToolResult { + output: "未关联患者档案,无法查询患者信息".to_string(), + display_hint: None, + }; + } + }; + + match ctx + .health_provider + .get_patient_summary(ctx.tenant_id, patient_id) + .await + { + Ok(summary) => { + let mut output = String::from("患者档案摘要:\n"); + output.push_str(&format!("- 年龄组: {}\n", summary.age_group)); + output.push_str(&format!("- 性别: {}\n", summary.sex)); + + if summary.chronic_conditions.is_empty() { + output.push_str("- 慢性疾病: 无\n"); + } else { + output.push_str(&format!( + "- 慢性疾病: {}\n", + summary.chronic_conditions.join("、") + )); + } + + if summary.medications.is_empty() { + output.push_str("- 当前用药: 无\n"); + } else { + output.push_str(&format!("- 当前用药: {}\n", summary.medications.join("、"))); + } + + if summary.family_history.is_empty() { + output.push_str("- 家族病史: 无\n"); + } else { + output.push_str(&format!( + "- 家族病史: {}\n", + summary.family_history.join("、") + )); + } + + output.push_str(&format!("- 最近体检: {}\n", summary.last_checkup_date)); + + let display_hint = DisplayHint::PatientProfile { + chronic_conditions: summary.chronic_conditions.clone(), + medication_count: summary.medications.len(), + }; + + ToolResult { + output, + display_hint: Some(display_hint), + } + } + Err(e) => ToolResult { + output: format!("查询患者档案失败: {}", e), + display_hint: None, + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tool_name() { + let tool = QueryPatientProfileTool; + assert_eq!(tool.name(), "query_patient_profile"); + } + + #[test] + fn parameters_schema_is_empty_object() { + let tool = QueryPatientProfileTool; + let schema = tool.parameters_schema(); + assert_eq!(schema["type"], "object"); + assert!(schema["properties"].as_object().unwrap().is_empty()); + } +} diff --git a/crates/erp-ai/src/handler/chat_handler.rs b/crates/erp-ai/src/handler/chat_handler.rs index bf68082..dbf6cf2 100644 --- a/crates/erp-ai/src/handler/chat_handler.rs +++ b/crates/erp-ai/src/handler/chat_handler.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::agent::orchestrator::AgentRunParams; use crate::agent::sandbox::{get_sandbox_config, resolve_role}; use crate::agent::tool::ToolContext; +use crate::agent::tools::QueryPatientProfileTool; use crate::agent::tools::QueryPatientVitalsTool; use crate::agent::tools::SearchMedicalKnowledgeTool; use crate::agent::tools::{QueryAppointmentsTool, QueryLabReportsTool, QueryMedicationsTool}; @@ -123,6 +124,7 @@ where registry.register(std::sync::Arc::new(QueryAppointmentsTool)); registry.register(std::sync::Arc::new(QueryMedicationsTool)); registry.register(std::sync::Arc::new(SearchMedicalKnowledgeTool)); + registry.register(std::sync::Arc::new(QueryPatientProfileTool)); // 根据用户角色获取沙箱配置 let user_role = resolve_role(&ctx.roles);