AI/Gen AI

RAG 구현 Step-by-Step: Generation 및 API 전환

문괜 2024. 9. 24. 21:00
반응형

 

RAG 구현 Step-by-Step: Intro

  1. 가상환경 설정 및 MVP 구현
  2. Dataset과 Vector DB 구현
    1. Dataset 확보
    2. Vector DB 구현
  3. Embedding & Searching 구현
  4. Generation  구현 및 API 전환

드디어 RAG 구현의 마지막 부분인 Generation과 API 전환 단계로 넘어왔다. Retrieval 파트 구현에 대한 시간 투자로 인해서 이번 포스트에는 간단히 구현동기, 구현과정 그리고 문제상황에 대해서만 간단히 정리해 봤다.

 

먼저 Generation을 구체화한 부분에 대한 구현동기는 아래와 같다.

구현동기

  1. 챗봇이 아닌 API 요청으로 작동하기 때문에 클라이언트로 반환해야 하는 응답이 일정해야 한다. 
    • 특히, 클라이언트로의 응답을 json으로 Restful api를 만들고 싶었다.
  2. 클라이언트와 약속된 응답 형식과 서비스 의도에 맞춰 단순히 조회된 내용을 작성하는 부분과 새로 생성하는 부분이 나누어졌다.
    • 앞전의 토큰이슈로 인해 생성하는 부분에 있어서도 토큰관리를 시도해보고 싶었다.

구현과정

그래서 위의 구현동기를 기반으로 아래와 같은 구현과정을 거쳤다.

  1. Generation을 지정하는 Prompt작성
  2. Generation을 통해 생성된 내용을 Json으로 전환

먼저 1번의 경우 아래와 같은 Prompt를 기반으로 LLM이 생성하도록 진행했다.

template = """
- You are a helpful assistant that answers questions about the context below.
- You do not make up answers to questions that cannot be found in the context.
- If you don't know the answer to a question, just say that you don't know. Don't try to make up an answer.
- You will generate a list of activities and please follow the format:
[
  {{
    "activityTitle": Get the name of the place,
    "activityLoc": Get the address of the place,
    "timeTotal": Generate your expected time about the place or just put 1 hour,
    "activityDescription": Generate a description of the place based on your understanding,
    "activityImage": Get url of the place
  }},
  ...
]
- Make sure the list contains at least 5 activities
- You have to answer in Korean.

Answer the following question based on this context:

{context}

Question: {question}
"""

prompt = ChatPromptTemplate.from_template(template)

# llm = ChatOpenAI(temperature=0).with_structured_output(Search)
llm = ChatOpenAI(temperature=0)

final_rag_chain = (
    {"context": retrieval_chain,
     "question": itemgetter("question")}
    | prompt
    | llm
    | StrOutputParser()
)

 

위의 내용처럼 Prompt를 구성하였다. 또한 해당내용을 ChatPromptTemplate에 담아서 일전에 구현한 retrieval_chain까지 포함하여 최종 형태의 rag_chain을 완성했다. 그리고 아래와 같은 결과가 출력 됐다.

{'activityTitle': '불타는소금구이', 'activityLoc': '서울 성북구 보문로30길 43-3', 'timeTotal': '1시간', 'activityDescription': '고기와 된장찌개가 맛있는 불타는소금구이. 북적북적한 분위기와 푸짐한 안주로 유명한 맛집입니다.', 'activityImage': 'https://naver.me/x9JcBZ9g'}
{'activityTitle': '열린마당', 'activityLoc': '서울 성북구 동소문로2길 27', 'timeTotal': '1시간', 'activityDescription': '한잔하기 좋은 분위기와 가성비 좋은 안주로 소문난 열린마당. 맛있는 안주와 친절한 서비스가 자랑입니다.', 'activityImage': 'https://naver.me/IxD0x0dR'}
{'activityTitle': '다바타식당', 'activityLoc': '서울 성북구 삼선교로16길 54 2층', 'timeTotal': '1시간', 'activityDescription': '한식과 일식이 어우러진 맛있는 음식을 즐길 수 있는 다바타식당. 아늑한 분위기와 친절한 사장님으로 소문난 곳입니다.', 'activityImage': 'https://naver.me/FWJ6y4fy'}
{'activityTitle': '달달커피', 'activityLoc': '서울 성북구 동소문로 22-1', 'timeTotal': '1시간', 'activityDescription': '미각을 만족시키는 커피와 디저트를 즐길 수 있는 달달커피. 아늑한 분위기와 다양한 디저트로 소문난 카페입니다.', 'activityImage': 'https://naver.me/5cTd22tI'}
{'activityTitle': '퐁닭퐁닭 본점', 'activityLoc': '서울 성북구 성북로4길 52 한진한신아파트상가', 'timeTotal': '1시간', 'activityDescription': '매콤하고 고소한 맛으로 유명한 퐁닭퐁닭 본점. 바삭한 맛을 즐길 수 있는 메뉴들이 인기를 끌고 있습니다.', 'activityImage': 'https://naver.me/5mYK3I3s'}

 

 

기본적으로 출력값을 json.loads를 활용하여 json 형태로 전환해서 나왔으나 2번 과정을 통해서 아래와 같이 API로써 사용될 수 있게 만들었다.

# rag-service/rag-api-server/app/crud/rag_methods/result_parsing.py
import json
from ...schema.response_dto.dating_generation_response_dto import DatingGenResponseDto


def get_dto(result):
    activities_list = json.loads(result)
    dating_generation_responses = []
    for activity in activities_list:
        response_dto = DatingGenResponseDto(
            activityTitle=activity['activityTitle'],
            activityLocation=activity['activityLoc'],
            timeTotal=activity['timeTotal'],
            activityDescription=activity['activityDescription'],
            activityImage=activity['activityImage']
        )
        dating_generation_responses.append(
            response_dto
        )
    return dating_generation_responses

 

위와 같은 방식으로 따로 빼둔 이유는 안정성을 추구하기 위해서였다. 어떤 Model을 사용한다 하더라도 출력도중 멈추는 문제가 발생했었다. 물론 흔히 벌어지는 일이 아니었기에 큰 문제라 생각되지 않았으나 잘못된 정보를 클라이언트에 보내기 전에 자체적으로 오류를 넘기도록 구성하는 게 좋다고 판단하여 위와 같은 함수를 추가하였다.

 

문제상황

문제상황이 특별하게 발생하지는 않았다 구현을 잘했다기보다는 무제가 발생할 수준으로 구현하지 못했다는 생각이 든다. 그래서 이번의 문제상황의 경우는 구현하면서 불확실하다고 느낀 점에 대해 적었다.

  1. Prompt 구성

보는 바와 같이 Prompt는 주어진 역할을 잘 해냈다. 하지만 여기서 문제는 저 프롬프트가 최선의 프롬프트인지 판단하기 어려웠다. 기본적으로 아래의 판단기준으로 작성하였다. 

  1. 한국어보다는 영어
  2. 역할 부여 
  3. Task 나열

하지만 구현이 급했던 나머지 비교 해보며 최적의 프롬프트를 찾아내지 못했다.

 

이로써 RAG를 다 완성했다. 각 부분별 고도화를 진행했으나 비교 분석 하는 부분이 부족하다고 느꼈다. 그래서 회고를 진행하려고 한다. 

아래 사진은 현재까지 완성된 API로 전환된 이번 프로젝트를 위하여 만든 RAG API 구조이다.

 

구체적인 코드를 보고 싶다면 아래의 링크를 통해 확인할 수 있다. 

RAG Prompt: Generation 

 

RAG/rag-practice/rag-langchain/RAG_application_mvp_query_analysis_prompt_langhcain.ipynb at main · jwywoo/RAG

1. RAG Practice. Contribute to jwywoo/RAG development by creating an account on GitHub.

github.com

RAG API

 

RAG/rag-service/rag-api-server at main · jwywoo/RAG

1. RAG Practice. Contribute to jwywoo/RAG development by creating an account on GitHub.

github.com

 

그리고 모든 파트별 구현에 대한 문서를 만들었으니 아래의 링크를 통해 확인이 가능하다.

RAG

 

GitHub - jwywoo/RAG: 1. RAG Practice

1. RAG Practice. Contribute to jwywoo/RAG development by creating an account on GitHub.

github.com

 

다음 포스트에서는 프로젝트에 대한 회고를 진행할 예정이다.

Project HowAbout: RAG Api 구현 회고

반응형