在 .NET 中使用 OpenAI 包接入 LLM

本文最后更新于: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"); // 从环境变量读取 apiKey
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 及其派生类

ChatMessage 说明

看名字就能够知道这是用来存放聊天消息的类www,但是要注意的是:一个 ChatMessage 对象代表对话中某一方的一条消息,并非整个对话。例如:用户发起提问”1+1等于几“,这就是一个 ChatMessage ,而模型回答”2“,这是另一个 ChatMessage

ChatMessage 有两个属性:一个是 ChatMessageRole 类 的Role 属性,用于记录聊天消息的角色,可以理解成本次消息的发送者;另一个是ChatMessageContent 类的 Content 属性,用于存放本次聊天的内容。

一般来说,前三个子类已经能够满足日常使用,这里简单介绍下:

  • SystemChatMessage :引导类的消息,比如”你是一位乐于助人的海盗,请你用海盗的口吻进行回答。“这类的。
  • UserChatMessage :用户(我们)发送的消息。
  • AssistantChatMessage :模型(ChatGPT)发送的消息。

用法

ChatMessage 并不支持直接实例化(不提供 publicconstructor )。要创建用于存放聊天消息的对象,要么使用 ChatMessage 提供的公有静态方法CreateXXMessageXX 为某个角色,如 User ),其返回对应的派生类的对象;要么直接调用派生类的 constructor 创建对象。

1
2
_historyMessages.Add(new SystemChatMessage("你是一只乐于助人的猫娘。"));
_historyMessages.Add(ChatMessage.CreateSystemMessage("你是一只乐于助人的猫娘。"));

在这里, _historyMessages 是一个 List<ChatMessage> 对象,因为 SystemChatMessageChatMessage 的派生类,所以其自然可以隐式转换成 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-previewdall-e-3

其他问题

我不想使用协议方法,该如何获取思考过程?

ClientResult 类本身提供一个 GetRawResponse 方法,不仅可以获取到返回响应的 Content ,也可以获取到 HeadersStatus 等,如下图所示:

ClientResult 都有什么

1
2
3
4
5
6
7
BinaryData output = result.GetRawResponse().Content;  // 获取 RawResponse,可以拿到思考过程("reasoning_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.99.11谁大"
}
],
"temperature": 1
}
""", Encoding.UTF8, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
//Console.WriteLine(await response.Content.ReadAsStringAsync());
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 文档

以及各个大语言模型。


这里有一只爱丽丝

希望本文章能够帮到您~


在 .NET 中使用 OpenAI 包接入 LLM
https://map1e-g.github.io/2025/03/05/how-to-use-openai-dotnet/
作者
MaP1e-G
发布于
2025年3月5日
许可协议