在Blazor中应用Semantic Kernel打造流畅的流式输出与多轮对话

        在今天的技术分享中,我们将深入探讨如何在Blazor应用中运用Semantic Kernel来实现流式输出和多轮对话的高效方案,为用户带来更快捷的交互体验。这一技术的应用开启了一个全新的篇章,在我们的AntSK项目中就得到了生动的实践和积极的反馈。现在,我们就来详细解读实现这一功能的步骤和技巧。

流式输出:快速响应的秘诀

        首先,我们需要了解什么是流式输出。在传统的请求-响应模式中,用户发送请求后往往需要等待整个响应内容返回后才能看到结果,这在多数据或复杂查询场景下会造成显著的等待时间。流式输出正是为解决这一问题而生,它能够实现逐步返回数据,让用户能够更早地看到部分结果,提高整体的响应性能。

        在Blazor中实现流式输出,我们需要将标准的_kernel.InvokeAsync方法替换为_kernel.InvokeStreamingAsync。这一替换意味着我们开始逐步、连续地返回内容,而非一次性返回全部数据。

        以AntSK项目为例,以下是实现流式输出的关键代码:

var func = _kernel.CreateFunctionFromPrompt(app.Prompt, new OpenAIPromptExecutionSettings());
var chatResult = _kernel.InvokeStreamingAsync<StreamingChatMessageContent>(function: func, arguments: new KernelArguments() { ["input"] = msg });
MessageInfo info = null;
var markdown = new Markdown();


await foreach (var content in chatResult)
{
    if (info == null)
    {
        info = new MessageInfo
        {
            ID = Guid.NewGuid().ToString(),
            Questions = questions,
            Answers = content.Content!,
            HtmlAnswers = content.Content!,
            CreateTime = DateTime.Now
        };
        
        MessageList.Add(info);
    }
    else
    {
        info.HtmlAnswers += content.Content;
    }
    await InvokeAsync(StateHasChanged);
}

        在上述代码中,我们遍历chatResult,这是一个我们从Semantic Kernel流式调用返回的异步枚举器。我们需要通过await InvokeAsync(StateHasChanged)来确保每次接收到新内容时UI都能及时更新,保持与用户的交互流畅。

        让我们一起来看看效果吧!

        在这里,我们看到虽然实现了流式输出,但很明显,它并不流畅。是一次出来了一批字,然后顿几秒后又出来一批。于是我便研究了一下 到底是Blazor组件渲染还是流式返回的问题。

3ea6d11a5cf2866250fe52c1eee73ee7.png

        我通过打印foreach中的日志发现。SK的流式返回虽然在循环里是一次返回一个字,但是他是在同一时间内返回了10多个字,然后会停顿几秒 然后接着返回十几个字。我们为了让效果更佳流畅,便在循环中适当加入了一点点延迟。

await foreach (var content in chatResult)
 {
     if (info == null)
     {
         info = new MessageInfo();
         info.ID = Guid.NewGuid().ToString();
         info.Questions = questions;
         info.Answers = content.Content!;
         info.HtmlAnswers = content.Content!;
         info.CreateTime = DateTime.Now;


         MessageList.Add(info);
     }
     else
     {
         info.HtmlAnswers += content.Content;
         await Task.Delay(50);
         Console.WriteLine(DateTime.Now + "  " + content.Content);
     }
     await InvokeAsync(StateHasChanged);
 }

        修改后的代码如上,每个字加入了50毫秒的延迟。然后我们在来看一看运行效果

        在这个视频中,我们可以看到回复的结果很明显的流畅了许多。基本是比较平顺的,到此我们的流式输出就完成了。然后我们在全部输出完以后,想用markdown将格式统一归整一下。

//全部处理完后再处理一次Markdown
info!.HtmlAnswers = markdown.Transform(info.HtmlAnswers);
await InvokeAsync(StateHasChanged);

        这样效果就非常不错了。

多轮对话:智能交互的核心

        有了流式输出之后,下一步我们要实现的就是多轮对话功能。多轮对话意味着系统不仅能处理一个简单的查询,还能记住之前的交互,形成对话上下文,从而提供更为连贯和精准的回答。

        为此,我们设计了HistorySummarize方法,它的作用是对之前的对话历史进行汇总,确保语义上的连续性,同时减少对诸如API令牌这类资源的消耗。

        在Blazor中,实现这一功能需要借助Semantic Kernel提供的ConversationSummaryPlugin。下面是HistorySummarize的实现:

private async Task<string> HistorySummarize(string questions, string msg)
{
    StringBuilder history = new StringBuilder();
    foreach (var item in MessageList)
    {
        history.Append($"user:{item.Questions}{Environment.NewLine}");
        history.Append($"assistant:{item.Answers}{Environment.NewLine}");
    }


    KernelFunction sunFun = _kernel.Plugins.GetFunction("ConversationSummaryPlugin", "SummarizeConversation");
    var summary = await _kernel.InvokeAsync(sunFun, new() { ["input"] = $"内容是:{history.ToString()} {Environment.NewLine}请注意用中文总结" });
    string his = summary.GetValue<string>();
    msg = $"历史对话:{his}{Environment.NewLine}{questions}";
    return msg;
}

        在上面的代码中,我们首先拼接历史消息,然后利用SummarizeConversation功能进行汇总,并且确保输出内容为中文,这对于中文用户来说是非常重要的细节。

        为了使汇总更加适合中文环境,我们通过更改提示中的内容为中文(如:“请注意用中文总结”),以引导生成的总结也是中文形式。

        在整合好流式输出和会话总结之后,SendAsync方法负责处理用户输入和确保多轮对话的顺畅进行。这个方法根据应用的不同类型(如普通会话或知识库问答),执行相应的处理逻辑:

protected async Task<bool> SendAsync(string questions)
{
    string msg = questions;


    // 处理多轮会话
    if (MessageList.Count > 0)
    {
        msg = await HistorySummarize(questions, msg);
    }


    // 根据应用类型处理不同逻辑
    Apps app = _apps_Repositories.GetFirst(p => p.Id == AppId);
    switch (app.Type)
    {
        case "chat":
            // 普通会话
            await SendChat(questions, msg, app);
            break;
        case "kms":
            // 知识库问答
            await SendKms(questions, msg, app);
            break;
    }


    return await Task.FromResult(true);
}

        通过以上深入的探讨和代码示例,我们了解了如何在Blazor应用中通过Semantic Kernel实现流式输出和多轮对话,为用户提供更加流畅的交互体验。无论是及时的响应还是上下文的连续性,在实时交互的应用场景中都是至关重要的。而在AntSK项目中,这些技术的应用让我们的产品更加智能、高效,得到了用户的一致好评。

        现在,我们希望这篇文章能给你带来启发,让你的Blazor应用也能够通过流式输出和多轮对话技术,提升用户体验,驱动业务的发展。如果你对这些技术有任何疑问或想要更深一步的讨论,欢迎在评论区展开交流,我们将乐于解答你的问题。另外,请记得关注我们的公众号,获取更多.Net技术相关的精彩内容。

相关文章:

在Blazor应用中实现认证授权:一步步构建安全的.Net项目

AntSK:打造.Net全能AI智能知识库,共启智能化管理新时代!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值