為文本到SQL應用程序創建一個主動式RAG

檢索增強生成(RAG)與生成性人工智慧模型的結合,透過改善查詢的回應,對自然語言處理帶來了變化。在代理 RAG 的領域中,這種依賴單一模型進行任務的傳統方法透過引入模組化和自主性得到了增強。代理 RAG 通過將問題解決過程分解為整合在代理內的工具,提供了準確性、透明性、可擴展性和除錯能力等優勢。

代理 RAG 在文本轉 SQL 的願景

傳統的 RAG 系統通常檢索相關文件並依賴單一的整體模型生成回應。雖然在某些情況下這是一種有效的方法,但在生成 SQL 等結構化輸出時,這種方法可能不是最有效的。這就是我們可以利用代理 RAG 框架的力量之處,在這裡我們:

  1. 將任務分解為代理內更小、更易於管理的工具
  2. 透過將任務分配給專門工具來提高準確性
  3. 透過追踪每個工具的推理和工作流程來增強透明度
  4. 通過模組化設計簡化擴展和除錯

讓我們來談談這個工具如何運作,以及每個組件在將用戶問題轉換為準確 SQL 查詢中所扮演的角色。

架構概述

該結構包括一個在文本轉 SQL 工作流中利用工具的代理。該過程可以總結如下:

用戶查詢 → 查詢轉換工具 → 少樣本提示工具 → 混合搜索工具 → 重新排序工具 → 表格檢索工具 → 提示建構工具 → LLM執行工具 → SQL執行工具 → 最終輸出

1. 用戶查詢轉換工具

該工具包括處理用戶查詢以更好理解LLM。它處理模稜兩可之處,重新表達用戶問題,將縮寫翻譯為全稱,並在必要時提供上下文。

增強功能

  • 處理時間參考。將”as of today”或”till now”等詞匯映射為具體日期。
  • 替換模糊詞語。例如,”recent”可替換為”過去7天”。
  • 將簡寫或縮寫連接到它們的全稱。

範例

輸入:”Show recent sales MTD.”

轉換後的查詢:”檢索過去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. 輸入問題:”Show me total sales by product for the 7 days.”

2. 預定義模板:

  • “Show sales grouped by region.” → 示例SQL; SELECT region, SUM(sales) …
  • “按產品顯示總銷售額。” → 例子SQL; 選擇 product_name, SUM(sales) …

3. 最相似的問題: “按產品顯示總銷售額.”

4. 輸出例子SQL: 選擇 product_name, SUM(sales) 從 …

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)方法強調出現在每個文件中的單詞減少重要性。

歸一化考慮到文檔長度,以防止對較長論文的偏見。

給定:

  • sales_data: 包含詞彙如”銷售,” “日期,” “產品.”
  • products: 包含詞彙如”產品,” “類別.”
  • orders: 包含詞彙如”訂單,” “日期,” “顧客.”
  • financials: 包含詞彙如”收入,” “利潤,” “費用.”

用戶查詢: “按產品顯示總銷售量.”

  • 識別用戶查詢中的詞彙: [“銷售,” “產品”].
  • 對 DataBaseTable 中的每個文檔進行排序(基於這些詞彙的頻率和相關性).

文檔的相關性:

  • sales: 因為包含”銷售”和”產品”而具有高相關性.
  • products: 因為包含”產品”而具有高相關性.
  • orders: 因為只包含”銷售”而相關性較低.
  • financials: 不相關.

輸出:

排序列表: [products, sales_data, orders, financials]

語義搜索

在這個搜索方法中, 我們通過計算相似度分數(如餘弦相似度)來尋找語義上相似的表格. 這是通過計算文檔(表格向量)和用戶查詢向量之間的相似度分數來實現的.

互惠排名融合

結合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)= .03226

3. 訂單

  • BM25 排名 = 3,語義排名 = 未排名
  • RRF 分數 = (1/60+3)= 0.01587

4. 財務

  • BM25 排名 = 未排名,語義排名 = 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:包含列[sales, date, product_id]。
  2. products:包含列[product_id, product_name]。

示例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. 代理人之間的協作。使用協調代理人來管理任務委派。

這種方法可以增強可擴展性,並允許進行更複雜的工作流程,尤其是在企業級部署中。

結論

用於文本到SQL應用的Agentic RAG提供了一種可擴展的模塊化方法來解決結構化查詢任務。通過在單一代理人框架內結合混合搜索、重新排名、少數提示和動態提示構建,該系統確保了準確性、透明度和可擴展性。這種增強的工作流展示了將自然語言問題轉化為可操作SQL查詢的強大藍圖。

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