feat(ai): Phase 2A-4 新增 3 个 Agent Tool — 化验报告/预约/用药查询
新增 3 个 AI Agent Tool 扩展医护沙箱能力: - query_patient_lab_reports: 查询患者化验报告列表(含异常计数) - query_patient_appointments: 查询患者即将到来的预约 - query_patient_medications: 查询患者当前用药列表 同时: - HealthDataProvider trait 新增 get_patient_lab_reports 方法 + LabReportListItemDto - erp-health 实现新 trait 方法(含 PII 解密) - sandbox.rs 更新角色权限:Patient 可查体征/化验/用药,MedicalStaff 额外可查预约 - 修复 ai_prompt_tests.rs 中 AnalysisService::new 签名变更的遗留编译错误 - 新增 5 个 agent 测试覆盖新 Tool 和沙箱权限过滤
This commit is contained in:
@@ -3,13 +3,15 @@ use erp_ai::agent::orchestrator::AgentRunParams;
|
||||
use erp_ai::agent::orchestrator::AgentRunResult;
|
||||
use erp_ai::agent::tool::{AgentTool, DisplayHint, ToolContext, ToolResult};
|
||||
use erp_ai::agent::tools::QueryPatientVitalsTool;
|
||||
use erp_ai::agent::tools::{QueryAppointmentsTool, QueryLabReportsTool, QueryMedicationsTool};
|
||||
use erp_ai::agent::{AgentOrchestrator, ToolRegistry};
|
||||
use erp_ai::dto::{AgentGenerateResponse, ChatMessage, ChatMessageRole, ToolCall, ToolDefinition};
|
||||
use erp_ai::error::AiResult;
|
||||
use erp_ai::provider::AiProvider;
|
||||
use erp_core::health_provider::{
|
||||
AppointmentSummaryDto, HealthDataProvider, HealthReportDto, LabItemDto, LabReportDto,
|
||||
MedicationSummaryDto, PatientSummaryDto, TimeRange, TrendAnalysisDto, VitalSignDto,
|
||||
LabReportListItemDto, MedicationSummaryDto, PatientSummaryDto, TimeRange, TrendAnalysisDto,
|
||||
VitalSignDto,
|
||||
};
|
||||
use futures::Stream;
|
||||
use std::pin::Pin;
|
||||
@@ -185,6 +187,14 @@ impl HealthDataProvider for MockHealthDataProvider {
|
||||
) -> erp_core::error::AppResult<Vec<MedicationSummaryDto>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn get_patient_lab_reports(
|
||||
&self,
|
||||
_tenant_id: Uuid,
|
||||
_patient_id: Uuid,
|
||||
_limit: u64,
|
||||
) -> erp_core::error::AppResult<Vec<LabReportListItemDto>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
// === 测试 ===
|
||||
@@ -216,7 +226,13 @@ async fn test_agent_direct_reply_no_tool_call() {
|
||||
|
||||
let ctx = make_tool_ctx(None);
|
||||
let result = orchestrator
|
||||
.run("你是助手", &mut messages, &ctx, &AgentRunParams::default())
|
||||
.run(
|
||||
"你是助手",
|
||||
&mut messages,
|
||||
&ctx,
|
||||
&AgentRunParams::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -241,7 +257,13 @@ async fn test_agent_tool_call_flow() {
|
||||
|
||||
let ctx = make_tool_ctx(Some(Uuid::now_v7()));
|
||||
let result = orchestrator
|
||||
.run("你是助手", &mut messages, &ctx, &AgentRunParams::default())
|
||||
.run(
|
||||
"你是助手",
|
||||
&mut messages,
|
||||
&ctx,
|
||||
&AgentRunParams::default(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -291,3 +313,64 @@ async fn test_tool_registry() {
|
||||
assert_eq!(defs[0].name, "query_patient_vitals");
|
||||
assert!(defs[0].parameters["properties"]["days"].is_object());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_query_lab_reports_no_patient() {
|
||||
let tool = QueryLabReportsTool;
|
||||
let ctx = make_tool_ctx(None);
|
||||
let result = tool.execute(&ctx, serde_json::json!({})).await;
|
||||
assert!(result.output.contains("未关联患者"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_query_appointments_no_patient() {
|
||||
let tool = QueryAppointmentsTool;
|
||||
let ctx = make_tool_ctx(None);
|
||||
let result = tool.execute(&ctx, serde_json::json!({})).await;
|
||||
assert!(result.output.contains("未关联患者"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_query_medications_no_patient() {
|
||||
let tool = QueryMedicationsTool;
|
||||
let ctx = make_tool_ctx(None);
|
||||
let result = tool.execute(&ctx, serde_json::json!({})).await;
|
||||
assert!(result.output.contains("未关联患者"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_all_tools_registered() {
|
||||
let mut registry = ToolRegistry::new();
|
||||
registry.register(Arc::new(QueryPatientVitalsTool));
|
||||
registry.register(Arc::new(QueryLabReportsTool));
|
||||
registry.register(Arc::new(QueryAppointmentsTool));
|
||||
registry.register(Arc::new(QueryMedicationsTool));
|
||||
|
||||
assert_eq!(registry.all_tools().len(), 4);
|
||||
assert!(registry.get("query_patient_vitals").is_some());
|
||||
assert!(registry.get("query_patient_lab_reports").is_some());
|
||||
assert!(registry.get("query_patient_appointments").is_some());
|
||||
assert!(registry.get("query_patient_medications").is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sandbox_role_tool_filtering() {
|
||||
use erp_ai::agent::sandbox::{UserRole, get_sandbox_config};
|
||||
use std::collections::HashSet;
|
||||
|
||||
let patient = get_sandbox_config(&UserRole::Patient);
|
||||
assert!(patient.allowed_tools.contains("query_patient_vitals"));
|
||||
assert!(patient.allowed_tools.contains("query_patient_lab_reports"));
|
||||
assert!(patient.allowed_tools.contains("query_patient_medications"));
|
||||
assert!(!patient.allowed_tools.contains("query_patient_appointments"));
|
||||
|
||||
let staff = get_sandbox_config(&UserRole::MedicalStaff);
|
||||
assert!(staff.allowed_tools.contains("query_patient_vitals"));
|
||||
assert!(staff.allowed_tools.contains("query_patient_lab_reports"));
|
||||
assert!(staff.allowed_tools.contains("query_patient_appointments"));
|
||||
assert!(staff.allowed_tools.contains("query_patient_medications"));
|
||||
|
||||
let admin = get_sandbox_config(&UserRole::Admin);
|
||||
// Admin 保持原有 tool,不新增
|
||||
assert!(admin.allowed_tools.contains("query_patient_vitals"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user