diff --git a/crates/erp-ai/src/agent/orchestrator.rs b/crates/erp-ai/src/agent/orchestrator.rs index 7f2ca55..7c210c6 100644 --- a/crates/erp-ai/src/agent/orchestrator.rs +++ b/crates/erp-ai/src/agent/orchestrator.rs @@ -6,6 +6,14 @@ use crate::dto::{ChatMessage, ChatMessageRole}; use crate::error::AiResult; use crate::provider::AiProvider; +/// 单次 Tool 调用日志 +#[derive(Debug, Clone)] +pub struct ToolCallLog { + pub tool_name: String, + pub duration_ms: u64, + pub success: bool, +} + /// Agent 运行时参数 pub struct AgentRunParams { pub model: String, @@ -40,6 +48,7 @@ pub struct AgentRunResult { pub total_input_tokens: u32, pub total_output_tokens: u32, pub iterations: usize, + pub tool_calls: Vec, } impl AgentOrchestrator { @@ -66,6 +75,7 @@ impl AgentOrchestrator { let mut iterations = 0; let mut total_input_tokens = 0u32; let mut total_output_tokens = 0u32; + let mut tool_call_logs: Vec = Vec::new(); loop { iterations += 1; @@ -96,6 +106,7 @@ impl AgentOrchestrator { total_input_tokens, total_output_tokens, iterations, + tool_calls: tool_call_logs, }); } }; @@ -143,23 +154,30 @@ impl AgentOrchestrator { // 执行每个 Tool Call(受沙箱 allowed_tools 约束) for tc in &tool_calls { - let tool_result = match self.tool_registry.get(&tc.name) { + let start = std::time::Instant::now(); + let (tool_result, success) = match self.tool_registry.get(&tc.name) { Some(tool) => { - // 沙箱过滤:如果 allowed_tools 存在且不包含此 Tool,拒绝执行 if let Some(allowed) = allowed_tools { if !allowed.contains(tc.name.as_str()) { - format!("Tool '{}' 在当前角色下不可用", tc.name) + (format!("Tool '{}' 在当前角色下不可用", tc.name), false) } else { let result = tool.execute(ctx, tc.arguments.clone()).await; - result.output + (result.output, true) } } else { let result = tool.execute(ctx, tc.arguments.clone()).await; - result.output + (result.output, true) } } - None => format!("未知 Tool: {}", tc.name), + None => (format!("未知 Tool: {}", tc.name), false), }; + let duration = start.elapsed(); + + tool_call_logs.push(ToolCallLog { + tool_name: tc.name.clone(), + duration_ms: duration.as_millis() as u64, + success, + }); messages.push(ChatMessage { role: ChatMessageRole::Tool, diff --git a/crates/erp-ai/src/config_resolver.rs b/crates/erp-ai/src/config_resolver.rs index a83a579..c609d2d 100644 --- a/crates/erp-ai/src/config_resolver.rs +++ b/crates/erp-ai/src/config_resolver.rs @@ -574,16 +574,16 @@ fn default_system_prompt() -> String { - 分享积极案例,降低恐惧感 2. 【医疗科普】当用户询问指标含义、疾病知识时: - - 调用 search_medical_knowledge 获取准确信息(如可用) + - 调用 search_medical_knowledge 获取准确信息 - 用比喻和类比让老年患者也能理解 - 强调"具体请以医生诊断为准" 3. 【服务推荐】当用户表达就医需求或身体不适时: - - 调用 query_appointments 查看已有预约(如可用) + - 调用 query_patient_appointments 查看已有预约 - 主动提出帮用户预约 4. 【风险预警】当用户描述的症状或数据异常时: - - 调用 query_patient_vitals 查看体征数据 + - 调用 get_health_insights 获取综合健康洞察 - 明确告知风险等级和需要注意的事项 - 高风险时建议尽快就医 @@ -591,6 +591,22 @@ fn default_system_prompt() -> String { - 提供科室位置、出诊医生信息 - 建议用户联系前台预约 +## 工具使用指引 +根据用户意图选择合适的工具,不要一次调用所有工具: + +- 用户首次对话或询问总体健康 → get_health_insights(综合洞察) +- 询问"我的血压/血糖怎么样" → query_patient_vitals(体征数据) +- 询问"化验结果/报告" → query_patient_lab_reports(化验报告列表) +- 拿到具体报告 ID 后追问详情 → analyze_lab_report(单份报告详细指标) +- 询问"趋势/最近变化" → analyze_health_trends(趋势分析) +- 询问"吃什么药" → query_patient_medications(用药列表) +- 询问"预约/挂号" → query_patient_appointments(预约列表) +- 询问疾病/指标知识 → search_medical_knowledge(医学知识搜索) +- 询问"我的档案/基本信息" → query_patient_profile(患者档案) + +优先使用 get_health_insights 作为首次对话的开场工具,获取全局概览后再深入。 +如果同时有多个相关工具可用,选择信息量最大的那个,避免冗余调用。 + ## 策略不是互斥的,你可以在一轮对话中自然切换。 ## 永远不要:推荐具体药物、给出明确诊断、替代医生建议。 ## 如果没有可用的工具数据,就基于常识回答,并建议用户咨询医生。"#