《Advanced RAG》-10-Corrective Retrieval Augmented Generation (CRAG)

news2024/9/22 10:03:53

摘要

CRAG 设计了一个轻量级检索评估器,用于评估针对特定查询检索到的文档的整体质量,并使用网络搜索作为改进检索结果的辅助工具。CRAG 可与基于 RAG 的各种方法无缝集成,并提供了一个插件式的解决方案。

CRAG 的主要思想是引入一个检索评估器,用于评估检索文档与查询之间的关系,并根据评估结果分为三种情况:正确、不正确和模棱两可。

  • 对于正确的情况,使用知识细化算法重写检索文档;
  • 对于不正确的情况,使用网络搜索引擎检索外部知识;
  • 对于模棱两可的情况,使用知识细化算法和搜索引擎进行处理。

文章要点

  1. CRAG 是一种改进传统 RAG 方法的技术,可以避免引入不相关信息,提高模型获取准确知识的能力。
  2. CRAG 设计了一个轻量级检索评估器,用于评估检索文档的质量,并使用网络搜索作为辅助工具。
  3. CRAG 可与基于 RAG 的各种方法无缝集成,并提供了一个插件式的解决方案。
  4. CRAG 的主要思想是引入一个检索评估器,用于评估检索文档与查询之间的关系,并根据评估结果分为三种情况:正确、不正确和模棱两可。
  5. CRAG 的代码已开源,并提供了 Langchain 和 LlamaIndex 的实现。

本文从一个常见的场景开始:参加开卷考试。

通常,我们有三种策略:

  • 方法 1:快速回答熟悉的题目。对于不熟悉的题目,可参考参考书。快速找到相关章节,在头脑中进行整理和归纳,然后将答案写在试卷上。
  • 方法 2:针对每个题目,参考书目。找出相关章节,在脑海中进行总结,然后将答案写在试卷上。
  • 方法 3:针对每个主题,查阅本书并确定相关章节。在形成观点之前,将收集到的信息分为三类:正确、不正确和模糊。分别处理每一类信息。然后,根据这些处理过的信息,在头脑中进行梳理和总结。在试卷上写下你的答案。

方法 1 涉及Self-RAG,而方法 2 则是传统的 RAG 流程。

最后,本文将介绍方法 3,即修正检索增强生成(CRAG)。

CRAG 的动机

在这里插入图片描述

图 1 显示,大多数传统的 RAG 方法并不考虑文档与问题的相关性,而只是简单地合并检索到的文档。这可能会引入不相关的信息,从而阻碍模型获取准确的知识,并可能误导模型,导致幻觉问题。

此外,大多数传统的 RAG 方法将整个检索文档作为输入。然而,在这些检索文档中,有很大一部分文本往往不是生成所必需的,也不应该同样参与到 RAG 中。

CRAG 的主要理念

CRAG 设计了一个轻量级检索评估器,用于评估针对特定查询检索到的文档的整体质量。它还将网络搜索作为改进检索结果的辅助工具。

CRAG 即插即用,可与基于 RAG 的各种方法无缝集成。整体架构如图 2 所示。

在这里插入图片描述

如图 2 所示,CRAG 通过引入检索评估器来评估检索文档与查询之间的关系,从而增强了传统的 RAG。

有 3 种可能的判断结果。

  1. 如果它是正确的,这意味着检索到的文档包含查询所需的必要内容,然后采用知识细化算法重写检索到的文档
  2. 如果检索到的文档不正确,这就意味着查询和检索到的文档不相关。因此,我们不能将文档发送给 LLM。在 CRAG 中,我们使用网络搜索引擎来检索外部知识。
  3. 对于模棱两可的情况,这意味着检索到的文件可能很接近,但不足以提供答案。在这种情况下,就需要通过网络搜索来获取更多信息。因此,需要同时使用知识提炼算法和搜索引擎。

最后,经过处理的信息被转发到 LLM,以生成响应。图 3 正式描述了这一过程。

在这里插入图片描述

请注意,网络搜索并不直接使用用户的输入查询进行搜索。相反,它会构建一个提示,并以少量样本的方式呈现给GPT-3.5 Turbo,以获取搜索的查询。

在对该方法有了总体了解之后,让我们逐一讨论 CRAG 的两个关键组成部分:检索评价器和知识完善算法。

检索评估(Retrieval Evaluator)

如图 4 所示,检索评价器对后续程序的结果有重大影响,是决定整个系统性能的关键。

在这里插入图片描述

CRAG 采用轻量级的 T5-large 模型作为检索评估器,并对其进行微调。值得注意的是,在大型语言模型时代,T5-large 也被认为是轻量级的。

对于每个查询,通常会检索到十个文档。然后,将查询与每个文档单独连接起来,作为预测文档相关性的输入。在微调过程中,为正样本赋予 1 的标签,为负样本赋予 -1 的标签。在推理过程中,评价器会给每个文档分配一个相关性分数,范围从 -1 到 1 不等。

这些分数将根据阈值分为三个等级。显然,这种分类需要两个阈值。在 CRAG 中,阈值的设置可能会根据实验数据的不同而有所变化:

触发三项行动之一的两个置信度阈值是根据经验设定的。具体来说,

  • 在 PopQA 中,它们被设定为(0.59, -0.99);
  • 在 PubQA 和 ArcChallenge 中,它们被设定为(0.5, -0.91);
  • 在 Biography 中,它们被设定为(0.95, -0.91)。

知识提炼算法

对于检索到的相关文档,CRAG 设计了一种分解–再分解的知识提取方法,以进一步提取最关键的知识语句,如图 4 所示。

首先,采用启发式规则将每篇文档分解成细粒度的知识条,以获得细粒度的结果。如果检索到的文档仅由一两个句子组成,则将其视为一个独立单元。否则,文档会被分割成更小的单元,通常由几个句子组成,具体取决于总长度。每个单元都应包含一条独立的信息。

接下来,检索评价器会计算每个知识条的相关性得分。相关性得分低的知识条带会被过滤掉。然后将剩余的相关知识条重新组合,形成内部知识。

代码解释

CRAG 是开源的,Langchain 和 LlamaIndex 都提供了自己的实现。我们将以 LlamaIndex 的实现 作为解释的参考。

环境配置

(base) Florian:~ Florian$ conda create -n crag python=3.11
(base) Florian:~ Florian$ conda activate crag
(crag) Florian:~ Florian$ pip install llama-index llama-index-tools-tavily-research
(crag) Florian:~ Florian$ mkdir "YOUR_DOWNLOAD_DIR"

安装完成后,LlamaIndex 和 Tavily 的相应版本如下:

(crag) Florian:~ Florian$ pip list | grep llama
llama-index                             0.10.29
llama-index-agent-openai                0.2.2
llama-index-cli                         0.1.11
llama-index-core                        0.10.29
llama-index-embeddings-openai           0.1.7
llama-index-indices-managed-llama-cloud 0.1.5
llama-index-legacy                      0.9.48
llama-index-llms-openai                 0.1.15
llama-index-multi-modal-llms-openai     0.1.5
llama-index-packs-corrective-rag        0.1.1
llama-index-program-openai              0.1.5
llama-index-question-gen-openai         0.1.3
llama-index-readers-file                0.1.19
llama-index-readers-llama-parse         0.1.4
llama-index-tools-tavily-research       0.1.3
llama-parse                             0.4.1
llamaindex-py-client                    0.1.18

(crag) Florian:~ Florian$ pip list | grep tavily
llama-index-tools-tavily-research       0.1.3

测试代码

测试代码如下。第一次执行需要下载 CorrectiveRAGPack。

import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

from llama_index.core import Document


# Option: Download CorrectiveRAGPack
# The first execution requires the download of CorrectiveRAGPack
# Subsequent executions can comment this out.
from llama_index.core.llama_pack import download_llama_pack
CorrectiveRAGPack = download_llama_pack(
    "CorrectiveRAGPack", "YOUR_DOWNLOAD_DIR"
)


# Create testing documents
documents = [
    Document(
        text="A group of penguins, known as a 'waddle' on land, shuffled across the Antarctic ice, their tuxedo-like plumage standing out against the snow."
    ),
    Document(
        text="Emperor penguins, the tallest of all penguin species, can dive deeper than any other bird, reaching depths of over 500 meters."
    ),
    Document(
        text="Penguins' black and white coloring is a form of camouflage called countershading; from above, their black back blends with the ocean depths, and from below, their white belly matches the bright surface."
    ),
    Document(
        text="Despite their upright stance, penguins are birds that cannot fly; their wings have evolved into flippers, making them expert swimmers."
    ),
    Document(
        text="The fastest species, the Gentoo penguin, can swim up to 36 kilometers per hour, using their flippers and streamlined bodies to slice through the water."
    ),
    Document(
        text="Penguins are social birds; many species form large colonies for breeding, which can number in the tens of thousands."
    ),
    Document(
        text="Intriguingly, penguins have excellent hearing and rely on distinct calls to identify their mates and chicks amidst the noisy colonies."
    ),
    Document(
        text="The smallest penguin species, the Little Blue Penguin, stands just about 40 cm tall and is found along the coastlines of southern Australia and New Zealand."
    ),
    Document(
        text="During the breeding season, male Emperor penguins endure the harsh Antarctic winter for months, fasting and incubating their eggs, while females hunt at sea."
    ),
    Document(
        text="Penguins consume a variety of seafood; their diet mainly consists of fish, squid, and krill, which they catch on their diving expeditions."
    ),
]


from llama_index.packs.corrective_rag import CorrectiveRAGPack
corrective_rag = CorrectiveRAGPack(documents, "YOUR_TAVILYAI_API_KEY")


# From here, you can use the pack, or inspect and modify the pack in ./corrective_rag_pack.
# The run() function contains around logic behind Corrective Retrieval Augmented Generation - CRAG paper.
query = "How tall is the smallest penguins?"
print('-' * 100)
print("The response of the query " + query + " is:")
response = corrective_rag.run(query, similarity_top_k=2)
print(response)

其中 YOUR_TAVILYAI_API_KEY 可通过tavilyai网站申请。

测试代码产生了以下结果(大部分调试信息已被删除):

(crag) Florian:~ Florian$ python /Users/Florian/Documents/crag.py
--------------------------------------------------------------------------------------
The response of the query How tall is the smallest penguins? is:
--------------------------------------------------------------------------------------
The smallest penguins are about 40 cm (16 inches) tall.

理解测试代码的关键在于 corrective_rag.run() 的实现,让我们深入了解一下。

类 CorrectiveRAGPack 的构造函数

首先,我们来看看构造函数,其源代码如下:

class CorrectiveRAGPack(BaseLlamaPack):
    def __init__(self, documents: List[Document], tavily_ai_apikey: str) -> None:
        """Init params."""
        llm = OpenAI(model="gpt-4")
        self.relevancy_pipeline = QueryPipeline(
            chain=[DEFAULT_RELEVANCY_PROMPT_TEMPLATE, llm]
        )
        self.transform_query_pipeline = QueryPipeline(
            chain=[DEFAULT_TRANSFORM_QUERY_TEMPLATE, llm]
        )

        self.llm = llm
        self.index = VectorStoreIndex.from_documents(documents)
        self.tavily_tool = TavilyToolSpec(api_key=tavily_ai_apikey)

请注意,默认设置为 gpt-4。如果没有权限使用 gpt-4,可以手动将其切换为 gpt-3.5-turbo。

类 CorrectiveRAGPack:: run()

函数 run() 的源代码如下:

class CorrectiveRAGPack(BaseLlamaPack):
    ...
    ...
    def run(self, query_str: str, **kwargs: Any) -> Any:
        """Run the pipeline."""
        # Retrieve nodes based on the input query string.
        retrieved_nodes = self.retrieve_nodes(query_str, **kwargs)

        # Evaluate the relevancy of each retrieved document in relation to the query string.
        relevancy_results = self.evaluate_relevancy(retrieved_nodes, query_str)
        # Extract texts from documents that are deemed relevant based on the evaluation.
        relevant_text = self.extract_relevant_texts(retrieved_nodes, relevancy_results)

        # Initialize search_text variable to handle cases where it might not get defined.
        search_text = ""

        # If any document is found irrelevant, transform the query string for better search results.
        if "no" in relevancy_results:
            transformed_query_str = self.transform_query_pipeline.run(
                query_str=query_str
            ).message.content
            # Conduct a search with the transformed query string and collect the results.
            search_text = self.search_with_transformed_query(transformed_query_str)

        # Compile the final result. If there's additional search text from the transformed query,
        # it's included; otherwise, only the relevant text from the initial retrieval is returned.
        if search_text:
            return self.get_result(relevant_text, search_text, query_str)
        else:
            return self.get_result(relevant_text, "", query_str)

上述代码与标准 CRAG 流程有三个主要区别:

  1. 无需判断或处理模棱两可的文件。
  2. 它不使用训练有素的 T5 大模型,而是利用 LLM 来评估检索到的信息。
  3. 跳过了知识提炼的过程。

尽管存在这些差异,但 LlamaIndex 提供了另一种思维方式(langchain 也是如此)。

使用 LLM 评估检索到的信息

代码如下

class CorrectiveRAGPack(BaseLlamaPack):
    ...
    ...
    def evaluate_relevancy(
        self, retrieved_nodes: List[Document], query_str: str
    ) -> List[str]:
        """Evaluate relevancy of retrieved documents with the query."""
        relevancy_results = []
        for node in retrieved_nodes:
            relevancy = self.relevancy_pipeline.run(
                context_str=node.text, query_str=query_str
            )
            relevancy_results.append(relevancy.message.content.lower().strip())
        return relevancy_results

调用 LLM 的提示如下所示:

DEFAULT_RELEVANCY_PROMPT_TEMPLATE = PromptTemplate(
    template="""As a grader, your task is to evaluate the relevance of a document retrieved in response to a user's question.

    Retrieved Document:
    -------------------
    {context_str}

    User Question:
    --------------
    {query_str}

    Evaluation Criteria:
    - Consider whether the document contains keywords or topics related to the user's question.
    - The evaluation should not be overly stringent; the primary objective is to identify and filter out clearly irrelevant retrievals.

    Decision:
    - Assign a binary score to indicate the document's relevance.
    - Use 'yes' if the document is relevant to the question, or 'no' if it is not.

    Please provide your binary score ('yes' or 'no') below to indicate the document's relevance to the user question."""
)

CRAG 论文显示,ChatGPT 在识别检索相关性方面的性能不如 T5-Large 高。

此外,在实际项目中,我们当然可以使用原始的知识提炼算法。你可以在这里找到相关代码。

重写搜索查询

如前所述,网络搜索并不直接使用用户的输入查询。取而代之的是,它构建了一个提示,并将其呈现给 GPT-3.5 Turbo,使用少量的方法来获取搜索查询。提示如下

DEFAULT_TRANSFORM_QUERY_TEMPLATE = PromptTemplate(
    template="""Your task is to refine a query to ensure it is highly effective for retrieving relevant search results. \n
    Analyze the given input to grasp the core semantic intent or meaning. \n
    Original Query:
    \n ------- \n
    {query_str}
    \n ------- \n
    Your goal is to rephrase or enhance this query to improve its search performance. Ensure the revised query is concise and directly aligned with the intended search objective. \n
    Respond with the optimized query only:"""
)

我对 CRAG 的见解和想法

CRAG 与 Self-RAG的区别

  1. 从流程的角度来看,self-RAG 可以使用 LLM 直接提供响应而无需检索,而 CRAG 则必须在添加评估层之前进行检索。
  2. 从结构上看,self-RAG 比 CRAG 更复杂,它需要更复杂的训练过程,并且在生成阶段需要生成和评估多个标签,这不可避免地增加了推理成本。因此,CRAG 比 selfRAG 更轻便。
  3. 从性能上看,如图 5 所示,在大多数情况下,CRAG 通常优于Self-RAG。

在这里插入图片描述

改进检索评估器

检索评估器可以看作是一个评分分类模型。该模型用于确定查询和文档的相关性,类似于 RAG 中的重新排序模型。

这种相关性判断模型可以通过整合更多符合现实世界场景的特征来加以改进。例如,科学论文问题解答 RAG 包含许多专业术语,而旅游领域的 RAG 则往往包含更多口语化的用户查询。

通过在检索评估器的训练数据中添加场景特征,它可以更好地评估检索文档的相关性。如图 6 所示,还可以加入用户意图和编辑距离等其他特征:

在这里插入图片描述

此外,考虑到 T5-Large 所取得的成果,轻量级模型似乎也能取得良好的成果。这为 CRAG 在小规模团队或公司中的应用带来了希望。

检索评价器的分数和阈值

如前所述,不同类型数据的阈值是不同的。此外,我们还可以发现,模棱两可和不正确的阈值基本上都在-0.9 左右,这表明大部分检索到的知识都与查询相关。完全摒弃这些检索到的知识而完全依赖网络搜索可能并不可取。

在实际应用中,我们需要根据实际问题和需求进行调整。

结论

本文从一个直观的例子入手,概述了 CRAG 的基本流程,并辅以代码解释。其中还包括我的见解和想法。

总之,CRAG 作为一个即插即用的插件,可以显著提高 RAG 的性能。它为改进 RAG 提供了一个轻量级解决方案。

本文为翻译,原文地址:https://ai.gopubby.com/advanced-rag-10-corrective-retrieval-augmented-generation-crag-3f5a140796f9

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

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

相关文章

前端面试宝典【vue篇】【5】

在前端开发的世界里,每一次面试都是一次机遇,也是一次挑战。 你是否曾因技术深度不够而错失良机? 或是面对最新的技术趋势感到迷茫? 我们的【前端面试宝典】正是为此而来。 由拥有多年一线实战经验的资深工程师亲自授课,结合最新的行业动态与实战案例,旨在全面提升你的技…

Java与Python的跨界融合:打造高效与灵活的编程体验

目录 一、引言 1.1 为什么结合Java与Python 1.2 结合方式概览 二、实现方法详解 2.1 Web服务接口 2.2 命令行调用 三、高级融合方式 3.1 Jython 3.2 GRPC/Protobuf 四、优势与挑战 4.1 优势 4.2 挑战 五、结论 在当今快速发展的软件开发领域,单一编程语…

零售企业中 SRM 系统与开源 AI 智能名片商城系统的协同作用

摘要:本文深入探讨了 SRM 系统在零售企业与上游供应商关系管理中的关键作用,并引入开源 AI 智能名片商城系统,细致分析了两者如何协同助力零售企业优化供应链、提升竞争力。通过阐述 SRM 系统的功能模块及其对零售企业的多方面积极影响&#…

职业教育物联网实验实训室建设应用案例

在万物互联的时代背景下,物联网技术以其强大的连接能力、数据分析与处理能力,正在深刻改变着社会生产和生活的方方面面。在这一背景下,职业教育也迎来了新的发展机遇和挑战。为了满足社会对物联网技术人才的需求,提高职业教育的质…

七夕警示:探索社工库与网络搜索下的个人隐私泄露与保护策略

随着七夕节的脚步日益临近,空气中弥漫着浪漫与温馨的气息。这个充满爱意的节日,我们沉浸在与心爱之人共享甜蜜时光的同时,不应忽视网络安全和个人隐私保护的重要性。在数字化时代,个人信息泄露的风险无处不在,如何在享…

Nginx隐藏欢迎页Welcome to CentOS

1、背景 刚刚搭建好Nginx,访问了一下根路径,就出现了欢迎页,额,这个得干掉才行。2、解决 网上搜索,说是使用 yum -y install nginx 安装的问题。 额,这个应该是配置的问题吧。原配置 location / {root h…

Linux磁盘管理_文件系统

系列文章目录 提示:仅用于个人学习,进行查漏补缺使用。 1.Linux介绍、目录结构、文件基本属性、Shell 2.Linux常用命令 3.Linux文件管理 4.Linux 命令安装(rpm、install) 5.Linux账号管理 6.Linux文件/目录权限管理 提示:写完文章后&…

Visual Studio 2022: 100 个实用技巧及详细操作方法

目录 1. 界面和环境设置 1.1 调整主题和配色 1.2 自定义工具栏 1.3 使用解决方案资源管理器的筛选功能 1.4 设置多行光标编辑 1.5 利用窗口布局保存和切换 2. 代码编辑 2.1 快速导航到文件、类、方法 2.2 使用代码片段加速编码 2.3 启用代码格式化 2.4 使用 CodeLens…

stm32入门学习12-软件和硬件SPI操作W25Q64

(一)SPI协议 SPI和I2C同样是一种通信协议,SPI相对I2C的优势是更快的传输速度,其和I2C一样为同步传输,即拥有一根时钟线,但是SPI拥有两根数据线,一根用于主机发送,一根用于主机接收&…

Kspider:超级给力的图形化爬虫平台

Kspider:超级给力的图形化爬虫平台 在如今数据驱动的世界,Kspider 作为一款功能强大的图形化爬虫平台,为用户提供了全面的网页数据抓取解决方案。本文将介绍 Kspider 的基本信息、特点以及它相对于其他爬虫工具的优势。 软件简介 Kspider 是…

【黑马】MyBatis

目录 MyBatis简介JDBC缺点:MyBatis针对于JDBC进行简化,简化思路: MyBatis快速入门具体构建步骤解决SQL映射文件的警告提示 Mapper代理开发案例:使用Mapper代理方式完成案例具体步骤详解:Mapper代理方式 Mapper核心配置…

《向量数据库指南》——企业采用非结构化数据的场景及其深远影响

引言 在当今数字化转型的浪潮中,企业数据的种类与规模正以前所未有的速度增长,其中非结构化数据作为信息时代的重要组成部分,其价值日益凸显。Lynn提出的关于企业最先采用非结构化数据的观察,引发了我们对这一领域深入探索的兴趣。Charles的见解则为我们揭示了非结构化数据…

JavaWeb-01(Java进阶内容详解,Html、CSS、JS)

一、前端技术结构分析 网页的结构(HTML)、表现(CSS)、行为(JS) 1.HTML定义界面整体结构 2.CSS定义页面样式 3.JS实现动态效果 二、HTML 2.1安装VS Code及前端开发插件 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code Code Spe…

高职院校云计算人才培养成果导向系统构建、实施要点与评量方法

一、引言 随着“十四五”规划的深入实施,云计算作为新一代信息技术的关键组成部分,已成为推动各行业数字化转型的重要驱动力。高职院校作为技术技能人才培养的重要阵地,如何根据云计算产业的发展需求,培养具备云计算技术应用与运…

展馆室内导航系统:增强现实技术与数据可视化分析在展馆中的应用

随着科技的飞速发展,展览行业正经历着前所未有的变革。作为信息交流与文化传播的重要场所,展馆在吸引访客、展示展品方面扮演着至关重要的角色。然而,在信息爆炸、时间宝贵以及访客需求日益多样化的今天,传统展馆在导览、管理和服…

PI案例分享--基于DDR4 PHY的VDDQ封装电源完整性分析

随着核心电源网络的电压裕度持续降低,端到端电源完整性建模变得愈发困难,究其原因,是作为系统设计者,我们通常无法得知供应商提供的芯片die模型(die model)的准确性。 通过一个案例对该问题进行研究&#x…

小智纯前端js报表实战4-绝对坐标纵向扩展

绝对坐标-纵向扩展 概述 绝对层次坐标 用于获取扩展后某一位置上的值。如获取A1扩展后的A3单元格的值,就可以在别的单元格如B1中输入A1[A1:3],其公式意义在于获取A1扩展后的第三个单元格的值,其效果如下 绝对坐标-纵向扩展:绝…

JavaEE-多线程编程单例模式

一、等待通知 系统内部,线程之间是抢占式执行的,随即调度,程序可以通过手动干预的方式,能够让线程一定程度的按咱们想要的顺序执行,无法主动让某个线程被调度,但可以主动让某个线程等待。等待通知可以安排…

嵌入式人工智能(45-基于树莓派4B的扩展板-舵机驱动板PCA9685)

1、简介 智能小车、机械臂、摄像头云台会有多个舵机,而微控制器芯片的PWM输出引脚不够的情况下,就可以用PCA9685(16路舵机)来解决这一问题。 PCA9685是一款I2C总线控制的16通道LED控制器,专为红/绿/蓝/琥珀&#xff…

Spring Boot - 在Spring Boot中实现灵活的API版本控制(下)_ 封装场景启动器Starter

文章目录 Pre设计思路ApiVersion 功能特性使用示例配置示例 ProjectStarter Code自定义注解 ApiVersion配置属性类用于管理API版本自动配置基于Spring MVC的API版本控制实现WebMvcRegistrations接口,用于自定义WebMvc的注册逻辑扩展RequestMappingHandlerMapping的类…