Github 上 Star 数最多的大模型应用基础服务 Dify 深度解读(一)

背景介绍

接触过大模型应用开发的研发同学应该都或多或少地听过 Dify 这个大模型应用基础服务,这个项目自从 2023 年上线以来,截止目前(2024-6)已经获得了 35k 多的 star,是目前大模型应用基础服务中最热门的项目之一。这篇文章对 Dify 中核心的基础模块 RAG 服务进行深入解读,后续可能会更新其他模块的内容。

Dify 简介

Dify 是一个 LLMOps 服务, 涵盖了大语言模型(如GPT系列)开发、部署、维护和优化的一整套实践和流程。可以大幅简化大模型应用的开发。

基于 Dify 可以在不需要太多开发的情况下,快速搭建一个大模型应用。应用中可以调用 Dify 中内置的大量基础能力,比如知识库检索 RAG,大模型调用。通过可插拔式的组合构建大模型应用。一个典型的应用如下所示:
请添加图片描述

上面的场景中使用分类场景,RAG 服务以及大模型调用的基础模块,组合生成一个大模型应用。

RAG 核心流程

RAG 服务的基础流程在之前的 搭建离线私有大模型知识库 文章中已经介绍过了。RAG 服务的开源框架 有道 QAnything 和 Ragflow 也都解读过基础的 RAG 流程了,这部分就不详细展开了。一般情况下,RAG 服务会包含如下所示的功能模块:

  • 文件加载的支持;
  • 文件的预处理策略;
  • 文件检索的支持;
  • 检索结果的重排;
  • 大模型的处理;

因为 RAG 服务只是 Dify 中的一个基础模块,官方没有过多强调 RAG 服务的独特设计,但是依旧可以看到一个独特点:

  1. 支持 Q&A 模式,与上述普通的「Q to P」(问题匹配文本段落)匹配模式不同,它是采用「Q to Q」(问题匹配问题)匹配工作;
  2. 丰富的召回模式,支持 N 选 1 召回多路召回

下面的部分会对独特之处进行详细展开。

核心模块解读

之前介绍过来自中科院的 RAG 服务 GoMate 采取的是模块化设计,方便进行上层应用的组合。从目前的实现来看,Dify 的 RAG 设计也是采用模块化设计,RAG 的代码实现都在 api/core/rag 中,从代码结构上也很容易理解各个模块的作用:

请添加图片描述

深入来看代码的实现质量也比较高,对 RAG 的模块化设计感兴趣的可以深入了解下实现细节。

文件加载

Dify 的文件加载都是在 api/core/rag/extractor/extract_processor.py 中实现的,主要的文件解析是基于 unstructured 实现,另外基于其他第三方库实现了特定格式文件的处理

比如对于 pdf 文件,会基于 pypdfium2 进行解析,html 是基于 BeautifulSoup 进行解析,这部分代码实现都比较简单,就不展开介绍了。

文件预处理

加载的模型中的内容可能会存在一些问题,比如多余的无用字符,编码错误或其他的一些问题,因此需要对文件解析的内容进行必要的清理,这部分代码实现在 api/core/rag/cleaner 中。实际的清理都是基于 unstructured cleaning 实现的,Dify 主要就是将不同的清理策略封装为同样的接口,方便应用层自由选择。这部分实现也比较简单,感兴趣可以自行了解下。

Q&A 模式

Q&A 分段模式功能,与上述普通的「Q to P」(问题匹配文本段落)匹配模式不同,它是采用「Q to Q」(问题匹配问题)匹配工作,在文档经过分段后,经过总结为每一个分段生成 Q&A 匹配对,当用户提问时,系统会找出与之最相似的问题,然后返回对应的分段作为答案,实际的流程如下所示:

请添加图片描述

从上面的流程可以看到,Q&A 模式下会根据原始文档生成问答对,实现实现是在 api/core/llm_generator/llm_generator.py 中:

# 构造 promptGENERATOR_QA_PROMPT = ('<Task> The user will send a long text. Generate a Question and Answer pairs only using the knowledge in the long text. Please think step by step.''Step 1: Understand and summarize the main content of this text.\n''Step 2: What key information or concepts are mentioned in this text?\n''Step 3: Decompose or combine multiple pieces of information and concepts.\n''Step 4: Generate questions and answers based on these key information and concepts.\n''<Constraints> The questions should be clear and detailed, and the answers should be detailed and complete. ''You must answer in {language}, in a style that is clear and detailed in {language}. No language other than {language} should be used. \n''<Format> Use the following format: Q1:\nA1:\nQ2:\nA2:...\n''<QA Pairs>'
)def generate_qa_document(cls, tenant_id: str, query, document_language: str):prompt = GENERATOR_QA_PROMPT.format(language=document_language)model_manager = ModelManager()model_instance = model_manager.get_default_model_instance(tenant_id=tenant_id,model_type=ModelType.LLM,)# 拼接出完整的调用 promptprompt_messages = [SystemPromptMessage(content=prompt),UserPromptMessage(content=query)]# 调用大模型直接生成问答对response = model_instance.invoke_llm(prompt_messages=prompt_messages,model_parameters={'temperature': 0.01,"max_tokens": 2000},stream=False)answer = response.message.contentreturn answer.strip()

可以看到就是通过一个 prompt 就完成了原始文档到问答对的转换,可以看到大模型确实可以帮助实现业务所需的基础能力。

文件检索

知识库的检索是在 api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py 中实现的,与常规的 RAG 存在明显不同之处在于支持了丰富的召回模式。

丰富的召回模式

用户可以自由选择所需的召回模式:

请添加图片描述

  • N 选 1 召回:先根据用户输入的意图选择合适的知识库,然后从知识库检索所需的文档,适用于知识库彼此隔离,不需要互相联合查询的场景;
  • 多路召回:此时会同时从多个知识库检索,然后进行重新排序,适用于知识库需要联合查询的场景。

常规的 RAG 服务需要先手工选择知识库,然后从对应的知识库进行检索,无法支持跨库检索。相对而言,Dify 的这个设计还是要更方便一些。下面以 N 选 1 召回 为例介绍下文件的检索,对应的流程如下所示:

请添加图片描述

N 选 1 召回 的知识库选择是基于用户问题与知识库描述的语义匹配性来进行选择,存在 Function Call/ReAct 两种模式,实现代码在 api/core/rag/retrieval/dataset_retrieval.py 中,具体如下所示:

tools = []
# 根据用户输入的意图,使用知识库的描述构造 promptfor dataset in available_datasets:description = dataset.descriptionif not description:description = 'useful for when you want to answer queries about the ' + dataset.namedescription = description.replace('\n', '').replace('\r', '')message_tool = PromptMessageTool(name=dataset.id,description=description,parameters={"type": "object","properties": {},"required": [],})tools.append(message_tool)# 支持 ReAct 模式if planning_strategy == PlanningStrategy.REACT_ROUTER:react_multi_dataset_router = ReactMultiDatasetRouter()dataset_id = react_multi_dataset_router.invoke(query, tools, model_config, model_instance,user_id, tenant_id)
# 支持 Function Call 模式elif planning_strategy == PlanningStrategy.ROUTER:function_call_router = FunctionCallMultiDatasetRouter()dataset_id = function_call_router.invoke(query, tools, model_config, model_instance)
  • Function Call 模式就是构造了一个 prompt,让大模型根据描述选择合适的知识库,实现比较简单
  • ReAct 模式则是基于 ReAct , 通过推理 + 任务的结合选择正确的知识库

知识库检索

在前面根据模式选择了对应的知识库之后,就可以在单个知识库内进行检索,目前支持的检索方式包含下面三种:

  • 向量检索,通过生成查询嵌入并查询与其向量表示最相似的文本分段。
  • 全文检索,索引文档中的所有词汇,从而允许用户查询任意词汇,并返回包含这些词汇的文本片段。
  • 混合检索,同时执行全文检索和向量检索,并附加重排序步骤,从两类查询结果中选择匹配用户问题的最佳结果。

从实际的代码来看,还有一个 关键词检索 的能力,但是没有特别介绍,不确定是否是效果上还不够稳定,可以关注下后续的进展。混合检索的流程如下所示:

请添加图片描述
实际的实现在 api/core/rag/datasource/retrieval_service.py

# 关键词检索if retrival_method == 'keyword_search':keyword_thread = threading.Thread(target=RetrievalService.keyword_search, kwargs={'flask_app': current_app._get_current_object(),'dataset_id': dataset_id,'query': query,'top_k': top_k,'all_documents': all_documents,'exceptions': exceptions,})threads.append(keyword_thread)keyword_thread.start()# 向量检索(混合检索中也会调用)if RetrievalMethod.is_support_semantic_search(retrival_method):embedding_thread = threading.Thread(target=RetrievalService.embedding_search, kwargs={'flask_app': current_app._get_current_object(),'dataset_id': dataset_id,'query': query,'top_k': top_k,'score_threshold': score_threshold,'reranking_model': reranking_model,'all_documents': all_documents,'retrival_method': retrival_method,'exceptions': exceptions,})threads.append(embedding_thread)embedding_thread.start()# 文本检索(混合检索中也会调用)if RetrievalMethod.is_support_fulltext_search(retrival_method):full_text_index_thread = threading.Thread(target=RetrievalService.full_text_index_search, kwargs={'flask_app': current_app._get_current_object(),'dataset_id': dataset_id,'query': query,'retrival_method': retrival_method,'score_threshold': score_threshold,'top_k': top_k,'reranking_model': reranking_model,'all_documents': all_documents,'exceptions': exceptions,})threads.append(full_text_index_thread)full_text_index_thread.start()for thread in threads:thread.join()# 混合检索之后会执行向量和文本检索结果合并后的重排序if retrival_method == RetrievalMethod.HYBRID_SEARCH:data_post_processor = DataPostProcessor(str(dataset.tenant_id), reranking_model, False)all_documents = data_post_processor.invoke(query=query,documents=all_documents,score_threshold=score_threshold,top_n=top_k)
检索结果重排

检索结果的重排也比较简单,就是通过外部模型进行打分,之后基于打分的结果进行排序,实际的实现在 api/core/model_manager.py 中:

def invoke_rerank(self, query: str, docs: list[str], score_threshold: Optional[float] = None,top_n: Optional[int] = None,user: Optional[str] = None) \-> RerankResult:self.model_type_instance = cast(RerankModel, self.model_type_instance)# 轮询调用重排序模型,获得重排序得分,并基于得分进行排序return self._round_robin_invoke(function=self.model_type_instance.invoke,model=self.model,credentials=self.credentials,query=query,docs=docs,score_threshold=score_threshold,top_n=top_n,user=user)

总结

本文主要介绍了 Dify 的知识库 RAG 服务,作为完整 Agent 中一个基础模块,Dify 没有特别强调 RAG 独特之处,但是依旧存在一些独特的亮点:

  1. 多种召回模式,支持根据用户意图自动选择合适的知识库,也可以跨知识库进行检索,弥补了常规的 RAG 服务需要用户手工选择知识库的不便之处;
  2. 独特的 Q&A 模式,可以直接根据文本生成问答对,可以解决常规情况下用户问题与文本匹配度不够的问题,但是应该只适用于特定类型的场景,否则可能会因为转换导致的信息损耗导致效果更差;

总体而言,Dify 中的 RAG 服务是一个设计完备的服务,模块化的设计方便组合与拓展,一些独特的设计也可以提升易用性,代码质量也很高,感兴趣的可以深入研究下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/37553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ss命令详细使用讲解文章

ss 命令作为 iproute2 软件包的一部分&#xff0c;是Socket Statistics的缩写&#xff0c;也称为IPC&#xff08;Inter-process Communication&#xff09;套接字统计 ss命令用于显示sockets&#xff08;套接字&#xff09;的状态。与netstat类似&#xff0c;但它通常提供更详…

WebKit 简介及工作流程探秘

在探索现代互联网世界的奥秘时&#xff0c;浏览器引擎是不可或缺的一环&#xff0c;而 WebKit 正是其中的佼佼者。WebKit&#xff0c;这个开源的浏览器渲染引擎&#xff0c;以其卓越的性能和广泛的支持度&#xff0c;成为了 Safari、早期的 Chrome 以及其他众多浏览器的核心。本…

【笔试记录】华为 | 20230823 | cpp

获取连通的相邻节点列表 题目描述 在网元内&#xff0c;存在了 N 个转发节点&#xff0c;每个转发节点有自己唯一的标识 TB 且每个节点有 M 个端口&#xff0c;节点间通过端口进行报文通讯。出于业务隔离的需求&#xff0c;服务器内的端口被划分为多个通讯平面(用 VLAN 隔离&…

取消lfs, 使用原始文件上传的办法

查询当前仓库使用lfs的文件&#xff0c;然后删除 git lfs ls-files 删除lfs文件后&#xff0c;提交commit git add . git commit -m"remove lfs file" 查询本地lfs配置 git config --local --list 重置本地lfs配置 git config --unset filter.lfs.smudgegit co…

从0到1搭建微服务框架

目录 1.技术栈&#xff1a; 2.模块介绍: 3.关键代码讲解 3.1基础公共模块(common)依赖&#xff1a; 3.3授权模块(auth)依赖: 3.4授权模块核心配置类(AuthrizatonConfig): 3.4 SecurityConfig.java 3.5 bootstrap的核心配置文件(其他服务配置类似这个)&#xff1a; 3.6n…

防爆巡检终端在石化工厂安全保障中的应用

防爆巡检终端在石化工厂安全保障中的应用是广泛而关键的&#xff0c;其设计旨在确保在易燃易爆环境中进行安全、有效的巡检工作。以下是防爆巡检终端在石化工厂安全保障中的详细应用描述&#xff1a; 1. 环境监测与预警 防爆巡检终端配备了各种传感器&#xff0c;能够实时监测…

网银U盾多又乱?后悔没早点用USB Server远程连接管理!

一、引言 网银服务已成为企业日常运营中不可或缺的一部分。但随着企业规模的扩大和业务的增多&#xff0c;网银U盾的数量也随之激增&#xff0c;又多又乱&#xff0c;只能频繁插拔、分散管理&#xff0c;不仅效率低下&#xff0c;而且存在严重的安全隐患。 事实上&#xff0…

ADS131A04硬件设计与软件调试

一、IC基本信息 ADS131A0x 双通道或四通道 24 位 128kSPS 同步采样 Δ-Σ ADC •双通道或四通道同步采样差分输入 • 数据速率&#xff1a;高达 128kSPS • 高性能&#xff1a; – 单通道精度&#xff1a;在 10,000:1 动态范围内优于 0.1% – 有效分辨率&#xff1a;20.6位…

SpringCloud-服务网关-Gateway

1.服务网关在微服务中的应用 (1)对外提供服务的难题分析&#xff1a; 微服务架构下的应用系统体系很庞大&#xff0c;光是需要独立部署的基础组件就有注册中心、配置中心和服务总线、Turbine异常聚合和监控大盘、调用链追踪器和链路聚合&#xff0c;还有Kaka和MQ之类的中间件&…

海思NNIE部署yolov5-shufflenet

1.简要说明 由于NNIE上transpose支持的顺序是固定的,shufflenet那种x=torch.transpose(x,1,2).contiguous() 的操作一般是不支持的。需要进行调整。 2.使用工程以及修改 使用的是开源工程:GitHub - Lufei-github/shufflev2-yolov5: shufflev2-yolov5:lighter, faster and ea…

c++应用网络编程之一基本介绍

一、网络编程介绍 c编程的应用场景在前面分析过&#xff0c;一个重要的方向就是网络编程。一般来说&#xff0c;开发者说的服务端编程在c方向上简单的可以认为是网络编程。首先需要说明的&#xff0c;本系列不对网络编程的相关基础知识展开详细的说明&#xff0c;因为这种知识…

玛格家居从深交所转板北交所:营收净利润连年下滑,销售费用大增

《港湾商业观察》施子夫 近日&#xff0c;玛格家居股份有限公司&#xff08;以下简称&#xff0c;玛格家居&#xff09;发布公告&#xff0c;重庆证监局已经受理其北交所上市的备案申请&#xff0c;辅导机构为国泰君安证券。 公开信息显示&#xff0c;2022年1月&#xff0c;玛…

【转】Android静态集成X5内核

项目中的老机器使用webview 无法加载vue3打包的网页&#xff0c;只能用独立的webview内核&#xff0c;采用静态加载x5内核的方式&#xff0c; 以下内容转自简书作者漆先生的博客&#xff0c;仅用作备份记录 之前在项目中在线集成的X5内核&#xff0c;但是效果不好&#xff0c;只…

基于STM32的智能电池管理系统

目录 引言环境准备智能电池管理系统基础代码实现&#xff1a;实现智能电池管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;电池管理与优化问题解决方案与优化收尾与总结 1. 引言 智能电池管理系统&#xff08;Ba…

【昇思25天学习打卡营打卡指南-第十三天】ShuffleNet图像分类

ShuffleNet图像分类 ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操…

GPT-5 一年半后发布,打开人工智能新世纪

关于GPT-5一年半后发布的消息&#xff0c;这一预测主要基于OpenAI首席技术官Mira Murati的采访和声明。然而&#xff0c;需要明确的是&#xff0c;这个时间点&#xff08;即2025年底或2026年初&#xff09;已经与早期传闻有所不同&#xff0c;显示了OpenAI对产品质量的重视&…

react18.x+播放文本内容

需要调接口将文字传递给后端将文字转换成音频文件&#xff0c;然后播放&#xff0c;同时每次播放不同文本时&#xff0c;当前播放的文本需要暂停&#xff0c;切换到播放新点击的文本 可以设置缓存播放过的音频&#xff0c;也可以不设置缓存&#xff1a; 设置缓存的代码如下&am…

骁龙相机拍照流程分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 1.deliverInputEvent 拍照点击事件处理 2.submitRequestList Camera 提交拍照请求 3.createCaptureRequest 拍照请求帧数 骁龙相机通过binder 数据传输…

idea 内存参数修改不生效问题解决 VM参数设置不生效解决

很多人配置idea 内存参数&#xff0c;怎么配置都不生效&#xff0c;主要原因是配置文件用的不是你修改的那个。 系统环境变量中的这个才是你真正要修改的配置文件。 找到并修改后保存&#xff0c;重启idea就可生效

C++ | Leetcode C++题解之第208题实现Trie(前缀树)

题目&#xff1a; 题解&#xff1a; class Trie { private:vector<Trie*> children;bool isEnd;Trie* searchPrefix(string prefix) {Trie* node this;for (char ch : prefix) {ch - a;if (node->children[ch] nullptr) {return nullptr;}node node->children[…