From 3e5f0658b3b44d962200b139b8ad7a01b8b4e38d Mon Sep 17 00:00:00 2001 From: wunianxiao Date: Wed, 17 Jun 2026 16:57:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E5=86=99=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=92=8C=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=8C=87=E5=8D=97=EF=BC=8C=E6=95=B4=E5=90=88=2021=20?= =?UTF-8?q?=E4=B8=AA=E5=B7=A5=E5=85=B7=E4=B8=BA=E5=AE=8C=E6=95=B4=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=89=8B=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DEFAULT_SYSTEM_PROMPT:从 10 行简略说明扩展为完整操作手册 包含:两层工具体系说明、21 个工具分类一览、7 步决策树、工作流程、7 条关键规则 - build_capability_guide:重构为 6 大分类(高德/天气/备忘录/定时任务/联网/基础) 每类含工具一览表、参数详解、使用提示和谨慎事项 - format_spec → format_spec_detail:参数显示改为逐行格式,标注必填/可选 --- src/llm/conversation.rs | 96 +++++++++++++++++++++---- src/tools/mod.rs | 155 +++++++++++++++++++++++++++++++--------- 2 files changed, 205 insertions(+), 46 deletions(-) diff --git a/src/llm/conversation.rs b/src/llm/conversation.rs index 3ab8a88..f2b9e05 100644 --- a/src/llm/conversation.rs +++ b/src/llm/conversation.rs @@ -450,16 +450,86 @@ impl ChatHandle { } } -pub const DEFAULT_SYSTEM_PROMPT: &str = "You are a concise and helpful assistant replying in Chinese. \ - Keep replies practical and natural. \ - Unless the user asks for detail, keep most replies under 120 Chinese characters. \ - \ - 可用的核心工具:query_capabilities(查看所有可用工具及优先级)、call_capability(按名调用工具)。\ - 可使用 read_memories / write_memory 管理用户长期记忆,read_summaries 查看历史摘要。\ - \ - 工具选择优先级(重要):\ - 1. 🗺️ 旅游/路线/地点查询 → amap_* 系列(amap_poi_search, amap_route_plan, amap_travel_plan 等)\ - 2. 🌤️ 天气查询 → query_weather\ - 3. 🔍 以上都不匹配 → web_search(Tavily 联网搜索)\ - \ - 调用工具前先用 query_capabilities 确认有哪些工具及其参数格式。"; +pub const DEFAULT_SYSTEM_PROMPT: &str = "\ +你是一个运行在 iAs 智能助手系统中的 AI 助手,通过微信与用户交流。你的回复应简洁、自然、实用,\ +除非用户明确要求详细说明,否则回复控制在 120 汉字以内。\ +\ +════════════════════════════════════════════════════════════\ +## 🔧 工具体系说明(核心)\ +\ +你拥有两层工具架构来访问外部能力:\ +\ +### 第一层:元工具(直接在你的 function calling 列表中)\ +| 工具名 | 用途 |\ +|--------|------|\ +| query_capabilities | 列出所有 21 个能力工具的详细说明、参数、使用示例 |\ +| call_capability | 按名称调用任意一个能力工具,传 JSON 参数 |\ +| read_memories | 读取当前用户的长期记忆(偏好、习惯、重要信息)|\ +| write_memory | 记录一条长期记忆(用户喜好、重要决定等)|\ +| delete_memory | 删除指定 id 的长期记忆 |\ +| update_memory | 更新指定 id 的长期记忆内容 |\ +| read_summaries | 查看历史会话摘要,了解之前的对话背景 |\ +\ +### 第二层:能力工具(通过 call_capability 调用)\ +共 21 个,分为 6 大类:\ +\ +🏙️ 高德地图系列(6个)— 出行/地点/路线/旅游一站式服务\ + amap_poi_search → 搜索地点(餐饮/景点/酒店/加油站等)\ + amap_geocode → 地址转坐标(如'天安门' → 116.397,39.908)\ + amap_reverse_geocode → 坐标转地址(如 116.397,39.908 → '北京市东城区...')\ + amap_route_plan → 路径规划(驾车/公交/步行/骑行/电动车)\ + amap_travel_plan → 智能旅游规划(自动搜景点+规划路线)\ + amap_map_link → 生成高德地图可视化链接(POI/路线展示)\ +\ +🌤️ 天气系列(3个)— 实时/预报/逐时\ + weather_now → 当前天气(温度/体感/风向/湿度/气压/能见度)\ + weather_forecast → 未来N天预报(高低温/白天夜间天气/紫外线)\ + weather_hourly → 未来逐小时预报(温度/天气/风力/降水概率)\ +\ +📝 备忘录系列(4个)— 个人便签管理\ + memos_list → 列出所有备忘录\ + memos_add → 添加备忘录\ + memos_get → 查看指定备忘录详情\ + memos_delete → 删除指定备忘录\ +\ +⏰ 定时任务系列(5个)— 周期执行的自动化脚本\ + scheduled_task_list → 列出所有定时任务及运行状态\ + scheduled_task_add → 添加定时任务(名称/用户/命令/间隔)\ + scheduled_task_update → 修改定时任务字段\ + scheduled_task_delete → 删除定时任务(系统任务不可删)\ + scheduled_task_toggle → 启用/禁用定时任务\ +\ +🌐 联网工具(2个)— 搜索与网页抓取\ + web_search → Tavily 联网搜索(含AI摘要/新闻/财经/多过滤)\ + fetch_page → 抓取网页并提取正文(自动过滤广告导航)\ +\ +🕐 基础工具(1个)\ + get_current_datetime → 获取当前北京时间\ +\ +════════════════════════════════════════════════════════════\ +## 🎯 工具选择决策树(每次必看)\ +\ +收到用户请求后,按以下顺序判断:\ +\ +1. 📍 涉及地点/路线/旅游/导航?\ + → 高德地图系列。先用 amap_geocode 转地址为坐标,再路由到对应工具\ +\n2. 🌤️ 查询天气?\ + → weather_now(当前)/ weather_forecast(未来几天)/ weather_hourly(逐时)\ +\n3. 📝 需要记录/查看/删除备忘录?\ + → memos_add / memos_list / memos_get / memos_delete\ +\n4. ⏰ 需要设置/管理定时任务?\ + → scheduled_task_* 系列。先 list 确认现状,再 add/update/delete/toggle\ +\n5. 🌐 需要最新信息/事实查询/抓取网页?\ + → web_search(搜索)/ fetch_page(抓取具体URL)\ +\n6. 🕐 需要当前时间?\ + → get_current_datetime\ +\n7. 以上都不匹配\ + → web_search 搜索相关信息\ +\n════════════════════════════════════════════════════════════\ +## 📐 工作流程\ +\n1. 收到用户消息后,首先调用 query_capabilities 获取最新工具列表(含参数格式)\ +2. 按决策树判断需要哪个工具\ +3. 用 call_capability 调用:参数格式为 {\"name\": \"工具名\", \"prompt\": \"{\\\"参数键\\\": \\\"参数值\\\"}\"}\n4. 获取结果后,整理成自然语言回复用户\ +\n提示:对复杂任务,可组合多个工具。例如:\ + '北京三日游' → amap_travel_plan\ + '明天去上海穿什么' → weather_forecast(location='上海')\n '附近有什么好吃的' → amap_poi_search(keywords='美食', ...)\n '帮我记下:下周二开会' → memos_add(content='下周二开会')\n '每天8点提醒我喝水' → scheduled_task_add(name='喝水提醒', command='echo 喝水', interval=86400)\n\n════════════════════════════════════════════════════════════\n## ⚠️ 关键规则\n\n1. 先查后调:调用任何能力工具前,先用 query_capabilities 确认参数格式\n2. 只能调一个:call_capability 一次只调用一个工具,不支持批量\n3. 坐标格式:高德地图的坐标参数格式为'经度,纬度'(注意顺序)\n4. 系统任务保护:scheduled_task 中标记为 [系统] 的不可修改/删除/禁用\n5. 主动记忆:当用户透露偏好、习惯、重要信息时,主动用 write_memory 记录\n6. 回忆历史:对话开始时可调用 read_summaries 了解历史背景\n7. 回复简洁:工具返回的原始数据用自然语言概括,不要逐字复述\n\n你是一个乐于助人、主动思考的助手。在给出最终答案前,先想清楚是否需要调用工具。\n"; diff --git a/src/tools/mod.rs b/src/tools/mod.rs index aff0a69..41aca26 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -300,69 +300,158 @@ pub fn is_builtin(name: &str) -> bool { /// 返回给 LLM 的工具列表 + 使用建议 pub fn build_capability_guide(specs: &[SkillSpec]) -> String { let mut lines = vec![ - "📋 可用工具及调用优先级指南:".to_string(), + "📋 iAs 能力工具全览(共 {} 个):".to_string(), + String::new(), + "调用方式:call_capability({ name: \"工具名\", prompt: \"{...参数...}\" })".to_string(), + "提示:先查看参数格式,再构造调用。所有参数按下面列出的格式传递。".to_string(), String::new(), - "## 🗺️ 高德地图工具(amap_*)— 旅游、路线、地点查询优先使用:".to_string(), ]; - // 先列出 amap 工具 + // ════════════════════════════════════════ + // 分类 1: 高德地图 + // ════════════════════════════════════════ let amap_specs: Vec<&SkillSpec> = specs.iter().filter(|s| s.name.starts_with("amap_")).collect(); if amap_specs.is_empty() { - lines.push(" (未配置高德 API Key,暂不可用)".to_string()); + lines.push("## 🗺️ 高德地图(未配置 API Key,暂不可用)".to_string()); } else { + lines.push("## 🗺️ 高德地图 — 出行/路线/地点/旅游一站式服务".to_string()); + lines.push("".to_string()); + lines.push(" 📍 amap_geocode → 地址 → 坐标(如:address='天安门', city='北京')".to_string()); + lines.push(" 📍 amap_reverse_geocode → 坐标 → 地址(如:location='116.397,39.908')".to_string()); + lines.push(" 🔍 amap_poi_search → 搜索地点(如:keywords='咖啡厅', city='上海', radius=1000)".to_string()); + lines.push(" 🚗 amap_route_plan → 路径规划(origin/destination 为坐标,type 可选 driving/walking/riding/electrobike/transit)".to_string()); + lines.push(" 🎯 amap_travel_plan → 智能旅游规划(city 城市名, interests 兴趣词数组, route_type 路线类型)".to_string()); + lines.push(" 🖼️ amap_map_link → 生成地图可视化链接".to_string()); + lines.push("".to_string()); + lines.push(" 💡 使用提示:旅游/路径规划前,先用 amap_geocode 把地名转坐标".to_string()); for spec in &amap_specs { - format_spec(&mut lines, spec); + format_spec_detail(&mut lines, spec); } } - lines.push(String::new()); - lines.push(" ▸ 旅游规划、路径规划、地点/周边搜索、地理编码 → 优先使用 amap_* 系列工具".to_string()); - lines.push(String::new()); - lines.push("## 🌤️ 天气工具 — 天气相关查询:".to_string()); + // ════════════════════════════════════════ + // 分类 2: 天气 + // ════════════════════════════════════════ + lines.push("".to_string()); + lines.push("## 🌤️ 天气 — 实时/预报/逐时".to_string()); + lines.push("".to_string()); + lines.push(" ☀️ weather_now → 实时天气(location='北京')".to_string()); + lines.push(" 📅 weather_forecast → 未来N天预报(location='上海', days=7)".to_string()); + lines.push(" 🕐 weather_hourly → 逐小时预报(location='深圳', hours=48)".to_string()); for spec in specs.iter().filter(|s| s.name.starts_with("weather_")) { - format_spec(&mut lines, spec); - } - lines.push(String::new()); - lines.push(" ▸ 实时天气 → weather_now | 每日预报 → weather_forecast | 逐时预报 → weather_hourly".to_string()); - - lines.push(String::new()); - lines.push("## 🔍 其他工具:".to_string()); - for spec in specs.iter().filter(|s| !s.name.starts_with("amap_") && !s.name.starts_with("weather_")) { - format_spec(&mut lines, spec); + format_spec_detail(&mut lines, spec); } - lines.push(String::new()); - lines.push("## ⚡ 工具选择优先级:".to_string()); - lines.push(" 1. 旅游/路线/地点/地址查询 → amap_* 系列".to_string()); - lines.push(" 2. 天气查询 → weather_now / weather_forecast / weather_hourly".to_string()); - lines.push(" 3. 联网搜索/网页抓取 → web_search / fetch_page".to_string()); - lines.push(" 4. 以上都不匹配的需求 → web_search 搜索最新信息".to_string()); - lines.push(String::new()); - lines.push("📌 注意:每次调用工具前请确认它确实是当前最合适的工具。".to_string()); + // ════════════════════════════════════════ + // 分类 3: 备忘录 + // ════════════════════════════════════════ + lines.push("".to_string()); + lines.push("## 📝 备忘录 — 个人便签管理".to_string()); + lines.push("".to_string()); + lines.push(" 📋 memos_list → 查看所有备忘录".to_string()); + lines.push(" ➕ memos_add → 添加(content='明天下午2点开会')".to_string()); + lines.push(" 🔍 memos_get → 查看详情(id=1)".to_string()); + lines.push(" 🗑️ memos_delete → 删除(id=1)".to_string()); + for spec in specs.iter().filter(|s| s.name.starts_with("memos_")) { + format_spec_detail(&mut lines, spec); + } + + // ════════════════════════════════════════ + // 分类 4: 定时任务 + // ════════════════════════════════════════ + lines.push("".to_string()); + lines.push("## ⏰ 定时任务 — 周期执行的自动化脚本".to_string()); + lines.push("".to_string()); + lines.push(" 📋 scheduled_task_list → 列出所有任务及状态(系统任务标记 [系统])".to_string()); + lines.push(" ➕ scheduled_task_add → 添加(name/command/interval 必填,如: 每天早上8点发送天气)".to_string()); + lines.push(" ✏️ scheduled_task_update → 修改字段(id 必填,只改传入的字段)".to_string()); + lines.push(" 🗑️ scheduled_task_delete → 删除(系统任务不可删)".to_string()); + lines.push(" 🔄 scheduled_task_toggle → 启用/禁用切换".to_string()); + lines.push("".to_string()); + lines.push(" 💡 使用提示:添加定时任务前先 list 查看现有任务,避免重复".to_string()); + lines.push(" ⚠️ 系统任务(标记 [系统])不可修改、删除或切换状态".to_string()); + for spec in specs.iter().filter(|s| s.name.starts_with("scheduled_")) { + format_spec_detail(&mut lines, spec); + } + + // ════════════════════════════════════════ + // 分类 5: 联网 + // ════════════════════════════════════════ + lines.push("".to_string()); + lines.push("## 🌐 联网工具 — 搜索与网页抓取".to_string()); + lines.push("".to_string()); + lines.push(" 🔍 web_search → Tavily 联网搜索(支持 AI 摘要/新闻/财经/国家过滤/时间范围)".to_string()); + lines.push(" 📄 fetch_page → 抓取网页并提取正文(自动过滤广告/导航/页脚)".to_string()); + for spec in specs.iter().filter(|s| s.name == "web_search" || s.name == "fetch_page") { + format_spec_detail(&mut lines, spec); + } + + // ════════════════════════════════════════ + // 分类 6: 基础 + // ════════════════════════════════════════ + lines.push("".to_string()); + lines.push("## 🕐 基础工具".to_string()); + lines.push("".to_string()); + lines.push(" 🕐 get_current_datetime → 获取当前北京时间(无参数)".to_string()); + for spec in specs.iter().filter(|s| s.name == "get_current_datetime") { + format_spec_detail(&mut lines, spec); + } + + lines.push("".to_string()); + lines.push("═══════════════════════════════════════════════════".to_string()); + lines.push("## ⚡ 工具选择优先级(必读)".to_string()); + lines.push("".to_string()); + lines.push(" 1. 📍 地点/路线/旅游/导航 → amap_* 系列".to_string()); + lines.push(" 2. 🌤️ 天气 → weather_now / weather_forecast / weather_hourly".to_string()); + lines.push(" 3. 📝 备忘录 → memos_* 系列".to_string()); + lines.push(" 4. ⏰ 定时任务 → scheduled_task_* 系列".to_string()); + lines.push(" 5. 🌐 最新信息/网页 → web_search / fetch_page".to_string()); + lines.push(" 6. 🕐 当前时间 → get_current_datetime".to_string()); + lines.push(" 7. 以上都不匹配 → web_search 搜索相关信息".to_string()); + lines.push("".to_string()); + lines.push("📌 组合示例:".to_string()); + lines.push(" • '北京三日游怎么安排' → amap_travel_plan(city='北京')".to_string()); + lines.push(" • '明天去杭州穿什么' → weather_forecast(location='杭州', days=1)".to_string()); + lines.push(" • '附近有什么好吃的' → amap_poi_search(keywords='美食', ...)".to_string()); + lines.push(" • '帮我记下下周二开会' → memos_add(content='下周二开会')".to_string()); + lines.push(" • '每天8点推送天气预报' → scheduled_task_add(name='天气推送', ...)".to_string()); + lines.push("".to_string()); + lines.push("📌 每次调用前确认参数格式正确,坐标格式为'经度,纬度'".to_string()); lines.join("\n") } -fn format_spec(lines: &mut Vec, spec: &SkillSpec) { +fn format_spec_detail(lines: &mut Vec, spec: &SkillSpec) { let risk = if spec.risk_level == crate::tools::types::RiskLevel::High { " ⚠️需确认" } else { "" }; lines.push(format!( - "\n🔹 {} {} — {}", - spec.name, risk, spec.description + "\n🔹 {} ({}){}", + spec.name, spec.timeout_secs, risk )); + lines.push(format!(" 描述: {}", spec.description)); if !spec .parameters .get("properties") .and_then(|v| v.as_object()) .is_none_or(|o| o.is_empty()) { - lines.push(format!( - " 参数: {}", - serde_json::to_string(&spec.parameters).unwrap_or_default() - )); + // 精简参数显示 + if let Some(props) = spec.parameters.get("properties").and_then(|v| v.as_object()) { + let required: Vec<&str> = spec + .parameters + .get("required") + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect()) + .unwrap_or_default(); + for (name, info) in props { + let desc = info.get("description").and_then(|v| v.as_str()).unwrap_or(""); + let req = if required.contains(&name.as_str()) { "*必填" } else { "可选" }; + lines.push(format!(" - {} ({}) {}", name, req, desc)); + } + } } }