本地部署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读取用户输入的文字

一个发送按钮一个取消思考按钮

附上源代码:

cs 复制代码
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更新UI
                            richTextBox1.Invoke(new Action(() =>
                            {
                                if (isFirstToken)
                                {
                                    richTextBox1.Focus();
                                    isFirstToken = false;
                                }
                                richTextBox1.AppendText(answerToken.Trim());
                            }));
                        }

                    }
                    string newmsg = "";
                    if (mmsf.Contains("```sql")) 
                    {
                        newmsg= FormatSql(mmsf);
                        // 使用Invoke更新UI
                        richTextBox1.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发送图片让他进行分析,只支持文字对话。断网也是可以继续运行的。

如有更好的想法,欢迎大家评论区畅所欲言!

相关推荐
海兰5 分钟前
【文字三国志:第一篇】天命重构,大语言模型(LLM)动态生成文言风格的叙事文本的文字游戏
人工智能·游戏·语言模型
cxr82831 分钟前
高分子复合材料 AI 逆向设计合——验证闭环、决策优化与中试放大
人工智能·材料逆向设计合成
litble38 分钟前
如何速成LLM以伪装成一个AI研究者(6)——LoRA,Adapter,P-tuning,量化,QLoRA
人工智能·lora·量化·peft·qlora·高效微调
开发者每周简报1 小时前
网海三部曲·无名宗师传
javascript·人工智能
卷毛的技术笔记1 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
Cosolar1 小时前
从零写一个 Attention Is All You Need
人工智能·面试·架构
GHL2842710901 小时前
换脸工作流学习
学习·ai
ai_xiaogui1 小时前
PanelAI:新一代AI算力调度系统,支持本地大模型一键部署与商业运营
人工智能·panelai·panelai算力调度系统·本地大模型一键部署平台·ai应用市场管理面板·企业级部署·2026本地ai私有化解决方案
冬奇Lab2 小时前
Agent 系列(9):多 Agent 架构设计模式——Supervisor 与 Pipeline
人工智能·源码·agent