From f42669f9343052019dc66c83d4d2c3f7ba4f6903 Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 18 May 2026 02:58:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E5=AE=9E=E7=8E=B0=20query=5Fpatien?= =?UTF-8?q?t=5Fvitals=20Tool=20=E2=80=94=20=E9=A6=96=E4=B8=AA=E7=AB=AF?= =?UTF-8?q?=E5=88=B0=E7=AB=AF=20Agent=20Tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/erp-ai/src/agent/tools/mod.rs | 5 + crates/erp-ai/src/agent/tools/query_vitals.rs | 96 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 crates/erp-ai/src/agent/tools/mod.rs create mode 100644 crates/erp-ai/src/agent/tools/query_vitals.rs diff --git a/crates/erp-ai/src/agent/tools/mod.rs b/crates/erp-ai/src/agent/tools/mod.rs new file mode 100644 index 0000000..d7b5d28 --- /dev/null +++ b/crates/erp-ai/src/agent/tools/mod.rs @@ -0,0 +1,5 @@ +// Agent Tool 实现 — Phase 0 添加 query_patient_vitals + +pub mod query_vitals; + +pub use query_vitals::QueryPatientVitalsTool; diff --git a/crates/erp-ai/src/agent/tools/query_vitals.rs b/crates/erp-ai/src/agent/tools/query_vitals.rs new file mode 100644 index 0000000..4689267 --- /dev/null +++ b/crates/erp-ai/src/agent/tools/query_vitals.rs @@ -0,0 +1,96 @@ +use async_trait::async_trait; +use erp_core::health_provider::TimeRange; + +use crate::agent::tool::{AgentTool, DisplayHint, ToolContext, ToolResult}; + +/// 查询患者最近体征数据(血压/血糖/心率等) +pub struct QueryPatientVitalsTool; + +#[async_trait] +impl AgentTool for QueryPatientVitalsTool { + fn name(&self) -> &str { + "query_patient_vitals" + } + + fn description(&self) -> &str { + "查询患者最近的体征数据(血压、血糖、心率、体重、血氧等)。需要提供天数范围(默认 7 天)。" + } + + fn parameters_schema(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "days": { + "type": "integer", + "description": "查询最近多少天的数据,默认 7 天" + } + } + }) + } + + 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, + }; + } + }; + + let days = params["days"].as_i64().unwrap_or(7); + let now = chrono::Utc::now(); + let start = now - chrono::Duration::days(days); + + let range = TimeRange { start, end: now }; + let metrics = vec![ + "systolic_bp_morning".into(), + "diastolic_bp_morning".into(), + "heart_rate".into(), + "blood_sugar".into(), + ]; + + match ctx + .health_provider + .get_vital_signs(ctx.tenant_id, patient_id, &metrics, &range) + .await + { + Ok(vitals) => { + if vitals.is_empty() { + return ToolResult { + output: "该时间段内无体征数据".to_string(), + display_hint: None, + }; + } + + let mut output = String::from("最近体征数据:\n"); + for v in &vitals { + output.push_str(&format!("- {}:", v.metric)); + let values_str: Vec = v + .values + .iter() + .take(10) + .map(|(date, val)| format!("{}={}", date, val)) + .collect(); + output.push_str(&format!(" ({})\n", values_str.join(", "))); + } + + let display_hint = vitals.first().map(|v| DisplayHint::VitalCard { + indicator_type: v.metric.clone(), + values: v.values.iter().take(10).cloned().collect(), + unit: v.unit.clone(), + }); + + ToolResult { + output, + display_hint, + } + } + Err(e) => ToolResult { + output: format!("查询体征数据失败: {}", e), + display_hint: None, + }, + } + } +}