关键要点
- GPT-4 优化用于营养分析:使用结构化输出和 JSON 模式,确保 AI 返回的膳食计划数据格式一致、可验证
- RAG 增强膳食建议:结合用户历史数据、营养知识库和实时偏好,生成个性化建议
- PostgreSQL 存储膳食数据:使用 JSONB 列存储灵活的营养成分和过敏原信息
- 流式响应提升体验:使用 Server-Sent Events 实时展示 AI 生成过程
- 营养验证保障安全:在 AI 输出后进行营养合理性检查,防止不健康的建议
个性化膳食规划是健康管理的重要组成部分。本教程将教你如何构建一个 AI 驱动的膳食规划应用,能够根据用户的健康目标、饮食偏好、过敏原和营养需求,生成每日膳食计划。
前置条件:
- Node.js 18+ 和 npm
- Next.js 14 基础知识
- OpenAI API 密钥
- PostgreSQL 数据库(可使用 Supabase)
项目架构概览
code
┌─────────────────────────────────────────────────┐
│ 前端层 │
│ (React 19、Tailwind CSS、用户偏好界面) │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ API 路由层 (Next.js) │
│ (OpenAI 集成、营养验证、用户偏好管理) │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ 数据持久化层 │
│ (PostgreSQL - 用户、偏好、膳食历史、营养库) │
└─────────────────────────────────────────────────┘
Code collapsed
步骤 1:项目初始化
code
npx create-next-app@latest ai-meal-planner --typescript --tailwind --app
cd ai-meal-planner
npm install openai @supabase/supabase-js zod cheerio
npm install -D @types/node
Code collapsed
配置环境变量 .env.local:
code
# OpenAI 配置
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-4-turbo-preview
# Supabase/PostgreSQL 配置
DATABASE_URL=postgresql://user:password@host:5432/mealplanner
SUPABASE_URL=your-supabase-url
SUPABASE_ANON_KEY=your-supabase-anon-key
# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000
Code collapsed
步骤 2:数据库模式设计
运行以下 SQL 创建数据库表:
code
-- migrations/001_initial_schema.sql
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 用户偏好表
CREATE TABLE user_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
-- 健康目标
health_goals JSONB DEFAULT '[]', -- ["weight-loss", "muscle-gain", "maintenance"]
-- 膳食类型
diet_type VARCHAR(50) DEFAULT 'omnivore', -- omnivore, vegetarian, vegan, keto, paleo
-- 过敏原和限制
allergies JSONB DEFAULT '[]', -- ["nuts", "dairy", "gluten"]
dislikes JSONB DEFAULT '[]',
-- 营养目标(每日)
daily_calories INTEGER DEFAULT 2000,
daily_protein INTEGER DEFAULT 50, -- 克
daily_carbs INTEGER DEFAULT 250,
daily_fats INTEGER DEFAULT 70,
-- 用餐偏好
meals_per_day INTEGER DEFAULT 3,
cooking_time_minutes INTEGER DEFAULT 30,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 膳食计划表
CREATE TABLE meal_plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
plan_date DATE NOT NULL,
-- 计划内容(JSONB 存储灵活结构)
meals JSONB NOT NULL, -- [{type: "breakfast", name: "...", calories: ..., nutrition: {...}, ingredients: [...]}]
-- AI 生成元数据
ai_model VARCHAR(100),
generated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, plan_date)
);
-- 营养知识库表(用于 RAG)
CREATE TABLE nutrition_knowledge (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category VARCHAR(100) NOT NULL, -- protein, vitamins, minerals, dietary-patterns
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
embedding VECTOR(1536), -- OpenAI embeddings
references TEXT[],
created_at TIMESTAMP DEFAULT NOW()
);
-- 膳食历史表(用于学习用户偏好)
CREATE TABLE meal_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
meal_date DATE NOT NULL,
meal_type VARCHAR(50) NOT NULL, -- breakfast, lunch, dinner, snack
meal_name TEXT NOT NULL,
-- 实际摄入(用户记录)
consumed_calories INTEGER,
consumed_protein NUMERIC(5,1),
consumed_carbs NUMERIC(5,1),
consumed_fats NUMERIC(5,1),
-- 用户评分
rating INTEGER CHECK (rating >= 1 AND rating <= 5),
notes TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- 索引优化查询
CREATE INDEX idx_meal_plans_user_date ON meal_plans(user_id, plan_date);
CREATE INDEX idx_meal_history_user_date ON meal_history(user_id, meal_date);
CREATE INDEX idx_nutrition_knowledge_embedding ON nutrition_knowledge USING ivfflat (embedding vector_cosine_ops);
Code collapsed
步骤 3:实现 OpenAI 服务
创建 lib/openai.ts:
code
// lib/openai.ts
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const MEAL_PLANNER_SYSTEM_PROMPT = `你是一位专业的注册营养师和膳食规划师。你的任务是根据用户的需求和偏好,创建健康、均衡的膳食计划。
规则:
1. 每餐应包含宏量营养素(蛋白质、碳水化合物、脂肪)的合理分配
2. 优先考虑全天然、未加工的食材
3. 考虑用户指定的过敏原和饮食限制
4. 提供清晰的烹饪说明和份量
5. 包含每餐的营养估算
输出格式:
- 必须是有效的 JSON
- 所有数值使用数字类型,不要使用字符串
- 遵循提供的 JSON Schema`;
interface NutritionInfo {
calories: number;
protein: number; // 克
carbs: number;
fats: number;
fiber?: number;
sugar?: number;
}
interface Meal {
type: 'breakfast' | 'lunch' | 'dinner' | 'snack';
name: string;
description: string;
ingredients: Array<{
item: string;
amount: string;
calories?: number;
}>;
nutrition: NutritionInfo;
instructions: string[];
prepTime: number; // 分钟
cookTime: number;
}
interface DayPlan {
date: string;
meals: Meal[];
dailyTotals: NutritionInfo;
notes?: string;
}
export class MealPlannerAI {
async generateMealPlan(params: {
healthGoals: string[];
dietType: string;
allergies: string[];
dailyCalories: number;
mealsPerDay: number;
cookingTime: number;
date: string;
}): Promise<DayPlan> {
const prompt = this.buildPrompt(params);
const response = await openai.chat.completions.create({
model: process.env.OPENAI_MODEL || 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: MEAL_PLANNER_SYSTEM_PROMPT },
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.7,
});
const content = response.choices[0].message.content;
if (!content) {
throw new Error('AI response is empty');
}
const plan = JSON.parse(content);
return this.validateAndNormalizePlan(plan, params.date);
}
async generateStreamingMealPlan(
params: any,
onData: (chunk: string) => void,
onComplete: (plan: DayPlan) => void,
onError: (error: Error) => void
): Promise<void> {
const prompt = this.buildPrompt(params);
const stream = await openai.chat.completions.create({
model: process.env.OPENAI_MODEL || 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: MEAL_PLANNER_SYSTEM_PROMPT },
{ role: 'user', content: prompt },
],
response_format: { type: 'json_object' },
temperature: 0.7,
stream: true,
});
let fullContent = '';
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
fullContent += content;
onData(content);
}
}
try {
const plan = JSON.parse(fullContent);
onComplete(this.validateAndNormalizePlan(plan, params.date));
} catch (error) {
onError(error as Error);
}
}
private buildPrompt(params: any): string {
return `请为 ${params.date} 创建膳食计划。
用户资料:
- 健康目标: ${params.healthGoals.join(', ') || '维持健康'}
- 饮食类型: ${params.dietType}
- 过敏原/限制: ${params.allergies.join(', ') || '无'}
- 每日目标卡路里: ${params.dailyCalories}
- 每日用餐次数: ${params.mealsPerDay}
- 烹饪时间限制: ${params.cookingTime} 分钟
请提供:
1. 详细的食谱
2. 营养成分估算
3. 准备和烹饪时间
4. 清晰的说明`;
}
private validateAndNormalizePlan(plan: any, date: string): DayPlan {
// 验证必需字段
if (!plan.meals || !Array.isArray(plan.meals)) {
throw new Error('Invalid meal plan: missing meals array');
}
// 确保每餐都有必需字段
const normalizedMeals: Meal[] = plan.meals.map((meal: any) => ({
type: meal.type || 'snack',
name: meal.name || 'Unnamed Meal',
description: meal.description || '',
ingredients: meal.ingredients || [],
nutrition: {
calories: Number(meal.nutrition?.calories) || 0,
protein: Number(meal.nutrition?.protein) || 0,
carbs: Number(meal.nutrition?.carbs) || 0,
fats: Number(meal.nutrition?.fats) || 0,
fiber: Number(meal.nutrition?.fiber) || 0,
sugar: Number(meal.nutrition?.sugar) || 0,
},
instructions: meal.instructions || [],
prepTime: Number(meal.prepTime) || 10,
cookTime: Number(meal.cookTime) || 20,
}));
// 计算每日总计
const dailyTotals = normalizedMeals.reduce(
(acc, meal) => ({
calories: acc.calories + meal.nutrition.calories,
protein: acc.protein + meal.nutrition.protein,
carbs: acc.carbs + meal.nutrition.carbs,
fats: acc.fats + meal.nutrition.fats,
fiber: acc.fiber + (meal.nutrition.fiber || 0),
sugar: acc.sugar + (meal.nutrition.sugar || 0),
}),
{ calories: 0, protein: 0, carbs: 0, fats: 0, fiber: 0, sugar: 0 }
);
return {
date,
meals: normalizedMeals,
dailyTotals,
notes: plan.notes,
};
}
}
export const mealPlannerAI = new MealPlannerAI();
Code collapsed
步骤 4:实现营养验证服务
创建 lib/nutrition-validator.ts:
code
// lib/nutrition-validator.ts
import { z } from 'zod';
import { NutritionInfo, Meal, DayPlan } from './openai';
// 营养合理性验证规则
const NUTRITION_RULES = {
calories: { min: 1200, max: 4000, warningThreshold: 0.8 },
protein: { min: 0.1, max: 0.35 }, // 占总卡路里的百分比
carbs: { min: 0.25, max: 0.55 },
fats: { min: 0.2, max: 0.35 },
fiber: { min: 25, max: 50 }, // 克
sugar: { max: 50 }, // 克
};
export interface ValidationResult {
isValid: boolean;
warnings: string[];
errors: string[];
correctedPlan?: DayPlan;
}
export class NutritionValidator {
validate(plan: DayPlan, targetCalories: number): ValidationResult {
const warnings: string[] = [];
const errors: string[] = [];
// 1. 验证总卡路里
const calorieDeviation = Math.abs(
(plan.dailyTotals.calories - targetCalories) / targetCalories
);
if (calorieDeviation > 0.15) {
errors.push(
`总卡路里 ${plan.dailyTotals.calories} 与目标 ${targetCalories} 偏差超过 15%`
);
} else if (calorieDeviation > 0.1) {
warnings.push(
`总卡路里 ${plan.dailyTotals.calories} 与目标 ${targetCalories} 偏差较大`
);
}
// 2. 验证宏量营养素比例
const proteinPercent = (plan.dailyTotals.protein * 4) / plan.dailyTotals.calories;
const carbsPercent = (plan.dailyTotals.carbs * 4) / plan.dailyTotals.calories;
const fatsPercent = (plan.dailyTotals.fats * 9) / plan.dailyTotals.calories;
if (proteinPercent < NUTRITION_RULES.protein.min) {
warnings.push('蛋白质摄入偏低,建议增加');
}
if (carbsPercent < NUTRITION_RULES.carbs.min) {
warnings.push('碳水化合物摄入偏低');
}
// 3. 验证每餐分布
const mealDistribution = this.validateMealDistribution(plan.meals);
warnings.push(...mealDistribution);
// 4. 检查过敏原(需要在调用方传入过敏原列表)
return {
isValid: errors.length === 0,
warnings,
errors,
};
}
private validateMealDistribution(meals: Meal[]): string[] {
const warnings: string[] = [];
const mealTypes = new Map<string, Meal[]>();
meals.forEach(meal => {
if (!mealTypes.has(meal.type)) {
mealTypes.set(meal.type, []);
}
mealTypes.get(meal.type)!.push(meal);
});
// 检查是否有早餐
if (!mealTypes.has('breakfast')) {
warnings.push('建议包含早餐以维持能量水平');
}
// 检查晚餐是否过晚(假设晚上 9 点后)
// 这里可以添加更复杂的逻辑
return warnings;
}
// 检查食材中是否包含过敏原
checkAllergens(meal: Meal, allergens: string[]): string[] {
const foundAllergens: string[] = [];
const lowerAllergens = allergens.map(a => a.toLowerCase());
meal.ingredients.forEach(ingredient => {
const item = ingredient.item.toLowerCase();
lowerAllergens.forEach(allergen => {
if (item.includes(allergen)) {
foundAllergens.push(allergen);
}
});
});
return foundAllergens;
}
}
export const nutritionValidator = new NutritionValidator();
Code collapsed
步骤 5:创建 API 路由
生成膳食计划
code
// app/api/meal-plans/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { mealPlannerAI } from '@/lib/openai';
import { nutritionValidator } from '@/lib/nutrition-validator';
import { supabase } from '@/lib/supabase';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { userId, date } = body;
// 1. 获取用户偏好
const { data: preferences } = await supabase
.from('user_preferences')
.select('*')
.eq('user_id', userId)
.single();
if (!preferences) {
return NextResponse.json(
{ error: 'User preferences not found' },
{ status: 404 }
);
}
// 2. 获取用户历史(用于个性化)
const { data: history } = await supabase
.from('meal_history')
.select('*')
.eq('user_id', userId)
.order('meal_date', { ascending: false })
.limit(20);
// 3. 生成膳食计划
const plan = await mealPlannerAI.generateMealPlan({
healthGoals: preferences.health_goals,
dietType: preferences.diet_type,
allergies: preferences.allergies,
dailyCalories: preferences.daily_calories,
mealsPerDay: preferences.meals_per_day,
cookingTime: preferences.cooking_time_minutes,
date,
});
// 4. 验证营养合理性
const validation = nutritionValidator.validate(
plan,
preferences.daily_calories
);
if (!validation.isValid) {
return NextResponse.json(
{ error: 'Generated meal plan failed validation', details: validation.errors },
{ status: 400 }
);
}
// 5. 保存到数据库
const { data: savedPlan, error } = await supabase
.from('meal_plans')
.insert({
user_id: userId,
plan_date: date,
meals: plan.meals,
ai_model: process.env.OPENAI_MODEL,
})
.select()
.single();
if (error) {
console.error('Failed to save meal plan:', error);
}
return NextResponse.json({
plan,
validation,
savedPlanId: savedPlan?.id,
});
} catch (error) {
console.error('Meal plan generation error:', error);
return NextResponse.json(
{ error: 'Failed to generate meal plan' },
{ status: 500 }
);
}
}
Code collapsed
流式生成端点
code
// app/api/meal-plans/generate-stream/route.ts
import { NextRequest } from 'next/server';
import { mealPlannerAI } from '@/lib/openai';
export async function POST(request: NextRequest) {
const body = await request.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
await mealPlannerAI.generateStreamingMealPlan(
body,
(chunk) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ chunk })}\n\n`));
},
(plan) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true, plan })}\n\n`));
controller.close();
},
(error) => {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: error.message })}\n\n`));
controller.close();
}
);
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
Code collapsed
步骤 6:前端组件实现
膳食计划生成器组件
code
// components/MealPlanGenerator.tsx
'use client';
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
interface GenerateParams {
userId: string;
date: string;
}
export function MealPlanGenerator() {
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [generatedPlan, setGeneratedPlan] = useState<any>(null);
const generateMutation = useMutation({
mutationFn: async (params: GenerateParams) => {
const res = await fetch('/api/meal-plans/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
if (!res.ok) throw new Error('Generation failed');
return res.json();
},
onSuccess: (data) => {
setGeneratedPlan(data.plan);
},
});
const handleGenerate = () => {
generateMutation.mutate({ userId: 'current-user-id', date });
};
return (
<div className: "max-w-4xl mx-auto p-6">
<div className: "mb-6">
<label className: "block text-sm font-medium text-gray-700 mb-2">
选择日期
</label>
<input
type: "date"
value={date}
onChange={(e) => setDate(e.target.value)}
className: "border rounded-lg px-4 py-2 w-full max-w-xs"
/>
<button
onClick={handleGenerate}
disabled={generateMutation.isPending}
className: "ml-4 bg-blue-600 text-white px-6 py-2 rounded-lg disabled:opacity-50"
>
{generateMutation.isPending ? '生成中...' : '生成膳食计划'}
</button>
</div>
{generateMutation.error && (
<div className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
生成失败: {(generateMutation.error as Error).message}
</div>
)}
{generatedPlan && (
<MealPlanDisplay plan={generatedPlan} />
)}
</div>
);
}
function MealPlanDisplay({ plan }: { plan: any }) {
return (
<div className: "space-y-6">
{/* 每日总计 */}
<div className: "bg-gray-50 rounded-lg p-6">
<h3 className: "text-lg font-semibold mb-4">每日营养总计</h3>
<div className: "grid grid-cols-2 md:grid-cols-5 gap-4">
<NutrientCard label: "卡路里" value={`${plan.dailyTotals.calories} kcal`} />
<NutrientCard label: "蛋白质" value={`${plan.dailyTotals.protein}g`} />
<NutrientCard label: "碳水" value={`${plan.dailyTotals.carbs}g`} />
<NutrientCard label: "脂肪" value={`${plan.dailyTotals.fats}g`} />
<NutrientCard label: "纤维" value={`${plan.dailyTotals.fiber || 0}g`} />
</div>
</div>
{/* 每餐详情 */}
{plan.meals.map((meal: any, index: number) => (
<MealCard key={index} meal={meal} />
))}
{plan.notes && (
<div className: "bg-blue-50 border-l-4 border-blue-400 p-4">
<p className: "text-blue-800">{plan.notes}</p>
</div>
)}
</div>
);
}
function NutrientCard({ label, value }: { label: string; value: string }) {
return (
<div className: "text-center">
<p className: "text-sm text-gray-600">{label}</p>
<p className: "text-xl font-bold text-gray-900">{value}</p>
</div>
);
}
function MealCard({ meal }: { meal: any }) {
return (
<div className: "bg-white border rounded-lg shadow-sm overflow-hidden">
<div className: "bg-gray-50 px-6 py-4 border-b">
<div className: "flex justify-between items-center">
<h4 className: "text-lg font-semibold capitalize">{meal.type}</h4>
<span className: "text-sm text-gray-500">
{meal.prepTime + meal.cookTime} 分钟
</span>
</div>
<h5 className: "text-xl font-medium text-gray-900 mt-2">{meal.name}</h5>
{meal.description && (
<p className: "text-gray-600 mt-1">{meal.description}</p>
)}
</div>
<div className: "p-6">
<div className: "grid grid-cols-4 gap-4 mb-4">
<NutrientMini label: "卡路里" value={`${meal.nutrition.calories}`} />
<NutrientMini label: "蛋白质" value={`${meal.nutrition.protein}g`} />
<NutrientMini label: "碳水" value={`${meal.nutrition.carbs}g`} />
<NutrientMini label: "脂肪" value={`${meal.nutrition.fats}g`} />
</div>
<div className: "mt-6">
<h6 className: "font-medium text-gray-900 mb-3">食材</h6>
<ul className: "space-y-2">
{meal.ingredients.map((ing: any, i: number) => (
<li key={i} className: "flex justify-between text-sm">
<span>{ing.item}</span>
<span className: "text-gray-500">{ing.amount}</span>
</li>
))}
</ul>
</div>
<div className: "mt-6">
<h6 className: "font-medium text-gray-900 mb-3">烹饪说明</h6>
<ol className: "space-y-2">
{meal.instructions.map((step: string, i: number) => (
<li key={i} className: "flex">
<span className: "flex-shrink-0 w-6 h-6 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center text-sm mr-3">
{i + 1}
</span>
<span className: "text-gray-700">{step}</span>
</li>
))}
</ol>
</div>
</div>
</div>
);
}
function NutrientMini({ label, value }: { label: string; value: string }) {
return (
<div className: "text-center">
<p className: "text-xs text-gray-500">{label}</p>
<p className: "text-sm font-semibold">{value}</p>
</div>
);
}
Code collapsed
步骤 7:添加 RAG 增强功能
code
// lib/rag-enhanced-meal-planner.ts
import { mealPlannerAI } from './openai';
import { supabase } from './supabase';
export class RAGEnhancedMealPlanner {
async generateWithContext(params: any, userId: string) {
// 1. 获取相关营养知识
const relevantKnowledge = await this.retrieveRelevantKnowledge(
params.healthGoals
);
// 2. 获取用户历史偏好
const userPreferences = await this.getUserLearnedPreferences(userId);
// 3. 构建增强提示
const enhancedPrompt = this.buildEnhancedPrompt({
...params,
knowledge: relevantKnowledge,
userPreferences,
});
// 4. 生成计划
return mealPlannerAI.generateMealPlan({ ...params, prompt: enhancedPrompt });
}
private async retrieveRelevantKnowledge(goals: string[]) {
// 使用向量相似度搜索相关营养知识
const { data } = await supabase.rpc('match_nutrition_knowledge', {
query_goals: goals,
match_threshold: 0.7,
match_count: 3,
});
return data || [];
}
private async getUserLearnedPreferences(userId: string) {
// 分析用户历史评分,找出偏好模式
const { data } = await supabase
.from('meal_history')
.select('*')
.eq('user_id', userId)
.gte('rating', 4);
// 这里可以实现更复杂的模式识别
return {
preferredCuisines: this.extractCuisines(data),
preferredIngredients: this.extractIngredients(data),
avoidedMeals: this.extractAvoided(data),
};
}
private extractCuisines(history: any[]) {
// 实现: 从历史记录中提取偏好菜系
return [];
}
private extractIngredients(history: any[]) {
// 实现: 从高分膳食中提取常用食材
return [];
}
private extractAvoided(history: any[]) {
// 实现: 从低分膳食中提取应避免的元素
return [];
}
private buildEnhancedPrompt(params: any): string {
let prompt = `基于以下营养建议,创建膳食计划:\n\n`;
if (params.knowledge?.length) {
prompt += '营养知识:\n';
params.knowledge.forEach((k: any) => {
prompt += `- ${k.title}: ${k.content}\n`;
});
prompt += '\n';
}
if (params.userPreferences) {
prompt += `用户偏好(基于历史学习):\n`;
prompt += `- 偏好菜系: ${params.userPreferences.preferredCuisines.join(', ') || '无特别偏好'}\n`;
prompt += `- 喜欢的食材: ${params.userPreferences.preferredIngredients.join(', ') || '无'}\n`;
}
return prompt;
}
}
export const ragEnhancedMealPlanner = new RAGEnhancedMealPlanner();
Code collapsed
总结
通过本教程,你学会了如何构建一个完整的 AI 膳食规划应用:
- 使用 OpenAI GPT-4 生成个性化膳食计划
- 实现 JSON Schema 验证确保输出质量
- 添加营养合理性验证
- 使用 PostgreSQL 存储和检索数据
- 实现 RAG 增强以提供更个性化的建议
扩展方向
- 添加购物清单生成功能
- 实现膳食计划分享和社交功能
- 集成食谱数据库 API 获取更多选项
- 添加照片识别功能记录实际膳食
参考资料
常见问题
Q: 如何降低 OpenAI API 成本?
A: 可以使用 GPT-3.5-turbo 进行初步生成,然后用 GPT-4 进行验证。或者实现缓存机制,避免重复生成相似的计划。
Q: 如何处理用户反馈和改进建议?
A: 实现评分系统和反馈收集,使用这些数据微调提示词,或考虑进行模型微调。
Q: 营养验证规则的准确性如何保证?
A: 应该参考权威营养学指南(如 USDA 营养数据库),并考虑与注册营养师合作验证规则。