feat(ai): Day 4 — 策略 Prompt 优化 + Tool 调用日志

- System Prompt 增加 10 个 Tool 的使用时机指引,Agent 自动选择最合适的 Tool
- 优先使用 get_health_insights 作为首次对话开场工具
- AgentRunResult 新增 tool_calls: Vec<ToolCallLog>,记录每次调用名称/耗时/成功状态
- ToolCallLog 将在 Phase 2 写入 ai_tool_call_logs 表
This commit is contained in:
iven
2026-05-19 11:01:03 +08:00
parent 8b59f2d7d9
commit 8064db3475
2 changed files with 43 additions and 9 deletions

View File

@@ -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<ToolCallLog>,
}
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<ToolCallLog> = 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,

View File

@@ -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 作为首次对话的开场工具,获取全局概览后再深入。
如果同时有多个相关工具可用,选择信息量最大的那个,避免冗余调用。
## 策略不是互斥的,你可以在一轮对话中自然切换。
## 永远不要:推荐具体药物、给出明确诊断、替代医生建议。
## 如果没有可用的工具数据,就基于常识回答,并建议用户咨询医生。"#