티스토리 뷰

반응형

https://www.arxiv.org/abs/2407.16833

 

RAG vs LC의 결과를 보여주는 논문이다.

 

LC는 확실히 성능면에서는 뛰어나지만, Token 값이 비싸고, RAG는 LC에 비해 한참 성능이 떨어진다.

 

그래서 해당 논문은 둘을 합친 Self-Route라는 기법을 제시했다.

 

개념은 간단하다. RAG가 retriever를 통해 unaswerable이라는 결론을 내놓으면, LC로 가고, 아니면 그냥 End로 끝낸다.

이걸 어떻게 구현하냐면, LangGraph로 구현하면 가능하다.

 

코드를 보기에 앞서서 그래프로 간략하게 구현해보면 다음과 같다.

요지는 retriever를 다르게 쓰는 것이다. llm_answer로 바로 줄 수도 있지만, 그렇게 되면 Unanswerable이 지속적으로 나올 수 있으므로 대답하는 속도가 느려질 수 있다.

 

RAG로서는 위와 같은 Graph가 제일 이상적이고, 결국 use_full_context에서 answer를 하여 최종적으로 대답하게 만드는 게 깔끔하다.

 

세부 코드를 보자면 다음과 같다. 이곳 에서 볼 수 있다.

 

retriever는 똑같으므로, Self_route의 핵심적인 건 이렇다.

 

def generate_answer(state: GraphState) -> GraphState:
    template = """
    다음 context를 이용해서 질문에 답하세요.
    
    {context}
    
    
    질문 : {question}
    
    주어진 질문에만 답변하세요. 문장으로 답변하세요. 답변에 질문의 주어를 써주세요.
    만약 주어진 context에서 답변할 수 없다면, 답을 생성해내지 말고 unanswerable이라고 답하세요.
    
    답변:
    """

처음에 답변에서 답을 못한다면 unsanswerable이라고 만들도록 유도한다.

 

그런 다음에는 해당하는 분기를 찾는다.

def is_answerable(state: GraphState) -> tuple[str, GraphState]:
    if state["answer"].lower() == "unanswerable":
        return "Unanswerable", state
    else:
        return "Answerable", state
        
 # Answerable 여부에 따른 조건부 엣지 추가
workflow.add_conditional_edges(
    "llm_answer",
    lambda x: is_answerable(x)[0],  # 튜플의 첫 번째 요소만 사용
    {
        "Answerable": END,
        "Unanswerable": "full_retrieve"
    }
)

 

자연스럽게 Answerable 하다면 END가 되고, 안된다면 full_retrieve를 해서 다시 한 번 질문한다.

이 방식이 바로 Self-Route. 구현도 쉽고 성능도 아주 좋아진다.

 

Agentic Workflow를 할 수 있는 아주 좋은 방식이라 많은 도움이 된 논문이다. 다들 이해해봤다면 LangGraph로 한번씩 구현해보도록 하자.

반응형