LLM & Generative AI/RAG in Practice

[LangChain RAG 구축 시리즈 Ep.27] 🔄 GPT를 활용한 질문 → 테이블 자동 매핑 고도화 전략

ygtoken 2025. 4. 5. 22:42
728x90

 

이전 글에서는 질문에서 단순한 키워드 기반으로 Iceberg 테이블명을 추출하고,
그에 따라 적절한 컬렉션을 자동으로 선택하는 방식을 구현했습니다.
하지만 이 방식은 키워드가 명확하게 포함된 경우에만 작동하고,
"이 고객이 어떤 상품을 구매했는지 알려줘" 같은 질문은 정확한 테이블 매핑이 어렵습니다.

그래서 이번 글에서는:

  • ✅ GPT 모델을 활용하여 질문 문맥을 분석하고
  • ✅ 어떤 테이블이 가장 적절한지 의사결정(Mapping) 하도록 설계하며
  • ✅ 기존 FastAPI API에 이 기능을 통합합니다.

이제는 질문에 "products"라는 단어가 없어도,
GPT가 "아 이건 products 테이블 질문이군"이라고 판단해서 연결해줍니다.


🎯 목표

  • GPT 모델을 활용해 질문 → 테이블명 추론 기능 구현
  • 기존 키워드 매칭 방식보다 유연하고 정확한 매핑 구현
  • FastAPI에서 컬렉션 자동 선택에 GPT 분류기 통합

🧠 Step 1. GPT 기반 테이블 분류 함수 구현

# src/table_classifier.py

from langchain.chat_models import ChatOpenAI          # GPT 호출을 위한 LLM
from langchain.prompts import ChatPromptTemplate      # 프롬프트 구성 유틸

def classify_table(question: str) -> str:
    """
    GPT를 사용하여 질문에 가장 적합한 테이블명을 추론합니다.
    예: "상품 정보를 알고 싶어" → "products"
    """

    # ✅ 사용할 테이블 목록 정의
    known_tables = ["products", "customers", "orders", "items"]

    # ✅ 프롬프트 템플릿 구성
    prompt = ChatPromptTemplate.from_messages([
        ("system", "다음은 Iceberg 테이블 이름 목록입니다: " + ", ".join(known_tables)),
        ("user", "질문: '{question}'\n이 질문에 가장 적합한 테이블 이름만 한 단어로 정확히 답하세요.")
    ])

    # ✅ LLM 인스턴스 구성 (온도 0 → 결정적 결과)
    llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

    # ✅ 실제 질문을 템플릿에 적용
    formatted_prompt = prompt.format_messages(question=question)

    # ✅ GPT 응답 생성
    response = llm(formatted_prompt)

    # ✅ 출력 텍스트 정제 후 반환
    return response.content.strip().lower()

⚙️ Step 2. FastAPI API에 GPT 분류기 통합

# src/rag_server_gptmap.py

from fastapi import FastAPI, Depends
from pydantic import BaseModel
from src.auth import verify_api_key
from src.table_classifier import classify_table            # GPT 기반 테이블 분류기
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
import uvicorn

app = FastAPI()

class QueryRequest(BaseModel):
    question: str

@app.post("/rag/query")
async def rag_query(request: QueryRequest, _: str = Depends(verify_api_key)):
    """
    GPT를 사용하여 질문의 의미를 분석한 뒤,
    가장 관련성 높은 테이블명을 추론하고 해당 컬렉션에서 응답을 생성합니다.
    """

    # ✅ 클라이언트 질문 추출
    question = request.question

    # ✅ GPT를 통해 테이블명 분류
    table = classify_table(question)                        # 예: "orders"

    # ✅ 추론된 테이블명으로 컬렉션 이름 구성
    collection_name = f"iceberg_{table}"

    # ✅ 벡터 저장소 로딩
    vectordb = Chroma(
        persist_directory="chroma_db",
        collection_name=collection_name,
        embedding_function=OpenAIEmbeddings()
    )

    # ✅ 검색기 구성
    retriever = vectordb.as_retriever(search_kwargs={"k": 2})

    # ✅ GPT 모델 설정
    llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.2)

    # ✅ QA 체인 구성
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        retriever=retriever
    )

    # ✅ 질문 실행 및 응답 생성
    result = qa_chain.run(question)

    # ✅ 결과 반환
    return {
        "question": question,
        "table": table,
        "collection": collection_name,
        "answer": result
    }

if __name__ == "__main__":
    uvicorn.run("src.rag_server_gptmap:app", host="0.0.0.0", port=8000, reload=True)

🧪 테스트 예시

http POST http://localhost:8000/rag/query \
  Authorization:"my-secret-key" \
  question="이 고객이 주문한 상품은 무엇인가요?"

✅ GPT는 "orders" 또는 "products"를 추론하여
해당 테이블의 문서를 검색 후 응답 생성


📎 요약 및 핵심 정리

  • GPT를 사용하여 자연어 질문 → 적절한 테이블명을 추론하도록 구성했습니다.
  • 이 전략은 키워드 매칭 기반보다 훨씬 유연하며, 질문이 더 자연스러워도 정확하게 동작합니다.
  • 이제 RAG 시스템은 사용자의 질문 의도를 문맥 기반으로 이해하고
    적절한 테이블 문서를 검색하여 정밀한 응답을 생성할 수 있습니다.
728x90