华为云AI Agent集成鸿蒙车票查询项目(二编)
【摘要】 华为云AI Agent集成鸿蒙车票查询项目文档 一、项目概述 1.1 项目背景随着AI技术与移动应用的深度融合,本项目旨在通过华为云配置AI Agent实现智能车票查询能力,并基于鸿蒙操作系统(HarmonyOS)开发前端应用,为用户提供便捷的列车票务查询服务。 1.2 项目目标在华为云平台配置AI Agent,实现车票查询、车次信息解析等核心能力开发鸿蒙应用,通过前端页面与华为云AI A...
华为云AI Agent集成鸿蒙车票查询项目文档
一、项目概述
1.1 项目背景
随着AI技术与移动应用的深度融合,本项目旨在通过华为云配置AI Agent实现智能车票查询能力,并基于鸿蒙操作系统(HarmonyOS)开发前端应用,为用户提供便捷的列车票务查询服务。
1.2 项目目标
- 在华为云平台配置AI Agent,实现车票查询、车次信息解析等核心能力
- 开发鸿蒙应用,通过前端页面与华为云AI Agent交互,完成车票查询功能
- 实现城市信息解析、令牌管理、数据存储等辅助功能,保障应用稳定性
二、技术栈
- 前端框架:HarmonyOS ArkUI(ETS语言)
- 后端服务:华为云AI Agent
- 网络通信:HTTP/HTTPS、SSE(Server-Sent Events)
- 数据存储:鸿蒙本地存储(Preferences)
- 开发工具:DevEco Studio、华为云控制台
三、华为云AI Agent配置步骤
3.1 登录华为云控制台
- 访问华为云官网(https://www.huaweicloud.com/),登录账号并进入控制台
- 搜索“AI Agent”服务,进入服务管理页面
3.2 创建AI Agent实例
- 点击“创建Agent”,配置基本信息(名称、描述、所属区域等)
- 选择“自定义技能”,配置车票查询相关能力:
- 定义输入参数:出发城市、到达城市、出发日期
- 定义输出格式:车次列表(包含车次号、出发时间、到达时间、余票信息等)
- 配置API访问凭证(Access Key、Secret Key),记录Agent访问地址(Endpoint)
3.3 测试AI Agent
通过华为云控制台的“在线调试”功能,输入测试参数(如“北京到上海,2025-10-25”),验证Agent是否能正确返回车票信息
四、鸿蒙应用开发架构
4.1 项目目录结构
ets/
├── entryability/ # 应用入口能力
├── pages/ # 页面组件
│ ├── API/ # 网络请求相关
│ │ └── http_ai.ets # 调用华为云AI Agent的HTTP工具
│ ├── Common/ # 公共模型与工具
│ │ └── Model/ # 数据模型定义
│ │ ├── cityModel.ets # 城市信息模型
│ │ ├── searchHistoryModel.ets #查询记录模型
│ │ ├── ticketModel.ets # 车票信息模型
│ │ └── tokenApi/ # 令牌相关模型
│ │ ├── ReceiveModel.ets # 令牌响应模型
│ │ └── TokenModel.ets # 令牌请求模型
│ └── Utils/ # 工具类
│ ├── getData.ets # 数据获取工具
│ ├── cityParse.ets # 城市信息解析工具
│ ├── getToken.ets # 令牌获取与管理
│ ├── MakeCall.ets # AI Agent调用封装
│ ├── SSEParser.ets # SSE响应解析工具
│ └── TicketDataStore.ets# 车票数据本地存储
├── index.ets # 应用首页
├── TrainHomePage.ets # 车票查询主页
└── TrainDetails.ets # 车次详情页
五、核心文件实现
5.1 entryability(应用入口)
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { CityListResponse } from '../pages/Common/Model/cityModel';
import json from '@ohos.util.json';
import { notificationManager } from '@kit.NotificationKit';
const TAG: string = '[PublishOperation]';
const DOMAIN_NUMBER: number = 0xFF00;
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
private fruits: TextCascadePickerRangeContent[] = []
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
// city.json获取
try {
this.context.resourceManager.getRawFileContent("city.json")
.then((value: Uint8Array) => {
console.log("city初始数据:" + value);
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
console.log("city通过解码器转为UTF-8后的数据:" + json.stringify(textDecoder));
let decodedData = textDecoder.decodeWithStream(value, { stream: false });
console.log("city完成解码后的数据:" + decodedData);
try {
let parseData: CityListResponse = JSON.parse(decodedData);
console.log("city通过JSON工具类解析后的数据:" + json.stringify(parseData));
for (let i = 0; i < parseData.cityList.cities.length; i++) {
this.fruits.push({
text: parseData.cityList.cities[i].name
})
}
console.log("city通过JSON工具类解析后放入数组的数据:" + JSON.stringify(this.fruits));
AppStorage.setOrCreate<TextCascadePickerRangeContent[]>('cityList', this.fruits)
} catch (error) {
console.error("promise getRawFileContent failed, error is " + error);
}
})
.catch((error: BusinessError) => {
console.error("getRawFileContent promise error is " + error);
});
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`promise getRawFileContent failed, error code: ${code}, message: ${message}.`);
}
// 请求通知权限
notificationManager.isNotificationEnabled().then((data: boolean) => {
hilog.info(DOMAIN_NUMBER, TAG, "isNotificationEnabled success, data: " + JSON.stringify(data));
if(!data){
notificationManager.requestEnableNotification(this.context).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification success`);
}).catch((err: BusinessError) => {
if(1600004 == err.code){
hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification refused, code is ${err.code}, message is ${err.message}`);
} else {
hilog.error(DOMAIN_NUMBER, TAG, `[ANS] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);
}
});
}
}).catch((err: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG, `isNotificationEnabled fail, code is ${err.code}, message is ${err.message}`);
});
// 发布通知
let notificationRequest: notificationManager.NotificationRequest = {
id: 1,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, // 普通文本类型通知
normal: {
title: 'test_title',
text: 'test_text',
additionalText: 'test_additionalText',
}
}
};
notificationManager.publish(notificationRequest, (err: BusinessError) => {
if (err) {
hilog.error(DOMAIN_NUMBER, TAG, `Failed to publish notification. Code is ${err.code}, message is ${err.message}`);
return;
}
hilog.info(DOMAIN_NUMBER, TAG, 'Succeeded in publishing notification.');
});
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy():
void {
// Main window is destroyed, release UI related resources
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onWindowStageDestroy'
)
;
}
onForeground():
void {
// Ability has brought to foreground
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onForeground'
)
;
}
onBackground():
void {
// Ability has back to background
hilog
.info
(
DOMAIN,
'testTag',
'%{public}s',
'Ability onBackground'
)
;
}
}
5.2 页面组件
5.2.1 index.ets(应用首页)
import { router } from '@kit.ArkUI'
import { getData } from './Utils/getData'
import { common } from '@kit.AbilityKit'
import { AITicketResponse } from './Commons/Model/ticketModel'
import { SearchHistory } from './Commons/Model/searchHistoryModel'
import { relationalStore } from '@kit.ArkData'
@Entry
@Component
struct Index {
@State cityList: TextCascadePickerRangeContent[] = []
@State fromCityIndex: number = 0
@State fromCityString: string = '郑州市'
@State toCityIndex: number = 0
@State toCityString: string = '上海市'
@State selectedDate: Date = new Date()
@State aiResponse: AITicketResponse | null = null
@State searchHistory: SearchHistory[] = [];
private context = getContext(this) as common.UIAbilityContext;
private rdbStore: relationalStore.RdbStore | null = null;
private readonly STORE_CONFIG: relationalStore.StoreConfig = {
name: 'TrainTicketDB.db',
securityLevel: relationalStore.SecurityLevel.S2
};
private readonly TABLE_NAME: string = 'search_history';
private readonly SQL_CREATE_TABLE: string = `
CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
fromCity TEXT NOT NULL,
toCity TEXT NOT NULL
)
`;
async initRdbStore(): Promise<void> {
try {
this.rdbStore = await relationalStore.getRdbStore(this.context, this.STORE_CONFIG);
await this.rdbStore.executeSql(this.SQL_CREATE_TABLE);
console.info('数据库初始化成功');
} catch (err) {
console.error(`数据库初始化失败: ${JSON.stringify(err)}`);
}
}
async saveSearchHistory(): Promise<void> {
if (!this.rdbStore) {
await this.initRdbStore();
}
console.info('数据库保存开始', this.fromCityString, this.toCityString);
// 修复:使用正确的目的地
const valueBucket: relationalStore.ValuesBucket = {
'fromCity': this.fromCityString,
'toCity': this.toCityString
};
try {
await this.rdbStore!.insert(this.TABLE_NAME, valueBucket);
console.info('搜索记录保存到数据库成功');
await this.loadSearchHistory();
} catch (err) {
console.error(`保存搜索记录到数据库失败: ${err}`);
}
}
async loadSearchHistory(): Promise<void> {
if (!this.rdbStore) {
await this.initRdbStore();
}
try {
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.orderByDesc('id');
predicates.limitAs(10);
const resultSet = await this.rdbStore!.query(predicates,
['id', 'fromCity', 'toCity']);
this.searchHistory = [];
while (resultSet.goToNextRow()) {
const history: SearchHistory = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
fromCity: resultSet.getString(resultSet.getColumnIndex('fromCity')),
toCity: resultSet.getString(resultSet.getColumnIndex('toCity'))
};
this.searchHistory.push(history);
}
resultSet.close();
} catch (err) {
console.error(`加载搜索历史失败: ${JSON.stringify(err)}`);
}
}
aboutToAppear(): void {
// 初始化数据库,完成后保存当前搜索记录
this.initRdbStore().then(() => {
this.saveSearchHistory();
});
}
build() {
Column({ space: 10 }) {
// 出发地、目的地的选择和显示功能
Row({ space: 10 }) {
Column({ space: 10 }) {
Button('出发地')
.onClick(() => {
this.getUIContext().showTextPickerDialog({
range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
selected: this.fromCityIndex,
onChange: (value: TextPickerResult) => {
this.fromCityString = value.value as string
},
onAccept: (value: TextPickerResult) => {
this.fromCityIndex = value.index as number
this.fromCityString = value.value as string
}
});
})
Text(this.fromCityString)
}
Text('——').fontWeight(FontWeight.Bold)
Column({ space: 10 }) {
Button('目的地')
.onClick(() => {
this.getUIContext().showTextPickerDialog({
range: AppStorage.get('cityList') as TextCascadePickerRangeContent[],
selected: this.toCityIndex,
onChange: (value: TextPickerResult) => {
this.toCityString = value.value as string
},
onAccept: (value: TextPickerResult) => {
this.toCityIndex = value.index as number
this.toCityString = value.value as string
}
});
})
Text(this.toCityString)
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
// 日期选择功能
Row({ space: 10 }) {
Text(this.selectedDate.toLocaleDateString() + '')
.padding({
left: 15,
right: 15,
top: 10,
bottom: 10
})
.borderRadius(20)
.fontColor('#ffffffff')
.backgroundColor('#007dfe')
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.getUIContext().showDatePickerDialog({
start: new Date(),
selected: this.selectedDate,
onDateChange: (value: Date) => {
this.selectedDate = value
}
})
})
}
// 搜索记录展示
Row() {
ForEach(this.searchHistory, (item: SearchHistory, index) => {
if (index <= 2) {
Text(item.fromCity + '--' + item.toCity)
.fontSize(12)
.fontColor('#8a8a8a')
.layoutWeight(1)
}
})
}
.width('90%')
Button('查询车次')
.onClick(() => {
router.pushUrl({
url: 'pages/TrainDetails',
params: {
fromCity: this.fromCityString,
toCity: this.toCityString,
selectedDate: this.selectedDate.toLocaleDateString(),
aiResponse: this.aiResponse
}
}, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info('Invoke pushUrl succeeded.');
})
this.saveSearchHistory()
})
Button('展示AI查询结果')
.onClick(async () => {
await getData(this.fromCityString, this.toCityString, this.selectedDate.toLocaleDateString(),
getContext(this) as common.UIAbilityContext)
.then((response) => {
this.aiResponse = response;
})
})
Scroll() {
Text(this.aiResponse ? JSON.stringify(this.aiResponse) : '暂无数据')
}
}
.width('100%')
.height('100%')
}
}
5.2.2 TrainHomePage.ets(车票查询主页)
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct TrainHomePage {
webviewController: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: "https://www.12306.cn",
controller: this.webviewController,
})
}
.width('100%')
.height('100%')
}
}
5.2.3 TrainDetails.ets(车次详情页)
import { router } from '@kit.ArkUI'
import { TicketItem, SeatType, AITicketResponse } from './Common/Model/ticketModel'
import { JSON } from '@kit.ArkTS'
import { MakeCall } from './Utils/MakeCall'
class TrainDetailsProps {
fromCity?: string
toCity?: string
selectedDate?: string
aiResponse: AITicketResponse | null = null
}
@Entry
@Component
struct TrainDetails {
scroller: Scroller = new Scroller();
@State fromCity: string = '郑州' // 出发城市
@State toCity: string = '上海' // 目的城市
@State aiResponse: AITicketResponse | null = null
@State selectedDateStr: string = '今天 10.19' // 日期显示字符串
@State ticketList: TicketItem[] = this.aiResponse?.ticketList || []
@State phoneNumber: string = '400 - 888 - 8888'
aboutToAppear(): void {
const params = router.getParams() as TrainDetailsProps
if (params) {
this.fromCity = params.fromCity || '郑州'
this.toCity = params.toCity || '上海'
this.aiResponse = params.aiResponse
// 日期参数处理(若需动态日期,可扩展此处)
if (params.selectedDate) {
this.selectedDateStr = `今天 ${params.selectedDate}`
}
}
console.log('ai对话返回结果:', JSON.stringify(this.aiResponse))
console.log('ai对话 selectedDate:', JSON.stringify(this.aiResponse))
console.log('ai对话返回列表结果:', JSON.stringify(this.aiResponse?.ticketList))
console.log('ai对话赋值给列表:', this.ticketList.toString())
console.log('ai对话返回的信息中的的温馨提示:', this.aiResponse?.tips.toString())
}
// 查找第一个有余票的席位(只包含"张"的才算有票)
getFirstAvailableSeat(seatTypes: SeatType[]): SeatType | null {
for (let seat of seatTypes) {
if (seat.ticketStatus.includes('张')) {
return seat
}
}
return null
}
//构建车次项
@Builder
TrainItemBuilder(item: TicketItem, index: number) {
// 在@Builder中调用方法获取有余票的席位
// let availableSeat: SeatType | null = this.getFirstAvailableSeat(item.seatTypes)
Column() {
// 车次核心信息行:出发时间/站 + 车次/历时 + 到达时间/站 + 票价
Row({ space: 20 }) {
// 出发信息:时间 + 站名
Column({ space: 5 }) {
Text(item.departureTime).fontSize(18).fontColor(Color.Black)
Text(item.departureStation).fontSize(14).fontColor('#666')
}
// 车次与历时
Column({ space: 5 }) {
Text(item.trainNo).fontSize(16).fontColor(Color.Black)
Text(item.duration).fontSize(14).fontColor('#666')
}
// 到达信息:时间 + 站名
Column({ space: 5 }) {
Text(item.arrivalTime).fontSize(18).fontColor(Color.Black)
Text(item.arrivalStation).fontSize(14).fontColor('#666')
}
// 票价显示:显示第一个有余票的席位价格,如果没有则显示售罄
Column({ space: 5 }) {
// if (availableSeat) {
Row() {
Text('¥').fontSize(14).fontColor('#f98131')
Text(item.seatTypes[0].price.split('元')[0]).fontSize(16).fontColor('#f98131')
Text('起').fontSize(14).fontColor('#f98131')
}
}
}
.padding(10)
Divider().width('90%')
// 席别与余票状态行
Row({ space: 15 }) {
ForEach(item.seatTypes, (seat: SeatType) => {
Row({ space: 5 }) {
Text(seat.seatName).fontSize(12).fontColor('#666')
// 不同余票状态,颜色区分
if (seat.ticketStatus === '候补+') {
Text(seat.ticketStatus).fontSize(12).fontColor('#007dff')
} else if (seat.ticketStatus.includes('张')) {
Text(seat.ticketStatus).fontSize(12).fontColor('#ff32e90b')
} else if (seat.ticketStatus === '售罄') {
Text(seat.ticketStatus).fontSize(12).fontColor('#999')
} else {
Text(seat.ticketStatus).fontSize(12).fontColor('#666')
}
}
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.padding({
left: 10,
right: 10,
top: 10,
bottom: 10
})
}
.width('95%')
.margin({
left: '2.5%',
right: '2.5%',
top: index == 0 ? 10 : 5,
bottom: 5
})
.borderRadius(8)
.backgroundColor(Color.White)
}
build() {
Column() {
// 顶部导航栏:城市切换 + 日期显示
Row({ space: 10 }) {
Text('<').fontSize(20).fontColor(Color.White).fontWeight(FontWeight.Bolder)
.onClick(() => router.back())
Row() {
Text(this.fromCity).fontColor(Color.White).fontWeight(FontWeight.Bolder)
Text(' <> ').fontColor(Color.White).fontWeight(FontWeight.Bolder)
Text(this.toCity).fontColor(Color.White).fontWeight(FontWeight.Bolder)
}
Text(this.selectedDateStr).fontColor(Color.White).fontWeight(FontWeight.Bolder)
}
.width('100%')
.padding({
left: 15,
right: 15,
top: 10,
bottom: 10
})
.backgroundColor('#3b99fb')
.justifyContent(FlexAlign.SpaceBetween)
// 车次标签页(直达/中转)
Tabs() {
// 直达标签内容
TabContent() {
Column() {
List() {
ForEach(this.aiResponse?.ticketList, (item: TicketItem, index) => {
ListItem() {
Column() {
// 使用@Builder构建每个车次项
this.TrainItemBuilder(item, index)
// 温馨提示
if (index == (this.aiResponse?.ticketList.length as number) - 1) {
Column({ space: 5 }) {
Text('温馨提示').width('90%').fontSize(14).fontColor('#ffff7500').margin({ top: 10 })
ForEach(this.aiResponse?.tips, (tip: string) => {
Text(tip).width('90%').fontSize(12).fontColor('#999')
})
}
Column({ space: 10}) {
Text('12306官网')
.fontColor('#1867f5')
.onClick(() => {
router.push({
url: 'pages/TrainHomePage'
})
})
Text(`可咨询详情请拨打:${this.phoneNumber}`)
.fontColor('#1867f5')
.onClick(() => {
MakeCall.makeCall(this.phoneNumber)
})
}
.margin({ top: 10 })
}
}
}
})
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
.backgroundColor('#f5f5f5')
}
}
.tabBar('直达')
// 中转标签内容
TabContent() {
Text('中转功能待完善')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
.backgroundColor('#f5f5f5')
}.tabBar('中转')
}
.width('100%')
.height('100%')
.barHeight(50)
}
.width('100%')
.height('100%')
}
}
5.3 API模块(网络请求)
5.3.1 http_ai.ets(AI Agent HTTP请求工具)
import { rcp } from '@kit.RemoteCommunicationKit'
import { auth, Inputs } from '../Common/Model/tokenApi/TokenModel'
// const url = 'https://123.249.99.67/v1/9d72d169ec634537bd3cfe7fa066eed6/agents/0a32ae0d-8f66-42b1-9d83-bb827d445332/conversations/1?version=1760701576547' // 王
const url = 'https://123.249.99.67/v1/b188216baed24ebfae78ed6de041db54/agents/450c4d64-1caf-4189-b881-5d829380e3e3/conversations/1?version=1760932844103' // 杨
export async function get_ai(token:string,a:Inputs){
try {
const headers:rcp.RequestHeaders = {
'X-Auth-Token':token,
'Content-Type': 'application/json'
}
const security:rcp.SecurityConfiguration = {
remoteValidation:'skip'
}
const session = rcp.createSession({
headers:headers,
requestConfiguration:{
security:security
}
})
const post_ai =await session.post(url,a)
return post_ai;
}catch (e) {
console.log('ai回答_get_ai_error'+JSON.stringify(e))
return null
}
}
const url_token = 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens'
export async function token_api():Promise<rcp.Response | undefined>{
const headers: rcp.RequestHeaders = {
'Content-Type': 'application/json'
}
const session = rcp.createSession({
headers: headers
})
try {
return await session.post(url_token, new auth() )
} catch (e) {
console.error(JSON.stringify(e) + "ai调用3")
}
return undefined
}
5.4 Common/Model模块(数据模型)
5.4.1 cityModel.ets(城市信息模型)
// city.json解析接口
export interface City {
id: number;
name: string;
province: string;
pinyin: string;
shortPinyin: string;
}
export interface CityList {
updateTime: string;
cities: City[];
}
export interface CityListResponse {
cityList: CityList;
}
5.4.2 searchHistoryModel.ets(查询记录模型)
export interface SearchHistory {
id: number;
fromCity: string;
toCity: string;
}
5.4.3 ticketModel.ets(车票信息模型)
// pages/Common/Model/ticketModel.ets
export interface QueryCondition {
departureCity: string; // 出发城市(与AI JSON字段一致)
arrivalCity: string; // 到达城市
travelDate: string; // 出行日期(yyyy-MM-dd)
}
export interface SeatType {
seatName: string; // 席别名称(如"二等座")
price: string; // 票价(含单位,如"553元")
ticketStatus: string; // 余票状态(如"有票"/"无票"/"候补")
}
export interface TicketItem {
trainNo: string; // 车次编号(如"G101")
departureStation: string; // 出发车站(如"北京南站")
arrivalStation: string; // 到达车站(如"上海虹桥站")
departureTime: string; // 出发时间(yyyy-MM-dd HH:mm:ss)
arrivalTime: string; // 到达时间(yyyy-MM-dd HH:mm:ss)
duration: string; // 历时(如"6小时30分")
seatTypes: SeatType[]; // 该车次的席别列表
}
export interface TicketMetadata {
dataGenerateTime: string; // 数据生成时间(yyyy-MM-dd HH:mm:ss)
dataSource: string; // 数据来源(如"12306-MCP对接数据")
status: "success" | "fail";// 数据状态
}
export interface AITicketResponse {
queryCondition: QueryCondition; // 查询条件
ticketList: TicketItem[]; // 车次列表
metadata: TicketMetadata; // 元数据
tips: string[]; // 提示信息
}
export interface LocalTicketResponse {
parseStatus: "success" | "fail" | "no_data"; // 解析状态
message: string; // 状态描述(如错误信息)
data?: AITicketResponse; // 结构化票务数据(成功时存在)
}
5.4.4 tokenApi/TokenModel.ets(令牌请求模型)
// pages/Common/Model/tokenApi/TokenModel.ets
//调取请求AIM获取token的请求体
class Domain {
// name:string ="GT-gcw_XaRUNQkg_Syk" // 王
name: string = "hid_4ocll2m1t6hwqj2" // 杨
}
class User {
domain: Domain = new Domain()
// name:string = "wwh" // 王
name: string = "Luo Yu" // 杨
// password:string = "wang2006" // 王
password: string = "By107871895123." // 杨
}
class Password {
user: User = new User()
}
class Identity {
methods: string[] = ["password"]
password: Password = new Password()
}
class Project {
// id:string = "9d72d169ec634537bd3cfe7fa066eed6" // 王
name: string = "cn-north-4" // 杨
}
class Scope {
project: Project = new Project()
}
interface GeneratedTypeLiteralInterface_1 {
identity: Identity;
scope: Scope;
}
export class auth {
auth: GeneratedTypeLiteralInterface_1 = {
identity: new Identity(),
scope: new Scope()
}
}
//ai参数
interface GeneratedTypeLiteralInterface_2 {
query: string
}
export class Inputs {
inputs: GeneratedTypeLiteralInterface_2 = {
query: '你好'
}
}
// 一、角色与任务定义
// 你是 “火车票查询系统” 的专属数据生成 Agent,需基于用户输入的火车票查询条件,返回标准化、结构化的数据,用于鸿蒙端应用(ArkTS 开发)的 API 接口调用,数据需完全匹配 (如直达车次展示、票价参考、余票提示等)。
// 二、查询条件必填项(用户需提供,Agent 需校验)
// 出发城市:需为国内地级市及以上城市名称(如 “深圳”“西安”,不可为 “广东省”“西安市长安区” 等非标准城市名);
// 到达城市:同 “出发城市” 规范,且不可与出发城市重复;
// 出行日期:需为 “yyyy-MM-dd” 格式(如 “2025-10-01”),且需为当前日期及未来 15天内(符合 12306 常规预售期逻辑);
// 若用户未提供上述任一必填项,需先返回提示:“请补充完整查询条件(出发城市、到达城市、出行日期,日期格式为 yyyy-MM-dd)”,不生成无效数据。
// 三、输出数据字段规范(API 数据源核心字段)
// 需返回包含「查询条件回显」「车票列表」「数据元信息」「温馨提示」4 大类的结构化数据,每个字段的定义、格式、约束如下表:
// 数据大类 字段名 字段定义 格式要求 约束条件
// 查询条件回显 departureCity 用户查询的出发城市 字符串(如 “深圳”) 非空,需与用户输入一致
// arrivalCity 用户查询的到达城市 字符串(如 “西安”) 非空,需与用户输入一致,且≠departureCity
// travelDate 用户查询的出行日期 字符串(yyyy-MM-dd,如 “2025-10-01”) 非空,需在当前日期 + 15天内
// 车票列表(array) trainNo 车次编号(如 G102、Z23) 字符串 非空,符合铁路车次编码规则(G/D/C/Z/T/K 开头 + 数字)
// departureStation 出发车站(需含城市名,如 “深圳北站”“西安站”) 字符串 非空,需属于 departureCity
// arrivalStation 到达车站(需含城市名,如 “西安北站”“深圳站”) 字符串 非空,需属于 arrivalCity
// departureTime 出发时间 字符串(HH:mm,如 “2025-10-01 08:00”) 非空,需匹配 travelDate
// arrivalTime 到达时间 字符串( HH:mm,如 “2025-10-01 14:30”) 非空,需晚于 departureTime
// duration 历时(如 “6 小时 30 分”) 字符串 非空,需根据 departureTime 与 arrivalTime 计算
// seatTypes(array) 席别及对应信息 数组(含子字段) 非空,至少包含 “二等座”“一等座”,最多含 “商务座”“无座”
// - seatName 席别名称 字符串(如 “二等座”“一等座”“商务座”“无座”) 非空,需为标准席别名称
// - price 席别票价 字符串(含单位 “元”,如 “580 元”) 非空,需符合对应席别市场价格范围
// - ticketStatus 余票状态 字符串(仅可选 “有票”“无票”“候补”) 非空,需与席别匹配
// 数据元信息 dataGenerateTime 数据生成时间(Agent 返回数据的时间) 字符串(yyyy-MM-dd HH:mm:ss,如 “2025-09-30 10:15:30”) 非空,自动生成当前时间
// dataSource 数据来源 字符串(固定为 “12306-MCP 对接数据”) 不可修改,固定值
// status 数据返回状态 字符串(仅可选 “success”“fail”) 非空,查询成功为 “success”,失败为 “fail”
// 温馨提示 tips(array) 火车票相关提示信息(退票、改签、实名制等) 字符串数组 非空,至少包含 2 条提示,最多 5 条
// 四、返回格式约束(强制 JSON 格式,不可增减字段)
// 仅返回 JSON 数据,无任何多余文本(如 “好的,查询结果如下:” 等描述性文字);
// JSON 字段大小写严格匹配上述 “输出数据字段规范”(如 departureCity 不可写为 DepartureCity);
// 数组类型字段(如 ticketList、seatTypes、tips)需保证元素格式统一,不可出现空数组;
// 若查询无结果(如无该日期的车次),需将 ticketList 设为空数组,status 设为 “success”,并在 tips 中添加 “当前日期无从 {departureCity} 到 {arrivalCity} 的直达车次,请调整日期或查询中转方案”;
// 示例格式如下(仅为示例,需根据实际查询条件生成真实数据):
// json
// {
// "queryCondition": {
// "departureCity": "深圳",
// "arrivalCity": "西安",
// "travelDate": "2025-10-01"
// },
// "ticketList": [
// {
// "trainNo": "G82",
// "departureStation": "深圳北站",
// "arrivalStation": "西安北站",
// "departureTime": "08:00:00",
// "arrivalTime": "14:30:00",
// "duration": "6小时30分",
// "seatTypes": [
// {
// "seatName": "二等座",
// "price": "580元",
// "ticketStatus": "有票"
// },
// {
// "seatName": "一等座",
// "price": "920元",
// "ticketStatus": "有票"
// },
// {
// "seatName": "商务座",
// "price": "1780元",
// "ticketStatus": "候补"
// }
// ]
// },
// {
// "trainNo": "G1024",
// "departureStation": "深圳站",
// "arrivalStation": "西安站",
// "departureTime": "10:15:00",
// "arrivalTime": "17:05:00",
// "duration": "6小时50分",
// "seatTypes": [
// {
// "seatName": "二等座",
// "price": "565元",
// "ticketStatus": "无票"
// },
// {
// "seatName": "一等座",
// "price": "900元",
// "ticketStatus": "候补"
// },
// {
// "seatName": "无座",
// "price": "565元",
// "ticketStatus": "有票"
// }
// ]
// }
// ],
// "metadata": {
// "dataGenerateTime": "2025-09-30 10:15:30",
// "dataSource": "12306-MCP对接数据",
// "status": "success"
// },
// "tips": [
// "退票需在发车前8天以上免手续费,发车前48小时-8天手续费5%,发车前24小时-48小时手续费10%,发车前24小时内手续费20%",
// "乘车需携带购票时所用的有效身份证件原件(身份证、护照等)",
// "儿童票为成人票的50%,身高1.2-1.5米儿童需购买儿童票,1.5米以上需购买成人票"
// ]
// }
// 五、数据约束补充
// 车次编号(trainNo)需符合真实铁路编码逻辑:高铁(G)、动车(D)、城际(C)、直达特快(Z)、特快(T)、快速(K),不可虚构无意义编码(如 “G000”“X123”);
// 出发 / 到达车站需为对应城市的真实车站(如深圳的 “深圳北站”“深圳站”“深圳东站”,不可写 “深圳西站”“深圳南站” 等不存在的车站);
// 票价需符合对应车次类型的市场价格范围(如深圳到西安的高铁二等座约 550-600 元,不可出现 “100 元”“2000 元” 等异常价格);
// 历时计算需准确(如出发时间 08:00、到达时间 14:30,历时需为 “6 小时 30 分”,不可出现 “5 小时 90 分”“7 小时” 等错误)。
// 响应速度要在20秒内
5.4.5 tokenApi/ReceiveModel.ets(令牌响应模型)
// pages/Common/Model/tokenApi/ReceiveModel.ets
/**
* 华为云IAM认证接口模型
*
*/
export interface AuthResponse {
token: Token;
}
/**
* Token核心数据结构(JSON中token对象的内容)
*/
export interface Token {
// token过期时间
expires_at: string;
methods: string[];
}
export interface data {
exent: string
content: string
createdTime: string
}
5.5 Utils模块(工具类)
5.5.1 getToken.ets(令牌获取与管理)
// pages/Utils/getToken.ets
import { preferences } from "@kit.ArkData"
import { common } from "@kit.AbilityKit"
import { token_api } from "../API/http_ai"
import { BusinessError } from "@kit.BasicServicesKit"
import { util } from "@kit.ArkTS"
import { AuthResponse } from "../Common/Model/tokenApi/ReceiveModel"
interface returnData {
token_get: string
time_get: string
}
export class Token_management {
private name: string = 'Token'
private preferences: preferences.Preferences | null = null
private context: common.UIAbilityContext
// 外部调用方传入上下文
constructor(context: common.UIAbilityContext) {
this.context = context
}
//初始化preferences
async init(): Promise<boolean> {
try {
this.preferences = await preferences.getPreferences(this.context, this.name)
console.log('初始化成功')
return true
} catch (err) {
return false
}
}
//调用获取token的api
async get_new_token(): Promise<returnData | null> {
if (!this.preferences) {
return null
}
let token_get: string = ''
let time_get: string = ''
try {
await token_api().then(response => {
console.log('获取token' + response)
token_get = response?.headers['x-subject-token'] as string;
time_get = buf2String(response?.body).token.expires_at
})
if (token_get && time_get) {
console.log('get_new_token' + "获取成功")
return { token_get, time_get }
} else {
console.log("返回值为空")
return null
}
} catch (err) {
console.error('token_get' + err)
return null
}
}
/**
* 储存新的token
*/
async set_token(): Promise<string | undefined> {
if (!this.preferences) {
console.log(this.preferences + '')
return undefined
}
//判读本地是否有token
if (this.preferences?.hasSync('token') && this.preferences.hasSync('time')) {
const storedToken = this.preferences.getSync('token', '') as string;
const storedExpireTime = this.preferences.getSync('time', '') as string;
//判断是否过期
if (!this.is_overdue(storedExpireTime)) {
console.log('token可以直接使用')
return storedToken
} else {
//已经过期重新将新的放在容器中 获取新的token
const new_token_vessel = await this.get_new_token()
const new_token = new_token_vessel?.token_get
const new_time = new_token_vessel?.time_get
//将获取到的token等持久化
this.persistent_token(new_token, new_time)
return new_token
}
} else {
//本地并没有储存调用接口来进行储存
const new_token_vessel = await this.get_new_token()
const new_token = new_token_vessel?.token_get
const new_time = new_token_vessel?.time_get
//将获取到的token等持久化
this.persistent_token(new_token, new_time)
return new_token
}
}
/**
* 拿到token可以自动判断是否过期
* 获取token
*/
async get_token(): Promise<string | null> {
try {
if (this.preferences?.hasSync('token') && this.preferences.hasSync('time')) {
const storedExpireTime = this.preferences.getSync('time', 'defValue') as string;
if (!this.is_overdue(storedExpireTime)) {
//没有过期可以直接使用
const token_get = this.preferences.getSync('token', 'defValue')
return token_get as string
} else {
//已经过期或不存在
return null
}
} else {
console.log('没有本地储存,进行储存')
// this.persistent_token()
return null
}
} catch (err) {
console.log('获取token失败' + err)
return null
}
}
//将获取到的token等持久化
async persistent_token(new_token: string | undefined, new_time: string | undefined): Promise<boolean> {
if (!this.preferences) {
return false
}
if (new_token && new_time) {
this.preferences.putSync(new_token, 'new_token')
this.preferences.putSync(new_time, 'new_time')
await new Promise<void>((resolve, reject) => {
this.preferences!.flush((err: BusinessError | undefined) => {
if (err) {
console.error(`Token持久化失败: 代码=${err.code}, 消息=${err.message}`);
reject(err);
} else {
console.info('Token已成功持久化到本地');
resolve();
}
});
});
return true
} else {
return false
}
}
//判断token是否过期如果过期就重新获取token
async is_overdue(End_time: string): Promise<boolean> {
try {
const end_time = new Date(End_time)
const Current_time = new Date()
return end_time.getTime() < Current_time.getTime()
} catch (err) {
console.log(err + '时间比对失败')
return true
}
}
//获取当前时间
get_Current_time(): string {
return new Date().toISOString()
}
async loadToken(): Promise<string | undefined> {
if (!this.get_token() == null) {
//可以正常获取到token
const token_get = await this.preferences?.getSync('token', 'defValue') as string
return token_get
} else {
//token过期或不存在时调用储存方法来重新获取
return await this.set_token()
}
}
}
//接收返回数据进行解析
function buf2String(buf: ArrayBuffer | undefined) {
let msgArray = new Uint8Array(buf as ArrayBuffer);
let textDecoder = util.TextDecoder.create("utf-8");
let rString = textDecoder.decodeWithStream(msgArray);
return JSON.parse(rString) as AuthResponse;
}
5.5.2 cityParse.ets(城市信息解析工具)
// pages/Utils/cityParse.ets
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import json from '@ohos.util.json';
import { CityListResponse } from '../Common/Model/cityModel';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
private fruits: TextCascadePickerRangeContent[] = []
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.resourceManager.getRawFileContent("city.json")
.then((value: Uint8Array) => {
//////
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
let decodedData = textDecoder.decodeWithStream(value, { stream: false });
try {
let parsedData: CityListResponse = JSON.parse(decodedData);
console.log("city通过JSON工具类解析后的数据:" + json.stringify(parsedData));
for (let i = 0; i < parsedData.cityList.cities.length; i++) {
this.fruits.push({
text: parsedData.cityList.cities[i].name
})
}
console.log("city通过JSON工具类解析后放入数组的数据:" + this.fruits);
AppStorage.SetOrCreate<TextCascadePickerRangeContent[]>('globalCityListData', this.fruits);
//////////
} catch (e) {
console.error('json文件' + json.stringify(e))
}
})
.catch((error: BusinessError) => {
console.error("json文件getRawFileContent promise error is " + error);
});
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.error(`json文件promise getRawFileContent failed, error code: ${code}, message: ${message}.`);
}
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
}
5.5.3 MakeCall.ets(AI Agent调用封装)
// pages/Utils/MakeCall.ets
// import需要的模块
import { call } from '@kit.TelephonyKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class MakeCall {
public static makeCall(phoneNumber: string): void {
// 调用查询能力接口
let isSupport = call.hasVoiceCapability();
if (isSupport) {
// 如果设备支持呼叫能力,则继续跳转到拨号界面,并显示拨号的号码
// 从API15开始支持tel格式电话号码,如:"tel:13xxxx"
call.makeCall(phoneNumber, (err: BusinessError) => {
if (!err) {
console.info("make call success.");
} else {
console.error("make call fail, err is:" + JSON.stringify(err));
}
});
}
}
}
5.5.4 SSEParser.ets(SSE响应解析工具)
// pages/Utils/SSEParser.ets
import { AITicketResponse, LocalTicketResponse } from '../Common/Model/ticketModel';
import { TicketDataStore } from './TicketDataStore';
// SSE原始事件结构
interface RawSSEEvent {
event: string; // 事件类型(如"summary_response"/"message")
content?: string; // 核心内容
role?: string; // 可选字段
createdTime?: number;// 可选字段
}
// SSE解析器
export class SSEParser {
// 主方法:解析SSE数据 → 结构化票务数据 → 存入全局存储
public static parseAndStore(sseData: string): LocalTicketResponse {
try {
// 1. 筛选有效SSE行(仅保留"data:"开头且非空的行)
const validLines = sseData.split('\n')
.map(line => line.trim())
.filter(line => line.startsWith('data:') && line.slice(5).trim());
if (validLines.length === 0) {
const res: LocalTicketResponse = {
parseStatus: "no_data",
message: "SSE响应无有效内容"
};
TicketDataStore.getInstance().setData(res);
return res;
}
// 2. 优先解析"summary_response"事件
for (const line of validLines) {
const sseEvent = SSEParser.parseSingleLine(line);
if (sseEvent?.event === 'summary_response' && sseEvent.content) {
console.log('原始content:', sseEvent.content); // 打印原始内容
const cleanedContent = SSEParser.cleanContent(sseEvent.content);
if (!cleanedContent) {
throw new Error("清理后无有效JSON内容");
}
console.log('清理后content:', cleanedContent);
// 解析纯净的JSON
const aiData = JSON.parse(cleanedContent) as AITicketResponse;
const res: LocalTicketResponse = {
parseStatus: "success",
message: "解析成功(来源:summary_response,已清理格式)",
data: aiData
};
TicketDataStore.getInstance().setData(res);
return res;
}
}
// 3. 解析"message"事件(多段拼接)
const fullContent = validLines
.map(line => SSEParser.parseSingleLine(line))
.filter(event => event?.event === 'message')
.map(event => event?.content || '')
.join('');
if (fullContent) {
const aiData = JSON.parse(fullContent) as AITicketResponse;
const res: LocalTicketResponse = {
parseStatus: "success",
message: "解析成功(来源:message拼接)",
data: aiData
};
TicketDataStore.getInstance().setData(res);
return res;
}
// 4. 无匹配事件
const noMatchRes: LocalTicketResponse = {
parseStatus: "no_data",
message: "未找到summary_response或message事件"
};
TicketDataStore.getInstance().setData(noMatchRes);
return noMatchRes;
} catch (error) {
// 解析异常(如JSON格式错误)
const errMsg = error instanceof Error ? error.message : "未知错误";
const errRes: LocalTicketResponse = {
parseStatus: "fail",
message: `SSE解析失败:${errMsg}`
};
TicketDataStore.getInstance().setData(errRes);
return errRes;
}
}
/**
* 清理content格式:移除```json开头、```结尾及多余换行/空格
* @param rawContent - 原始content字符串
* @returns 纯净的JSON字符串(失败返回空)
*/
private static cleanContent(rawContent: string): string {
if (!rawContent) return "";
// 1. 移除开头的换行符(\n)和空格
let cleaned = rawContent.trimStart();
// 2. 移除开头的```json标记(不区分大小写,兼容可能的格式差异)
const startMarker = /^```json/i; // 正则:匹配开头的```json(忽略大小写)
cleaned = cleaned.replace(startMarker, "").trimStart();
// 3. 移除结尾的```标记(可能带换行或空格)
const endMarker = /```\s*$/; // 正则:匹配结尾的```及后续空格/换行
cleaned = cleaned.replace(endMarker, "").trimEnd();
return cleaned;
}
// 辅助方法:解析单行SSE(移除"data:"前缀,转JSON)
private static parseSingleLine(line: string): RawSSEEvent | null {
try {
const jsonStr = line.slice(5).trim();
return JSON.parse(jsonStr);
} catch (e) {
console.warn(`单行SSE解析失败(跳过): ${line}`);
return null;
}
}
}
5.5.5 TicketDataStore.ets(车票数据本地存储)
// pages/Utils/TicketDataStore.ets
import { LocalTicketResponse } from '../Common/Model/ticketModel';
// 票务数据单例
export class TicketDataStore {
private static instance: TicketDataStore;
private ticketData: LocalTicketResponse; // 存储解析后的票务数据
// 私有构造,初始化默认状态
private constructor() {
this.ticketData = {
parseStatus: "no_data",
message: "尚未查询票务数据"
};
}
// 获取单例实例
public static getInstance(): TicketDataStore {
if (!TicketDataStore.instance) {
TicketDataStore.instance = new TicketDataStore();
}
return TicketDataStore.instance;
}
// 存储票务数据
public setData(data: LocalTicketResponse): void {
this.ticketData = data;
}
// 获取票务数据
public getData(): LocalTicketResponse {
return this.ticketData;
}
// 清空数据
public clearData(): void {
this.ticketData = {
parseStatus: "no_data",
message: "数据已清空,等待新查询"
};
}
}
5.5.6 getData.ets(通用数据获取工具)
// pages/Utils/getData.ets
import { AITicketResponse, LocalTicketResponse } from '../Common/Model/ticketModel';
import { Inputs } from '../Common/Model/tokenApi/TokenModel';
import { get_ai } from '../API/http_ai';
import { SSEParser } from './SSEParser';
import { Token_management } from './getToken';
import { TicketDataStore } from './TicketDataStore';
import { common } from '@kit.AbilityKit';
let aiData: AITicketResponse | null = null
let token: string | undefined = ''
let tokenManager: Token_management | null = null;
let ticketData: LocalTicketResponse = TicketDataStore.getInstance().getData();
/**
* 获取数据
* @param fromStation 传入始站
* @param toStation 传入终点
* @param date 传入日期
* @param context 传入上下文 示例:getContext(this) as common.UIAbilityContext
* @returns : AITicketResponse | null 返回数据
*/
export async function getData(fromStation: string, toStation: string, date: string,
context: common.UIAbilityContext): Promise<AITicketResponse | null> {
try {
tokenManager = new Token_management(context)
await tokenManager.init()
token = await tokenManager.loadToken()
const aa = new Inputs();
aa.inputs.query = `{
"fromStation": ${fromStation},
"toStation": ${toStation},
"date": ${date}
}`
await get_ai(token as string, aa).then(async (car) => {
console.log("ai对话 token" , car)
ticketData = SSEParser.parseAndStore(car?.toString() as string)
console.log("ai对话 解析成功1" , ticketData.message)
aiData = ticketData.data as AITicketResponse
return aiData
})
} catch (error) {
ticketData = {
parseStatus: "fail",
message: `查询失败:${(error as Error).message}`
};
}
;
return aiData
}
六、测试与部署
6.1 本地测试
- 在DevEco Studio中配置鸿蒙模拟器或连接真实设备
- 运行应用,测试核心流程:
- 首页跳转至查询页
- 输入城市和日期,点击查询
- 验证是否能正确显示车票列表
- 点击车次进入详情页,验证数据展示
6.2 华为云AI Agent联调
- 确保AI Agent已正确配置并启动
- 检查Access Key、Secret Key和Endpoint是否正确
- 测试令牌获取功能,验证是否能正常拿到accessToken
- 模拟异常场景(如网络中断、参数错误),验证错误处理逻辑
6.3 应用部署
- 生成鸿蒙应用签名(参考华为开发者文档)
- 在DevEco Studio中打包HAP文件
- 通过华为应用市场或本地安装部署到设备
七、注意事项
- 华为云Access Key和Secret Key需妥善保管,避免硬编码在前端代码中(建议通过后端服务转发)
- 令牌有效期通常为24小时,需确保
getToken.ets
中的缓存逻辑正确 - AI Agent的响应格式需严格约定,避免解析失败
- 网络请求需处理超时和异常,提升用户体验
- 本地存储数据需定期清理,避免占用过多设备空间
八、总结
本项目通过华为云AI Agent实现了智能车票查询的后端能力,结合鸿蒙应用的前端交互,为用户提供了便捷的票务查询服务。核心实现包括AI Agent配置、令牌管理、网络通信、数据解析与存储等模块,各模块职责清晰、分工明确,可扩展性强。后续可优化方向:增加车票预订功能、集成用户登录、优化AI响应速度等。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)