本地部署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]表示 以…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...