스터디라이프/패스트캠퍼스 환급 챌린지

패스트캠퍼스 환급챌린지 4일차: 테디노트의 RAG 비법노트 : 랭체인을 활용한 GPT부터 로컬 모델까지의 RAG 가이드 강의 후기

도토리묵사발 2025. 4. 4. 22:23
728x90

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다. (https://abit.ly/lisbva)

학습 인증샷

공부시작

공부종료

수강인증

학습인증

학습 후기

Example Selector

필요한 이유?

저번 시간에 FewShotPromptTemplate를 사용해서 LLM에게 답변으로 받고싶은 예제들을 여러 개 주고, 새로운 답변을 그에 맞추어서 답하게 하는 방법을 배웠다. 그런데 이런 방식으로는 매 쿼리마다 여러 예제들이 LLM에 입력으로 투입되고 과금되는 문제가 있다. 그런데 우리가 보통 FewShotPromptTemplate에 주는 예제들은 꽤 양이 방대한 경우가 많기 때문에 이런식으로 쓰는건 현실적으로 문제가 있다.

저번 시간에 만들어본 쿼리의 사용량을 추적해보니 다음과 같이 나왔다.

무려 입력 토큰으로만 1400토큰이고 $0.014의 비용이 발생했고 한 번의 요청에 이런 비용이 발생한다면 서비스를 유지할 수 없을 것이다.. ExampleSelector는 이런 경우에 사용한다고 한다.

하는 일

Langchain 문서를 찾아보니 example selector가 하는 일은 다음과 같다.

Example Selector는 프롬프트에 포함시킬 예제들을 선택하는 논리를 구현하는데 사용된다.

BaseExampleSelector라는 녀석을 인터페이스로 상속받아서 커스텀 셀렉터를 만들 수도 있는거 같다. 인터페이스에는 add_exampleselect_examples 메서드를 구현하면 상속 가능하게 되어있다.

랭체인에 이미 구현되어있는 셀렉터들은 다음과 같다.

  • example_selectors.length_based.LengthBasedExampleSelector: 길이 기반 예제 선택
  • example_selectors.semantic_similarity.MaxMarginalRelevanceExampleSelector: 최대 마지널 유사도 기반 선택
  • example_selectors.semantic_similarity.SemanticSimilarityExampleSelector: 시맨틱 유사도 기반 선택

각자 하는 일은 어떤 예제를 선택하는거고, 차이점은 무엇을 기준으로 선택하는가에 달려있다.

SemanticSimilarityExampleSelector

example_selector = SemanticSimilarityExampleSelector.from_examples(
    # 여기에는 선택 가능한 예시 목록이 있습니다.
    examples,
    # 여기에는 의미적 유사성을 측정하는 데 사용되는 임베딩을 생성하는 임베딩 클래스가 있습니다.
    OpenAIEmbeddings(),
    # 여기에는 임베딩을 저장하고 유사성 검색을 수행하는 데 사용되는 VectorStore 클래스가 있습니다.
    Chroma,
    # 이것은 생성할 예시의 수입니다.
    k=1,
)

이녀석은 시맨틱 유사도를 통해 선택하는데, 시맨틱 유사도를 측정하기 위해서는 기준이 되는 임베딩 클래스가 있어야 하고, 해당 임베딩들을 저장하는 벡터스토어가 있어야 한다. 여기서 임베딩이란 어떤 텍스트를 벡터로 변환해주는 것을 의미한다. 잘 학습된 임베딩을 사용하면 서로 의미론적으로 비슷한 텍스트는 벡터스페이스에서 가까운 거리를 갖게 될 것이다. (이 거리를 기반으로 유사도를 측정한다.) 위의 코드에서 마지막에 k 값이 1로 지정되었기 때문에, 셀렉터는 받아온 입력과 가장 유사도가 높은 예제 1개를 선택하게 된다.

어제 주어진 예제는 역사속 인물 두 사람이 대화를 나누는 내용이었고, 각각 이순신-유관순, 도요토미 히데요시 - 아돌프 히틀러 2쌍의 예제를 만들었었고 이를 셀렉터에 임베딩시켰다.

question = "심봉사와 광개토대왕이 할 만한 대화를 만들어주세요."

# 입력과 가장 유사한 예시를 선택합니다.
selected_examples = example_selector.select_examples({"question": question})

print(f"입력에 가장 유사한 예시:\n{question}\n")
for example in selected_examples:
    print(example)

먼저 셀렉터에 심봉사-광개토대왕에 대한 것을 넣었더니 이순신-유관순의 예제가 도출되었다.

question = "이토 히로부미와 마리퀴리가 할 만한 대화를 만들어주세요."

# 입력과 가장 유사한 예시를 선택합니다.
selected_examples = example_selector.select_examples({"question": question})

print(f"입력에 가장 유사한 예시:\n{question}\n")
for example in selected_examples:
    print(example)

question 내용을 이토히로부미-마리퀴리로 하자 신기하게도 도요토미히데요시-히틀러 예시가 도출되었다.

이렇게 만든 example_selector를 이제 원래 쓰던 FewShotPromptTemplate에 집어넣으면 알아서 example에서 유사도를 계산해서 제일 잘 맞는 예제만 가져다 주는 것!

prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    suffix="situation:\n역사속 인물 {person1}, {person2}가 실제 만나서 할 만한 대화를 생성합니다.\nConversations:\n",
    input_variables=["person1", "person2"],
)

# 체인 생성
chain = prompt | llm

# 결과 출력
answer = chain.stream(
    {"person1": "광개토대왕", "person2": "고종황제"}
)
stream_response(answer)
<생성된 결과>
다음은 광개토대왕과 고종황제가 만약 만났다면 했을 법한 대화입니다.

광개토대왕:
(호탕하게 웃으며) 황제님, 이 먼 길을 오시다니 영광이오. 황제님의 시대는 어떠하신가?

고종황제:
대왕님, 저의 시대는 변화와 도전의 연속이었습니다. 서양의 세력이 들어와 우리의 전통과 주권을 위협하였지요. 대왕님의 시대는 어떠셨습니까?

광개토대왕:
나의 시대에는 고구려가 가장 강성했던 때로, 여러 나라를 정복하고 영토를 확장하였소. 하지만 나 역시 외침에 맞서 싸워야 했소.

고종황제:
그렇군요. 우리는 시대가 달라도 외침에 맞서 싸우는 것은 같습니다. 저는 국권을 회복하고자 독립을 위해 노력했으나, 많은 어려움이 있었습니다.

광개토대왕:
황제님의 노력이 헛되지 않았기를 바라오. 나라를 위한 헌신은 시대를 초월하여 기억될 것이오.

고종황제:
대왕님의 말씀에 감사드립니다. 우리 모두가 그러한 헌신을 이어가야 할 것입니다. 대왕님의 지혜와 용맹을 배우고 싶습니다.

광개토대왕:
서로의 지혜를 나누며 더욱 발전하는 길을 모색하자오. 함께 힘을 모아 나라를 지키는 것이 우리의 책임이니까요.

고종황제:
그렇습니다. 우리의 연대가 미래 세대에게도 큰 힘이 될 것입니다. 함께 나아가 더욱 강한 나라를 만들어 가시다.

그리고 랭스미스를 확인해보니 역시 유관순 관련 예제 1개만 들어가 있다.

정리

예제 선택기를 사용하면, LLM에 여러 예제를 모두 제공하지 않더라도 입력단에서 필요한 예제만 가져다가 뽑아 줄 수 있겠다.
이 경우 내가 원하는 여러 답변 형태를 미리 작성해두고 알아서 그에 맞춰서 알맞은 상황에 알맞은 예제에 맞는 답변을 줄 수 있다는 것을 배웠다. 오공완!

728x90