【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服务的场景,更多功能,可以后续拓展。
相关文章:

【C#实现手写Ollama服务交互,实现本地模型对话】
前言 C#手写Ollama服务交互,实现本地模型对话 最近使用C#调用OllamaSharpe库实现Ollama本地对话,然后思考着能否自己实现这个功能。经过一番查找,和查看OllamaSharpe源码发现确实可以。其实就是开启Ollama服务后,发送HTTP请求&a…...
Android Glide 框架线程管理模块原理的源码级别深入分析
一、引言 在现代的 Android 应用开发中,图片加载是一个常见且重要的功能。Glide 作为一款广泛使用的图片加载框架,以其高效、灵活和易用的特点受到了开发者的青睐。其中,线程管理模块是 Glide 框架中至关重要的一部分,它负责协调…...
每天记录一道Java面试题---day32
MySQL索引的数据结构、各自优劣 回答重点 B树:是一个平衡的多叉树,从根节点到每个叶子节点的高度差不超过1,而且同层级的节点间有指针相互连接。在B树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大…...

Vue3 Pinia 符合直觉的Vue.js状态管理库
Pinia 符合直觉的Vue.js状态管理库 什么时候使用Pinia 当两个关系非常远的组件,要传递参数时使用Pinia组件的公共参数使用Pinia...
深度学习与大模型基础-向量
大家好!今天我们来聊聊向量(Vector)。别被这个词吓到,其实向量在我们的生活中无处不在,只是我们没注意罢了。 1. 向量是什么? 简单来说,向量就是有大小和方向的量。比如你从家走到学校&#x…...
【网络编程】完成端口 IOCP
10.11 完成端口 10.11.1 基本概念 完成端口的全称是I/O 完成端口,英文为IOCP(I/O Completion Port) 。IOCP是一个异 步I/O 的 API, 可以高效地将I/O 事件通知给应用程序。与使用select() 或是其他异步方法不同 的是,一个套接字与一个完成端口关联了起来…...

《苍穹外卖》SpringBoot后端开发项目重点知识整理(DAY1 to DAY3)
目录 一、在本地部署并启动Nginx服务1. 解压Nginx压缩包2. 启动Nginx服务3. 验证Nginx是否启动成功: 二、导入接口文档1. 黑马程序员提供的YApi平台2. YApi Pro平台3. 推荐工具:Apifox 三、Swagger1. 常用注解1.1 Api与ApiModel1.2 ApiModelProperty与Ap…...
管理网络安全
防火墙在 Linux 系统安全中有哪些重要的作用? 防火墙作为网络安全的第一道防线,能够根据预设的规则,对进出系统的网络流量进行严格筛选。它可以阻止未经授权的外部访问,只允许符合规则的流量进入系统,从而保护系统免受…...

明日直播|Go IoT 开发平台,开启万物智联新征程
在物联网技术飞速发展的当下,物联网行业却深受协议碎片化、生态封闭、开发低效等难题的困扰。企业和开发者们渴望找到一个能突破这些困境,实现高效、便捷开发的有力工具。 3 月 11 日(星期二)19:00,GitCode 特别邀请独…...

系统架构设计师—系统架构设计篇—软件架构风格
文章目录 概述经典体系结构风格数据流风格批处理管道过滤器对比 调用/返回风格主程序/子程序面向对象架构风格层次架构风格 独立构件风格进程通信事件驱动的系统 虚拟机风格解释器基于规则的系统 仓库风格(数据共享风格)数据库系统黑板系统超文本系统 闭…...

【MySQL_04】数据库基本操作(用户管理--配置文件--远程连接--数据库信息查看、创建、删除)
文章目录 一、MySQL 用户管理1.1 用户管理1.11 mysql.user表详解1.12 添加用户1.13 修改用户权限1.14 删除用户1.15 密码问题 二、MySQL 配置文件2.1 配置文件位置2.2 配置文件结构2.3 常用配置参数 三、MySQL远程连接四、数据库的查看、创建、删除4.1 查看数据库4.2 创建、删除…...
【Zinx】Day5-Part4:Zinx 的连接属性设置
目录 Day5-Part4:Zinx 的连接属性设置给连接添加连接配置的接口连接属性方法的实现 测试 Zinx-v1.0总结 Day5-Part4:Zinx 的连接属性设置 在 Zinx 当中,我们使用 Server 来开启服务并监听指定的端口,当接收到来自客户端的连接请求…...

【英伟达AI论文】多模态大型语言模型的高效长视频理解
摘要:近年来,基于视频的多模态大型语言模型(Video-LLMs)通过将视频处理为图像帧序列,显著提升了视频理解能力。然而,许多现有方法在视觉主干网络中独立处理各帧,缺乏显式的时序建模,…...

小程序事件系统 —— 32 事件系统 - 事件分类以及阻止事件冒泡
在微信小程序中,事件分为 冒泡事件 和 非冒泡事件 : 冒泡事件:当一个组件的事件被触发后,该事件会向父节点传递;(如果父节点中也绑定了一个事件,父节点事件也会被触发,也就是说子组…...
全球首款 5G-A 人形机器人发布
全球首款 5G-A 人形机器人于世界移动通信大会(MWC2025)上由中国移动、华为、乐聚联合发布。以下是关于这款机器人的详细介绍: 名称与背景 名称9:这款人形机器人名为 “夸父”,是中国移动、华为与乐聚机器人在 GTI 平台…...
Tomcat 新手入门指南
Tomcat 新手入门指南 Apache Tomcat 是一个开源的 Java Servlet 容器和 Web 服务器,广泛用于部署和运行 Java Web 应用程序。以下是 Tomcat 的入门指南,帮助你快速上手。 1. 安装 Tomcat 步骤 1: 下载 Tomcat 访问 Apache Tomcat 官网。选择适合的版…...

Flink-DataStreamAPI-生成水印
下面我们将学习Flink提供的用于处理事件时间戳和水印的API,也会介绍有关事件时间、流转时长和摄取时间,下面就让我们跟着官网来学习吧 一、水印策略介绍 为了处理事件时间,Flink需要知道事件时间戳,这意味着流中的每个元素都需要…...

【单片机】ARM 处理器简介
ARM 公司简介 ARM(Advanced RISC Machine) 是英国 ARM 公司(原 Acorn RISC Machine) 开发的一种精简指令集(RISC) 处理器架构。ARM 处理器因其低功耗、高性能、广泛适用性,成为嵌入式系统、移动…...
Flutter——最详细原生交互(MethodChannel、EventChannel、BasicMessageChannel)使用教程
MethodChannel(方法通道) 用途:实现 双向通信,用于调用原生平台提供的 API 并获取返回结果。 场景:适合一次性操作,如调用相机、获取设备信息等。 使用步骤: Flutter 端:通过 Meth…...

Kafka常用指令(详细)
Kafka常用指令(详细) 启停命令 前台启动 前台启动命令 ./bin/kafka-server-start.sh config/server.properties 后台启动方式1 后台启动命令加上参数-daemon,窗口关闭之后kafka后台程序继续运行 ./bin/kafka-server-start.sh -daemon co…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...