文末附全部源代码
(1):踏上旅程
(2):引入 Semantic Kernel 框架
(3): 创建第一个 AI 应用
(4):提示模板
在前面的章节中,我们探索了构建一个基础的问答系统。然而,在当今的人工智能领域,更为流行的是类似于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.IsOrEmpty(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.IsOrEmpty(responseMessage))
{
chatHistory.Last().AiMessage.TextMessage += responseMessage;
yield return gr.Output("", chatHistory);
}
await Task.Delay(50);
}
}