FewShot, ChatHistory 활용해서 RAG 고도화 + Streamlit으로 배포 (LangChain)

2025. 5. 28. 13:32·Python/Langchain

들어가며

이번 포스팅은 이전 키워드 사전 활용으로 Retrieval 효율개선이라는 내용의 포스팅에서, 추가로 FewShot, ChatHistory를 통해 고도화한 내용이라 아래 포스팅을 못본 사람들은 참고해주면 좋을거같다 🙇🏻‍♂️
https://huncozyboy.tistory.com/42

 

키워드 사전 활용으로 Retrieval 효율개선 (LangChain)

들어가며사용자 질문 표현이 검색 성능에 미치는 영향여러가지 Chat GPT를 포함한 생성형 AI 모델들을 사용하게 되면, 종종 사용자 질문이 모델의 답변 정확도에 큰 영향을 주는 것을 체감하게 된

huncozyboy.tistory.com


FewShot이란?

먼저 실습을 진행하며 코드를 입력했던 내용을 공유하기 전에, FewShot이라는 개념에 대해서 알아보려고 한다

FewShot이란 ?
LLM(Large Language Model)에서 훈련 없이도 예제 몇 개만 주면 작업을 수행할 수 있는 능력을 말한다


우리가 챗봇을 구현하는데 사용한 GPT-4o 같은 모델을 예시로 들면,

Q: 한국의 수도는 어디인가요? A: 서울입니다
와 같은 내용을 예시로 준다면, 일본의 수도는 어디인가요? 라고 물으면 자동으로 도쿄입니다 라고 답할 수도 있다

물론 위 예시는 간단한 내용이라 FewShot Learning의 적용 없이도, 당연히 추론해서 답변할 수 있는 내용이지만, 우리가 구현한 소득세법과 관련한 내용이나 각종 법조문, 세법 용어, 복잡한 규정 해석 같은 고도화된 내용일 수록 FewShot Learning이 중요해진다고 생각해주면 될거같다

 

실제로 내가 들었던 강의에서도 설명한 내용인데, 나와 있는 논문에 따르면 예제를 1개 넣으면 정확도가 확 올라가고, 여러 개를 넣을 수록 결과가 더 좋아지긴 하는데, 토큰 수가 많아져 관리하기가 힘들고 과도하게 많이 넣으면 환각 문제(hallucination)가 나올 수도 있다고 했다. 즉, 너무 많은 예제를 넣으면 모델이 오히려 헷갈리거나 불필요한 상상을 할 수도 있다는 말이다

결론적으로  적당히 3개만 넣는 게 실용적이라는 팁도 있기도했어서, 우리 플젝에서도 이 팁을 반영해서 3개의 대표 예제만 프롬프트에 넣는 방식으로 설계해줬다

config.py 로 선언해준 answer examples

def get_ai_response(user_message):
    dictionary_chain = get_dicionary_chain()
    rag_chain = get_rag_chain()
    tax_chain = {"input": dictionary_chain} | rag_chain
    ai_response = tax_chain.stream(
        {
            "question": user_message
        }, 
        config={
        "configurable": {"session_id": "abc123"}
        },
    )
    return ai_response

이렇게 config={ "configurable": {"session_id": "abc123"} }, 로 선언해줘서 사용할 수 있었다

 

난 위 내용을, OpenAI API 같은 걸 사용하면 요청 길이에 따라 요금이 청구되니까, 불필요하게 긴 프롬프트는 지양해야되고, 그중에서도 핵심적인 예제만 골라 넣는 최적화 작업이 필요하는 것도 프롬프트 설계의 핵심이라고 이해했다 💡


ChatHistory란?

공식문서 Adding chat history

ChatHistory라는 개념은, LangChain에서 제공하는 ChatMessageHistory는 각 세션별로 대화 기록을 저장해서 챗봇이 맥락을 잃지 않도록 해주는데 여기서 RunnableWithMessageHistory라는 걸 추가로 쓰면, 질문이 들어올 때마다 알아서 과거 질문 + 과거 답변을 모두 LLM에 넘겨줘서, 챗봇이 연속적인 대화를 이해할 수 있게 만들어준다 


본문


Streamlit

Streamlit hello로 간단한 접속 테스트

평소에 기존 플젝을 할때는 보통 내가 백엔드면 클라우드 분들은 다른 분들이 맡아주셨어서 (사실 혼자 공부할때는 항상 이 케이스긴하지만) 소득세 챗봇을 구현하려 했을때, 당연하게도 UI와 관련된 부분이 없을거라고 생각했었다

기본 Streamlit 적용해서 UI 생성

위 사진처럼 Python 기반의 오픈 소스 라이브러리인 Streamlit을 사용해서 몇 줄만 써도 생각보다 예쁜 웹앱을 만들어주었다. 근데 추가로 구글링을 해보니 해당 라이브러리는 보안 기능은 미제공 해준다고 해서, 실제 서비스에서는 조금 사용이 어려울거같다는 생각을 했었다 😂

https://streamlit.io/cloud

 

Streamlit Community Cloud • Streamlit

Deploy, manage, and share your Streamlit apps — all for free.

streamlit.io

 

Streamlit에서 라이브러리를 생성해준 다음, streamlit run chat.py 을 입력해서 간단하게 로컬 (기본세팅은 localhost:8501 포트이다)에서도 실행이 가능했고, 배포 한 다음 URL에서도 정상적으로 실행되는 모습을 확인할 수 있었다

로컬에서 Streamlit 실행
APP URL 으로 접속하면 배포된 실행 환경에서 확인 가능


dicionary 체인 구성

def get_dicionary_chain():
    dictionary = ["사람을 나타내는 표현 -> 거주자"]
    llm = get_llm()
    prompt = ChatPromptTemplate.from_template(f"""
        사용자의 질문을 보고, 우리의 사전을 참고해서 사용자의 질문을 변경해주세요.
        만약 변경할 필요가 없다고 판단된다면, 사용자의 질문을 변경하지 않아도 됩니다.
        그런 경우에는 질문만 리턴해주세요
        사전: {dictionary}
        
        질문: {{question}}
    """)

    dictionary_chain = prompt | llm | StrOutputParser()
    return dictionary_chain

 

단순히 질문 문장을 바꿔주는 프롬프트 같지만, 이걸 통해 LLM이 훨씬 정확하고 일관된 답변을 하도록 도울 수 있다는 장점이 있었다

LangChain에서 제공하는 LangChain Expression Language(LCEL) 를 활용해줘서, 프롬프트 → LLM → OutputParser 의 구조로 설계했다
이런 체인이 필요한 이유는 LLM은 기본적으로 언어 패턴에 기반해서 답변을 생성하기 때문에, 사용자 질문이 정확하지 않거나 모호할때도 최대한 의도한 답변이 나올 수 있도록 해주는거라고 생각해주면 될거같다


RAG 체인 구성

qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        few_shot_prompt,
        MessagesPlaceholder("chat_history"),
        ("human", "{input}"),
    ]
)

먼저 qa_prompt에서 MessagesPlaceholder로 chat_history를 선언해줬는데, 해당 개념이 서론에서 설명했던 세션별로 대화 기록을 저장해줄 수 있는 기능이다

 

conversational_rag_chain = RunnableWithMessageHistory(
    rag_chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history",
    output_messages_key="answer",
).pick('answer')

이부분도 앞서 설명했던 공식문서에 있었던 RunnableWithMessageHistory 다

질문-답변마다 히스토리에 추가하고, 히스토리를 LLM 프롬프트에 붙여서 넘겨주는 플로우의 코드를 설계했다


전체 코드 + 구현 결과

def get_rag_chain():
    llm = get_llm()
    example_prompt = ChatPromptTemplate.from_messages(
        [
            ("human", "{input}"),
            ("ai", "{answer}"),
        ]
    )
    few_shot_prompt = FewShotChatMessagePromptTemplate(
        example_prompt=example_prompt,
        examples=answer_examples,
    )
    system_prompt = (
        " 당신은 소득세법 전문가입니다. 사용자의 소득세법과 관한 질문에 답변해주세요"
        " 아래 제공된 검색된 문서를 활용해서 질문에 답변 해주세요"
        " 만약 답을 모른다면, 모른다고 솔직히 답변 해주세요 "
        " 답변할땐 소득세법 (XX조)에 따르면 이라고 시작하면서 답변해주시고, "
        " 답변은 최대 세 문장으로 간결하게 답변해주세요 "
        "\n\n"
        "{context}"
    )
    qa_prompt = ChatPromptTemplate.from_messages(
        [
            ("system", system_prompt),
            few_shot_prompt,
            MessagesPlaceholder("chat_history"),
            ("human", "{input}"),
        ]
    )
    history_aware_retriever = get_history_retriver()
    question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)

    rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

    conversational_rag_chain = RunnableWithMessageHistory(
        rag_chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="chat_history",
        output_messages_key="answer",
    ).pick('answer')

    return conversational_rag_chain

spinner를 활용한 응답 대기 형식


마치며

RAG 구조를  LangChain으로 활용하는 LLM 챗봇의 전체 설계와 구현 방식을 학습한 가장 큰 이유는 스프링 프로젝트에서 적용하기 위해 공부했었는데, LLM 파인튜닝이나  Prompt Optimization와 같은 깊은 주제들을 다루려면 아직까진 더 학습해야 할 것도 많고, 기술 난이도도 높다고 느껴져서 정말 어려울거같다... (괜히 대학원을 가는게 아니구나)

Spring + FastAPI

추후 목표는 지금은 개인적으로 공부 중인 FastAPI를 적용해 서버를 별도로 띄운 뒤,  위 파이프 라인처럼 별도의 개인 플젝으로 Spring 서버랑 연동까지 해보고싶다 💪🏻

'Python > Langchain' 카테고리의 다른 글

키워드 사전 활용으로 Retrieval 효율개선 (LangChain)  (1) 2025.05.17
RAG 개념 + Vector와 Embedding (Python)  (1) 2025.05.13
'Python/Langchain' 카테고리의 다른 글
  • 키워드 사전 활용으로 Retrieval 효율개선 (LangChain)
  • RAG 개념 + Vector와 Embedding (Python)
huncozyboy
huncozyboy
이지훈
  • huncozyboy
    열정을 기록하기
    huncozyboy
  • 전체
    오늘
    어제
    • 분류 전체보기 (63)
      • Spring (26)
        • JWT (3)
        • 무한 스크롤 (1)
        • 매칭 로직 (2)
        • OAuth (4)
        • 자동화 (1)
        • 캐싱 (1)
        • AOP (2)
        • Swagger (1)
        • S3 (1)
        • CORS (1)
        • Spring Retry (0)
        • Webhook (2)
        • Grapheme Cluster (1)
        • 연관관계 (1)
        • CS 개념 (5)
      • DevOps (13)
        • 스왑 메모리 (1)
        • Blue Green (2)
        • Docker (7)
        • Route 53 (1)
        • 리버스 프록시 (2)
      • AI (2)
        • Claude Code (1)
        • Copilot (1)
      • CS (4)
        • JAVA (4)
      • Github (1)
        • Conflict (1)
      • Python (4)
        • Langchain (3)
        • Crawling (1)
      • 일상 (3)
        • 회고록 (1)
      • 알고리즘 (10)
        • 투포인터 (0)
        • 슬라이딩 윈도우 (0)
        • 정렬 (0)
        • 이분 탐색 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    redis
    스프링
    JWT
    도커
    수도코드
    코테
    프로그래머스
    LangChain
    OAuth
    aws
    Docker
    알고리즘
    백준
    코딩테스트
    DevOps
    EC2
    java
    https
    Spring
    자바
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
huncozyboy
FewShot, ChatHistory 활용해서 RAG 고도화 + Streamlit으로 배포 (LangChain)
상단으로

티스토리툴바