AWS 기술 블로그

Amazon Bedrock과 OpenSearch를 활용한 Multimodal RAG 기반 상품 검색 챗봇

이 글에서는 Multimodal LLM과 Multimodal Embedding을 활용하여 Multimodal RAG를 구현하는 몇 가지 방법을 제안하고, 하나의 예시 애플리케이션으로 패션 상품 검색을 위한 챗봇 구현 방안을 소개합니다.

주요 기술 개념

검색 증강 생성 (Retrieval-Augmented Generation, RAG)

대규모 언어 모델 (Large Language Model, LLM)이 응답을 생성하기 전에, 외부 지식 소스를 참조하여 보다 정확하고 풍부한 답변을 생성하도록 개발된 기술입니다. 이를 통해 LLM이 고정된 훈련 데이터에만 의존하지 않고, 환각(hallucination) 현상을 줄여 보다 신뢰성 있는 결과를 생성할 수 있습니다.

Multimodal RAG

텍스트 뿐만 아니라 이미지, 차트 등 다양한 형태의 데이터를 처리할 수 있도록 지원합니다. 이는 시각적 정보를 포함한 복잡한 요청에도 정확한 답변을 생성할 수 있도록 설계되었습니다. 예를 들어, 상품 검색에서 텍스트 설명 뿐만 아니라 상품 이미지도 함께 고려하여 더 정확한 검색 결과를 제공할 수 있습니다.

Multimodal Embedding

모달리티(Modality)는 입력 데이터의 유형을 의미하며, 이미지, 텍스트, 음성 등을 뜻합니다. Multimodal Embedding 모델은 이러한 서로 다른 모달리티를 하나의 공통된 벡터 공간에 표현하는 기술로, 각 모달리티 간의 상관관계를 학습하여 데이터를 통합합니다.텍스트와 이미지 등의 데이터를 입력으로 받아 이를 각각 임베딩 벡터로 변환하여 동일한 벡터 공간에 표현합니다. 이를 통해 두 모달리티 간의 유사성을 계산할 수 있어, 텍스트와 이미지 간의 관계를 이해하고 예측을 수행할 수 있습니다.

[그림 1] Joint Embedding Space: 서로 다른 모달리티(텍스트, 이미지)를 단일 벡터 공간에 표현

이 벡터 공간 내에서 유사한 의미를 가진 데이터들은 서로 가깝게 위치하고, 관련 없는 데이터는 더 멀리 위치합니다. 이는 다양한 데이터 간의 관계를 이해하고 분석하는 데 매우 유용합니다. 예를 들어, 텍스트로 이미지를 검색하거나, 이미지로 유사한 이미지를 찾는 등의 사용 사례에 적합합니다.

Multimodal RAG 구현 방안

Multimodal RAG를 구현하기 위해서는 서로 다른 유형의 데이터(예: 상품명, 상품 이미지)를 임베딩하여 데이터 간의 의미와 관계를 저장해야 합니다. 또한, 사용자로부터 이미지 또는 텍스트 형태의 요청이 있을 때 이를 임베딩하여 관련된 데이터를 효과적으로 검색하는 것이 중요합니다. Multimodal RAG를 구현하기 위해 아래와 같은 방법들을 고려할 수 있습니다.

1. Multimodal Embedding Retrieval

[그림2] Multimodal Embedding Retrieval

텍스트와 이미지를 동일한 벡터 공간에 임베딩하여 통합적으로 처리하는 방법입니다. 이 접근 방식에서는 텍스트와 이미지를 결합하여 Multimodal Embedding 모델을 통해 단일 벡터를 생성하고, 이를 사용하여 검색을 수행합니다.

이는 텍스트와 이미지를 동일한 벡터 공간에 임베딩하여 일관된 비교가 가능하고, 단일 벡터를 사용하므로 검색 시스템의 구조가 단순하고 구현이 용이합니다. 하지만 모든 모달리티를 동일한 벡터 공간에 임베딩 하기 때문에 각 모달리티의 개별 특성을 충분히 반영하지 못할 수 있습니다.

2. Cross-modal Retrieval

[그림3] Cross-modal Retrieval

하나의 모달리티로 쿼리를 입력하고, 다른 모달리티에서 유사한 개념을 가진 객체를 검색하는 방법입니다. 텍스트 쿼리를 입력하여 해당 내용과 유사한 이미지를 검색하기 위해, Multimodal LLM을 통해 이미지를 묘사하는 캡션을 생성하고 해당 텍스트를 임베딩하여 저장합니다.

텍스트와 이미지를 동일한 벡터 공간에 위치시킴으로써 개념적으로 유사한 객체를 찾을 수 있습니다. 하지만 이미지를 텍스트로 변환하는 추가적인 처리 단계가 필요하고, 임베딩 모델의 성능에 따라 이미지의 캡션 생성 과정에서 정보 손실이 발생할 수 있습니다.

3. Multi-vector Retrieval

[그림4] Multi-vector Retrieval

Multi-vector Retrieval은 각 모달리티 데이터를 각각의 벡터 공간에 임베딩하여 저장하고, 검색 시 여러 벡터 공간에서 검색을 수행하는 방법입니다. 각 모달리티의 특성을 개별적으로 유지하면서도 통합된 검색 결과를 얻을 수 있습니다.

각 모달리티를 별도의 벡터 공간에 임베딩하므로, 개별 특성을 잘 반영할 수 있고 모달리티의 특성에 맞는 최적화된 검색을 수행할 수 있습니다. 하지만 각 모달리티별로 별도의 임베딩과 검색을 수행하므로 계산 리소스와 저장 공간, 검색 소요시간이 추가로 요구됩니다.

각 접근 방식은 사용자의 쿼리 유형과 특정 요구 사항에 맞게 선택될 수 있으며, 데이터의 특성, 벡터 공간의 리소스, 반응 속도 등을 종합적으로 고려하여 가장 적합한 방법을 선택하는 것이 중요합니다.

Multimodal RAG 기반 상품 검색을 위한 챗봇

Multimodal RAG의 예시 애플리케이션으로 상품 검색을 위한 챗봇 구현 방안을 소개합니다.

문제 정의

고도화된 검색 알고리즘과 상품 추천 시스템에도 불구하고, 사용자들이 원하는 상품을 검색하는 것은 여전히 어려운 문제입니다. 기존 방식은 사용자의 검색 키워드에 따라 단발성으로 상품을 제안하는 데 그칩니다. 단순 키워드 검색만으로는 사용자의 의도를 파악하고 연속적인 질의를 이어 나가는 것이 어렵습니다. 이러한 한계로 사용자는 원하는 상품을 찾기 위해 여러 키워드를 조합하여 여러 번 검색해야 하는 불편함을 겪고 있습니다.

특히 상품 정보는 카테고리나 태그와 같은 단순한 형태로 데이터가 분류되고 있어, 상품의 세부 설명이나 이미지 등 Multimodal 데이터를 충분히 활용하지 못하고 있습니다. 이로 인해 검색 시 제한된 정보만을 사용할 수밖에 없는 한계가 존재합니다.

해결 전략

[그림5] Multimodal RAG 기반 챗봇 아키텍처

위 문제를 해결하기 위해, Amazon Bedrock과  Amazon OpenSearch Service 를 활용하여 Multimodal RAG 기반의 상품 검색 챗봇을 제안합니다. 이 애플리케이션은 상품의 JSON 메타데이터로부터 자동으로 상품 소개 문구를 생성하고, 이를 텍스트와 이미지 등 멀티 모달리티를 결합하여 검색에 활용합니다. 사용자가 이미지와 자연어를 이용해 원하는 상품에 대해 서술하면, 챗봇은 대화형으로 사용자가 검색하고자 하는 적합한 상품을 찾아 제안합니다.

상품 추천 챗봇에 대한 사용자 시나리오는 다음과 같이 가정합니다.

  • 텍스트로 원하는 상품의 특성을 기술 (예시: “체크 셔츠”, “30대 여성이 가을에 입기 좋은 옷 추천해줘”)
  • 이미지를 통해 유사한 상품 검색

1. 상품의 JSON 메타데이터로부터 자동으로 상품 소개 문구 생성

대부분의 전자 상거래 상품 데이터는 이미지와 JSON 조합으로, 각 속성에 대해 key-value 형태로 상품의 정보가 저장되어 있습니다. LLM, Embedding 모델은 학습할 때 프로그래밍 코드, 표, JSON, CSV 등의 학습 데이터도 사용하지만 일반적으로는 자연어 텍스트를 많이 사용합니다. 안정적인 검색 성능을 지원하기 위해 JSON 데이터를 문장 형태의 자연어로 변환합니다.

또한 기존의 JSON 상품 정보는 간략한 설명이나 카테고리, 속성 위주로 작성되어 있어, 사용자에게 충분한 정보를 제공하기에는 부족합니다. 이를 보완하기 위해 Multimodal LLM을 통해 더 풍부하고 상세한 상품 소개글을 생성합니다.

Prompt Engineering을 활용하여 원하는 형태로 상품 소개글을 생성할 수 있습니다. 다음은 상품 데이터(JSON, 이미지)를 아래 Prompt Template과 함께 Multimodal LLM에 요청한 테스트 결과 입니다. 본 글에서 Multimodal LLM으로는 Amazon Bedrock의 Claude 3 Sonnet 모델을 사용합니다.

from langchain.prompts import PromptTemplate

def gen_description(item: dict):
        template = PromptTemplate(
              template="""
                  look at the product properties and write a detailed and narrative product description in Korean.
                  Keep a lively tone and use a hook to make users want to buy the product.
        
                  Here are the properties of the product:
                  <product>
                  {product}
                  </product>
                  """,
              input_variables=['product']
        )
  
        return invoke_llm(
            text=template.format(product=str(item))
        )
나이키 남성용 AS 쇼다운 F 블랙 티셔츠를 소개합니다. 이 제품은 스포츠웨어와 패션의 완벽한 조화를 이루었습니다.당신은 테니스 코트에서 활약하는 프로 선수가 되고 싶으신가요? 아니면 단순히 편안하고 스타일리시한 옷차림을 원하시나요? 이 티셔츠 하나로 두 가지 모두를 만족시킬 수 있습니다.88% 폴리에스터와 12% 스판덱스 소재로 제작된 이 블랙 V넥 티셔츠는 부드러우면서도 땀 흡수력이 뛰어납니다. 전면에는 눈길을 사로잡는 그래픽 프린트가 있고, 목 뒷부분에는 통기성 좋은 메쉬 패널이 달려 있어 운동할 때도 쾌적합니다.나이키 로고와 브랜드 태그가 세련된 디테일을 더해주며, Dri-Fit 기술이 적용되어 땀을 잘 흡수하고 건조한 상태를 유지해 줍니다. 테니스 코트에서 활약할 때 입거나 데님 팬츠와 매치해 캐주얼 웨어로도 완벽합니다.이 티셔츠 하나로 스포티하면서도 스타일리시한 룩을 연출해 보세요. 편안함과 스타일을 모두 갖춘 나이키 AS 쇼다운 F 티셔츠를 지금 바로 만나보세요!

상품 소개글 외에도 상품을 관리하기 위한 추가적인 메타데이터를 생성할 수도 있습니다. 아래는 상품명을 한글로 표현하고, 상품의 특성과 이미지에 대해 간략한 요약과 태그들을 생성하는 예시 템플릿 입니다. 출력 포맷을 JSON으로 요청함으로써, 즉시 데이터를 활용할 수 있도록 결과물을 생성합니다.

template_fields = PromptTemplate(
      template="""
          Look at the image of the product and properties of this product and describe it in 3000 characters in Korean.
          Format the response as a JSON object with four keys: "namekor", "summary", "image_summary" and "tags".
          - "namekor": Name of product in Korean
          - "summary": Summary of product form based on appearance
          - "image_summary": Describe this image of product based on its type, color, material, pattern, and features.
          - "tags": An array of strings representing key features or properties that can represent color, pattern, material, type of the product.
          
          Here are the properties of the product:
          <product>
          {product}
          </product>
          """,
      input_variables=['product']
  )
{
    "namekor": "나이키 남성 AS 쇼다운 F 블랙 티셔츠",
    "summary": "이 제품은 스포츠웨어 브랜드 나이키에서 출시한 남성용 블랙 컬러 티셔츠입니다. 폴리에스터와 스판덱스 소재로 제작되었으며, 전면에 그래픽 프린트가 있고 뒷목 부분에 메쉬 패널이 있습니다. 나이키 스우시 로고와 브랜드 태그가 있으며 Dri-Fit 기술이 적용되어 있습니다.",
    "image_summary": "이미지는 검정색 반팔 티셔츠를 보여주고 있습니다. 전면에 녹색 라인 패턴이 있고, 왼쪽 가슴에 작은 나이키 로고가 프린트되어 있습니다. 소재는 폴리에스터와 스판덱스 혼방으로 보이며, 스포츠웨어 스타일의 디자인입니다.",
    "tags": ["블랙", "V넥", "반팔", "그래픽 프린트", "메쉬", "나이키 로고", "드라이핏", "스포츠웨어", "티셔츠"]
}

JSON 데이터와 상품 이미지를 활용하여 생성한 메타데이터가 요청한 포맷으로 응답된 것을 확인할 수 있습니다. 각 필드는 한국어로 적절히 변환 되었으며, 상품의 주요 속성들이 상세하게 설명되었습니다.

처리된 JSON 데이터는 기존 JSON 데이터에 추가하여 DynamoDB에 저장하고, 저장된 상품 데이터는 Frontend에서 아이템 정보를 렌더링하는 데 사용됩니다.

[그림7] metadata 생성 전

[그림8] Multimodal LLM을 통해 metadata 생성 (한글 상품명, 태그, 상품 설명, 이미지 요약, 샹품 요약)

[그림7]은 기본 JSON 데이터로 출력한 값이며, [그림8]은 Multimodal LLM을 통해 생성된 정보가 추가된 값들입니다. (✨ 이모지가 포함된 것은 모두 LLM으로 생성된 값입니다.) 기존 상품에 대한 설명은 없지만, LLM을 활용해 다른 속성 값들과 상품의 이미지를 이용해 더욱 풍부한 상품 소개 글을 만들어 낸 것을 볼 수 있습니다.

2. Multimodal RAG 위한 임베딩

앞서 설명한 것과 같이 Multimodal RAG를 위한 Vector Search 전략은 여러 가지가 있습니다. 사용자의 입력 쿼리에 따라 Multimodal Embedding Retrieval과 Cross-modal Retrieval을 모두 지원하기 위해 hybrid 형태의 Multi-vector Retriever 를 구현합니다.

Multimodal Embedding과 Text Embedding은 서로 다른 특성을 가지고 있어, 동일한 벡터 스토어에 혼합하여 저장하면 임베딩 공간이 혼재되어 검색 정확도가 떨어지고 속도 또한 저하될 수 있습니다. 이러한 문제를 방지하기 위해, Multimodal Embedding과 Text Embedding을 별도의 벡터 스토어에 저장하여 관리합니다. 이 방법은 검색 쿼리 유형에 따라 적절한 벡터 스토어를 선택하여 검색을 수행함으로써, 각 임베딩 특성을 최대한 활용하여 검색 정확도를 향상시킬 수 있습니다.

Multimodal Embedding과 Text Embedding을 위해 Amazon Titan Multimodal Embeddings G1Amazon Titan Text Embeddings v2을 활용하고, Amazon OpenSearch Service를 벡터 DB로 사용하여 임베딩 데이터를 저장 및 검색 합니다.

2-1. OpenSearch 인덱스 정의

먼저 OpenSearch에서 사용할 인덱스를 정의합니다. 이 인덱스 정의는 OpenSearch에서 벡터 검색과 텍스트 검색을 함께 수행할 수 있도록 설계되었습니다. vector_field는 임베딩된 벡터 값을 저장하고, text 필드는 임베딩한 텍스트 값 또는 이미지에 대한 설명글을 저장합니다. 이를 통해 연관 데이터를 검색하고 LLM으로 관련 데이터를 넘길 때 이미지에 대한 설명을 포함시킬 수 있습니다. 또한 필요한 경우 텍스트 검색도 가능합니다.

"mappings": {
      "properties": {
          "vector_field": {
              "type": "knn_vector",
              "dimension": 1024
          },
          "text": {
              "type": "text",
              "analyzer": "nori_analyzer",
              "search_analyzer": "nori_analyzer"
          },
          "metadata": {
              "properties": {
                  "id": {
                      "type": "keyword"
                  },
                  "embedType": {
                      "type": "keyword"
                  },
                  "productDisplayName": {
                      "type": "text"
                  },
                  "namekor": {
                      "type": "text"
                  },
                  "thumbnail": {
                      "type": "keyword"
                  },
                  "displayCategories": {
                      "type": "keyword"
                  }
              }
          }
      }
  }

2-2 임베딩 데이터 저장

[그림9] Embedding Vector

하나의 상품 데이터는 위와 같이 5개의 임베딩 벡터로 생성하여 저장합니다. 각 임베딩 벡터는 아래와 같이 구성하여 임베딩 됩니다. 임베딩한 방식을 구분하기 위해 metadata.embedType 에 임베딩 타입을 입력합니다.

embedType vector_field text Embedding model
image 이미지 base64 인코딩 이미지 설명글 Multimodal Embedding
image-namekor 이미지 base64 인코딩 + 상품명 상품명 Multimodal Embedding
text LLM을 통해 생성한 상품 설명글 임베딩한 텍스트 Text Embedding
text-summary JSON 필드를 문장 형태로 나열하고, 상품의 summary와 tags를 포함한 설명글 임베딩한 텍스트 Text Embedding
text-imgdesc LLM을 통해 생성한 이미지 설명글 임베딩한 텍스트 Text Embedding

Multimodal Embedding 모델은 텍스트와 이미지 등 다양한 형태의 데이터를 통합하여 학습되어, 모달리티 간 상호 연관성을 고려한 벡터를 생성합니다. 관련된 이미지를 찾거나, 이미지를 하여 관련된 텍스트를 찾는 사례에 적합합니다.

Text Embedding 모델은 방대한 텍스트 데이터를 통해 학습되어 신뢰성 높은 결과를 제공합니다. 텍스트의 의미를 깊이 이해하고 문맥을 고려한 벡터를 생성할 수 있어서, 텍스트 기반 시맨틱 검색, 질의 응답 등의 사례에 적합합니다.

Bedrock을 이용해 Multimodal Embedding, Text Embedding 하는 코드는 다음과 같습니다.

def embedding_multimodal(text=None, image=None):
    body = dict()
    if text is not None: body['inputText'] = text
    if image is not None: body['inputImage'] = image

    res = bedrock.invoke_model(
        body=json.dumps(body),
        modelId='amazon.titan-embed-image-v1',
        accept="application/json",
        contentType="application/json"
    )
    return json.loads(res.get("body").read()).get("embedding")

def embedding_text(text=None):
    body = dict()
    if text is not None: body['inputText'] = text

    res = bedrock.invoke_model(
        body=json.dumps(body),
        modelId='amazon.titan-embed-text-v2:0',
        accept="application/json",
        contentType="application/json"
    )
    return json.loads(res.get("body").read()).get("embedding")

임베딩 함수를 수행하면 벡터 배열로 값이 생성됩니다. 해당 값은 vector_field에 넣어주고, text에는 이미지에 대한 설명 또는 임베딩 대상이 된 텍스트 값을 저장합니다. metadata 에는 검색 결과를 확인할 때 참고할 수 있는 값들을 넣어줍니다.

vector = embedding_multimodal(image=image, text=text)

# Put embedding data into OpenSearch (client: OpenSearch)
client.update(
    index=index_name,
    id=id,
    body={
        'doc': {
            'vector_field': vector,
            'text': text,
            'metadata': {
                    'id': id,
                    'embedType': embedType,
                    'productDisplayName': nameeng,
                    'namekor': namekor,
                    'thumbnail': thumbnail,
                }
      },
      'doc_as_upsert': True
    },
    refresh=True
)

2-3 벡터 DB 검색

쿼리 유형에 따라 임베딩 하는 방식은 4가지로 구분할 수 있습니다.

  Multimodal Embedding Retrieval Cross-modal Retrieval 
텍스트 쿼리 Multimodal Embedding Text Embedding
이미지 쿼리 Multimodal Embedding Multimodal LLM을 통해 이미지에 대한 설명 글을 생성, 해당 텍스트를 Text Embedding

Multimodal Embedding한 데이터는 Image Vector Store 에서 검색하고, Text Embedding한 데이터는 Text Vector Store에서 검색합니다.

다양한 입력 데이터를 테스트 하기 위해 영문 텍스트 쿼리, 한글 텍스트 쿼리, 이미지 쿼리, 표 이미지 쿼리에 대해 검색 테스트를 수행했습니다. 자세한 테스트 내용은 아래 결과 스크린샷을 통해 확인할 수 있습니다. 각 쿼리 유형별(영문 텍스트 쿼리, 한글 텍스트 쿼리, 이미지 쿼리, 표 이미지 쿼리)로 테스트한 결과입니다.

2-3-1. Multimodal Embedding Retrieval

[그림11] 영문 텍스트 쿼리: red t-shirt with Emirates on it

[그림12] 한글 텍스트 쿼리: 시계

[그림11] 임베딩 시에 옷에 적힌 문자에 대한 설명을 하지 않더라도, 이미지의 시각적 특징이 고려되어 임베딩 되었기 때문에, 옷에 적힌 텍스트로 쿼리 했을 때도 관련 데이터를 검색할 수 있습니다.

[그림12] 한글 텍스트 쿼리는 검색 성능이 낮습니다. Multimodal Embedding 모델은 주로 영어 데이터 기반으로 학습되어 있어, 한글 텍스트와 이미지 간의 관계를 제대로 이해하지 못합니다.

[그림13] 이미지 쿼리: 시계

[그림14] 표 이미지 쿼리

[그림13] 시계 이미지로 쿼리를 했을 때, 시각적으로 유사한 시계들이 검색되는 것을 확인할 수 있습니다. 이미지 쿼리는 텍스트 쿼리보다 시각적으로 유사한 항목을 찾는 데 유리합니다.

[그림14] 표로 되어 있는 쿼리 이미지에는 상품의 카테고리(Accessories > Watches > Watches)가 텍스트로 적혀 있습니다. 이미지 내의 텍스트를 인식하고 이미지를 검색할 수 있습니다.

2-3-2. Cross-modal Retrieval

[그림15] 한글 텍스트 쿼리: 시계

[그림16] 영문 텍스트 쿼리: red t-shirt with Emirates on it

[그림15] Text Embedding 모델은 Multimodal Embedding 모델에 비해 상대적으로 더 많은 언어에 대해 방대하게 학습이 되어 있어, 한글 텍스트 쿼리도 잘 검색해냅니다. 특히, 사용한 Amazon Titan Text Embeddings v2은 100개 이상의 언어를 지원합니다.

[그림16] 이미지에 대해 묘사한 데이터도 검색되지만, 쿼리와 유사한 문맥의 데이터도 검색되는 것을 볼 수 있습니다. ID 의 경우, page_content 내용이 축구 유니폼과 연관되어서 검색 되었을 가능성이 있고, 티셔츠에 특정 텍스트가 프린트 되어 있다는 문맥에서 유사하다고 판단되어 검색될 수 있습니다.

[그림17] 이미지 쿼리: 시계

[그림18] 이미지 쿼리: 표

[그림17] 쿼리 이미지에 대해 Multimodal LLM으로 이미지 설명글을 생성하고, 해당 텍스트를 통해 Text Embedding 후 관련 데이터를 검색합니다. LLM이 설명한 글의 내용과 유사한 데이터들이 검색됩니다.

[그림18] Multimodal LLM을 통해 표로 되어 있는 쿼리 이미지의 내용을 해석하고, 관련된 데이터를 검색합니다. 이미지에 대해 텍스트로 묘사한 데이터가 저장되어 있어, Text Embedding 만으로도 이미지에 대한 검색이 가능합니다.

테스트 결과를 정리해보면 다음과 같습니다.

Multimodal Embedding Retrieval Cross-modal Retrieval
영문 텍스트 쿼리 이미지의 시각적 특징을 고려하여 검색 쿼리의 의도에 맞는 데이터 검색
한글 텍스트 쿼리 검색 성능 낮음
이미지 쿼리 시각적으로 유사한 이미지 검색 이미지에 대한 설명 글을 생성하고, 해당 내용과 유사한 데이터 검색
표 이미지 쿼리 이미지 내의 텍스트를 인식하고 검색

3. 챗봇 애플리케이션

챗봇은 이전 대화 기록을 기반으로 응답을 생성해야 하므로 텍스트 쿼리의 문맥 이해가 중요합니다. 이를 위해 다음과 같은 방식으로 시스템을 구성합니다.

[그림19] 챗봇 애플리케이션 동작 과정

Frontend와 Backend 코드는 Container 형태로  Amazon Elastic Kubernetes Service 에 배포됩니다. 애플리케이션 배포에 관한 내용은 해당 글의 영역 밖으로, 별도 설명은 생략합니다.

1. 사용자는 Frontend 페이지에서 텍스트 또는 이미지를 입력합니다.

2. 사용자의 요청 쿼리는 HTTPS를 통해 Backend로 전달됩니다.

3. 쿼리는 Titan Embedding 모델을 통해 벡터로 변환됩니다.

    • 텍스트 쿼리문맥 이해가 중요한 만큼 Text Embedding 하여 Text Vector Store에서 관련 문서를 검색합니다.
    • 이미지 쿼리Multimodal Embedding 해서 Image Vector Store에서 관련 문서를 검색합니다.

4. 임베딩 된 벡터 값을 통해 벡터 DB에서 관련 문서를 검색합니다. (Multi-vector Retrieval)

    • 텍스트 쿼리: Text Vector Store에서 Hybrid Search (Lexical Search + Semantic Search) 합니다.
    • 이미지 쿼리: Image Vector Store에서 Vector Search 합니다.

5. Chat ID를 통해 Redis에서 이전 대화 히스토리를 가져옵니다.

    • Amazon ElasticCache는 인메모리 데이터 스토어로, 빠른 읽기 및 쓰기 성능을 제공합니다.

6. 사용자의 쿼리, 관련 문서, 이전 대화 히스토리를 기반으로 프롬프트가 생성됩니다.

7. Multimodal LLM에 프롬프트를 넘겨 응답을 생성합니다.

8. Multimodal LLM이 비동기적으로 응답을 생성하면, Server-Sent Events (SSE)를 통해 실시간으로 생성된 토큰을 Frontend로 전송합니다.

3-1. Chat Memory

LLM 모델은 상태를 저장하지 않습니다. 대화 형태로 질의를 이어 나가기 위해서는 이전 대화 내용을 프롬프트에 포함해 전달해야 합니다. 대화 기록을 저장하기 위해 LangChain의 Memory를 사용합니다.

from langchain.memory import RedisChatMessageHistory, ConversationTokenBufferMemory

def get_redis_chat(self, chatId: str):
    return RedisChatMessageHistory(
            session_id=chatId,
            url=f"redis://{self.host}:{self.port}",
            key_prefix=self.keyPrefix
        )

def get_memory(self, chatId: str, llm: BaseChatModel):
    return ConversationTokenBufferMemory(
            llm=llm,
            max_token_limit=3000,
            memory_key="history",
            chat_memory=self.get_redis_chat(chatId),
            ai_prefix="Assistant",
            human_prefix="Human",
            return_messages=True
        )

def load_memory(chatId):
    memory = get_memory(chatId)
    return memory.load_memory_variables({})["history"]

RedisChatMessageHistory 는 Redis를 사용하여 대화 기록을 저장 및 관리하고, ConversationTokenBufferMemory 를 이용해 최근 대화의 히스토리만 유지하도록 하여 LLM 입력 토큰 제한을 초과하지 않도록 합니다.

3-2. Prompt

Multimodal LLM에 요청하기 위해, 이전 대화 내용과 검색된 문서들을 하나의 프롬프트에 넣어 생성합니다.

def get_prompt(text: str, image: str = None, conversations = None, context: List = None):
    content = []

    if image:
        content.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/webp;base64,{image}",
            },
        })
    
     text = PromptTemplate(
            template=FASHION_PROMPT_TEMPLATE,
            input_variables=["question", "conversations", "search"]
        ).format(question=text, conversations=conversations, search=context)

    content.append({
        "type": "text",
        "text": text
    })

    messages = [
        SystemMessage(content="You are an assistant who recommends appropriate fashion products."),
        HumanMessage(
            content=content
        )
    ]

    return messages

3-3. LLM

Bedrock과 LangChain을 이용해 LLM 모델로 사용할 chat model을 정의하고, 위에서 생성한 prompt와 함께 ainvoke 로 비동기 호출합니다.

from langchain_aws.chat_models import ChatBedrock
from langchain.callbacks import AsyncIteratorCallbackHandler

llm = ChatBedrock(
        model_id = 'anthropic.claude-3-sonnet-20240229-v1:0',
        client = bedrock,
        streaming = True,
        callbacks = [AsyncIteratorCallbackHandler()],
        model_kwargs = {
            'anthropic_version': 'bedrock-2023-05-31',
            'max_tokens': 4096,
       },
    )

context = retrieve(text=question, image=image)
prompt = get_prompt(
    text='빨간색 티셔츠트 추천해줘. 한 문장으로 말해',
    conversations=load_memory(chatId),
    context=context
)
await llm.ainvoke(prompt)

LLM 호출 결과물은 다음과 같습니다. content에 LLM이 생성한 답변이 포함되어 있고, 프롬프트 토큰 수, 사용한 토큰 수도 확인할 수 있습니다.

AIMessage(
    content=(
        '랭글러 남성 모터 라이더 레드 티셔츠를 추천합니다. 이 티셔츠는 라운드 넥 디자인에 오토바이 그래픽 프린트가 있어 캐주얼하면서도 스타일리시한 룩을 연출할 수 있습니다.'
    ),
    additional_kwargs={
        'usage': {'prompt_tokens': 4626, 'completion_tokens': 108, 'total_tokens': 4734},
        'stop_reason': 'end_turn',
        'model_id': 'anthropic.claude-3-sonnet-20240229-v1:0'
     },
     response_metadata={
        'usage': {'prompt_tokens': 4626, 'completion_tokens': 108, 'total_tokens': 4734},
        'stop_reason': 'end_turn',
        'model_id': 'anthropic.claude-3-sonnet-20240229-v1:0'
     },
     id='run-73c8d294-5426-4aba-805c-737d9d172502-0’
)

4. 결과 테스트

전체 데모영상 입니다.

[그림20] 기본 Multimodal LLM + Prompt Engineering

패션 상품 추천 어시스턴트로 페르소나를 정의하고 이미지로 질의하면, 이미지 내의 패션 상품을 설명합니다. Chat Memory를 통해 대화 기록을 저장/로드하여, 이어지는 질문에도 context를 유지하며 답변합니다.

[그림21] 기본 Multimodal LLM + Prompt Engineering

[그림22] MULTIMODAL_SEARCH: 텍스트 쿼리

[그림21] 텍스트로 질문 했을 때, 연관된 데이터로 이미지와 상품명이 함께 임베딩 된 데이터들을 검색하고, 이를 기반으로 답변을 생성합니다. 검색된 연관 데이터에는 metadata가 포함되어 있어, 검색된 아이템 정보를 렌더링 할 수 있습니다.

[그림22] 이미지로 질문 했을 때에도, 유사한 검은색 가방 이미지를 찾아 답변을 생성합니다.

[그림23] MULTIMODAL_SEARCH: 이미지 쿼리

[그림24] CROSS_MODAL_SEARCH: 이미지 쿼리

[그림23] Multimodal LLM은 이미지를 이해할 수 있지만, Text Embedding 만으로는 이미지를 이해하고 검색할 수 없습니다. 내부적으로 Multimodal LLM 모델을 통해 보이는 이미지에 대해 묘사하고, 해당 텍스트를 Text Embedding 하여 관련 데이터를 검색합니다. 실제로 검색된 데이터는 이미지 데이터가 아닌, 이미지에 대해 텍스트로 묘사한 임베딩 데이터 입니다.

[그림24] 이미지로 쿼리 했을 때의 케이스와 유사하게, 특정 이미지에 대한 설명을 했을 때 Text Embedding 된 데이터로 잘 검색되는 것을 볼 수 있습니다.

[그림25] MULTI_VECTOR_SEARCH: 이미지, 텍스트 쿼리

이미지와 텍스트 쿼리가 동시에 들어오면, 각각 embedding 관련 데이터를 검색하여 모두 활용합니다.

일반적인 Multimodal LLM 질의 시나리오는 이미지를 첨부하고 이미지와 연관된 질문을 텍스트로 하는 것입니다. 여기서 입력 이미지와 텍스트를 함께 임베딩 하면 관련 데이터 검색이 더 어려워질 수 있습니다. 임베딩 하는 과정에서 정보 손실이 발생할 수 있기 때문입니다.

예를들어, 빨간 티셔츠 이미지와 “이거 파란색으로 찾아줘” 같은 관련 없는 텍스트를 함께 입력하면 모델의 임베딩 성능이 떨어질 수 있습니다. 따라서, 두 가지 정보를 개별적으로 검색해 사용합니다.

[그림26] Multimodal 인덱싱 및 검색 방법

정리하면, 동일한 소스 데이터와 동일한 입력 쿼리를 사용하더라도, 지식 베이스 구축을 위한 인덱싱 방법과 검색 쿼리 형태에 따라 검색 결과가 달라집니다.

(a) 원본 이미지를 저장하면, 유사한 이미지를 검색할 수 있습니다.
(b) 이미지나 표로부터 텍스트 캡션을 생성해서 저장하면, 이미지나 테이블 내의 정보를 텍스트로 검색할 수 있습니다.
(c) 텍스트 문서를 요약해서 저장하면, Chunk로 인해 유실 또는 왜곡되는 정보를 방지할 수 있습니다.

검색 쿼리의 형태도 중요합니다.

(d) 원본 이미지와 텍스트를 함께 입력하면, 텍스트와 이미지의 상호 보완 효과를 얻을 수 있습니다.
(e) 이미지로부터 캡션을 생성해 텍스트와 함께 입력하면, 사용자의 의도 파악이 강화됩니다.

이러한 다양한 인덱싱 및 검색 방법을 조합하여 최적의 검색 결과를 얻을 수 있습니다.

결론

이 글에서는 Multimodal RAG를 활용한 다양한 접근 방식(Multimodal Embedding Retrieval, Cross-modal Retrieval, Multi-vector Retrieval)과 이를 기반으로 한 상품 검색 챗봇의 구현 방안을 소개했습니다. Multimodal RAG는 개인화된 데이터 기반의 챗봇 구현이 가능하며, 텍스트와 이미지를 결합한 검색을 지원합니다. 이는 특히 패션 상품과 같은 시각적 정보가 중요한 분야에서 유용하게 활용될 수 있습니다.

사용자는 Multimodal 데이터를 활용해 직관적이고 편리한 검색을 경험하고, 챗봇과의 양방향 대화를 통해 검색의 효율성을 극대화할 수 있습니다. 솔루션 제공자는 카테고리나 태그 외에도 이미지와 복합적인 메타데이터를 활용해, 별도의 LLM 모델 fine-tuning 없이도 효율적인 GenAI 검색 시스템을 구축할 수 있습니다.

효과적인 Multimodal RAG 기반 검색 시스템의 핵심은 텍스트와 이미지 데이터를 효과적으로 임베딩 하고 검색하는 것입니다. 데이터의 유형과 검색 시나리오, 데이터 리소스를 바탕으로 가장 적합한 방법을 선택해, 보다 정교하고 효율적인 검색 서비스를 구축할 수 있습니다.

Yoojung Lee

Yoojung Lee

이유정 Solutions Architect는 자율주행, 디지털트윈, AI 연구개발 경험을 바탕으로 고객이 비즈니스 목표를 달성하도록 아키텍처 설계 가이드와 기술을 지원하고 있습니다.