Skip to main content

FLARE (Forward-Looking Active REtrieval augmented generation)

FLARE는 LLM이 텍스트를 생성하는 도중, 불확실한 부분이 감지되면 능동적으로 검색을 수행하는 RAG 아키텍처입니다. 기존 RAG가 생성 전에 한 번만 검색하는 반면, FLARE는 생성 과정에서 필요한 시점에 반복적으로 검색하여 환각을 줄입니다.

핵심 아이디어

기존 RAG는 사용자 질문을 받으면 한 번 검색한 뒤 답변을 생성합니다. 그러나 긴 답변을 생성하는 과정에서 모델의 확신도가 떨어지는 구간이 발생할 수 있고, 이때 추가 검색 없이 생성을 이어가면 환각이 발생합니다.
  • 생성 전 단 한 번의 검색으로 모든 정보를 확보해야 함
  • 긴 답변 생성 시 후반부로 갈수록 컨텍스트 부족
  • 답변 중간에 새로운 주제가 등장해도 추가 검색 불가
  • 검색 시점이 고정되어 있어 유연성 부족

동작 방식

FLARE의 핵심은 생성 → 확신도 확인 → 조건부 검색 → 재생성 사이클입니다.
1

문장 단위 생성

LLM이 다음 문장을 생성합니다. 이때 각 토큰의 생성 확률(logprob)을 함께 기록합니다.
2

확신도 판단

생성된 문장의 토큰 확률을 확인합니다. 모든 토큰의 확률이 임계값 이상이면 해당 문장을 확정합니다. (원 논문에서는 토큰별 logprob을 사용하며, 임계값은 실험 설정에 따라 다릅니다.)
3

능동적 검색

저확률 토큰이 포함된 문장은 모델이 불확실하다는 신호입니다. 이 임시 문장을 검색 쿼리로 사용하여 관련 문서를 검색합니다.
4

문장 재생성

검색된 문서를 컨텍스트에 추가한 뒤, 불확실했던 문장을 다시 생성합니다. 이 과정을 답변이 완성될 때까지 반복합니다.

LangGraph 구현

상태 정의

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

class FlareState(TypedDict):
    question: str
    partial_answer: str          # 현재까지 생성된 답변
    current_sentence: str        # 현재 생성 중인 문장
    is_low_confidence: bool      # 저확률 토큰 포함 여부
    documents: List[Document]    # 검색된 문서
    generation: str              # 최종 답변
    iteration: int               # 반복 횟수

노드 함수

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

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

def generate_next_sentence(state: FlareState) -> FlareState:
    """다음 문장을 생성합니다."""
    question = state["question"]
    partial = state.get("partial_answer", "")
    docs = state.get("documents", [])
    iteration = state.get("iteration", 0)

    context = "\n\n".join(doc.page_content for doc in docs) if docs else ""

    prompt = ChatPromptTemplate.from_messages([
        ("system",
         "질문에 대한 답변을 이어서 작성하세요. "
         "정확히 1~2문장만 생성하세요.\n\n"
         "컨텍스트:\n{context}"),
        ("human",
         "질문: {question}\n\n"
         "현재까지의 답변: {partial}\n\n"
         "다음 1~2문장을 이어서 작성하세요. "
         "더 이상 쓸 내용이 없으면 [완료]를 출력하세요."),
    ])

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

    return {
        "current_sentence": sentence.strip(),
        "iteration": iteration + 1,
    }

조건부 엣지

from typing import Literal

def route_by_confidence(state: FlareState) -> Literal["retrieve", "confirm"]:
    """확신도에 따라 검색 또는 확정으로 분기합니다."""
    if state.get("is_low_confidence", False):
        return "retrieve"
    return "confirm"

def check_completion(state: FlareState) -> Literal["continue", "end"]:
    """답변 완료 여부를 확인합니다."""
    sentence = state.get("current_sentence", "")
    iteration = state.get("iteration", 0)

    if "[완료]" in sentence or iteration >= 10:
        return "end"
    return "continue"

그래프 구성

from langgraph.graph import StateGraph, START, END

workflow = StateGraph(FlareState)

# 노드 추가
workflow.add_node("generate_sentence", generate_next_sentence)
workflow.add_node("check_confidence", check_confidence)
workflow.add_node("active_retrieve", active_retrieve)
workflow.add_node("regenerate", regenerate_sentence)
workflow.add_node("confirm", confirm_sentence)

# 엣지 연결
workflow.add_edge(START, "generate_sentence")
workflow.add_edge("generate_sentence", "check_confidence")

workflow.add_conditional_edges("check_confidence", route_by_confidence, {
    "retrieve": "active_retrieve",
    "confirm": "confirm",
})

workflow.add_edge("active_retrieve", "regenerate")
workflow.add_edge("regenerate", "confirm")

workflow.add_conditional_edges("confirm", check_completion, {
    "continue": "generate_sentence",
    "end": END,
})

app = workflow.compile()

실행

result = app.invoke({
    "question": "트랜스포머 아키텍처의 핵심 메커니즘과 발전 과정을 설명해줘",
    "partial_answer": "",
    "iteration": 0,
})
print(result["generation"])
FLARE는 문장마다 확신도를 평가하고 필요 시 검색 + 재생성을 수행하므로, LLM 호출 횟수가 기존 RAG 대비 크게 증가합니다. iteration 제한으로 무한 루프를 방지하세요. 위 예제에서는 최대 10회로 제한합니다.

Self-RAG와의 비교

항목Self-RAGFLARE
검색 결정 시점생성 전 (Retrieve 토큰)생성 중 (토큰 확률)
불확실성 감지학습된 Reflection Token토큰 생성 확률 (logprob)
검색 쿼리원본 질문생성 중인 문장 (forward-looking)
평가 대상생성된 전체 답변문장 단위
재생성 단위전체 답변불확실한 문장만
추가 학습필요 (Reflection Token)불필요 (추론 시 적용)
FLARE는 추가 파인튜닝 없이 기존 LLM의 토큰 확률만으로 동작합니다. 단, OpenAI API 등 일부 API에서는 logprob 접근이 제한되므로, 위 예제처럼 LLM 기반 자기 평가로 대체할 수 있습니다.

적용 시나리오

보고서, 에세이, 기술 문서 등 여러 문단에 걸친 긴 답변을 생성할 때, 후반부의 환각을 방지합니다.
“A는 B에 영향을 미쳤고, B는 C를 발생시켰는데, C의 결과는?” 같은 연쇄적 추론이 필요한 질문에서 각 단계마다 필요한 정보를 검색합니다.
답변 생성 중 시간에 민감한 정보(날짜, 수치 등)가 등장할 때 실시간으로 검색하여 정확한 데이터를 반영합니다.

참고 논문

논문학회링크
Active Retrieval Augmented Generation (Jiang et al., 2023)EMNLP 2023arXiv 2305.06983