本文最后更新于:2025年3月5日 晚上
在 .NET 中使用 OpenAI 包接入 LLM
写在最前面
我建议有什么地方看不懂或者不明白地的就去提问 ChatGPT、Claude、DeepSeek。
鉴于本人会直接在文章中对部分代码进行修改与调整(又或者偷偷加点注释),所以不能确保代码 Copy 过去不会报红,请根据需要自行调整,感谢您的理解与配合(鞠躬)。
错误指正或修改建议 -> 联系邮箱:Centaurea_G@hotmail.com
推荐安装的包
如图所示:
答应我,没事就别去装预发行版了好吗?
相关类介绍
ChatClient
及其相关类
ChatClient
类,最关键的一个类,用来创建聊天客户端,我一般喜欢使用 ChatClient(string model, ApiKeyCredential credential, OpenAIClientOptions options)
来创建它的对象,其中第一个参数 model
指模型名,第二个参数接受一个 ApiKeyCredential
对象,我们直接用我们的 apiKey 创建一个该对象即可,第三个对象用于配置客户端的选项,比如 EndPoint
,若配置此项,则客户端将会向指定的 Uri
发送 request 。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void InitializeChatClient () { string ? apiKey = Environment.GetEnvironmentVariable("BAILIAN_API_KEY" ); if (string .IsNullOrEmpty(apiKey)) { MessageBox.Show("Can not get the api key!" ); return ; } _apiKeyCredential = new ApiKeyCredential(apiKey); _openAIClientOptions = new OpenAIClientOptions(); _openAIClientOptions.Endpoint = new Uri("https://dashscope.aliyuncs.com/compatible-mode/v1" ); _openAIClientOptions.UserAgentApplicationId = "Centaurea" ; _openAIClientOptions.ProjectId = "deepseek-test" ; _openAIClientOptions.RetryPolicy = ClientRetryPolicy.Default; _chatClient = new (model: _modelName, credential: _apiKeyCredential, options: _openAIClientOptions); }
在上面的示例中,我把 apiKey 放在了系统环境变量中,并将EndPoint
指向了阿里云百炼提供的 API 接口(并非广告!),其它配置其实都无关痛痒,可以不用理会(
我要怎么获取 EndPoint 和 ApiKey ?
阅读提供大语言模型服务的供应商的文档。
ChatMessage
及其派生类
看名字就能够知道这是用来存放聊天消息的类www,但是要注意的是:一个 ChatMessage
对象代表对话中某一方的一条消息 ,并非整个对话。例如:用户发起提问”1+1等于几“,这就是一个 ChatMessage
,而模型回答”2“,这是另一个 ChatMessage
。
ChatMessage
有两个属性:一个是 ChatMessageRole
类 的Role
属性,用于记录聊天消息的角色,可以理解成本次消息的发送者;另一个是ChatMessageContent
类的 Content
属性,用于存放本次聊天的内容。
一般来说,前三个子类已经能够满足日常使用,这里简单介绍下:
SystemChatMessage
:引导类的消息,比如”你是一位乐于助人的海盗,请你用海盗的口吻进行回答。“这类的。
UserChatMessage
:用户(我们)发送的消息。
AssistantChatMessage
:模型(ChatGPT)发送的消息。
用法
ChatMessage
并不支持直接实例化(不提供 public
的 constructor
)。要创建用于存放聊天消息的对象,要么使用 ChatMessage
提供的公有静态方法CreateXXMessage
( XX
为某个角色,如 User
),其返回对应的派生类的对象;要么直接调用派生类的 constructor
创建对象。
1 2 _historyMessages.Add(new SystemChatMessage("你是一只乐于助人的猫娘。" )); _historyMessages.Add(ChatMessage.CreateSystemMessage("你是一只乐于助人的猫娘。" ));
在这里, _historyMessages
是一个 List<ChatMessage>
对象,因为 SystemChatMessage
是 ChatMessage
的派生类,所以其自然可以隐式转换成 ChatMessage
。
开始聊天
此处默认您已经创建好了ChatClient
对象,若没有,请阅读:[ChatClient
及其相关类](#ChatClient
及其相关类)
基本用法
1 2 ChatCompletion completion = _chatClient.CompleteChat("请帮我用C#写一个程序,循环输出1到100。" ); Console.Write(completion.Content[0 ].Text);
多轮对话
由于服务端并不会帮我们保存对话记录,所以我们需要将对话记录保存在本地,并且在发送请求的时候需要将对话记录一并传送出去。
1 2 3 4 5 6 7 8 9 10 private List<ChatMessage> _historyMessages = new List<ChatMessage>(); _historyMessages.Add(new SystemChatMessage("你是一只乐于助人的猫娘。" )); _historyMessages.Add(new UserChatMessage("请帮我用C#写一个程序,循环输出1到100。" )); ClientResult<ChatCompletion> result = _chatClient.CompleteChat(_historyMessages);if (result != null ) { string output = result.Value.Content[0 ].Text; Console.WriteLine(output); _historyMessages.Add(new AssistantChatMessage(output)); }
使用协议方法(Protocol methods)
胜似HttpClient
的方法(指拼 content
)。 openai-dotnet
中对其的描述是:“Enable more direct access to the REST API.”。通过这样的方式,可以将更多参数加入到 request 的 content 里面,比如 temperature
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 BinaryData input = BinaryData.FromBytes("" " { " model": " deepseek-r1-distill-qwen-32b ", " messages": [ { " role": " user", " content": " 下午好啊~" } ] } " "" u8.ToArray());using BinaryContent content = BinaryContent.Create(input); ClientResult result = _chatClient.CompleteChat(content); BinaryData output = result.GetRawResponse().Content;using JsonDocument outputAsJson = JsonDocument.Parse(output.ToString());string ? message = outputAsJson.RootElement .GetProperty("choices" u8)[0 ] .GetProperty("message" u8) .GetProperty("content" u8) .GetString(); Console.WriteLine(messages)
使用流式输出
在使用 CompleteChat
方法时,都是在等服务器生成好一个完整的回复后再将其返回在一次 response 中,所以生成的 token 如果太多的话,可能很久都收不到响应。这个时候就可以用流式传输 来返回部分结果,我们也可以先处理已经返回的这些结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 StringBuilder contents = new StringBuilder("" );string styledHtmlStart = "<html><body>" ;string styledHtmlEnd = "</body></html>" ; AsyncCollectionResult<StreamingChatCompletionUpdate> completionUpdates = _chatClient.CompleteChatStreamingAsync("介绍一下C#中的async与await,最好可以给出一个实用的例子。" );await foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates) { if (completionUpdate.ContentUpdate.Count > 0 ) { string currentText = completionUpdate.ContentUpdate[0 ].Text; contents.Append(currentText); wbvAnswerOutput.BeginInvoke(new Action(() => { wbvAnswerOutput.CoreWebView2.NavigateToString(styledHtmlStart + Markdown.ToHtml(contents.ToString()) + styledHtmlEnd); })); } }
上面的示例能让 wbvAnswerOutput
控件实现一段一段回答逐渐显示的效果。
其他用法(如音频、图片)
我懒,还没研究这块。(另外这块得模型支持音频或图片处理才行,比如: gpt-4o-audio-preview
、 dall-e-3
)
其他问题
我不想使用协议方法,该如何获取思考过程?
ClientResult
类本身提供一个 GetRawResponse
方法,不仅可以获取到返回响应的 Content
,也可以获取到 Headers
、 Status
等,如下图所示:
1 2 3 4 5 6 7 BinaryData output = result.GetRawResponse().Content; using JsonDocument outputAsJson = JsonDocument.Parse(output.ToString());string ? thinkingMessage = outputAsJson.RootElement .GetProperty("choices" u8)[0 ] .GetProperty("message" u8) .GetProperty("reasoning_content" u8) .GetString();
主包主包,我不想使用 OpenAI 包,我要自己使用 HttpClient 封装!
那你很喜欢折腾了。
可以的兄弟可以的,用 HttpClient
更灵活且支持更多选项(如temperature
)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var client = new HttpClient();var request = new HttpRequestMessage(HttpMethod.Post, "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" ); request.Headers.Add("Authorization" , "Bearer " + Environment.GetEnvironmentVariable("BAILIAN_API_KEY" ));var content = new StringContent("" " { " model": " deepseek-r1-distill-qwen-32b ", " messages": [ { " role": " user", " content": " 9.9 和9.11 谁大" } ], " temperature": 1 } " "" , Encoding.UTF8, "application/json" ); request.Content = content;var response = await client.SendAsync(request); response.EnsureSuccessStatusCode();string result = response.Content.ReadAsStringAsync().Result;using JsonDocument outputAsJson = JsonDocument.Parse(result);string ? message = outputAsJson.RootElement .GetProperty("choices" u8)[0 ] .GetProperty("message" u8) .GetProperty("content" u8) .GetString();
如何接入本地模型?
什么时候 RTX5090 卖 2w 了,我就马上接入本地模型并更新文章。
等换张卡先吧…实在不想部署 1.5b…
可以提供源码吗?
不就在文章里吗。
为什么源码不直接推 GitHub?
推了,但是 private。
为什么会写这篇文章?
太闲了。
参考文档
OpenAI .NET API library
DeepSeek API 文档
以及各个大语言模型。
希望本文章能够帮到您~