From 3d36ee90f06ddd7af6e4a84029855d40cfb01915 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 18 Aug 2025 15:07:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0AI=E6=95=99=E7=BB=83?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=E5=9B=BE=E7=89=87URL=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=A5=AE=E9=A3=9F=E5=88=86=E6=9E=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E3=80=82=E8=B0=83=E6=95=B4=E7=9B=B8=E5=85=B3DTO=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=95=B0=E7=BB=84=E6=A0=BC=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E5=9B=BE=E7=89=87URL=EF=BC=8C=E5=B9=B6=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=A7=86=E8=A7=89=E6=A8=A1=E5=9E=8B=E4=B8=BA?= =?UTF-8?q?'qwen-vl-max'=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ai-coach/ai-coach.controller.ts | 4 ++-- src/ai-coach/ai-coach.service.ts | 14 +++++++------- src/ai-coach/dto/ai-chat.dto.ts | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ai-coach/ai-coach.controller.ts b/src/ai-coach/ai-coach.controller.ts index a4e0052..ed1cffd 100644 --- a/src/ai-coach/ai-coach.controller.ts +++ b/src/ai-coach/ai-coach.controller.ts @@ -42,7 +42,7 @@ export class AiCoachController { userId, conversationId, userContent, - imageUrl: body.imageUrl, + imageUrls: body.imageUrls, }); let text = ''; for await (const chunk of readable) { @@ -62,7 +62,7 @@ export class AiCoachController { userId, conversationId, userContent, - imageUrl: body.imageUrl, + imageUrls: body.imageUrls, }); readable.on('data', (chunk) => { diff --git a/src/ai-coach/ai-coach.service.ts b/src/ai-coach/ai-coach.service.ts index ae05aef..7334067 100644 --- a/src/ai-coach/ai-coach.service.ts +++ b/src/ai-coach/ai-coach.service.ts @@ -103,7 +103,7 @@ export class AiCoachService { }); // 默认选择通义千问对话模型(OpenAI兼容名),可通过环境覆盖 this.model = this.configService.get('DASHSCOPE_MODEL') || 'qwen-flash'; - this.visionModel = this.configService.get('DASHSCOPE_VISION_MODEL') || 'qwen-vl-plus'; + this.visionModel = this.configService.get('DASHSCOPE_VISION_MODEL') || 'qwen-vl-max'; } async createOrAppendMessages(params: { @@ -200,7 +200,7 @@ export class AiCoachService { conversationId: string; userContent: string; systemNotice?: string; - imageUrl?: string; + imageUrls?: string[]; }): Promise { // 解析指令(如果以 # 开头) const commandResult = this.parseCommand(params.userContent); @@ -227,8 +227,8 @@ export class AiCoachService { } } else if (commandResult.command === 'diet') { // 使用视觉模型分析饮食图片 - if (params.imageUrl) { - const dietAnalysis = await this.analyzeDietImage(params.imageUrl); + if (params.imageUrls) { + const dietAnalysis = await this.analyzeDietImage(params.imageUrls); messages.push({ role: 'user', content: `用户通过拍照记录饮食,图片分析结果如下:\n${dietAnalysis}` @@ -418,7 +418,7 @@ export class AiCoachService { * @param imageUrl 图片URL * @returns 饮食分析结果 */ - private async analyzeDietImage(imageUrl: string): Promise { + private async analyzeDietImage(imageUrls: string[]): Promise { try { const prompt = `请分析这张食物图片,识别其中的食物种类、分量,并提供以下信息: @@ -445,11 +445,11 @@ export class AiCoachService { role: 'user', content: [ { type: 'text', text: prompt }, - { type: 'image_url', image_url: { url: imageUrl } as any }, + ...imageUrls.map((imageUrl) => ({ type: 'image_url', image_url: { url: imageUrl } as any })), ] as any, }, ], - temperature: 0, + temperature: 1, }); this.logger.log(`diet image analysis result: ${completion.choices?.[0]?.message?.content}`); diff --git a/src/ai-coach/dto/ai-chat.dto.ts b/src/ai-coach/dto/ai-chat.dto.ts index fca4616..7a4c5b9 100644 --- a/src/ai-coach/dto/ai-chat.dto.ts +++ b/src/ai-coach/dto/ai-chat.dto.ts @@ -25,8 +25,9 @@ export class AiChatRequestDto { @ApiProperty({ required: false, description: '当用户要记体重时的图片URL(电子秤等)' }) @IsOptional() - @IsString() - imageUrl?: string; + @IsArray() + @IsString({ each: true }) + imageUrls?: string[]; @ApiProperty({ required: false, description: '是否启用流式输出', default: true }) @IsOptional()