从零开始学 Dify - 详细介绍 Dify 工具(Tool)系统核心架构设计

在 AI 应用开发平台中,工具(Tool)系统是连接大语言模型与外部世界的桥梁,它使 AI 能够执行各种实际任务,如搜索信息、生成图片、处理数据等。本文将深入解析 Dify 工具系统的核心架构设计,帮助理解其工作原理并进行扩展开发。

👆👆👆欢迎关注,一起进步👆👆👆

文件组织结构

  
core/tools/  
├── provider/builtin/  # 内置工具供应商  
│   └── {provider\_name}/  # 如 google  
│       ├── \_assets/  # 图标等资源  
│       ├── {provider\_name}.yaml  # 供应商配置  
│       ├── {provider\_name}.py  # 供应商代码  
│       └── tools/  # 工具集合  
│           ├── {tool\_name}.yaml  # 工具配置  
│           └── {tool\_name}.py  # 工具实现  
└── docs/  # 文档  

1、工具系统概述

Dify 工具系统是一个模块化、可扩展的框架,用于在 Agent 助手和工作流中实现各种功能。该系统的设计理念是实现前后端解耦,使开发者能够在不修改前端逻辑的情况下定义和展示自己的工具,从而实现 Dify 能力的水平扩展。(推荐阅读,了解 Dify 如何快速集成新模型👉从零开始学 Dify-详细介绍 Dify 模型运行时的核心架构

picture.image

目前,Dify 工具系统支持两种类型的工具:

  1. 内置工具(Built-in Tools) :在产品内部实现,硬编码用于 Agent 和工作流。
  2. API 工具(Api-Based Tools) :利用第三方 API 实现,无需编码即可集成,只需在前端提供 OpenAPI、Swagger 或 OpenAI-plugin 格式的接口定义。

2、核心架构设计

2.1 整体架构

Dify 工具系统采用了分层架构设计,主要包括管理层、提供者层和工具层三个层次。

picture.image

2.2 核心组件

2.2.1 工具管理器(ToolManager)

ToolManager 是工具系统的核心管理类,负责管理所有工具提供者和工具实例。它提供了获取工具提供者、工具实例和工具运行时的方法,是工具系统的入口点。

主要功能:

  • 加载和管理内置工具提供者
  • 获取特定工具提供者和工具实例
  • 为 Agent 和工作流创建工具运行时

2.2.2 工具提供者控制器(ToolProviderController)

ToolProviderController 是工具提供者的抽象基类,定义了工具提供者的接口。每个工具提供者都需要实现这个接口,以提供工具列表、获取特定工具、验证凭据等功能。

主要子类:

  • BuiltinToolProviderController :内置工具提供者控制器
  • ApiToolProviderController :API 工具提供者控制器

2.2.3 工具(Tool)

Tool 是工具的抽象基类,定义了工具的接口。每个工具都需要实现这个接口,以提供工具的调用、参数验证等功能。

主要子类:

  • BuiltinTool :内置工具
  • ApiTool :API 工具

2.3 数据结构

picture.image

3、工具接入方式

3.1 快速接入

快速接入适用于简单工具的开发,通过配置驱动的方式快速实现工具功能。以 Google 搜索工具为例,快速接入需要完成以下步骤:

3.1.1 准备工具供应商 YAML

  
identity:  
  author:Dify  

 
 name
 :google  
label:  
    en\_US:Google  
    zh\_Hans:Google  
description:  
    en\_US:Google  
    zh\_Hans:Google  
icon:icon.svg  
tags:  
    -search  

3.1.2 准备供应商凭据

  

 
 credentials
 \_for\_provider:  
  serpapi\_api\_key:  
    type:secret-input  
    required:true  
    label:  
      en\_US:SerpApiAPIkey  
      zh\_Hans:SerpApiAPIkey  
    placeholder:  
      en\_US:PleaseinputyourSerpApiAPIkey  
      zh\_Hans:请输入你的SerpApiAPIkey  
    help:  
      en\_US:GetyourSerpApiAPIkey
 
 from
 SerpApi  
      zh\_Hans:从SerpApi获取您的SerpApiAPIkey  
    url:https://serpapi.com/manage-api-key  

3.1.3 准备工具 YAML

  
identity:  
  
 
 name
 :google\_search  
author:Dify  
label:  
    en\_US:GoogleSearch  
    zh\_Hans:谷歌搜索  
description:  
human:  
    en\_US:AtoolforperformingaGoogleSERPsearchandextractingsnippetsandwebpages.  
    zh\_Hans:一个用于执行GoogleSERP搜索并提取片段和网页的工具。  
llm:AtoolforperformingaGoogleSERPsearchandextractingsnippetsandwebpages.  
parameters:  
-name:query  
    type:
 
 string
   
    required:true  
    label:  
      en\_US:Query
 
 string
   
      zh\_Hans:查询语句  
    human\_description:  
      en\_US:usedforsearching  
      zh\_Hans:用于搜索网页内容  
    llm\_description:keywordsforsearching  
    form:llm  
-name:result\_type  
    type:select  
    required:true  
    options:  
      -value:text  
        label:  
          en\_US:text  
          zh\_Hans:文本  
      -value:link  
        label:  
          en\_US:link  
          zh\_Hans:链接  
    default:link  
    label:  
      en\_US:Resulttype  
      zh\_Hans:结果类型  
    human\_description:  
      en\_US:usedforselectingtheresulttype,textorlink  
      zh\_Hans:用于选择结果类型,使用文本还是链接进行展示  
    form:form  

3.1.4 准备工具代码

  

 
 from
  core.tools.tool.builtin\_tool import BuiltinTool  

 
 from
  core.tools.entities.tool\_entities import ToolInvokeMessage  
from typing import Any, Dict, List, Union  
  
class GoogleSearchTool(BuiltinTool):  
    def \_invoke(self,   
                user\_id: str,  
               tool\_parameters: Dict[str, Any],   
        ) -> Union[ToolInvokeMessage, List[ToolInvokeMessage]]:  
        """  
            invoke tools  
        """  
        query = tool\_parameters['query']  
        result\_type = tool\_parameters['result\_type']  
        api\_key = self.runtime.
   
 credential
 s['serpapi\_api\_key']  
        result = SerpAPI(api\_key).run(query, result\_type=result\_type)  
  
        if result\_type == 'text':  
            return self.create\_text\_message(text=result)  
        return self.create\_link\_message(link=result)  

3.1.5 准备供应商代码

  

 
 from
  core.tools.provider.builtin\_tool\_provider import BuiltinToolProviderController  

 
 from
  core.tools.errors import ToolProvider
   
 Credential
 
   
 Validation
 
   
 Error
   

 
 from
  core.tools.provider.builtin.google.tools.google\_search import GoogleSearchTool  

 
 from
  typing import Any, Dict  
  
class GoogleProvider(BuiltinToolProviderController):  
    def \_validate\_
 
 credentials
 (self, 
 
 credentials
 : Dict[str, Any]) -> None:  
        try:  
            GoogleSearchTool().fork\_tool\_runtime(  
                meta={  
                    "
 
 credential
 s": 
   
 credentials
 ,  
                }  
            ).invoke(  
                user\_id='',  
                tool\_parameters={  
                    "query": "test",  
                    "result\_type": "link"  
                },  
            )  
        
 
 except
  Exception as e:  
            raise ToolProvider
   
 Credential
 
   
 Validation
 
   
 Error
 (str(e))  

3.2 高级接入

高级接入适用于复杂工具的开发,支持多种消息类型、变量池和工具链的构建。

3.2.1 多种消息类型

Dify 工具系统支持多种消息类型,包括文本、图片、链接、文件 BLOB 和 JSON。

picture.image

示例代码:

  
# 返回文本消息  
self.create\_text\_message(text="Hello, world!")  
  
# 返回图片消息  
self.create\_image\_message(image="https://example.com/image.png")  
  
# 返回链接消息  
self.create\_link\_message(link="https://example.com")  
  
# 返回文件 BLOB 消息  
self.create\_blob\_message(blob=binary\_data, meta={"mime\_type": "image/png"})  
  
# 返回 JSON 消息  
self.create\_json\_message(object={"key": "value"})  

3.2.2 变量池机制

变量池是工具系统中的一个重要组件,用于在工具之间共享数据。它允许一个工具生成的数据被另一个工具使用,从而构建复杂的工具链。

picture.image

示例代码(DallE3 生成图片并保存到变量池):

  
# 将图片保存到变量池  
self.create\_blob\_message(  
    blob=b64decode(image.b64\_json),   
    meta={"mime\_type": "image/png"},  
    save\_as=self.VARIABLE\_KEY.IMAGE.value  
)  

示例代码(Vectorizer.AI 从变量池获取图片):

  
# 从变量池获取图片  
image\_binary = self.get\_variable\_file(self.VARIABLE\_KEY.IMAGE)  
if not image\_binary:  
    return self.create\_text\_message("Image not found")  
  
# 处理图片  

   
 response
  = post(  
    "https://vectorizer.ai/api/v1/vectorize",  
    files={"image": image\_binary},  
    data={"mode": "test"},  
    auth=(api\_key\_name, api\_key\_value),   
    timeout=30  
)  

3.2.3 动态参数生成

工具可以根据当前变量池中的数据动态生成参数列表,使 LLM 能够根据实际情况选择合适的参数。

  
def get\_runtime\_parameters(self) -> List[ToolParameter]:  
    """  
    override the runtime parameters  
    """  
    return [  
        ToolParameter.get\_simple\_instance(  
            name="image\_id",  
            llm\_description="the image id that you want to vectorize",  
            type=ToolParameter.ToolParameterType.SELECT,  
            required=True,  
            options=[i.name for i in self.list\_default\_image\_variables()]  
        )  
    ]  

3.2.4 工具可用性控制

工具可以根据当前变量池中的数据决定自己是否可用,避免在不适合的情况下被调用。

  
def is\_tool\_
 
 available
 (self) -> bool:  
    # 只有当变量池中有图片时,工具才可用  
    return len(self.list\_default\_image\_variables()) > 0  

4、工具系统工作流程

picture.image

4.1 工具调用过程

  1. 用户发送请求到 Agent 或工作流
  2. Agent 或工作流通过 ToolManager 获取工具运行时
  3. ToolManager 获取工具提供者和工具实例
  4. ToolManager 创建工具运行时,包括加载凭据和变量池
  5. Agent 或工作流调用工具
  6. 工具执行逻辑,可能会保存结果到变量池
  7. 工具返回结果给 Agent 或工作流
  8. Agent 或工作流返回响应给用户

4.2 变量池使用过程

  1. 工具 A 执行逻辑,生成结果
  2. 工具 A 将结果保存到变量池
  3. 工具 B 从变量池获取工具 A 的结果
  4. 工具 B 使用工具 A 的结果执行自己的逻辑

5、总结

Dify 的工具架构设计充分考虑了灵活性和可扩展性,通过配置驱动开发、变量池机制、动态参数生成和工具可用性控制等技术,使开发者能够快速构建从简单到复杂的各类工具。特别是变量池机制,为构建复杂工具链提供了强大支持,使得多个工具可以协同工作,完成更复杂的任务。

参考资料

https://github.com/langgenius/dify (v0.6.3)

推荐阅读

👆👆👆欢迎关注,一起进步👆👆👆

欢迎留言讨论哈

🧐点赞、分享、推荐 ,一键三连,养成习惯👍

0
0
0
0
评论
未登录
暂无评论