Dify中的工具分为内置工具(硬编码)和第三方工具(OpenAPI Swagger/ChatGPT Plugin)。工具可被Workflow(工作流)和Agent使用,当然Workflow也可被发布为工具,这样Workflow(工作流)中又可以使用Workflow(工具)。
一.Dify内置工具
下面以Google为例介绍。从前端看只要输入SerpApi API key即可,接下来重点分析后端实现。
源码位置:dify-0.6.9/api/core/tools/provider/builtin/google
1.准备工具供应商 yaml
源码位置:dify-0.6.9/api/core/tools/provider/builtin/google/google.yaml
identity: # 工具供应商的基本信息
author: Dify # 作者
name: google # 工具供应商的名称,名称是唯一的,不允许和其它供应商重名
label: # 标签用于前端展示
en_US: Google # 英文标签
zh_Hans: Google # 简体中文标签
pt_BR: Google # 葡萄牙语标签
description: # 描述用于前端展示
en_US: Google # 英文描述
zh_Hans: GoogleSearch # 简体中文描述
pt_BR: Google # 葡萄牙语描述
icon: icon.svg # 图标文件名,图标文件需要放在当前模块的_assets目录下
2.准备供应商凭据
源码位置:dify-0.6.9/api/core/tools/provider/builtin/google/google.yaml
Google使用了SerpApi提供的API,而SerpApi需要一个API Key才能使用,即该工具需要一个凭证才能使用,也是前端需要输入SerpApi API key的原因。
credentials_for_provider: # 凭据字段
serpapi_api_key: # 凭据字段的唯一标识
type: secret-input # 凭据字段的类型
required: true # 是否必填
label: # 标签用于前端展示
en_US: SerpApi API key # 英文标签
zh_Hans: SerpApi API key # 简体中文标签
pt_BR: SerpApi API key # 葡萄牙语标签
placeholder: # 提示用于前端展示
en_US: Please input your SerpApi API key # 英文提示
zh_Hans: 请输入你的 SerpApi API key # 简体中文提示
pt_BR: Please input your SerpApi API key # 葡萄牙语提示
help: # 凭据字段帮助文本
en_US: Get your SerpApi API key from SerpApi # 英文帮助文本
zh_Hans: 从 SerpApi 获取您的 SerpApi API key # 简体中文帮助文本
pt_BR: Get your SerpApi API key from SerpApi # 葡萄牙语帮助文本
url: https://serpapi.com/manage-api-key # 凭据字段帮助链接
type:凭据字段类型,目前支持secret-input、text-input、select 三种类型,分别对应密码输入框、文本输入框、下拉框,如果为secret-input,则会在前端隐藏输入内容,并且后端会对输入内容进行加密。
3.准备工具 yaml
源码位置:dify-0.6.9\api\core\tools\provider\builtin\google\tools\google_search.yaml
一个供应商底下可以有多个工具,每个工具都需要一个 yaml 文件来描述,这个文件包含了工具的基本信息、参数、输出等。
identity: # 工具的基本信息
name: google_search # 工具的唯一名称
author: Dify # 工具的作者
label: # 工具的标签,用于前端展示
en_US: GoogleSearch # 英文标签
zh_Hans: 谷歌搜索 # 简体中文标签
pt_BR: GoogleSearch # 葡萄牙语标签
description: # 工具的描述
human: # 人类可读的描述
en_US: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
zh_Hans: 一个用于执行 Google SERP 搜索并提取片段和网页的工具。输入应该是一个搜索查询。
pt_BR: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
# 传递给 LLM 的介绍,为了使得LLM更好理解这个工具,我们建议在这里写上关于这个工具尽可能详细的信息,让 LLM 能够理解并使用这个工具
llm: A tool for performing a Google SERP search and extracting snippets and webpages.Input should be a search query.
parameters: # 参数列表
- name: query # 参数名称
type: string # 参数类型
required: true # 是否必填
label: # 参数标签
en_US: Query string # 英文标签
zh_Hans: 查询语句 # 简体中文标签
pt_BR: Query string # 葡萄牙语标签
human_description: # 参数描述,用于前端展示
en_US: used for searching # 英文描述
zh_Hans: 用于搜索网页内容 # 简体中文描述
pt_BR: used for searching # 葡萄牙语描述
# 传递给LLM的介绍,同上,为了使得LLM更好理解这个参数,我们建议在这里写上关于这个参数尽可能详细的信息,让LLM能够理解这个参数
llm_description: key words for searching
form: llm # 参数的表单类型,llm表示这个参数需要由Agent自行推理出来,前端将不会展示这个参数
- name: result_type # 参数名称
type: select # 参数类型
required: true # 是否必填
options: # 参数的选项
- value: text
label:
en_US: text
zh_Hans: 文本
pt_BR: texto
- value: link
label:
en_US: link
zh_Hans: 链接
pt_BR: link
default: link # 默认值为链接
label:
en_US: Result type
zh_Hans: 结果类型
pt_BR: Result type
human_description:
en_US: used for selecting the result type, text or link
zh_Hans: 用于选择结果类型,使用文本还是链接进行展示
pt_BR: used for selecting the result type, text or link
form: form # 参数的表单类型,form表示这个参数需要由用户在对话开始前在前端填写
identity
字段是必须的,它包含了工具的基本信息,包括名称、作者、标签、描述等parameters
参数列表name
参数名称,唯一,不允许和其他参数重名type
参数类型,目前支持string
、number
、boolean
、select
四种类型,分别对应字符串、数字、布尔值、下拉框required
是否必填在
llm
模式下,如果参数为必填,则会要求 Agent 必须要推理出这个参数在
form
模式下,如果参数为必填,则会要求用户在对话开始前在前端填写这个参数
options
参数选项在
llm
模式下,Dify 会将所有选项传递给 LLM,LLM 可以根据这些选项进行推理在
form
模式下,type
为select
时,前端会展示这些选项
default
默认值label
参数标签,用于前端展示human_description
用于前端展示的介绍,支持多语言llm_description
传递给 LLM 的介绍,为了使得 LLM 更好理解这个参数,我们建议在这里写上关于这个参数尽可能详细的信息,让 LLM 能够理解这个参数form
表单类型,目前支持llm
、form
两种类型,分别对应 Agent 自行推理和前端填写
4.准备工具代码
源码位置:dify-0.6.9\api\core\tools\provider\builtin\google\tools\google_search.py
class GoogleSearchTool(BuiltinTool):
def _invoke(self,
user_id: str, # 表示用户ID
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.credentials['serpapi_api_key'] # 表示API密钥
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) # 返回链接消息
5.准备供应商代码
源码位置:dify-0.6.9\api\core\tools\provider\builtin\google\google.py
class GoogleSearchTool(BuiltinTool):
def _invoke(self,
user_id: str, # 表示用户ID
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.credentials['serpapi_api_key'] # 表示API密钥
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) # 返回链接消息
二.工具接口中的消息返回
1.返回消息类型
源码位置:dify-0.6.9\api\core\tools\tool\tool.py
Dify支持文本
链接
图片
文件BLOB
等多种消息类型,可通过以下几个接口返回不同类型的消息给 LLM 和用户。
def create_image_message(self, image: str, save_as: str = '') -> ToolInvokeMessage:
"""
create an image message
:param image: the url of the image
:return: the image message
"""
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.IMAGE,
message=image,
save_as=save_as)
def create_file_var_message(self, file_var: FileVar) -> ToolInvokeMessage:
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.FILE_VAR,
message='',
meta={
'file_var': file_var
},
save_as='')
def create_link_message(self, link: str, save_as: str = '') -> ToolInvokeMessage:
"""
create a link message
:param link: the url of the link
:return: the link message
"""
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.LINK,
message=link,
save_as=save_as)
def create_text_message(self, text: str, save_as: str = '') -> ToolInvokeMessage:
"""
create a text message
:param text: the text
:return: the text message
"""
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.TEXT,
message=text,
save_as=save_as
)
def create_blob_message(self, blob: bytes, meta: dict = None, save_as: str = '') -> ToolInvokeMessage:
"""
create a blob message
:param blob: the blob
:return: the blob message
"""
return ToolInvokeMessage(type=ToolInvokeMessage.MessageType.BLOB,
message=blob, meta=meta,
save_as=save_as
)
如果要返回文件的原始数据,如图片、音频、视频、PPT、Word、Excel 等,可以使用文件 BLOB。
blob
文件的原始数据,bytes 类型。meta
文件的元数据,如果知道该文件的类型,最好传递一个mime_type
,否则Dify将使用octet/stream
作为默认类型。比如:
# b64decode函数的作用是将一个Base64编码的字符串解码为原始的字节数据
self.create_blob_message(blob=b64decode(image.b64_json), meta={ 'mime_type': 'image/png' }, save_as=self.VARIABLE_KEY.IMAGE.value)
self.create_blob_message(blob=response.content, meta={'mime_type': 'image/svg+xml'})
application/octet-stream
是一种通用的二进制数据的 MIME 类型。“Octet” 是一个八位字节,“stream” 指的是数据流。这种类型通常用于表示未知的、二进制的数据。当下载或上传文件时,如果服务器或客户端不能确定文件的具体类型,就可能会使用 application/octet-stream
。例如,当下载一个 .exe 文件或者 .zip 文件时,HTTP 响应的 Content-Type
头部字段可能就会被设置为 application/octet-stream
。
2.总结和爬虫
还有2个常用的文本总结工具和网络爬虫工具如下:
源码位置:dify-0.6.9\api\core\tools\tool\builtin_tool.py
def summary(self, user_id: str, content: str) -> str:
max_tokens = self.get_max_tokens()
if self.get_prompt_tokens(prompt_messages=[
UserPromptMessage(content=content)
]) < max_tokens * 0.6:
return content
def get_prompt_tokens(content: str) -> int:
return self.get_prompt_tokens(prompt_messages=[
SystemPromptMessage(content=_SUMMARY_PROMPT),
UserPromptMessage(content=content)
])
def summarize(content: str) -> str:
summary = self.invoke_model(user_id=user_id, prompt_messages=[
SystemPromptMessage(content=_SUMMARY_PROMPT),
UserPromptMessage(content=content)
], stop=[])
return summary.message.content
lines = content.split('\n')
new_lines = []
# split long line into multiple lines
for i in range(len(lines)):
line = lines[i]
if not line.strip():
continue
if len(line) < max_tokens * 0.5:
new_lines.append(line)
elif get_prompt_tokens(line) > max_tokens * 0.7:
while get_prompt_tokens(line) > max_tokens * 0.7:
new_lines.append(line[:int(max_tokens * 0.5)])
line = line[int(max_tokens * 0.5):]
new_lines.append(line)
else:
new_lines.append(line)
# merge lines into messages with max tokens
messages: list[str] = []
for i in new_lines:
if len(messages) == 0:
messages.append(i)
else:
if len(messages[-1]) + len(i) < max_tokens * 0.5:
messages[-1] += i
if get_prompt_tokens(messages[-1] + i) > max_tokens * 0.7:
messages.append(i)
else:
messages[-1] += i
summaries = []
for i in range(len(messages)):
message = messages[i]
summary = summarize(message)
summaries.append(summary)
result = '\n'.join(summaries)
if self.get_prompt_tokens(prompt_messages=[
UserPromptMessage(content=result)
]) > max_tokens * 0.7:
return self.summary(user_id=user_id, content=result)
return result
def get_url(self, url: str, user_agent: str = None) -> str:
"""
get url
"""
return get_url(url, user_agent=user_agent)
3.变量池
简单理解变量池用于存储工具运行过程中产生的变量、文件等,这些变量可以在工具运行过程中被其它工具使用。以DallE3
和Vectorizer.AI
为例,介绍如何使用变量池。
DallE3
是一个图片生成工具,它可以根据文本生成图片,将让DallE3
生成一个咖啡厅的 Logo。Vectorizer.AI
是一个矢量图转换工具,它可以将图片转换为矢量图,将DallE3
生成的PNG图标转换为矢量图,从而可真正被设计师使用。
# DallE 消息返回
self.create_blob_message(blob=b64decode(image.b64_json), meta={ 'mime_type': 'image/png' }, save_as=self.VARIABLE_KEY.IMAGE.value)
# 从变量池中获取到之前 DallE 生成的图片
image_binary = self.get_variable_file(self.VARIABLE_KEY.IMAGE)
三.Dify第三方工具
创建自定义工具,目前支持 OpenAPI Swagger 和 ChatGPT Plugin 规范。可将 OpenAPI schema 内容直接粘贴或从 URL 内导入。工具目前支持两种鉴权方式:无鉴权 和 API Key。
1.天气(JSON)
{
"openapi": "3.1.0",
"info": {
"title": "Get weather data",
"description": "Retrieves current weather data for a location.",
"version": "v1.0.0"
},
"servers": [
{
"url": "https://weather.example.com"
}
],
"paths": {
"/location": {
"get": {
"description": "Get temperature for a specific location",
"operationId": "GetCurrentWeather",
"parameters": [
{
"name": "location",
"in": "query",
"description": "The city and state to retrieve the weather for",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": false
}
}
},
"components": {
"schemas": {}
}
}
2.宠物商店(YAML)
# Taken from https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: https://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
maximum: 100
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
maxItems: 100
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
3.空模板(JSON)
{
"openapi": "3.1.0",
"info": {
"title": "Untitled",
"description": "Your OpenAPI specification",
"version": "v1.0.0"
},
"servers": [
{
"url": ""
}
],
"paths": {},
"components": {
"schemas": {}
}
}
四.Cloudflare Workers
一个函数调用工具可以部署到Cloudflare Workers,并使用OpenAPI模式。其中,Cloudflare Workers是Cloudflare提供的一种在边缘网络运行JavaScript函数的服务。简单理解这是一个用于为dify应用创建工具的Cloudflare Worker。
# 克隆代码
git clone https://github.com/crazywoola/dify-tools-worker
# 开发模式
cp .wrangler.toml.example .wrangler.toml
npm install
npm run dev
# You will get a url like this: http://localhost:8787
# 部署模式
npm run deploy
# You will get a url like this: https://difytoolsworker.yourname.workers.dev
填写URL从URL中导入,如下所示:
参考文献
[1] Tools:https://github.com/langgenius/dify/blob/main/api/core/tools/README_CN.md
[2] 快速接入Tool:https://github.com/langgenius/dify/blob/main/api/core/tools/docs/zh_Hans/tool_scale_out.md
[3] 高级接入Tool:https://github.com/langgenius/dify/blob/main/api/core/tools/docs/zh_Hans/advanced_scale_out.md
[4] OpenAPI Specification:https://swagger.io/specification/
[5] https://github.com/crazywoola/dify-tools-worker