From 97e6a0ff6dc4bf0d538aec8d3098d3fe9c57696d Mon Sep 17 00:00:00 2001 From: richarjiang Date: Thu, 4 Sep 2025 10:16:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0GLM-4.5V=E5=92=8CDash?= =?UTF-8?q?Scope=E6=A8=A1=E5=9E=8B=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=A5=AE=E9=A3=9F=E5=88=86=E6=9E=90=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=9A=84API=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.glm.example | 24 +++ .../services/diet-analysis.service.ts | 152 ++++++++++++------ 2 files changed, 129 insertions(+), 47 deletions(-) create mode 100644 .env.glm.example diff --git a/.env.glm.example b/.env.glm.example new file mode 100644 index 0000000..583f490 --- /dev/null +++ b/.env.glm.example @@ -0,0 +1,24 @@ +# GLM-4.5V Configuration Example +# Copy this to your .env file and update with your actual API key + +# AI Vision Provider - set to 'glm' to use GLM-4.5V, 'dashscope' for Qwen (default) +AI_VISION_PROVIDER=glm + +# GLM-4.5V API Configuration +GLM_API_KEY=your_glm_api_key_here +GLM_BASE_URL=https://open.bigmodel.cn/api/paas/v4 + +# GLM Model Names +GLM_MODEL=glm-4-flash +GLM_VISION_MODEL=glm-4v-plus + +# Alternative: Use GLM-4.5V models (if available) +# GLM_MODEL=glm-4.5 +# GLM_VISION_MODEL=glm-4.5v + +# DashScope Configuration (fallback/default) +# Keep these for fallback or if you want to switch between providers +DASHSCOPE_API_KEY=your_dashscope_api_key_here +DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +DASHSCOPE_MODEL=qwen-flash +DASHSCOPE_VISION_MODEL=qwen-vl-max \ No newline at end of file diff --git a/src/ai-coach/services/diet-analysis.service.ts b/src/ai-coach/services/diet-analysis.service.ts index 72e28b7..60e5a31 100644 --- a/src/ai-coach/services/diet-analysis.service.ts +++ b/src/ai-coach/services/diet-analysis.service.ts @@ -57,6 +57,10 @@ export interface FoodRecognitionResult { /** * 饮食分析服务 * 负责处理饮食相关的AI分析、营养评估和上下文构建 + * + * 支持多种AI模型: + * - GLM-4.5V (智谱AI) - 设置 AI_VISION_PROVIDER=glm + * - Qwen VL (阿里云DashScope) - 设置 AI_VISION_PROVIDER=dashscope (默认) */ @Injectable() export class DietAnalysisService { @@ -64,21 +68,111 @@ export class DietAnalysisService { private readonly client: OpenAI; private readonly visionModel: string; private readonly model: string; + private readonly apiProvider: string; constructor( private readonly configService: ConfigService, private readonly dietRecordsService: DietRecordsService, ) { - const dashScopeApiKey = this.configService.get('DASHSCOPE_API_KEY') || 'sk-e3ff4494c2f1463a8910d5b3d05d3143'; - const baseURL = this.configService.get('DASHSCOPE_BASE_URL') || 'https://dashscope.aliyuncs.com/compatible-mode/v1'; + // Support both GLM-4.5V and DashScope (Qwen) models + this.apiProvider = this.configService.get('AI_VISION_PROVIDER') || 'dashscope'; + + if (this.apiProvider === 'glm') { + // GLM-4.5V Configuration + const glmApiKey = this.configService.get('GLM_API_KEY'); + const glmBaseURL = this.configService.get('GLM_BASE_URL') || 'https://open.bigmodel.cn/api/paas/v4'; + + this.client = new OpenAI({ + apiKey: glmApiKey, + baseURL: glmBaseURL, + }); + + this.model = this.configService.get('GLM_MODEL') || 'glm-4-flash'; + this.visionModel = this.configService.get('GLM_VISION_MODEL') || 'glm-4v-plus'; + } else { + // DashScope Configuration (default) + const dashScopeApiKey = this.configService.get('DASHSCOPE_API_KEY') || 'sk-e3ff4494c2f1463a8910d5b3d05d3143'; + const baseURL = this.configService.get('DASHSCOPE_BASE_URL') || 'https://dashscope.aliyuncs.com/compatible-mode/v1'; - this.client = new OpenAI({ - apiKey: dashScopeApiKey, - baseURL, + this.client = new OpenAI({ + apiKey: dashScopeApiKey, + baseURL, + }); + + this.model = this.configService.get('DASHSCOPE_MODEL') || 'qwen-flash'; + this.visionModel = this.configService.get('DASHSCOPE_VISION_MODEL') || 'qwen-vl-max'; + } + } + + /** + * 制作视觉模型API调用 - 兼容GLM-4.5V和DashScope + * @param prompt 提示文本 + * @param imageUrls 图片URL数组 + * @returns API响应 + */ + private async makeVisionApiCall(prompt: string, imageUrls: string[]) { + const baseParams = { + model: this.visionModel, + temperature: 0.3, + response_format: { type: 'json_object' } as any, + }; + + if (this.apiProvider === 'glm') { + // GLM-4.5V format + return await this.client.chat.completions.create({ + ...baseParams, + messages: [ + { + role: 'user', + content: [ + { type: 'text', text: prompt }, + ...imageUrls.map((imageUrl) => ({ + type: 'image_url', + image_url: { url: imageUrl } + } as any)), + ] as any, + }, + ], + } as any); + } else { + // DashScope format (default) + return await this.client.chat.completions.create({ + ...baseParams, + messages: [ + { + role: 'user', + content: [ + { type: 'text', text: prompt }, + ...imageUrls.map((imageUrl) => ({ type: 'image_url', image_url: { url: imageUrl } as any })), + ] as any, + }, + ], + }); + } + } + + /** + * 制作文本模型API调用 - 兼容GLM-4.5和DashScope + * @param prompt 提示文本 + * @param userText 用户文本 + * @returns API响应 + */ + private async makeTextApiCall(prompt: string, userText: string) { + const baseParams = { + model: this.model, + temperature: 0.3, + response_format: { type: 'json_object' } as any, + }; + + return await this.client.chat.completions.create({ + ...baseParams, + messages: [ + { + role: 'user', + content: `${prompt}\n\n用户描述:${userText}` + } + ], }); - - this.model = this.configService.get('DASHSCOPE_MODEL') || 'qwen-flash'; - this.visionModel = this.configService.get('DASHSCOPE_VISION_MODEL') || 'qwen-vl-max'; } /** @@ -93,20 +187,7 @@ export class DietAnalysisService { const prompt = this.buildFoodRecognitionPrompt(suggestedMealType); - const completion = await this.client.chat.completions.create({ - model: this.visionModel, - messages: [ - { - role: 'user', - content: [ - { type: 'text', text: prompt }, - ...imageUrls.map((imageUrl) => ({ type: 'image_url', image_url: { url: imageUrl } as any })), - ] as any, - }, - ], - temperature: 0.3, - response_format: { type: 'json_object' } as any, - }); + const completion = await this.makeVisionApiCall(prompt, imageUrls); const rawResult = completion.choices?.[0]?.message?.content || '{}'; this.logger.log(`Food recognition result: ${rawResult}`); @@ -136,20 +217,7 @@ export class DietAnalysisService { const prompt = this.buildDietAnalysisPrompt(suggestedMealType); - const completion = await this.client.chat.completions.create({ - model: this.visionModel, - messages: [ - { - role: 'user', - content: [ - { type: 'text', text: prompt }, - ...imageUrls.map((imageUrl) => ({ type: 'image_url', image_url: { url: imageUrl } as any })), - ] as any, - }, - ], - temperature: 0.3, - response_format: { type: 'json_object' } as any, - }); + const completion = await this.makeVisionApiCall(prompt, imageUrls); const rawResult = completion.choices?.[0]?.message?.content || '{}'; this.logger.log(`Enhanced diet analysis result: ${rawResult}`); @@ -177,17 +245,7 @@ export class DietAnalysisService { 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 completion = await this.makeTextApiCall(prompt, userText); const rawResult = completion.choices?.[0]?.message?.content || '{}'; this.logger.log(`Text diet analysis result: ${rawResult}`);