feat: VLM 模型切换 qwen3-vl-plus + Diet Agent 患者档案联动

- VLM 模型切换为 qwen3-vl-plus(中餐识别准确率大幅提升)
- VLM Prompt 简化为仅识别食物名+份量+热量
- 营养分析/禁忌提醒移至 Diet Agent(可查患者档案)
- Diet Agent Prompt 强化:过敏→红色警告,低盐低脂→黄色提醒
- 上传限制调整至 20MB
- 服务端图片压缩参数优化(960px/Q72)
This commit is contained in:
MingNian
2026-06-02 14:23:40 +08:00
parent 27cc920a4a
commit d095832a10
39 changed files with 18 additions and 22 deletions

BIN
9103.jpg_wh860.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -85,12 +85,12 @@ public sealed class DeepSeekClient(HttpClient http, IConfiguration config)
} }
/// <summary> /// <summary>
/// 千问 VL 视觉客户端(食物识别 + 报告解读) /// VLM 视觉客户端——支持千问/豆包,通过 .env 切换
/// </summary> /// </summary>
public sealed class QwenVisionClient(HttpClient http, IConfiguration config) public sealed class VisionClient(HttpClient http, IConfiguration config)
{ {
private readonly HttpClient _http = http; private readonly HttpClient _http = http;
private readonly string _model = config["QWEN_VISION_MODEL"] ?? "qwen-vl-max"; private readonly string _model = config["VLM_MODEL"] ?? "doubao-vision-pro";
private readonly JsonSerializerOptions _jsonOptions = new() private readonly JsonSerializerOptions _jsonOptions = new()
{ {
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,

View File

@@ -71,12 +71,15 @@ public sealed class PromptManager
1. VLM食物识别结果后 1. VLM食物识别结果后 check_archive
2. 2. "能不能吃"
3. "能不能吃"/// -
4. 1-5 - //
5. + - 尿
6. /// 3. 1-5
4. PCI术后/
5. + +
6. ///
"""; """;
private const string MedicationPrompt = """ private const string MedicationPrompt = """

View File

@@ -249,8 +249,8 @@ public static class AiChatEndpoints
foreach (var file in files) foreach (var file in files)
{ {
if (file.Length > 10 * 1024 * 1024) if (file.Length > 20 * 1024 * 1024)
return Results.Ok(new { code = 40001, data = (object?)null, message = "文件大小超过 10MB 限制" }); return Results.Ok(new { code = 40001, data = (object?)null, message = "文件大小超过 20MB 限制" });
var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
if (ext is not ".jpg" and not ".jpeg" and not ".png" and not ".heic") if (ext is not ".jpg" and not ".jpeg" and not ".png" and not ".heic")
@@ -263,25 +263,18 @@ public static class AiChatEndpoints
// 压缩图片后转 base64VLM API 有请求体大小限制) // 压缩图片后转 base64VLM API 有请求体大小限制)
var compressedPath = Path.Combine(uploadsDir, $"compressed_{safeName}"); var compressedPath = Path.Combine(uploadsDir, $"compressed_{safeName}");
CompressImage(filePath, compressedPath, maxWidth: 860, quality: 65L); CompressImage(filePath, compressedPath, maxWidth: 960, quality: 72L);
var compressedBytes = await File.ReadAllBytesAsync(compressedPath, ct); var compressedBytes = await File.ReadAllBytesAsync(compressedPath, ct);
var base64 = Convert.ToBase64String(compressedBytes); var base64 = Convert.ToBase64String(compressedBytes);
imageUrls.Add($"data:image/jpeg;base64,{base64}"); imageUrls.Add($"data:image/jpeg;base64,{base64}");
} }
var prompt = """ var prompt = """
JSON
JSON
{ {
"foods": [{"name":"食物名(中文)","portion":"份量","calories":,"proteinGrams":,"carbsGrams":,"fatGrams":,"warning":null}], "foods": [{"name":"中文菜名","portion":"份量","calories":}]
"totalCalories":,
"warnings":["整体建议"],
"score":1-5
} }
200g/230200g/80200g/200150g/400200g/180200g/220 200g/230200g/80150g/400200g/180200g/200
JSON
"""; """;
try try

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
backend/uploads/ft_q50.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
backend/uploads/ft_q55.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB