为文本到SQL应用程序创建自主性RAG

检索增强生成(RAG)和生成式人工智能模型的融合改变了自然语言处理,通过改进对查询的响应。在代理 RAG 领域,通过引入模块化和自治性,这种传统依赖单一模型执行任务的方法得到加强。代理 RAG 将问题解决过程分解为集成在代理中的工具,提供了诸如准确性、透明度、可扩展性和调试能力等好处。

代理 RAG 用于文本转 SQL 的愿景

传统 RAG 系统通常检索相关文档,依赖单个单块模型生成响应。虽然在某些情况下这是一种有效的方法,但在生成 SQL 等需要结构化输出的情况下,这种方法可能不是最有效的。这就是我们可以利用代理 RAG 框架的强大之处,我们:

  1. 将任务分解为代理内的更小、更易管理的工具
  2. 通过将任务分配给专门工具来提高准确性
  3. 通过跟踪每个工具的推理和工作流程来增强透明度
  4. 通过模块化设计简化扩展和调试

让我们探讨这个工具的工作原理以及每个组件在将用户问题转换为准确 SQL 查询中扮演的角色。

架构概述

该结构包括一个代理利用文本转 SQL 工作流中的工具。该过程可总结如下:

用户查询 → 查询转换工具 → 少样本提示工具 → 混合搜索工具 → 重新排序工具 → 表检索工具 → 提示构建工具 → LLM执行工具 → SQL执行工具 → 最终输出

1. 用户查询转换工具

该工具包括处理用户查询,以更好地理解LLM。它解决了歧义,重新表达用户问题,将缩写转换为它们的形式,并在必要时提供上下文。

增强功能

  • 处理时间参考。将诸如“截至今天”或“至今”之类的术语映射到明确的日期。
  • 替换模糊词语。例如,“最近”可以替换为“过去7天”。
  • 将缩略语或缩写连接到它们的名称。

示例

输入:“显示最近的销售月至今。”

转换后的查询:“检索过去7天的销售数据(月至今)。”

Python

 

from datetime import date, timedelta

def transform_query(user_query):
    # 处理开放式时间参考
    today = date.today()
    transformations = {
        "as of today": f"up to {today}",
        "till now": f"up to {today}",
        "recent": "last 7 days",
        "last week": f"from {today - timedelta(days=7)} to {today}",
    }
    
    for key, value in transformations.items():
        user_query = user_query.replace(key, value)

    # 映射常见缩写
    abbreviations = {
        "MTD": "Month to Date",
        "YTD": "Year to Date",
    }
    for abbr, full_form in abbreviations.items():
        user_query = user_query.replace(abbr, full_form)

    return user_query

query_transform_tool = Tool(
    name="Query Transformer",
    func=transform_query,
    description="Refines user queries for clarity and specificity, handles abbreviations and open-ended terms."
)

2. 少样本提示工具

该工具调用LLM识别一组问题中的某种问题(也可以说匹配模板)。匹配的问题通过示例SQL查询增强提示。

示例工作流程

1. 输入问题:“显示过去7天按产品分组的总销售额。”

2. 预定义模板:

  • “按地区分组的销售额。” → 示例SQL; 选择地区,总销售额…
  • “按产品显示总销售额。” → 示例SQL;SELECT product_name, SUM(sales) …

3. 最相似的问题:”按产品显示总销售额。”

4. 输出示例SQL:SELECT product_name, SUM(sales) FROM …

Python

 

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(model="gpt-4")

predefined_examples = {
    "Show sales grouped by region": "SELECT region, SUM(sales) FROM sales_data GROUP BY region;",
    "Show total sales by product": "SELECT product_name, SUM(sales) FROM sales_data GROUP BY product_name;",
}

def find_similar_question(user_query):
    prompt = "Find the most similar question type for the following user query:\n"
    prompt += f"User Query: {user_query}\n\nOptions:\n"
    for example in predefined_examples.keys():
        prompt += f"- {example}\n"
    prompt += "\nRespond with the closest match."

    response = llm.call_as_function(prompt)
    most_similar = response['content']
    return predefined_examples.get(most_similar, "")

few_shot_tool = Tool(
    name="Few-Shot Prompting",
    func=find_similar_question,
    description="Finds the most similar question type using an additional LLM call and retrieves the corresponding example SQL."
)

3. 混合搜索工具

为了实现强大的检索,该工具结合了语义搜索、基于BM25的关键字搜索以及基于关键字的映射。这些搜索方法的搜索结果通过互惠排名融合在一起。

它们如何结合在一起呢?

关键字表映射

这种方法将表映射到查询中包含的关键字。例如:

  • “销售”一词的存在导致销售表被列入候选名单。
  • “产品”一词的存在导致产品表被列入候选名单。

关键字重叠映射(BM25)

这是一种基于关键字重叠的搜索方法,根据相关性筛选表。为此,我们将应用BM25技术。它根据与用户搜索相关性的顺序对论文进行排序。此搜索技术考虑了术语饱和度以及TF-IDF(词项频率-逆向文件频率)。 

术语频率(TF)有助于衡量给定文档中术语的频率。逆向文件频率(IDF)方法强调出现在每个文档中的单词,减少了其重要性。

归一化考虑文档长度以防止对较长的论文产生偏见。

给定:

  • 销售数据: 包含类似”销售”、”日期”、”产品”等术语。
  • 产品: 包含类似”产品”、”类别”等术语。
  • 订单: 包含类似”订单”、”日期”、”客户”等术语。
  • 财务数据: 包含类似”收入”、”利润”、”支出”等术语。

用户查询: “按产品显示总销售额”。

  • 识别用户查询中的术语: [“销售”,”产品”]。
  • 对DataBaseTable中的每个文档进行排序(基于这些术语的频率和相关性)。

文档的相关性:

  • 销售: 由于同时包含”销售”和”产品”,相关性高。
  • 产品: 由于包含”产品”,相关性高。
  • 订单: 仅包含”销售”,相关性较低。
  • 财务数据: 不相关。

输出:

排序列表: [产品, 销售数据, 订单, 财务数据]

语义搜索

在这种搜索方法中,正如名称所示,我们利用向量嵌入找到语义上相似的表。我们通过计算文档(表向量)与用户查询向量之间的相似度分数(如余弦相似性)来实现这一点。

互倒序列融合

结合BM25和语义搜索结果,使用倒数秩融合策略,以下详细解释:

倒数秩融合(RRF)结合BM25和语义搜索:

RRF是一种将多个排名算法(例如BM25和语义搜索)的结果结合起来的方法。它根据文档在各个方法中的排名为每个文档分配分数,为在多个方法中排名较高的文档给予较高的分数。

RRF公式:

RRF(d) = Σ(r ∈ R) 1 / (k + r(d))

其中:

  • d为一个文档
  • R为排名者(搜索方法)的集合
  • k为一个常数(通常为60)
  • r(d)为文档d在搜索方法r中的排名

逐步示例

输入数据。

1. BM25排名结果:

  • 产品(排名1)
  • 销售数据(排名2)
  • 订单(排名3)

2. 语义搜索排名结果:

  • 销售数据(排名1)
  • 财务(排名2)
  • 产品(排名3)

逐步融合

为每个表格计算分数:

1. 销售数据

  • BM25排名 = 2,语义排名 = 1
  • RRF分数 = (1/60+2) + (1/60+1) = 0.03252

2. 产品

  • BM25排名 = 1,语义排名 = 3
  • RRF分数 = (1/60+1) + (1/60+3) = 0.03226

3. 订单

  • BM25 Rank = 3,语义排名 = 未排名
  • RRF 得分 = (1/60+3)= 0.01587

4. 财务数据

  • BM25 Rank = 未排名,语义排名 = 2
  • RRF 得分 = (1/60+2)=0.01613

5. 按 RRF 得分排序

  • 销售数据(由于在语义搜索中排名最高,得分最高)。
  • 产品(来自 BM25 的高得分)。
  • 订单(整体相关性较低)。
  • 财务数据(重叠性有限)。

最终输出:[‘销售数据’, ‘产品,’ ‘财务数据,’ ‘订单’]

使用关键词表映射检索的表格始终包括在内。

Python

 

from rank_bm25 import BM25Okapi

def hybrid_search(query):
    # 基于关键词的映射
    keyword_to_table = {
        "sales": "sales_data",
        "product": "products",
    }
    keyword_results = [table for keyword, table in keyword_to_table.items() if keyword in query.lower()]

    # BM25 搜索
    bm25 = BM25Okapi(["sales_data", "products", "orders", "financials"])
    bm25_results = bm25.get_top_n(query.split(), bm25.corpus, n=5)

    # 语义搜索
    semantic_results = vector_store.similarity_search(query, k=5)

    # 逆向排序融合
    def reciprocal_rank_fusion(results):
        rank_map = {}
        for rank, table in enumerate(results):
            rank_map[table] = rank_map.get(table, 0) + 1 / (1 + rank)
        return sorted(rank_map, key=rank_map.get, reverse=True)

    combined_results = reciprocal_rank_fusion(bm25_results + semantic_results)

    return list(set(keyword_results + combined_results))

hybrid_search_tool = Tool(
    name="Hybrid Search",
    func=hybrid_search,
    description="Combines keyword mapping, BM25, and semantic search with RRF for table retrieval."
)

4. 重新排序工具

此工具确保最相关的表格被优先考虑。

示例

  • 输入表格:[“销售数据,” “产品,” “财务数据”]
  • 重新排序逻辑
    • 对于每个表格,通过连接查询和表格描述来计算相关性得分。
    • 按相关性得分排序。
  • 输出:[“销售数据,” “产品”]

对重新排名逻辑的进一步了解:

交叉编码器通过分析连接的查询和表描述作为单个输入对来计算相关性分数。这个过程涉及:

  • 对输入。查询和每个表描述被配对并作为输入传递给交叉编码器。
  • 联合编码。与分开的编码器(例如双编码器)不同,交叉编码器联合编码这对数据,使其能够更好地捕捉查询和表描述之间的上下文和依赖关系。
  • 评分。模型为每对数据输出一个相关性分数,指示表与查询匹配程度如何。
Python

 

from transformers import pipeline

reranker = pipeline("text-classification", model="cross-encoder/ms-marco-TinyBERT-L-2")

def re_rank_context(query, results):
    scores = [(doc, reranker(query + " " + doc)[0]['score']) for doc in results]
    return [doc for doc, score in sorted(scores, key=lambda x: x[1], reverse=True)]

re_rank_tool = Tool(
    name="Re-Ranker",
    func=re_rank_context,
    description="Re-ranks the retrieved tables based on relevance to the query."
)

5. 提示构建工具

这个工具为语言模型构建了详细的提示,融入了用户精炼的查询、检索的模式以及来自Few-Shot提示工具的示例。

假设您是一个擅长生成SQL查询的人。生成一个SQL查询以:检索过去7天产品销售总额按产品分组。

相关表:

  1. sales_data:包含列[销售额,日期,产品ID]。
  2. products:包含列[产品ID,产品名称]。

示例SQL:

Plain Text

 

SELECT product_name, SUM(sales) FROM sales_data JOIN products ON sales_data.product_id = products.product_id GROUP BY product_name;

未来展望

虽然这个系统使用一个具有多个工具的单一代理来简化模块化并减少复杂性,但未来可以探索一种多代理框架。我们可能会探索以下内容:

  1. 专门的代理进行上下文检索。语义和关键字搜索分开的代理。
  2. 特定任务代理。专门用于SQL验证或优化的代理。
  3. 代理之间的协作。使用协调代理来管理任务委派。

这种方法可以增强可扩展性,并允许在企业级部署中实现更复杂的工作流程。

结论

Agentic RAG用于文本到SQL应用程序提供了一种可扩展的、模块化的方法来解决结构化查询任务。通过在单一代理框架内结合混合搜索、重新排序、少量提示和动态提示构建,该系统确保准确性、透明度和可扩展性。这种增强的工作流展示了将自然语言问题转化为可执行SQL查询的有效蓝图。

Source:
https://dzone.com/articles/creating-an-agentic-rag-for-text-to-sql-applications