- 人工智能
- 大模型
- 本地部署
- AI 应用
- 移动开发
- AI Agent
- AI 技能
- MCP Clients
【免费下载链接】gallery
A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.
read-calendar-events 是 Edge Gallery(Google 开源的端侧 ML/GenAI 演示应用)内置的一项 Agent 技能,它让本地运行的 LLM 能够通过run_intent工具读取操作系统日历中指定日期的日程。本文以该技能随仓库发布的SKILL.md为核心骨架,结合 IntentHandler、RunIntentTool、SkillManager 等源码,完整拆解技能从 SKILL.md 解析、指令注入、工具注册到日历查询与 JSON 返回的全链路实现,并给出可直接复用的参数格式与调用流程。
技能文件定位:一个极简但完整的 Agent 指令单元
在仓库中,该技能位于 Android/src/app/src/main/assets/skills/read-calendar-events/SKILL.md。文件本身非常短小,却包含了 Edge Gallery 技能体系的两个核心组成部分:YAML 风格的 frontmatter 元数据与自由文本的 Instructions 指令区。
--- name: read-calendar-events description: Read OS calendar events for a specific date. --- # Read calendar events ## Instructions To read calendar events for a specific date, you must follow these exact steps: ...其中:
name是技能的唯一标识,也是模型在意图识别时用于匹配技能的名称(如read-calendar-events);description用于向模型描述该技能的能力边界,帮助模型在多种技能中选择正确的那一个;## Instructions之后的正文则是注入到模型上下文的完整操作指令,指导模型以固定步骤完成"读取某日日历事件"这一任务。
这个三段式结构并不是随意的约定。在 SkillManager.kt 的convertSkillMdToProto中,解析器以---分隔符切分文件,第一段被解析为 header(提取name、description等键),其余内容作为instructions文本,最终转换为 skill.proto 中定义的Skill消息(包含name、description、instructions、built_in、selected等字段)。技能目录被放在assets/skills/下,因此被标记为 built-in 技能;而通过 URL 添加或本地导入的技能则分别携带skill_url或import_dir_name字段。
当模型需要该技能时,由 LoadSkillTool.kt 调用skillsProvider.loadSkill(skillName)取回技能内容,并通过 SkillExtensions.kt 中的模板SKILL_INSTRUCTIONS_TEMPLATE重新拼装为---\nname: %s\ndescription: %s\n---\n\n%s的形式,连同技能描述一起注入模型提示词(prompt),再交由 AgentRuntimeExecutor.kt 驱动的 ReAct 循环执行。
run_intent:技能指令与系统能力之间的桥梁
SKILL.md中反复出现的run_intent并不是日历功能专属接口,而是 Edge Gallery 提供给端侧 Agent 的通用 Android Intent 执行工具。其实现位于 RunIntentTool.kt,核心方法签名如下:
run_intent(intent: String, parameters: String) -> Map<String, String>intent:要执行的 Intent 动作名(字符串);parameters:包含该 Intent 所需参数值的 JSON 字符串。
调用成功后,工具返回一个包含三个键的 Map:action(回显的 intent 名)、parameters(回显的参数字符串)与result(IntentHandler 的执行结果字符串)。若传入的 intent 名不在已知 IntentAction 列表中,RunIntentTool会走guardMissingEntityWithSkillFallback兜底逻辑:若该名称恰好命中某个已启用技能,则返回提示"该实体不存在,请尝试作为技能运行",否则返回Tool not found。
该工具在 AgentTools.kt 的AgentToolsImpl中通过lazy方式注册为RunIntentTool(context, skillsProvider),与load_skill、run_js、run_mcp等工具一起作为 Agent 的可用工具集暴露给模型。同时,工具执行过程中会通过 ToolAction.kt 定义的SkillProgressToolAction向聊天界面推送"Executing intent ..."进度项,使用户在 UI 上可见每一步工具调用。
第一步:先取当前本地日期时间
SKILL.md规定,读取日历事件前必须先调用run_intent获取用户本地时间,而不能直接假设"今天":
intent: get_current_date_and_time parameters: {}该动作在 IntentHandler.kt 中的实现非常直接:使用SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss EEEE", Locale.getDefault())格式化当前时间并原样返回。也就是说,返回字符串同时携带了:
yyyy-MM-dd:本地日期;HH:mm:ss:本地时间;EEEE:本地时区的星期几(如 Friday)。
由于格式化基于Locale.getDefault(),星期名称会随系统语言变化,这要求模型在后续日期推算时既能解析"May 15"这类绝对日期,也能解析"tomorrow""this Friday"这类相对表达。
第二步:显式推算目标日期(YYYY-MM-DD)
SKILL.md特别强调,在读取事件之前,模型必须在自己的回复中显式写出日期推算过程,包括:
- 今天的确切日期与星期几;
- 用户请求的目标日或相对时间(如 "tomorrow"、"this Friday"、"May 15");
- 最终计算得到的目标日期,格式为
YYYY-MM-DD。
这种"把中间计算过程写进回复"的做法,是引导端侧小模型降低日期推算错误率的关键设计——它要求模型把隐性推理外化为可校验的显式步骤,同时让用户能在界面上看到模型的推算依据。推算时应特别留意跨月、跨年场景(例如 1 月 31 日加 1 天应为 2 月 1 日),这与同目录下 schedule-notification/SKILL.md 中对"added days 超过当月天数时要正确滚动到下月/下一年"的约束是一致的。
第三步:调用 read_calendar_events 读取指定日期
日期确定后,按SKILL.md的固定参数调用:
intent: read_calendar_events parameters: {"date": "YYYY-MM-DD"}date是唯一的必填字段,类型为字符串,取值就是第二步推算出的目标日期。参数由 Moshi 解析为 IntentHandler.kt 中定义的ReadCalendarEventsParams(date: String)。
权限检查:READ_CALENDAR
readCalendarEvents在查询前会先检查Manifest.permission.READ_CALENDAR是否已授予(该权限已在 AndroidManifest.xml 中声明)。若未授予,则通过RunIntentTool传入的requestPermission回调发起授权请求——这个回调最终会转换为 ToolAction.kt 中的RequestPermissionToolAction,在 Agent 聊天界面弹出系统权限对话框,用户同意后返回true继续执行,拒绝则直接返回"failed: READ_CALENDAR permission denied by user"。
底层查询:CalendarContract.Instances 的当天时间窗
拿到权限后,源码使用 Android 标准的CalendarContract.Instances进行查询,逻辑分为四步:
- 用
SimpleDateFormat("yyyy-MM-dd")解析传入的date,得到该日零点对应的Date; - 构造当天的完整时间窗:先把小时/分钟/秒/毫秒全部清零得到
startOfDayMillis,再add(DAY_OF_MONTH, 1)并减 1 毫秒得到endOfDayMillis(即当日 23:59:59.999); - 通过
Instances.CONTENT_URI.buildUpon()配合ContentUris.appendId把起止时间拼进 URI,构成按实例区间查询; - 以
projection = {Instances.TITLE, Instances.DESCRIPTION, Instances.BEGIN, Instances.END}查询,并按${Instances.BEGIN} ASC升序排序。
这意味着"读取某日事件"实际覆盖的是该日零点到次日零点前最后一毫秒的所有日历实例,既包含整天事件也包含跨日事件的当日片段。
第四步:解析 JSON 结果并组织回答
查询得到的光标被逐行转换为CalendarEventDto(title、description、begin_time、end_time),其中时间字段统一格式化为yyyy-MM-dd'T'HH:mm:ss,最终序列化为ReadCalendarEventsResponse的 JSON:
{ "events": [ { "title": "Team standup", "description": "Weekly sync", "begin_time": "2026-09-30T10:00:00", "end_time": "2026-09-30T10:30:00" } ] }SKILL.md的最后一步要求模型解释这份 JSON 列表并给出清晰、友好的回答,即把结构化的时间/标题数据转述为自然语言日程摘要。若当天没有任何事件,返回的events为空数组[],模型应如实告知用户"当天没有安排"。若解析失败(如日期格式非法),工具返回"failed"或"failed: <错误信息>"。
与周边 Intent 的联动:一个完整的日程操作家族
read_calendar_events只是 IntentHandler.kt 中IntentAction枚举的一员。从枚举定义可以推断,同一套run_intent机制还支撑着另外五个动作:
| intent 动作 | 用途 |
|---|---|
get_current_date_and_time | 获取本地日期、时间与星期 |
read_calendar_events | 读取指定日期日历事件 |
create_calendar_event | 调起系统日历创建事件(title、description、begin_time、end_time) |
schedule_notification | 调度一条通知(支持指定年月日与repeat_daily每日重复) |
send_email | 调起系统邮件应用 |
send_sms | 调起系统短信应用 |
这些动作的 Skill 指令文档同样位于 assets/skills/ 下,例如 create-calendar-event/SKILL.md 与 schedule-notification/SKILL.md。其中schedule_notification的参数(title、message、hour、minute、可选的year/month/day、deeplink与repeat_daily)由ScheduleNotificationParams承载,并经 NotificationScheduleManager.kt 通过AlarmManager.setRepeating/setAndAllowWhileIdle落地到系统闹钟,由 NotificationReceiver.kt 在触发时弹出通知,并可携带构造出的com.google.ai.edge.gallery://...deeplink 在点击后回跳 Agent 聊天页面。由此可见,"读日历"与"建日程""定时提醒"共同构成了一个完整的日程管理技能矩阵,全部复用run_intent这一条工具通道。
从源码结构看技能体系的扩展方式
从 SkillManager.kt 的实现可以推断,Edge Gallery 的技能体系是开放的:除了assets/skills/下的 built-in 技能外,还支持从远程 URL(addSkillFromUrl,自动去掉末尾/SKILL.md或/后拼接下载)、本地目录导入(addSkillFromLocalImport,把目录递归拷贝进应用私有 files 目录并设置import_dir_name)以及用户在界面中直接编辑(saveSkillEdit,规范化空格为连字符并写入skills/<name>/SKILL.md)。任何满足 frontmatter + instructions 格式的SKILL.md都可以成为 Agent 的一项新能力,前提是它所依赖的动作(Intent、JS 脚本或 MCP 工具)已存在。技能启用状态由selected字段与模型级defaultDisabledSkills共同管理,用户手动修改后会标记user_modified_selection以保留用户偏好。
小结
read-calendar-events是一个把"端侧 LLM 意图理解"与"Android 系统日历能力"无缝衔接的最小范例:模型通过run_intent工具先后调用get_current_date_and_time与read_calendar_events,前者提供日期推算所需的本地基准时间,后者在READ_CALENDAR权限授权后利用CalendarContract.Instances完成当天时间窗内的事件查询,并以 JSON 形式返回结构化日程;而驱动这一切的,正是仓库中以SKILL.md为载体的可插拔技能指令体系。理解这条链路,也就掌握了 Edge Gallery 中所有基于 Intent 的系统操作类技能(建日程、发邮件、定时提醒)的共同运行原理。
- 人工智能
- 大模型
- 本地部署
- AI 应用
- 移动开发
- AI Agent
- AI 技能
- MCP Clients
【免费下载链接】gallery
A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally.
相关推荐
Gallery Agent 技能开发指南:基于 run_intent 工具实现 create-calendar-event 日历事件创建
Gallery Agent 技能开发指南:基于 run_intent 工具实现 create calendar event 日历事件创建 导读 本文以 Gall
人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP ClientsAI Edge Gallery QR Code 技能深度解析:基于 run_js 的端侧二维码生成实现
AI Edge Gallery QR Code 技能深度解析:基于 run_js 的端侧二维码生成实现 导读 本文以 AI Edge Gallery(Andro
人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP ClientsAI Edge Gallery 端侧邮件发送技能实战:从 send-email 的 SKILL.md 到 run_intent 原生意图的实现链路
AI Edge Gallery 端侧邮件发送技能实战:从 send email 的 SKILL.md 到 run_intent 原生意图的实现链路 导读 sen
人工智能大模型本地部署AI 应用移动开发AI AgentAI 技能MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考