[LangGraph] Self-RAG

Hunie_07ยท2025๋…„ 4์›” 23์ผ
0

Langchain

๋ชฉ๋ก ๋ณด๊ธฐ
35/35

๐Ÿ“Œ Self-RAG

  • ์ฃผ์š” ๋‹จ๊ณ„:

    1. ๊ฒ€์ƒ‰ ๊ฒฐ์ • (Retrieval Decision):

      • ์ž…๋ ฅ: ์งˆ๋ฌธ x ๋˜๋Š” ์งˆ๋ฌธ x์™€ ์ƒ์„ฑ๋œ ๋‹ต๋ณ€ y
      • ๋ชฉ์ : ๊ฒ€์ƒ‰๊ธฐ R์„ ์‚ฌ์šฉํ•˜์—ฌ D ๊ฐœ์˜ ์ฒญํฌ๋ฅผ ๊ฒ€์ƒ‰ํ• ์ง€ ๊ฒฐ์ •
      • ์ถœ๋ ฅ: "yes", "no", "continue" ์ค‘ ํ•˜๋‚˜
      • ์˜๋ฏธ: ์‹œ์Šคํ…œ์ด ์ถ”๊ฐ€ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•œ์ง€ ํŒ๋‹จ
    2. ๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€:

      • ์ž…๋ ฅ: ์งˆ๋ฌธ x์™€ ๊ฐ ๊ฒ€์ƒ‰๋œ ์ฒญํฌ d
      • ๋ชฉ์ : ๊ฐ ์ฒญํฌ๊ฐ€ ์งˆ๋ฌธ์— ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š”์ง€ ํ‰๊ฐ€
      • ์ถœ๋ ฅ: "relevant" ๋˜๋Š” "irrelevant"
      • ์˜๋ฏธ: ๊ด€๋ จ ์—†๋Š” ์ •๋ณด๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ ํ’ˆ์งˆ์„ ํ–ฅ์ƒ
    3. ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์˜ ํ™˜๊ฐ ํ‰๊ฐ€:

      • ์ž…๋ ฅ: ์งˆ๋ฌธ x, ์ฒญํฌ d, ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ y
      • ๋ชฉ์ : ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ๊ฐ€ ์ฒญํฌ์˜ ์ •๋ณด์— ์˜ํ•ด ์ง€์ง€๋˜๋Š”์ง€ ํ‰๊ฐ€
      • ์ถœ๋ ฅ: "fully supported", "partially supported", "no support"
      • ์˜๋ฏธ: ํ™˜๊ฐ(hallucination)์„ ๊ฐ์ง€ํ•˜๊ณ  ์ •๋ณด์˜ ์‹ ๋ขฐ์„ฑ์„ ํ™•์ธ
    4. ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์˜ ์œ ์šฉ์„ฑ ํ‰๊ฐ€:

      • ์ž…๋ ฅ: ์งˆ๋ฌธ x์™€ ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ y
      • ๋ชฉ์ : ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ๊ฐ€ ์งˆ๋ฌธ์— ์œ ์šฉํ•œ ์‘๋‹ต์ธ์ง€ ํ‰๊ฐ€
      • ์ถœ๋ ฅ: 5์  ์ฒ™๋„ (5: ๋งค์šฐ ์œ ์šฉ, 1: ์ „ํ˜€ ์œ ์šฉํ•˜์ง€ ์•Š์Œ)
      • ์˜๋ฏธ: ์‘๋‹ต์˜ ํ’ˆ์งˆ๊ณผ ๊ด€๋ จ์„ฑ์„ ์ˆ˜์น˜ํ™”
  • ๋…ผ๋ฌธ: https://arxiv.org/abs/2310.11511


1๏ธโƒฃ ๋ฒกํ„ฐ์ €์žฅ์†Œ

  • ๋ฉ”๋‰ด ๊ฒ€์ƒ‰์„ ์œ„ํ•ด ์ €์žฅํ•ด๋‘” ์ €์žฅ์†Œ๋ฅผ ๋กœ๋“œ
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings

embeddings_model = OpenAIEmbeddings(model="text-embedding-3-small")

# Chroma ์ธ๋ฑ์Šค ๋กœ๋“œ
vector_db = Chroma(
    embedding_function=embeddings_model,   
    collection_name="restaurant_menu",
    persist_directory="./chroma_db",
)

๊ฒ€์ƒ‰๊ธฐ ์ƒ์„ฑ

# ๊ฒ€์ƒ‰๊ธฐ ์ƒ์„ฑ 
retriever= vector_db.as_retriever(search_kwargs={"k":3})

# ๊ฒ€์ƒ‰ ํ…Œ์ŠคํŠธ
query = "์Šคํ…Œ์ดํฌ์™€ ์–ด์šธ๋ฆฌ๋Š” ์™€์ธ์„ ์ถ”์ฒœํ•ด์ฃผ์„ธ์š”."
results = retriever.invoke(query)

pprint(results)

- ์ถœ๋ ฅ

[Document(id='9c439fb7-5b36-462e-9020-ce8b1d0298c8', metadata={'menu_name': '์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ ์ƒ๋Ÿฌ๋“œ', 'menu_number': 8, 'source': './data/restaurant_menu.txt'}, page_content='8. ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ ์ƒ๋Ÿฌ๋“œ\n   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ26,000\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์†Œ๊ณ ๊ธฐ ์•ˆ์‹ฌ, ๋ฃจ๊ผด๋ผ, ์ฒด๋ฆฌ ํ† ๋งˆํ† , ๋ฐœ์‚ฌ๋ฏน ๊ธ€๋ ˆ์ด์ฆˆ\n   โ€ข ์„ค๋ช…: ๋ถ€๋“œ๋Ÿฌ์šด ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ๋ฅผ ์–‡๊ฒŒ ์Šฌ๋ผ์ด์Šคํ•˜์—ฌ ์‹ ์„ ํ•œ ๋ฃจ๊ผด๋ผ ์œ„์— ์˜ฌ๋ฆฐ ๋ฉ”์ธ ์š”๋ฆฌ ์ƒ๋Ÿฌ๋“œ์ž…๋‹ˆ๋‹ค. ์ฒด๋ฆฌ ํ† ๋งˆํ† ์™€ ํŒŒ๋งˆ์‚ฐ ์น˜์ฆˆ ํ”Œ๋ ˆ์ดํฌ๋กœ ํ’๋ฏธ๋ฅผ ๋”ํ•˜๊ณ , ๋ฐœ์‚ฌ๋ฏน ๊ธ€๋ ˆ์ด์ฆˆ๋กœ ๋งˆ๋ฌด๋ฆฌํ•˜์—ฌ ๊ณ ๊ธฐ์˜ ํ’๋ฏธ๋ฅผ ํ•œ์ธต ๋Œ์–ด์˜ฌ๋ ธ์Šต๋‹ˆ๋‹ค.'),
 Document(id='315f4f43-b824-496f-a575-bd526310c694', metadata={'menu_name': '๋ž์Šคํ„ฐ ๋น„์Šคํฌ', 'menu_number': 7, 'source': './data/restaurant_menu.txt'}, page_content='7. ๋ž์Šคํ„ฐ ๋น„์Šคํฌ\n   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ28,000\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋ž์Šคํ„ฐ, ์ƒํฌ๋ฆผ, ๋ธŒ๋žœ๋””, ํŒŒํ”„๋ฆฌ์นด\n   โ€ข ์„ค๋ช…: ๋ž์Šคํ„ฐ ๊ป์งˆ๊ณผ ์œก์ˆ˜๋กœ ์˜ค๋žœ ์‹œ๊ฐ„ ์šฐ๋ ค๋‚ธ ์ง„ํ•œ ๋น„์Šคํฌ ์ˆ˜ํ”„์ž…๋‹ˆ๋‹ค. ์ƒํฌ๋ฆผ์œผ๋กœ ๋ถ€๋“œ๋Ÿฌ์šด ์งˆ๊ฐ์„ ๋”ํ•˜๊ณ  ๋ธŒ๋žœ๋””๋กœ ๊นŠ์€ ํ’๋ฏธ๋ฅผ ์‚ด๋ ธ์Šต๋‹ˆ๋‹ค. ์ž‘์€ ๋ž์Šคํ„ฐ ์‚ด์„ ํ† ํ•‘์œผ๋กœ ์˜ฌ๋ ค ๊ณ ๊ธ‰์Šค๋Ÿฌ์›€์„ ๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.'),
 Document(id='2d9aa586-8de2-4a67-b69a-5dfb153e7a86', metadata={'menu_name': '์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ', 'menu_number': 1, 'source': './data/restaurant_menu.txt'}, page_content='1. ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ\n   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ35,000\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž, ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค\n   โ€ข ์„ค๋ช…: ์…ฐํ”„์˜ ํŠน์ œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ฉ”๋‰ด๋กœ, 21์ผ๊ฐ„ ๊ฑด์กฐ ์ˆ™์„ฑํ•œ ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๋””์—„ ๋ ˆ์–ด๋กœ ์กฐ๋ฆฌํ•˜์—ฌ ์œก์ฆ™์„ ์ตœ๋Œ€ํ•œ ๋ณด์กดํ•˜๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ํ–ฅ์˜ ๊ฐ์ž์™€ ์•„์‚ญํ•œ ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค๊ฐ€ ๊ณ๋“ค์—ฌ์ง‘๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜์–ด ํ’๋ถ€ํ•œ ๋ง›์„ ๋”ํ•ฉ๋‹ˆ๋‹ค.')]

2๏ธโƒฃ Retrieval Grader

  • ๊ฒ€์ƒ‰ ํ‰๊ฐ€์ž๋Š” ํ‚ค์›Œ๋“œ ๊ด€๋ จ์„ฑ๊ณผ ์˜๋ฏธ์  ๊ด€๋ จ์„ฑ์„ ๊ธฐ์ค€์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ํ‰๊ฐ€

  • ํ‰๊ฐ€๋Š” 'yes/no' ์ด์ง„๋ฒ•์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ๋ถˆํ™•์‹คํ•œ ๊ฒฝ์šฐ 'no' ์ฒ˜๋ฆฌ

  • ๋ถ€๋ถ„ ๊ด€๋ จ์„ฑ์ด๋‚˜ ๋งฅ๋ฝ ์ •๋ณด๋„ ๋‹ต๋ณ€ ํ˜•์„ฑ ๊ธฐ์—ฌ๋„ ๊ธฐ์ค€์œผ๋กœ ํ‰๊ฐ€


1. ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ๋ฐ LLM ์ดˆ๊ธฐํ™”

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Literal


# ๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ์˜ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜
class GradeDocuments(BaseModel):
    """Binary score for relevance check on retrieved documents."""

    binary_score: Literal['yes', 'no'] = Field(
        description="Documents are relevant to the question, 'yes' or 'no'"
    )

# LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๋ฐ ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ ์„ค์ •
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm_grader = llm.with_structured_output(GradeDocuments)

2. LCEL ์ฒด์ธ ๊ตฌ์„ฑ

# ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€๋ฅผ ์œ„ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ •์˜
system_prompt = """๋‹น์‹ ์€ ์œ ๋Šฅํ•œ ๋ฌธ์„œ ์ฒ˜๋ฆฌ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.

ํ‰๊ฐ€ ๊ธฐ์ค€:
- ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์˜ ๊ด€๋ จ์„ฑ์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค

์ ์ˆ˜ ์ฒด๊ณ„:
- ๊ด€๋ จ ์žˆ์œผ๋ฉด 'yes', ์—†์œผ๋ฉด 'no'๋กœ ํ‰๊ฐ€

์ฃผ์˜์‚ฌํ•ญ:
- ๋‹จ์ˆœ ๋‹จ์–ด ๋งค์นญ์ด ์•„๋‹Œ ์งˆ๋ฌธ์˜ ์ „์ฒด ๋งฅ๋ฝ์„ ๊ณ ๋ คํ•˜์„ธ์š”
"""

human_prompt = """
๋‹ค์Œ ๋ฌธ์„œ๊ฐ€ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๊ด€๋ จ ์žˆ๋Š”์ง€ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”.

[๋ฌธ์„œ]
{document}

[์งˆ๋ฌธ]
{question}

[ํ‰๊ฐ€]
"""

# ์ฑ„์  ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
grade_prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("human", human_prompt),
])

# Retrieval Grader ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
retrieval_grader = grade_prompt | structured_llm_grader

3. ํ‰๊ฐ€ ์ˆ˜ํ–‰

# ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ์‹คํ–‰
question = "์ด ์‹๋‹น์„ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”๋‰ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?"
retrieved_docs = vector_db.similarity_search(question, k=2)
print(f"๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ ์ˆ˜: {len(retrieved_docs)}")
print("===============================================================================")
print()

relevant_docs = []

for doc in retrieved_docs:
    print("๋ฌธ์„œ:", doc.page_content)
    print("---------------------------------------------------------------------------")

    relevance = retrieval_grader.invoke(
        input={"question": question, "document": doc},
        config={
            "callbacks": [langfuse_handler],
            "tags":["self-rag", "relevance-grader"],
        }
        )
    print(f"๋ฌธ์„œ ๊ด€๋ จ์„ฑ: {relevance}")

    if relevance.binary_score == 'yes':
        relevant_docs.append(doc)
    
    print("===========================================================================")

- ์ถœ๋ ฅ

๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ ์ˆ˜: 2
===============================================================================

๋ฌธ์„œ: 1. ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ
   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ35,000
   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž, ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค
   โ€ข ์„ค๋ช…: ์…ฐํ”„์˜ ํŠน์ œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ฉ”๋‰ด๋กœ, 21์ผ๊ฐ„ ๊ฑด์กฐ ์ˆ™์„ฑํ•œ ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๋””์—„ ๋ ˆ์–ด๋กœ ์กฐ๋ฆฌํ•˜์—ฌ ์œก์ฆ™์„ ์ตœ๋Œ€ํ•œ ๋ณด์กดํ•˜๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ํ–ฅ์˜ ๊ฐ์ž์™€ ์•„์‚ญํ•œ ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค๊ฐ€ ๊ณ๋“ค์—ฌ์ง‘๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜์–ด ํ’๋ถ€ํ•œ ๋ง›์„ ๋”ํ•ฉ๋‹ˆ๋‹ค.
---------------------------------------------------------------------------
๋ฌธ์„œ ๊ด€๋ จ์„ฑ: binary_score='yes'
===========================================================================
๋ฌธ์„œ: 3. ์—ฐ์–ด ํƒ€๋ฅดํƒ€๋ฅด
   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ18,000
   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋…ธ๋ฅด์›จ์ด์‚ฐ ์ƒ์—ฐ์–ด, ์•„๋ณด์นด๋„, ์ผ€์ดํผ, ์ ์–‘ํŒŒ
   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ๋…ธ๋ฅด์›จ์ด์‚ฐ ์ƒ์—ฐ์–ด๋ฅผ ๊ณฑ๊ฒŒ ๋‹ค์ ธ ์•„๋ณด์นด๋„, ์ผ€์ดํผ, ์ ์–‘ํŒŒ์™€ ํ•จ๊ป˜ ์„ž์–ด ๋งŒ๋“  ํƒ€๋ฅดํƒ€๋ฅด์ž…๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ ๋“œ๋ ˆ์‹ฑ์œผ๋กœ ์ƒํผํ•œ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๋ฐ”์‚ญํ•œ ๋ธŒ๋ฆฌ์˜ค์‰ฌ ํ† ์ŠคํŠธ์™€ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์ „์ฑ„์š”๋ฆฌ๋กœ ์™„๋ฒฝํ•œ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.
---------------------------------------------------------------------------
๋ฌธ์„œ ๊ด€๋ จ์„ฑ: binary_score='no'
===========================================================================

3๏ธโƒฃ Answer Generator

  • ๋‹ต๋ณ€ ์ƒ์„ฑ ์‹œ ๋ฌธ๋งฅ ๋‚ด ์ •๋ณด๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ์ถ”์ธก ๋ฐฐ์ œ
  • ๋‹ต๋ณ€์€ ์ง์ ‘ ๊ด€๋ จ ์ •๋ณด๋งŒ ํฌํ•จํ•˜์—ฌ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑ
  • ์ •๋ณด ๋ถ€์กฑ ์‹œ "์ฃผ์–ด์ง„ ์ •๋ณด๋งŒ์œผ๋กœ๋Š” ๋‹ตํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" ๋ช…์‹œ
  • ํ•„์š”์‹œ ์ง์ ‘ ์ธ์šฉ๋ฌธ ํ™œ์šฉํ•˜์—ฌ ์ •ํ™•์„ฑ ํ™•๋ณด

# ๊ธฐ๋ณธ RAG ์ฒด์ธ
from langchain_core.output_parsers import StrOutputParser

def generator_answer(question, docs):

    template = """
    ์ œ์‹œ๋œ ๋ฌธ๋งฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์„ธ์š”.

    [๋ฌธ๋งฅ]
    {context}

    [์งˆ๋ฌธ]
    {question}

    [๋‹ต๋ณ€]
    """

    prompt = ChatPromptTemplate.from_template(template)
    llm = ChatOpenAI(model='gpt-4o-mini', temperature=0)    

    def format_docs(docs):
        return "\n\n".join([d.page_content for d in docs])
    
    rag_chain = prompt | llm | StrOutputParser()
    
    generation = rag_chain.invoke(
        {"context": format_docs(docs), "question": question},
        config={
            "callbacks": [langfuse_handler],
            "tags": ["self-rag", "answer-generator"],
            }
        )

    return generation


# ๊ด€๋ น์„ฑ ํ‰๊ฐ€๋ฅผ ํ†ต๊ณผํ•œ ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€ ์ƒ์„ฑ
generation = generator_answer(question, docs=relevant_docs)
print(generation)

- ์ถœ๋ ฅ

์ด ์‹๋‹น์„ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”๋‰ด๋Š” '์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ'์ž…๋‹ˆ๋‹ค.

4๏ธโƒฃ Hallucination Grader

  • ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋Š” ์‚ฌ์‹ค ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€์„ ํ‰๊ฐ€ํ•˜๋Š” ์ „๋ฌธ๊ฐ€ ์—ญํ• ์„ ์ •์˜ํ•จ
  • ํ‰๊ฐ€๋Š” ๋‘ ๊ฐ€์ง€ ๊ธฐ์ค€์œผ๋กœ ์ง„ํ–‰: ์‚ฌ์‹ค ๊ทผ๊ฑฐ ์‹œ 'yes', ๊ทผ๊ฑฐ ๋ถ€์กฑ ์‹œ 'no'

# ํ™˜๊ฐ(Hallucination) ํ‰๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜
class GradeHallucinations(BaseModel):
    """Binary score for hallucination present in generation answer."""

    binary_score: str = Field(
        description="Answer is grounded in the facts, 'yes' or 'no'"
    )


# LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๋ฐ ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ ์„ค์ •
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm_grader = llm.with_structured_output(GradeHallucinations)


# ํ™˜๊ฐ ํ‰๊ฐ€๋ฅผ ์œ„ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ •์˜
system_prompt = """
๋‹น์‹ ์€ ์ฃผ์–ด์ง„ ์‚ฌ์‹ค์— ๊ทผ๊ฑฐํ•œ ๋‹ต๋ณ€์„ ํ‰๊ฐ€ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.

[ํ‰๊ฐ€ ๊ธฐ์ค€]
- ๋‹ต๋ณ€์ด ์‚ฌ์‹ค์— ๊ทผ๊ฑฐํ•˜๊ณ  ์™„์ „ํžˆ ์ง€์›๋˜๋Š” ๊ฒฝ์šฐ 'yes'
- ๋‹ต๋ณ€์— ์‚ฌ์‹ค์— ๊ทผ๊ฑฐํ•˜์ง€ ์•Š์€ ์ •๋ณด๋‚˜ ์ฃผ์žฅ์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ 'no'
"""

human_prompt = """
๋‹ค์Œ ๋‹ต๋ณ€์ด ์‚ฌ์‹ค์— ๊ทผ๊ฑฐํ•˜๋Š”์ง€ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”.

[์‚ฌ์‹ค]
{documents}

[๋‹ต๋ณ€]
{generation}

[ํ‰๊ฐ€]
"""

# ํ™˜๊ฐ ํ‰๊ฐ€ ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
hallucination_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", human_prompt),
    ]
)

# Hallucination Grader ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
hallucination_grader = hallucination_prompt | structured_llm_grader

# ํ™˜๊ฐ ํ‰๊ฐ€ ์‹คํ–‰
hallucination = hallucination_grader.invoke(
    {"documents": relevant_docs, "generation": generation},
    config={
        "callbacks": [langfuse_handler],
        "tags": ["self-rag", "hallucination-grader"],
    }
    )
print(f"ํ™˜๊ฐ ํ‰๊ฐ€: {hallucination}")

- ์ถœ๋ ฅ

ํ™˜๊ฐ ํ‰๊ฐ€: binary_score='yes'

5๏ธโƒฃ Answer Grader

  • ๋‹ต๋ณ€ ํ‰๊ฐ€๋Š” ์ •๋ณด ํฌํ•จ ์—ฌ๋ถ€๋ฅผ ๊ธฐ์ค€์œผ๋กœ 'yes' ๋˜๋Š” 'no'๋กœ ํŒ๋‹จ
  • ์‹œ์Šคํ…œ์€ ๋‹จ์ˆœ๋ช…ํ™•ํ•œ ๊ธฐ์ค€์œผ๋กœ ๋‹ต๋ณ€์˜ ์ ์ ˆ์„ฑ์„ ํ‰๊ฐ€ํ•จ

# ๋‹ต๋ณ€ ํ‰๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜
class GradeAnswer(BaseModel):
    """Binary score to assess answer addresses question."""

    binary_score: str = Field(
        description="Answer addresses the question, 'yes' or 'no'"
    )


# LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๋ฐ ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ ์„ค์ •
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
structured_llm_grader = llm.with_structured_output(GradeAnswer)

# ๋‹ต๋ณ€ ํ‰๊ฐ€๋ฅผ ์œ„ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ •์˜
system_prompt = """
๋‹น์‹ ์€ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ํ‰๊ฐ€ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.

[ํ‰๊ฐ€ ๊ธฐ์ค€]
- ๋‹ต๋ณ€์ด ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ 'yes'
- ๋‹ต๋ณ€์ด ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ 'no'
"""

human_prompt = """
๋‹ค์Œ ๋‹ต๋ณ€์ด ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ‰๊ฐ€ํ•ด์ฃผ์„ธ์š”.

[์งˆ๋ฌธ]
{question}

[๋‹ต๋ณ€]
{generation}

[ํ‰๊ฐ€]
"""


# ๋‹ต๋ณ€ ํ‰๊ฐ€ ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
answer_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", human_prompt),
    ]
)


# Answer Grader ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
answer_grader = answer_prompt | structured_llm_grader

# ๋‹ต๋ณ€ ํ‰๊ฐ€ ์‹คํ–‰
print("Question:", question)
print("Generation:", generation)

answer_score = answer_grader.invoke(
    {"question": question, "generation": generation},
    config={
        "callbacks": [langfuse_handler],
        "tags": ["self-rag", "answer-grader"],
        }
)
print(f"๋‹ต๋ณ€ ํ‰๊ฐ€: {answer_score}")

- ์ถœ๋ ฅ

Question: ์ด ์‹๋‹น์„ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”๋‰ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?
Generation: ์ด ์‹๋‹น์„ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”๋‰ด๋Š” '์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ'์ž…๋‹ˆ๋‹ค.
๋‹ต๋ณ€ ํ‰๊ฐ€: binary_score='yes'

6๏ธโƒฃ Question Re-writer

  • ์งˆ๋ฌธ์„ ๋ช…ํ™•์„ฑ๊ณผ ๊ฐ„๊ฒฐ์„ฑ ์ค‘์‹ฌ์œผ๋กœ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ฃผ์š” ๋ชฉํ‘œ
  • ๋ถˆํ•„์š”ํ•œ ์ •๋ณด ์ œ๊ฑฐ ์‹œ์—๋„ ์›๋ž˜ ์˜๋„ ์œ ์ง€๊ฐ€ ํ•ต์‹ฌ ์ง€์นจ

def rewrite_question(question: str) -> str:
    """
    ์ฃผ์–ด์ง„ ์งˆ๋ฌธ์„ ๋ฒกํ„ฐ ์ €์žฅ์†Œ ๊ฒ€์ƒ‰์— ์ตœ์ ํ™”๋œ ํ˜•ํƒœ๋กœ ๋‹ค์‹œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

    :param question: ์›๋ณธ ์งˆ๋ฌธ ๋ฌธ์ž์—ด
    :return: ๋‹ค์‹œ ์ž‘์„ฑ๋œ ์งˆ๋ฌธ ๋ฌธ์ž์—ด
    """
    # LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

    # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ •์˜
    system_prompt = """
    ๋‹น์‹ ์€ ์ „๋ฌธ์ ์ธ ์งˆ๋ฌธ ๋‹ค์‹œ ์“ฐ๊ธฐ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.

    [์ง€์นจ]
    - ์งˆ๋ฌธ์„ ๋” ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋‹ค์‹œ ์ž‘์„ฑํ•˜์„ธ์š”
    - ์งˆ๋ฌธ์˜ ์˜๋„๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ถˆํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”
    """

    human_prompt = """
    ๋‹ค์Œ ์งˆ๋ฌธ์„ ๋” ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋‹ค์‹œ ์ž‘์„ฑํ•˜์„ธ์š”.

    [์›๋ณธ ์งˆ๋ฌธ]
    {question}

    [๊ฐœ์„ ๋œ ์งˆ๋ฌธ]
    """

    # ์งˆ๋ฌธ ๋‹ค์‹œ ์“ฐ๊ธฐ ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ
    re_write_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", system_prompt),
            ("human", human_prompt),
        ]
    )

    # ์งˆ๋ฌธ ๋‹ค์‹œ ์“ฐ๊ธฐ ์ฒด์ธ ๊ตฌ์„ฑ
    question_rewriter = re_write_prompt | llm | StrOutputParser()

    # ์งˆ๋ฌธ ๋‹ค์‹œ ์“ฐ๊ธฐ ์‹คํ–‰
    rewritten_question = question_rewriter.invoke(
        {"question": question},
        config={
            "callbacks": [langfuse_handler],
            "tags": ["self-rag", "question-rewriter"],
        },
    )

    return rewritten_question

# ์งˆ๋ฌธ ๋‹ค์‹œ ์“ฐ๊ธฐ ํ…Œ์ŠคํŠธ
rewritten_question = rewrite_question(question)
print(f"์›๋ณธ ์งˆ๋ฌธ: {question}")
print(f"๋‹ค์‹œ ์“ด ์งˆ๋ฌธ: {rewritten_question}")

- ์ถœ๋ ฅ

์›๋ณธ ์งˆ๋ฌธ: ์ด ์‹๋‹น์„ ๋Œ€ํ‘œํ•˜๋Š” ๋ฉ”๋‰ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?
๋‹ค์‹œ ์“ด ์งˆ๋ฌธ: ์ด ์‹๋‹น์˜ ๋Œ€ํ‘œ ๋ฉ”๋‰ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?

์žฌ์ž‘์„ฑํ•œ ์ฟผ๋ฆฌ๋กœ ๊ฒ€์ƒ‰

# ๋‹ค์‹œ ์“ด ์งˆ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒกํ„ฐ ์ €์žฅ์†Œ์—์„œ ๋ฌธ์„œ ๊ฒ€์ƒ‰
query = rewritten_question
retrieved_docs = vector_db.similarity_search(query, k=2)
print(len(retrieved_docs))
print("===========================================================================")

for doc in retrieved_docs:
    print("๋ฌธ์„œ:", doc.page_content)
    print("---------------------------------------------------------------------------")

- ์ถœ๋ ฅ

2
===========================================================================
๋ฌธ์„œ: 1. ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ
   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ35,000
   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž, ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค
   โ€ข ์„ค๋ช…: ์…ฐํ”„์˜ ํŠน์ œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋ฉ”๋‰ด๋กœ, 21์ผ๊ฐ„ ๊ฑด์กฐ ์ˆ™์„ฑํ•œ ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๋””์—„ ๋ ˆ์–ด๋กœ ์กฐ๋ฆฌํ•˜์—ฌ ์œก์ฆ™์„ ์ตœ๋Œ€ํ•œ ๋ณด์กดํ•˜๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ํ–ฅ์˜ ๊ฐ์ž์™€ ์•„์‚ญํ•œ ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค๊ฐ€ ๊ณ๋“ค์—ฌ์ง‘๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜์–ด ํ’๋ถ€ํ•œ ๋ง›์„ ๋”ํ•ฉ๋‹ˆ๋‹ค.
---------------------------------------------------------------------------
๋ฌธ์„œ: 3. ์—ฐ์–ด ํƒ€๋ฅดํƒ€๋ฅด
   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ18,000
   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋…ธ๋ฅด์›จ์ด์‚ฐ ์ƒ์—ฐ์–ด, ์•„๋ณด์นด๋„, ์ผ€์ดํผ, ์ ์–‘ํŒŒ
   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ๋…ธ๋ฅด์›จ์ด์‚ฐ ์ƒ์—ฐ์–ด๋ฅผ ๊ณฑ๊ฒŒ ๋‹ค์ ธ ์•„๋ณด์นด๋„, ์ผ€์ดํผ, ์ ์–‘ํŒŒ์™€ ํ•จ๊ป˜ ์„ž์–ด ๋งŒ๋“  ํƒ€๋ฅดํƒ€๋ฅด์ž…๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ ๋“œ๋ ˆ์‹ฑ์œผ๋กœ ์ƒํผํ•œ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๋ฐ”์‚ญํ•œ ๋ธŒ๋ฆฌ์˜ค์‰ฌ ํ† ์ŠคํŠธ์™€ ํ•จ๊ป˜ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์ „์ฑ„์š”๋ฆฌ๋กœ ์™„๋ฒฝํ•œ ๋ฉ”๋‰ด์ž…๋‹ˆ๋‹ค.
---------------------------------------------------------------------------

7๏ธโƒฃ LangGraph ๋กœ ๊ทธ๋ž˜ํ”„ ๊ตฌํ˜„

  • LangGraph ๊ตฌํ˜„์€ ๊ธฐ๋ณธ์ ์ธ StateGraph๋ฅผ ํ™œ์šฉ
  • Node์™€ Edge ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์ด์ „์— ๊ตฌํ˜„ํ•œ ๋…ธ๋“œ ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐ

1. ๊ทธ๋ž˜ํ”„ State ์ƒ์„ฑ

from typing import List, TypedDict
from langchain_core.documents import Document

class GraphState(TypedDict):
    question: str                 # ์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ
    generation: str               # LLM ์ƒ์„ฑ ๋‹ต๋ณ€
    documents: List[Document]     # ์ปจํ…์ŠคํŠธ ๋ฌธ์„œ (๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ)
    num_generations: int          # ์งˆ๋ฌธ or ๋‹ต๋ณ€ ์ƒ์„ฑ ํšŸ์ˆ˜ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€์— ํ™œ์šฉ)

2. Node ์ƒ์„ฑ

# Node ์ •์˜

def retrieve(state: GraphState) -> GraphState:
    """๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ํ•จ์ˆ˜"""
    print("--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---")
    question = state["question"]
    
    # ๋ฌธ์„œ ๊ฒ€์ƒ‰ ๋กœ์ง
    documents = vector_db.similarity_search(question)
    return {"documents": documents}      # ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์— ๊ฒ€์ƒ‰ํ•œ ๋ฌธ์„œ ๊ฐ์ฒด๋“ค๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ 

def generate(state: GraphState) -> GraphState:
    """๋‹ต๋ณ€์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜"""
    print("--- ๋‹ต๋ณ€ ์ƒ์„ฑ ---")
    question = state["question"]
    documents = state["documents"]
    
    # RAG๋ฅผ ์ด์šฉํ•œ ๋‹ต๋ณ€ ์ƒ์„ฑ
    generation = generator_answer(question, docs=documents)

    # ์ƒ์„ฑ ํšŸ์ˆ˜ ์—…๋ฐ์ดํŠธ
    num_generations = state.get("num_generations", 0)
    num_generations += 1
    return {"generation": generation, "num_generations": num_generations}      # ๋‹ต๋ณ€, ์ƒ์„ฑํšŸ์ˆ˜ ์—…๋ฐ์ดํŠธ 

def grade_documents(state: GraphState) -> GraphState:
    """๊ฒ€์ƒ‰๋œ ๋ฌธ์„œ์˜ ๊ด€๋ จ์„ฑ์„ ํ‰๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜"""
    print("--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---")
    question = state["question"]
    documents = state["documents"]
    
    # ๊ฐ ๋ฌธ์„œ ํ‰๊ฐ€
    filtered_docs = []
    for d in documents:
        score = retrieval_grader.invoke({"question": question, "document": d})
        grade = score.binary_score
        if grade == "yes":
            print("---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์žˆ์Œ---")
            filtered_docs.append(d)
        else:
            print("---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---")
            
    return {"documents": filtered_docs}       # ๊ด€๋ จ์„ฑ ํ‰๊ฐ€์— ํ•ฉ๊ฒฉํ•œ ๋ฌธ์„œ๋“ค๋งŒ ์ €์žฅ (override)

def transform_query(state: GraphState) -> GraphState:
    """์งˆ๋ฌธ์„ ๊ฐœ์„ ํ•˜๋Š” ํ•จ์ˆ˜"""
    print("--- ์งˆ๋ฌธ ๊ฐœ์„  ---")
    question = state["question"]
    
    # ์งˆ๋ฌธ ์žฌ์ž‘์„ฑ
    rewritten_question = rewrite_question(question)

    # ์ƒ์„ฑ ํšŸ์ˆ˜ ์—…๋ฐ์ดํŠธ
    num_generations = state.get("num_generations", 0)
    num_generations += 1
    return {"question": rewritten_question, "num_generations": num_generations}      # ์žฌ์ž‘์„ฑํ•œ ์งˆ๋ฌธ์„ ์ €์žฅ, ์ƒ์„ฑํšŸ์ˆ˜ ์—…๋ฐ์ดํŠธ 

3. Edge ๊ตฌ์„ฑ

def decide_to_generate(state: GraphState) -> str:
    """๋‹ต๋ณ€ ์ƒ์„ฑ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํ•จ์ˆ˜"""

    num_generations = state.get("num_generations", 0)
    if num_generations > 2:
        print("--- ๊ฒฐ์ •: ์ƒ์„ฑ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ๋‹ต๋ณ€ ์ƒ์„ฑ (-> generate)---")
        return "generate"

    print("--- ํ‰๊ฐ€๋œ ๋ฌธ์„œ ๋ถ„์„ ---")
    filtered_documents = state["documents"]
    
    if not filtered_documents:
        print("--- ๊ฒฐ์ •: ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ์ด ์—†์Œ, ์งˆ๋ฌธ ๊ฐœ์„  ํ•„์š” (-> transform_query)---")
        return "transform_query"
    else:
        print("--- ๊ฒฐ์ •: ๋‹ต๋ณ€ ์ƒ์„ฑ (-> generate)---")
        return "generate"

def grade_generation(state: GraphState) -> str:
    """์ƒ์„ฑ๋œ ๋‹ต๋ณ€์˜ ํ’ˆ์งˆ์„ ํ‰๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜"""

    num_generations = state.get("num_generations", 0)
    if num_generations > 2:
        print("--- ๊ฒฐ์ •: ์ƒ์„ฑ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ์ข…๋ฃŒ (-> END)---")
        return "end"
    
    # 1๋‹จ๊ณ„: ํ™˜๊ฐ ์—ฌ๋ถ€ ํ™•์ธ
    print("--- ํ™˜๊ฐ ์—ฌ๋ถ€ ํ™•์ธ ---")
    question, documents, generation = state["question"], state["documents"], state["generation"]
    
    hallucination_grade = hallucination_grader.invoke(
        {"documents": documents, "generation": generation}
    )

    if hallucination_grade.binary_score == "yes":
        print("--- ๊ฒฐ์ •: No ํ™˜๊ฐ (๋‹ต๋ณ€์ด ์ปจํ…์ŠคํŠธ์— ๊ทผ๊ฑฐํ•จ) ---")

        # 1๋‹จ๊ณ„ ํ†ต๊ณผํ•  ๊ฒฝ์šฐ -> 2๋‹จ๊ณ„: ์งˆ๋ฌธ-๋‹ต๋ณ€ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ 
        print("---์งˆ๋ฌธ-๋‹ต๋ณ€ ๊ด€๋ จ์„ฑ ํ™•์ธ---")
        relevance_grade = answer_grader.invoke({"question": question, "generation": generation})
        if relevance_grade.binary_score == "yes":
            print("--- ๊ฒฐ์ •: ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์ด ์งˆ๋ฌธ์„ ์ž˜ ๋‹ค๋ฃธ (-> END) ---")
            return "useful"
        else:
            print("--- ๊ฒฐ์ •: ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์ด ์งˆ๋ฌธ์„ ์ œ๋Œ€๋กœ ๋‹ค๋ฃจ์ง€ ์•Š์Œ (-> transform_query) ---")
            return "not useful"
    else:
        print("--- ๊ฒฐ์ •: ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์ด ๋ฌธ์„œ์— ๊ทผ๊ฑฐํ•˜์ง€ ์•Š์Œ, ์žฌ์‹œ๋„ ํ•„์š” (-> generate) ---")
        return "not supported"

4. ๊ทธ๋ž˜ํ”„ ์—ฐ๊ฒฐ

from langgraph.graph import StateGraph, START, END
from IPython.display import Image, display

# ์›Œํฌํ”Œ๋กœ์šฐ ๊ทธ๋ž˜ํ”„ ์ดˆ๊ธฐํ™”
builder = StateGraph(GraphState)

# ๋…ธ๋“œ ์ •์˜
builder.add_node("retrieve", retrieve)                # ๋ฌธ์„œ ๊ฒ€์ƒ‰
builder.add_node("grade_documents", grade_documents)  # ๋ฌธ์„œ ํ‰๊ฐ€
builder.add_node("generate", generate)                # ๋‹ต๋ณ€ ์ƒ์„ฑ
builder.add_node("transform_query", transform_query)  # ์งˆ๋ฌธ ๊ฐœ์„ 

# ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ•
builder.add_edge(START, "retrieve")
builder.add_edge("retrieve", "grade_documents")

# ์กฐ๊ฑด๋ถ€ ์—ฃ์ง€ ์ถ”๊ฐ€: ๋ฌธ์„œ ํ‰๊ฐ€ ํ›„ ๊ฒฐ์ •
builder.add_conditional_edges(
    "grade_documents",
    decide_to_generate,
    {
        "transform_query": "transform_query",
        "generate": "generate",
    },
)
builder.add_edge("transform_query", "retrieve")

# ์กฐ๊ฑด๋ถ€ ์—ฃ์ง€ ์ถ”๊ฐ€: ๋‹ต๋ณ€ ์ƒ์„ฑ ํ›„ ํ‰๊ฐ€
builder.add_conditional_edges(
    "generate",
    grade_generation,
    {
        "not supported": "generate",          # ํ™˜๊ฐ์ด ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ -> ๋‹ต๋ณ€์„ ๋‹ค์‹œ ์ƒ์„ฑ 
        "not useful": "transform_query",      # ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์˜ ๊ด€๋ จ์„ฑ์ด ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ -> ์ฟผ๋ฆฌ ๊ฐœ์„ ํ•ด์„œ ๋‹ค์‹œ ๊ฒ€์ƒ‰ 
        "useful": END, 
        "end": END,
    },
)

# ๊ทธ๋ž˜ํ”„ ์ปดํŒŒ์ผ
graph = builder.compile()

# ๊ทธ๋ž˜ํ”„ ์‹œ๊ฐํ™”
display(Image(graph.get_graph().draw_mermaid_png()))

- ์ถœ๋ ฅ


5. ๊ทธ๋ž˜ํ”„ ์‹คํ–‰

inputs = {"question": "์ด ์‹๋‹น์˜ ๋Œ€ํ‘œ ๋ฉ”๋‰ด๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”? ์ฃผ์žฌ๋ฃŒ๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?"}
final_output = graph.invoke(inputs,
                            config={
                                "callbacks": [langfuse_handler], 
                                "tags": ["self-rag", "workflow"]
                                },
                        )

- ์ถœ๋ ฅ

--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---
--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์žˆ์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์žˆ์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์žˆ์Œ---
--- ํ‰๊ฐ€๋œ ๋ฌธ์„œ ๋ถ„์„ ---
--- ๊ฒฐ์ •: ๋‹ต๋ณ€ ์ƒ์„ฑ (-> generate)---
--- ๋‹ต๋ณ€ ์ƒ์„ฑ ---
--- ํ™˜๊ฐ ์—ฌ๋ถ€ ํ™•์ธ ---
--- ๊ฒฐ์ •: No ํ™˜๊ฐ (๋‹ต๋ณ€์ด ์ปจํ…์ŠคํŠธ์— ๊ทผ๊ฑฐํ•จ) ---
---์งˆ๋ฌธ-๋‹ต๋ณ€ ๊ด€๋ จ์„ฑ ํ™•์ธ---
--- ๊ฒฐ์ •: ์ƒ์„ฑ๋œ ๋‹ต๋ณ€์ด ์งˆ๋ฌธ์„ ์ž˜ ๋‹ค๋ฃธ (-> END) ---

๋‹ต๋ณ€ ํ™•์ธ

# ์ตœ์ข… ๋‹ต๋ณ€ 
final_output["generation"]

- ์ถœ๋ ฅ

'์ด ์‹๋‹น์˜ ๋Œ€ํ‘œ ๋ฉ”๋‰ด๋Š” ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์Šคํ…Œ์ดํฌ์ž…๋‹ˆ๋‹ค. ์ฃผ์žฌ๋ฃŒ๋กœ๋Š” ์ตœ์ƒ๊ธ‰ ํ•œ์šฐ ๋“ฑ์‹ฌ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž, ๊ทธ๋ฆด๋“œ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.'

๋‹ต๋ณ€ ํ™•์ธ (๊ด€๋ จ X ์ฟผ๋ฆฌ)

inputs = {"question": "๊น€์น˜์ฐŒ๊ฐœ ๋ฉ”๋‰ด๊ฐ€ ์žˆ๋‚˜์š”?"}
for output in graph.stream(inputs,
                            config={
                                "callbacks": [langfuse_handler],
                                "tags": ["self-rag", "workflow"]
                                },
                        ):
    for key, value in output.items():
        # ๋…ธ๋“œ ์ถœ๋ ฅ
        pprint(f"Node '{key}':")
        pprint(f"Value: {value}", indent=2, width=80, depth=None)
    print("\n----------------------------------------------------------\n")

- ์ถœ๋ ฅ

--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---
"Node 'retrieve':"
("Value: {'documents': [Document(id='d61dfeea-17be-4cec-917b-90325c957d15', "
 "metadata={'menu_name': '๋žœ์น˜ ๋žจ ์ฐน', 'menu_number': 27, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='27. ๋žœ์น˜ ๋žจ ์ฐน\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ33,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„, ํƒ€์ž„, ๋งˆ๋Š˜, ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„๋ฅผ '
 'ํƒ€์ž„๊ณผ ๋งˆ๋Š˜๋กœ ํ–ฅ์„ ๋‚ด์–ด ๋ฏธ๋””์—„์œผ๋กœ ๊ตฌ์›Œ๋‚ธ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค๋กœ ๊นŠ์€ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๊ตฌ์šด ์ง€์ค‘ํ•ด์‹ ์•ผ์ฑ„์™€ ํด๋ Œํƒ€๋ฅผ '
 "๊ณ๋“ค์ž…๋‹ˆ๋‹ค.'), Document(id='f22453dd-3476-4ad9-8898-fb3c5ab02424', "
 "metadata={'menu_name': '์น˜ํ‚จ ์ฝ˜ํ”ผ', 'menu_number': 9, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='9. ์น˜ํ‚จ ์ฝ˜ํ”ผ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ23,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‹ญ๋‹ค๋ฆฌ์‚ด, ํ—ˆ๋ธŒ, ๋งˆ๋Š˜, ์˜ฌ๋ฆฌ๋ธŒ ์˜ค์ผ\\n   โ€ข ์„ค๋ช…: ๋‹ญ๋‹ค๋ฆฌ์‚ด์„ ํ—ˆ๋ธŒ์™€ ๋งˆ๋Š˜์„ ๋„ฃ์€ ์˜ฌ๋ฆฌ๋ธŒ '
 '์˜ค์ผ์— ์ €์˜จ์—์„œ ์žฅ์‹œ๊ฐ„ ์กฐ๋ฆฌํ•œ ํ”„๋ž‘์Šค ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ด‰์ด‰ํ•œ ์œก์งˆ์ด ํŠน์ง•์ด๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž์™€ ์ œ์ฒ  ์ฑ„์†Œ๋ฅผ ๊ณ๋“ค์—ฌ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ '
 "์ œ์ŠคํŠธ๋ฅผ ๋ฟŒ๋ ค ์ƒํผํ•œ ํ–ฅ์„ ๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.'), Document(id='595554b8-a3c3-4370-8389-bc9a253cc3d4', "
 "metadata={'menu_name': '์ƒคํ† ๋ธŒ๋ฆฌ์•™ ์Šคํ…Œ์ดํฌ', 'menu_number': 26, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='26. ์ƒคํ† ๋ธŒ๋ฆฌ์•™ ์Šคํ…Œ์ดํฌ\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ42,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ํ”„๋ฆฌ๋ฏธ์—„ ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ, ํ‘ธ์•„๊ทธ๋ผ, ํŠธ๋Ÿฌํ”Œ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ์ตœ์ƒ๊ธ‰ ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ์— '
 'ํ‘ธ์•„๊ทธ๋ผ๋ฅผ ์˜ฌ๋ฆฌ๊ณ  ํŠธ๋Ÿฌํ”Œ ์†Œ์Šค๋ฅผ ๊ณ๋“ค์ธ ํด๋ž˜์‹ ํ”„๋ Œ์น˜ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฌ์šด ์œก์งˆ๊ณผ ๊นŠ์€ ํ’๋ฏธ๊ฐ€ ํŠน์ง•์ด๋ฉฐ, ๊ทธ๋ฆฐ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค์™€ ๊ฐ์ž '
 "๊ทธ๋ผํƒ•์„ ํ•จ๊ป˜ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.'), Document(id='1042c919-0711-4b2b-9644-aae8632c1d63', "
 "metadata={'menu_name': '๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ', 'menu_number': 5, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='5. ๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ12,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์œ ๊ธฐ๋† ๋ฏน์Šค ๊ทธ๋ฆฐ, ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ, ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ\\n   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ์œ ๊ธฐ๋† '
 '์ฑ„์†Œ๋“ค๋กœ ๊ตฌ์„ฑ๋œ ๊ฑด๊ฐ•ํ•œ ์ƒ๋Ÿฌ๋“œ์ž…๋‹ˆ๋‹ค. ์•„์‚ญํ•œ ์‹๊ฐ์˜ ๋ฏน์Šค ๊ทธ๋ฆฐ์— ๋‹ฌ์ฝคํ•œ ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ์„ ๋”ํ•ด ๋‹ค์–‘ํ•œ ๋ง›๊ณผ ์‹๊ฐ์„ ์ฆ๊ธธ ์ˆ˜ '
 "์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ œ ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ์ด ์ฑ„์†Œ ๋ณธ์—ฐ์˜ ๋ง›์„ ์‚ด๋ ค์ค๋‹ˆ๋‹ค.')]}")

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
--- ํ‰๊ฐ€๋œ ๋ฌธ์„œ ๋ถ„์„ ---
--- ๊ฒฐ์ •: ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ์ด ์—†์Œ, ์งˆ๋ฌธ ๊ฐœ์„  ํ•„์š” (-> transform_query)---
"Node 'grade_documents':"
"Value: {'documents': []}"

----------------------------------------------------------

--- ์งˆ๋ฌธ ๊ฐœ์„  ---
"Node 'transform_query':"
"Value: {'question': '๊น€์น˜์ฐŒ๊ฐœ ๋ฉ”๋‰ด๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?', 'num_generations': 1}"

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---
"Node 'retrieve':"
("Value: {'documents': [Document(id='d61dfeea-17be-4cec-917b-90325c957d15', "
 "metadata={'menu_name': '๋žœ์น˜ ๋žจ ์ฐน', 'menu_number': 27, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='27. ๋žœ์น˜ ๋žจ ์ฐน\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ33,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„, ํƒ€์ž„, ๋งˆ๋Š˜, ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„๋ฅผ '
 'ํƒ€์ž„๊ณผ ๋งˆ๋Š˜๋กœ ํ–ฅ์„ ๋‚ด์–ด ๋ฏธ๋””์—„์œผ๋กœ ๊ตฌ์›Œ๋‚ธ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค๋กœ ๊นŠ์€ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๊ตฌ์šด ์ง€์ค‘ํ•ด์‹ ์•ผ์ฑ„์™€ ํด๋ Œํƒ€๋ฅผ '
 "๊ณ๋“ค์ž…๋‹ˆ๋‹ค.'), Document(id='f22453dd-3476-4ad9-8898-fb3c5ab02424', "
 "metadata={'menu_name': '์น˜ํ‚จ ์ฝ˜ํ”ผ', 'menu_number': 9, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='9. ์น˜ํ‚จ ์ฝ˜ํ”ผ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ23,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‹ญ๋‹ค๋ฆฌ์‚ด, ํ—ˆ๋ธŒ, ๋งˆ๋Š˜, ์˜ฌ๋ฆฌ๋ธŒ ์˜ค์ผ\\n   โ€ข ์„ค๋ช…: ๋‹ญ๋‹ค๋ฆฌ์‚ด์„ ํ—ˆ๋ธŒ์™€ ๋งˆ๋Š˜์„ ๋„ฃ์€ ์˜ฌ๋ฆฌ๋ธŒ '
 '์˜ค์ผ์— ์ €์˜จ์—์„œ ์žฅ์‹œ๊ฐ„ ์กฐ๋ฆฌํ•œ ํ”„๋ž‘์Šค ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ด‰์ด‰ํ•œ ์œก์งˆ์ด ํŠน์ง•์ด๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž์™€ ์ œ์ฒ  ์ฑ„์†Œ๋ฅผ ๊ณ๋“ค์—ฌ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ '
 "์ œ์ŠคํŠธ๋ฅผ ๋ฟŒ๋ ค ์ƒํผํ•œ ํ–ฅ์„ ๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.'), Document(id='595554b8-a3c3-4370-8389-bc9a253cc3d4', "
 "metadata={'menu_name': '์ƒคํ† ๋ธŒ๋ฆฌ์•™ ์Šคํ…Œ์ดํฌ', 'menu_number': 26, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='26. ์ƒคํ† ๋ธŒ๋ฆฌ์•™ ์Šคํ…Œ์ดํฌ\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ42,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ํ”„๋ฆฌ๋ฏธ์—„ ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ, ํ‘ธ์•„๊ทธ๋ผ, ํŠธ๋Ÿฌํ”Œ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ์ตœ์ƒ๊ธ‰ ์•ˆ์‹ฌ ์Šคํ…Œ์ดํฌ์— '
 'ํ‘ธ์•„๊ทธ๋ผ๋ฅผ ์˜ฌ๋ฆฌ๊ณ  ํŠธ๋Ÿฌํ”Œ ์†Œ์Šค๋ฅผ ๊ณ๋“ค์ธ ํด๋ž˜์‹ ํ”„๋ Œ์น˜ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฌ์šด ์œก์งˆ๊ณผ ๊นŠ์€ ํ’๋ฏธ๊ฐ€ ํŠน์ง•์ด๋ฉฐ, ๊ทธ๋ฆฐ ์•„์ŠคํŒŒ๋ผ๊ฑฐ์Šค์™€ ๊ฐ์ž '
 "๊ทธ๋ผํƒ•์„ ํ•จ๊ป˜ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.'), Document(id='1042c919-0711-4b2b-9644-aae8632c1d63', "
 "metadata={'menu_name': '๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ', 'menu_number': 5, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='5. ๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ12,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์œ ๊ธฐ๋† ๋ฏน์Šค ๊ทธ๋ฆฐ, ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ, ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ\\n   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ์œ ๊ธฐ๋† '
 '์ฑ„์†Œ๋“ค๋กœ ๊ตฌ์„ฑ๋œ ๊ฑด๊ฐ•ํ•œ ์ƒ๋Ÿฌ๋“œ์ž…๋‹ˆ๋‹ค. ์•„์‚ญํ•œ ์‹๊ฐ์˜ ๋ฏน์Šค ๊ทธ๋ฆฐ์— ๋‹ฌ์ฝคํ•œ ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ์„ ๋”ํ•ด ๋‹ค์–‘ํ•œ ๋ง›๊ณผ ์‹๊ฐ์„ ์ฆ๊ธธ ์ˆ˜ '
 "์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ œ ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ์ด ์ฑ„์†Œ ๋ณธ์—ฐ์˜ ๋ง›์„ ์‚ด๋ ค์ค๋‹ˆ๋‹ค.')]}")

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
--- ํ‰๊ฐ€๋œ ๋ฌธ์„œ ๋ถ„์„ ---
--- ๊ฒฐ์ •: ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ์ด ์—†์Œ, ์งˆ๋ฌธ ๊ฐœ์„  ํ•„์š” (-> transform_query)---
"Node 'grade_documents':"
"Value: {'documents': []}"

----------------------------------------------------------

--- ์งˆ๋ฌธ ๊ฐœ์„  ---
"Node 'transform_query':"
"Value: {'question': '๊น€์น˜์ฐŒ๊ฐœ๊ฐ€ ์žˆ๋‚˜์š”?', 'num_generations': 2}"

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---
"Node 'retrieve':"
("Value: {'documents': [Document(id='d61dfeea-17be-4cec-917b-90325c957d15', "
 "metadata={'menu_name': '๋žœ์น˜ ๋žจ ์ฐน', 'menu_number': 27, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='27. ๋žœ์น˜ ๋žจ ์ฐน\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ33,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„, ํƒ€์ž„, ๋งˆ๋Š˜, ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„๋ฅผ '
 'ํƒ€์ž„๊ณผ ๋งˆ๋Š˜๋กœ ํ–ฅ์„ ๋‚ด์–ด ๋ฏธ๋””์—„์œผ๋กœ ๊ตฌ์›Œ๋‚ธ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค๋กœ ๊นŠ์€ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๊ตฌ์šด ์ง€์ค‘ํ•ด์‹ ์•ผ์ฑ„์™€ ํด๋ Œํƒ€๋ฅผ '
 "๊ณ๋“ค์ž…๋‹ˆ๋‹ค.'), Document(id='37b2ed8e-72e8-4cc3-a841-504f93569a72', "
 "metadata={'menu_name': '๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์น˜์ฆˆ์ผ€์ดํฌ', 'menu_number': 29, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='29. ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์น˜์ฆˆ์ผ€์ดํฌ\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ10,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ํฌ๋ฆผ์น˜์ฆˆ, ์œ ๊ธฐ๋† ๋ธ”๋ฃจ๋ฒ ๋ฆฌ, ๊ทธ๋ผํ•จ ํฌ๋ž˜์ปค\\n    โ€ข ์„ค๋ช…: ๋ถ€๋“œ๋Ÿฌ์šด ํฌ๋ฆผ์น˜์ฆˆ ๋ฌด์Šค์™€ '
 '์‹ ์„ ํ•œ ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์ฝคํฌํŠธ๋ฅผ ์ธต์ธต์ด ์Œ“์•„ ๋งŒ๋“  ๋””์ €ํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐ”์‚ญํ•œ ๊ทธ๋ผํ•จ ํฌ๋ž˜์ปค ๋ฒ ์ด์Šค์™€ ์ˆ˜์ œ ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์†Œ์Šค๊ฐ€ ์กฐํ™”๋ฅผ ์ด๋ฃจ๋ฉฐ, ๋ฏผํŠธ ์žŽ์œผ๋กœ '
 "์žฅ์‹ํ–ˆ์Šต๋‹ˆ๋‹ค.'), Document(id='1042c919-0711-4b2b-9644-aae8632c1d63', "
 "metadata={'menu_name': '๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ', 'menu_number': 5, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='5. ๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ12,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์œ ๊ธฐ๋† ๋ฏน์Šค ๊ทธ๋ฆฐ, ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ, ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ\\n   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ์œ ๊ธฐ๋† '
 '์ฑ„์†Œ๋“ค๋กœ ๊ตฌ์„ฑ๋œ ๊ฑด๊ฐ•ํ•œ ์ƒ๋Ÿฌ๋“œ์ž…๋‹ˆ๋‹ค. ์•„์‚ญํ•œ ์‹๊ฐ์˜ ๋ฏน์Šค ๊ทธ๋ฆฐ์— ๋‹ฌ์ฝคํ•œ ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ์„ ๋”ํ•ด ๋‹ค์–‘ํ•œ ๋ง›๊ณผ ์‹๊ฐ์„ ์ฆ๊ธธ ์ˆ˜ '
 "์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ œ ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ์ด ์ฑ„์†Œ ๋ณธ์—ฐ์˜ ๋ง›์„ ์‚ด๋ ค์ค๋‹ˆ๋‹ค.'), "
 "Document(id='f22453dd-3476-4ad9-8898-fb3c5ab02424', metadata={'menu_name': "
 "'์น˜ํ‚จ ์ฝ˜ํ”ผ', 'menu_number': 9, 'source': './data/restaurant_menu.txt'}, "
 "page_content='9. ์น˜ํ‚จ ์ฝ˜ํ”ผ\\n   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ23,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‹ญ๋‹ค๋ฆฌ์‚ด, ํ—ˆ๋ธŒ, ๋งˆ๋Š˜, ์˜ฌ๋ฆฌ๋ธŒ "
 '์˜ค์ผ\\n   โ€ข ์„ค๋ช…: ๋‹ญ๋‹ค๋ฆฌ์‚ด์„ ํ—ˆ๋ธŒ์™€ ๋งˆ๋Š˜์„ ๋„ฃ์€ ์˜ฌ๋ฆฌ๋ธŒ ์˜ค์ผ์— ์ €์˜จ์—์„œ ์žฅ์‹œ๊ฐ„ ์กฐ๋ฆฌํ•œ ํ”„๋ž‘์Šค ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ด‰์ด‰ํ•œ ์œก์งˆ์ด '
 "ํŠน์ง•์ด๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž์™€ ์ œ์ฒ  ์ฑ„์†Œ๋ฅผ ๊ณ๋“ค์—ฌ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ ์ œ์ŠคํŠธ๋ฅผ ๋ฟŒ๋ ค ์ƒํผํ•œ ํ–ฅ์„ ๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.')]}")

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
--- ํ‰๊ฐ€๋œ ๋ฌธ์„œ ๋ถ„์„ ---
--- ๊ฒฐ์ •: ๋ชจ๋“  ๋ฌธ์„œ๊ฐ€ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ์ด ์—†์Œ, ์งˆ๋ฌธ ๊ฐœ์„  ํ•„์š” (-> transform_query)---
"Node 'grade_documents':"
"Value: {'documents': []}"

----------------------------------------------------------

--- ์งˆ๋ฌธ ๊ฐœ์„  ---
"Node 'transform_query':"
"Value: {'question': '๊น€์น˜์ฐŒ๊ฐœ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?', 'num_generations': 3}"

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ฒ€์ƒ‰ ---
"Node 'retrieve':"
("Value: {'documents': [Document(id='d61dfeea-17be-4cec-917b-90325c957d15', "
 "metadata={'menu_name': '๋žœ์น˜ ๋žจ ์ฐน', 'menu_number': 27, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='27. ๋žœ์น˜ ๋žจ ์ฐน\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ33,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„, ํƒ€์ž„, ๋งˆ๋Š˜, ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค\\n    โ€ข ์„ค๋ช…: ๋‰ด์งˆ๋žœ๋“œ์‚ฐ ์–‘๊ฐˆ๋น„๋ฅผ '
 'ํƒ€์ž„๊ณผ ๋งˆ๋Š˜๋กœ ํ–ฅ์„ ๋‚ด์–ด ๋ฏธ๋””์—„์œผ๋กœ ๊ตฌ์›Œ๋‚ธ ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ ˆ๋“œ์™€์ธ ์†Œ์Šค๋กœ ๊นŠ์€ ๋ง›์„ ๋”ํ–ˆ์œผ๋ฉฐ, ๊ตฌ์šด ์ง€์ค‘ํ•ด์‹ ์•ผ์ฑ„์™€ ํด๋ Œํƒ€๋ฅผ '
 "๊ณ๋“ค์ž…๋‹ˆ๋‹ค.'), Document(id='37b2ed8e-72e8-4cc3-a841-504f93569a72', "
 "metadata={'menu_name': '๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์น˜์ฆˆ์ผ€์ดํฌ', 'menu_number': 29, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='29. ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์น˜์ฆˆ์ผ€์ดํฌ\\n    โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ10,000\\n    โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ํฌ๋ฆผ์น˜์ฆˆ, ์œ ๊ธฐ๋† ๋ธ”๋ฃจ๋ฒ ๋ฆฌ, ๊ทธ๋ผํ•จ ํฌ๋ž˜์ปค\\n    โ€ข ์„ค๋ช…: ๋ถ€๋“œ๋Ÿฌ์šด ํฌ๋ฆผ์น˜์ฆˆ ๋ฌด์Šค์™€ '
 '์‹ ์„ ํ•œ ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์ฝคํฌํŠธ๋ฅผ ์ธต์ธต์ด ์Œ“์•„ ๋งŒ๋“  ๋””์ €ํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐ”์‚ญํ•œ ๊ทธ๋ผํ•จ ํฌ๋ž˜์ปค ๋ฒ ์ด์Šค์™€ ์ˆ˜์ œ ๋ธ”๋ฃจ๋ฒ ๋ฆฌ ์†Œ์Šค๊ฐ€ ์กฐํ™”๋ฅผ ์ด๋ฃจ๋ฉฐ, ๋ฏผํŠธ ์žŽ์œผ๋กœ '
 "์žฅ์‹ํ–ˆ์Šต๋‹ˆ๋‹ค.'), Document(id='1042c919-0711-4b2b-9644-aae8632c1d63', "
 "metadata={'menu_name': '๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ', 'menu_number': 5, 'source': "
 "'./data/restaurant_menu.txt'}, page_content='5. ๊ฐ€๋“  ์ƒ๋Ÿฌ๋“œ\\n   โ€ข ๊ฐ€๊ฒฉ: "
 'โ‚ฉ12,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ์œ ๊ธฐ๋† ๋ฏน์Šค ๊ทธ๋ฆฐ, ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ, ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ\\n   โ€ข ์„ค๋ช…: ์‹ ์„ ํ•œ ์œ ๊ธฐ๋† '
 '์ฑ„์†Œ๋“ค๋กœ ๊ตฌ์„ฑ๋œ ๊ฑด๊ฐ•ํ•œ ์ƒ๋Ÿฌ๋“œ์ž…๋‹ˆ๋‹ค. ์•„์‚ญํ•œ ์‹๊ฐ์˜ ๋ฏน์Šค ๊ทธ๋ฆฐ์— ๋‹ฌ์ฝคํ•œ ์ฒด๋ฆฌ ํ† ๋งˆํ† , ์˜ค์ด, ๋‹น๊ทผ์„ ๋”ํ•ด ๋‹ค์–‘ํ•œ ๋ง›๊ณผ ์‹๊ฐ์„ ์ฆ๊ธธ ์ˆ˜ '
 "์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ œ ๋ฐœ์‚ฌ๋ฏน ๋“œ๋ ˆ์‹ฑ์ด ์ฑ„์†Œ ๋ณธ์—ฐ์˜ ๋ง›์„ ์‚ด๋ ค์ค๋‹ˆ๋‹ค.'), "
 "Document(id='f22453dd-3476-4ad9-8898-fb3c5ab02424', metadata={'menu_name': "
 "'์น˜ํ‚จ ์ฝ˜ํ”ผ', 'menu_number': 9, 'source': './data/restaurant_menu.txt'}, "
 "page_content='9. ์น˜ํ‚จ ์ฝ˜ํ”ผ\\n   โ€ข ๊ฐ€๊ฒฉ: โ‚ฉ23,000\\n   โ€ข ์ฃผ์š” ์‹์žฌ๋ฃŒ: ๋‹ญ๋‹ค๋ฆฌ์‚ด, ํ—ˆ๋ธŒ, ๋งˆ๋Š˜, ์˜ฌ๋ฆฌ๋ธŒ "
 '์˜ค์ผ\\n   โ€ข ์„ค๋ช…: ๋‹ญ๋‹ค๋ฆฌ์‚ด์„ ํ—ˆ๋ธŒ์™€ ๋งˆ๋Š˜์„ ๋„ฃ์€ ์˜ฌ๋ฆฌ๋ธŒ ์˜ค์ผ์— ์ €์˜จ์—์„œ ์žฅ์‹œ๊ฐ„ ์กฐ๋ฆฌํ•œ ํ”„๋ž‘์Šค ์š”๋ฆฌ์ž…๋‹ˆ๋‹ค. ๋ถ€๋“œ๋Ÿฝ๊ณ  ์ด‰์ด‰ํ•œ ์œก์งˆ์ด '
 "ํŠน์ง•์ด๋ฉฐ, ๋กœ์ฆˆ๋ฉ”๋ฆฌ ๊ฐ์ž์™€ ์ œ์ฒ  ์ฑ„์†Œ๋ฅผ ๊ณ๋“ค์—ฌ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ๋ชฌ ์ œ์ŠคํŠธ๋ฅผ ๋ฟŒ๋ ค ์ƒํผํ•œ ํ–ฅ์„ ๋”ํ–ˆ์Šต๋‹ˆ๋‹ค.')]}")

----------------------------------------------------------

--- ๋ฌธ์„œ ๊ด€๋ จ์„ฑ ํ‰๊ฐ€ ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
---๋ฌธ์„œ ๊ด€๋ จ์„ฑ: ์—†์Œ---
--- ๊ฒฐ์ •: ์ƒ์„ฑ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ๋‹ต๋ณ€ ์ƒ์„ฑ (-> generate)---
"Node 'grade_documents':"
"Value: {'documents': []}"

----------------------------------------------------------

--- ๋‹ต๋ณ€ ์ƒ์„ฑ ---
--- ๊ฒฐ์ •: ์ƒ์„ฑ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ์ข…๋ฃŒ (-> END)---
"Node 'generate':"
"Value: {'generation': '๋„ค, ๊น€์น˜์ฐŒ๊ฐœ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.', 'num_generations': 4}"

----------------------------------------------------------

์ตœ์ข… ๋‹ต๋ณ€

# ์ตœ์ข… ๋‹ต๋ณ€
print(value["generation"])

- ์ถœ๋ ฅ

๋„ค, ๊น€์น˜์ฐŒ๊ฐœ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€