【HarmonyOS学习笔记】2026-08-27 | 端插件踩坑与设计取舍
date: 2026-08-27
tags: HarmonyOS, 端插件, InsightIntentEntry, 小艺开放平台, LLM, 踩坑, 设计取舍
type: 实战笔记
一、optional 参数被跳过------description 太弱
现象
测试中,title 有值但 scheduledDate 仍然为空:
onExecute START: title=去XX地点做XX事, parentId=, scheduledDate=
用户说了"明天",但 LLM 没有把"明天"提取到 scheduledDate。
根因
scheduledDate 的 description 太弱:"计划日期,如明天"。对 optional 参数,LLM 倾向跳过;description 不够强时,LLM 倾向于把时间信息融进 title 而不是单独提取。
修复
这个问题有三种思路,各有取舍:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 提示词加强 | description 写得更明确,强调"单独提取" | 简单,不改架构 | 依赖 LLM 理解,不稳定 |
| 端侧实体提取 | textProcessing 等 API 在端侧提取时间实体,不靠 LLM | 确定性强,不依赖 LLM | 需要额外代码,只能处理格式化时间 |
| 独立时间端插件 | 单独建一个端插件,告诉模型当前日期和时间,由模型自己判断用户说的"明天"是哪天 | 模型有完整时间上下文,判断更准 | 多占一个端插件名额 |
我当前的做法:提示词加强 + 独立时间端插件组合。 description 写清楚时间格式和提取要求,同时通过独立时间端插件让模型知道"今天是几号",模型有了当前时间上下文后自己判断"明天"对应哪个日期,比纯 description 更稳定。
教训
optional 参数的 description 需要比 required 参数更强。 但 description 不是万能的------对于时间这种需要上下文才能判断的信息,光靠 description 让 LLM 提取是不够的。端侧实体提取可以兜底确定性格式的时间,独立时间端插件可以补足"当前时间"这个上下文缺口。三者组合比单靠任何一个都稳。
二、LLM "假装"调工具------文字模拟成功
现象
某次测试中,小艺回复"已为你创建了一个明天去吃牛肉的记录",但端侧没有任何 onExecute 日志。看起来像调了 CreateRecord,实际没调。
根因
LLM 模式的智能体,LLM 有时会选择不调用工具,而是用文字描述"已经做了"来回复用户。这在以下场景更容易发生:
- 插件刚重新发布,LLM 的缓存还没更新
- LLM 对工具调用的信心不足(参数不确定等)
- LLM 觉得文字回复就够了
验证方法
- 查端侧日志:有
onExecute START→ 真实调用;没有 → LLM 假装 - 查返回值:真实调用会返回
_source: 'MyEndPlugin'和_timestamp - 查小艺回复:真实调用后小艺会说"工具返回结果";假装时小艺直接说"已为你创建"
修复
我在提示词中约束了"必须基于工具返回值回复,不得编造结果"。同时在返回值中加入可观测标记(_source 和 _timestamp),方便区分真实调用和文字模拟。
第二种形态:拿提示词示例当事实
比凭空编造更隐蔽的一种情况------模型直接拿系统提示词(角色指令)中的示例数据当作事实回复。
比如提示词里写了:"用户说'帮我记录明天去XX',你应该调用 CreateRecord",模型可能直接回复"已为你创建了明天去XX的记录"------内容看起来很具体,但端侧没有执行记录。
这种比泛泛的假装更难识别,因为回复内容不是凭空编的,而是有"出处"(提示词示例),看起来更真实。但本质一样------没有真实调用端插件,回复就不是基于真实数据的。
修复方案和上面一样:端侧可观测标记(_source + _timestamp)+ 提示词约束"必须基于工具返回值回复,不得编造结果"。可观测标记是物理证据,提示词约束是逻辑防线,两个一起上才能确认工具是否真实执行过。
教训
不能只看小艺回复判断工具是否被调用。 LLM 可能用文字模拟工具调用结果,必须看端侧日志或返回值中的可观测标记确认。这不是 bug,这是 LLM 的特性------无论是凭空编还是拿提示词示例当素材,本质都是"没有真实执行却假装执行了"。需要在系统层面用可观测标记 + 提示词约束双防线防范。
三、覆盖安装导致端插件注册失效------code=204
现象
新增端插件后,从 DevEco Studio 直接 Run 覆盖安装,所有插件报 code=204("未安装或未升级")。App 能正常打开,界面正常,但小艺调不了任何端插件。
根因
覆盖安装时,Insight Intent 的注册缓存可能不一致------旧版本的注册信息残留在系统中,新版本没有完全覆盖。这是系统层面的注册缓存问题。
修复
这种缓存干扰的发生率其实不高,不一定每次覆盖安装都会触发。一般的覆盖安装是正常的,不需要每次都卸载重启。
出现 204 时的排查顺序:
- 先直接覆盖安装测试,多数情况下是 OK 的
- 如果 204 出现,且确认是新增/修改了 insight_intent.json 注册条目,再走完全卸载 → 重启设备 → 重新安装 → 手动打开应用一次
- 开发阶段如果频繁改注册条目,才需要经常走完整卸载流程
教训
新增或修改 insight_intent.json 注册条目后首次部署,有可能遇到注册缓存不一致导致 204。 但不需要把"卸载重启"当作每次覆盖安装的标配------只在改了注册条目后首次部署时留意即可。遇到 204 先判断是缓存问题还是其他原因(比如下一个坑),别上来就卸载重启。
四、intentVersion 不一致------又报 204
现象
新增一个端插件后,小艺调用报 code=204("未安装或未升级")。这次不是覆盖安装的问题------已经卸载重装了,重启也试了,其他插件正常,唯独这个新插件报 204。
根因
端侧 @InsightIntentEntry 装饰器中的 intentVersion 必须与小艺平台侧填写的版本号完全一致。本次端侧写了 '0.0.2',但小艺平台填的是 '0.0.1',版本号不匹配 → 系统认为插件未注册 → 204。
即使该版本号从未被注册过,不一致也会导致引用失败。版本号是两端合约的一部分,不匹配就等于"找不到插件"。
修复
将端侧 intentVersion 改为 '0.0.1',与平台一致。
教训
端插件 intentVersion 必须和小艺平台填的版本号完全一致。 新建插件时统一用 '0.0.1' 起步,改版时两端同步更新。这和参数名两端一致的道理一样------版本号也是两端合约的一部分。坑 3 和坑 4 的报错信息一样(都是 204),但根因完全不同------一个是系统缓存问题,一个是版本号合约问题。遇到 204 时先排查哪个,能省很多时间。
五、端插件数量限制与功能聚合策略
20 个数量限制
小艺开放平台工作流中加入的端插件有 20 个的数量限制。当端插件数量接近这个上限时,需要考虑如何优化。
功能聚合方案
可以在一个端插件中集成多个功能,用一个字段(如 action 参数)控制具体执行哪个功能:
@InsightIntentEntry({
intentName: 'RecordManager',
parameters: {
action: { type: 'string' }, // 'create' | 'update' | 'delete' | 'query'
title: { type: 'string' },
...
}
})
对外只算 1 个端插件,实际内部根据 action 字段分发到不同逻辑。
对谁有效,对谁无效
| 智能体模式 | 功能聚合是否有效 | 原因 |
|---|---|---|
| LLM 模式(模型节点) | ✅ 有效 | 一个端插件只算 1 个名额,多个功能打包后能显著减少总数 |
| 工作流模式(流程中插件节点) | ❌ 无效 | 同一个端插件在不同步骤出现时,重复计算个数 |
代价:误调用风险
功能越多 → LLM 越容易混淆 → 误调用风险越高。一个端插件包含 create/update/delete/query 四个功能时,LLM 可能本该调 update 却调了 create,或者把 action 填错。
设计取舍
三个维度需要平衡:
| 维度 | 趋势 | 冲突 |
|---|---|---|
| 端插件数量 | 越少越好(受 20 个限制) | --- |
| 功能聚合度 | 越高越省名额 | 聚合越高 → 误调用风险越高 |
| 误调用风险 | 越低越好 | 风险低 → 功能拆细 → 数量多 |
我的做法:在 LLM 模式下,把语义相近、不易混淆的功能聚合到一个端插件(如 create+update 都是"写操作"),把语义差异大的功能保持独立(如 query 是"读操作")。在工作流模式下,功能聚合没有数量优化效果,但仍有维护便利性,视情况决定。
六、模型版本差异与分工
两个版本的特点
小艺开放平台提供了两种模型版本:
| 模型 | 工具调用 | 文本分析与输出 |
|---|---|---|
| deepseek 版 | 不稳定(可能假装/跳过工具调用) | 稳定(分析准确、输出格式规范) |
| deepseek 增强版 | 稳定(更可靠地调用端插件) | 不稳定(输出格式/逻辑可能出问题) |
两种版本各有所长,也各有所短。
临时做法:各取所长
我目前的做法是把两种版本组合使用:
- deepseek 版 :负责分析问题、输出任务规划------需要准确理解和结构化输出,但不涉及工具调用
- deepseek 增强版 :负责执行端插件调用------需要可靠地调用工具并拿到返回值,对输出格式要求相对低
本质
这不是"哪个模型更好"的问题,而是不同模型有不同的能力边界。分析能力强的做分析,执行能力强的做执行------和端插件功能聚合的取舍逻辑一样,都是在约束条件下寻找最优分工。
学习小结:这篇笔记的核心是"取舍"。optional 参数提取不是只靠 description,三种方案(提示词/端侧实体/独立时间端插件)各有适用场景,我选择了提示词+独立时间端插件的组合。覆盖安装缓存干扰发生率不高,不需要每次都卸载重启,但要能快速判断 204 是缓存问题还是版本号问题。端插件 20 个数量限制引出了功能聚合策略------聚合能省名额但增加误调用风险,对模型节点有效对流程节点无效,三者平衡是关键。两个模型版本各有所短,各取所长的分工比强求一个模型包打天下更实际。端插件开发中"取舍"比"最优"更常见------没有银弹,只有适合当前场景的方案。
懿路向前 · AI辅助整理
2026-08-27