HarmonyOS 6.1 全场景实战|《灵犀厨房》实战(番外篇):【AI 推荐】场景优先的智能推荐引擎——从"偏好不可靠"到"食材即真理"

摘要:上一篇我们为《灵犀厨房》接入了 AI 食材识别,用户拍照即可识别冰箱里的蔬菜肉类。但推荐结果却让人哭笑不得:明明识别出"西兰花、白菜",推荐列表却全是"青椒肉丝、脆皮五花肉、酱骨架"——用户偏好标签"高蛋白"完全绑架了推荐逻辑!本篇,我们将重构推荐引擎的核心算法,建立"场景优先,偏好辅助"的评分体系,通过食材分类表、类型一致性检查、偏好冲突检测三大机制,让推荐结果真正贴合用户的实际烹饪场景。


一、引言:当 AI 识别遇上用户偏好

经过前 19 篇的积累,《灵犀厨房》已经掌握了语音播报、声控操作、通知提醒、元服务直达、桌面卡片等核心能力。但在 AI 推荐这个"招牌功能"上,却存在一个致命缺陷:

场景 用户操作 识别结果 推荐结果 问题
冰箱有蔬菜 拍照识别 西兰花、白菜 青椒肉丝、脆皮五花肉 ❌ 偏好"高蛋白"绑架推荐
冰箱有肉类 拍照识别 猪肉、鸡肉 蒜蓉西兰花、醋溜白菜 ❌ 偏好"快手菜"误导推荐
用户改偏好 切换到"素食" 西兰花、白菜 蒜蓉西兰花、香菇青菜 ✅ 但用户可能只是试试

问题根源:旧版推荐引擎的评分权重中,偏好标签权重(30分)高于食材匹配权重(20分),导致用户随意修改的偏好标签完全主导了推荐结果,而真正反映用户意图的食材识别结果反而成了"配角"。


二、核心原理:场景优先的评分体系

2.1 权重重构:食材为王

// ---- 评分权重常量(新版)----
const SCORE_INGREDIENT_BASE = 50;      // 食材匹配基础权重(最高)
const SCORE_INGREDIENT_FULL = 30;      // 全匹配奖励
const SCORE_PREF_TAG = 20;             // 偏好标签基础权重(降级)
const SCORE_PREF_CONFLICT = -25;       // 偏好冲突惩罚
const SCORE_SEASON = 15;               // 季节权重
const SCORE_TYPE_MISMATCH = -30;       // 类型不一致惩罚
const RECENT_PENALTY = -20;            // 历史惩罚

权重对比

维度 旧版权重 新版权重 变化
食材匹配 20 50 ⬆️ +150%
全匹配奖励 - 30 🆕 新增
偏好标签 30 20 ⬇️ -33%
类型不一致 - -30 🆕 新增
偏好冲突 - -25 🆕 新增

2.2 食材分类表

建立 7 大类食材分类表,用于识别食材类型和检测类型一致性:

const FOOD_CATEGORIES = new Map<string, string[]>([
  ['vegetables', ['西兰花', '白菜', '菠菜', '生菜', '番茄', '黄瓜', '茄子', '土豆', '胡萝卜', '洋葱', '蒜', '姜', '青椒', '豆芽', '蘑菇', '香菇', '青菜', '芹菜', '韭菜', '豆角']],
  ['meat', ['猪肉', '牛肉', '鸡肉', '羊肉', '鸭肉', '排骨', '里脊', '五花肉', '肘子', '骨架', '肉末', '肉丝', '肉片', '鸡块', '鸡翅', '鸡腿']],
  ['seafood', ['鱼', '虾', '蟹', '龙虾', '鱿鱼', '扇贝', '带鱼', '鲈鱼', '鲫鱼', '鲤鱼']],
  ['eggs', ['鸡蛋', '鸭蛋', '鹌鹑蛋', '蛋']],
  ['dairy', ['牛奶', '奶酪', '黄油', '奶油']],
  ['staples', ['米饭', '面条', '面包', '饺子', '包子', '馒头', '饼', '粥', '粉']],
  ['fruits', ['苹果', '香蕉', '橙子', '柠檬', '草莓', '葡萄', '西瓜', '梨', '桃', '菠萝']]
]);

2.3 偏好冲突检测

定义偏好标签与食材类型的冲突关系:

const PREFERENCE_CONFLICTS = new Map<string, string[]>([
  ['高蛋白', ['vegetables', 'fruits']],  // 高蛋白不适合纯蔬菜/水果场景
  ['低脂', ['meat']],                    // 低脂不适合纯肉类场景
  ['素食', ['meat', 'seafood']],         // 素食不适合肉类/海鲜场景
  ['清淡', ['meat']],                    // 清淡不适合重口味肉类场景
]);

三、分层架构:推荐引擎在 HSP 中的位置

按照《灵犀厨房》四层架构,推荐引擎位于共享库(HSP)的 Business 层:

📊 数据源

📦 共享库(Shared HSP)

📱 主应用(Entry HAP)

🏛️ Foundation 层

🧠 Business 层

import from 'shared'

import

读取

读取

查询

HomeViewModel.ets
UI 状态管理

HomeTabContent.ets
首页 UI

RecommendEngine.ets
核心推荐算法
场景优先评分

Recipe.ets
菜谱模型

UserPreference.ets
用户偏好模型

RelationalStore
菜谱数据库

图一解读:推荐引擎位于共享库的 Business 层,主应用通过 import { recommendEngine } from 'shared' 导入。这种架构确保主应用和元服务使用同一套推荐逻辑,避免代码重复和不一致问题。


四、关键实现步骤

Step 1:食材类型识别

/**
 * 识别食材类型集合
 * @returns 返回食材类型集合(如 {'vegetables', 'meat'})
 */
private identifyIngredientTypes(ingredients: string[]): Set<string> {
  const types = new Set<string>();
  for (const ing of ingredients) {
    FOOD_CATEGORIES.forEach((list: string[], type: string) => {
      if (list.some((item: string) => ing.includes(item) || item.includes(ing))) {
        types.add(type);
      }
    });
  }
  return types;
}

Step 2:菜谱主要类型识别

/**
 * 识别菜谱的主要食材类型
 * @returns 返回主要类型(如 'meat'、'vegetables')
 */
private getRecipeMainType(recipe: Recipe): string | null {
  const typeScores = new Map<string, number>();
  
  for (const ing of recipe.ingredients) {
    FOOD_CATEGORIES.forEach((list: string[], type: string) => {
      if (list.some((item: string) => ing.includes(item) || item.includes(ing))) {
        const currentScore = typeScores.get(type) ?? 0;
        typeScores.set(type, currentScore + 1);
      }
    });
  }
  
  // 返回得分最高的类型
  let maxScore = 0;
  let mainType: string | null = null;
  typeScores.forEach((score: number, type: string) => {
    if (score > maxScore) {
      maxScore = score;
      mainType = type;
    }
  });
  return mainType;
}

Step 3:偏好冲突检测

/**
 * 检查偏好标签与食材类型是否冲突
 */
private isPreferenceConflict(tag: string, ingredientTypes: Set<string>): boolean {
  const conflicts = PREFERENCE_CONFLICTS.get(tag);
  if (!conflicts) return false;
  
  // 如果识别的食材类型全部在冲突列表中,则判定为冲突
  for (const type of ingredientTypes) {
    if (!conflicts.includes(type)) {
      return false;  // 有不冲突的类型
    }
  }
  return ingredientTypes.size > 0;  // 只有当有识别食材时才判定冲突
}

Step 4:核心评分逻辑

private calcScore(recipe: Recipe, pref: UserPreference, ingredients: string[]): number {
  let score = 0;
  const debugParts: string[] = [];

  // 1. 食材匹配评分(场景核心,权重最高)
  if (ingredients && ingredients.length > 0) {
    const matchCount = ingredients.filter((ing: string) =>
      recipe.ingredients.some((ri: string) => ri.includes(ing) || ing.includes(ri))
    ).length;
    
    if (matchCount > 0) {
      const ingredientScore = matchCount * SCORE_INGREDIENT_BASE;
      score += ingredientScore;
      debugParts.push(`食材匹配+${ingredientScore}(${matchCount}/${ingredients.length})`);
      
      // 全匹配奖励
      if (matchCount === ingredients.length) {
        score += SCORE_INGREDIENT_FULL;
        debugParts.push(`全匹配奖励+${SCORE_INGREDIENT_FULL}`);
      }
    }
  }

  // 2. 食材类型一致性检查
  if (ingredients && ingredients.length > 0) {
    const ingredientTypes = this.identifyIngredientTypes(ingredients);
    const recipeMainType = this.getRecipeMainType(recipe);
    
    // 如果识别的是蔬菜/水果,但菜谱主要是肉类/海鲜,惩罚
    if ((ingredientTypes.has('vegetables') || ingredientTypes.has('fruits')) && 
        (recipeMainType === 'meat' || recipeMainType === 'seafood')) {
      score += SCORE_TYPE_MISMATCH;
      debugParts.push(`类型不一致${SCORE_TYPE_MISMATCH}(蔬菜→肉类)`);
    }
    
    // 如果识别的是肉类,但菜谱主要是蔬菜,轻微惩罚
    if (ingredientTypes.has('meat') && recipeMainType === 'vegetables') {
      score += Math.floor(SCORE_TYPE_MISMATCH / 2);
      debugParts.push(`类型不一致${Math.floor(SCORE_TYPE_MISMATCH / 2)}(肉类→蔬菜)`);
    }
  }

  // 3. 偏好标签评分(动态权重)
  const ingredientTypes = ingredients && ingredients.length > 0 
    ? this.identifyIngredientTypes(ingredients) 
    : new Set<string>();
  
  for (const tag of pref.favoriteTags) {
    if (recipe.tags?.includes(tag)) {
      // 检查偏好标签与食材类型是否冲突
      if (this.isPreferenceConflict(tag, ingredientTypes)) {
        score += SCORE_PREF_CONFLICT;
        debugParts.push(`偏好冲突${SCORE_PREF_CONFLICT}(${tag})`);
      } else {
        score += SCORE_PREF_TAG;
        debugParts.push(`偏好匹配+${SCORE_PREF_TAG}(${tag})`);
      }
    }
  }

  // 4. 季节匹配评分
  const season = this.getSeason();
  if (recipe.seasonTags?.includes(season)) {
    score += SCORE_SEASON;
    debugParts.push(`季节匹配+${SCORE_SEASON}(${season})`);
  }

  // 5. 历史惩罚
  if (this.recentIds.has(recipe.id)) {
    score += RECENT_PENALTY;
    debugParts.push(`历史惩罚${RECENT_PENALTY}`);
  }

  return score;
}

五、评分示例:场景对比

场景 1:识别蔬菜(西兰花、白菜)

菜谱 食材匹配 类型检查 偏好匹配 总分 排名
蒜蓉西兰花 +50(1/2) - +20(快手菜) 70 🥇
醋溜白菜 +50(1/2) - +20(快手菜) 70 🥈
香菇青菜 - - +20(快手菜) 20 🥉
青椒肉丝 - -30(蔬菜→肉类) +20(快手菜)-25(高蛋白冲突) -35
脆皮五花肉 - -30(蔬菜→肉类) +20(高蛋白) -10

场景 2:识别肉类(猪肉、鸡肉)

菜谱 食材匹配 类型检查 偏好匹配 总分 排名
红烧肉 +100(2/2)+30(全匹配) - +20(高蛋白) 150 🥇
青椒肉丝 +50(1/2) - +20(快手菜)+20(高蛋白) 90 🥈
口水鸡 +50(1/2) - +20(高蛋白) 70 🥉
蒜蓉西兰花 - -15(肉类→蔬菜) - -15

六、代码交付清单

文件 修改 职责
shared/src/main/ets/business/RecommendEngine.ets 重构 场景优先评分、食材分类、类型检查、冲突检测
shared/Index.ets 保持 导出 RecommendEngine 和 recommendEngine
entry/src/main/ets/viewmodel/HomeViewModel.ets 修改 从 shared 导入推荐引擎
entry/src/main/ets/business/RecommendEngine.ets 删除 移除主应用中的重复实现

七、架构优化:统一推荐引擎

7.1 问题发现

原架构中存在推荐引擎重复实现:

entry/src/main/ets/business/RecommendEngine.ets  ← 主应用版本
shared/src/main/ets/business/RecommendEngine.ets ← 共享库版本

主应用导入的是自己的版本,导致共享库中的优化无法生效。

7.2 修复方案

// 修复前:主应用导入自己的推荐引擎
import { recommendEngine } from '../business/RecommendEngine';

// 修复后:主应用导入共享库的推荐引擎
import { recommendEngine } from 'shared';

八、设计决策

决策 选择 理由
食材匹配权重 50 分(最高) 食材是用户意图的最直接表达,应作为推荐的核心依据
偏好标签权重 20 分(降级) 用户偏好可能随意修改,不应主导推荐结果
类型不一致惩罚 -30 分 识别蔬菜却推荐肉类,严重违背用户意图
偏好冲突惩罚 -25 分 "高蛋白"偏好与蔬菜场景冲突时,降低偏好权重
推荐引擎位置 Shared HSP 主应用和元服务共用一套逻辑,避免重复和不一致

九、总结与下篇预告

本篇我们重构了《灵犀厨房》的推荐引擎,建立了"场景优先,偏好辅助"的评分体系。通过食材分类表、类型一致性检查、偏好冲突检测三大机制,让推荐结果真正贴合用户的实际烹饪场景。

核心成就:

  • 食材匹配权重提升至 50 分,成为推荐的核心依据
  • 类型一致性检查,避免"识别蔬菜却推荐肉类"的尴尬
  • 偏好冲突检测,当偏好与场景冲突时自动降权
  • 统一到 Shared HSP,主应用和元服务共用一套逻辑

📚 本系列持续更新中:敬请关注。

🔗 专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包包括第1-15篇所有代码 + 架构文档 + Flask 后端
**如果你发现本文还有任何不严谨之处,欢迎随时指出,我们一起共建最优质的 HarmonyOS 6.1 学习内容!如果觉得有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬!
纯血鸿蒙,用心造厨。我们下一篇见!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐