摘要
本篇是对 Dify v1.16.1 源码进行深度解析的文章。通过阅读源码文件,结合知识库检索的完整流程,包括召回设置、检索方式、向量化、重排序等核心环节,分析知识库测试用例。
- 知识库检索节点位于
api/core/workflow/nodes/knowledge_retrieval/ - 检索服务位于
api/core/rag/retrieval/dataset_retrieval.py - 支持单路召回和多路召回两种模式
一、知识库检索节点源码解析
1.1 入口函数
源码路径: api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py
ini
# 源码 - knowledge_retrieval_node.py 第100-182行
@override
def _run(self) -> NodeRunResult:
usage = LLMUsage.empty_usage()
if not self._node_data.query_variable_selector and not self._node_data.query_attachment_selector:
return NodeRunResult(
status=WorkflowNodeExecutionStatus.SUCCEEDED,
inputs={},
process_data={},
outputs={},
metadata={},
llm_usage=usage,
)
variables: dict[str, Any] = {}
# extract variables
if self._node_data.query_variable_selector:
variable = self.graph_runtime_state.variable_pool.get(self._node_data.query_variable_selector)
if not isinstance(variable, StringSegment):
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs={},
error="Query variable is not string type.",
)
query = variable.value
variables["query"] = query
if self._node_data.query_attachment_selector:
variable = self.graph_runtime_state.variable_pool.get(self._node_data.query_attachment_selector)
if not isinstance(variable, ArrayFileSegment) and not isinstance(variable, FileSegment):
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs={},
error="Attachments variable is not array file or file type.",
)
if isinstance(variable, ArrayFileSegment):
variables["attachments"] = variable.value
else:
variables["attachments"] = [variable.value]
try:
with self._session_maker() as session:
results, usage = self._fetch_dataset_retriever(
session=session, node_data=self._node_data, variables=variables
)
outputs = {"result": ArrayObjectSegment(value=[item.model_dump(by_alias=True) for item in results])}
return NodeRunResult(
status=WorkflowNodeExecutionStatus.SUCCEEDED,
inputs=variables,
process_data={"usage": jsonable_encoder(usage)},
outputs=outputs,
metadata={
WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: usage.total_tokens,
WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: usage.total_price,
WorkflowNodeExecutionMetadataKey.CURRENCY: usage.currency,
},
llm_usage=usage,
)
except RateLimitExceededError as e:
logger.warning(e, exc_info=True)
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs=variables,
error=str(e),
error_type=type(e).__name__,
llm_usage=usage,
)
except KnowledgeRetrievalNodeError as e:
logger.warning("Error when running knowledge retrieval node", exc_info=True)
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs=variables,
error=str(e),
error_type=type(e).__name__,
llm_usage=usage,
)
# Temporary handle all exceptions from DatasetRetrieval class here.
except Exception as e:
logger.warning(e, exc_info=True)
return NodeRunResult(
status=WorkflowNodeExecutionStatus.FAILED,
inputs=variables,
error=str(e),
error_type=type(e).__name__,
llm_usage=usage,
)
分析:
- 入口函数
_run调用_fetch_dataset_retriever获取检索结果 - 支持文本查询和附件查询两种方式
- 使用
graphon框架的Node基类 - 结果封装为
ArrayObjectSegment返回
1.2 召回设置解析
源码路径: api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py 第184-294行
ini
# 源码 - knowledge_retrieval_node.py 第227-291行
elif str(node_data.retrieval_mode) == DatasetRetrieveConfigEntity.RetrieveStrategy.MULTIPLE:
if node_data.multiple_retrieval_config is None:
raise ValueError("multiple_retrieval_config is required")
reranking_model: RerankingModelDict | None = None
weights: WeightsDict | None = None
match node_data.multiple_retrieval_config.reranking_mode:
case "reranking_model":
if node_data.multiple_retrieval_config.reranking_model:
reranking_model = {
"reranking_provider_name": node_data.multiple_retrieval_config.reranking_model.provider,
"reranking_model_name": node_data.multiple_retrieval_config.reranking_model.model,
}
else:
reranking_model = None
weights = None
case "weighted_score":
if node_data.multiple_retrieval_config.weights is None:
raise ValueError("weights is required")
reranking_model = None
vector_setting = node_data.multiple_retrieval_config.weights.vector_setting
weights = {
"vector_setting": {
"vector_weight": vector_setting.vector_weight,
"embedding_provider_name": vector_setting.embedding_provider_name,
"embedding_model_name": vector_setting.embedding_model_name,
},
"keyword_setting": {
"keyword_weight": node_data.multiple_retrieval_config.weights.keyword_setting.keyword_weight
},
}
case _:
# Handle any other reranking_mode values
reranking_model = None
weights = None
retrieval_resource_list = self._rag_retrieval.knowledge_retrieval(
session=session,
request=KnowledgeRetrievalRequest(
app_id=dify_ctx.app_id,
tenant_id=dify_ctx.tenant_id,
user_id=dify_ctx.user_id,
user_from=dify_ctx.user_from.value,
dataset_ids=dataset_ids,
query=query,
retrieval_mode=DatasetRetrieveConfigEntity.RetrieveStrategy.MULTIPLE.value,
top_k=node_data.multiple_retrieval_config.top_k,
score_threshold=node_data.multiple_retrieval_config.score_threshold
if node_data.multiple_retrieval_config.score_threshold is not None
else 0.0,
reranking_mode=node_data.multiple_retrieval_config.reranking_mode,
reranking_model=reranking_model,
weights=weights,
reranking_enable=node_data.multiple_retrieval_config.reranking_enable,
metadata_model_config=node_data.metadata_model_config,
metadata_filtering_conditions=resolved_metadata_conditions,
metadata_filtering_mode=metadata_filtering_mode,
attachment_ids=[
parsed_reference.record_id
for attachment in attachments
if (parsed_reference := parse_file_reference(attachment.reference)) is not None
]
if attachments
else None,
),
)
实例配置:
| 配置项 | 源码说明 | 实例配置 |
|---|---|---|
reranking_mode |
reranking_model 或 weighted_score |
段落切割/知识库:reranking_model;企业知识库:未配置 |
reranking_model |
包含 provider 和 model | 使用 bona/bge-reranker-v2-m3 |
weights |
包含 vector_weight 和 keyword_weight | 段落切割/知识库:向量0.7 + 关键词0.3 |
top_k |
返回结果数量 | 段落切割/知识库:2;企业知识库:5 |
二、检索服务源码解析
2.1 多路召回函数
源码路径: api/core/rag/retrieval/dataset_retrieval.py 第748-895行
python
# 源码 - dataset_retrieval.py 第748-895行
@trace_span()
def multiple_retrieve(
self,
app_id: str,
tenant_id: str,
user_id: str,
user_from: str,
available_datasets: list[Dataset],
query: str | None,
top_k: int,
score_threshold: float,
reranking_mode: str,
reranking_model: RerankingModelDict | None = None,
weights: WeightsDict | None = None,
reranking_enable: bool = True,
message_id: str | None = None,
metadata_filter_document_ids: dict[str, list[str]] | None = None,
metadata_condition: MetadataFilteringCondition | None = None,
attachment_ids: list[str] | None = None,
):
if not available_datasets:
return []
all_threads = []
all_documents: list[Document] = []
dataset_ids = [dataset.id for dataset in available_datasets]
index_type_check = all(
item.indexing_technique == available_datasets[0].indexing_technique for item in available_datasets
)
if not index_type_check and (not reranking_enable or reranking_mode != RerankMode.RERANKING_MODEL):
raise ValueError(
"The configured knowledge base list have different indexing technique, please set reranking model."
)
index_type = available_datasets[0].indexing_technique
if index_type == IndexTechniqueType.HIGH_QUALITY:
embedding_model_check = all(
item.embedding_model == available_datasets[0].embedding_model for item in available_datasets
)
embedding_model_provider_check = all(
item.embedding_model_provider == available_datasets[0].embedding_model_provider
for item in available_datasets
)
if (
reranking_enable
and reranking_mode == "weighted_score"
and (not embedding_model_check or not embedding_model_provider_check)
):
raise ValueError(
"The configured knowledge base list have different embedding model, please set reranking model."
)
if reranking_enable and reranking_mode == RerankMode.WEIGHTED_SCORE:
if weights is not None:
weights["vector_setting"]["embedding_provider_name"] = available_datasets[
0
].embedding_model_provider
weights["vector_setting"]["embedding_model_name"] = available_datasets[0].embedding_model
dataset_count = len(available_datasets)
with measure_time() as timer:
cancel_event = threading.Event()
thread_exceptions: list[Exception] = []
if query:
query_thread = threading.Thread(
target=propagate_context(self._multiple_retrieve_thread_safely),
kwargs={
"flask_app": current_app._get_current_object(),
"available_datasets": available_datasets,
"metadata_condition": metadata_condition,
"metadata_filter_document_ids": metadata_filter_document_ids,
"all_documents": all_documents,
"tenant_id": tenant_id,
"reranking_enable": reranking_enable,
"reranking_mode": reranking_mode,
"reranking_model": reranking_model,
"weights": weights,
"top_k": top_k,
"score_threshold": score_threshold,
"query": query,
"attachment_id": None,
"dataset_count": dataset_count,
"cancel_event": cancel_event,
"thread_exceptions": thread_exceptions,
},
)
all_threads.append(query_thread)
query_thread.start()
if attachment_ids:
for attachment_id in attachment_ids:
attachment_thread = threading.Thread(
target=propagate_context(self._multiple_retrieve_thread_safely),
kwargs={
"flask_app": current_app._get_current_object(),
"available_datasets": available_datasets,
"metadata_condition": metadata_condition,
"metadata_filter_document_ids": metadata_filter_document_ids,
"all_documents": all_documents,
"tenant_id": tenant_id,
"reranking_enable": reranking_enable,
"reranking_mode": reranking_mode,
"reranking_model": reranking_model,
"weights": weights,
"top_k": top_k,
"score_threshold": score_threshold,
"query": None,
"attachment_id": attachment_id,
"dataset_count": dataset_count,
"cancel_event": cancel_event,
"thread_exceptions": thread_exceptions,
},
)
all_threads.append(attachment_thread)
attachment_thread.start()
# Poll threads with short timeout to detect errors quickly (fail-fast)
while any(t.is_alive() for t in all_threads):
for thread in all_threads:
thread.join(timeout=0.1)
if thread_exceptions:
cancel_event.set()
break
if thread_exceptions:
break
if thread_exceptions:
raise thread_exceptions[0]
self._on_query(query, attachment_ids, dataset_ids, app_id, user_from, user_id)
if all_documents:
# add thread to call _on_retrieval_end
retrieval_end_thread = threading.Thread(
target=propagate_context(self._on_retrieval_end),
kwargs={
"flask_app": current_app._get_current_object(),
"documents": all_documents,
"message_id": message_id,
"timer": timer,
},
)
retrieval_end_thread.start()
retrieval_resource_list = []
doc_ids_filter = []
for document in all_documents:
if document.provider == "dify":
doc_id = document.metadata.get("doc_id")
if doc_id and doc_id not in doc_ids_filter:
doc_ids_filter.append(doc_id)
retrieval_resource_list.append(document)
elif document.provider == "external":
retrieval_resource_list.append(document)
return retrieval_resource_list
- 源码增加了附件查询支持(
attachment_ids) - 使用
propagate_context装饰器传播上下文 - 增加了 fail-fast 错误处理机制
- 增加了
cancel_event用于取消线程 - 增加了
_on_retrieval_end回调用于更新命中次数
2.2 单知识库检索函数
源码路径: api/core/rag/retrieval/dataset_retrieval.py 第1091-1170行
ini
# 源码 - dataset_retrieval.py 第1091-1170行
def _retriever(
self,
flask_app: Flask,
session: Session,
dataset_id: str,
query: str,
top_k: int,
all_documents: list[Document],
document_ids_filter: list[str] | None = None,
metadata_condition: MetadataFilteringCondition | None = None,
attachment_ids: list[str] | None = None,
):
with flask_app.app_context():
dataset_stmt = select(Dataset).where(Dataset.id == dataset_id)
dataset = session.scalar(dataset_stmt)
if not dataset:
return []
if dataset.provider == "external" and query:
external_documents = ExternalDatasetService.fetch_external_knowledge_retrieval(
session=session,
tenant_id=dataset.tenant_id,
dataset_id=dataset_id,
query=query,
external_retrieval_parameters=dataset.retrieval_model,
metadata_condition=metadata_condition,
)
for external_document in external_documents:
document = Document(
page_content=external_document.get("content"),
metadata=external_document.get("metadata"),
provider="external",
)
if document.metadata is not None:
document.metadata["score"] = external_document.get("score")
document.metadata["title"] = external_document.get("title")
document.metadata["dataset_id"] = dataset_id
document.metadata["dataset_name"] = dataset.name
all_documents.append(document)
else:
# get retrieval model , if the model is not setting , using default
retrieval_model: DefaultRetrievalModelDict = (
cast(DefaultRetrievalModelDict, dataset.retrieval_model)
if dataset.retrieval_model
else default_retrieval_model
)
if dataset.indexing_technique == IndexTechniqueType.ECONOMY:
# use keyword table query
documents = RetrievalService.retrieve(
retrieval_method=RetrievalMethod.KEYWORD_SEARCH,
dataset_id=dataset.id,
query=query,
top_k=top_k,
document_ids_filter=document_ids_filter,
)
if documents:
all_documents.extend(documents)
else:
if top_k > 0:
# retrieval source
documents = RetrievalService.retrieve(
retrieval_method=retrieval_model["search_method"],
dataset_id=dataset.id,
query=query,
top_k=retrieval_model.get("top_k") or 4,
score_threshold=retrieval_model.get("score_threshold", 0.0)
if retrieval_model["score_threshold_enabled"]
else 0.0,
reranking_model=retrieval_model.get("reranking_model", None)
if retrieval_model["reranking_enable"]
else None,
reranking_mode=retrieval_model.get("reranking_mode") or "reranking_model",
weights=retrieval_model.get("weights", None),
document_ids_filter=document_ids_filter,
attachment_ids=attachment_ids,
)
all_documents.extend(documents)
关键点:
-
根据
indexing_technique选择检索方式:ECONOMY:使用关键词检索(已废弃)HIGH_QUALITY:使用配置的检索方法(向量/全文/混合)
-
支持外部知识库(
provider == "external") -
支持文档ID过滤(
document_ids_filter)
三、检索方式源码解析
3.1 检索服务核心函数
源码路径: api/core/rag/datasource/retrieval_service.py
ini
# 源码 - retrieval_service.py
def retrieve(
cls,
retrieval_method: str,
dataset_id: str,
query: str,
top_k: int,
score_threshold: Optional[float] = 0.0,
reranking_model: Optional[dict] = None,
reranking_mode: Optional[str] = "reranking_model",
weights: Optional[dict] = None,
document_ids_filter: Optional[list[str]] = None,
attachment_ids: Optional[list[str]] = None,
):
dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first()
...
# 关键字检索(已废弃)
if retrieval_method == "keyword_search":
...
# 向量检索
if RetrievalMethod.is_support_semantic_search(retrieval_method):
embedding_thread = threading.Thread(
target=RetrievalService.embedding_search,
kwargs={...},
)
threads.append(embedding_thread)
embedding_thread.start()
# 全文检索
if RetrievalMethod.is_support_fulltext_search(retrieval_method):
full_text_index_thread = threading.Thread(
target=RetrievalService.full_text_index_search,
kwargs={...},
)
threads.append(full_text_index_thread)
full_text_index_thread.start()
...
# 混合检索重排序
if retrieval_method == RetrievalMethod.HYBRID_SEARCH.value:
data_post_processor = DataPostProcessor(
str(dataset.tenant_id), reranking_mode, reranking_model, weights, False
)
all_documents = data_post_processor.invoke(
query=query, documents=all_documents,
score_threshold=score_threshold, top_n=top_k
)
return all_documents
3.2 检索方式支持矩阵
根据源码中的 is_support_semantic_search 和 is_support_fulltext_search 函数:
python
# 源码 - retrieval_methods.py
@staticmethod
def is_support_semantic_search(retrieval_method: str) -> bool:
return retrieval_method in {
RetrievalMethod.SEMANTIC_SEARCH.value,
RetrievalMethod.HYBRID_SEARCH.value
}
@staticmethod
def is_support_fulltext_search(retrieval_method: str) -> bool:
return retrieval_method in {
RetrievalMethod.FULL_TEXT_SEARCH.value,
RetrievalMethod.HYBRID_SEARCH.value
}
检索方式支持矩阵:
| 检索方式 | 向量检索 | 全文检索 | 说明 |
|---|---|---|---|
semantic_search |
✅ | ❌ | 仅向量检索 |
full_text_search |
❌ | ✅ | 仅全文检索(BM25) |
hybrid_search |
✅ | ✅ | 混合检索,两者都执行 |
实例配置:
| 知识库 | 检索方式 | 向量检索 | 全文检索 |
|---|---|---|---|
| 段落切割 | hybrid_search |
✅ | ✅ |
| 知识库 | hybrid_search |
✅ | ✅ |
| 企业知识库 | semantic_search |
✅ | ❌ |
四、重排序源码解析
4.1 DataPostProcessor 核心函数
源码路径: api/core/rag/data_post_processor/data_post_processor.py
ruby
# 源码 - data_post_processor.py
def invoke(self, ...) -> list[Document]:
if self.rerank_runner:
documents = self.rerank_runner.run(
query, documents, score_threshold, top_n, user
)
if self.reorder_runner:
documents = self.reorder_runner.run(documents)
return documents
4.2 关键词打分
源码路径: api/core/rag/retrieval/dataset_retrieval.py 第1355-1401行
ini
# 源码 - dataset_retrieval.py 第1355-1401行
def calculate_keyword_score(self, query: str, documents: list[Document], top_k: int) -> list[Document]:
"""
Calculate keywords scores
:param query: search query
:param documents: documents for reranking
:param top_k: top k
:return:
"""
keyword_table_handler = JiebaKeywordTableHandler()
query_keywords = keyword_table_handler.extract_keywords(query, None)
documents_keywords = []
for document in documents:
if document.metadata is not None:
# get the document keywords
document_keywords = keyword_table_handler.extract_keywords(document.page_content, None)
document.metadata["keywords"] = document_keywords
documents_keywords.append(document_keywords)
# Counter query keywords(TF)
query_keyword_counts = Counter(query_keywords)
# total documents
total_documents = len(documents)
# calculate all documents' keywords IDF
all_keywords = set()
for document_keywords in documents_keywords:
all_keywords.update(document_keywords)
keyword_idf = {}
for keyword in all_keywords:
# calculate include query keywords' documents
doc_count_containing_keyword = sum(1 for doc_keywords in documents_keywords if keyword in doc_keywords)
# IDF
keyword_idf[keyword] = math.log((1 + total_documents) / (1 + doc_count_containing_keyword)) + 1
query_tfidf = {}
for keyword, count in query_keyword_counts.items():
tf = count
idf = keyword_idf.get(keyword, 0)
query_tfidf[keyword] = tf * idf
# calculate all documents' TF-IDF
documents_tfidf = []
...
关键点:
- 使用 Jieba 进行中文分词
- 计算 TF-IDF 得分
- 使用余弦相似度计算 query 与文档的相似度
五、实例配置分析
5.1 知识库检索配置总览
基于源码分析,对比实例的配置:
json
{
"知识库配置": {
"段落切割": {
"检索方式": "hybrid_search",
"Top K": 2,
"分数阈值": "启用",
"Reranking": "启用",
"Reranking模式": "reranking_model",
"Reranking模型": "bona/bge-reranker-v2-m3",
"权重": "向量0.7 + 关键词0.3"
},
"知识库": {
"检索方式": "hybrid_search",
"Top K": 2,
"分数阈值": "启用",
"Reranking": "启用",
"Reranking模式": "weighted_score",
"权重": "向量0.7 + 关键词0.3"
},
"企业知识库": {
"检索方式": "semantic_search",
"Top K": 5,
"分数阈值": 0.5,
"Reranking": "启用",
"Reranking模型": "bona/bge-reranker-v2-m3"
}
}
}
5.2 配置问题分析
| 问题 | 源码依据 | 配置 | 建议 |
|---|---|---|---|
| Top K 过低 | top_k 参数控制返回数量 |
段落切割/知识库:2 | 调整为 5 |
| 检索方式不一致 | retrieval_method 参数 |
混合/语义不统一 | 统一使用混合检索 |
| Reranking 模式不一致 | reranking_mode 参数 |
模型/权重不统一 | 统一使用 Reranking 模型 |
| 同一文档重复入库 | dataset_id 过滤 |
两个知识库同一文档 | 合并知识库 |
六、优化建议
6.1 基于源码的优化策略
根据源码分析,推荐以下配置:
json
{
"推荐配置": {
"场景1-追求速度": {
"检索方式": "semantic_search",
"召回设置": "weighted_score",
"权重": "向量1.0 + 关键词0.0",
"说明": "仅使用向量检索,权重重排时语义打分直接复用,速度快"
},
"场景2-追求效果": {
"检索方式": "hybrid_search",
"召回设置": "reranking_model",
"Reranking模型": "bge-reranker-v2-m3",
"说明": "混合检索 + Reranking 模型,效果最好"
},
"场景3-平衡方案": {
"检索方式": "hybrid_search",
"召回设置": "weighted_score",
"权重": "向量0.7 + 关键词0.3",
"说明": "混合检索 + 权重重排,平衡速度和效果"
}
}
}
6.2 实例优化建议
-
统一检索配置
json{ "检索方式": "hybrid_search", "Top K": 5, "分数阈值": 0.5, "Reranking": true, "Reranking模式": "reranking_model", "Reranking模型": "bona/bge-reranker-v2-m3" } -
合并重复知识库
- 删除"段落切割"或"知识库"中的一个
- 统一分段规则为
\n\n
-
优化分段规则
swift{ "separator": "\n\n", "max_tokens": 1024, "chunk_overlap": 50 }
短期优化
-
评估 Reranking 模型效果
- 对比
bge-reranker-v2-m3与其他模型 - 测试不同 Top K 值的效果
- 对比
-
监控检索性能
- 记录检索耗时
- 分析缓存命中率
七、总结
7.1 源码分析总结
通过对照源码分析,我们深入了解了 Dify 知识库检索的完整流程:
- 召回设置:支持 Reranking 模型和权重设置两种方式
- 检索方式:支持向量检索、全文检索、混合检索
- 向量化:使用 Redis 缓存,避免重复调用 embedding 模型
- 重排序 :所有重排序都通过
DataPostProcessor实现 - 语义打分:使用余弦相似度,复用向量检索的得分
- 关键词打分:使用 Jieba 分词 + TF-IDF + 余弦相似度
7.2 版本优势
相比原文版本,版本有以下优势:
- 更现代的语法 :使用
match-case替代if-elif - 更好的封装 :使用
KnowledgeRetrievalRequest对象 - 更多功能:支持附件查询、元数据过滤
- 更好的错误处理 :
cancel_event+ fail-fast 机制 - 更好的可观测性 :
_on_retrieval_end回调更新命中次数
7.3 下一步行动
- 统一检索配置:所有知识库使用混合检索 + Reranking 模型
- 优化分段规则 :统一使用
\n\n分隔符 - 合并重复知识库:减少资源浪费
- 监控检索效果:记录耗时和准确率
附录
A. 源码文件路径
| 文件 | 路径 | 说明 |
|---|---|---|
| 知识库检索节点 | api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py |
入口函数 |
| 检索请求实体 | api/core/workflow/nodes/knowledge_retrieval/retrieval.py |
请求封装 |
| 检索服务 | api/core/rag/retrieval/dataset_retrieval.py |
核心检索逻辑 |
| 数据后处理 | api/core/rag/data_post_processor/data_post_processor.py |
重排序 |
| 重排序运行器 | api/core/rag/rerank/ |
权重/Rerank 模型 |
| 向量数据库 | api/core/rag/datasource/vdb/weaviate/ |
Weaviate 实现 |
B. 关键类和函数
| 类/函数 | 作用 |
|---|---|
KnowledgeRetrievalNode._run |
知识库检索节点入口 |
KnowledgeRetrievalNode._fetch_dataset_retriever |
召回设置处理 |
DatasetRetrieval.knowledge_retrieval |
检索服务入口 |
DatasetRetrieval.multiple_retrieve |
多路召回核心函数 |
DatasetRetrieval._retriever |
单知识库检索 |
DataPostProcessor.invoke |
重排序入口 |
DatasetRetrieval.calculate_keyword_score |
关键词打分 |