当前位置: 欣欣网 > 码农

使用 .NET 开发 AI 应用(5): 创建第一个 AI 聊天机器人应用

2024-06-18码农

文末附全部源代码


在前面的章节中,我们探索了构建一个基础的问答系统。然而,在当今的人工智能领域,更为流行的是类似于ChatGPT这样的聊天机器人。本章将指导你如何使用.NET技术开发一个类似的聊天机器人应用程序。

前端页面设计

Gradio.Net提供了一个内置的Chatbot聊天机器人组件,为了增强用户体验,我们还将添加一个文本框,使用户能够输入他们的问题或命令:

var chatbot = gr.Chatbot();
var txt = gr.Textbox(placeholder:"请输入您的问题后按回车键");

通过简洁的两行代码,我们就能够轻松创建一个直观的聊天界面。

后端逻辑实现

一旦界面部分准备就绪,我们接下来需要处理文本框的回车事件 Submit

txt.Submit(streamingFn: (input) => GenerateResponse(input, kernel), inputs: new Gradio.Net.Component[] { txt, chatbot }, outputs: new Gradio.Net.Component[] { txt, chatbot });

值得注意的是,我们采用 streamingFn 参数来处理Submit事件,这样可以启动组件的流式输出模式,从而实现类似ChatGPT的连续打字效果。

接着,我们定义一个生成响应的函数:

static async IAsyncEnumerable<Output> GenerateResponse(Input input, Kernel kernel)
{
var userPrompt = Textbox.Payload(input.Data[0]);
var chatHistory = Chatbot.Payload(input.Data[1]);
chatHistory.Add(new ChatbotMessagePair(new ChatMessage { TextMessage = userPrompt }, new ChatMessage { TextMessage = "" }));
await foreach (var responseMessage in kernel.InvokePromptStreamingAsync<string>(userPrompt))
{
if (!string.IsNullOrEmpty(responseMessage))
{
chatHistory.Last().AiMessage.TextMessage += responseMessage;
yield return gr.Output(""chatHistory);
}
await Task.Delay(50);
}
}

由于我们采用了流式输出,因此方法返回值必须是 IAsyncEnumerable<> 类型。

在这里, userPormpt 代表用户的输入,而 chatHistory 则是Chatbot组件内部保存的对话历史。由于对话通常是成对出现的,使用两个ChatMessage来分别代表本次对话中用户和AI的发言。由于AI尚未回复,因此其 TextMessage 字段为空。

我们使用SK提供的 InvokePromptStreamingAsync 方法来支持流式输出,通过简单的 foreach 循环,我们可以连续获取AI返回的文本。

最终,我们通过 yield return 语句及时输出AI的最新回复作为对话历史返回给Chatbot组件。

接下来,让我们看看实际运行的效果:

结论

在本章中,我们探讨了如何利用.NET技术构建一个功能齐全的聊天机器人。

通过Gradio.Net的组件,我们能够快速搭建起聊天界面,并且通过流式输出模式,实现了类似ChatGPT的动态交互效果。

在实现过程中,我们注意到了细节的重要性,比如如何处理文本框的回车事件,以及如何优雅地管理对话历史。这些细节不仅影响着程序的性能,也直接关系到用户的满意度。

Gradio.NET(https://github.com/feiyun0112/Gradio.Net/)的目标是成为用于开发 Web 应用的 .NET 开发者的首选框架。它的设计理念是让开发变得更加简单,让每个人都能够参与到Web应用的创造中来。

添加微信 GradioDotNet ,通过加入技术讨论群,开发者们可以分享经验,解决问题,并共同推动.NET的发展。

完整源代码:

using Gradio.Net;
using Microsoft.SemanticKernel;
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_GPT_NAME");
string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: deploymentName,
endpoint: endpoint,
apiKey: apiKey)
.Build();
App.Launch(await CreateBlocks(kernel));
static async Task<Blocks> CreateBlocks(Kernel kernel)
{
using (var blocks = gr.Blocks())
{
var chatbot = gr.Chatbot();
var txt = gr.Textbox(showLabel: false,
placeholder: "输入文本并按回车键"
);
txt.Submit(streamingFn: (input) => GenerateResponse(input, kernel), inputs: new Gradio.Net.Component[] { txt, chatbot }, outputs: new Gradio.Net.Component[] { txt, chatbot });
return blocks;
}
}
static async IAsyncEnumerable<Output> GenerateResponse(Input input, Kernel kernel)
{
var userPrompt = Textbox.Payload(input.Data[0]);
var chatHistory = Chatbot.Payload(input.Data[1]);
chatHistory.Add(new ChatbotMessagePair(new ChatMessage { TextMessage = userPrompt }, new ChatMessage { TextMessage = "" }));
await foreach (var responseMessage in kernel.InvokePromptStreamingAsync<string>(userPrompt))
{
if (!string.IsNullOrEmpty(responseMessage))
{
chatHistory.Last().AiMessage.TextMessage += responseMessage;
yield return gr.Output(""chatHistory);
}
await Task.Delay(50);
}
}