背景
业务在使用Dify开发AI应用,AI应用中会调用一些外部服务,就需要获取服务地址、认证等信息。如果在每个应用中配置,会比较多,不同环境也不好维护,因此需要全局变量配置,但Dify并不支持,这里另辟蹊径解决。
Dify变量
Dify有4种变量,即用户变量、系统变量、环境变量和会话变量,各类变量如下。
| 变量类型 | 前缀/标识 | 作用域与生命周期 | 主要特点与用途 | 常见示例 |
|---|---|---|---|---|
| 用户变量 | 自定义命名 | 单次请求有效 | 存储用户直接输入的原始数据,是工作流的起点 | 用户输入的查询文本、上传的文件列表 |
| 系统变量 | sys. | 全局只读 | 由系统自动生成,提供应用运行时的上下文信息 | sys.query:用户查询,sys.dialogue_count:对话轮数 |
| 环境变量 | 自定义命名 | 单个应用有效 | 存储敏感配置信息(如 API 密钥),避免硬编码,提升安全性和可移植性 | API 密钥、数据库连接字符串 |
| 会话变量 | 自定义命名 | Chatflow 多轮对话期间 | 在对话会话期内临时存储信息,是实现多轮对话记忆和状态管理的关键 | 用户语言偏好、已收集的用户信息 |
从表可以看出,Dify并没有全局变量,我们需要其他机制来扩展,Dify提供了代码执行,因此,可以考虑通过Python代码读取系统配置文件,并将其封装成工具,给工作流使用。在介绍此方法之前,必须先来了解下Dify的Sandbox。
Sandbox概述
Dify Sandbox是Dify平台中的关键组件,负责隔离执行工作流中的代码节点。当用户在Dify工作流中使用"代码执行"节点时,相关代码并非直接在主机环境中运行,而是由Sandbox接管处理。Dify Sandbox采用了双重安全隔离策略,兼顾了系统级隔离和文件系统隔离,作者在Introduction to DifySandbox有详细介绍:
系统级隔离
- 原理:利用Linux的Seccomp(安全计算模式)功能,拦截和控制各种系统调用。
- 实现:通过白名单机制,仅允许预先授权的系统调用执行。Dify的源码中docker/volumes/sandbox/conf/config.yaml文件包含allowed_syscalls参数,可控制开放约400个系统调用。
- 限制范围:包括文件读写、系统配置修改、网络访问等关键操作。
文件系统隔离
- 原理:使用chroot命令将代码执行环境限制在特定目录(如/tmp)内。
- 效果:沙箱中的代码只能看到和访问/tmp目录下的内容,无法感知或访问系统其他部分的文件。
- 实现方式:Dify-sandbox使用Go语言实现,在代码执行进程开始时调用chroot('/tmp')。
全局变量实现
配置文件读取
Dify的Sandbox机制,限制了我们只能读取到/tmp目录下的内容,在Dify部署时,可以将配置文件映射到该目录,这样就可以通过Python代码读取到。
编辑docker-compose.yaml文件,在sandbox模块的volumes配置项中增加映射。这里需要特别注意,Dify的Sandbox会将进程可见的根目录/从系统真实的根目录切换至沙箱内的/var/sandbox/sandbox-python目录,代码在执行时,只能访问到/var/sandbox/sandbox-python目录及其子目录下的内容,其他目录均无法访问。在Dify Sandbox的FAQ文档中,有下面这一句话:
Before running your Python code, it uses
syscall.Chrootto restrict the current process's root to the/var/sandbox/sandbox-python/ directory. This directory structure, visible to the Python process, determines all the Python modules/packages that can be imported, including modules based on C code.
- Root:
/var/sandbox/sandbox-python/is the root directory from the Python process perspective. Its subdirectories depend on thepython_lib_pathconfiguration in yourconfig.yaml. Usually, it includes:
etc/directorypython.soshared object, compiled and built bydify-sandboxusr/libdirectoryusr/local/
因此,为了能让Dify AI应用中的Python代码读到全局配置文件,需要将全局配置文件映射到/var/sandbox/sandbox-python目录下。比如将env.ini映射到Sandbox的根目录。
env.ini文件内容:
$ cat volumes/sandbox/env.ini
[datasource] jdbc-url = jdbc:mysql://127.0.0.1:3306/db_name
username = root
password = toor
docker-compose.yaml中sandbox模块映射配置:
volumes:
- ./volumes/sandbox/dependencies:/dependencies
- ./volumes/sandbox/conf:/conf
- ./volumes/sandbox/env.ini:/var/sandbox/sandbox-python/env.ini:ro
下面就可以使用Python代码读取:
import os
def read_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as file:
content = file.read()
return content
except Exception as e:
return str(e)
def main() -> dict:
return {
"result": {
'文件位置': os.path.abspath('/env.ini'),
'文件内容': read_file('/env.ini'),
}
}
执行结果:
容器内目录结构如下:
root@8deca62266c5:/# ls /var/sandbox/
sandbox-nodejs sandbox-python
root@8deca62266c5:/# ls /var/sandbox/sandbox-python/
env.ini etc python.so tmp usr
# cat /var/sandbox/sandbox-python/env.ini
[datasource]
jdbc-url = jdbc:mysql://127.0.0.1:3306/db_name
username = root
password = toor
封装成变量
Python代码:
import os
import configparser
def main() -> dict:
config = configparser.ConfigParser()
config.read('env.ini', encoding='utf-8')
result_dict = {}
for section in config.sections():
for key in config[section]:
safe_section = section.replace('.', '_').replace('-', '_')
safe_key = key.replace('.', '_').replace('-', '_')
dict_key = f"{safe_section}_{safe_key}"
result_dict[dict_key] = config[section][key]
return result_dict