☰
鸿蒙+蓝耘元生代MaaS:端云协同AI导游开发实战
2026/10/14 4:12:04 网站建设 项目流程

1. 从一次景区体验说起:这个项目到底在做什么

国庆假期我在故宫逛了整整一天,人挤人不说,最让我抓狂的是——请个真人导游要排队两小时,租个电子讲解器又只能按固定路线念稿子,问它“这个殿是干嘛的”它根本不理你。当时我就想,能不能用手机做一个真正能对话的AI导游?回来之后我花了大概一周时间,用蓝耘元生代MaaS平台加上鸿蒙HarmonyOS,真把这个东西跑通了。

这个项目的核心逻辑其实不复杂:鸿蒙端负责交互和定位,MaaS平台负责大模型推理。用户在鸿蒙App里对着某个景点拍照或者语音提问,App把请求发到蓝耘元生代的MaaS接口,大模型返回一段有上下文、有温度的讲解,再通过鸿蒙的TTS播报出来。整个过程延迟控制在两秒以内,体验上基本就是“随问随答”。

为什么选鸿蒙而不是直接做个微信小程序?因为鸿蒙的分布式能力和原生AI框架(比如HiAI Foundation)能直接调用端侧算力做预处理,比如图像压缩、语音降噪、位置围栏判断,这些如果全丢给云端,流量和延迟都扛不住。而蓝耘元生代MaaS的好处是它把模型部署、API网关、计费、监控都打包好了,我不需要自己买GPU服务器,也不用折腾推理框架的版本兼容,注册完拿到API Key就能调。

适合谁来参考这篇记录?如果你是一个有一定前端基础(ArkTS或者TypeScript)的开发者,想快速验证一个“端云协同AI应用”的可行性,或者你正在找MaaS平台做落地Demo,那这篇内容基本可以照着抄。哪怕你之前没碰过鸿蒙,只要会写基本的HTTP请求和UI布局,跟着走也能跑起来。

2. 整体架构设计与技术选型背后的考量

2.1 为什么是“端侧预处理+云端推理”而不是全云端

一开始我试过把所有逻辑都放在云端:鸿蒙端只负责录音和拍照,原图原音频直接上传。实测下来两个问题很致命。第一是延迟,一张故宫太和殿的照片原图大概4MB,4G网络下上传就要3到5秒,加上模型推理时间,用户等七八秒才听到第一句话,体验直接崩了。第二是流量成本,如果按这个量级跑,一天下来光上传流量就得好几百MB,用户不可能接受。

所以后来改成了端侧先做一轮处理。图片在鸿蒙端用ImagePacker压缩到800px宽、质量70%,体积降到200KB左右;音频用AudioCapturer采集后做VAD(语音活动检测),只把有效语音段上传,静音部分直接丢掉。这样上传数据量降了90%以上,延迟也压到了一秒出头。

注意:端侧压缩不是越狠越好。我试过把图片压到400px,结果模型识别建筑细节时经常认错,比如把“中和殿”认成“保和殿”。800px是一个比较平衡的值,既能保留屋檐、匾额这些关键特征,又不会让上传太慢。

2.2 蓝耘元生代MaaS的接入方式选择

蓝耘元生代MaaS提供了两种调用方式:一种是标准的RESTful API,另一种是WebSocket流式输出。对于导游场景,我选了流式输出。原因很简单——用户问“这个宫殿是谁建的”,如果等模型把整段话生成完再返回,可能要等三四秒;而流式输出是生成一个字就推一个字,鸿蒙端可以边收边播,用户感觉上就是“秒回”。

具体配置上,我在MaaS控制台创建了一个“对话补全”类型的应用,模型选了平台推荐的通用对话模型(具体名称就不提了,各家平台命名不一样),温度设成0.7,最大token设成512。温度这个参数我调过好几轮:0.3的时候回答太死板,像在背课文;1.0的时候又容易胡编,比如把康熙说成乾隆。0.7是一个既有一定灵活性、又不会太离谱的值。

2.3 鸿蒙端的技术栈拆解

鸿蒙这边我用的API版本是API 11,开发语言是ArkTS。核心用到了这几个模块:

  • @ohos.net.http:负责和MaaS平台通信,支持流式读取。
  • @ohos.multimedia.audio:录音和播放TTS音频。
  • @ohos.multimedia.image:图片压缩和格式转换。
  • @ohos.geoLocationManager:获取经纬度,判断用户是否在景区范围内。
  • @ohos.ai.tts:端侧文本转语音,把模型返回的文字读出来。

这里有个细节:鸿蒙的TTS引擎默认是离线合成的,音色比较机械。如果你想要更自然的音色,可以把模型返回的文字再丢给MaaS平台上的语音合成接口,但那样会多一次网络请求。我实测下来,离线TTS虽然音色一般,但延迟几乎为零,对于导游场景来说“快”比“好听”更重要。

3. 核心细节解析与实操要点

3.1 鸿蒙端如何做图片压缩与编码

图片处理这块我踩了不少坑。鸿蒙的ImagePacker在压缩时,如果直接指定quality: 70,有时候出来的图片反而比原图还大,原因是它默认用的是PNG格式,PNG是无损压缩,质量参数对它影响不大。后来我改成了先解码成PixelMap,再用packToData指定JPEG格式,才真正把体积降下来。

import image from '@ohos.multimedia.image'; async function compressImage(uri: string): Promise<ArrayBuffer> { const imageSource = image.createImageSource(uri); const pixelMap = await imageSource.createPixelMap({ desiredSize: { width: 800, height: 600 } }); const packer = image.createImagePacker(); const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 70 }; const data = await packer.packToData(pixelMap, packOpts); return data; }

这段代码的关键是desiredSize,它会在解码阶段就做缩放,而不是先解全图再缩,内存占用小很多。我试过在低端鸿蒙设备上跑,如果不加这个参数,直接解一张4000x3000的图,App直接闪退。

3.2 流式请求的鸿蒙端实现

鸿蒙的http模块默认是把整个响应体收完才回调,但MaaS的流式输出是分块返回的。这里需要用on('dataReceive')事件来逐块读取。我一开始没注意,直接用request方法,结果等了好几秒才拿到完整回复,流式完全没起作用。

import http from '@ohos.net.http'; function streamChat(prompt: string, onChunk: (text: string) => void) { const httpRequest = http.createHttp(); httpRequest.on('dataReceive', (data: ArrayBuffer) => { const decoder = new util.TextDecoder('utf-8'); const text = decoder.decodeWithStream(new Uint8Array(data), { stream: true }); onChunk(text); }); httpRequest.request( 'https://api.example-maas.com/v1/chat/completions', { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_API_KEY' }, extraData: JSON.stringify({ model: 'general-chat', messages: [{ role: 'user', content: prompt }], stream: true, temperature: 0.7, max_tokens: 512 }) } ); }

提示:decodeWithStream的stream: true参数很关键。如果不加,每个数据块单独解码,中文字符被截断时会出现乱码。我一开始就遇到了“太和殿”变成“太口殿”的情况,排查了半天才发现是解码问题。

3.3 位置围栏与场景触发逻辑

导游场景有个特殊需求:用户走到某个景点附近时,App应该主动推送讲解,而不是等用户问。鸿蒙的geoLocationManager可以设置地理围栏,但精度有限,在故宫这种建筑密集的地方,GPS漂移经常有十几米误差。我的做法是围栏触发+手动确认:当用户进入某个景点的50米范围时,App弹出一个轻提示“是否了解当前景点”,用户点一下才发起请求。这样既不会误触发,也不会让用户觉得被骚扰。

围栏的坐标数据我是提前在MaaS平台上用另一个模型生成的——把故宫的景点列表和大致经纬度喂给模型,让它输出每个景点的围栏中心和半径。虽然模型给的坐标不一定精确到米级,但作为触发参考足够了。

4. 完整实操流程与核心环节实现

4.1 第一步:在MaaS平台创建应用并获取凭证

登录蓝耘元生代MaaS控制台,进入“应用管理”,点击“新建应用”。应用类型选“对话补全”,模型选通用对话模型。创建完成后,在应用详情页能看到API Endpoint和API Key。这两个值要记下来,后面鸿蒙端要用。

这里有个省钱技巧:MaaS平台通常按token计费,我一开始没注意,每次请求都把整个对话历史带上,导致token消耗很快。后来改成了滑动窗口,只保留最近三轮对话,更早的直接丢掉。导游场景下用户一般不会连续问超过三个问题,所以这个策略完全够用。

4.2 第二步:鸿蒙工程配置与权限申请

在module.json5里需要声明以下权限:

{ "requestPermissions": [ { "name": "ohos.permission.INTERNET" }, { "name": "ohos.permission.MICROPHONE" }, { "name": "ohos.permission.LOCATION" }, { "name": "ohos.permission.READ_IMAGEVIDEO" } ] }

麦克风和定位权限是动态权限,需要在代码里用abilityAccessCtrl主动申请。我遇到过一个坑:如果用户拒绝了麦克风权限,后续再调用录音接口会直接抛异常,而不是弹窗再次询问。所以代码里必须做异常捕获,引导用户去设置页手动开启。

4.3 第三步:构建Prompt模板

模型返回的质量很大程度上取决于Prompt怎么写。我试过直接问“介绍一下太和殿”,模型返回的内容很泛,像百科词条。后来改成了带角色设定的模板:

你是一位在故宫工作了二十年的资深导游,说话风格亲切自然,喜欢用讲故事的方式介绍景点。用户当前位于{景点名称}附近,请用不超过200字介绍这个景点的历史背景和一个有趣的细节。不要使用“首先”“其次”这类书面语。

这个模板里,{景点名称}是动态替换的。加了角色设定之后,模型返回的内容明显更有温度,比如它会说“您眼前这座太和殿,老百姓常叫它金銮殿,但您知道吗,其实明朝的时候它差点被改名叫皇极殿……”这种带转折的叙述,比干巴巴的百科有意思多了。

4.4 第四步:端到端联调与延迟优化

联调阶段我主要盯三个指标:首字延迟、完整响应时间、端侧CPU占用。首字延迟指的是从用户松手(停止录音)到听到第一个字的时间。优化前是2.8秒,优化后降到了1.2秒。主要做了三件事:

  1. 图片压缩从主线程移到Worker线程,避免阻塞UI。
  2. 流式请求的dataReceive回调里直接触发TTS,不等完整句子。
  3. 把MaaS的Endpoint换成了离用户最近的区域节点(蓝耘元生代支持多区域部署,选就近节点能省不少网络时间)。

端侧CPU占用方面,录音和压缩同时进行时,低端设备会掉帧。我的做法是串行化:先录音,录音结束后再压缩图片,两个操作不同时跑。虽然总时间长了0.3秒,但用户体验反而更流畅,因为不会卡顿。

5. 常见问题与排查技巧实录

5.1 模型返回内容不相关或胡编

这是最常见的问题。表现是用户问“这个殿是干嘛的”,模型回答“故宫是中国明清两代的皇家宫殿”。排查下来原因有两个:一是Prompt里没有明确限定回答范围,二是温度设得太高。

解决方法:在Prompt里加一句“只回答与{景点名称}直接相关的内容,如果问题与景点无关,请回复‘这个问题我建议您咨询专业导游’”。同时把温度从1.0降到0.7。实测下来,胡编率从大概30%降到了5%以下。

5.2 流式输出中文乱码

前面提过,是解码方式的问题。除了decodeWithStream,还有一个更稳妥的做法:在MaaS请求里加一个参数stream_options: { include_usage: true },这样每个数据块都是完整的JSON,不会出现半个汉字被截断的情况。虽然数据量稍微大一点,但稳定性高很多。

5.3 鸿蒙TTS播报断句不自然

离线TTS引擎对长句的处理不太好,经常在奇怪的地方停顿。我的解决办法是在模型返回的文字里手动插入停顿标记。比如把“太和殿始建于明永乐十八年”改成“太和殿,始建于,明永乐十八年”。逗号会让TTS引擎自然停顿。这个技巧是我试了十几种标点组合之后找到的最优解。

5.4 定位漂移导致围栏误触发

在故宫这种地方,GPS信号被建筑遮挡,漂移是常态。我的做法是双重判断:GPS围栏触发后,再检查用户是否连接了景区的WiFi(如果有的话),或者检查最近30秒内用户是否移动超过10米。如果没移动,说明可能是漂移,不触发。这个逻辑虽然不能100%解决,但误触发率降了大概七成。

5.5 常见问题速查表

问题现象可能原因排查方法解决措施
首字延迟超过3秒图片未压缩或网络节点远抓包看上传数据量端侧压缩+就近节点
模型回答与景点无关Prompt未限定范围打印实际发送的Prompt加角色设定和范围限制
中文乱码流式解码方式错误检查decodeWithStream参数加stream:true或改用完整JSON
TTS断句奇怪长句无标点听录音定位断句位置手动插入逗号
围栏误触发GPS漂移记录触发时经纬度加移动距离判断
低端设备卡顿录音和压缩并发用DevEco Profiler看CPU串行化处理

6. 这套方案还能怎么扩展

跑通基础版本之后,我又试了几个扩展方向。一个是多模态输入,让用户直接拍照问“这个屋檐上的小兽是什么”,鸿蒙端把图片和文字一起发给MaaS平台的多模态模型。实测下来识别准确率还不错,但延迟比纯文字高了大概0.8秒,因为图片编码和传输需要时间。

另一个方向是离线兜底。景区里有些地方信号很差,这时候可以预先把热门景点的讲解文本缓存在鸿蒙端,用端侧的小模型做简单问答。虽然回答质量不如云端大模型,但至少不会完全不能用。鸿蒙的@ohos.ai框架里有一些轻量级NLP能力,做关键词匹配和模板回复够用了。

还有一个我觉得挺有意思的扩展是游客画像。如果用户授权,App可以记录他问过哪些问题、在哪些景点停留时间长,然后生成一份“游览报告”。这个报告本身也可以用MaaS平台生成,把用户的游览轨迹喂给模型,让它写一段个性化的总结。我试了一版,模型写出来的东西还挺像那么回事,比如“您今天在太和殿停留了15分钟,看来对皇家建筑很感兴趣,下次可以去看看角楼”。

最后分享一个我在调试过程中发现的小技巧:MaaS平台的API Key如果直接写在鸿蒙代码里,反编译很容易泄露。我的做法是在MaaS控制台设置IP白名单和调用频率限制,即使Key泄露了,别人也用不了。另外鸿蒙端可以用@ohos.security.cryptoFramework对Key做一层简单加密,虽然不能完全防住,但至少提高了门槛。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询