微信扫码
添加专属顾问
我要投稿
探索Dify插件机制,解锁系统灵活性和可扩展性的新高度。核心内容:1. Dify插件机制的核心价值与系统变革2. 插件化带来的用户体验与技术实现细节3. 针对用户需求,实现Dify工具与模型的独立安装与按需选择
Dify v1.0.0 版本的发布,标志着插件机制正式成为系统核心组成部分。作为该机制的主要设计者及 Dify 后端工程师 Yeuoly,我将带大家了解插件在 Dify 系统中的作用与价值。插件机制的核心在于将 Dify 中可横向扩展的模块解耦并独立为运行时,这带来了几个显著的变化:
插件 Endpoint :新增了 Extension 插件 Endpoint,这使得更多实际场景能够在 Dify 内部闭环落地。
虽然这些变化在用户体验上是显而易见的,但对于大多数用户来说,插件机制的技术实现仍然是一个相对模糊的黑盒。本文将带你逐步深入了解插件机制的设计理念、用户价值和技术实现细节。
在设计插件机制之前,我们主要面临以下几个问题:
定制化模块固定性:例如,Dify 的 PDF 解析器效果不理想,且 RAG 等定制化模块无法灵活调整。
针对这些问题,我们决定实现一套统一的框架,拆分 Dify 的工具和模型,使它们可以独立安装并按需选择。同时,文档解析器、OCR 等 RAG 相关的功能也实现插件化,从而满足不同的场景需求。对于 Dify 内无法闭环的场景,插件机制通过开放接口实现与外部系统的对接,比如支持对接 IM 平台的 Outgoing Webhook。
这些产品需求看起来比较简单,但在工程实现过程中却极具挑战。在初期的设计阶段不到一周,我们就遇到了以下一系列问题:
插件长期运行:例如,用户需要长期运行一个 HTTP 服务器来监听 IM 平台的 Webhook 事件,Dify 插件是否应该支持?
在考虑如何实现插件机制之前,我们首先思考了如何优化调试体验,尤其是针对开发者而言。理想的调试体验应该具备以下两个要求:
本地调试:插件的代码需要在本地运行,以便能够通过断点等方式调试,查错过程更加便捷。
我们参考了老牌调试器如 GDB 的设计,采用了调试器和运行时分离的方式:调试器等待运行时主动连接。一旦连接建立,本地插件就可以与 Dify 建立长连接,Dify 会将其视为已安装插件,标记为调试模式。用户请求通过长连接转发给本地插件,插件的返回结果也通过长连接发送给 Dify,从而实现了流畅的调试体验。
然而,这种设计面临一个问题:长连接是带状态的,而 Dify 目前的服务是无状态的。在 Kubernetes 集群中,负载均衡会将请求路由到不同的 Dify Pod 上。例如,Plugin 1 连接到 Dify 1,Plugin 2 连接到 Dify 2。当用户请求调用 Plugin 1 时,请求可能会被负载均衡路由到 Dify 2,导致用户无法访问 Plugin 1。因此,我们需要在后续实现流量转发机制来解决这一问题。
我们调研了许多现有的即时通讯(IM)工具和办公协作软件,并综合当前需求,明确了要解决的关键问题:如何让 Dify 接收来自这些平台的 Webhook 请求,并让插件能够处理这些 HTTP 请求。例如,使用 Dify 的 App 来处理用户消息。
为了解决这个问题,我们设计了生成随机 URL 的机制,并将该 URL 与 Discord 等 IM 平台集成。这种方式避免了在每个插件中长期运行一个服务器的问题,因为 Dify 承担了 HTTP 请求转发的责任。Dify 通过生成的 URL 来接收来自这些平台的 Webhook 请求,而插件则处理转发的请求。
解决了如何接收消息的问题后,紧接着是如何处理消息。设想我正在开发一个 Discord Bot,想让 Dify 的某个 Chatflow 来回复用户消息,代码可以是这样的:
class Webhook:
def _invoke(self, r: Request) -> Response:
message = r.json()['message']
respose = invoke_app(app_id, message)
return Response(response)
在这个插件中,我需要能够调用 Dify 的 App 来处理请求,这样就可以实现 Bot 功能。此时,我们引出了 Dify v1.0.0 中的一个重要概念:反向调用。
反向调用是 Dify 插件机制中的重要概念,允许插件调用 Dify 内部的服务。例如,插件可以调用已经鉴权的模型、工具,或 Dify 的 App。在以下几个场景中,反向调用发挥了重要作用:
Agent 插件化:如果支持反向调用工具,那么就可以实现 Agent 的插件化,自动接收参数执行操作并返回结果,并根据需求自定义 Agent 策略。
首先,插件运行时的设计是我们需要解决的首要问题。最终,插件运行时到底是 Docker 容器、进程、虚拟机,还是 Serverless 运行时?在对 Dify 用户群体进行评估后,我们决定实现四种完全不同的运行时,分别为:
本地部署版本的实现注重“一键部署”和开箱即用。用户可以通过一句 `docker compose up -d` 命令运行整个 Dify,并且安装插件时无需额外配置。在这种环境下,插件的运行时设计为一个子进程,由父进程管理所有生命周期控制,包括插件依赖包的安装,两者通过标准输入输出管道进行通信。在理论上,这种设计是可行的。
考虑到大规模用户的需求,SaaS 版本的设计采用类似 Serverless 的架构,能够根据使用量自动弹性伸缩,从而确保高并发、高资源利用率和高可用性。最终,我们选择了 AWS Lambda 作为解决方案,AWS 作为 Dify 的合作伙伴,已经支持现有 SaaS 业务,且 Dify 与 Lambda 使用网络进行通信,提供了一个非常合适的架构。
考虑到企业客户在解决方案选择上的多样性,因此我们设计了一种可控、可信的运行时。这个版本可以为企业提供高度的可控性和隐私保护,支持企业内部私有部署。
对于调试模式,Dify 支持通过 TCP 网络长连接调试插件,并解决了有状态问题。我们采用类似 etcd 的设计,通过 Redis HashMap 管理插件的连接状态,确保插件的请求能够正确转发到相应的 Pod。为了管理 IP,我们设置了如下两项机制:
集群需要有一个 Master 节点,定期检查 Pod 的存活情况,清理掉已退出的节点状态,确保集群的稳定性。
插件机制的安全性主要依赖于密码学中的签名,而不是类似 Sandbox 的强制性安全限制。Sandbox 的限制非常严格,导致很多依赖包无法安装和使用,因为这些包可能会涉及未经许可的系统操作,严重影响插件使用体验。相较之下,插件是已经编写完成的代码包,在安装插件之前,人工审核能够为插件包打上“安全”标签,极大地降低风险。我们采用了基于公钥密码学的签名策略:如果插件通过审核,我们将使用私钥对插件进行签名,标记为“已认证”,如果未通过审查,用户将看到“不安全”的提示。所有未签名的插件无法安装,除非用户手动更改设置。
所有插件均须明确声明其使用的权限、数据存储等隐私政策,尤其是涉及敏感数据的插件。对于简单的权限声明,插件开发者必须显式声明插件的功能权限,未声明的权限 Dify 会直接拒绝使用。而对于复杂的隐私政策,我们要求开发者提供详细的隐私策略,并在 Manifest 中引用,所有上架 Marketplace 的插件都需要经过隐私策略审核。
本文简要介绍了 Dify 插件机制的设计与技术实现。通过插件化,Dify 提供了更高的灵活性和可定制性,支持多种使用场景,同时也为开发者提供了更好的调试和开发体验。我们已开源相关代码,欢迎大家参与其中并贡献新的插件需求:
https://github.com/langgenius/dify
期待与社区共同进步,探索更多创新应用。
在 GitHub 上给我们点亮:支持我们的开源项目 https://github.com/langgenius/dify
扫码添加 Dify 小助手:加入我们的用户群,和大家一起分享经验、交流问题,探索更多可能!
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-12-24
2024-04-25
2024-07-16
2024-04-24
2024-07-20
2024-05-08
2024-05-09
2024-06-21
2024-05-07
2024-08-06