RAG 사전 적용

이 JSON(사전 항목 리스트)도 “텍스트 → 검색 → 상위 n개만 LLM에 전달” 흐름으로 쓰면 됩니다. 항목별로 definition, example, pos 같은 필드를 묶어 한 문서로 보고, BM25+FAISS로 질의와 가까운 항목만 뽑아 LLM 프롬프트에 넣으면 OK.

아래는 지금 쓰신 구조를 그대로 JSON에 맞춰 살짝 바꾼 예시에요:

# pip install rank_bm25 langchain-openai faiss-cpu

import json, os
from dotenv import load_dotenv
from openai import OpenAI

from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.retrievers import EnsembleRetriever

# 1) 환경 변수 & 클라이언트 ---------------------------------------------------
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY"); assert api_key
client = OpenAI(api_key=api_key)
CHAT_MODEL = "gpt-4o-mini"
EMB_MODEL  = "text-embedding-3-small"

# 2) JSON 로드: 항목별 텍스트/메타 준비 ---------------------------------------
JSON_PATH = "/mnt/data/llm_dictionary_2letter_20250812_000751.json"

with open(JSON_PATH, "r", encoding="utf-8") as f:
    data = json.load(f)

entries = data["entries"]  # [{word, prefix, length, pos, definition, example, ...}, ...]

texts, metadatas = [], []
for i, e in enumerate(entries):
    # 한 항목을 하나의 "문서"로 구성 (필요한 필드만 선택)
    content = "\n".join([
        f"word: {e.get('word','')}",
        f"pos: {e.get('pos','')}",
        f"definition: {e.get('definition','')}",
        f"example: {e.get('example','')}",
    ])
    texts.append(content)
    metadatas.append({
        "idx": i,
        "word": e.get("word",""),
        "prefix": e.get("prefix",""),
        "length": e.get("length",""),
        "rarity": e.get("rarity",""),
        "source": f"json_entry_{i}"
    })

# 3) 앙상블 검색기(BM25 + FAISS) ---------------------------------------------
bm25 = BM25Retriever.from_texts(texts, metadatas=metadatas); bm25.k = 3
emb  = OpenAIEmbeddings(api_key=api_key, model=EMB_MODEL)
faiss_store = FAISS.from_texts(texts, emb, metadatas=metadatas)
faiss = faiss_store.as_retriever(search_kwargs={"k": 3})
retriever = EnsembleRetriever(retrievers=[bm25, faiss], weights=[0.3, 0.7])

# 4) 프롬프트 빌드 -------------------------------------------------------------
def build_prompt(query: str, docs):
    ctx = []
    for i, d in enumerate(docs, 1):
        m = d.metadata
        ctx.append(
            f"[문서{i}] (source={m.get('source')}, word={m.get('word')}, prefix={m.get('prefix')}, length={m.get('length')}, rarity={m.get('rarity')})\n"
            f"{d.page_content}"
        )
    context = "\n\n".join(ctx) if ctx else "N/A"
    return f"""아래 '자료'만 근거로 한국어로 간결히 답하세요.
- 자료 밖 정보는 추측하지 마세요.
- 답할 수 없으면 '제공된 문서에서 찾지 못했습니다.'라고 말하세요.

질문:
{query}

자료:
{context}
"""

def ask_llm(prompt: str) -> str:
    resp = client.chat.completions.create(
        model=CHAT_MODEL,
        temperature=0,
        messages=[
            {"role": "system", "content": "You are a helpful assistant that answers in Korean."},
            {"role": "user", "content": prompt},
        ],
    )
    return resp.choices[0].message.content.strip()

# 5) 실행 예 -------------------------------------------------------------------
if __name__ == "__main__":
    query = "‘aer-’로 시작하고 공기/비행과 관련된 단어의 정의와 예문을 알려줘."
    docs = retriever.invoke(query) or []

    if not docs:
        print("제공된 문서에서 찾지 못했습니다.")
    else:
        prompt = build_prompt(query, docs)
        answer = ask_llm(prompt)
        print(answer)

빠른 팁:

  • JSON처럼 “레코드가 많은 구조”는 항목별로 하나의 문서로 보고 인덱싱하세요.
  • 어떤 필드를 묶어 텍스트로 쓸지가 검색 품질에 직결됩니다. 사전은 보통 word + pos + definition + example 조합이 가장 효과적.
  • 데이터가 큰 경우, 위처럼 즉석에서 임베딩을 매번 만들지 말고 FAISS 인덱스를 디스크에 저장/재사용하세요.
  • 한글 질의 ↔ 영어 문서면, 질의를 영어로 번역한 뒤 듀얼 질의(한글+영문)로 검색하는 게 종종 성능이 좋습니다.

요약하면: 네, 이 JSON도 읽어서 검색으로 상위 문서만 추려 LLM에 전달하면 RAG 맞습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다