基于MaaS与鸿蒙端云协同的AI行程规划应用实战
1. 从一句标题拆出来的真实需求国庆黄金周出行这件事每年都在重复同一个剧本提前两周开始刷攻略收藏夹里塞了四五十篇笔记真到要排行程的时候还是一团乱麻。机票时间、酒店位置、景点开放时段、同行人的体力差异、临时想改路线……这些变量叠在一起靠人脑去排一个最优解基本等于用算盘跑深度学习。我这次做的事情就是把这套排行程的脏活累活交给大模型用蓝耘元生代 MaaS 平台做推理后端再套一层鸿蒙端的原生应用壳做成一个能对话、能改方案、能实时重排的“AI 行程规划师”。标题里说的“8 亿人”不是我要服务 8 亿用户而是这个场景的潜在受众规模——国庆期间有出行意愿的人群量级就在这个数量级任何一个做行程规划的 AI 应用面对的都是一个极其庞大且需求高度同质化的市场。先把这件事的边界说清楚。这个项目不是一个商业产品是我自己在假期前用两周时间搭出来的一套可运行方案核心解决三个问题第一用户用自然语言描述出行需求模型输出结构化行程第二行程不是一次性生成就完事用户中途改需求系统要能基于已有行程做增量重排第三整个交互跑在鸿蒙设备上调用云端 MaaS 推理服务端侧负责交互和状态管理云侧负责重推理。适合谁看这篇内容如果你是大模型应用开发者想找一个完整的“MaaS 平台 端侧应用”的落地案例这篇可以直接抄作业如果你是鸿蒙开发者想接入大模型能力但不确定怎么设计调用链路这篇的架构部分能帮你省掉不少试错如果你只是对 AI 行程规划这个场景感兴趣想知道背后是怎么跑起来的我也会把关键原理用生活化的方式讲明白。2. 整体架构设计与技术选型逻辑2.1 为什么是 MaaS 而不是自己部署模型做行程规划这件事对模型能力的要求其实不低。它需要理解自然语言里的模糊表达“不要太赶”“带老人”“想吃本地菜”需要做多约束条件下的排程推理还需要输出结构化的 JSON 让端侧能解析渲染。这种任务小模型扛不住自己部署大模型成本又太高。我算过一笔账如果自己租 GPU 部署一个 70B 级别的模型光是推理服务的运维成本按国庆期间可能出现的并发量来估算一个月下来够我买好几台鸿蒙设备了。而 MaaS 平台的核心价值就在于它把模型推理这件事变成了一个 API 调用你按 token 付费不用管底层 GPU 调度、不用管模型版本更新、不用管扩缩容。对于我这种个人开发者做验证性项目来说这是唯一理性的选择。蓝耘元生代 MaaS 平台在这件事上的优势我实际用下来感受比较深的有三点。一是模型选择灵活平台上有多个不同参数规模的模型可选我可以在开发阶段用大模型调 prompt上线前换成性价比更高的版本做推理切换成本几乎为零。二是 API 兼容性好接口设计跟主流推理服务的调用方式基本一致我原来写的一些调用逻辑改个 endpoint 和 key 就能跑。三是计费透明每次调用的 token 消耗在控制台里看得清清楚楚方便我做成本预估和优化。2.2 鸿蒙端为什么适合做这个场景的载体行程规划这个场景有一个很明显的特征用户的使用时机高度碎片化。可能在通勤路上突然想改一下明天的安排可能在景区排队时想临时加一个附近的点也可能在酒店晚上没事干的时候重新规划后天的路线。这种场景下打开一个网页、等加载、再输入需求体验链路太长了。鸿蒙端的原生应用能解决这个问题。桌面卡片可以做到一键唤起用户说一句话就能触发行程重排结果直接以卡片形式回显不需要进入完整应用。这个体验差异用过的人都知道回不去了。另外鸿蒙的分布式能力在这个场景里也有想象空间。比如用户在手机上排好的行程可以流转到平板或者车机上继续查看和修改状态是同步的。虽然我这次的项目里没有完整实现多设备流转但架构上留了口子端侧的状态管理用的是可序列化的数据结构后续要做流转不需要重构。2.3 端云职责划分的核心原则这个项目里端侧和云侧的职责划分我遵循一个原则端侧管交互和状态云侧管推理和生成。端侧负责的事情包括收集用户输入、维护当前行程的状态、渲染行程卡片、处理用户对行程的局部修改操作、管理本地缓存。云侧负责的事情包括理解用户意图、做排程推理、生成结构化行程数据、处理复杂的约束条件。为什么这么分因为推理这件事对算力和内存的要求端侧设备扛不住。而交互这件事对延迟和离线可用的要求云侧又保证不了。把两者分开各干各擅长的事整个系统的响应速度和稳定性都是最优的。有一个细节值得说端侧在发送请求之前会先做一轮本地预处理。比如用户说“把明天的故宫换成国博”端侧会先解析出这是一个“替换景点”的操作然后把当前行程里明天的安排、故宫的相关信息、国博的基本信息一起打包发给云侧。这样云侧拿到的上下文是完整的不需要再去查一遍当前行程状态推理效率会高很多。3. 核心细节解析与实操要点3.1 Prompt 工程怎么让模型输出可解析的行程这是整个项目里最花时间的地方。大模型很聪明但它的聪明有时候是负担——你让它排行程它可能会给你写一篇散文读起来很优美但端侧解析不了。我的做法是设计一套严格的输出格式约束。Prompt 里明确要求模型以 JSON 格式输出并且给出完整的 schema 定义。比如每一天的行程必须包含date、activities数组每个 activity 必须包含time_slot、location、duration、notes四个字段。这些字段的类型和取值范围都在 prompt 里写清楚。但光有格式约束还不够。实际跑下来发现两个问题一是模型有时候会在 JSON 外面包一层解释性文字导致解析失败二是模型对时间槽的划分有时候会重叠比如上午安排了两个活动但时间对不上。针对第一个问题我在 prompt 里加了一句“直接输出 JSON不要包含任何其他文字”并且在端侧做解析的时候加了一层容错——先用正则提取 JSON 块再解析。针对第二个问题我在 prompt 里明确了时间槽的划分规则上午 08:00-12:00下午 13:00-18:00晚上 19:00-22:00每个时间段最多安排两个活动且活动时长之和不能超过时间段长度。实操心得prompt 里的格式约束最好用“正面示例 反面示例”的方式写。只告诉模型“要输出 JSON”它可能理解不到位给它看一个正确的 JSON 样例和一个错误的样例它就能准确理解你的意图。这个技巧在多个模型上都验证过效果很稳。3.2 增量重排用户改需求时怎么不推翻重来一次性生成行程不难难的是用户中途改需求。比如用户说“第二天下午不想去景点了想找个咖啡馆坐坐”这时候如果让模型重新生成整个行程第一天的安排可能也会变用户会觉得很困惑。我的解决方案是设计一套“增量重排”的 prompt 模板。核心思路是把当前行程作为上下文传给模型明确告诉它“只修改第二天下午的安排其他部分保持不变”。同时在 prompt 里给出修改的约束条件比如“新安排的活动必须与前后活动在地理位置上合理衔接”“不能与已预订的餐厅时间冲突”。这套逻辑跑下来模型的输出稳定性比全量重排高很多。但有一个坑要注意模型有时候会“过度理解”用户的意图比如用户只是想把一个景点换成另一个模型却把前后几个活动都调整了。解决办法是在 prompt 里加一句“除非用户明确要求否则不要修改未提及的行程部分”并且在端侧做 diff 校验如果发现未提及的部分被修改了就回滚到修改前的状态只保留用户明确要求修改的部分。3.3 鸿蒙端的状态管理与渲染优化鸿蒙端的应用我用的是 ArkTS 开发状态管理用的是 AppStorage 和 LocalStorage 的组合。行程数据是一个嵌套比较深的对象直接放在 AppStorage 里会导致不必要的刷新。我的做法是把行程数据拆成两层顶层是行程的元信息日期范围、目的地、同行人数放在 AppStorage 里具体的每日安排放在 LocalStorage 里按天做 key 值隔离。这样用户修改某一天的安排时只有那一天的 UI 会刷新其他天的卡片不受影响。渲染方面行程卡片用的是 LazyForEach 做懒加载。国庆期间的行程可能有七八天每天又有好几个活动如果一次性全部渲染首屏加载会明显卡顿。用 LazyForEach 之后只有可视区域内的卡片会被渲染滚动的时候再动态加载实测下来首屏加载时间从 1.2 秒降到了 300 毫秒以内。还有一个细节行程卡片的布局用的是 Flex 而不是 Grid。因为活动卡片的数量是不固定的Flex 的自动换行和自适应能力比 Grid 更适合这种场景。而且 Flex 在鸿蒙上的渲染性能比 Grid 好尤其是在卡片数量多的时候差异比较明显。4. 实操过程与核心环节实现4.1 环境准备与依赖配置先说一下我用的开发环境。鸿蒙端用的是 DevEco Studio 4.0 版本SDK 版本是 API 10。MaaS 平台这边我选的是蓝耘元生代平台上提供的一个中等参数规模的模型具体型号就不说了反正平台上可选的模型挺多你可以根据自己的预算和效果要求来挑。端侧的网络请求库我用的是鸿蒙原生的ohos.net.http没有引入第三方库。原因很简单这个项目的网络请求逻辑不复杂就是 POST 一个 JSON 到 MaaS 的推理接口拿回一个 JSON 响应。原生库足够用而且没有额外的依赖打包体积也小。MaaS 平台的 API Key 管理要注意千万不要把 Key 硬编码在端侧代码里。我的做法是在应用启动时通过一个轻量级的鉴权服务换取临时 Token端侧只持有这个临时 Token有效期设的是 2 小时。这样即使 Token 泄露影响范围也有限。4.2 端侧请求构造与云侧响应解析端侧构造请求的时候有几个关键字段需要动态填充。一个是messages数组里面包含 system prompt 和 user prompt。system prompt 是固定的定义了模型的角色和输出格式要求user prompt 是动态的包含用户当前输入和当前行程的上下文。另一个是temperature参数。这个参数控制模型输出的随机性。排行程这件事我不希望模型太有创造力所以 temperature 设的是 0.3比默认值低不少。实测下来这个值能在保证输出多样性的同时避免模型生成过于离谱的安排。响应解析这块我写了一个专门的解析函数处理三种情况正常 JSON、带 markdown 代码块标记的 JSON、以及解析失败时的降级处理。降级处理的逻辑是如果 JSON 解析失败就把原始响应文本展示给用户并提示“AI 返回格式异常请重试或调整输入”。虽然这种情况很少发生但有了降级处理用户体验不会直接崩掉。// 端侧请求构造的核心逻辑简化版 interface TripRequest { messages: Array{ role: string; content: string }; temperature: number; max_tokens: number; } function buildRequest(userInput: string, currentTrip: TripState): TripRequest { const systemPrompt 你是一个专业的行程规划助手。请以 JSON 格式输出行程 每天包含 date 和 activities 数组每个 activity 包含 time_slot、location、 duration、notes 字段。直接输出 JSON不要包含其他文字。; const contextPrompt currentTrip ? 当前行程如下${JSON.stringify(currentTrip)}。用户的新需求${userInput} : 用户的出行需求${userInput}; return { messages: [ { role: system, content: systemPrompt }, { role: user, content: contextPrompt } ], temperature: 0.3, max_tokens: 4096 }; }4.3 行程数据的本地持久化方案用户排好的行程不能每次打开应用都重新生成。我在端侧做了本地持久化用的是鸿蒙的关系型数据库ohos.data.relationalStore。为什么不用 Preferences因为行程数据的结构比较复杂有嵌套的数组和对象Preferences 只适合存简单的键值对存这种结构化数据会很别扭。数据库表的设计是这样的一张trips表存行程的元信息一张activities表存具体的活动安排两张表通过trip_id关联。这样设计的好处是查询某一天的安排时只需要查activities表里对应日期的记录不需要把整个行程都加载出来。写入的时候要注意事务处理。用户修改行程时可能涉及多条记录的更新如果不用事务中途失败会导致数据不一致。我用的是beginTransaction和commit的显式事务确保要么全部成功要么全部回滚。4.4 一个完整的交互流程实录拿一个真实场景来走一遍。用户打开应用说“我国庆想去成都玩五天带爸妈不要太赶想吃本地菜”。端侧先把这句话和当前行程状态此时为空打包构造请求发给 MaaS 平台。模型返回一个五天的行程 JSON端侧解析后存入数据库同时渲染成卡片展示。用户看了之后说“第二天下午想休息一下别安排景点”。端侧识别出这是一个修改请求把当前行程和这句话一起发给模型。模型返回修改后的行程端侧做 diff 校验确认只有第二天下午变了然后更新数据库和 UI。用户又说“第三天晚上想吃火锅”。端侧同样处理模型在第三天晚上加了一个火锅店的活动。整个流程下来用户不需要填任何表单不需要选任何选项全程自然语言交互。注意事项模型返回的餐厅、景点信息最好在端侧做一轮校验。我遇到过模型编造不存在的餐厅名字的情况。解决办法是在 prompt 里加一句“只推荐真实存在的、有知名度的地点”并且在端侧维护一个热门旅游城市的地点白名单模型返回的地点如果不在白名单里就标记为“待确认”提示用户自行核实。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定的排查思路这是最高频的问题。表现是有时候模型返回纯 JSON有时候返回带 markdown 标记的 JSON有时候 JSON 里某个字段的类型不对比如 duration 应该是数字返回了字符串。排查步骤是这样的第一步先看 prompt 里的格式约束是否足够明确。如果只写了“输出 JSON”模型的理解可能不到位要给出完整的 schema 和示例。第二步检查 temperature 参数。temperature 太高会导致输出随机性增大格式出错的概率也会上升。第三步如果前两步都没问题那就是模型本身的能力边界了可以考虑换一个指令遵循能力更强的模型。我的经验是在 prompt 里加一句“如果无法按要求输出 JSON请输出一个空的 JSON 对象”能显著降低格式出错的概率。因为模型有了一个明确的“兜底选项”不会在无法满足要求时胡乱输出。5.2 行程安排不合理的问题定位模型排出来的行程有时候会出现“上午在城东下午在城西晚上又回城东”这种地理上不合理的情况。这个问题根因是模型对地理距离没有概念。解决办法有两个。一是在 prompt 里明确要求“同一天的活动地点应尽量集中避免跨区域往返”并且给出城市的分区信息作为参考。二是在端侧做后处理计算相邻活动地点之间的距离如果超过阈值就标记出来提示用户“此段行程距离较远建议调整”。我实测下来第一种方法的效果更好因为模型在生成阶段就考虑了地理因素比生成后再修正要自然。但第一种方法需要你在 prompt 里提供城市的分区数据这个数据可以从公开的旅游资料里整理不需要很精确大致分区就行。5.3 端侧渲染卡顿的性能优化行程卡片多的时候滚动会卡。用 DevEco Studio 的性能分析工具抓了一下发现主要耗时在卡片组件的重复创建和销毁上。优化手段有三个。第一用 LazyForEach 替代 ForEach只渲染可视区域内的卡片。第二把卡片组件里的图片加载改成异步不要阻塞主线程。第三减少卡片组件里的状态变量数量能用常量就不用状态变量因为状态变量的每次变更都会触发组件重新渲染。这三个手段叠加之后滚动帧率从 45fps 左右稳定到了 58fps 以上基本感觉不到卡顿了。5.4 常见问题速查表问题现象可能原因排查方向解决手段模型返回非 JSON 格式prompt 约束不明确检查 prompt 里的格式要求补充 schema 和示例加兜底指令行程地点跨区域往返模型无地理概念检查 prompt 是否提供分区信息补充城市分区数据加距离校验端侧滚动卡顿卡片组件重复创建用性能工具抓渲染耗时改用 LazyForEach异步加载图片修改需求后行程全变prompt 未限定修改范围检查增量重排的 prompt明确“只改指定部分”端侧做 diff 校验API 调用超时网络波动或模型负载高检查网络状态和平台状态加超时重试机制设置合理超时时间6. 成本控制与性能调优的实操经验6.1 Token 消耗的优化策略MaaS 平台按 token 计费行程规划这个场景的 token 消耗主要来自两部分输入侧的 prompt 和输出侧的行程 JSON。输入侧的 prompt 里system prompt 是固定的每次调用都会消耗 tokenuser prompt 里包含当前行程的上下文行程越长token 消耗越大。优化手段有三个。第一把 system prompt 精简到最核心的约束去掉冗余的描述性文字。我最初的 system prompt 有 500 多 token精简后降到了 200 以内效果没有明显下降。第二当前行程的上下文只传必要的字段不要把所有历史数据都塞进去。比如用户只修改第二天的安排就只传第二天的数据不需要传整个行程。第三输出侧限制 max_tokens避免模型生成过长的响应。行程 JSON 的长度是可以预估的设一个合理的上限既能满足需求又不会浪费 token。这三招下来单次调用的 token 消耗大概降了 40% 左右。对于个人开发者来说这个优化幅度意味着同样的预算能多跑很多次测试。6.2 响应延迟的优化用户对延迟的容忍度很低尤其是对话式交互。我实测下来从用户发送请求到看到行程卡片如果超过 3 秒用户就会觉得“卡了”。优化延迟的手段端侧和云侧各有一半。端侧这边把请求构造和网络发送做成异步不要阻塞 UI 线程同时加一个 loading 状态让用户知道系统在处理。云侧这边选择推理速度更快的模型版本或者在 prompt 里限制输出长度减少模型的生成时间。还有一个技巧对于常见的修改操作比如“换一个景点”“加一个餐厅”可以在端侧做本地缓存。如果用户的操作和之前的某次操作相似度很高直接返回缓存结果不需要调用模型。这个策略能显著降低高频操作的延迟但要注意缓存的有效期和失效策略避免返回过时的结果。6.3 模型选择的权衡蓝耘元生代平台上可选的模型不少我前后试了三个不同参数规模的模型。大模型的效果确实好行程安排更合理对模糊表达的理解也更准确但推理速度慢、成本高。小模型速度快、成本低但在处理复杂约束时容易出错。最终的方案是混合使用首次生成行程用大模型保证质量后续的增量修改用小模型保证速度和成本。这个策略的前提是增量修改的 prompt 里已经包含了完整的行程上下文小模型只需要做局部调整不需要做复杂的全局推理所以效果差距不明显。实操心得模型选择不是一锤子买卖要根据具体场景动态调整。我的做法是在端侧维护一个简单的路由逻辑如果用户输入里包含“重新规划”“全部重排”这类词走大模型如果只是“换一个”“加一个”“删掉”这类局部操作走小模型。这个路由逻辑用简单的关键词匹配就能实现不需要额外的模型来判断。7. 这个方案还能怎么扩展行程规划这个场景往深了做还有很多可以打磨的地方。我目前这个版本是一个最小可行方案验证了“MaaS 鸿蒙端”这条链路是跑得通的。后续如果要继续迭代我觉得有三个方向值得尝试。第一个方向是接入实时数据。现在的行程规划是基于静态信息的模型不知道景点今天是否开放、餐厅是否需要排队、路上是否堵车。如果能把实时的开放状态、排队时长、交通路况接入进来行程规划的准确度会有一个质的提升。技术上这需要在端侧增加数据拉取和融合的逻辑在 prompt 里把这些实时数据作为约束条件传给模型。第二个方向是多模态交互。现在用户只能打字或者语音输入如果能让用户拍一张照片比如拍一个景点的指示牌模型识别出地点后自动加入行程交互会更自然。鸿蒙端有原生的相机能力MaaS 平台也支持多模态输入这条链路在技术上是通的只是需要额外的开发工作量。第三个方向是行程的协同编辑。国庆出行往往是多人同行如果同行的人都能看到行程、都能提修改意见体验会好很多。这需要端侧增加数据同步的逻辑云侧增加冲突检测和合并的策略。鸿蒙的分布式能力在这个场景里能派上用场但具体的实现方案我还没有深入验证这里只是提一个思路。最后分享一个我在这个项目里踩过的坑不要试图让模型一次性把所有事情都做完。我最初的想法是用户说一句话模型直接输出一个完美的行程。实际跑下来发现模型在单次调用里能处理的约束条件是有限的约束太多反而会导致输出质量下降。后来改成多轮交互的方式第一轮生成框架后续轮次逐步细化效果反而更好。这个经验不仅适用于行程规划做任何大模型应用都值得参考——把复杂任务拆成多个简单任务让模型每次只专注做好一件事。