【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.pswp.cn/bicheng/73020.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73020.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73020.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C#學習筆記02】基本元素與數據類型

引言 深入了解C語言的基本元素、計算機存儲器結構、常量與變量的概念以及數據類型。這些內容是C語言編程的基礎&#xff0c;掌握它們對于編寫高效、可靠的嵌入式程序至關重要。 1.C語言的基本元素 ?編程語言的發展離不開自然語言&#xff0c;所以編程語言的語法和詞匯也是由…

ESP8266TCP客戶端(單連接TCP Client)

單連接TCP Client 電腦作為服務器&#xff0c;8266作為客戶端 1.配置WiFi模式 ATCWMODE3 //softAPstation mode 相應&#xff1a;ok 2.連接路由器 ATCWJAP“SSID”&#xff0c;“password” //SSID就是wifi的名字&#xff0c; password WIFI密碼 響應&#xff…

洛谷 P2234:[HNOI2002] 營業額統計 ← STL set

【題目來源】 https://www.luogu.com.cn/problem/P2234 【題目描述】 Tiger 最近被公司升任為營業部經理&#xff0c;他上任后接受公司交給的第一項任務便是統計并分析公司成立以來的營業情況。 Tiger 拿出了公司的賬本&#xff0c;賬本上記錄了公司成立以來每天的營業額。分析…

VSCode 2025最新前端開發必備插件推薦匯總(提效指南)

&#x1f31f;前言: 如果你是一名前端開發工程師&#xff0c;合適的開發工具能大大提高工作效率。Visual Studio Code (VSCode) 憑借其輕量級、高擴展性的特點&#xff0c;已成為眾多前端開發者在win系電腦的首選IDE。 名人說&#xff1a;博觀而約取&#xff0c;厚積而薄發。—…

Java學習--Redis

官網&#xff1a;https://redis.io 中文網&#xff1a;Redis中文網 Redis安裝包分為 Windows 版和 Linux 版&#xff1a; Windows版下載地址&#xff1a;Releases microsoftarchive/redis GitHub Linux版下載地址&#xff1a; Index of /releases/ 一、Redis簡介 Redis是…

matlab慕課學習3.2+3.3

于20250310 3.2用if語句實現選擇結構 3.2.1什么是選擇結構 用if 語句和switch語句可實現選擇結構 3.2.2單分支if語句 if 條件語句組 %可以是一條也可是多條end 當條件為標量&#xff0c;非0表成立&#xff0c;0表示不成立。 當條件為矩陣時&#xff0c;矩陣非空&#xff…

JavaScript性能優化:DOM操作優化實戰

JavaScript性能優化&#xff1a;DOM操作優化實戰 一 重排與重繪的代價 問題場景 用戶點擊按鈕后&#xff0c;需要動態生成一個包含10,000個選項的下拉列表&#xff0c;但界面出現長達5秒的凍結。 錯誤代碼示例 function createList() {const ul document.getElementById(…

【Java學習】包裝類

面向對象系列九 包裝類變量 一、裝箱 1.實例化包裝對象 2.靜態緩存池 3.寫法 二、拆箱 包裝類變量 每個基本數據類型都有對應的基本類型的包裝類變量&#xff0c;將基本數據類型通過對應的包裝類對象載入著進入到類與對象面向對象體系 一、裝箱 Integer.valueOf(int) —…

【第22節】C++設計模式(行為模式)-Iterator(迭代器)模式

一、問題背景 Iterator 模式是設計模式中最為常見和實用的模式之一。它的核心思想是將對聚合對象的遍歷操作封裝到一個獨立的類中&#xff0c;從而避免暴露聚合對象的內部表示。通過 Iterator 模式&#xff0c;我們可以實現對聚合對象的統一遍歷接口&#xff0c;而不需要關心聚…

02C#基本結構篇(D4_注釋-訪問修飾符-標識符-關鍵字-運算符-流程控制語句)

目錄 一、注釋 1. 單行注釋 2. 多行注釋 3. XML文檔注釋 4. 使用建議和最佳實踐&#xff1a; 二、訪問修飾符 1. public 2. private 3. protected 4. internal 5. protected internal 或 protected and internal 6. private protected 或 private and protected 7.…

【CXX】6.2 str — rust::Str

Rust::Str 公共 API // rust/cxx.hclass Str final { public:Str() noexcept;Str(const Str &) noexcept;Str(const String &) noexcept;// 如果輸入不是 UTF-8&#xff0c;拋出 std::invalid_argument 異常。Str(const std::string &);Str(const char *);Str(con…

基于windows的MySQL安裝(2025最新,小白可用)

目錄 一&#xff0c;下載官網地址&#xff08;及版本選擇&#xff09;&#xff1a; 二&#xff0c;以安裝程序的方式安裝MySQL 1&#xff0c;安裝過程 2&#xff0c;用客戶端使用MySQL 3&#xff0c;配置環境變量在windows命令行界面使用mysql 下次開機后手動啟用服務 三…

Jenkins實現自動化構建與部署:上手攻略

一、持續集成與Jenkins核心價值 1.1 為什么需要自動化構建&#xff1f; 在現代化軟件開發中&#xff0c;團隊每日面臨以下挑戰&#xff1a; 高頻代碼提交&#xff1a;平均每個開發者每天提交5-10次代碼。多環境部署&#xff1a;開發、測試、預發布、生產環境需頻繁同步。復雜…

4個 Vue 路由實現的過程

大家好&#xff0c;我是大澈&#xff01;一個喜歡結交朋友、喜歡編程技術和科技前沿的老程序員&#x1f468;&#x1f3fb;?&#x1f4bb;&#xff0c;關注我&#xff0c;科技未來或許我能幫到你&#xff01; Vue 路由相信朋友們用的都很熟了&#xff0c;但是你知道 Vue 路由…

數學之快速冪-數的冪次

題目描述 給定三個正整數 N,M,P&#xff0c;求 輸入描述 第 1 行為一個整數 T&#xff0c;表示測試數據數量。 接下來的 T 行每行包含三個正整數 N,M,P。 輸出描述 輸出共 T 行&#xff0c;每行包含一個整數&#xff0c;表示答案。 輸入輸出樣例 示例 1 輸入 3 2 3 7 4…

【JavaEE】多線程進階(2)

【JavaEE】多線程進階&#xff08;2&#xff09; 一、JUC(java.util.concurrent) 的常?類1.1 Callable 接?1.2 ReentrantLock1.3 原子類原子類的特性&#xff1a;常見原子類&#xff1a;原子類的實例&#xff1a; 1.4 線程池1.5 信號量 Semaphore代碼實例 1.6 CountDownLatch…

[漏洞篇]XSS漏洞詳解

[漏洞篇]XSS漏洞 一、 介紹 概念 XSS&#xff1a;通過JS達到攻擊效果 XSS全稱跨站腳本(Cross Site Scripting)&#xff0c;為避免與層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆&#xff0c;故縮寫為XSS。這是一種將任意 Javascript 代碼插入到其他Web用戶頁面里執行以…

越早越好!8 個反直覺的金錢真相|金錢心理學

很多人都追求財富自由&#xff0c;但成功的人少之又少。 這可能是因為&#xff0c;人們往往忽略了一些金錢的真相和常識。 01 金錢常識 & 真相 為了構建健康的金錢觀&#xff0c;我讀了一本有點反直覺&#xff0c;有點像雞湯&#xff0c;但都是財富真相的書。 來自 Morg…

Spring Boot/Spring Cloud 整合 ELK(Elasticsearch、Logstash、Kibana)詳細避坑指南

我們在開發中經常會寫日志&#xff0c;所以需要有個日志可視化界面管理&#xff0c;使用ELK可以實現高效集中化的日志管理與分析&#xff0c;提升性能穩定性&#xff0c;滿足安全合規要求&#xff0c;支持開發運維工作。 下述是我在搭建ELK時遇到的許許多多的坑&#xff0c;希望…

AI編程: 一個案例對比CPU和GPU在深度學習方面的性能差異

背景 字節跳動正式發布中國首個AI原生集成開發環境工具&#xff08;AI IDE&#xff09;——AI編程工具Trae國內版。 該工具模型搭載doubao-1.5-pro&#xff0c;支持切換滿血版DeepSeek R1&V3&#xff0c; 可以幫助各階段開發者與AI流暢協作&#xff0c;更快、更高質量地完…