搜索框里输入“海边 日落”,图库却把一张海报也排进来,用户往往会说“搜索不准”。有时问题不在模型,而在图片的标签来自不同环节:导入文件名带了“日落”,人工标注补了“海边”,用户纠错又删掉了“人像”。如果所有标签都当成同样可信,就很难解释为什么一张图进了结果,另一张被排除。这次只讨论文搜图的可解释本地后备层,不是模拟一个具有自然语言理解能力的模型。Demo 叫 TagLens,页面为 TagLensPage,本地查询单 TL-032。搜索文字固定为 “海边 日落 -人像”,在六条人为构造的元数据中筛出 两张:IMG-211、IMG-204,界面状态标为 LOCAL_MATCH。其中 -人像 是 Demo 自己定义的排除标记,不是 HarmonyOS Search 组件或 Core Vision Kit 的内置语法。这里的照片 ID、标签和结果均为夹具,不代表真实相册内容。## 一、搜索结果首先要能说明“凭什么”对于图库应用,语义模型可能返回某张照片的相似度,但用户还希望知道:“这张照片明明没有人,为什么因为人像被排掉?”答案通常在业务层的元数据。无论图像向量来自端侧 AI、云服务还是本地手填,展示端都需要把匹配原因、排除原因、纠错来源与结果一起管理。HarmonyOS 7 的官方 AI 能力介绍已经将“通过文本搜索图片”列为 Core Vision Kit 相关新能力,但模型接口与输入限制不能由宣传名称倒推出来。本文保留后续对接语义召回的扩展口,现有实现限定为对本地标签的精确字符串匹配,不给它取名“语义相似度”。这样在不支持模型的设备或离线权限受限时,页面仍可给出清楚但有限的检索结果。为了讨论得具体一点,定义三种来源:manual 表示录入或确认过的标签;imported 表示随素材导入的旧标签;corrected 表示用户明确改过的标签。它们不等于系统级信任等级,只是 TagLens 自己维护的业务优先级,分别赋值 2、1、3。分数只用于两个结果都满足条件时决定展示顺序,不能拿去冒充模型置信度。### 1. 输入语法不必神秘我们把空格分隔的普通词解释为必须包含,在词前加半角减号解释为不得包含。所以“海边 日落 -人像”意味着标签必须同时有“海边”和“日落”,而且不能有“人像”。它既不支持复杂布尔表达式,也不能理解“傍晚在沙滩”与“海边日落”是否语义相近;这些是有意保留的能力边界。## 二、先把数据形状固定,再决定排序下方代码是 entry/src/main/ets/model/LocalMatcher.ets 的核心逻辑。它不依赖系统相册权限,也不会扫描设备照片,只接受调用方传进来的六条元数据。这有利于用确定性的夹具验证排除词优先级,并测试标签纠错后结果位置是否变化。tstype TagSource = 'manual' | 'imported' | 'corrected';export interface PhotoTag { value: string; source: TagSource; }export interface PhotoCard { id: string; tags: PhotoTag[]; }export interface MatchRow { id: string; score: number; reason: string; }const weight: Record<TagSource, number> = { manual: 2, imported: 1, corrected: 3};export function localMatch(cards: PhotoCard[], query: string): MatchRow[] { const words = query.trim().split(/\s+/).filter(v => v.length > 0); const must = words.filter(v => !v.startsWith('-')); const ban = words.filter(v => v.startsWith('-')).map(v => v.slice(1)); return cards.filter(card => { const labels = card.tags.map(tag => tag.value); return must.every(v => labels.includes(v)) && ban.every(v => !labels.includes(v)); }).map(card => ({ id: card.id, score: card.tags.filter(t => must.includes(t.value)) .reduce((sum, t) => sum + weight[t.source], 0), reason: `命中 ${must.join('、')};排除 ${ban.join('、')}` })).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));}这里明确采用“先过滤、再排序”。如果把排除规则设计成扣分,在结果很少时仍可能把含有人像的图片放回来,就违背了用户输入的硬条件。源码中的 reason 也没有写“AI 认为相关”,只列出命中的标签与主动排除项;发布版应区分“查询用了排除词”与“此图确实被排除”,避免把原因文字写得含混。Record 是 ArkTS/TypeScript 里常见的类型表达;权重值属于业务代码而非系统接口。localeCompare 只承担相同分数时的稳定排序。输入中若含多个空格、只有减号或重复词,生产版需要进一步规范化;此处让规范留在同一个模型文件,避免多个页面各自拆字导致不一致。## 三、用户纠错不能直接改掉“来源事实”小图库最常见的隐患是把标注对象在原数组里就地修改,然后 UI 与缓存各持有一份引用。一个页面显示新标签,另一个页面还在用旧索引,结果就会变得难解释。TagLens 的处理方式是:纠错产生新的 PhotoCard,原对象只读;UI 收到新数组后统一刷新。tsimport { PhotoCard, PhotoTag } from './LocalMatcher';export function correctTag(card: PhotoCard, before: string, after: string): PhotoCard { const preserved = card.tags.filter(t => t.value !== before); const newTag: PhotoTag = { value: after, source: 'corrected' }; const next = preserved.filter(t => t.value !== after); return { id: card.id, tags: [...next, newTag] };}对示例 IMG-211 来说,旧标签“黄昏”由用户纠正成“日落”;另一标签“海边”保留 manual 来源。这样它在当前查询中得到 5 分,高于 IMG-204 的两个 manual 标签合计 4 分。两张都满足硬过滤条件,分数只影响先后顺序。纠错本身的时间戳、操作者及撤销历史没有放进最小示例,正式产品应将它们作为审计字段保存,而不只是保留最终字符串。## 四、Search 只负责输入,别让 UI 偷换搜索规则页面层要克制。ArkUI Search 提供输入与 onChange 等交互事件,但不会自动替应用构建图像语义索引,更不知道 -人像 的业务含义。结果筛选由上面的纯函数负责;页面只更新查询文字、结果数和状态,避免把某个标签来源的特殊判断散落在组件里。tsimport { localMatch, PhotoCard } from '../model/LocalMatcher';const FIXTURES: PhotoCard[] = [ { id: 'IMG-204', tags: [{ value: '海边', source: 'manual' }, { value: '日落', source: 'manual' }] }, { id: 'IMG-211', tags: [{ value: '海边', source: 'manual' }, { value: '日落', source: 'corrected' }] }, { id: 'IMG-205', tags: [{ value: '海边', source: 'imported' }, { value: '日落', source: 'imported' }, { value: '人像', source: 'manual' }] }, { id: 'IMG-206', tags: [{ value: '海边', source: 'manual' }, { value: '清晨', source: 'manual' }] }, { id: 'IMG-207', tags: [{ value: '城市', source: 'manual' }, { value: '日落', source: 'manual' }] }, { id: 'IMG-208', tags: [{ value: '海边', source: 'manual' }, { value: '日落', source: 'manual' }, { value: '人像', source: 'manual' }] }];@Entry@Componentstruct TagLensPage { @State private query: string = '海边 日落 -人像'; @State private resultCount: number = 2; private update(value: string): void { this.query = value; this.resultCount = localMatch(FIXTURES, value).length; console.info(`[TL-032] LOCAL_MATCH count=${this.resultCount}`); } build() { Column({ space: 18 }) { Text('TagLens · 本地标签检索').fontSize(24) Search({ value: this.query, placeholder: '输入标签或 -排除词' }) .onChange((value: string) => this.update(value)) Text('LOCAL_MATCH · ' + this.resultCount + ' 条') Text('当前按本地标签匹配,非语义模型结果').fontColor('#64748B') }.padding(24).width('100%') }}这段最小 UI 没有把图片缩略图伪装成系统相册资源。实际绘制结果卡片时,再用 localMatch 返回的 ID 找到对应的业务图片,绑定明确的本地资源或经过授权的 URI。这里的初始 resultCount=2 与夹具一致,但真实工程通常在 aboutToAppear 或数据加载完成后运行一次检索,不应假定素材永远是六张。输入修改调用 update 会输出示例日志,若要让页面展示详细列表,还需将结果数组纳入响应式状态。## 五、怎样检验“纠错”和“排除”确实生效先看最直观的样例:IMG-205 与 IMG-208 虽然都有“海边”“日落”,但它们同时带“人像”标签,所以必须被排除。IMG-206 的“清晨”不是“日落”;IMG-207 虽有“日落”但没有“海边”。剩下 IMG-211 和 IMG-204,恰好两条。这里没有随机推荐,也没有模糊匹配,逐条检查标签就可以复核结果。然后模拟纠错事件。将 IMG-211 的“黄昏”改为“日落”之前,它不会命中当前正向条件;变更为 corrected 后,才能出现在第一位。删除查询中的 -人像,则 IMG-205 和 IMG-208 也应进入候选集合。若搜索字符串变成“-人像”而没有正向词,当前实现会找出所有不包含“人像”的图片;产品是否允许纯排除检索,应由业务产品定义,并给出明确的空查询行为。日志检查同样要避开假结论。合成 DevEco 画面中 TL-032 LOCAL_MATCH count=2 是设计数据,不是从模拟器录制的事件;交付时只要求图片与代码示例的查询、结果 ID、来源评分一致。在真实设备验证时,应额外记录查询耗时、索引规模、数据加载状态与纠错前后快照,用以区分过滤逻辑错误和界面未刷新。## 六、把可解释性留给正式语义召回这套标签匹配不是完整的文搜图。它不会理解“无人海岸的晚霞”和“海边 日落 -人像”之间的同义关系,也无法从像素本身推断“海边”。它的价值是帮助产品在模型暂不可用、用户刚完成手动整理、标签需要人工审计的情况下,提供一个知道自己为什么命中的保底通道。如果之后接入官方 Core Vision Kit 文搜图能力,应单独确认目标 SDK、能力可用性、模型数据授权与回调签名,再把语义候选与本地排除规则组合。最需要保留的约束是来源透明。用户修正过的标签可以获得更高排序权重,却不能自动说明图像客观内容;导入的旧标签也可能正确,只是需要更谨慎地展示。真正可用的检索产品应允许用户查看标签来自哪里、撤销纠错、报告错误召回,并区分“本地标签命中”与“模型语义召回”两种结果。这种诚实的能力标注,比做一个没有依据的百分比置信度更能减少误解。参考资料(2026-10-19 检索):- 华为 ArkUI Search 事件用法与输入示例 —— https://developer.huawei.com/consumer/cn/doc/doccenter-dev-faq/faqs-arkui-1249- 华为 ArkUI 搜索组件扩展能力 —— https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/api/ohos-atomicservice-atomicservicesearch- 华为开发者 AI 能力目录(含文搜图新能力说明)—— https://developer.huawei.com/consumer/cn/harmonyos-ai验证范围说明:排序与匹配结果可用给出的六条夹具静态复核;未在真机调用语义模型,也未声称访问了用户图库或进行过真实性能测试。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐