이 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 맞습니다.