[大模型应用].Net下接入VLM多模态模型分析

目录

一、目的

二、解决方案

[2.1 环境说明](#2.1 环境说明)

[2.2 功能实现流程](#2.2 功能实现流程)

[2.3 代码实现](#2.3 代码实现)

[2.3.1 提示词说明](#2.3.1 提示词说明)

[2.3.2 AI接口请求实体](#2.3.2 AI接口请求实体)

[2.3.3 AI接口返回实体](#2.3.3 AI接口返回实体)

三、文章总结


一、目的

目前在开发一个健康养生类产品时,考虑用户可以上传自身的病例报告,通过调用AI大模型给出病例报告解读。在实现的过程中碰到了一些问题,最后想着记录一下,避免大家以后踩坑。

首先理解一下什么是多模态模型,多模态模型是能够同时处理多种模态信息(文本、图像、音频、视频)的大语言模型。SiliconFlow 提供了多个支持不同模态组合的强大模型,能够:

  1. 视觉理解:理解图片内容、OCR、图像描述
  2. 视频分析:提取视频帧、理解视频内容、动作识别
  3. 音频处理:语音识别、音频内容分析
  4. 多模态融合:同时处理多种媒体类型的综合分析

在考虑使用VLM模型解决自己问题时,最开始第一个考虑是使用DeepSeek的开放接口,最开始通过网页版尝试分析图象,并根据提示词给出相关结果,发现是可行的且返回结果还不错。

但后续发现截止文章发布的时间点下,他们的API开放平台中的相关接口目前不支持多模态模型。DeepSeek本身使用VL2和OCR的视觉处理模型,但是在API开放平台中未开放,这个大家要注意。

最后对比硅基流动和百度千帆,选择了硅基流动。两者区别倒是不大,按时尝试了下几个视觉模型的返回结果,感觉硅基流动下处理时间和分析内容都还不错。当然主要是白嫖了14块钱。

对硅基流动感兴趣可以看看这个博文:【大模型应用】初学:硅基流动DeepSeekAI+Cherry-studio使用

二、解决方案

2.1 环境说明

语言:C#

技术框架:.Net6 Asp.Net WebApi

第三方接口:https://api.siliconflow.cn/v1/chat/completions

2.2 功能实现流程

这是一个医学报告图像分析服务,通过调用DeepSeek VL2多模态AI模型,对用户上传的医学报告图像进行智能分析和解读。

1、输入处理

  • 参数验证‌:确保imageBase64参数不为空
  • 格式兼容‌:支持原始base64字符串和完整data URI格式
  • 自动补全‌:对原始base64数据自动添加data URI前缀

2、分析流程设计

系统按照严格的医学报告分析步骤:

  1. 图像类型识别‌:判断是否为医学报告
  2. 关键信息提取‌:患者信息、检查项目、指标数值
  3. 专业解读‌:异常指标说明、健康风险评估

3、输出处理

标准化输出‌:统一的JSON响应格式

2.3 代码实现

以下是一个接口请示例中在进行Http请求时需要请求头信息,同时需要前往硅基流动平台获取ApiKey,接口地址:https://api.siliconflow.cn/v1/chat/completions

cs 复制代码
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
 _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
cs 复制代码
   /// <summary>
   /// 图像AI分析接口
   /// </summary>
   /// <param name="imageBase64"></param>
   /// <param name="userText"></param>
   /// <returns></returns>
   public async Task<string> SendChatWithImageAsync(string imageBase64, string userText = "请帮我描述这张图片的内容")
   {
       if (string.IsNullOrWhiteSpace(imageBase64))
       {
           return "Error: imageBase64 is required";
       }

       // 兼容传入 raw base64 或完整 data URI
       var trimmed = imageBase64.Trim();
       var dataUri = trimmed.StartsWith("data:image", StringComparison.OrdinalIgnoreCase)
           ? trimmed
           : $"data:image/jpeg;base64,{trimmed}";

       // 构造图文混合消息
       var Sys_Content = new List<ContentItem>
                   {
                       new ContentItem { Type = "text", Text = "你是一个专业的医学报告分析助手。" }
                   };
       var User_Content = new List<ContentItem>
                   {
                       new ContentItem { Type = "image_url", ImageUrl = new ImageUrl { Url = dataUri } },
                       new ContentItem { Type = "text", Text = "输入相关提示词内容" }
                   };
      
       var requestBody = new ChatRequestSModel
       {
           Model = "deepseek-ai/deepseek-vl2",
           Messages = new List<Message>
           {
               new Message
               {
                   Role = "system",
                   Content = Sys_Content
               },
               new Message
               {
                   Role = "user",
                   Content = User_Content
               }
           },
           MaxTokens = 4096,
           ResponseFormat = new ResponseFormat { Type = "json_object" },
           Stream = false
       };

       // 按 reqinfo 结构要求,序列化请求体
       var json = JsonSerializer.Serialize(requestBody, new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
       var content = new StringContent(json, Encoding.UTF8, "application/json");

       try
       {
           var response = await _httpClient.PostAsync(ApiUrl, content);
           response.EnsureSuccessStatusCode();

           // 获取响应并反序列化为
           var responseBytes = await response.Content.ReadAsByteArrayAsync();
           var responseContent = Encoding.UTF8.GetString(responseBytes);

           // 提取 assistant 的 message.content
           ChatResponseSModel? resp = null;
           try
           {
               resp = JsonSerializer.Deserialize<ChatResponseSModel>(responseContent, new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
           }
           catch
           {
               // 反序列化失败则直接返回原始字符串
               return responseContent;
           }

           if (resp?.Choices != null && resp.Choices.Count > 0 && resp.Choices[0]?.Message != null)
           {
               // content 本身就是一个 JSON 字符串(包含 success/message/content),直接返回该字符串
               return resp.Choices[0].Message.Content ?? string.Empty;
           }

           // 未取到有效内容时返回原始响应,避免信息丢失
           return responseContent;
       }
       catch (Exception ex)
       {
           return $"Error: {ex.Message}";
       }
   }

2.3.1 提示词说明

多模态信息在请求时用户可以输出相关提示词,让AI来指导如何对于上传文件进行处理,以下是一个参考的提示词内容。

提示词:

你是一个专业的医学报告分析助手。请按照以下步骤处理用户上传的图像:1. ‌图像类型识别‌:首先判断图像是否为医学报告(如CT报告、血液检验单、超声图像等)。若非医学报告,直接返回:{\"success\": false, \"message\": \"请传递医学报告\", \"content\": \"\"} 2. ‌内容分析‌:若确认为医学报告,则提取以下关键信息: - 患者基本信息(姓名、年龄、性别) - 检查项目(如MRI、X光、血液分析) - 主要指标与数值 - 医生结论或建议3. ‌解读方案‌:基于提取内容,生成通俗易懂的解读: - 异常指标说明及其临床意义 - 可能关联的健康风险 - 建议的后续行动(如复检、专科就诊)4. ‌输出格式‌:严格使用以下JSON结构: {\"success\": true, \"message\": \"分析成功\", \"content\": \"报告解读内容以Html格式返回\"} 注意事项‌:- 若图像模糊或信息不全,在content中注明"部分数据无法识别" - 避免提供未经证实的诊断,仅基于报告客观解读 - 所有医疗建议需标注"仅供参考,具体诊疗请遵医嘱"

2.3.2 AI接口请求实体

cs 复制代码
    public class ChatRequestSModel
    {
        /// <summary>
        /// 模型名称,例如:deepseek-chat
        /// </summary>
        public string Model { get; set; }

        /// <summary>
        /// 对话消息列表
        /// </summary>
        public List<Message> Messages { get; set; } = new List<Message>();

        /// <summary>
        /// 最大 tokens 限制
        /// </summary>
        public int MaxTokens { get; set; } = 1024;

        /// <summary>
        /// 响应格式
        /// </summary>
        public ResponseFormat ResponseFormat { get; set; }


        /// <summary>
        /// 是否流式输出
        /// </summary>
        public bool Stream { get; set; } = false;
    }

    /// <summary>
    /// 单条对话消息
    /// </summary>
    public class Message
    {
        /// <summary>
        /// 角色,如:user、assistant
        /// </summary>
        [JsonPropertyName("role")]
        public string Role { get; set; }

        /// <summary>
        /// 多段内容(文本、图片等)
        /// </summary>
        [JsonPropertyName("content")]
        public List<ContentItem> Content { get; set; } 
    }

    /// <summary>
    /// 图文内容项
    /// </summary>
    public class ContentItem
    {
        /// <summary>
        /// 内容类型:text 或 image_url
        /// </summary>
        [JsonPropertyName("type")]
        public string Type { get; set; }

        /// <summary>
        /// 文本内容(当 type = text 时使用)
        /// </summary>
        [JsonPropertyName("text")]
        public string Text { get; set; }

        /// <summary>
        /// 图片地址或数据(当 type = image 时使用)
        /// </summary>
        [JsonPropertyName("image_url")]
        public ImageUrl ImageUrl { get; set; }
    }

    /// <summary>
    /// 图片地址或 Base64 数据
    /// </summary>
    public class ImageUrl
    {
        /// <summary>
        /// 图片 URL(与 data 二选一)
        /// </summary>
        [JsonPropertyName("url")]
        public string Url { get; set; }

        [JsonPropertyName("detail")]
        public string Detail { get; set; }
    }

    /// <summary>
    /// 响应格式定义
    /// </summary>
    public class ResponseFormat
    {
        /// <summary>
        /// 类型,如:text
        /// </summary>
        [JsonPropertyName("type")]
        public string Type { get; set; }
    }

2.3.3 AI接口返回实体

cs 复制代码
  public class ChatResponseSModel
  {
      [JsonPropertyName("id")] public string Id { get; set; }
      [JsonPropertyName("object")] public string Object { get; set; }
      [JsonPropertyName("created")] public long Created { get; set; }
      [JsonPropertyName("model")] public string Model { get; set; }

      [JsonPropertyName("choices")] public List<Choice> Choices { get; set; } = new List<DeepSeekChoice>();

      [JsonPropertyName("usage")] public Usage Usage { get; set; }

      [JsonPropertyName("system_fingerprint")] public string SystemFingerprint { get; set; }
  }

  public class Choice
  {
      [JsonPropertyName("index")] public int Index { get; set; }
      [JsonPropertyName("message")] public DeepSeekMessageResp Message { get; set; }
      [JsonPropertyName("finish_reason")] public string FinishReason { get; set; }
  }

  public class MessageResp
  {
      [JsonPropertyName("role")] public string Role { get; set; }
      // 注意:在响应中 content 是一个字符串(JSON 字符串),与请求时的多段 content 结构不同
      [JsonPropertyName("content")] public string Content { get; set; }
  }

  public class Usage
  {
      [JsonPropertyName("prompt_tokens")] public int PromptTokens { get; set; }
      [JsonPropertyName("completion_tokens")] public int CompletionTokens { get; set; }
      [JsonPropertyName("total_tokens")] public int TotalTokens { get; set; }
  }

三、文章总结

以上多模态的使用是通过Http直接通过接口请求,没有使用通用的OpenApi标准方式调用,但是内部核心逻辑是一致。在使用过程也是踩坑不少。

相关推荐
KEEN的创享空间17 小时前
AI编程从0到1之10X提效(Vibe Coding 氛围式编码 )09篇
openai·ai编程
AlienZHOU18 小时前
为 AI Agent 编写高质量 Skill:Claude 官方指南
agent·ai编程·claude
恋猫de小郭19 小时前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
KaneLogger20 小时前
【翻译】打造 Agent Skills 的最佳实践
agent·ai编程·claude
王小酱20 小时前
Everything Claude Code 文档
openai·ai编程·aiops
雮尘21 小时前
如何在非 Claude IDE (TARE、 Cursor、Antigravity 等)下使用 Agent Skills
前端·agent·ai编程
刘贺同学21 小时前
Day12-龙虾哥打工日记:OpenClaw 子 Agent 到底看到了什么?
aigc·ai编程
程序员鱼皮1 天前
离大谱,我竟然在 VS Code 里做了个视频!
github·aigc·ai编程
Kayshen1 天前
我用纯前端逆向了 Figma 的二进制文件格式,实现了 .fig 文件的完整解析和导入
前端·agent·ai编程