Kitex Thrift Streaming 在 Prompt 平台的实践

本文根据2024年3月30日在北京举办的“云原生✖️AI时代的微服务架构与技术实践”CloudWeGo技术沙龙北京站活动字节跳动-Flow 研发工程师杜少丰的演讲《Kitex Thrift Streaming 在 AI 场景落地实践》整理而来。

概述

字节跳动 Prompt 平台旨在为用户提供全面的 Prompt 开发、调优、评测及应用等全生命周期功能。在这些功能中,打字机效果的流式输出大模型结果是一项至关重要的特性。 基于 SSE(Server-Sent Events)实现虽然可行,但需要额外编写 HTTP 服务,这增加了开发的复杂性。而轮询方式虽然简单,但用户体验并不理想,显得过于笨拙。 至于 gRPC,虽然性能出色,但可能引入兼容性问题,使得部署和维护变得复杂。因此,我们借助 Kitex 的 Thrift streaming 能力,成功实现了流式接口的落地,从而为用户提供了流畅、高效的打字机效果大模型结果输出体验。

业务背景

随着 AI 技术的不断发展,人们的生活正在发生深刻的变化。以字节旗下的 AI 产品豆包为例,其中的智能体给人们带来了许多新奇的体验。 其中,AI男友、AI女友等趣味智能机器人尤其受欢迎,它们不仅能够以风趣幽默的方式与用户互动,还能展现出温柔体贴的一面。

这一切都离不开一个与大模型紧密相连的概念——提示词(Prompt)。简单来说,Prompt 就是向预训练模型输入的文本,用以引导模型生成符合特定需求的文本输出。 形象地讲,Prompt 就像是为大模型打造一个专属的梦境,通过它,我们能够引导大模型在特定场景下给出更贴切、更有针对性的回答。

以AI女友为例,我们会通过精心设计的 Prompt 来告诉大模型,它的角色是一个温柔体贴的虚拟女友。同时,我们还会设定一些限制条件,比如要求它以温柔体贴的方式与用户交流,并具备倾听、理解、鼓励和建议等技能。 此外,我们还会详细描述它的工作流程,比如在问候时引导用户说出自己的名字,为用户起一个合适的昵称,然后与用户进行深入的沟通交流,并提供有益的建议。

通过这样的 Prompt,我们为大模型构建了一个完整的“梦境”,让它明白自己是一个AI女友,并清楚自己应该如何与用户互动。 当这个 Prompt 被激活后,我们与大模型进行问答时,它就会根据我们的提示给出相应的回复。比如,当我们向它问好时,它会引导我们说出自己的名字,并为我们取一个可爱的昵称,然后给予我们鼓励和宽慰。

从这个例子中可以看出,Prompt 在特定场景下对大模型的输出起着决定性的作用。更进一步地说,它还会影响到大模型在输出过程中 token 的消耗以及响应时间的快慢。因此,一个优秀的 Prompt 对于提升模型输出效果至关重要。

需求场景

字节跳动 Flow 团队正致力于构建一个全面而成熟的平台/方法,旨在帮助 Prompt 开发者设计、迭代、评测及优化其Prompt,从而增强 LLM(大型语言模型)的表现力。 在开发阶段,我们计划提供结构化生成和引导式生成的方式,以辅助用户编写出高效且精准的 Prompt,并进行相应的调试运行。

随着开发的深入,我们将进一步引入 COT(Chain of Thought)、Few shots 等自动调优技术,以及 APO(Auto Prompt Optimization)方法,帮助 Prompt 提高回答的正确率。 同时,我们还将提供 Prompt 扩缩写的能力,以优化大模型在 token 消耗方面的效率。

此外,为了全面评估 Prompt 的效果,我们将结合多样化的数据集对 Prompt 进行打分,并深入分析其性能瓶颈,以便进行针对性的改进。 最终,我们将提供一键部署的功能,使开发者能够轻松地将 Prompt 能力及其背后的大模型集成到他们的应用中。

flow_platform

当然,这些功能的实现都离不开对实时流式传输技术的支持。就像你体验过的GPT、豆包、百度AI搜索等AI能力一样,它们在用户提问后,都采用了打字机形式的回复方式, 让用户感受到数据在不断流入屏幕,从而提高了聊天的流畅性和响应速度。这种实时流式传输技术正是我们 Prompt 平台需要提供的最基础能力。 通过将数据分成多个数据流进行网络传输,我们可以有效减少网络延迟,提高性能,确保用户在与大型语言模型交互时获得更好的体验。

解决方案

为了实现流式输出功能,我们进行了深入的调研,综合考虑了多种方案:

  • 轮询
  • HTTP SSE
  • Kitex gRPC Streaming(protobuf)
  • Kitex Thrift Streaming

首先,轮询方案由于其呆板性,不符合我们的需求,因此被排除在外。其次,虽然基于 HTTP 的 SSE 是一种可行的方案,但考虑到我们对 RPC(远程过程调用)同样有严格的要求,因此也需要寻找更为合适的方案。 另外,我们发现 Protobuf 协议的流式处理支持并不能完全满足我们的需求,尤其是在 Thrift 接口方面。最后,我们注意到了 Kitex 对于 Thrift Streaming 的支持。 当时,Kitex Thrift Streaming 正处于开发阶段,我们果断决定成为其首批用户,并以此为基础构建整个 Prompt 平台的基本框架。

basic_framework

在架构设计上,我们首先对标 LangChain,建立 LLM 工程化服务。在此基础上,我们进一步构建 Prompt 服务,以提供最基本的 Prompt 管理及应用能力。 为了与前端进行交互,我们通过 API Gateway 提供 HTTP 接口。而在微服务之间的通信方面,我们采用了 Kitex 框架,以提供流式接口和非流式接口的支持,确保数据的高效传输和处理。

通过这一解决方案,我们成功实现了流式输出功能,为用户提供了更加流畅、高效的AI交互体验。同时,我们也为未来的扩展和优化打下了坚实的基础。

实践与踩坑

流式调用

流式调用的流程起始于用户发起提问。这一请求首先被发送至网关,网关随后与下游的 Prompt RPC 接口建立连接。Prompt RPC 接口进一步与 LLM 工程化服务建立通信, 该服务负责持续与模型交互,并获取模型的输出结果。这些结果通过流式的方式逐层向上传递,直至到达网关层,并最终实现流式上屏展示给用户。

在此过程中,我们在 Prompt 服务中编写了一个流式接口,用于处理流式调用。该接口首先通过调用下游接口建立与下游的连接,然后通过一个 for 循环不断地去接收下游吐给我们的这个流式的包的结果。 一旦接收到数据包,我们通过 send 方法将其向上层透传,直至遇到错误或流关闭为止,循环随之结束。

在实现过程中,我们体验到了 Kitex Thrift Streaming 的简洁性。然而,我们也遇到了一些问题。尤其是在错误处理方面,我们发现代码运行时无法获取预期结果,甚至导致 CPU 负载过高。

error_handle

进一步分析错误日志后,我们发现在单个请求中存在错误信息,特别是关于首包的 QPM(查询每秒)超限问题。按照我们的代码逻辑,遇到这类错误应该快速退出 for 循环,但实际情况并非如此。 于是,我们开始利用 Kitex 提供的排查手段进行问题定位。Kitex 提供了 RPCStart 和 RPCEnd 的埋点,以及更细粒度的包接收和发送事件埋点。 通过对这些埋点的分析,我们发现 Kitex 将整个请求识别为正常响应,并且在调用链路上有大量的数据包在发送。进一步查看单个包的打点信息,也显示被 Kitex 识别为正常响应。

经过初步判断,我们认为 Kitex 的流式处理中可能忽略了业务错误,导致错误未被正确识别。与 Kitex 团队沟通后,他们进行了相应的调整,例如在代码中增加了对 biz status error(业务状态错误)的识别。

基于这次错误处理的经验,我们进一步分析了流式调用中可能遇到的其他异常场景,如建联阶段的权限报错、首包阶段的 TPM/QPM 超限、中间包阶段的流超时以及内容审核错误等。 我们重点关注了 Kitex Thrift Streaming 在这些场景下的错误处理表现,如建联时是否能快速返回错误信息,以及在首包和中间包返回错误时是否能迅速停止流等待。经过与 Kitex 团队的共同调整和测试,最终这些场景下的错误处理均符合预期。

服务治理

在服务治理方面,我们特别关注超时和限流两个关键环节。

service_governance

首先,超时管理至关重要。由于我们的模块与大模型进行交互,这种交互可能涉及秒级甚至分钟级的响应时间。因此,在 HTTP 层和 RPC 层, 我们都对流处理设置了分钟级的超时限制。这样做可以避免因无法退出for循环而导致的服务阻塞,确保服务的稳定性和可用性。

在限流方面,虽然 Kitex 支持在创建流时进行限流,但对于 LLM 场景来说,我们的关注点不仅在于建立连接时的 QPM 限流,更在于大模型 token 消耗的限流。 大模型的推理过程中会产生大量的 token 消耗,如果不加以限制,可能会导致资源耗尽和服务崩溃。 因此,我们利用 Kitex 实现了建联的限流,同时借助自己的分布式组件来计算不同模型下的 token 消耗,并据此实现 token 级别的限流。这样做可以有效地控制资源使用,避免服务过载。

然而,我们也对 Kitex 抱有期待。我们希望未来 Kitex 能够提供包粒度上的自定义限流能力。这样,我们可以更灵活地定义限流规则,更精确地控制资源使用,从而进一步提升服务的稳定性和性能。

未来的期待

随着AI技术的不断发展和应用,我们对微服务框架在AI场景下的能力有着更高的期待。特别是在便捷性、AI场景下的能力以及传统框架能力的适配等方面,我们希望能够看到更多的创新和进步。

便捷性

首先,在便捷性方面,我们期待微服务框架能够支持更多的测试工具接入,尤其是针对流式接口的测试。目前,对于Kitex Thrift streaming 接口的测试还存在一定的局限性, 主要依赖于编写非流式接口进行包装调用。未来,我们希望能够通过泛化调用等方式,使得流式接口能够更方便地支持各种测试工具,提高开发效率。

AI场景下的能力

随着AI技术的蓬勃发展,越来越多的产品开始融入AI能力以优化用户体验和功能。在AI场景下,我们对微服务框架如 Kitex 提出了更高的期待,期望它能更好地支持AI组件的集成与编排,以及适配传统的框架能力。

  • 开箱即用的 AI 组件编排能力

    在当前的开发实践中,当需要集成AI能力时,开发人员通常需要自行处理复杂的逻辑,如调用 prompt、解析大模型输出、以及将结果转换为机器语言等。 这不仅增加了开发难度,也降低了开发效率。因此,我们期待 Kitex 框架能够提供开箱即用的AI组件编排能力。

    具体来说,我们期望框架能够预置一系列封装好的AI组件,如 prompt 组件、大模型组件、结果解析组件以及RPC调用组件等。 这些组件应该具备高度的可配置性和可扩展性,以便能够适应不同的业务需求。开发人员只需将业务逻辑传入这些组件,而无需关心组件内部的实现细节,从而能够更专注于业务逻辑的实现。

  • 灵活的 AI 组件编排能力

    除了提供预置的AI组件外,我们还期待 Kitex 框架能够支持灵活的AI组件编排能力。这意味着框架应该提供一种表达式语言或可视化工具,使得开发人员能够轻松地按照业务需求编排这些AI组件。 通过这种方式,开发人员可以定义组件之间的执行顺序、通信方式以及并行处理策略等,而无需深入了解组件之间的交互细节。这将大大提高AI应用的开发效率和可维护性。

  • 传统框架能力在 LLM 链路上适配

    在AI场景下,传统的框架能力如服务治理、元数据透传以及可观测性等仍然具有重要意义。因此,我们期待Kitex框架能够在这些方面做出适配和优化。

    首先,在服务治理方面,由于AI应用可能涉及长时间的推理过程,因此框架需要提供针对秒级甚至分钟级响应时间的超时和限流策略。同时,还需要考虑如何处理与AI组件相关的异常情况。

    其次,在元数据透传方面,我们期望框架能够支持在AI组件之间传递元数据,以便进行更精细化的监控和调试。这将有助于我们更好地理解AI应用的运行状况,并快速定位问题。

    最后,在可观测性方面,我们期待 Kitex 框架能够提供全面的日志、追踪和指标收集功能,以便对AI链路进行全方位的监控和分析。这将有助于我们及时发现潜在的性能瓶颈和优化点,从而提升AI应用的性能和稳定性。

综上所述,我们对 Kitex 框架在AI场景下的未来期待主要集中在开箱即用的AI组件编排能力灵活的AI组件编排能力以及传统框架能力在LLM链路上的适配等方面。我们相信随着技术的不断进步和团队的深入合作,这些期待将逐渐变为现实,为AI应用的开发带来更大的便利和效率。

实际上,我们团队已经与 Kitex 团队展开了深入的合作,共同探讨如何在微服务框架中更好地支持 AI 场景。我们相信,在不久的将来,我们将能够推出一个 MVP 版本的解决方案,为业务开发人员提供一个简单、无缝地与 AI 能力结合的框架。这将是一个激动人心的时刻,我们期待着这一天的到来。


最后修改 September 18, 2024 : Update typo in hertz docs (#1138) (12492e4)