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

View File

@@ -85,12 +85,12 @@ public sealed class DeepSeekClient(HttpClient http, IConfiguration config)
}
/// <summary>
/// 千问 VL 视觉客户端(食物识别 + 报告解读)
/// VLM 视觉客户端——支持千问/豆包,通过 .env 切换
/// </summary>
public sealed class QwenVisionClient(HttpClient http, IConfiguration config)
public sealed class VisionClient(HttpClient http, IConfiguration config)
{
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()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,

View File

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

View File

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