Dify配置全局变量

529 阅读4分钟

背景

业务在使用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.Chroot to 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 the python_lib_path configuration in your config.yaml. Usually, it includes:
    • etc/ directory
    • python.so shared object, compiled and built by dify-sandbox
    • usr/lib directory
    • usr/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'),       
        }
    }

执行结果: 1.png

容器内目录结构如下:

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

2.jpg

参考资料