此文章来自海外技术部 - 钟杏锋
一、背景
dify中内置了知识库,知识库高质量索引模式下支持“检索设置”。当在工作流中使用该知识库时也可以对引用的知识库进行“召回设置”。知识库的检索方式配置和工作流中的召回设置比较相像。我们也常常会比较疑惑这两个配置之间是什么关系。了解这两种配置的区别和联系对于优化知识库检索效果至关重要。
核心概念澄清:
- "检索设置":是知识库自身的配置,决定知识如何被索引和初步检索。
- "召回设置":是工作流中使用知识库时的重排(Rerank)配置,依赖于检索设置提供的初步结果。
二、知识库检索配置
1. 索引模式
Dify知识库有两种索引模式:经济模式和高质量模式,这是知识库建立的基础。
2. 经济模式
经济模式,不会产生额外的服务费用的模式,不论在上传知识还是检索知识时都可以利用本地支持的关键词“倒排索引”来实现。
实现原理
# 检索代码实现: dify/api/core/rag/datasource/keyword/jieba/jieba.py
def search(self, query: str, **kwargs: Any) -> list[Document]:
# 获取所有倒排索引
keyword_table = self._get_dataset_keyword_table()
k = kwargs.get("top_k", 4)
document_ids_filter = kwargs.get("document_ids_filter")
sorted_chunk_indices = self._retrieve_ids_by_query(keyword_table or {}, query, k)
...
def _retrieve_ids_by_query(self, keyword_table: dict, query: str, k: int = 4):
# 提取句子关键字
keyword_table_handler = JiebaKeywordTableHandler()
keywords = keyword_table_handler.extract_keywords(query)
# 遍历倒排索引,得出命中关键词最多的知识
# go through text chunks in order of most matching keywords
chunk_indices_count: dict[str, int] = defaultdict(int)
keywords_list = [keyword for keyword in keywords if keyword in set(keyword_table.keys())]
for keyword in keywords_list:
for node_id in keyword_table[keyword]:
chunk_indices_count[node_id] += 1
sorted_chunk_indices = sorted(
chunk_indices_count.keys(),
key=lambda x: chunk_indices_count[x],
reverse=True,
)
return sorted_chunk_indices[:k]
# 对检索出来的知识进行打分: dify/api/core/rag/retrieval/dataset_retrieval.py
def calculate_keyword_score(self, query: str, documents: list[Document], top_k: int) -> list[Document]:
# TF-IDF算法实现
# 计算所有关键词的IDF值
keyword_idf = {}
for keyword in all_keywords:
doc_count_containing_keyword = sum(1 for doc_keywords in documents_keywords if keyword in doc_keywords)
keyword_idf[keyword] = math.log((1 + total_documents) / (1 + doc_count_containing_keyword)) + 1
# 计算相似度
def cosine_similarity(vec1, vec2):
intersection = set(vec1.keys()) & set(vec2.keys())
numerator = sum(vec1[x] * vec2[x] for x in intersection)
sum1 = sum(vec1[x] ** 2 for x in vec1)
sum2 = sum(vec2[x] ** 2 for x in vec2)
denominator = math.sqrt(sum1) * math.sqrt(sum2)
if not denominator:
return 0.0
else:
return float(numerator) / denominator
小结
- 由于使用jieba分词,对非中文内容的检索效果较差
- 经济模式切换到高质量模式时,必须重新建立索引
- 检索出来的知识通过
calculate_keyword_score函数进行余弦相似度打分,按分数排序输出
高质量模式
相对于经济模式,这种模式可能会产生额外的费用,但检索效果会远好于经济模式。高质量模式可使用向量检索和全文检索的方式,在上传知识库和检索知识库时需要先将文本进行“向量化”,再以向量的形式存到向量数据库或去向量数据库检索出相似的记录。所以需要配置Embedding模型(向量化)。
实现原理
# 以adb检索代码为例: dify/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector_openapi.py
def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]:
from alibabacloud_gpdb20160503 import models as gpdb_20160503_models
score_threshold = kwargs.get("score_threshold") or 0.0
request = gpdb_20160503_models.QueryCollectionDataRequest(
dbinstance_id=self.config.instance_id,
region_id=self.config.region_id,
namespace=self.config.namespace,
namespace_password=self.config.namespace_password,
collection=self._collection_name,
include_values=kwargs.pop("include_values", True),
metrics=self.config.metrics,
vector=query_vector, # 传入当前检索的向量数据
content=None,
top_k=kwargs.get("top_k", 4),
filter=None,
)
# 调用数据库api检索最相似的tok_k条数据
response = self._client.query_collection_data(request)
documents = []
for match in response.body.matches.match:
if match.score > score_threshold:
metadata = json.loads(match.metadata.get("metadata_"))
metadata["score"] = match.score # 检索结果包含分数,不需要重新打分
doc = Document(
page_content=match.metadata.get("page_content"),
vector=match.values.value,
metadata=metadata,
)
documents.append(doc)
documents = sorted(documents, key=lambda x: x.metadata["score"] if x.metadata else 0, reverse=True)
return documents
检索设置
向量检索
在检索时需要先将文本进行向量化,再用向量化后的数据到向量数据库中进行检索,检索时可限制向量检索的的条数(topk)和检索的相似度阈值(Score阈值)。
在检索结果后也可选用“Rerank模型”对检索出来的结果再做一次打分重排,以得出最有结果。
全文检索
相比向量检索,全文检索时则不需要对文本进行向量化,而是直接进行文本搜索(大多由向量数据库提供)。针对检索的结果也可以限制条数(topk)和“Rerank模型”重排。
混合检索
混合检索可以并发执行“向量检索”和“全文检索”,在两边返回结果后再进行重新排序,以选出分数最高的topk条记录。因为需要对两个检索结果进行重新排序,所以Rerank是必选的,除了可选“Rerank模型”外也提供了“权重设置”这种内置的免费方式(:。
一般情况我们只需要选择“权重设置”即可,可以对“语义”和“关键词”配置权重。两者分数计算方式如下:
- 语义分值:直接取向量检索返回的分值
- 关键词分值:通过jieba分词分成多个关键词,再使用关键词与输入文本一起计算得出分值
- 最终得分:语义权重*语义分值 + 关键词权重*关键词分值
小结
- Embedding模型选定后就不能再变化,不同的Embedding模型向量化文本的结果不一致可能会影响检索效果。
- 因为“混合检索”中计算关键词权重时使用到了jieba分词,所以理论上这个混合索引对中文支持会比较友好,其它语种慎用。
三、检索召回配置
在工作流中配置知识检索时,可以选择多个知识库作为检索条件。多个知识库的检索是并行的,等所有知识检索出来后再进行重排,返回分数最高的topk条记录,这也是知识库检索节点的“多路召回”功能。
注意到知识库检索召回设置中有一行标题“RERANK设置”,其实已经指明了它只是一个重排的配置,设置topk和阈值只是对重排生效。以下是一些例子:
- 当“知识库1”的检索配置中设置topk=1,检索节点只引用该知识库且topk=3时,最多只能召回1条知识。
- 当“知识库1”和“知识库2”的检索配置中设置topk=1,检索节点引用这两个知识库且topk=3时,最多只能召回2条知识。
- 当“知识库1”的检索配置中设置Score阈值=0.8,检索节点只引用该知识库且Score阈值=0.6(权重语义=1)时,只能召回Score阈值为0.8以上的知识。
所以“检索节点”的召回设置是在知识库召回配置的基础上进行重排过滤的。所以它受限于知识库的配置,并不能召回更多的知识,而是只能作为一个重排优化项。
四、总结
- 知识库使用“经济模式”的“倒排索引”时,需要注意知识库是否为中文,该方式对于非中文的检索不友好。
- 知识库索引模式由“经济模式”改为“高质量模式”时,需要重新建立向量索引,反之则不需要。
- 知识库使用“高质量模式”的“混合检索”时,同样需要考虑知识库是否为中文,在设置“关键词”权重时对非中文不友好。
- Embedding模型选定后不宜再修改,修改需要对整个知识库重新向量化。
- 工作流“知识库检索”节点中的“召回设置”不要于“知识库设置”中的“检索设置”搞混,“召回设置”只是对“检索设置”下召回的知识做重排过滤。