feat: 增强饮食分析服务,支持文本饮食记录处理

- 新增分析用户文本中的饮食信息功能,自动记录饮食信息并提供营养分析。
- 优化饮食记录处理逻辑,支持无图片的文本记录,提升用户体验。
- 添加单元测试,确保文本分析功能的准确性和稳定性。
- 更新相关文档,详细说明新功能的使用方法和示例。
This commit is contained in:
richarjiang
2025-08-19 08:58:52 +08:00
parent a56d1d5255
commit 8e27e3d3e3
3 changed files with 347 additions and 7 deletions

View File

@@ -61,6 +61,7 @@ export class DietAnalysisService {
private readonly logger = new Logger(DietAnalysisService.name);
private readonly client: OpenAI;
private readonly visionModel: string;
private readonly model: string;
constructor(
private readonly configService: ConfigService,
@@ -74,6 +75,7 @@ export class DietAnalysisService {
baseURL,
});
this.model = this.configService.get<string>('DASHSCOPE_MODEL') || 'qwen-flash';
this.visionModel = this.configService.get<string>('DASHSCOPE_VISION_MODEL') || 'qwen-vl-max';
}
@@ -159,6 +161,44 @@ export class DietAnalysisService {
}
}
/**
* 分析用户文本中的饮食信息
* @param userText 用户输入的文本
* @returns 饮食分析结果
*/
async analyzeDietFromText(userText: string): Promise<DietAnalysisResult> {
try {
const currentHour = new Date().getHours();
const suggestedMealType = this.getSuggestedMealType(currentHour);
const prompt = this.buildTextDietAnalysisPrompt(suggestedMealType);
const completion = await this.client.chat.completions.create({
model: this.model,
messages: [
{
role: 'user',
content: `${prompt}\n\n用户描述${userText}`
}
],
temperature: 0.3,
response_format: { type: 'json_object' } as any,
});
const rawResult = completion.choices?.[0]?.message?.content || '{}';
this.logger.log(`Text diet analysis result: ${rawResult}`);
return this.parseAndValidateResult(rawResult, suggestedMealType);
} catch (error) {
this.logger.error(`文本饮食分析失败: ${error instanceof Error ? error.message : String(error)}`);
return {
shouldRecord: false,
confidence: 0,
analysisText: '文本饮食分析失败,请稍后重试'
};
}
}
/**
* 从用户确认的选项创建饮食记录
* @param userId 用户ID
@@ -212,15 +252,18 @@ export class DietAnalysisService {
* 处理饮食记录并添加到数据库
* @param userId 用户ID
* @param analysisResult 分析结果
* @param imageUrl 图片URL
* @param imageUrl 图片URL(可选,文本记录时为空)
* @returns 饮食记录响应
*/
async processDietRecord(userId: string, analysisResult: DietAnalysisResult, imageUrl: string): Promise<CreateDietRecordDto | null> {
async processDietRecord(userId: string, analysisResult: DietAnalysisResult, imageUrl?: string): Promise<CreateDietRecordDto | null> {
if (!analysisResult.shouldRecord || !analysisResult.extractedData) {
return null;
}
try {
// 根据是否有图片URL来确定数据源
const source = imageUrl ? DietRecordSource.Vision : DietRecordSource.Manual;
const createDto: CreateDietRecordDto = {
mealType: analysisResult.extractedData.mealType,
foodName: analysisResult.extractedData.foodName,
@@ -230,8 +273,8 @@ export class DietAnalysisService {
carbohydrateGrams: analysisResult.extractedData.carbohydrateGrams,
fatGrams: analysisResult.extractedData.fatGrams,
fiberGrams: analysisResult.extractedData.fiberGrams,
source: DietRecordSource.Vision,
imageUrl: imageUrl,
source: source,
imageUrl: imageUrl || undefined,
aiAnalysisResult: analysisResult,
};
@@ -399,6 +442,54 @@ export class DietAnalysisService {
4. analysisText要详细说明识别的食物和营养分析`;
}
/**
* 构建文本饮食分析提示
* @param suggestedMealType 建议的餐次类型
* @returns 提示文本
*/
private buildTextDietAnalysisPrompt(suggestedMealType: MealType): string {
return `作为专业营养分析师请分析用户描述的饮食内容并以严格JSON格式返回结果。
当前时间建议餐次:${suggestedMealType}
请返回以下格式的JSON不要包含其他文本
{
"shouldRecord": boolean, // 是否应该记录如果描述包含具体食物则为true
"confidence": number, // 识别置信度 0-100
"extractedData": {
"foodName": string, // 主要食物名称(简洁,如"鸡胸肉沙拉"、"牛肉面"等)
"mealType": "${suggestedMealType}", // 餐次类型,优先使用建议值
"portionDescription": string, // 份量描述(如"1碗"、"200g"、"一份"等)
"estimatedCalories": number, // 估算总热量
"proteinGrams": number, // 蛋白质含量(克)
"carbohydrateGrams": number, // 碳水化合物含量(克)
"fatGrams": number, // 脂肪含量(克)
"fiberGrams": number, // 膳食纤维含量(克)
"nutritionDetails": { // 其他营养信息
"mainIngredients": string[], // 主要食材列表
"cookingMethod": string, // 烹饪方式(如"清蒸"、"炒制"、"生食"等)
"foodCategories": string[] // 食物分类(如"主食"、"蛋白质"、"蔬菜"等)
}
},
"analysisText": string // 详细的文字分析说明
}
分析要求:
1. 仔细识别用户描述中的食物名称、数量、烹饪方式等信息
2. 如果描述模糊或不包含具体食物设置shouldRecord为false
3. 营养数据要基于识别的食物种类和分量合理估算
4. foodName要简洁明了便于记录和查找
5. 支持中文食物描述,如"一碗米饭"、"两个鸡蛋"、"一份青菜"等
6. analysisText要详细说明识别的食物和营养分析
7. 如果用户提到多种食物,选择主要的食物作为记录对象,或合并为一餐记录
示例用户输入:
- "今天早餐吃了一碗燕麦粥加香蕉"
- "午餐点了一份鸡胸肉沙拉"
- "晚上吃了牛肉面,还有小菜"
- "刚吃了两个苹果当零食"`;
}
/**
* 解析食物识别结果
* @param rawResult 原始结果字符串