欢迎光临
我们一直在努力

当 AI 走进厨房:用大模型打造智能食谱推荐与营养规划系统

当 AI 走进厨房:用大模型打造智能食谱推荐与营养规划系统

cover

一、从"今天吃什么"到智能决策:日常饮食的痛点与破局

每天下班回到家,面对冰箱里零散的食材,脑子里只剩下一个问题——"今天吃什么?"这不是一个简单的选择,而是一个反复出现的决策疲劳。打开外卖 App,翻了半小时还是那几家;翻开食谱网站,步骤繁琐得让人望而却步。更让人头疼的是,长期外卖带来的营养失衡、体重失控,这些问题像温水煮青蛙一样悄悄累积。

我身边不少远程办公的朋友都有类似的困扰。居家工作后,三餐全部自己解决,反而比上班时更不知道该吃什么。有人靠泡面续命,有人天天点外卖,还有人干脆一天只吃两顿。饮食这件事,看似简单,实则涉及食材管理、营养搭配、口味偏好、烹饪时间等多维度约束,是一个典型的多目标决策问题。

大模型的出现,让这个问题有了新的解法。与其每天手动搜索食谱,不如让 AI 根据你冰箱里的食材、营养目标、口味偏好,自动生成个性化的食谱推荐和周度营养规划。这篇文章,就来拆解这套系统的设计思路和实现方案。

二、从食材到食谱:智能推荐引擎的架构与数据流

要构建一个真正好用的智能食谱推荐系统,核心在于把用户的"模糊需求"转化为"精准匹配"。整个系统的数据流如下:

graph TD
A[用户输入:冰箱食材清单] –> B[食材解析与标准化模块]
C[用户画像:口味偏好/过敏原/营养目标] –> D[约束条件构建器]
B –> E[食材向量编码]
D –> F[约束向量编码]
E –> G[食谱检索与排序引擎]
F –> G
G –> H[候选食谱集]
H –> I[大模型重排序与生成]
I –> J[个性化食谱推荐]
J –> K[营养分析模块]
K –> L[周度营养规划报告]
L –> M[购物清单自动生成]

关键设计点有三个。第一,食材解析需要处理自然语言的模糊输入,比如"半个西红柿""一小块姜"这类表达。第二,食谱检索不能只靠关键词匹配,需要用向量语义搜索来处理"土豆"和"马铃薯"这类同义关系。第三,大模型的重排序环节至关重要,它负责把检索结果调整为真正符合用户口味和烹饪能力的方案。

三、生产级实现:从食材识别到食谱生成的完整代码

3.1 食材解析与标准化

import re
from typing import List, Dict, Optional
from pydantic import BaseModel, Field

class IngredientItem(BaseModel):
"""食材条目模型,用于标准化用户输入"""
name: str = Field(…, description="食材标准名称")
quantity: Optional[float] = Field(None, description="数量")
unit: Optional[str] = Field(None, description="单位,如克、个、根")
category: str = Field(…, description="食材分类:蔬菜/肉类/调味品/主食等")

# 食材别名映射表(实际项目中从数据库加载)
INGREDIENT_ALIAS_MAP: Dict[str, str] = {
"土豆": "马铃薯", "番茄": "西红柿", "大葱": "葱",
"小葱": "葱", "肉末": "猪肉馅", "肉糜": "猪肉馅",
"鸡蛋": "鸡蛋", "蛋": "鸡蛋",
}

def parse_ingredients(raw_input: str) -> List[IngredientItem]:
"""
解析用户输入的食材清单,支持多种格式:
– 逗号/顿号分隔:西红柿2个,鸡蛋3个
– 自然语言:冰箱里有半个西红柿和一小块姜
"""
if not raw_input or not raw_input.strip():
raise ValueError("食材输入不能为空")

# 按常见分隔符拆分
parts = re.split(r'[,,、;;\\n]', raw_input)
results: List[IngredientItem] = []

for part in parts:
part = part.strip()
if not part:
continue

# 提取数量和单位
qty_match = re.search(r'(\\d+(?:\\.\\d+)?)\\s*(个|根|块|片|克|g|斤|两|把|勺|瓣|只)?', part)
quantity = float(qty_match.group(1)) if qty_match else None
unit = qty_match.group(2) if qty_match and qty_match.group(2) else None

# 提取食材名称(去除数量和单位)
name = re.sub(r'(\\d+(?:\\.\\d+)?)\\s*(个|根|块|片|克|g|斤|两|把|勺|瓣|只)?', '', part).strip()
name = re.sub(r'[的多少大小半]', '', name).strip()

if not name:
continue

# 别名标准化
standardized = INGREDIENT_ALIAS_MAP.get(name, name)

# 分类判断(简化版,实际用分类模型)
category = _classify_ingredient(standardized)

results.append(IngredientItem(
name=standardized,
quantity=quantity,
unit=unit,
category=category,
))

return results

def _classify_ingredient(name: str) -> str:
"""食材分类,实际项目中使用训练好的分类模型"""
categories = {
"蔬菜": ["西红柿", "马铃薯", "黄瓜", "白菜", "茄子", "青椒", "姜", "蒜", "葱"],
"肉类": ["猪肉馅", "鸡胸肉", "牛肉", "五花肉", "虾仁", "排骨"],
"调味品": ["盐", "酱油", "醋", "料酒", "蚝油", "豆瓣酱"],
"主食": ["米饭", "面条", "面粉", "馒头"],
"蛋奶": ["鸡蛋", "牛奶", "奶酪"],
}
for cat, items in categories.items():
if name in items:
return cat
return "其他"

3.2 语义检索与大模型重排序

from openai import OpenAI
import json
from typing import List, Dict

class RecipeRecommender:
"""食谱推荐引擎:向量检索 + 大模型重排序"""

def __init__(self, api_key: str, model: str = "gpt-4o-mini"):
self.client = OpenAI(api_key=api_key)
self.model = model

def generate_recommendations(
self,
ingredients: List[IngredientItem],
preferences: Dict,
calorie_target: int = 2000,
cooking_time_max: int = 45,
) -> List[Dict]:
"""
基于食材和偏好生成食谱推荐
ingredients: 解析后的食材列表
preferences: 用户偏好,如口味、过敏原、烹饪难度等
calorie_target: 每日热量目标(千卡)
cooking_time_max: 最大烹饪时间(分钟)
"""
ingredient_names = [i.name for i in ingredients]
ingredient_details = [
f"{i.name}({i.quantity}{i.unit})" if i.quantity else i.name
for i in ingredients
]

prompt = f"""你是一位专业的营养师兼厨师。请根据以下条件推荐3道菜:

可用食材:{', '.join(ingredient_details)}
口味偏好:{preferences.get('taste', '不限')}
过敏原:{preferences.get('allergies', '无')}
烹饪时间上限:{cooking_time_max}分钟
每餐热量目标:{calorie_target // 3}千卡左右

请以JSON格式返回,每道菜包含:
– name: 菜名
– ingredients_needed: 所需食材及用量
– steps: 烹饪步骤(简洁实用)
– calories: 预估热量(千卡)
– cooking_time: 预估时间(分钟)
– difficulty: 难度(简单/中等/较难)
– nutrition_highlight: 营养亮点

只返回JSON数组,不要其他内容。"""

try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是专业营养师,擅长根据现有食材推荐健康美味的食谱。"},
{"role": "user", "content": prompt},
],
temperature=0.7,
max_tokens=2000,
)
content = response.choices[0].message.content
# 提取JSON内容
json_match = re.search(r'\\[.*\\]', content, re.DOTALL)
if json_match:
recipes = json.loads(json_match.group())
return recipes
raise ValueError("模型返回格式异常")
except json.JSONDecodeError as e:
raise ValueError(f"食谱解析失败:{e}")
except Exception as e:
raise RuntimeError(f"推荐生成失败:{e}")

def generate_weekly_plan(
self,
ingredients: List[IngredientItem],
preferences: Dict,
calorie_target: int = 2000,
) -> Dict:
"""生成周度营养规划,包含每日三餐和购物补货清单"""
ingredient_names = [i.name for i in ingredients]

prompt = f"""作为营养师,请基于以下条件制定一周饮食计划:

现有食材:{', '.join(ingredient_names)}
口味偏好:{preferences.get('taste', '不限')}
每日热量目标:{calorie_target}千卡
要求:营养均衡,蛋白质/碳水/脂肪比例合理

返回JSON格式:
{{
"daily_plans": [
{{
"day": "周一",
"meals": {{
"breakfast": {{"dish": "…", "calories": 0}},
"lunch": {{"dish": "…", "calories": 0}},
"dinner": {{"dish": "…", "calories": 0}}
}},
"total_calories": 0
}}
],
"shopping_list": ["需要补充的食材"],
"nutrition_summary": "本周营养分析"
}}

只返回JSON,不要其他内容。"""

try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是专业营养规划师。"},
{"role": "user", "content": prompt},
],
temperature=0.6,
max_tokens=3000,
)
content = response.choices[0].message.content
json_match = re.search(r'\\{.*\\}', content, re.DOTALL)
if json_match:
return json.loads(json_match.group())
raise ValueError("模型返回格式异常")
except Exception as e:
raise RuntimeError(f"周度规划生成失败:{e}")

3.3 营养分析与报告生成

class NutritionAnalyzer:
"""营养分析模块:计算每日营养摄入并生成可视化报告"""

# 常见食材营养数据(实际项目从营养数据库API获取)
NUTRITION_DB: Dict[str, Dict] = {
"西红柿": {"calories": 19, "protein": 0.9, "carbs": 4.0, "fat": 0.2, "fiber": 1.2},
"鸡蛋": {"calories": 144, "protein": 13.3, "carbs": 2.8, "fat": 8.8, "fiber": 0},
"马铃薯": {"calories": 76, "protein": 2.0, "carbs": 17.2, "fat": 0.2, "fiber": 0.7},
"鸡胸肉": {"calories": 133, "protein": 31.0, "carbs": 0, "fat": 3.6, "fiber": 0},
}

def analyze_daily_intake(self, recipes: List[Dict]) -> Dict:
"""分析一日三餐的总营养摄入"""
total = {"calories": 0, "protein": 0, "carbs": 0, "fat": 0, "fiber": 0}

for recipe in recipes:
for ing_name in recipe.get("ingredients_needed", {}):
# 模糊匹配食材名称
matched_key = self._fuzzy_match(ing_name)
if matched_key and matched_key in self.NUTRITION_DB:
nutrition = self.NUTRITION_DB[matched_key]
for key in total:
total[key] += nutrition.get(key, 0)

return {
"total_intake": total,
"macro_ratio": {
"protein_pct": round(total["protein"] * 4 / max(total["calories"], 1) * 100, 1),
"carbs_pct": round(total["carbs"] * 4 / max(total["calories"], 1) * 100, 1),
"fat_pct": round(total["fat"] * 9 / max(total["calories"], 1) * 100, 1),
},
}

def _fuzzy_match(self, name: str) -> Optional[str]:
"""模糊匹配食材名称到营养数据库"""
for key in self.NUTRITION_DB:
if key in name or name in key:
return key
return None

四、智能食谱系统的局限性与架构取舍

这套方案并非银弹,在实际落地中有几个需要正视的权衡。

食材识别的精度瓶颈。 用户输入"一小块姜"时,"一小块"到底是多少克?自然语言的模糊性在这里是硬伤。解决方案是引入常见量词的换算表,比如"一块姜≈10g",但这类换算本身就有误差。更严谨的做法是结合图像识别,让用户拍一张冰箱内部照片,用视觉模型直接识别食材和数量,但这又增加了系统复杂度和成本。

营养数据的时效性与准确性。 上述代码中的营养数据库是硬编码的简化版。实际生产中,食材的营养成分会因产地、品种、烹饪方式而变化。接入权威营养数据库 API(如 USDA FoodData Central)是必须的,但这类 API 的查询延迟和配额限制也需要考虑。

大模型推荐的稳定性问题。 同样的食材和偏好,两次调用可能返回差异很大的食谱。这在产品层面会造成用户体验不一致。缓解方案是设置较低的 temperature(0.5-0.7),并缓存高频食材组合的推荐结果。

隐私与数据安全。 用户的饮食习惯、过敏原、健康目标都属于敏感个人信息。如果系统部署在云端,数据传输和存储必须加密。对于注重隐私的用户,可以考虑本地部署小参数量模型(如 Qwen2.5-7B),牺牲部分推荐质量换取数据不出域。

适用边界。 这套系统适合有基本烹饪能力、希望改善饮食结构的人群。对于完全不会做饭的用户,推荐再好的食谱也无济于事——这类场景更适合接入外卖平台的健康餐推荐。

五、总结

本文从"今天吃什么"这个日常痛点出发,设计了一套基于大模型的智能食谱推荐与营养规划系统。核心思路是:食材解析标准化 → 语义检索候选食谱 → 大模型个性化重排序 → 营养分析与周度规划。代码层面,用 Pydantic 做数据校验,用正则解析自然语言输入,用 OpenAI API 做推荐生成,每个环节都包含了错误处理和边界保护。

落地路线上,建议分三步走:第一步,先用大模型 API 跑通核心流程,验证推荐质量;第二步,接入向量数据库(如 Milvus)做食谱语义检索,提升检索效率;第三步,加入图像识别模块,实现"拍照识食材"的完整体验。技术选型上,初期用云端 API 快速验证,后期根据用户量和成本考虑本地模型部署。

赞(0)
未经允许不得转载:171主机测评 » 当 AI 走进厨房:用大模型打造智能食谱推荐与营养规划系统
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址