微软计划使用 SignalR 和其他开源工具构建 AI 驱动的 Microsoft Copilot

        Microsoft Copilot 由多个开源工具提供支持,例如:SignalR、自适应卡片、Markdown 和对象盆地,以解决大规模构建支持 AI 的应用程序的独特挑战。在本文中,我们将分享设计注意事项以及如何集成各种工具。更具体地说,本文重点介绍我们如何将消息和响应流式传输到前端 UI 的各个方面,同时概述服务器端发生的情况。随着越来越多的团队和企业考虑利用大型语言模型 (LLMs),我们希望本文有助于扩展 AI 生态系统。

概述

        Microsoft Copilot 可在移动应用程序、必应等许多表面上使用,并且它内置于 Microsoft Edge 侧边栏中,为 Edge 用户提供轻松访问。它提供了流畅的对话体验,增强了传统的网络搜索和浏览体验。由于 Microsoft Copilot 由 AI 提供支持并驻留在 Edge 浏览器中,因此用户可以提出以自然人类语言形成的复杂问题,甚至可以在浏览 Web 时需要获得创意源泉时获得帮助。在幕后,最先进的 AI 模型通过考虑用户当前的浏览上下文并合成 Web 来生成响应。除了文本输入和输出之外,用户还可以使用语音和图像与 Microsoft Copilot 进行通信,以实现更丰富的交互。

 大纲

  • 使用 SignalR 建立低延迟通信通道

  • 使用自适应卡片 + Markdown 描述和呈现 UI

  •  申请流程

  • 深入探讨:我们如何使用 SignalR?

  •  下一步是什么?

使用 SignalR 建立低延迟通信通道

        等待 AI 模型生成完整响应后再将其发送回客户端是不切实际的。我们需要的是一种让服务器以块形式流式传输响应的方法,以便用户可以尽快看到部分结果。虽然我们无法更改生成完整响应所需的时间,但我们希望提供近乎实时的感知响应时间。我们首先研究了服务器发送事件,因为它是 OpenAI 选择的协议,我们的一些团队成员在 Python 中也有使用它的经验。但是,我们最终决定不这样做,原因有两个:

  • 我们从从事 Azure 服务的同事那里了解到,他们已经看到有关 SSE 被代理阻止的报告。

  • 我们想添加一项功能,允许用户打断 AI 模型的进一步响应。当从 AI 模型生成极长的响应时,这会派上用场。SSE 是一种单向协议,在这里帮不上什么忙。

        初步调查使我们对需要实现的目标有了更深入的了解 - 客户端和服务器之间的通信通道具有四个关键特征:

  •  低延迟

  •  网络容错

  •  易于扩展

  • 双向(服务器和客户端都可以在需要时来回发送消息)

        SignalR 似乎几乎完全符合描述。它会自动检测并在各种 Web 标准协议和技术中选择最佳传输方法。默认情况下,WebSocket 用作传输方法,它优雅地回退到服务器发送的事件和长轮询技术,以满足客户端的能力。

        使用 SignalR 库,我们无需担心使用哪种传输协议,我们可以超越网络级别的问题,专注于实现核心业务逻辑,该逻辑以各种数据格式生成有用的响应。我们的团队可以在安全的假设下运作,即实时通信渠道已经建立并得到照顾。对我们来说,另一个方便的好处是 SignalR 是 ASP.NET Core 的一部分,我们不需要新的后端依赖项。

使用自适应卡片 + Markdown 描述和呈现 UI

        大多数消息都使用自适应卡片,因为可以有 Markdown。这有助于我们支持和控制如何以标准方式显示许多内容,例如代码块、链接、图像和样式文本。“自适应卡片是与平台无关的 UI 片段,以 JSON 编写,应用和服务可以公开交换。当交付到特定应用时,JSON 将转换为自动适应其周围环境的本机 UI。它有助于为所有主要平台和框架设计和集成轻量级 UI。(摘自 https://adaptivecards.io)。

图片

        使用自适应卡片的响应示例,其中包含显示自适应卡片的 JSON 的代码块。

        我们在后端使用标准的自适应卡片架构对象,以便各种组件可以控制与前端共享的内容。例如,在模型生成响应后,我们后端的组件可以轻松地在生成的文本上方添加图像。

申请流程

文本输入 -> 文本输出

用户可以使用关键字键入其输入消息。该页面使用 SignalR 通过 WebSocket 与名为 /ChatHub 的终结点进行通信。 /ChatHub 终结点接受一个请求,其中包含有关用户及其消息的元数据。 /ChatHub 发回带有响应的事件,供客户端处理或显示。

图片

        当您说“我可以为只吃橙色食物的挑剔的幼儿做些什么饭菜”时,事件通过 WebSocket 连接流式传输。第一行是来自客户端(前端)的请求。以下内容是来自服务的响应。它们包括要在聊天窗口中显示的每个项目的消息对象。我们将在本文后面详细介绍它们。

        该页面使用 FAST 在生成和接收消息时有效地更新页面 /ChatHub 。

语音输入 - >语音输出

        用户还可以使用语音提供输入,Microsoft Copilot 将读出响应。

        为了处理语音输入,我们使用与必应相同的语音识别服务,并且多年来一直在改进。一开始,在客户端和我们的后端之间,我们只是放置了语音识别服务,它充当反向代理。该服务将语音转换为文本,并将其作为提示发送到我们的后端。从后端返回响应后,语音识别服务将执行文本转语音,并将文本和音频发送到前端。

        通过这种简单的方法,我们很快意识到向最终用户显示文本存在延迟,因为代理正在等待响应文本转换为音频。因此,我们改用让对话管理服务异步调用语音服务,告诉它开始计算响应的音频,并为要播放的每个音频段返回确定生成的 URL。UI 使用这些 URL 从语音服务获取要播放的音频。在音频准备就绪之前,UI 可能需要稍等片刻,但至少最终用户会看到一些结果,而不必等待音频段准备就绪。

        此优化还有助于减少语音服务的负载,因为对语音服务的请求要短得多,因为它们不需要在向对话管理层发出请求的整个持续时间内持续。

        您可能已经注意到,输出是分段播放的。我们仍在尝试不同的方法将文本拆分为多个段,以最大程度地减少段之间的延迟。我们可能会决定按句子拆分,并且可能会在它们自己的段中放置前几个单词,以便我们可以尽快开始播放音频,而不是等待生成第一个完整的句子。

深入探讨:如何使用 SignalR?

我们为每个用户的消息使用一个连接

        对于具有使用 SignalR 或 WebSockets 经验的开发人员来说,这可能会让他们感到奇怪。对于游戏,应保持 WebSocket 连接处于打开状态,因为您希望最大程度地减少用户输入的延迟。然而,在我们的例子中,用户发送新消息可能需要几秒钟或几分钟,如果他们这样做的话。因此,保持连接打开是不值得的。无论如何,该模型都需要一段时间才能完全生成响应,这使得重用同一连接所节省的费用微不足道。

        除了这个考虑之外,还有其他原因使这个决定适合我们。客户端更容易管理,因为它们不需要保留对连接的引用,也不需要在发送新消息之前检查其状态以查看它是否已在处理连接。对聊天中的每条消息使用单独的连接,可以在测试时更轻松地调试我们的代码,因为您可以在 DevTools 中打开浏览器的“网络”选项卡,并在其自己的行😉中清楚地看到每个测试请求。当每个 WebSocket 连接的生存期非常短时,管理后端负载要简单得多。使用简单的负载均衡方案,每个 SignalR 连接都可以在后端使用不同的节点。这样可以防止某些节点提供过多长时间运行的连接,而其他节点则具有短运行连接的不平衡。

利用现有功能以必应本机方式显示消息

        当你在必应中查询天气或新闻时,必应有特殊的卡片来显示此类信息,并具有丰富的交互性。我们的团队利用了这些出色的现有功能。Microsoft Copilot 前端以自定义方式解释或处理某些消息,以必应用户熟悉的方式显示信息。

        在这些情况下,后端会生成一个查询供客户端使用。某些消息显示处理信息,例如模型的搜索 Web 决策、搜索查询、搜索结果以及生成的响应建议,您可以对模型说出这些建议。

响应流式处理的两种类型的操作

覆盖邮件

        如果您查看过网络流量,您会注意到我们有时会在每次更新中发送完整的响应。

图片

        在“update”事件的消息中发送整个响应的示例。

        你可能会问,为什么我们不能在每次更新中发送一些文本来附加?在模型输出到达用户之前,我们会对模型的输出进行大量内部处理,用户也无法看到所有输出。我们寻找特殊的信号来确定要返回的响应类型,很好地格式化响应,添加引文链接,重新排序引文链接,缓冲区,以便我们不会在链接创建完成之前显示链接,检查冒犯性,检查有害内容,计算要大声说出的文本等。用户看到的是模型流输出的修改版本,因此,我们不能总是像您在某些产品中看到的那样以仅追加的方式进行更新。

        目前,我们通常在流式处理响应时依赖覆盖消息,主要是为了利用这种方法为我们提供的简单实现。我们的模型会生成 Markdown,但尚未将 Markdown 流式传输到渲染中。相反,我们会发送整个 Markdown 响应,并在为每次修改消息重新呈现自适应卡片时将其转换为 HTML。如果我们确实流式传输文本而不是整个对象,那么理想情况下我们会做一些特殊的事情,比如将新的 Markdown 流式传输到一个组件,该组件可以在流中处理 Markdown 并将其添加到已经构建的 HTML 中。

        另一个优点是容错:如果未收到更新,或由于某种原因未处理更新,我们将在处理以后的更新或消息的最终版本时进行恢复。

高效更新发送到客户端的消息对象

        每条消息都有一个唯一的 messageId .每次更新都包括 messageId 要修改的消息。我们使用该 "w" 事件来有效地更新消息对象中的数据,例如自适应卡片中的内容。

        虽然熟悉的 JSON 修补程序格式提供了一些更新对象或列出 JSON 对象的功能,但它仅支持覆盖字符串,不支持对字符串进行简洁的修改,例如在字符串中附加或插入文本。我们的团队为JavaScript / TypeScript和.NET创建了一个名为object-basin的新库,以提供Microsoft Copilot所需的JSON对象的精确更新。object-basin 有助于将各种更新应用于流消息对象,并允许我们有效地修改已发送到前端的文本。大多数更新只是要附加的文本,但我们可以从后端服务控制更多内容。例如,我们可以在任何字符串中插入文本、删除文本、创建新对象或列表、修改列表等。其工作原理是将游标声明为 JSONPath(例如, $['a7dea828-4be2-4528-bd92-118e9c12dbc6'].adaptiveCards[0].body[0].text )并发送文本、对象或列表以写入该游标上的值。在流式传输这些更简洁的更新时,我们仍会重新处理整个 Markdown 和自适应卡片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许泽宇的技术分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值