feat: VLM 模型切换 qwen3-vl-plus + Diet Agent 患者档案联动
- VLM 模型切换为 qwen3-vl-plus(中餐识别准确率大幅提升) - VLM Prompt 简化为仅识别食物名+份量+热量 - 营养分析/禁忌提醒移至 Diet Agent(可查患者档案) - Diet Agent Prompt 强化:过敏→红色警告,低盐低脂→黄色提醒 - 上传限制调整至 20MB - 服务端图片压缩参数优化(960px/Q72)
@@ -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,
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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
|
||||
|
||||
// 压缩图片后转 base64(VLM 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/230卡,炒青菜约200g/80卡,青椒肉丝约200g/200卡,红烧肉约150g/400卡,番茄炒蛋约200g/180卡,麻婆豆腐约200g/220卡。
|
||||
只返回JSON。
|
||||
常见参考:米饭200g/230卡,炒青菜200g/80卡,红烧肉150g/400卡,番茄炒蛋200g/180卡,青椒肉丝200g/200卡。
|
||||
""";
|
||||
|
||||
try
|
||||
|
||||
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 420 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 5.2 MiB |