本地部署deepseek大模型后使用c# winform调用(可离线)
介于最近deepseek的大火,我就在想能不能用winform也玩一玩本地部署,于是经过查阅资料,然后了解到ollama部署deepseek,最后用ollama sharp NUGet包来实现winform调用ollama 部署的deepseek。
本项目使用Vs2022和.net 8.0开发,ollama sharp 使用的是最新版本。也可以使用.net farmwork 4.7.2开发,但是ollama sharp 没办法使用最新的,只能使用3.几的版本,3点几的版本有问题,因为ollama sharp提供的交互方法不是异步的,这就会导致,大模型如果回复你一个很长的的问题的时候,就会突然中断,最后我就彻底放弃了,发现最新版本的ollama sharp的交互方法是异步的,最后抱着试一试的心态,果然成功了,让写个4000字的论文框架,基本上回答时间在2分钟左右也不会中断,(2分钟是因为我的内存有点少,显卡还行吧)。效果还是很不错的,本人使用的deepseek r1 14b的大模型,4060的显卡,16G的内存,回复速度还是很快的,内存基本上跑80%左右。显卡40%上下浮动。
展示图
下载ollama
地址:奥拉马
下载Windows版本然后进行安装就好了,安装完成以后,我们可以在系统环境变量里面添加这两个
第二个是利用ollama下载的大模型的位置,C盘不够的可以加这个变量,如果C盘够多可以忽略,最好设置完以后重启一下电脑再安装ollama,安装好以后可以打开cmd 如图所示:如果是这样,说明你已经安装成功了,
利用ollama安装deepseek r1 14b
这里我们还是打开ollama网站,打开
如果说内存在32G可以选择32b的体验一下,应该会比14b更好用些,最后点击箭头所指的地方复制下来打开cmd,直接ctrl+c复制然后回车他就会自动下载,这里有个小技巧:他下载会越来越慢,我们可以按一下ctrl+c,再按一下键盘的上方向键他就会接着下载,这个时候慢慢就快起来了。
下载完成后我们新打开一个cmd输入ollama list这个可以查看我们已经下载下来的大模型
补充一点:还可以使用ollama rm 大模型的Name进行删除
Ollama Sharp
awaescher/OllamaSharp:在 .NET 中使用 Ollama API 的最简单方法
上面的是链接地址,这是github里面的一个开源项目,使用之前可以看看他的介绍以及使用方法,知其然,知其所以然。
winform 连接大模型
我们打开我们的vs2022。创建新工程,一定要选择后面不带括号.netfarmwork的,才会用到8,.0框架
我们进去以后先添加nuget包,找到依赖项,右键管理NUGET包,打开以后搜索ollama sharp
这里我已经安装过了
等待安装成功以后,我们打开我们窗体的设计器,在左侧的工具箱添加一下的控件
listbox主要用来展示安装的大模型
richtextbox主要用来展示用户输入的文字和deepseek回复的文字
textBox读取用户输入的文字
一个发送按钮一个取消思考按钮
附上源代码:
using OllamaSharp.Models;
using OllamaSharp;
using System.Text.RegularExpressions;namespace WinFormsApp1
{public partial class Form1 : Form{private Uri uri;private OllamaApiClient ollama;private List<Model> models;private bool connect;static ManualResetEvent resetEvent = new ManualResetEvent(false);private CancellationTokenSource cancellationTokenSource;int step = 0;private bool mIsCancel = false;public Form1(){InitializeComponent();}private async void Form1_Load(object sender, EventArgs e){richTextBox1.AppendText("稍等,我正在加载模型。。。。。" + Environment.NewLine);uri = new Uri("http://localhost:11434");ollama = new OllamaApiClient(uri);connect = await ollama.IsRunningAsync();models = (await ollama.ListLocalModelsAsync()).ToList();mSelectItem = 0;LoadModles();step = 1;richTextBox1.AppendText("请在上方选择你要使用的模型,单击即可" + Environment.NewLine);}/// <summary>/// 流程交互/// </summary>public void WorkFololw(){Task.Run(() =>{while (true){Thread.Sleep(200);string cleanText = "";if (textBox2.Text != ""){cleanText = textBox2.Text;}switch (step){case 1:Thread.Sleep(100);if (models.Count == 0){return;}ollama.SelectedModel = models.ToArray()[mSelectItem].Name; // 选择模型名称Log("我已经准备好了小帅哥快来玩呀!", 0, Color.Black);step = 2;break;case 2:if (cleanText.Contains("\r\n")){var prompt = textBox2.Text; // 从文本框读取提示词Log(Environment.NewLine + "用户哥:" + textBox2.Text.TrimEnd('\r', '\n') + Environment.NewLine, 0, Color.Blue);var keepChatting = true;var chat = new Chat(ollama, prompt);Invoke(new Action(() =>{button2.Visible = true;richTextBox1.AppendText("deepSeek-R1>:" + Environment.NewLine);}));BeginSiKao(keepChatting, chat, "");step = 3;}break;case 3:if (cleanText.Contains("\r\n"))step = 2;break;}}});}/// <summary>/// 开始思考/// </summary>/// <param name="keepChatting"></param>/// <param name="chat"></param>public async void BeginSiKao(bool keepChatting, Chat chat, string mImageMsg){//开始聊天await BeginChat(keepChatting, chat, mImageMsg);}/// <summary>/// 加载本地大模型/// </summary>public void LoadModles(){if (models.Any()){foreach (var model in models){if (model.Name.Contains("v2")){Log($"大模型:{model.Name} {model.Size / 1024 / 1024} MB", 1, Color.MediumSeaGreen); // 输出模型名称和大小}Invoke(new Action(() =>{listBox1.Items.Add($"大模型:{model.Name} {model.Size / 1024 / 1024} MB");}));}}else{Log("没有大模型环境,请自行下载大模型", 1, Color.Red);return;}}/// <summary>/// 开始聊天/// </summary>/// <param name="keepChatting"></param>/// <param name="chat"></param>/// <returns></returns>public async Task BeginChat(bool keepChatting, Chat chat, string ImageMsg){cancellationTokenSource = new CancellationTokenSource();var tokenx = cancellationTokenSource.Token;Invoke(new Action(() =>{button1.Text = "思考回答中...";}));string message;message = textBox2.Text.TrimEnd('\r', '\n'); // 从文本框读取用户输入的消息if (message == ""){message = ImageMsg;}Clear(); // 清空文本框以便用户输入下一条消息Task sendTask = Task.Run(async () =>{if (string.IsNullOrEmpty(message.Trim())){return;}bool isFirstToken = true;try{string mmsf = "";await foreach (var answerToken in chat.SendAsync(message)){// 如果取消了操作,提前退出if (cancellationTokenSource.Token.IsCancellationRequested){continue;}if (answerToken != "<think>" && answerToken != "</think>"){mmsf += answerToken;// 使用Invoke更新UIrichTextBox1.Invoke(new Action(() =>{if (isFirstToken){richTextBox1.Focus();isFirstToken = false;}richTextBox1.AppendText(answerToken.Trim());}));}}string newmsg = "";if (mmsf.Contains("```sql")) {newmsg= FormatSql(mmsf);// 使用Invoke更新UIrichTextBox1.Invoke(new Action(() =>{if (isFirstToken){richTextBox1.Focus();isFirstToken = false;}richTextBox1.AppendText(newmsg.Trim());}));}}catch (OperationCanceledException){// 处理取消操作时的异常Invoke(new Action(() =>{if (button1.Text == "思考回答中..."){button1.Text = "发送";}}));}catch (Exception ex){if (mIsCancel == true){// 捕获其他类型的异常并记录Log(Environment.NewLine + $"用户哥取消了回答", 0, Color.Red);mIsCancel = false;}else{// 捕获其他类型的异常并记录Log(Environment.NewLine + $"哎呦出错了" + ex, 0, Color.Red);}}});await sendTask;Invoke(new Action(() =>{button2.Visible = false; // 隐藏取消按钮button1.Text = "发送";textBox2.Focus();richTextBox1.AppendText(Environment.NewLine);}));}/// <summary>/// 清空输入文本框/// </summary>public void Clear(){Invoke(new Action(() =>{textBox2.Clear();}));}/// <summary>/// 更新控件的一些值或者追加文字/// </summary>/// <param name="message"></param>/// <param name="mtype">0:追加文字,1:大模型使用</param>public void Log(string message, int mtype, Color color){if (mtype == 0){Invoke(new Action(() =>{richTextBox1.AppendText(message + Environment.NewLine);textBox2.Focus();}));}else{Invoke(new Action(() =>{label1.Text = message;label1.ForeColor = color;textBox2.Focus();}));}}/// <summary>/// 发送按钮/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void button1_Click(object sender, EventArgs e){if (models.Count == 0){MessageBox.Show("没有大模型环境,怎么玩啊!");return;}if (button1.Text == "思考回答中..."){MessageBox.Show("正想着呢,别点了爷们");}else{string mm = textBox2.Text;textBox2.Text = "用户哥:" + mm + Environment.NewLine;Log(textBox2.Text.TrimEnd('\r', '\n'), 0, Color.Blue);var prompt = mm; // 从文本框读取提示词var keepChatting = true;var chat = new Chat(ollama, prompt);Invoke(new Action(() =>{richTextBox1.AppendText("deepSeek-R1>:");}));BeginSiKao(keepChatting, chat, "");step = 3;if (button2.Visible == false){button2.Visible = true;}}}/// <summary>/// 取消回答/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void button2_Click(object sender, EventArgs e){mIsCancel = true;StopThinking();}public void StopThinking(){cancellationTokenSource?.Cancel(); // 取消当前的操作Invoke(new Action(() =>{button2.Visible = false; // 隐藏取消按钮button1.Enabled = true; // 恢复发送按钮textBox2.Focus(); // 让用户可以继续输入}));}private int mSelectItem = 99;private void listBox1_SelectedIndexChanged(object sender, EventArgs e){mSelectItem = listBox1.SelectedIndex;WorkFololw();}public static string FormatSql(string input){// 移除开头的 sql 和多余的空格input = input.Trim();// 用正则表达式找到从 sql 开头到下一个结束符号的 SQL 代码string pattern = @"`sql(.*?)```";var match = Regex.Match(input, pattern, RegexOptions.Singleline);if (match.Success){// 获取 sql 语句部分string sql = match.Groups[1].Value.Trim();// 分析 SQL 的每个部分并格式化return FormatSqlServerCreateTable(sql);}return input;}private static string FormatSqlServerCreateTable(string sql){// 分割 SQL 语句sql = sql.Replace("CREATETABLE", "CREATE TABLE").Replace("NOTNULL", "NOT NULL").Replace("VARCHAR", "VARCHAR").Replace("NVARCHAR", "NVARCHAR").Replace("CHECK", "CHECK").Replace("PRIMARYKEY", "PRIMARY KEY").Replace("UNIQUE", "UNIQUE").Replace("CHAR", "CHAR").Replace("DATENOTNULL", "DATE NOT NULL").Replace("TEXT", "TEXT").Replace("--", "-- "); // 确保注释有一个空格// 添加换行和缩进string formattedSql = "";int indentationLevel = 0;bool insideComment = false;for (int i = 0; i < sql.Length; i++){char currentChar = sql[i];// 检查是否进入注释if (i < sql.Length - 1 && sql.Substring(i, 2) == "--"){insideComment = true;}// 增加缩进处理if (currentChar == '('){formattedSql += " (";indentationLevel++;}else if (currentChar == ')'){formattedSql += "\n" + new string(' ', indentationLevel * 4) + ")";indentationLevel--;}else if (currentChar == ','){formattedSql += ",\n" + new string(' ', indentationLevel * 4);}else{if (insideComment){formattedSql += currentChar;if (currentChar == '\n'){insideComment = false;}}else{formattedSql += currentChar;}}}return formattedSql;}}
}
有些地方有些小bug,比如取消思考没有进行细节的处理,但是不影响正常的使用,
整体的逻辑就是:窗体启动时候在线程里面进行一个死循环,只要textBox文本框里面出现回车就根据变量step的值来进行对应的操作。目前无法给deepseek发送图片让他进行分析,只支持文字对话。断网也是可以继续运行的。
如有更好的想法,欢迎大家评论区畅所欲言!
相关文章:

本地部署deepseek大模型后使用c# winform调用(可离线)
介于最近deepseek的大火,我就在想能不能用winform也玩一玩本地部署,于是经过查阅资料,然后了解到ollama部署deepseek,最后用ollama sharp NUGet包来实现winform调用ollama 部署的deepseek。 本项目使用Vs2022和.net 8.0开发,ollam…...

Python----数据分析(Numpy:安装,数组创建,切片和索引,数组的属性,数据类型,数组形状,数组的运算,基本函数)
一、 Numpy库简介 1.1、概念 NumPy(Numerical Python)是一个开源的Python科学计算库,旨在为Python提供 高性能的多维数组对象和一系列工具。NumPy数组是Python数据分析的基础,许多 其他的数据处理库(如Pandas、SciPy)都依赖于Num…...

Leetcode-最大矩形(单调栈)
一、题目描述 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 输入:matrix [["1","0","1","0","0"],["1","0&…...

域内委派维权
为某个服务账户配置 krbtgt 用户的非约束性委派或基于资源的约束性委派。这里我的 krbtgt 的基于资源约束性委派我利用不了,所以使用的是域控的机器账户 dc01$ 进行维权。 抓取所有 hash。 mimikatz.exe "privilege::debug" "lsadump::dcsync /doma…...

leetcode---LCR 140.训练计划
给定一个头节点为 head 的链表用于记录一系列核心肌群训练项目编号,请查找并返回倒数第 cnt 个训练项目编号。 示例 1: 输入:head [2,4,7,8], cnt 1 输出:8 提示: 1 < head.length < 1000 < head[i] <…...
Linux基础 -- ARM 32位常用机器码(指令)整理
ARM 32位常用机器码(指令)整理 1. 数据处理指令(运算、逻辑、比较) 指令含义示例备注MOV赋值(寄存器传输)MOV R0, R1直接将 R1 复制到 R0MVN取反MVN R0, R1R0 ~R1ADD加法ADD R0, R1, R2R0 R1 R2ADC带进…...
内存中的缓存区
在 Java 的 I/O 流设计中,BufferedInputStream 和 BufferedOutputStream 的“缓冲区”是 内存中的缓存区(具体是 JVM 堆内存的一部分),但它们的作用是优化数据的传输效率,并不是直接操作硬盘和内存之间的缓存。以下是详…...

基于 Spring Boot 的 +Vue“宠物咖啡馆平台” 系统的设计与实现
大家好,今天要和大家聊的是一款基于 Spring Boot 的 “宠物咖啡馆平台” 系统的设计与实现。项目源码以及部署相关事宜请联系我,文末附上联系方式。 项目简介 基于 Spring Boot 的 “宠物咖啡馆平台” 系统设计与实现的主要使用者分为 管理员、用户 和…...

LeetCode 解题思路 7(Hot 100)
解题思路: 初始化窗口元素: 遍历前 k 个元素,构建初始单调队列。若当前索引对应值大于等于队尾索引对应值,移除队尾索引,将当前索引加入队尾。遍历结束时当前队头索引即为当前窗口最大值,将其存入结果数组…...
linux-Dockerfile及docker-compose.yml相关字段用途
文章目录 计算机系统5G云计算LINUX Dockerfile及docker-conpose.yml相关字段用途一、Dockerfile1、基础指令2、.高级指令3、多阶段构建指令 二、Docker-Compose.yml1、服务定义(services)2、高级服务配置3、网络配置 (networks)4、卷配置 (volumes)5、扩…...
deepseek部署:ELK + Filebeat + Zookeeper + Kafka
## 1. 概述 本文档旨在指导如何在7台机器上部署ELK(Elasticsearch, Logstash, Kibana)堆栈、Filebeat、Zookeeper和Kafka。该部署方案适用于日志收集、处理和可视化场景。 ## 2. 环境准备 ### 2.1 机器分配 | 机器编号 | 主机名 | IP地址 | 部署组件 |-…...

微软Office 2016-2024 x86直装版 v16.0.18324 32位
微软 Office 是一款由微软公司开发的办公软件套装,能满足各种办公需求。包含 Word、Excel、PowerPoint、Outlook 和 OneNote 等软件。Word 有强大文档编辑功能和多人协作;Excel 可处理分析大量数据及支持宏编程;PowerPoint 用于制作演示文稿且…...
CMake宏定义管理:如何优雅处理第三方库的宏冲突
在C/C项目开发中,我们常常会遇到这样的困境: 当引入一个功能强大的第三方库时,却发现它定义的某个宏与我们的项目产生冲突。比如: 库定义了 BUFFER_SIZE 1024,而我们需要 BUFFER_SIZE 2048库内部使用 DEBUG 宏控制日志…...

【SpringCloud】Gateway
目录 一、网关路由 1.1.认识网关 1.2.快速入门? 1.2.1.引入依赖 1.2.2.配置路由 二、网关登录校验 2.1.Gateway工作原理 ?2.2.自定义过滤器 2.3.登录校验 2.4.微服务获取用户 2.4.1.保存用户信息到请求头 2.4.2.拦截器获取用户? ?2.5.OpenFeign传递用户 三、…...

Maven入门教程
一、Maven简介 Maven 是一个基于项目对象模型(Project Object Model)的构建工具,用于管理 Java 项目的依赖、构建流程和文档生成。它的核心功能包括: 依赖管理(Dependency Management):自动下载和管理第三方库&#x…...
大数据与金融科技:革新金融行业的动力引擎
大数据与金融科技:革新金融行业的动力引擎 在今天的金融行业,大数据与金融科技的结合正在以惊人的速度推动着金融服务的创新与变革。通过精准的数据分析与智能化决策,金融机构能够更高效地进行风险管理、客户服务、资产管理等一系列关键操作…...

Autosar RTE配置-Port Update配置及使用-基于ETAS工具
文章目录 前言Autosar Rte中enableUpdate参数定义ETAS工具中的配置生成代码分析总结前言 在E2E校验中,需要对Counter进行自增,但每个报文周期不一样,导致自增的周期不一样。且Counter应该在收到报文之后才进行自增。基于这些需求,本文介绍使用RTE Port中的参数enableUpdat…...

【AVRCP】深入理解蓝牙音频 / 视频远程控制规范:从基础到应用
AVRCP(Audio/Video Remote Control Profile)作为蓝牙音频 / 视频控制领域的重要规范,通过其完善的协议架构、丰富的功能分类以及对用户需求的深入考量,为我们带来了便捷、高效的音频 / 视频设备控制体验。无论是在日常生活中的音乐…...
AWS SQS跨账户访问失败排查指南
引言 在使用AWS SQS(Simple Queue Service)时,跨账户访问是常见的业务场景。例如,账户A的应用程序向队列发送消息,账户B的消费者从队列拉取消息。尽管AWS官方文档明确支持此类配置,但在实际应用中,由于权限模型的复杂性,开发者和运维人员常会遇到“策略已配置但无法接…...
算法训练(leetcode)二刷第三十八天 | 1143. 最长公共子序列、1035. 不相交的线、53. 最大子数组和、392. 判断子序列
刷题记录 1143. 最长公共子序列1035. 不相交的线53. 最大子数组和动态规划优化版 392. 判断子序列 1143. 最长公共子序列 leetcode题目地址 本题和300. 最长递增子序列相似(题解)。 使用动态规划: dp数组含义:dp[i][j]表示 以…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...