Agent开发:Dify 知识库检索源码分析

摘要

本篇是对 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_modelweighted_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_searchis_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 实例优化建议

  1. 统一检索配置

    json 复制代码
    {
      "检索方式": "hybrid_search",
      "Top K": 5,
      "分数阈值": 0.5,
      "Reranking": true,
      "Reranking模式": "reranking_model",
      "Reranking模型": "bona/bge-reranker-v2-m3"
    }
  2. 合并重复知识库

    • 删除"段落切割"或"知识库"中的一个
    • 统一分段规则为 \n\n
  3. 优化分段规则

    swift 复制代码
    {
      "separator": "\n\n",
      "max_tokens": 1024,
      "chunk_overlap": 50
    }

短期优化

  1. 评估 Reranking 模型效果

    • 对比 bge-reranker-v2-m3 与其他模型
    • 测试不同 Top K 值的效果
  2. 监控检索性能

    • 记录检索耗时
    • 分析缓存命中率

七、总结

7.1 源码分析总结

通过对照源码分析,我们深入了解了 Dify 知识库检索的完整流程:

  1. 召回设置:支持 Reranking 模型和权重设置两种方式
  2. 检索方式:支持向量检索、全文检索、混合检索
  3. 向量化:使用 Redis 缓存,避免重复调用 embedding 模型
  4. 重排序 :所有重排序都通过 DataPostProcessor 实现
  5. 语义打分:使用余弦相似度,复用向量检索的得分
  6. 关键词打分:使用 Jieba 分词 + TF-IDF + 余弦相似度

7.2 版本优势

相比原文版本,版本有以下优势:

  1. 更现代的语法 :使用 match-case 替代 if-elif
  2. 更好的封装 :使用 KnowledgeRetrievalRequest 对象
  3. 更多功能:支持附件查询、元数据过滤
  4. 更好的错误处理cancel_event + fail-fast 机制
  5. 更好的可观测性_on_retrieval_end 回调更新命中次数

7.3 下一步行动

  1. 统一检索配置:所有知识库使用混合检索 + Reranking 模型
  2. 优化分段规则 :统一使用 \n\n 分隔符
  3. 合并重复知识库:减少资源浪费
  4. 监控检索效果:记录耗时和准确率

附录

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 关键词打分
相关推荐
曦云沐2 小时前
DeepSeek Harness 3 步跑通 Agent 运行时框架(npx 一键启动 Web UI)| 2026 实测
agent·deepseek·harness
星栈3 小时前
决定 Agent 交付下限的「操作系统」:Harness 六层架构拆解
人工智能·架构·agent
机械改造鹅4 小时前
从零开始拆解Pi系列——(4)工具体系
agent
Rubin智造社4 小时前
字节AI生产力大整合:TRAE、扣子并入豆包,「豆包工作」登场,工作方式正在改写
agent·trae·ai生产力·豆包工作·扣子coze
IvanCodes4 小时前
RAG 实战教程(四):GraphRAG 查询实战,本地检索、全局检索与 DRIFT Search
人工智能·agent
CoovallyAIHub5 小时前
客户问交期、报价、配置?销售最值钱的时间不该花在翻资料上,这中间差了一张客户信息拼图
agent·数据可视化
小七-七牛开发者5 小时前
拆解 DeepSeek Harness:Profile 与 Bundle 如何装配运行时
ai·大模型·agent·token·工作流·claudecode·ai coding