一、实验目的

(一)知识目标

  • 理解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。

三、实验前思考题

请在进行实验前思考以下问题,并在实验报告中给出你的理解:

  1. 在传统的日程管理应用中,用户需要手动填写标题、时间、地点等表单字段。使用AI Agent方式后,用户只需用自然语言说"下周三下午3点开项目评审会"即可自动创建日程。请思考:这种交互方式相比传统表单有哪些优势?又可能带来哪些新的挑战?
  2. Function Calling机制中,大模型只返回工具调用指令(函数名+参数),而由应用程序负责实际执行。请思考:为什么不直接让大模型执行函数?这种"决策与执行分离"的设计在安全性和隐私保护方面有什么好处?
  3. 在多轮对话场景中,用户可能说"把那个会议改到后天"。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字段。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐