一、实验目的
(一)知识目标
- 理解AI Agent的基本概念、核心特征与工作原理。
- 掌握ReAct(Reasoning + Acting)模式的工作流程与适用场景。
- 理解Function Calling机制的设计原理与安全优势。
- 掌握鸿蒙Calendar Kit日历服务API与Notification Kit通知服务API的使用方法。
(二)能力目标
- 能够设计并实现基于Function Calling的Agent工具函数体系。
- 能够实现多轮对话管理器,支持上下文维护与指代消解。
- 能够将云端大模型能力与鸿蒙系统能力(日历、通知)融合,构建完整的智能应用。
- 能够对Agent应用进行功能测试与性能优化。
(三)素养目标
- 培养"以用户为中心"的AI应用设计思维,关注交互体验与隐私安全。
- 理解端云协同架构中数据最小化原则的工程价值。
- 建立AI Agent在智能办公场景中的应用视野。
二、实验环境与准备
2.1 开发环境
2.2 知识准备
完成本实验前,请确认已掌握以下知识:
1.ArkTS基本语法:接口(interface)、枚举(enum)、泛型、异步编程(async/await)。
2.ArkUI声明式开发:组件定义(@Component)、状态管理(@State、@Prop、@Link)。
3.HTTP网络请求:使用@kit.NetworkKit的http模块发送POST请求。
4.第5章内容:云端大模型API调用方法、请求参数格式、响应解析。
5.第10章理论内容:Agent概念、ReAct模式、Function Calling机制、意图理解与实体抽取。
2.3 项目创建
请按以下步骤创建项目:
6.打开DevEco Studio,选择"Create Project"。
7.选择"Empty Ability"模板,点击Next。
8.项目配置:Project Name填入"SmartScheduleAgent",Bundle Name填入"com.example.smartschedule",Compile SDK选择API 12。
9.点击Finish完成项目创建。
【注意】请确保在module.json5中声明INTERNET权限,否则无法调用云端大模型API。
三、实验前思考题
请在进行实验前思考以下问题,并在实验报告中给出你的理解:
- 在传统的日程管理应用中,用户需要手动填写标题、时间、地点等表单字段。使用AI Agent方式后,用户只需用自然语言说"下周三下午3点开项目评审会"即可自动创建日程。请思考:这种交互方式相比传统表单有哪些优势?又可能带来哪些新的挑战?
- Function Calling机制中,大模型只返回工具调用指令(函数名+参数),而由应用程序负责实际执行。请思考:为什么不直接让大模型执行函数?这种"决策与执行分离"的设计在安全性和隐私保护方面有什么好处?
- 在多轮对话场景中,用户可能说"把那个会议改到后天"。Agent需要知道"那个会议"指的是哪条日程。请思考:Agent可以通过哪些信息来确定指代对象?上下文管理需要维护哪些状态?
四、实验任务与步骤
本实验共10个任务,按照从底层到上层、从数据到界面的顺序逐步构建智能日程Agent应用。
任务1 项目结构与类型定义
【任务目标】搭建项目工程结构,定义全局类型和数据模型。
步骤1.1 创建目录结构
在项目ets目录下创建以下子目录:
SmartScheduleAgent/ ├── entry/ │ └── src/main/ets/ │ ├── entryability/ │ │ └── EntryAbility.ets # Ability入口 │ ├── model/ │ │ └── Types.ets # 全局类型定义 │ ├── service/ │ │ ├── CalendarManager.ets # 日历管理服务 │ │ ├── ReminderService.ets # 提醒通知服务 │ │ ├── DialogManager.ets # 对话管理器 │ │ └── AgentService.ets # Agent核心服务 │ ├── pages/ │ │ ├── ChatPage.ets # 对话界面 │ │ ├── ScheduleListPage.ets # 日程列表界面 │ │ └── IndexPage.ets # 入口页面(Tab) │ └── common/ │ └── Constants.ets # 常量定义 └── entry/src/main/resources/ └── base/profile/ └── main_pages.json # 页面路由配置步骤1.2 定义类型模型
在model/Types.ets中定义以下类型。请根据提示完成代码:
// model/Types.ets // 意图类型枚举 export enum IntentType { CREATE = 'create', QUERY = 'query', UPDATE = 'update', DELETE = 'delete', CHAT = 'chat' } // 日程事件接口 export interface ScheduleEvent { id: string; title: string; startTime: string; // ISO格式时间字符串 endTime: string; location?: string; reminderMinutes?: number; // 提前提醒分钟数 isAllDay?: boolean; } // 对话消息接口 export interface ChatMessage { role: 'user' | 'assistant' | 'tool'; content: string; timestamp: number; toolCallId?: string; } // 工具调用接口 export interface ToolCall { name: string; arguments: Record<string, string | number | undefined>; } // 对话状态接口 export interface DialogState { messages: ChatMessage[]; // 对话历史 pendingAction: string | null; // 待确认操作 contextScheduleId: string | null; // 上下文日程ID lastIntent: IntentType | null; // 上一轮意图 } // Agent响应接口 export interface AgentResponse { success: boolean; message: string; data?: Record<string, Object>; }【提示】Types.ets是全局类型定义文件,所有其他文件通过import引用这些类型。良好的类型定义是TypeScript/ArkTS工程的基础。
步骤1.3 定义常量
在common/Constants.ets中定义API地址等常量:
// common/Constants.ets export const LLM_API_URL = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation'; export const LLM_MODEL = 'qwen-plus'; export const API_KEY = 'your_api_key'; // 请替换为你的实际API Key export const MAX_HISTORY_LENGTH = 10; // 对话历史最大轮数 export const DEFAULT_REMINDER_MINUTES = 15; // 默认提前提醒时间【注意】API Key属于敏感信息,请勿将真实Key提交到版本控制系统。实际部署时应使用安全存储机制,参见第14章。
任务2 日历管理服务
【任务目标】封装Calendar Kit API,实现日程的创建、查询、修改、删除和冲突检测。
步骤2.1 导入Calendar Kit
在service/CalendarManager.ets中导入日历服务模块:
// service/CalendarManager.ets import { calendarMgr } from '@kit.CalendarKit'; import { ScheduleEvent } from '../model/Types';步骤2.2 实现日程创建方法
创建日程需要调用calendarMgr.addEvent方法。请完成以下代码框架:
export class CalendarManager { private calendar: calendarMgr.Calendar | null = null; // 初始化日历 async init(): Promise<void> { // TODO: 获取默认日历实例 // 提示:使用 calendarMgr.getCalendar() 获取日历对象 } // 创建日程 async createSchedule(params: { title: string; startTime: Date; endTime: Date; location?: string; reminderMinutes?: number; }): Promise<string> { // TODO: 构造Event对象并调用addEvent // 提示: // 1. 构造 calendarMgr.Event 对象 // 2. 设置 title, startTime, endTime, location // 3. 设置 reminderTime 为 reminderMinutes // 4. 调用 this.calendar.addEvent(event) 获取eventId // 5. 返回 eventId } }【提示】calendarMgr.Event对象的核心字段:title(标题)、startTime(开始时间Date)、endTime(结束时间Date)、location(地点)、reminderTime(提前提醒分钟数)。
步骤2.3 实现日程查询方法
查询日程使用getEvents方法,支持按时间范围查询:
// 查询日程 async querySchedules( startDate: Date, endDate: Date ): Promise<ScheduleEvent[]> { // TODO: 按时间范围查询日程 // 提示: // 1. 调用 this.calendar.getEvents({ start: startDate, end: endDate }) // 2. 将返回的Event数组转换为ScheduleEvent数组 // 3. 按startTime排序 }步骤2.4 实现修改与删除方法
请参考创建方法的模式,实现以下两个方法:
// 修改日程 async updateSchedule( scheduleId: string, updates: Partial<ScheduleEvent> ): Promise<boolean> { // TODO: 根据scheduleId获取日程,更新字段后保存 // 提示:使用 calendar.getEvent(id) 获取,修改后 updateEvent } // 删除日程 async deleteSchedule(scheduleId: string): Promise<boolean> { // TODO: 根据scheduleId删除日程 // 提示:使用 calendar.deleteEvent(id) }步骤2.5 实现冲突检测
冲突检测是日程管理的核心逻辑,用于判断新日程是否与已有日程时间重叠:
// 检测时间冲突 async checkConflict( startTime: Date, endTime: Date ): Promise<{ hasConflict: boolean; conflictTitle?: string }> { // TODO: 查询该时间段内是否已有日程 // 提示: // 1. 查询 startTime 到 endTime 范围内的日程 // 2. 如果有日程,返回 hasConflict=true 和 conflictTitle // 3. 时间重叠判断:两个时间段 [s1,e1) 和 [s2,e2) 重叠的条件是 s1 < e2 && s2 < e1 }【注意】时间重叠判断是本任务的核心算法。两个时间段[A_start, A_end)和[B_start, B_end)重叠的条件是:A_start < B_end 且 B_start < A_end。请仔细验证这个逻辑。
任务3 提醒通知服务
【任务目标】封装Notification Kit,实现日程提醒通知功能。
步骤3.1 导入通知模块
// service/ReminderService.ets import { notificationManager } from '@kit.NotificationKit'; import { ScheduleEvent } from '../model/Types';步骤3.2 实现即时通知
当用户创建日程后,可以立即发送一条通知告知用户:
export class ReminderService { // 发送即时通知 async publishNotification( title: string, content: string ): Promise<void> { // TODO: 构造NotificationContent并发布 // 提示: // 1. 构造 notificationManager.NotificationContent 对象 // 2. 设置 title 和 text // 3. 构造 notificationManager.NotificationRequest // 4. 调用 notificationManager.publish(request) } }步骤3.3 实现定时提醒
定时提醒需要在日程开始前指定时间发送通知。请实现以下方法:
// 设置定时提醒 async setReminder( schedule: ScheduleEvent, minutesBefore: number = 15 ): Promise<number> { // TODO: 计算提醒时间并设置定时通知 // 提示: // 1. 将schedule.startTime转为Date对象 // 2. 计算提醒时间 = startTime - minutesBefore分钟 // 3. 如果提醒时间已过,直接发送即时通知 // 4. 否则使用 notificationManager.publishScheduledNotification 设置定时通知 // 5. 返回通知ID用于后续取消 } // 取消提醒 async cancelReminder(notificationId: number): Promise<void> { // TODO: 调用 notificationManager.cancel(notificationId) }【提示】notificationManager.publishScheduledNotification需要传入触发时间戳(毫秒)。使用Date.getTime()获取时间戳。
任务4 对话管理器
【任务目标】实现多轮对话管理器,维护对话历史、上下文状态和待确认操作。
步骤4.1 定义对话管理器类
// service/DialogManager.ets import { ChatMessage, DialogState, IntentType } from '../model/Types'; import { MAX_HISTORY_LENGTH } from '../common/Constants'; export class DialogManager { private state: DialogState = { messages: [], pendingAction: null, contextScheduleId: null, lastIntent: null }; // 添加用户消息 addUserMessage(content: string): void { // TODO: 将用户消息添加到messages数组 // 提示:构造ChatMessage对象,role='user' // 注意:如果消息数超过MAX_HISTORY_LENGTH,移除最早的消息 } // 添加助手消息 addAssistantMessage(content: string): void { // TODO: 将助手消息添加到messages数组 // 提示:构造ChatMessage对象,role='assistant' } }步骤4.2 实现上下文管理
上下文管理是指代消解和多轮交互的关键。请实现以下方法:
// 设置上下文日程ID setContextScheduleId(id: string | null): void { // TODO: 更新state.contextScheduleId } // 设置待确认操作 setPendingAction(action: string | null): void { // TODO: 更新state.pendingAction } // 设置上一轮意图 setLastIntent(intent: IntentType | null): void { // TODO: 更新state.lastIntent } // 获取上下文日程ID getContextScheduleId(): string | null { // TODO: 返回state.contextScheduleId }步骤4.3 构建系统提示词
系统提示词需要包含上下文信息,帮助大模型理解指代关系:
// 获取系统提示词 getSystemPrompt(): string { // TODO: 构建包含上下文信息的系统提示词 // 提示: // 1. 基础提示词:你是一个智能日程助手,可以帮助用户创建、查询、修改和删除日程。 // 2. 如果有contextScheduleId,添加:当前上下文关联的日程ID为xxx // 3. 如果有pendingAction,添加:待确认操作为xxx // 4. 添加指代消解指引:当用户使用指代词时,使用上下文中的日程ID }步骤4.4 获取对话历史
对话历史需要格式化为大模型能理解的格式:
// 获取对话历史(供大模型使用) getHistoryForLLM(): Array<{ role: string; content: string }> { // TODO: 将messages数组转换为大模型API所需的格式 // 提示:将ChatMessage数组映射为 {role, content} 数组 // 注意:tool角色的消息可能需要特殊处理 }【提示】对话历史长度限制(MAX_HISTORY_LENGTH=10)是为了控制Token消耗。每轮对话都会增加Token数量,过长的历史会导致API调用变慢且费用增加。
任务5 Agent核心服务
【任务目标】实现AgentService,集成云端大模型Function Calling能力,完成意图理解、工具调用和结果生成的完整ReAct流程。
步骤5.1 定义工具Schema
Function Calling的核心是工具函数的定义。大模型根据这些定义决定调用哪个工具:
// service/AgentService.ets import { http } from '@kit.NetworkKit'; import { ToolCall, AgentResponse, ScheduleEvent } from '../model/Types'; import { LLM_API_URL, LLM_MODEL, API_KEY } from '../common/Constants'; import { CalendarManager } from './CalendarManager'; import { ReminderService } from './ReminderService'; import { DialogManager } from './DialogManager'; export class AgentService { private calendarManager: CalendarManager = new CalendarManager(); private reminderService: ReminderService = new ReminderService(); private dialogManager: DialogManager = new DialogManager(); // 工具函数定义 private static readonly TOOLS = [ { name: 'createSchedule', description: '创建一条新日程。当用户想要新建、安排或设置日程时调用此工具。', parameters: { type: 'object', properties: { title: { type: 'string', description: '日程标题' }, startTime: { type: 'string', description: '开始时间,格式YYYY-MM-DD HH:mm' }, endTime: { type: 'string', description: '结束时间,格式YYYY-MM-DD HH:mm' }, location: { type: 'string', description: '地点(可选)' } }, required: ['title', 'startTime'] } }, { name: 'querySchedule', description: '查询日程。当用户想要查看、搜索或列出日程时调用此工具。', parameters: { type: 'object', properties: { startDate: { type: 'string', description: '查询开始日期' }, endDate: { type: 'string', description: '查询结束日期' } }, required: ['startDate'] } }, { name: 'updateSchedule', description: '修改已有日程。当用户想要修改、调整或变更日程时调用此工具。', parameters: { type: 'object', properties: { scheduleId: { type: 'string', description: '日程ID' }, updates: { type: 'object', description: '修改的字段' } }, required: ['scheduleId', 'updates'] } }, { name: 'deleteSchedule', description: '删除日程。当用户想要取消或删除日程时调用此工具。', parameters: { type: 'object', properties: { scheduleId: { type: 'string', description: '日程ID' } }, required: ['scheduleId'] } } ]; }【注意】工具的description字段非常重要,大模型根据描述判断是否调用该工具。描述应清晰说明工具的用途和调用时机。
步骤5.2 实现callLLM方法
callLLM方法将用户输入、对话历史和工具定义发送给云端大模型:
// 调用大模型(带Function Calling) private async callLLM(input: string): Promise<string> { // TODO: 构造API请求并发送 // 提示: // 1. 构造请求体: // - model: LLM_MODEL // - messages: [system_prompt, ...history, user_input] // - tools: AgentService.TOOLS (转换为API要求的格式) // - tool_choice: 'auto' (让模型自动选择) // 2. 设置Header: Authorization: Bearer API_KEY // 3. 发送POST请求到LLM_API_URL // 4. 返回响应文本 }【提示】qwen-plus的Function Calling API格式:在请求体中添加tools参数(工具定义数组)和tool_choice参数(设为"auto"表示自动选择工具)。响应中如果模型决定调用工具,会包含tool_calls字段。
步骤5.3 实现parseLLMResponse方法
解析大模型响应,判断是返回文本回复还是工具调用:
// 解析大模型响应 private parseLLMResponse(response: string): ToolCall | null { // TODO: 解析响应JSON,判断是否包含工具调用 // 提示: // 1. 解析JSON响应 // 2. 检查是否有 tool_calls 字段 // 3. 如果有,提取第一个tool_call的name和arguments // 4. 如果没有,返回null(表示模型直接回复文本) }步骤5.4 实现executeTool方法
executeTool是工具执行分发器,根据工具名调用对应的方法:
// 执行工具调用 private async executeTool(toolCall: ToolCall): Promise<AgentResponse> { // TODO: 根据toolCall.name分发到对应的工具方法 // 提示:使用switch语句 // case 'createSchedule': return this.toolCreateSchedule(toolCall.arguments) // case 'querySchedule': return this.toolQuerySchedule(toolCall.arguments) // case 'updateSchedule': return this.toolUpdateSchedule(toolCall.arguments) // case 'deleteSchedule': return this.toolDeleteSchedule(toolCall.arguments) // default: 返回错误信息 }步骤5.5 实现各工具方法
请实现4个工具方法,每个方法调用CalendarManager完成实际操作:
// 工具:创建日程 private async toolCreateSchedule(args: Record<string, string>): Promise<AgentResponse> { // TODO: // 1. 解析参数:title, startTime, endTime, location // 2. 转换时间为Date对象 // 3. 调用 calendarManager.createSchedule() // 4. 设置提醒:reminderService.setReminder() // 5. 更新上下文:dialogManager.setContextScheduleId() // 6. 返回成功消息 } // 工具:查询日程 private async toolQuerySchedule(args: Record<string, string>): Promise<AgentResponse> { // TODO: // 1. 解析参数:startDate, endDate // 2. 调用 calendarManager.querySchedules() // 3. 格式化查询结果为文本摘要 // 4. 返回查询结果 } // 工具:修改日程 private async toolUpdateSchedule(args: Record<string, string>): Promise<AgentResponse> { // TODO: // 1. 解析参数:scheduleId, updates // 2. 调用 calendarManager.updateSchedule() // 3. 返回修改结果 } // 工具:删除日程 private async toolDeleteSchedule(args: Record<string, string>): Promise<AgentResponse> { // TODO: // 1. 解析参数:scheduleId // 2. 调用 calendarManager.deleteSchedule() // 3. 清除上下文:dialogManager.setContextScheduleId(null) // 4. 返回删除结果 }步骤5.6 实现handleUserInput主方法
handleUserInput是Agent的入口方法,串联完整的ReAct流程:
// 处理用户输入(主入口) async handleUserInput(input: string): Promise<AgentResponse> { // TODO: 实现完整的ReAct流程 // 提示: // 1. 将用户输入添加到对话历史:dialogManager.addUserMessage(input) // 2. 调用大模型:const response = await this.callLLM(input) // 3. 解析响应:const toolCall = this.parseLLMResponse(response) // 4. 如果有工具调用: // a. 执行工具:const result = await this.executeTool(toolCall) // b. 将工具结果反馈给大模型:await this.callLLMWithToolResults(input, toolCall, result) // c. 大模型基于工具结果生成最终回复 // 5. 如果没有工具调用,直接返回大模型的文本回复 // 6. 将助手回复添加到对话历史:dialogManager.addAssistantMessage(message) }【注意】步骤5.6是整个Agent的核心流程。如果工具执行后大模型需要再次生成回复,需要实现callLLMWithToolResults方法,将工具执行结果作为tool角色的消息发送给大模型。
任务6 对话界面
【任务目标】实现ChatPage对话界面,支持消息气泡展示、文本输入和发送。
步骤6.1 定义页面组件
// pages/ChatPage.ets import { AgentService } from '../service/AgentService'; import { ChatMessage } from '../model/Types'; @Entry @Component struct ChatPage { @State messages: ChatMessage[] = []; @State inputText: string = ''; @State isLoading: boolean = false; private agentService: AgentService = new AgentService(); build() { Column() { // 消息列表区域 List({ space: 12 }) { ForEach(this.messages, (msg: ChatMessage) => { // TODO: 根据msg.role渲染不同样式的消息气泡 }) } .layoutWeight(1) .padding(12) // 输入栏区域 // TODO: 实现输入框和发送按钮 } } }步骤6.2 实现消息气泡
用户消息和助手消息使用不同的样式(背景色、对齐方式):
// 消息气泡组件 @Builder MessageBubble(msg: ChatMessage) { Row() { if (msg.role === 'user') { // 用户消息:右对齐,蓝色背景 Column() { Text(msg.content) .fontSize(14) .fontColor('#FFFFFF') .padding(10) } .backgroundColor('#0070C0') .borderRadius(12) .constraintSize({ maxWidth: '75%' }) .margin({ left: 80 }) } else { // 助手消息:左对齐,灰色背景 Column() { Text(msg.content) .fontSize(14) .fontColor('#333333') .padding(10) } .backgroundColor('#F0F0F0') .borderRadius(12) .constraintSize({ maxWidth: '75%' }) .margin({ right: 80 }) } } .width('100%') }步骤6.3 实现输入栏与发送逻辑
@Builder InputBar() { Row({ space: 8 }) { TextInput({ text: this.inputText, placeholder: '输入消息...' }) .layoutWeight(1) .height(40) .onChange((value: string) => { this.inputText = value; }) Button('发送') .height(40) .enabled(this.inputText.length > 0 && !this.isLoading) .onClick(() => { this.sendMessage(); }) } .padding(8) } private async sendMessage(): Promise<void> { if (this.inputText.length === 0 || this.isLoading) return; const userInput = this.inputText; this.inputText = ''; // 添加用户消息到界面 this.messages.push({ role: 'user', content: userInput, timestamp: Date.now() }); // 显示加载状态 this.isLoading = true; try { // 调用Agent处理 const response = await this.agentService.handleUserInput(userInput); // 添加助手回复到界面 this.messages.push({ role: 'assistant', content: response.message, timestamp: Date.now() }); } catch (e) { this.messages.push({ role: 'assistant', content: '处理失败:' + e.message, timestamp: Date.now() }); } finally { this.isLoading = false; } }【提示】使用isLoading状态控制加载指示器的显示。在等待大模型响应时,可以显示"正在思考…"的加载动画。
任务7 日程列表界面
【任务目标】实现ScheduleListPage日程列表界面,展示已有日程并支持查看详情。
步骤7.1 定义列表页面
// pages/ScheduleListPage.ets import { CalendarManager } from '../service/CalendarManager'; import { ScheduleEvent } from '../model/Types'; @Entry @Component struct ScheduleListPage { @State schedules: ScheduleEvent[] = []; @State selectedDate: string = ''; private calendarManager: CalendarManager = new CalendarManager(); aboutToAppear(): void { this.loadSchedules(); } private async loadSchedules(): Promise<void> { // TODO: 加载今日及未来7天的日程 // 提示: // 1. 计算今天和7天后的日期 // 2. 调用 calendarManager.querySchedules() // 3. 更新schedules状态 } build() { Column() { // 标题栏 Text('我的日程') .fontSize(20).fontWeight(FontWeight.Bold) .padding(16) // 日程列表 List({ space: 8 }) { ForEach(this.schedules, (schedule: ScheduleEvent) => { // TODO: 渲染日程卡片 }) } .layoutWeight(1) .padding(12) } } }步骤7.2 实现日程卡片
@Builder ScheduleCard(schedule: ScheduleEvent) { Row({ space: 12 }) { // 时间列 Column() { Text(this.formatTime(schedule.startTime)) .fontSize(16).fontWeight(FontWeight.Bold) Text(this.formatTime(schedule.endTime)) .fontSize(12).fontColor('#999999') } .width(60) .alignItems(HorizontalAlign.Center) // 分隔线 Divider() .vertical(true) .height(40) // 内容列 Column() { Text(schedule.title) .fontSize(15).fontWeight(FontWeight.Medium) if (schedule.location) { Text(schedule.location) .fontSize(12).fontColor('#666666') .margin({ top: 4 }) } } .layoutWeight(1) .alignItems(HorizontalAlign.Start) // 提醒图标 if (schedule.reminderMinutes && schedule.reminderMinutes > 0) { Image($r('app.media.ic_reminder')) .width(20).height(20) } } .padding(12) .backgroundColor('#FFFFFF') .borderRadius(8) .shadow({ radius: 2, color: '#10000000' }) } private formatTime(timeStr: string): string { // TODO: 将ISO时间字符串格式化为HH:mm const date = new Date(timeStr); const h = date.getHours().toString().padStart(2, '0'); const m = date.getMinutes().toString().padStart(2, '0'); return h + ':' + m; }任务8 入口页面与配置
【任务目标】实现IndexPage入口页面(Tab布局)、EntryAbility和module.json5权限配置。
步骤8.1 实现Tab入口页面
// pages/IndexPage.ets import { ChatPage } from './ChatPage'; import { ScheduleListPage } from './ScheduleListPage'; @Entry @Component struct IndexPage { @State currentIndex: number = 0; build() { Column() { TabBar({ currentIndex: this.currentIndex, onTabClick: (index: number) => { this.currentIndex = index; } }) if (this.currentIndex === 0) { ChatPage() } else { ScheduleListPage() } } } } @Component struct TabBar { @Prop currentIndex: number; onTabClick: (index: number) => void; build() { Row() { Button('对话') .layoutWeight(1) .backgroundColor(this.currentIndex === 0 ? '#0070C0' : '#F0F0F0') .fontColor(this.currentIndex === 0 ? '#FFFFFF' : '#333333') .onClick(() => this.onTabClick(0)) Button('日程') .layoutWeight(1) .backgroundColor(this.currentIndex === 1 ? '#0070C0' : '#F0F0F0') .fontColor(this.currentIndex === 1 ? '#FFFFFF' : '#333333') .onClick(() => this.onTabClick(1)) } .height(50) } }步骤8.2 配置module.json5权限
在module.json5中声明所需权限:
// module.json5 { "module": { "name": "entry", "type": "entry", "srcEntry": "./ets/entryability/EntryAbility.ets", "requestPermissions": [ { "name": "ohos.permission.INTERNET", "reason": "调用云端大模型API", "usedScene": {} }, { "name": "ohos.permission.READ_CALENDAR", "reason": "读取日程信息", "usedScene": {} }, { "name": "ohos.permission.WRITE_CALENDAR", "reason": "创建和修改日程", "usedScene": {} }, { "name": "ohos.permission.NOTIFICATION_CONTROLLER", "reason": "发送日程提醒通知", "usedScene": {} } ] } }步骤8.3 配置页面路由
// resources/base/profile/main_pages.json
{ "src": [ "pages/IndexPage", "pages/ChatPage", "pages/ScheduleListPage" ] }【注意】READ_CALENDAR和WRITE_CALENDAR属于用户授权权限,需要在运行时动态申请。请在EntryAbility的onWindowStageCreate中添加权限申请逻辑。
任务9 功能测试
【任务目标】对完成的智能日程Agent应用进行全面功能测试,验证各功能正常工作。
9.1 测试用例
请按以下测试用例逐一验证应用功能:
9.2 测试方法
1.启动应用,进入对话页面。
2.按T01-T08顺序输入测试用例中的文本。
3.观察Agent的回复是否符合预期结果。
4.切换到日程列表页面,验证日程是否正确创建/修改/删除。
5.在"通过"列填写测试结果(通过/失败)。
9.3 常见问题排查
任务10 优化与完善
【任务目标】对应用进行性能优化和体验完善。
10.1 延迟优化
大模型API调用通常需要1-3秒,在此期间应给用户明确的反馈:
// 在ChatPage中添加加载动画
@Builder LoadingIndicator() { if (this.isLoading) { Row({ space: 8 }) { LoadingProgress() .width(20).height(20) Text('Agent正在思考...') .fontSize(13).fontColor('#999999') } .padding(12) } }10.2 意图准确率优化
如果大模型偶尔无法正确理解意图,可以通过优化系统提示词提升准确率:
// 在DialogManager.getSystemPrompt()中添加few-shot示例 const FEW_SHOT = ` 示例1: 用户:明天下午3点开会 意图:创建日程,title=会议,startTime=明天15:00 示例2: 用户:这周有什么安排 意图:查询日程,startDate=本周一,endDate=本周日 示例3: 用户:取消明天的会议 意图:删除日程,需要先查询确定scheduleId`;10.3 用户体验优化
- 消息列表自动滚动到最新消息。
- 长按消息可复制内容。
- 日程列表支持下拉刷新。
- 添加空状态提示(无日程时显示"暂无日程")。
五、拓展任务(选做)
以下拓展任务供学有余力的同学选做,完成任意一项即可获得额外加分。
拓展1 语音输入扩展
在ChatPage中集成语音识别功能,允许用户通过语音输入创建日程。要求:使用AudioCapturer采集音频,调用云端ASR服务识别文本,自动填入输入框。
拓展2 日程冲突智能解决
当检测到时间冲突时,Agent不仅提示冲突,还主动建议替代时间。要求:修改toolCreateSchedule方法,在冲突时查询附近可用时间段并返回建议。
拓展3 日程智能分析
扩展Agent能力,支持自然语言分析日程统计。例如用户输入"这周开了多少会",Agent调用querySchedule获取数据并统计回复。要求:新增analyzeSchedule工具函数。
拓展4 重复日程支持
扩展createSchedule工具,支持重复日程(如"每周三下午3点开周会")。要求:解析重复规则,在日历API中设置recurrence字段。