【C#实现手写Ollama服务交互,实现本地模型对话】

前言

C#手写Ollama服务交互,实现本地模型对话

最近使用C#调用OllamaSharpe库实现Ollama本地对话,然后思考着能否自己实现这个功能。经过一番查找,和查看OllamaSharpe源码发现确实可以。其实就是开启Ollama服务后,发送HTTP请求,获取返回结果以及一些数据处理。

基本流程

1、启动Ollama服务进程。
2、创建HttpClient对象。
3、创建请求体(参数:模型名称、提示语、是否流式生成)。
4、将请求体序列化为JSON。
5、创建HTTP请求内容。
6、发送POST请求,并确保请求成功。
7、读取并返回响应内容,并解析相应字符串。
8、返回结果。

 		//创建请求体:模型名称、提示语、是否流式生成var request = new RequestModel{Model = model,Prompt = prompt,Stream = false};// 将请求体序列化为JSONvar json = JsonSerializer.Serialize(request);// 创建HTTP请求内容var content = new StringContent(json, Encoding.UTF8, "application/json");// 发送POST请求var response = await _httpClient.PostAsync("/api/generate", content);// 确保请求成功response.EnsureSuccessStatusCode();// 读取并返回响应内容string responseString = await response.Content.ReadAsStringAsync();///解析相应字符串ResponseModel results = JsonSerializer.Deserialize<ResponseModel>(responseString);//返回结果return results.Response;

项目结构

OllamaClient :实现基本的对话请求、获取模型列表功能。
Model :创建模型结果的一些参数
RequestModel:请求参数模型
ResponseModel:结果参数模型,用于解析返回的结果。
MainWindow:用户界面
MainWindowViewModel:界面交互业务处理

在这里插入图片描述

案例

模型加载

在这里插入图片描述

发送聊天

在这里插入图片描述

代码

OllamaSharpe

Ollama客户端 OllamaClient

public class OllamaClient{public IEnumerable<Model> ModelList { get; set; }private readonly HttpClient _httpClient;public OllamaClient(string baseAddress = "http://localhost:11434"){_httpClient = new HttpClient{BaseAddress = new Uri(baseAddress)};ExecuteCommand("ollama list");  //启动Ollama服务}/// <summary>/// 异步生成文本/// </summary>public async Task<string> GenerateTextAsync(string model, string prompt){try{//创建请求体:模型名称、提示语、是否流式生成var request = new RequestModel{Model = model,Prompt = prompt,Stream = false};// 将请求体序列化为JSONvar json = JsonSerializer.Serialize(request);// 创建HTTP请求内容var content = new StringContent(json, Encoding.UTF8, "application/json");// 发送POST请求var response = await _httpClient.PostAsync("/api/generate", content);// 确保请求成功response.EnsureSuccessStatusCode();// 读取并返回响应内容string responseString = await response.Content.ReadAsStringAsync();///解析相应字符串ResponseModel results = JsonSerializer.Deserialize<ResponseModel>(responseString);//返回结果return results.Response;}catch (HttpRequestException e){throw new Exception($"Request failed: {e.Message}");}}/// <summary>/// 异步流式生成文本/// </summary>public async IAsyncEnumerable<string> StreamGenerateTextAsync(string model, string prompt){//创建请求体:模型名称、提示语、是否流式生成var request = new RequestModel{Model = model,Prompt = prompt, Stream = true};// 将请求体序列化为JSONvar json = JsonSerializer.Serialize(request);//创建HTTP请求内容var content = new StringContent(json, Encoding.UTF8, "application/json");//发送POST请求using var response = await _httpClient.PostAsync("/api/generate", content);// 确保请求成功response.EnsureSuccessStatusCode();// 读取流并解析为ResponseModelusing var stream = await response.Content.ReadAsStreamAsync();// 创建流读取器using var reader = new StreamReader(stream);// 循环读取流while (!reader.EndOfStream){// 读取一行var line = await reader.ReadLineAsync();// 如果行不为空,则解析为ResponseModel并返回if (!string.IsNullOrEmpty(line)){var partial = JsonSerializer.Deserialize<ResponseModel>(line);yield return partial.Response;}}}/// <summary>/// 异步获取本地模型列表/// </summary>public async Task<IEnumerable<Model>> ListLocalModelsAsync(){//相应请求HttpResponseMessage responseMessage = await _httpClient.GetAsync("/api/tags").ConfigureAwait(false);;//确保请求成功responseMessage.EnsureSuccessStatusCode();//读取响应string response = await responseMessage.Content.ReadAsStringAsync();//读取流并解析为LocalModelsLocalModels localModel = JsonSerializer.Deserialize<LocalModels>(response);await Task.Delay(3000);//返回结果ModelList = localModel.Models;return localModel.Models;}// <summary>/// 执行CMD指令:用于启动Ollama服务,/// </summary>public static bool ExecuteCommand(string command){// 创建一个新的进程启动信息ProcessStartInfo processStartInfo = new ProcessStartInfo{FileName = "cmd.exe",           // 设置要启动的程序为cmd.exeArguments = $"/C {command}",    // 设置要执行的命令UseShellExecute = true,         // 使用操作系统shell启动进程CreateNoWindow = false,         //不创建窗体};try{Process process = Process.Start(processStartInfo);// 启动进程process.WaitForExit();    // 等待进程退出process.Close();          // 返回是否成功执行return process.ExitCode == 0;}catch (Exception ex){Debug.WriteLine($"发生错误: {ex.Message}");// 其他异常处理return false;}}}

请求模型:RequestModel

/// <summary>
/// 请求模型
/// </summary>
public class RequestModel
{public string Model { get; set; }public string Prompt { get; set; }public bool Stream { get; set; }
}

响应模型:ResponseModel

/// <summary>
/// 响应模型
/// </summary>
public class ResponseModel
{/// <summary>/// 模型名称/// </summary>[JsonPropertyName("model")]public string Model { get; set; }/// <summary>/// 创建时间/// </summary>[JsonPropertyName("created_at")]public string CreatedTime { get; set; }/// <summary>/// 响应:返回文本/// </summary>[JsonPropertyName("response")]public string Response { get; set; }/// <summary>/// 是否结束/// </summary>[JsonPropertyName("done")]public bool Done { get; set; }/// <summary>/// 结束原因/// </summary>[JsonPropertyName("done_reason")]public string Done_Reason { get; set; }/// <summary>/// 上下文/// </summary>[JsonPropertyName("context")]public List<int> Context { get; set; }/// <summary>/// 总耗时/// </summary>[JsonPropertyName("total_duration")]public long TotalDuration { get; set; }/// <summary>/// 加载耗时/// </summary>[JsonPropertyName("load_duration")]public long LoadDuration { get; set; }/// <summary>/// 提示词评估次数/// </summary>[JsonPropertyName("prompt_eval_count")]public long PromptEvalCount { get; set; }/// <summary>/// 提示词评估耗时/// </summary>[JsonPropertyName("prompt_eval_duration")]public long PromptEvalDuration { get; set; }/// <summary>/// 评估次数/// </summary>[JsonPropertyName("eval_count")]public long EvalCount { get; set; }/// <summary>/// 评估耗时/// </summary>[JsonPropertyName("eval_duration")]public long EvalDuration { get; set; }
}

结果模型:LocalModels | Model

/// <summary>
/// 本地模型
/// </summary>
public class LocalModels
{[JsonPropertyName("models")]public IEnumerable<Model> Models { get; set; }
}
/// <summary>
/// 模型
/// </summary>
public class Model
{/// <summary>/// 模型名称/// </summary>[JsonPropertyName("name")]public string Name { get; set; }/// <summary>/// 模型名称/// </summary>[JsonPropertyName("model")]public string ModelName { get; set; }/// <summary>/// 修改时间/// </summary>[JsonPropertyName("modified_at")]public DateTime ModifiedAt { get; set; }/// <summary>/// 大小/// </summary>[JsonPropertyName("size")]public long Size { get; set; }/// <summary>/// /// </summary>[JsonPropertyName("digest")]public string Digest { get; set; }/// <summary>/// 模型细节/// </summary>[JsonPropertyName("details")]public ModelDetails Details { get; set; }
}/// <summary>
/// 模型细节
/// </summary>
public class ModelDetails
{/// <summary>/// 父模型/// </summary>[JsonPropertyName("parent_model")]public string ParentModel { get; set; }/// <summary>/// 格式/// </summary>[JsonPropertyName("format")]public string Format { get; set; }/// <summary>/// /// </summary>[JsonPropertyName("family")]public string Family { get; set; }/// <summary>/// /// </summary>[JsonPropertyName("families")]public List<string> Families { get; set; }/// <summary>/// 参数大小/// </summary>[JsonPropertyName("parameter_size")]public string ParameterSize { get; set; }/// <summary>/// 质量等级/// </summary>[JsonPropertyName("quantization_level")]public string QuantizationLevel { get; set; }
}

简单的界面

MainWindow

<Window.DataContext><local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>
<Grid><Grid.RowDefinitions><RowDefinition Height="50"/><RowDefinition Height="*"/><RowDefinition Height="300"/></Grid.RowDefinitions><Grid Grid.Row="0"><WrapPanel VerticalAlignment="Center" Margin="5"><Label Content="模型列表" Margin="5"/><ComboBox Width="200" Margin="5" Name="ModelListBox"ItemsSource="{Binding ModelCollection}"SelectedItem="{Binding SelectedModel}"/></WrapPanel></Grid><Grid Grid.Row="1"><TextBox x:Name="OutputBox" Text="{Binding OutputText}"ScrollViewer.HorizontalScrollBarVisibility="Visible"ScrollViewer.VerticalScrollBarVisibility="Visible"/></Grid><Grid Grid.Row="2"><Grid.RowDefinitions><RowDefinition Height="*"/><RowDefinition Height="50"/></Grid.RowDefinitions><TextBox Grid.Row="0" x:Name="InputBox" Background="#AAAAAA"Text="{Binding InputText}"TextWrapping="WrapWithOverflow"ScrollViewer.VerticalScrollBarVisibility="Auto"ScrollViewer.HorizontalScrollBarVisibility="Auto" ></TextBox><WrapPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5"><Button  Grid.Row="1"  Width="100" Height="30" x:Name="Btn_Submit" Command="{Binding SendQuestionCommand}">发送</Button></WrapPanel></Grid>
</Grid>

MainWindowViewModel

public class MainWindowViewModel: PropertyChangedBase
{#region 字段、属性private string _inputText = "";         //输入文本private string _outputText = "";        //输出文本private OllamaClient _ollama;            //Ollama客户端private string _selectedModel = "deepseek-r1:1.5b";     //选择模型private ObservableCollection<string> _modelCollection;  //模型列表#region 属性public ObservableCollection<string> ModelCollection{get => _modelCollection;set{if (_modelCollection != value){_modelCollection = value;OnPropertyChanged();}}}public string SelectedModel{get => _selectedModel;set{if (_selectedModel != value){_selectedModel = value;OnPropertyChanged();}}}private OllamaClient Ollama { get => _ollama; }public string OutputText{get => _outputText;set{if (_outputText != value){_outputText = value;OnPropertyChanged();}}}public string InputText{get => _inputText;set{if (_inputText != value){_inputText = value;OnPropertyChanged();}}}public ICommand SendQuestionCommand { get; set; }#endregion#endregionpublic MainWindowViewModel(){Initialze();}/// <summary>/// 初始化/// </summary>private void Initialze(){_ollama = new OllamaClient();_modelCollection = new ObservableCollection<string>();SelectedModel = "deepseek-r1:1.5b";var models = Ollama.ListLocalModelsAsync();AppendLine($"模型列表;{Environment.NewLine}");foreach (var model in models.Result){ModelCollection.Add(model.ModelName);AppendLine($"{model.ModelName}{FormatFileSize(model.Size)}\r\n");}SendQuestionCommand = new ParameterlessCommand(OnSendQuestion);}/// <summary>/// 格式化文件大小/// </summary>private string FormatFileSize(long bytes){string[] sizes = { "B", "KB", "MB", "GB", "TB" };int order = 0;while (bytes >= 1024 && order < sizes.Length - 1){order++;bytes = bytes / 1024;}return $"{bytes:0.##} {sizes[order]}";}/// <summary>/// 发送文本/// </summary>public async void OnSendQuestion(){try{AppendLine($"【用户】{InputText}\r\n\r\n");AppendLine($"【AI】\r\n\r\n");await foreach (var answerToken in Ollama.StreamGenerateTextAsync(SelectedModel, InputText)){AppendText(answerToken);}AppendLine($"\r\n");}catch (Exception ex){AppendText($"Error: {ex.Message}");}}/// <summary>/// 附加文本/// </summary>private async void AppendText(string text){Debug.Print($"{text}");OutputText += text;}/// <summary>/// 附加文本行/// </summary>private async void AppendLine(string text){Debug.Print($"{text}");OutputText += $"{text}\r\n";}
}
 /// <summary>/// 属性变更/// </summary>public class PropertyChangedBase : INotifyPropertyChanged{public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}

总结

案例代码实现了与Ollama的HTTP交互,通过使用HttpClient、JSON序列化和错误处理,提供了一个简洁的异步文本生成接口。适合直接调用本地Ollama服务的场景,更多功能,可以后续拓展。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/31417.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【十四】Golang 接口

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 接口接口定义接口初始化接口嵌套空接口存储任意类…

Spark(8)配置Hadoop集群环境-使用脚本命令实现集群文件同步

一.hadoop的运行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync远程同步 五.xsync脚本集群之间的同步 一.hadoop的运行模式 hadoop一共有如下三种运行方式&#xff1a; 1. 本地运行。数据存储在linux本地&#xff0c;测试偶尔用一下。我们上一节课使用…

基于Django的协同过滤算法养老新闻推荐系统的设计与实现

基于Django的协同过滤算法养老新闻推荐系统&#xff08;可改成普通新闻推荐系统使用&#xff09; 开发工具和实现技术 Pycharm&#xff0c;Python&#xff0c;Django框架&#xff0c;mysql8&#xff0c;navicat数据库管理工具&#xff0c;vue&#xff0c;spider爬虫&#xff0…

基于STM32的逻辑分析仪

目录 制约性能因素协议命令下位机回复CMD_ID的回复CMD_METADATA命令的回复上报的采样数 设置使用开源软件PulseView设置操作1&#xff0e;设置采样数2&#xff0e;设置采样频率3.使能或禁止通道4.设置通道的触发条件 实现准备汇编指令精确测量时间 程序C语言初实现采集数据上报…

Kafka,Mq,Redis作为消息队列使用时的差异?|消息队列

在分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff0c;MQ&#xff09;扮演着至关重要的角色&#xff0c;负责解耦系统、削峰填谷、提升系统的吞吐量。Kafka、传统的MQ&#xff08;如RabbitMQ、ActiveMQ&#xff09;和Redis在实际应用中都被广泛用作消息队列…

SpringBoot + vue 管理系统

SpringBoot vue 管理系统 文章目录 SpringBoot vue 管理系统 1、成品效果展示2、项目准备3、项目开发 3.1、部门管理 3.1.1、前端核心代码3.1.2、后端代码实现 3.2、员工管理 3.2.1、前端核心代码3.2.2、后端代码实现 3.3、班级管理 3.3.1、前端核心代码3.3.2、后端代码实现 …

JVM常用概念之本地内存跟踪

问题 Java应用启动或者运行过程中报“内存不足&#xff01;”&#xff0c;我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言&#xff0c;需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构&#xff0c;来保证JVM应用的成功启动以及未来平…

计算机网络基础知识(web漏洞解析与攻防实战)

感谢机械工业出版社赠送的《web漏洞解析与攻防实战》&#xff0c;这本书是2023年刚刚出版的&#xff0c;作者是安全圈子里非常出名的do9gy和phith0n&#xff0c;今日有幸拜读大作&#xff0c;欣喜万分。受益匪浅的一点是&#xff0c;认识到POST请求上传文件的时候&#xff0c;H…

Vue 过滤器 filter(s) 的使用

即过滤器是用来格式化数据的一个函数。过滤器不会修改原始数据&#xff0c;它的作用是过滤数据&#xff0c;就是对数据进行加工处理并返回处理后的数据&#xff0c;比如做一些数据格式上的修改&#xff0c;状态转换等。 过滤器分为两种 组件内的过滤器(组件内有效) 全局过滤器…

cocos creator使用mesh修改图片为圆形,减少使用mask,j减少drawcall,优化性能

cocos creator版本2.4.11 一个mask占用drawcall 3个以上&#xff0c;针对游戏中技能图标&#xff0c;cd,以及多玩家头像&#xff0c;是有很大优化空间 1.上代码&#xff0c;只适合单独图片的&#xff0c;不适合在图集中的图片 const { ccclass, property } cc._decorator;c…

deepseek的regflow安装mac版本

deepseek的ragflow部署安装 一:ollama安装,自行完成,我本地已安装 二:查看大模型情况oll::命令ollama list,我本地无ragflow 三:docker安装:命令docker version ,自行完成,我本地已安装 四:安装知识库软件ragflow: 简单科普下Ragflow 是一个基于深度学习模型的问答生成工具&…

【华三】STP端口角色与状态深度解析

STP端口角色与状态深度解析&#xff1a;构建无环网络的基石 引言一、STP基础回顾二、端口角色详解1. 根端口&#xff08;Root Port&#xff09;2. 指定端口&#xff08;Designated Port&#xff09;3. 非指定端口&#xff08;阻塞端口&#xff09; 三、端口状态转换流程四、角色…

【JavaWeb学习Day23】

Maven高级 分模块设计与开发 分模块设计&#xff1a;将一个大项目分成若干个子模块&#xff0c;方便项目的维护、扩展&#xff0c;也方便模块间的相互引用&#xff0c;资源共享。 策略&#xff1a; 1.策略一&#xff1a;按照功能模块拆分&#xff0c;比如&#xff1a;公共组…

MySQL(社区版)安装过程

1.下载地址 mysql官方网站&#xff1a;www.mysql.com 也可以从Oracle官网进入&#xff1a;https://www.oracle.com/ 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 选择对应版本以及操作系统&#xff0c;MSI是安装包方式&#xff0c;ZIP是压缩包方式。 2.解…

【神经网络】python实现神经网络(二)——正向推理的模拟演练

一.神经网络假设 在开始讲解之前,首先我们假设有这样一套神经网络,一共有三层: 其中,关于神经网络的权重、偏置的符号定义如下(如果不知道什么是权重和偏置,可以参考我之前写过的一篇文章:【机器学习】机器学习是什么意思): 以下文章将沿用以上这个设…

Ubuntu用户安装cpolar内网穿透

前言 Cpolar作为一款体积小巧却功能强大的内网穿透软件&#xff0c;不仅能够在多种环境和应用场景中发挥巨大作用&#xff0c;还能适应多种操作系统&#xff0c;应用最为广泛的Windows、Mac OS系统自不必多说&#xff0c;稍显小众的Linux、树莓派、群辉等也在起支持之列&#…

leetcode 78. 子集(二进制枚举详解)c++

⼆进制枚举 ⼆进制枚举&#xff1a;⽤⼀个数⼆进制表⽰中的 0/1 表⽰两种状态&#xff0c;从⽽达到枚举各种情况。 利⽤⼆进制枚举时&#xff0c;会⽤到⼀些位运算的知识。关于⽤⼆进制中的 0/1 表⽰状态这种⽅法&#xff0c;以后在讨论状态压缩 dp 中会继续使⽤到。 ⼆进制…

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程(通用)!

PyCharm 接入 DeepSeek、OpenAI、Gemini、Mistral等大模型完整版教程&#xff08;通用&#xff09;&#xff01; 当我们成功接入大模型时&#xff0c;可以选中任意代码区域进行解答&#xff0c;共分为三个区域&#xff0c;分别是选中区域、提问区域以及回答区域&#xff0c;我…

调试正常 ≠ 运行正常:Keil5中MicroLIB的“量子态BUG”破解实录

调试正常 ≠ 运行正常&#xff1a;Keil5中MicroLIB的“量子态BUG”破解实录——从勾选一个选项到理解半主机模式&#xff0c;嵌入式开发的认知升级 &#x1f4cc; 现象描述&#xff1a;调试与烧录的诡异差异 在线调试时 程序正常运行 - 独立运行时 设备无响应 ! 编译过程 0 Err…

使用PySpark进行大数据处理与机器学习实战指南

1. 技术介绍 1.1 PySpark概述 PySpark是Apache Spark的Python API&#xff0c;它结合了Python的易用性和Spark的分布式计算能力&#xff0c;能够高效处理PB级数据集。Spark基于内存计算的特性使其比传统Hadoop MapReduce快10-100倍&#xff0c;支持流处理、SQL查询、机器学习…