Skip to main content
생성은 RAG 파이프라인의 마지막 단계로, 검색된 문서를 컨텍스트로 활용하여 LLM이 답변을 만드는 과정입니다. 프롬프트 설계와 컨텍스트 관리가 답변 품질을 결정합니다.

생성 파이프라인

프롬프트 구성

RAG 프롬프트는 세 가지 요소로 구성됩니다: 시스템 프롬프트, 컨텍스트, 사용자 질문.

기본 프롬프트

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", """당신은 주어진 컨텍스트를 기반으로 질문에 답변하는 AI 어시스턴트입니다.

규칙:
- 컨텍스트에 포함된 정보만 사용하여 답변하세요
- 컨텍스트에 답변이 없으면 "제공된 문서에서 관련 정보를 찾을 수 없습니다"라고 답변하세요
- 답변에 출처를 명시하세요

컨텍스트:
{context}"""),
    ("human", "{question}"),
])

출처 인용 프롬프트

prompt_with_citation = ChatPromptTemplate.from_messages([
    ("system", """주어진 컨텍스트를 참고하여 질문에 답변하세요.

규칙:
- 각 주장 뒤에 [출처: 문서명] 형식으로 출처를 표기하세요
- 여러 문서의 정보를 종합할 경우 모든 출처를 명시하세요
- 컨텍스트에 없는 정보는 사용하지 마세요

컨텍스트:
{context}"""),
    ("human", "{question}"),
])

구조화된 답변 프롬프트

from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

class RAGAnswer(BaseModel):
    answer: str = Field(description="질문에 대한 답변")
    sources: List[str] = Field(description="답변에 사용된 출처 목록")
    confidence: str = Field(description="답변 신뢰도: high, medium, low")

parser = PydanticOutputParser(pydantic_object=RAGAnswer)

prompt_structured = ChatPromptTemplate.from_messages([
    ("system", """컨텍스트를 기반으로 질문에 답변하세요.

{format_instructions}

컨텍스트:
{context}"""),
    ("human", "{question}"),
]).partial(format_instructions=parser.get_format_instructions())

컨텍스트 윈도우 관리

LLM의 컨텍스트 윈도우 크기는 제한적입니다. 검색된 문서를 효율적으로 구성해야 합니다.

컨텍스트 크기 계산

모델컨텍스트 윈도우권장 컨텍스트 비율
GPT-4o-mini128K tokens60~70%
GPT-4o128K tokens60~70%
Claude 3.5 Sonnet200K tokens60~70%
Llama 3.1 8B128K tokens50~60%
import tiktoken

def count_tokens(text: str, model: str = "gpt-4o-mini") -> int:
    """텍스트의 토큰 수를 계산합니다."""
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

def build_context(documents, max_tokens: int = 3000, model: str = "gpt-4o-mini"):
    """토큰 제한 내에서 컨텍스트를 구성합니다."""
    context_parts = []
    total_tokens = 0

    for i, doc in enumerate(documents):
        doc_text = f"[문서 {i+1}] {doc.page_content}"
        doc_tokens = count_tokens(doc_text, model)

        if total_tokens + doc_tokens > max_tokens:
            break

        context_parts.append(doc_text)
        total_tokens += doc_tokens

    return "\n\n".join(context_parts)

컨텍스트 압축

검색된 문서에서 질문과 관련된 부분만 추출하여 컨텍스트를 압축합니다.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain.chat_models import init_chat_model

llm = init_chat_model("gpt-4o-mini", temperature=0)
compressor = LLMChainExtractor.from_llm(llm)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=retriever,
)

# 압축된 결과 검색
compressed_docs = compression_retriever.invoke("RAG의 생성 단계란?")

출처 인용 (Citation)

사용자가 답변의 근거를 확인할 수 있도록 출처를 명시합니다.
def format_context_with_sources(documents):
    """출처 정보가 포함된 컨텍스트를 구성합니다."""
    formatted = []
    for i, doc in enumerate(documents, 1):
        source = doc.metadata.get("source", "unknown")
        page = doc.metadata.get("page", "")
        source_info = f"{source}" + (f", p.{page}" if page else "")
        formatted.append(f"[{i}] (출처: {source_info})\n{doc.page_content}")
    return "\n\n---\n\n".join(formatted)

def generate_with_citations(question, documents, llm):
    """출처가 포함된 답변을 생성합니다."""
    context = format_context_with_sources(documents)

    prompt = ChatPromptTemplate.from_messages([
        ("system", """컨텍스트의 각 문서에는 번호가 부여되어 있습니다.
답변 시 해당 정보의 출처를 [1], [2] 형식으로 인라인 인용하세요.

컨텍스트:
{context}"""),
        ("human", "{question}"),
    ])

    chain = prompt | llm | StrOutputParser()
    return chain.invoke({"context": context, "question": question})

스트리밍 생성

긴 답변을 생성할 때 스트리밍으로 실시간 출력합니다.
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser

llm = init_chat_model("gpt-4o-mini", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "컨텍스트를 참고하여 답변하세요.\n\n{context}"),
    ("human", "{question}"),
])

chain = prompt | llm | StrOutputParser()

# 스트리밍 출력
for chunk in chain.stream({"context": context, "question": question}):
    print(chunk, end="", flush=True)

LangGraph 생성 노드

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = init_chat_model("gpt-4o-mini", temperature=0)

def generate(state: GraphState) -> GraphState:
    """검색된 문서를 기반으로 답변을 생성합니다."""
    question = state["question"]
    documents = state["documents"]

    # 컨텍스트 구성
    context = "\n\n".join(
        f"[{i+1}] {doc.page_content}"
        for i, doc in enumerate(documents)
    )

    prompt = ChatPromptTemplate.from_messages([
        ("system", """다음 컨텍스트를 참고하여 질문에 답변하세요.
컨텍스트에 없는 내용은 답변하지 마세요.

컨텍스트:
{context}"""),
        ("human", "{question}"),
    ])

    chain = prompt | llm | StrOutputParser()
    generation = chain.invoke({"context": context, "question": question})
    return {"generation": generation}

생성 품질 체크리스트

시스템 프롬프트에서 “컨텍스트에 없는 정보는 사용하지 말 것”을 명시합니다. temperature를 0에 가깝게 설정하면 환각을 줄일 수 있습니다.
사용자가 답변의 근거를 확인할 수 있도록 인라인 인용이나 출처 목록을 포함합니다.
불필요한 내용은 제거하고, 질문과 관련된 핵심 정보만 컨텍스트에 포함합니다.
검색 결과가 부족하거나 질문과 무관할 때, “알 수 없습니다”라고 정직하게 답변하도록 설계합니다.