网站购买云空间,公司设计网页设计,网站建设的三大原则,md短视频传媒免费版怎么下载介于最近deepseek的大火#xff0c;我就在想能不能用winform也玩一玩本地部署#xff0c;于是经过查阅资料#xff0c;然后了解到ollama部署deepseek,最后用ollama sharp NUGet包来实现winform调用ollama 部署的deepseek。 本项目使用Vs2022和.net 8.0开发#xff0c;ollam… 介于最近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,直接ctrlc复制然后回车他就会自动下载这里有个小技巧他下载会越来越慢我们可以按一下ctrlc再按一下键盘的上方向键他就会接着下载这个时候慢慢就快起来了。
下载完成后我们新打开一个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 ListModel 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/// 流程交互/// /summarypublic 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 namekeepChatting/param/// param namechat/parampublic async void BeginSiKao(bool keepChatting, Chat chat, string mImageMsg){//开始聊天await BeginChat(keepChatting, chat, mImageMsg);}/// summary/// 加载本地大模型/// /summarypublic 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 namekeepChatting/param/// param namechat/param/// returns/returnspublic 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/// 清空输入文本框/// /summarypublic void Clear(){Invoke(new Action(() {textBox2.Clear();}));}/// summary/// 更新控件的一些值或者追加文字/// /summary/// param namemessage/param/// param namemtype0追加文字1大模型使用/parampublic 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 namesender/param/// param namee/paramprivate 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 namesender/param/// param namee/paramprivate 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发送图片让他进行分析只支持文字对话。断网也是可以继续运行的。
如有更好的想法欢迎大家评论区畅所欲言