article content thumbnail

[Python] 캐릭터 챗봇 장소 추천 기능 연구

RAG를 기반으로 Chatbot 개선 해보기.

Orb라는 서비스를 최근에 출시하여 운영하고 있다. 캐릭터 챗봇이 사용자와 다양하게 상호작용 하면서 즐길 수 있는 모험형 서비스이다.

아래 사진처럼 Orb에 등록된 장소에 방문해있다면, 위치를 기반으로 장소를 인증하고 퀘스트 달성과 칭호를 얻을 수 있다.

orb 장소 조회 이미지


단순히 이러한 기능만 존재하는 것보다 사용자가 어떤 장소에 방문하든 앱에 접속해서 인증할 수 있고, 또 장소 추천을 받고 싶을 때도 해당 앱을 사용할 수 있도록 캐릭터 챗봇과의 대화 기능이 존재한다.


현재 운영하고 있는 버전에서는 fine tuning과 프롬프트 엔지니어링을 통해 캐릭터를 학습시키고 운영하고 있다. 하지만 prompt 엔지니어링으로는 사용자의 컨텍스트를 포함하기 힘들 뿐만 아니라, 할루시네이션 등의 결과가 있어서 추가적으로 개선이 필요했다.


요구사항은 다음과 같다.

요구사항

  • 오브 서비스의 챗봇이 사용자의 위치를 기반으로 장소를 추천해 줄 수 있다.

  • 챗봇의 경우 최대한 유저 입장에서 사람과 대화하는 것처럼 만들어야 함.


따라서 사용자가 방문했던 장소 정보들을 기반으로 새롭게 장소를 추천해주는 기능을 구현하려고 연구를 진행했다.

우선 사용자가 방문한 장소들을 컨텍스트로 추천을 해야했기 때문에, 다양한 컨텍스트가 필요했고 따라서 RAG를 사용한 연구를 진행했다.


RAG(Retrieval-Augmented Generation)는 쉽게 말해 Search + Generation이다.

기존 LLM이 프롬프트를 기반으로 결과물을 만들어냈다면, RAG는 검색의 과정을 포함하고 있다.

  • RAG 동작 방식

    1. 질문 입력

      사용자의 질의가 입력됩니다.

    2. 검색 단계 (Retrieval)

      외부 검색 엔진(예: 벡터 데이터베이스, Elasticsearch 등)을 사용해 관련 문서나 데이터를 검색합니다.

    3. 응답 생성 (Generation)

      검색된 데이터를 LLM에 전달하여, 이를 기반으로 응답을 생성합니다.


RAG를 활용한 장소 추천 플로우

  • database에서 user가 방문한 장소 정보를 조회한다.

  • user가 방문한 장소 정보를 vector embedding 한다.

  • 사용자의 위치 정보를 포함한 입력 prompt를 통해 유사한 장소 정보들을 조회한다.

  • 해당 장소 중에서 적절한 장소를 추천 받는다.


다음과 같은 방법으로 장소 추천을 받을 수 있었고 다음으로 어떻게 사람처럼 정보를 제공해줄 수 있을까 ?에 대한 고민을 했다. 정답은 '기억'에 있다고 생각했다. 처음에는 방문한 장소 정보를 단순 vector embedding 했는데, 방문한 장소들의 timestamp를 기반으로 가장 대표적인 이론으로 에빙하우스의 망각곡선을 적용해보기로 했다.

https://en.wikipedia.org/wiki/Forgetting_curve


망각 곡선 함수를 정의하고 embedding한 vector에 weight를 준 후에 vector db에 저장했다.

def forgetting_curve(t, a=1, b=0.5):
    """
    Calculate the forgetting curve value at time t.










    Parameters:
    t (float): 





Time
    a (float): Initial memory strength
    b (float): Decay rate

    Returns:
    float: Memory retention at time t
    """
    return a * np.exp(-b * t)


embedding vector들을 저장할 vector db로는 Meta에서 만든 vector db인 FAISS를 사용했다.

https://github.com/facebookresearch/faiss

uv pip install faiss-cpu


vector embedding model과 chat completion을 사용하기 위해서 Langchain openai를 사용했다.

https://python.langchain.com/docs/integrations/llms/openai/

uv pip install langchain-apenai

text embedding과 chat completion을 위해 두 모델을 선언한다.

# 모델 초기화
model = ChatOpenAI(model="gpt-4o-mini", api_key=API_KEY)
# OpenAI 임베딩 모델 초기화
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", api_key=API_KEY)


faiss를 생성한다. (faiss의 dimenstion의 경우 embedding model의 dimension을 참고했다.)

 index = faiss.IndexFlatL2(1536)
 vector_store = FAISS(
   embedding_function=embeddings,
   index=index,
   docstore=InMemoryDocstore(),
   index_to_docstore_id={}
 )

 vector_store.save_local("faiss_index")


pandas를 이용해서 ORB에 등록 되어있는 장소들을 vector db에 저장하고 추가적으로 user 방문 장소 정보 csv 파일을 읽어서 Document로 저장했다.

    for index, row in filtered_place_df.iterrows():
        time_diff = (current_time - pd.to_datetime(row['vp.created_at'])).total_seconds()
        weight = forgetting_curve(time_diff / 3600)  # 초를 시간으로 변환
        place_str = str(row['name']) + ": " + str(row['short_introduction'])
        print(place_str, weight)
        embedding = np.array(embeddings.embed_query(place_str))  # Convert to NumPy array
        weighted_embedding = embedding * weight
        doc = Document(page_content=place_str, metadata={"source": "place_log"})
        vector_store.add_documents(documents=[doc], embeddings=[weighted_embedding], ids=[str(uuid4())])







이와 같은 방식으로 RAG를 구성한 후에 위치를 강남역으로 두고 test를 진행했다.

<최근 방문한 장소 목록>
A: 사무실을 탈출해 잠시 쉴 수 있는 도심 속 공원 5.135880337562668e-43B: 50년 전통 비법이 담긴 순대국 맛집 5.139252515453827e-43C: 최상의 재료로 이탈리안 젤라또와 커피를 제공하는 카페 5.147522644427003e-43D: 정성과 열정이 가득 담긴 햄버거 맛집 2.1255087283593723e-126D: 정성과 열정이 가득 담긴 햄버거 맛집 5.63028602935597e-116E: 분위기 좋고 청결한 강남역 볼링장 5.155332824832486e-43F: 이색 데이트하기 좋은 실내 야구장 5.1588256700650466e-43G: 사장님이 친절한 라떼 맛집 6.314128423662769e-43H: 순수 모짜렐라 치즈를 사용한 닭갈비 맛집 6.357661717514701e-43I: 후카와 칵테일을 즐길 수 있는 강남역 바 9.14940674605202e-43J: 보드게임 초보자부터 매니아까지 즐길 수 있는 공간 9.1601159108862e-43K: 사무실을 탈출해 잠시 쉴 수 있는 도심 속 공원 6.621362530140044e-27B: 50년 전통 비법이 담긴 순대국 맛집 6.63328031754398e-27D: 정성과 열정이 가득 담긴 햄버거 맛집 6.737442536060442e-27

<질문> 내가 제일 좋아하는 음식은 뭘까 ?

<응답> 'B의 햄버거를 좋아할 것 같아요! 너의 제일 좋아하는 음식이 햄버거인지 궁금하네!'

가장 많이 방문한 장소인 B를 추천하고 있다.


- 최근 방문한 장소를 프롬프트에 넣어서 질문할 경우.

<최근 방문한 장소 목록>
A: 사무실을 탈출해 잠시 쉴 수 있는 도심 속 공원 5.135880337562668e-43B: 50년 전통 비법이 담긴 순대국 맛집 5.139252515453827e-43C: 최상의 재료로 이탈리안 젤라또와 커피를 제공하는 카페 5.147522644427003e-43D: 정성과 열정이 가득 담긴 햄버거 맛집 2.1255087283593723e-126D: 정성과 열정이 가득 담긴 햄버거 맛집 5.63028602935597e-116E: 분위기 좋고 청결한 강남역 볼링장 5.155332824832486e-43F: 이색 데이트하기 좋은 실내 야구장 5.1588256700650466e-43G: 사장님이 친절한 라떼 맛집 6.314128423662769e-43H: 순수 모짜렐라 치즈를 사용한 닭갈비 맛집 6.357661717514701e-43I: 후카와 칵테일을 즐길 수 있는 강남역 바 9.14940674605202e-43J: 보드게임 초보자부터 매니아까지 즐길 수 있는 공간 9.1601159108862e-43K: 사무실을 탈출해 잠시 쉴 수 있는 도심 속 공원 6.621362530140044e-27B: 50년 전통 비법이 담긴 순대국 맛집 6.63328031754398e-27D: 정성과 열정이 가득 담긴 햄버거 맛집 6.737442536060442e-27

<질문> 가장 최근에 방문한 장소 말고 최근 방문한 장소 목록 중에서 내가 좋아할 만한 곳을 추천해줘.

<응답> '물론! X: 특별한 고급 야끼니꾸를 즐길 수 있는 곳 어때?'

방문했던 장소들을 기반으로 장소를 추천해주고 있다.


현재는 망각곡선을 지수함수로 설정하여 test 하고 있는데, 사용자의 장소 방문 기록이나 채팅 데이터가 부족하여 데이터를 확보한 후에 연구를 추가적으로 진행하기로 결정했다.

이후에 system instruction을 보완하고 조금 더 망각곡선을 test 해볼 수 있는 상황을 만들어서 추가로 실험을 진행할 예정이다.

최신 아티클
Article Thumbnail
최윤한
|
2025.03.30
A-MEM: Agentic Memory for LLM Agents 리뷰
AI Agent의 기억 메모리 조직화 시스템 방법론
Article Thumbnail
최윤한
|
2025.03.08
IT 커뮤니티 SIPE 3기 운영진 마침표.
SIPE 운영진 활동 회고와 SIPE를 고민하는 하는 사람을 위한 후기와 추천글
(python) spotlight로 시각화 feature vector
최윤한
|
2025.03.02
(python) spotlight로 시각화 feature vector
Spotlight로 Feature Vector 시각화