1. 项目缘起与整体设计思路
国庆黄金周出行这件事,每年都在重复同一个剧本:提前半个月开始刷攻略,收藏夹里塞满了几十篇"必去景点""避坑指南",结果真到了出发那天,打开手机还是不知道该先去哪、几点出门、路上堵不堵、带老人小孩怎么调整节奏。信息不是不够,而是太多、太碎、太不贴合个人情况。我身边不少朋友最后干脆放弃规划,走到哪算哪,结果就是排队三小时、拍照五分钟。
这个项目要解决的就是这个具体问题:做一个能根据用户实时输入(出发地、天数、同行人构成、预算档位、偏好标签)自动生成可执行行程的"AI行程规划师",并且把它塞进手机里,让用户不用装新App、不用注册新账号,打开就能用。技术选型上我用了蓝耘元生代MaaS平台提供大模型推理能力,前端落在鸿蒙生态里做一个轻量级元服务卡片。选这个组合的原因很直接:MaaS把模型部署和调用的脏活累活接走了,我不用关心GPU在哪、显存够不够、并发怎么扩;鸿蒙元服务卡片则解决了"8亿人"这个量级下的触达问题——不用下载安装,负一屏、桌面卡片、搜索入口都能直接唤起。
先把这个项目的核心链路说清楚,后面再逐层拆。整体思路是四步:第一步,用户在鸿蒙卡片上填一个极简表单,字段控制在六个以内,避免劝退;第二步,卡片把结构化参数打包成一段提示词,通过HTTP请求发给蓝耘元生代MaaS的推理接口;第三步,模型返回结构化JSON,包含每天的时段、地点、交通方式、预计耗时、备选方案;第四步,卡片把JSON渲染成可折叠的时间轴视图,支持一键复制到备忘录或分享给同行人。
为什么强调"结构化JSON"而不是让模型直接输出一段自然语言?这是我在实际调试中踩过的第一个坑。早期版本我让模型直接吐一段"第一天上午建议去XX,下午去YY"的文字,结果前端没法解析,用户也没法按需折叠。改成强制JSON schema之后,前端渲染逻辑变得极其简单,而且可以在提示词里约束字段,比如duration_minutes必须是整数、transport只能从枚举值里选。这个改动让整个项目的可维护性上了一个台阶。
关于"8亿人"这个说法,需要说明一下:它指的是鸿蒙生态的潜在设备覆盖量级,不是说我这个卡片真能服务8亿人。但正因为面对的是这种量级的普通用户,产品设计上必须极度克制——不能有学习成本,不能有复杂配置,打开即用、用完即走。这也是我选择元服务卡片而不是完整App的根本原因。完整App要下载、要权限、要登录,每一步都在流失用户;卡片形态把这些摩擦全部消掉了。
适用人群方面,这个方案对三类人价值最大:一是技术开发者,想了解MaaS+鸿蒙这套组合怎么落地一个真实场景;二是产品经理,想看一个"AI+出行"的轻量级产品怎么设计交互;三是普通用户,如果你只是想找个能用的行程规划工具,照着本文的提示词模板和参数说明,自己也能复现一个。下面进入具体拆解。
2. 核心技术点深度拆解
2.1 为什么选MaaS而不是自己部署模型
自己部署模型这件事我做过,结论是:对于个人开发者和小团队,除非你有明确的私有化需求或者超大规模调用量,否则不划算。自己部署意味着你要处理GPU采购或租用、模型量化、推理框架选型、并发调度、故障转移、版本更新这一整套事情。我早期用一台带24G显存的机器跑一个70亿参数模型,光是环境配置和显存调优就花了两天,而且一到节假日调用高峰就排队。
MaaS(Model as a Service)的核心价值就是把上面这些全部托管掉。蓝耘元生代MaaS提供的是标准化的推理API,我只需要关心三件事:选哪个模型、怎么组织提示词、怎么处理返回结果。模型版本更新、算力扩容、稳定性保障都由平台侧负责。对于这个行程规划场景,我实测下来最合适的模型是中等参数规模的通义系或类似量级的中文对话模型——太小了规划质量不够,太大了响应慢且成本高。中等规模模型在"理解中文出行语境"和"响应速度"之间平衡得最好。
这里有个选型经验:不要一上来就选最大的模型。行程规划这个任务本质上是"结构化信息填充+常识推理",不需要模型有特别强的数学或代码能力。我对比过几个不同规模的模型,在同样的提示词下,中等规模模型生成的行程在合理性上和大模型差距很小,但首字延迟能低一半以上。对于卡片这种"用户等三秒就烦躁"的场景,延迟比那一点点质量提升重要得多。
2.2 鸿蒙元服务卡片为什么适合这个场景
鸿蒙元服务卡片最大的特点是"免安装、可直达"。用户不需要去应用市场搜索下载,卡片可以直接出现在桌面、负一屏或者通过搜索唤起。这个特性对行程规划场景太关键了——用户往往是在"马上要出门"或者"刚到酒店"这种碎片时间才想起来要规划,这时候让他去下载一个App,转化率极低。
卡片的技术形态是ArkTS写的UI组件,通过form_config.json声明卡片尺寸和刷新策略。我做了两个尺寸:2x2的小卡片只显示"今日行程"和"下一站",4x4的大卡片显示完整时间轴。小卡片适合出行途中快速查看,大卡片适合出发前做整体规划。这种"一鱼两吃"的设计让同一个服务覆盖了不同使用阶段。
另一个关键点是卡片和主应用(或元服务逻辑)之间的数据通道。鸿蒙提供了postCardAction机制,卡片上的按钮可以触发后台逻辑。我的实现是:用户在卡片上点"生成行程",触发一个后台任务去调用MaaS接口,拿到结果后通过updateForm刷新卡片内容。整个过程用户不需要离开桌面,体验非常顺。
2.3 提示词工程:把"规划师"的思考方式写进Prompt
这个项目里,提示词的质量直接决定输出质量。我前后迭代了十几版提示词,总结出几个关键设计原则。
第一,角色设定要具体。不要写"你是一个旅行助手",要写"你是一个有十年经验的国内自由行规划师,擅长为带老人和小孩的家庭设计节奏舒缓、避开高峰的行程"。角色越具体,模型的输出越贴近真实规划师的思路。
第二,约束条件要硬。我在提示词里明确写了:每天安排不超过4个主要节点,每个节点之间必须留出交通时间,午餐时间固定在11:30-13:00之间,如果同行人包含"老人"或"儿童",单日步行距离不超过5公里。这些硬约束是保证输出可执行的关键。
第三,输出格式要锁死。我用了JSON schema的方式,在提示词里给出完整的字段定义和示例,并要求模型"只输出JSON,不要输出任何解释性文字"。这一条极其重要,因为模型有很强的"想解释"的倾向,如果不强制,它会在JSON前后加一堆废话,导致解析失败。
第四,要留"备选方案"字段。真实出行中变数太多,天气、排队、体力都可能打乱计划。我要求模型为每个主要节点提供一个备选方案,比如"如果博物馆排队超过40分钟,改去旁边的XX公园"。这个字段让行程从"死计划"变成了"活方案",实用性提升明显。
2.4 结构化输出的字段设计与校验
模型返回的JSON我定义了这些核心字段:day_index(第几天)、time_slot(时段,枚举值:morning/afternoon/evening)、location(地点名)、activity(活动描述)、duration_minutes(预计耗时,整数)、transport(交通方式,枚举值:walk/subway/taxi/bus)、transport_minutes(交通耗时)、backup(备选方案)、tips(实用提示)。
拿到返回后,前端会做一层校验:检查必填字段是否存在、枚举值是否合法、duration_minutes是否为正整数。如果校验失败,会触发一次重试,重试时在提示词里追加"上次输出格式有误,请严格按照schema输出"。实测下来,加了这层校验和重试之后,解析成功率从最初的七成左右提升到了接近百分之百。
这里有个细节:transport_minutes这个字段我让模型自己估算,但会在提示词里给它一个参考表——比如"同城地铁相邻站之间约3分钟,跨区打车约20-40分钟"。给了参考之后,模型估算的交通时间明显更靠谱,不会出现"从城东到城西打车5分钟"这种离谱结果。
3. 实操过程与核心环节实现
3.1 环境准备与账号配置
先说要准备什么。蓝耘元生代MaaS这边,你需要注册账号、创建一个应用、拿到API Key和接口地址。具体路径是登录控制台后进入"模型服务"或"推理服务"板块,选择你要用的模型(我选的是中等规模的中文对话模型),然后生成一个API Key。这个Key要保管好,不要写死在客户端代码里。
鸿蒙这边,你需要DevEco Studio和一台能跑HarmonyOS的设备或模拟器。元服务卡片的开发需要创建"元服务"类型的工程,而不是普通App工程。创建时选择"卡片"模板,IDE会自动生成卡片的基本结构和配置文件。
注意:API Key绝对不能硬编码在卡片前端代码里。卡片的代码包是可以被反编译的,Key泄露会导致你的额度被盗用。正确做法是把Key放在你自己的后端服务里,卡片只调用你的后端,由后端去调MaaS。如果只是本地调试,可以用环境变量或配置文件临时存放,但上线前必须改掉。
3.2 提示词模板的完整实现
下面是我实际使用的提示词模板,做了脱敏处理,你可以直接参考这个结构改:
你是一个有十年经验的国内自由行规划师,擅长为不同人群设计节奏合理、避开高峰的行程。 用户输入: - 出发城市:{{city}} - 出行天数:{{days}}天 - 同行人:{{companions}} - 预算档位:{{budget}} - 偏好标签:{{preferences}} 硬性约束: 1. 每天安排不超过4个主要节点 2. 每个节点之间必须留出交通时间 3. 午餐时间固定在11:30-13:00,晚餐18:00-19:30 4. 如果同行人包含"老人"或"儿童",单日步行距离不超过5公里 5. 每个主要节点必须提供一个备选方案 交通时间参考: - 同城地铁相邻站之间约3分钟 - 跨区打车约20-40分钟 - 步行速度按每分钟80米计算 输出要求: 只输出JSON数组,不要输出任何解释性文字。每个元素包含以下字段: { "day_index": 整数,第几天,从1开始, "time_slot": "morning" | "afternoon" | "evening", "location": "地点名称", "activity": "活动描述,30字以内", "duration_minutes": 整数,预计停留时长, "transport": "walk" | "subway" | "taxi" | "bus", "transport_minutes": 整数,从上一节点到本节点的交通耗时, "backup": "备选方案,30字以内", "tips": "实用提示,50字以内" }这个模板的关键在于"硬性约束"和"交通时间参考"两段。前者保证输出的行程符合真实出行逻辑,后者让模型的时间估算有据可依。我试过删掉交通参考,结果模型给出的交通时间波动极大,同一个距离有时写10分钟有时写60分钟。
3.3 卡片端调用与渲染逻辑
卡片端的核心逻辑分三块:表单收集、请求发送、结果渲染。
表单收集部分,我用的是卡片内置的输入组件。考虑到卡片空间有限,我把字段做了精简:出发城市用文本输入,天数用步进器,同行人用多选标签(老人/儿童/成人/宠物),预算用三档选择(经济/舒适/品质),偏好用多选标签(自然风光/人文历史/美食/购物/亲子)。整个表单在一屏内能填完,不需要滚动。
请求发送部分,用户点"生成"按钮后,通过postCardAction触发后台任务。后台任务里用@ohos.net.http发起POST请求,把表单数据发给我的后端服务,后端再转发给MaaS。这里我加了一个loading状态,因为模型推理需要几秒到十几秒,没有反馈用户会以为卡死了。
结果渲染部分,拿到JSON数组后,按day_index分组,每组渲染成一个可折叠的卡片区块。每个节点显示时间、地点、活动、交通方式和耗时。备选方案和提示用小字放在节点下方,默认折叠,点击展开。整个渲染逻辑用ArkTS的ForEach实现,代码量不大,但要注意列表项的key要唯一,否则刷新时会出问题。
3.4 参数计算与性能调优
响应速度是这个项目的生命线。我做了几项优化。
第一,提示词精简。早期版本提示词有八百多字,后来压缩到四百字左右,去掉了所有不影响输出的描述性文字。提示词越短,模型处理越快。
第二,限制输出长度。我在请求参数里设置了max_tokens,根据天数动态计算:每天大约需要200个token,三天行程设800,五天设1200。不设上限的话,模型有时会输出很长的tips,拖慢响应。
第三,加缓存。同一个城市+天数+同行人组合的请求,结果缓存一小时。国庆期间热门城市的相似请求很多,缓存命中率不低,能显著降低平均响应时间。
第四,超时重试。设置8秒超时,超时后自动重试一次,重试时把max_tokens调低20%。实测下来,绝大多数请求能在5秒内返回,超过8秒的基本是网络抖动,重试一次通常能成功。
关于成本,中等规模模型的推理费用按token计,一次三天行程的请求大约消耗1500个输入token和800个输出token,成本在可接受范围内。如果做大规模推广,建议和平台侧沟通批量调用方案。
4. 常见问题与排查技巧实录
4.1 模型返回格式错误怎么排查
这是最高频的问题。表现是前端解析JSON时报错,或者渲染出来是空白。排查思路分三步:第一步,把模型的原始返回打印出来看,确认是JSON本身有问题还是解析逻辑有问题;第二步,如果是JSON有问题,检查提示词里的schema定义是否足够明确,特别是枚举值和必填字段;第三步,如果模型在JSON前后加了文字,在提示词末尾追加"再次强调:只输出JSON,不要有任何其他文字"。
我遇到过一个隐蔽的情况:模型返回的JSON里,字符串值包含了未转义的换行符,导致解析失败。解决办法是在提示词里要求"所有字符串值中不要包含换行符",同时在解析前做一次预处理,把裸换行替换成空格。
4.2 行程不合理怎么调整
模型偶尔会给出不合理的行程,比如把两个相距很远的景点排在一起,或者一天安排了六个节点。这类问题的根源通常是提示词约束不够硬。我的调整方法是:在提示词里增加"反面示例",比如"不要出现以下情况:一天超过4个节点、相邻节点交通时间超过60分钟、连续两个节点都是户外景点且中间没有休息"。
另一个技巧是让模型"自我检查"。在提示词最后加一句"输出前请检查:每天节点数是否超过4个、是否每个节点都有备选方案、交通时间是否合理。如果有问题请修正后再输出"。这一句话让输出质量提升了不少。
4.3 卡片刷新不及时怎么办
鸿蒙卡片有刷新频率限制,不能无限次刷新。我遇到的情况是用户点了生成,后台任务完成了,但卡片没更新。排查后发现是updateForm的调用时机不对——必须在后台任务里拿到结果后立即调用,不能延迟。另外,卡片刷新有最小间隔限制,如果用户连续点击,后面的刷新会被系统忽略。我的处理是在UI上加一个冷却时间,生成后按钮置灰10秒,避免用户狂点。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 解析JSON失败 | 模型输出含额外文字 | 打印原始返回 | 强化提示词格式约束 |
| 行程节点过多 | 约束不够硬 | 检查提示词约束段 | 增加反面示例和自我检查 |
| 交通时间离谱 | 缺少参考标准 | 对比实际距离 | 在提示词中给交通时间参考表 |
| 卡片不刷新 | 刷新时机或频率问题 | 检查updateForm调用 | 结果返回后立即刷新,加冷却 |
| 响应超时 | 提示词过长或输出过长 | 统计token数 | 精简提示词,限制max_tokens |
| API Key报错 | Key泄露或过期 | 检查控制台额度 | 更换Key,改为后端转发 |
4.5 几个踩过的坑和独家技巧
第一个坑:不要用"你是一个AI助手"这种泛化角色。我试过,输出质量明显不如"你是一个有十年经验的自由行规划师"。角色越具体,模型越能调用相关的"知识"。
第二个坑:不要在提示词里写"尽量""最好"这种软约束。模型对软约束的执行率很低。要写"必须""不超过""固定"这种硬约束。
第三个技巧:给模型一个"输出示例"。在提示词里放一个完整的JSON示例,模型会模仿这个格式。这比单纯描述字段有效得多。
第四个技巧:如果预算允许,可以对同一个请求调两次模型,取两次结果中更合理的那次。两次结果对比能过滤掉大部分明显不合理的输出。这个做法成本翻倍,但对质量要求高的场景值得。
第五个坑:鸿蒙卡片的调试比普通App麻烦,因为卡片运行在系统进程里,日志不好看。建议先在普通页面里把逻辑跑通,再移植到卡片。我一开始直接在卡片里调,排查一个问题花了大半天,后来改成先在页面里调通再移植,效率高很多。
5. 场景延展与后续可扩展方向
这个项目跑通之后,我发现这套"MaaS+鸿蒙卡片"的组合其实可以复用到很多类似场景。核心模式是一样的:用户在卡片上输入少量参数,后台调MaaS生成结构化结果,卡片渲染展示。把"行程规划"换成"菜谱推荐""健身计划""学习路径",技术链路几乎不用改,只需要换提示词和渲染模板。
具体到行程规划这个场景,后续可以扩展的方向有几个。一是接入实时数据,比如天气、景区人流、交通管制,让模型在生成时把这些因素考虑进去。这需要后端多接几个数据源,在提示词里把实时数据作为上下文传进去。二是做多轮调整,用户对生成的行程不满意时,可以点"调整"按钮,输入"第二天太累了"或"想多加一个博物馆",后台带着上一版结果和调整要求再调一次模型。三是做分享和协作,把行程生成一个链接或图片,同行人可以查看和评论。
从技术演进角度看,MaaS平台的能力在持续增强,未来可能会有更细粒度的模型选择、更低的延迟、更灵活的函数调用能力。鸿蒙生态的卡片能力也在迭代,比如更丰富的交互组件、更智能的推荐入口。这个项目现在的实现是一个可用的基线版本,后续跟着平台能力升级,能做的事情会越来越多。
我个人在实际操作中的体会是:做这类AI+前端的项目,最大的时间消耗不在写代码,而在调提示词和调交互。代码逻辑其实很简单,但要让模型稳定输出可用的结果,要让用户在卡片这种小空间里顺畅完成操作,需要反复试。我的建议是先把提示词在平台的控制台里调稳,确认输出质量达标了,再动手写前端。反过来先写前端再调提示词,会浪费很多时间在联调上。
最后分享一个小技巧:如果你也想做类似的项目,建议从最小的闭环开始——先做一个只支持"一个城市、一天、两个节点"的版本,把链路跑通,再逐步加字段、加约束、加渲染复杂度。我一开始就想做完整版,结果卡在提示词调优上很久,后来退回去做最小版本,两天就跑通了,然后再往上加功能,顺畅很多。