亚马逊AWS官方博客

集成 Dify 和 AWS Service 实现更具灵活性的翻译工作流

什么是 Dify

Dify 是一个开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力,轻松构建和运营生成式 AI 原生应用。它为开发者提供了一套简单易用的低代码平台,其中的 AI workflow 编排能力非常容易帮助开发者构建复杂的工作流,它可以支持开发者通过界面执行/测试工作流,还可以单点直接工作流中的节点,并可以可视化的查看每个步骤的输入输出。除此以外 Dify 还提供了现成的机制帮助开发者将一个工作流发布成一个 API。

目前 Dify 已经形成了比较成熟的社区和生态,集成了绝大部分的大语言模型厂商,并且构建了一些内置的工具,并基于这些基础设施发布了不少的工作流供用户参考。

基于目前 Dify 提供的现有能力,已经能够对不少的业务场景提供帮助,但对于一些特定的诉求,还需要借助它的扩展机制,本文利用翻译场景举例详细说明。

翻译场景复杂性分析

翻译场景其实是一个从简单到复杂各个级别都存在的场景,比较简单的翻译可能一句简单的 Prompt 就能处理,但对于一些复杂的、效果要求比较高的翻译场景,可能需要一些复杂的 LLM 编排,比如吴恩达开源的 Translation Agent 工作。

从效果层面看,有些翻译要求比较高的意译水平,比如广告词的翻译,需要理解原文的深层含义,而非逐字翻译。之前在类似场景的实践中,采用了多轮调用 COT 的技巧,还需要不断反思修正,来得到最优的答案。这种场景往往要求灵活的 LLM 编排能力。这种场景是 Dify 所擅长的。

同时也有另外一些翻译场景,要求非常高的场景化和专业化,比如游戏论坛的评论翻译,需要通过 Prompt 给出目标受众期待的语气和翻译风格,同时还需要专词映射机制,来支持一些专业的游戏词汇(角色/道具/活动)或者黑话。

《基于 AWS 服务实现具备专词映射能力的大语言模型翻译》一文中介绍了专词翻译的方案,其中借助分词器进行专词提取和 KV 数据库存贮映射关系,方案中包含的 DynamoDB & Glue 服务,其服务能力是目前 Dify 所不具备的,单纯依靠 Dify 无法支持这种翻译诉求。

但这个方案的问题在于它是基于代码的实现,并没有提供友好的界面来调整 Prompt,对于复杂的 LLM 编排仅仅只能通过修改代码实现,没有足够的灵活性去应对各种各样的具体场景,也缺乏通用能力的支持,比如想要实现 stream response 则比较麻烦,而 Dify 的 API 发布能力则可以很轻松的弥补这一点,同时还可以利用 Dify API 监控等一系列通用能力。

为了结合两者的优势,本文尝试了对两者进行集成实践。

Dify 与外部工具的集成方式

Dify 的社区版文档中,目前主要提供了 2 种集成方式:

  • HTTP 节点:允许通过 Restful API 与外部接口进行交互。
  • 自定义工具:通过自定义工具添加一种新的节点类型,可以编排在工作流中。

AWS 的能力从原则上可以与 Dify 通过这两种方式进行集成,但依然存在一些问题:

  1. HTTP 方式存在鉴权的问题,鉴权步骤比较麻烦,且需要用到 AK/SK,这可能受到安全方面的限制。
  2. AWS 的一些能力,并不是直接可以访问的 SAAS API 服务,是需要预先进行私有化部署的,如果一直没人使用,或者使用过少,可能存在闲置率率过高的问题。

对于第一个问题,可以通过自定义工具来对接 AWS 的能力,自定义工具本质上是运行在 Dify docker 运行的实例中的,无需 AK/SK 的配置,直接通过实例上 AWS IAM Role 来获得执行权限。对于第二个问题,《基于 AWS 服务实现具备专词映射能力的大语言模型翻译》一文中,在设计方案的时候,主要基于 serverless 的服务来进行搭建,大大降低了空置的问题,其中 Lambda 的接口设计时,也提供了多种接口,除了直接翻译,还可以支持获取专词映射和切词结果。

具体集成过程

  1. 部署 Dify

采用社区版 – Docker Compose 方式进行部署,具体参见 Dify 官方文档

  1. 编辑自定义工具

需要参考 Dify 文档定义工具,一个工具一般对应两个文件(py,yaml),其中 python 文件为对接 AWS 服务的连接器,一般利用 boto3 来访问 AWS 服务,Dify 的 Docker 环境中已经集成了 boto3 的依赖。具体实现可以参考下面代码:

import boto3
import json

from typing import Any, Optional, Union, List
from core.tools.entities.tool_entities import ToolInvokeMessage
from core.tools.tool.builtin_tool import BuiltinTool


class LambdaTranslateUtilsTool(BuiltinTool):
    lambda_client: Any = None

    def _invoke_lambda(self, text_content, src_lang, dest_lang, model_id, request_type, lambda_name):
        msg = { 
            "src_content":text_content, 
            "src_lang": src_lang, 
            "dest_lang":dest_lang, 
            "request_type" : request_type, 
            "model_id" : model_id
        }

        invoke_response = self.lambda_client.invoke(FunctionName=lambda_name,
                                               InvocationType='RequestResponse',
                                               Payload=json.dumps(msg))
        response_body = invoke_response['Payload']

        response_str = response_body.read().decode("unicode_escape")

        return response_str

    def _invoke(self, 
                user_id: str, 
               tool_parameters: dict[str, Any], 
        ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
        """
            invoke tools
        """
        line = 0
        try:
            if not self.lambda_client:
                aws_region = tool_parameters.get('aws_region', None)
                if aws_region:
                    self.lambda_client = boto3.client("lambda", region_name=aws_region)
                else:
                    self.lambda_client = boto3.client("lambda")

            line = 1
            text_content = tool_parameters.get('text_content', '')
            if not text_content:
                return self.create_text_message('Please input text_content')
            
            line = 2
            src_lang = tool_parameters.get('src_lang', '')
            if not src_lang:
                return self.create_text_message('Please input src_lang')
            
            line = 3
            dest_lang = tool_parameters.get('dest_lang', '')
            if not dest_lang:
                return self.create_text_message('Please input dest_lang')
            
            line = 4
            lambda_name = tool_parameters.get('lambda_name', '')
            if not lambda_name:
                return self.create_text_message('Please input lambda_name')
            
            line = 5
            request_type = tool_parameters.get('request_type', '')
            if not request_type:
                return self.create_text_message('Please input request_type')
            
            line = 6
            model_id = tool_parameters.get('model_id', '')
            if not model_id:
                return self.create_text_message('Please input model_id')
            
            result = self._invoke_lambda(text_content, src_lang, dest_lang, model_id, request_type, lambda_name)

            return self.create_text_message(text=result)

        except Exception as e:
            return self.create_text_message(f'Exception {str(e)}, line : {line}')

yaml 文件为该工具的输入输出的界面定义文件,可以参考下面代码,注意 name 字段需要和真实文件名保持一致,否则加载时会出现问题。

identity:
  name: lambda_translate_utils
  author: AWS
  label:
    en_US: LambdaTranslateTool
    zh_Hans: Lambda翻译工具
  icon: icon.svg
description:
  human:
    en_US: A util tools for LLM translation, specfic Lambda Function deployment is needed on AWS. Deployment tutorial - https://amzn-chn.feishu.cn/docx/HxO8dK41UosPFvxAylScW6Xunah
    zh_Hans: 大语言模型翻译工具(专词映射获取),需要在AWS上部署对应Lambda,可参考https://amzn-chn.feishu.cn/docx/HxO8dK41UosPFvxAylScW6Xunah
  llm: A util tools for translation.
parameters:
  - name: text_content
    type: string
    required: true
    label:
      en_US: source content for translation
      zh_Hans: 待翻译原文
    human_description:
      en_US: source content for translation
      zh_Hans: 待翻译原文
    llm_description: source content for translation
    form: llm
  - name: src_lang
    type: string
    required: true
    label:
      en_US: language code for source text
      zh_Hans: 原文的语言代号
    human_description:
      en_US: language code for source text
      zh_Hans: 原文的语言代号
    llm_description: language code for source text
    form: form
  - name: dest_lang
    type: string
    required: true
    label:
      en_US: destination language code of translation
      zh_Hans: 翻译目标语言代号
    human_description:
      en_US: destination language code of translation
      zh_Hans: 翻译目标语言代号
    llm_description: destination language code of translation
    form: form
  - name: aws_region
    type: string
    required: false
    label:
      en_US: region of lambda
      zh_Hans: Lambda 所在的region
    human_description:
      en_US: region of lambda
      zh_Hans: Lambda 所在的region
    llm_description: region of lambda
    form: form
  - name: model_id
    type: string
    required: false
    default: anthropic.claude-3-sonnet-20240229-v1:0
    label:
      en_US: LLM model_id in bedrock
      zh_Hans: bedrock上的大语言模型model_id
    human_description:
      en_US: LLM model_id in bedrock
      zh_Hans: bedrock上的大语言模型model_id
    llm_description: LLM model_id in bedrock
    form: form
  - name: request_type
    type: select
    required: false
    label:
      en_US: request type
      zh_Hans: 请求类型
    human_description:
      en_US: request type
      zh_Hans: 请求类型
    default: term_mapping
    options:
      - value: term_mapping
        label:
          en_US: term_mapping
          zh_Hans: 专词映射
      - value: segment_only
        label:
          en_US: segment_only
          zh_Hans: 仅切词
      - value: translate
        label:
          en_US: translate
          zh_Hans: 翻译内容
    form: form
  - name: lambda_name
    type: string
    default: "translate_tool"
    required: true
    label:
      en_US: lambda name for term mapping retrieval
      zh_Hans: 专词召回映射的lambda名称
    human_description:
      en_US: lambda name for term mapping retrieval
      zh_Hans: 专词召回映射的lambda名称
    llm_description: lambda name for term mapping retrieval
    form: form

更多问题可以直接参考目前代码库 dify-aws-tool

  1. 构建自定义 Docker 镜像

参考下面伪代码:

# 按照下面步骤把工具对应的代码文件置入指定位置
cp -r ${tool_folder} ~/dify/api/core/tools/provider/builtin/

# 构建新镜像
cd ~/dify/api
sudo docker build -t dify-api:${tag} .

# 指定启动镜像
cd ../dify/docker/
vim docker-compose.yaml
# 修改image
# image: langgenius/dify-api:0.6.11 => image: langgenius/dify-api:${tag}

# 停止docker (也可以只更新修改过镜像的Container)
sudo docker compose down

# 启动docker
sudo docker compose up -d
  1. 添加自定义工具到工作流

检查自定义工具是否安装成功,如果安装成功,则可以在 dify 首页的 Tools Tab 中看到新增的工具集,如下图:

然后在工作流编排的时候,右键添加节点,可以在 Tools/Built-in 中看到添加的自定义工具,如下图:

  1. 调试自定义 Tool(当工具没有正确加载不可见时)

参考下面伪代码,查看服务的日志,根据日志来修改代码:

# 查看dify-api所在的container id
sudo docker ps -a

# 查看dify-api 这个container的日志
sudo docker logs <container_id_or_name>

总结

通过这些步骤,我们可以利用 Dify 的强大功能,构建一个高效、智能的翻译服务,满足各种复杂的翻译需求。这种集成不仅简化了开发过程,还能充分发挥了 Dify 在 LLMOps 方面的优势,为用户提供高质量的翻译体验。同时这种集成方式,也大大扩展了 Dify 的能力边界,让它具备了专词召回的能力。对于其他的一些复杂的生成式 AI 相关的场景,提供了借鉴价值。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。

相关博客

  1. <基于AWS服务实现具备专词映射能力的大语言模型翻译> https://aws.amazon.com/cn/blogs/china/implementing-llm-translation-with-word-mapping-capabilities-based-on-aws-services/

相关代码

  1. 代码库 https://github.com/aws-samples/dify-aws-tool/

本篇作者

李元博

亚马逊云科技 AI/ML GenAI 解决方案架构师,专注于 AI/ML 特别是GenAI场景的落地的端到端架构设计和业务优化。在互联网行业工作多年,对用户画像,精细化运营,推荐系统,大数据处理方面有丰富的实战经验。

韩医徽

亚马逊云科技资深解决方案架构师,曾负责亚马逊云科技合作伙伴生态系统的云计算方案架构咨询和设计,现负责游戏行业技术架构设计和支持,同时致力于亚马逊云科技云服务的应用和推广。