텍스트-투-SQL 애플리케이션을 위한 에이전틱 RAG 생성하기

검색 증강 생성(RAG)과 생성형 AI 모델의 결합은 쿼리에 대한 응답을 개선하여 자연어 처리에 변화를 가져왔습니다. 에이전트식 RAG의 영역에서, 작업을 위해 단일 거대한 모델에 의존하는 기존 방법은 모듈성과 자율성을 도입함으로써 향상되었습니다. 문제 해결 과정을 에이전트 내에 통합된 도구로 분해함으로써, 에이전트식 RAG는 정확성, 투명성, 확장성 및 디버깅 기능과 같은 혜택을 제공합니다. 에이전트식 RAG를 위한 비전

텍스트-대-SQL용 에이전트식 RAG의 비전

전통적인 RAG 시스템은 종종 관련 문서를 검색하고 응답을 생성하기 위해 단일 거대한 모델에 의존합니다. 이 방법은 일부 경우에 효과적일 수 있지만, SQL을 생성하는 경우와 같이 구조적 출력물에 대한 경우에는 가장 효과적이지 않을 수 있습니다. 여기서 우리는 에이전트식 RAG 프레임워크의 힘을 발휘할 수 있습니다. 여기에서 우리가 하는 방식은:

  1. 작업을 에이전트 내에서 더 작고 더 관리하기 쉬운 도구로 분할
  2. 작업을 전문 도구에 할당함으로써 정확성 향상
  3. 각 도구의 추론 및 워크플로우를 추적함으로써 투명성 향상
  4. 모듈형 설계를 통해 확장 및 디버깅을 단순화

이 도구가 어떻게 작동하며 각 구성 요소가 사용자 질문을 정확한 SQL 쿼리로 변환하는 데 어떤 역할을 하는지에 대해 이야기해 봅시다.

아키텍처 개요

구조는 텍스트-대-SQL 워크플로 내에서 도구를 활용하는 에이전트로 구성됩니다. 프로세스는 다음과 같이 요약할 수 있습니다:

사용자 쿼리 → 쿼리 변환 도구 → 퓨 샷 프롬프팅 도구 → 하이브리드 검색 도구 → 재랭킹 도구 → 테이블 검색 도구 → 프롬프트 빌딩 도구 → LLM 실행 도구 → SQL 실행 도구 → 최종 결과

1. 사용자 쿼리 변환 도구

이 도구는 사용자 쿼리를 처리하여 LLM을 더 잘 이해하기 위한 것으로, 모호성을 해결하고 사용자 질문을 다시 구성하며, 약어를 해당 형태로 번역하고 필요할 때 문맥을 제공합니다.

향상점

  • 시간적 참조 처리. “오늘 기준”이나 “지금까지”와 같은 용어를 명시적인 날짜로 매핑합니다.
  • 모호한 단어 대체. 예를 들어, “최근”은 “지난 7일”로 대체될 수 있습니다.
  • 약어나 준말을 해당 이름으로 연결합니다.

예시

입력: “최근 판매 데이터 표시 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. 입력 질문: “지난 7일간 제품별 총 매출을 보여줘.”

2. 사전 정의된 템플릿:

  • “지역별로 그룹화된 판매 표시.” → 예시 SQL; SELECT 지역, SUM(판매) …
  • “제품별 총 매출을 표시합니다.” → 예시 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를 기반으로 한 키워드 검색, 그리고 키워드 기반 매핑을 결합합니다. 이러한 검색 방법으로부터 나온 검색 결과는 상호 순위 퓨전을 통해 함께 조합됩니다.

모두 어떻게 결합되나요?

키워드 테이블 매핑

이 방법은 쿼리에 포함된 키워드를 테이블에 매핑합니다. 예를 들어:

  • “sales”가 있는 경우 매출 테이블이 최종 후보로 지정됩니다.
  • “product”가 있는 경우 제품 테이블이 최종 후보로 지정됩니다.

키워드 중복 매핑 (BM25)

이는 관련성에 따라 테이블을 최종 후보로 지정하는 키워드 중복 기반 검색 방법입니다. 이를 위해 BM25 기법을 적용할 것입니다. 이 기법은 사용자 검색과 관련된 논문을 관련성 순으로 정렬합니다. 이 검색 기술은 단어 포화도 뿐만 아니라 TF-IDF(Term Frequency-Inverse Document Frequency)를 고려합니다.

단어 빈도 (TF)는 특정 문서 내에서 용어의 빈도를 측정하는 데 도움이 됩니다. 역문서 빈도 (IDF) 접근 방식은 모든 문서에 나타나는 단어를 강조하여 중요성을 줄입니다.

정규화는 문서 길이를 고려하여 긴 논문에 편향되지 않도록 합니다.

주어진:

  • sales_data: “sales,” “date,” “product.”
  • products: “product,” “category.”
  • orders: “order,” “date,” “customer.”
  • financials: “revenue,” “profit,” “expense.”

사용자 쿼리: “제품별 총 판매량 보기.”

  • 사용자 쿼리에서 식별된 용어: [“sales,” “product”].
  • DataBaseTable의 모든 문서를 (이 용어의 빈도 및 관련성에 따라) 정렬합니다.

문서의 관련성:

  • sales: “sales” 및 “product”으로 인한 높은 관련성
  • products: “product”으로 인한 높은 관련성.
  • orders: “sales”만 존재하여 낮은 관련성.
  • financials: 관련 없음.

출력:

순위 목록: [products, sales_data, orders, financials]

의미 검색

검색 방법에서는, 이름에서 알 수 있듯이, 벡터 임베딩을 활용하여 의미론적으로 유사한 테이블을 찾습니다. 이를 위해 문서(테이블 벡터)와 사용자 쿼리 벡터 간의 코사인 유사도와 같은 유사도 점수를 계산합니다.

상호 순위 퓨전

BM25과 의미 검색 결과를 상호 순위 결합 전략을 사용하여 결합합니다. 이는 아래에서 조금 더 자세히 설명됩니다:

BM25와 의미 검색을 결합한 상호 순위 결합 (RRF) 방법:

RRF는 여러 랭킹 알고리즘 (예: BM25 및 의미 검색)의 결과를 결합하는 방법입니다. 각 문서에 점수를 할당하며, 각 방법에서 상위에 랭크된 문서에 더 높은 점수를 부여합니다.

RRF 공식:

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

여기서:

  • d는 문서
  • R은 랭커(검색 방법)의 집합
  • k는 상수(일반적으로 60)
  • r(d)는 검색 방법 r에서 문서 d의 순위입니다

단계별 예시

입력 데이터.

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 점수로 정렬

  • sales_data (의미론적 검색 상위 랭크로 인한 가장 높은 점수).
  • 제품 (BM25로부터 높은 점수).
  • 주문 (전체적으로 낮은 관련성).
  • 재무 (한정된 중첩).

최종 출력: [‘sales_data’, ‘products,’ ‘financials,’ ‘orders’]

키워드 테이블 매핑을 사용하여 검색된 테이블은 항상 포함됩니다.

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. 재랭킹 도구

이 도구는 가장 관련성 높은 테이블을 우선 순위로 설정합니다.

예시

  • 입력 테이블: [“sales_data,” “products,” “financials”]
  • 재랭킹 로직
    • 각 테이블에 대해 쿼리와 테이블 설명을 연결하여 관련성 점수를 계산합니다.
    • 관련성 점수로 정렬합니다.
  • 출력: [“sales_data,” “products”]

Re-ranking 논리에 대해 조금 더 자세히:

교차-인코더는 연결된 쿼리와 테이블 설명을 단일 입력 쌍으로 분석하여 관련성 점수를 계산합니다. 이 과정에는 다음이 포함됩니다:

  • 쌍 입력. 쿼리와 각 테이블 설명은 쌍을 이루어 교차-인코더로 입력됩니다.
  • 공동 인코딩. 별도의 인코더(예: 양방향 인코더)와 달리, 교차-인코더는 쌍을 공동으로 인코딩하여 쿼리와 테이블 설명 사이의 맥락과 의존 관계를 더 잘 파악할 수 있습니다.
  • 점수 매기기. 모델은 각 쌍에 대해 테이블이 쿼리와 얼마나 잘 일치하는지 나타내는 관련성 점수를 출력합니다.
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 Prompting Tool의 예시를 통합합니다.

SQL 쿼리를 생성하는 데 능숙한 사람으로 가정하십시오. 지난 7일간의 제품별 총 매출을 검색하려면 SQL 쿼리를 생성하십시오.

관련 테이블:

  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 응용 프로그램을 위한 에이전트 RAG는 구조화된 쿼리 작업을 해결하기 위한 확장 가능하고 모듈식 접근 방식을 제공합니다. 하이브리드 검색, 재순위 매기기, 퓨샷 프롬프팅, 동적 프롬프트 구성을 단일 에이전트 프레임워크 내에서 통합함으로써, 이 시스템은 정확성, 투명성 및 확장성을 보장합니다. 이 향상된 워크플로우는 자연어 질문을 실행 가능한 SQL 쿼리로 변환하는 강력한 청사진을 보여줍니다.

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