Dify是一个开源的LLM应用开发平台。提供从Agent构建到AI工作流编排、RAG检索、模型管理等能力,帮助开发者轻松构建和运营生成式AI原生应用。
它为开发者提供了一套简单易用的低代码平台,并支持开发者通过界面执行或测试工作流,可视化地查看每个步骤的输入输出。除此以外,Dify还提供了现成的机制帮助开发者将一个工作流发布成一个API。
Dify
https://dify.ai/zh
基于目前Dify提供的现有能力,已经能够对不少业务场景提供帮助,但对于一些特定的诉求,还需要借助它的扩展机制,本文利用翻译场景举例详细说明。
翻译场景复杂性分析
翻译场景其实是一个从简单到复杂各个级别都存在的场景,比较简单的翻译可能一句简单的Prompt就能处理,但对于一些复杂的、效果要求比较高的翻译场景,可能需要一些复杂的LLM编排,比如吴恩达开源的Translation Agent工作。
从效果层面看,有些翻译要求比较高的意译水平,比如广告词的翻译,需要理解原文的深层含义,而非逐字翻译。之前在类似场景的实践中,采用了多轮调用COT的技巧,还需要不断反思修正,来得到最优的答案。这种场景往往要求灵活的LLM编排能力。这种场景是Dify所擅长的。
同时也有另外一些翻译场景,要求非常高的场景化和专业化,比如游戏论坛的评论翻译,需要通过Prompt给出目标受众期待的语气和翻译风格,同时还需要专词映射机制,来支持一些专业的游戏词汇(角色、道具、活动)或者黑话。
《基于亚马逊云科技服务实现具备专词映射能力的大语言模型翻译》一文中介绍了专词翻译的方案,其中借助分词器进行专词提取和KV数据库存贮映射关系,方案中包含的Amazon DynamoDB和Amazon Glue服务,其服务能力是目前Dify所不具备的,单纯依靠Dify无法支持这种翻译诉求。
《基于亚马逊云科技服务实现具备专词映射能力的大语言模型翻译》
https://aws.amazon.com/cn/blogs/china/implementing-llm-translation-with-word-mapping-capabilities-based-on-aws-services/
但这个方案的问题在于它是基于代码的实现,并没有提供友好的界面来调整Prompt,对于复杂的LLM编排仅仅只能通过修改代码实现,没有足够的灵活性去应对各种各样的具体场景,也缺乏通用能力的支持,比如想要实现stream response则比较麻烦,而Dify的API发布能力则可以很轻松的弥补这一点,同时还可以利用Dify API监控等一系列通用能力。
为了结合两者的优势,本文尝试了对两者进行集成实践。
Dify与外部工具的集成方式
Dify的社区版文档中,目前主要提供了2种集成方式:
HTTP节点:允许通过Restful API与外部接口进行交互。
自定义工具:通过自定义工具添加一种新的节点类型,可以编排在工作流中。
亚马逊云科技的能力从原则上可以与Dify通过这两种方式进行集成,但需要解决以下两点:
HTTP方式存在鉴权的问题,鉴权步骤比较麻烦,且需要用到AK或SK,这可能受到安全方面的限制。
并不是直接可以访问SAAS API服务,需要预先进行私有化部署的,如果一直没人使用,或者使用过少,可能存在闲置率率过高的问题。
对于第一个问题,可以通过自定义工具来对接亚马逊云科技的能力,自定义工具本质上是运行在Dify Docker运行的实例中的,无需AK或SK的配置,直接通过实例上Amazon IAM Role来获得执行权限。对于第二个问题,《基于亚马逊云科技服务实现具备专词映射能力的大语言模型翻译》一文中,在设计方案的时候,主要基于Serverless的服务来进行搭建,大大降低了空置的问题,其中Amazon Lambda的接口设计时,也提供了多种接口,除了直接翻译,还可以支持获取专词映射和切词结果。
具体集成过程
1.部署Dify
采用社区版 – Docker Compose方式进行部署,具体参见Dify官方文档。
Dify官方文档
https://docs.dify.ai/v/zh-hans/getting-started/install-self-hosted/docker-compose
2.编辑自定义工具
需要参考Dify文档定义工具,一个工具一般对应两个文件(py、yaml),其中Python文件为对接亚马逊云科技服务的连接器,一般利用Boto3来访问亚马逊云科技服务,Dify的Docker环境中已经集成了Boto3的依赖。具体实现可以参考下面代码:
Dify文档
https://docs.dify.ai/v/zh-hans/guides/gong-ju/quick-tool-integration
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-Amazon-Tool。
Dify-Amazon-Tool
https://github.com/aws-samples/dify-aws-tool/
3.构建自定义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
左右滑动查看完整示意
4.添加自定义工具到工作流
检查自定义工具是否安装成功,如果安装成功,则可以在Dify首页的Tools Tab中看到新增的工具集。
然后在工作流编排的时候,右键添加节点,可以在Tools/Built-in中看到添加的自定义工具,如下图:
5.调试自定义Tool(当工具没有正确加载不可见时)
参考下面伪代码,查看服务的日志,根据日志来修改代码:
# 查看dify-api所在的container id
sudo docker ps -a
# 查看dify-api 这个container的日志
sudo docker logs <container_id_or_name>
左右滑动查看完整示意
总结
通过这些步骤,我们可以利用Dify的强大功能,构建一个高效、智能的翻译服务,满足各种复杂的翻译需求。
这种集成不仅简化了开发过程,还能充分发挥了Dify在LLMOps方面的优势,为用户提供高质量的翻译体验。
同时这种集成方式,也大大扩展了Dify的能力边界,让它具备了专词召回的能力。对于其他的一些复杂的生成式AI相关的场景,提供了借鉴价值。
相关代码
代码库
https://github.com/aws-samples/dify-aws-tool/
本篇作者
李元博
亚马逊云科技机器学习、生成式AI解决方案架构师,专注于人工智能和机器学习,特别是生成式AI场落地的端到端架构设计和业务优化。在互联网行业工作多年,对用户画像、精细化运营、推荐系统、大数据处理方面有丰富的实战经验。
韩医徽
亚马逊云科技资深解决方案架构师,曾负责亚马逊云科技合作伙伴生态系统的云计算方案架构咨询和设计,现负责游戏行业技术架构设计和支持,同时致力于亚马逊云科技云服务的应用和推广。
星标不迷路,开发更极速!
关注后记得星标「亚马逊云开发者」
听说,点完下面4个按钮
就不会碰到bug了!
点击阅读原文查看博客!获得更详细内容!