AWS 기술 블로그

Amazon OpenSearch Service Integration 기능을 활용한 손쉬운 임베딩 파이프라인 구성

서론

최근 자체적인 생성형 AI를 만들기 위한 여러가지 노력들이 있습니다. 특히 검색 증강 생성(Retrieval Augmented Generation, RAG) 모델을 활용하여 외부 소스의 정보를 사전에 지식 데이터베이스로 사용하며 생성형 AI 모델의 정확성과 신뢰성을 향상시키기 위해 다양한 방법으로 실험이 진행 되고 있습니다. Amazon OpenSearch Service는 Vector Database로 많은 사랑을 받고 있으며 2.9 버전부터 Neural Search 기능이 출시됨에 따라 AI/ML 모델과 통합하여 Semantic 검색 기능을 강화 할 수 있게 되었습니다.

Neural Search 기능은 Embedding Model과 함께 동작하며, 이러한 색인 파이프라인을 구성하는 것은 복잡한 작업입니다. 전통적인 방식에서는 위 그림과 같이 먼저 색인 파이프라인에서 임베딩 모델을 사용해 데이터를 임베딩 처리한 후, 데이터를 색인합니다. 그리고 나서, 백엔드 API 시스템에서는 검색 키워드를 임베딩한 후 Semantic 검색을 수행합니다. 이 과정은 Semantic 검색 기능을 구현하기 위한 방법으로 사용되었습니다.

이 게시물에서는 OpenSearch 콘솔에서 외부 모델에 대한 AI/ML 커넥터를 구성하여 복잡한 색인 절차와 검색 절차를 단순화 하는 방법에 대해 알아보겠습니다.

솔루션

기본적으로 Amazon OpenSearch Service에서는 서드파티 ML 플랫폼과 연결을 위한 커넥터(Connector)를 직접 생성할 수 있습니다. 이 게시물에서는 OpenSearch 콘솔을 사용하여 OpenSearch 커넥터를 조금 더 간편하게 만들 수 있는 방법을 설명합니다. 특히, Amazon Bedrock을 연결해서 Cohere multilingual 모델을 사용해 데이터를 색인하고 검색하는 방법을 안내합니다. 오픈소스에서 제공하는 더 많은 커넥터를 확인하시려면 여기를 참고하세요.

사전 준비 사항

Semenatic 검색을 수행하기 위한 OpenSearch 도메인(Domain)이 이미 생성됐다고 가정합니다. OpenSearch Dashboards에 접근하여 Security > Roles > ml_full_access 역할의 Backend roles[1]LambdaInvokeOpenSearchMLCommonsRole 라는 이름의 AWS Identity and Access Management(IAM) 등록해 주어야 합니다. OpenSearch의 ML 커넥터는 이 게시글의 Step1에서 생성할 LambdaInvokeOpenSearchMLCommonsRole 이라는 IAM 역할을 사용합니다.

  1. Management 메뉴에서 Security 를 선택합니다.
  2. Role 을 선택합니다.
  3. ml_full_access [2] 검색합니다.
  4. 클릭 후 상세페이지 에서 Mapped users 선택합니다.
  5. Manage Mapping 클릭 합니다.
  6. Backend Role에 아래와 같이 arn:aws:iam::<account id>:role/LambdaInvokeOpenSearchMLCommonsRole 규칙으로 IAM 역할을 등록합니다.

Step 1 : AWS CloudFormation을 사용한 모델 배포와 커넥터 생성

ML 커넥터는 AWS CloudFormation를 활용해서 생성됩니다. 사전 준비가 끝났다면 OpenSearch 콘솔의 탐색 창에서 Integrations을 선택해 지원 가능한 모델을 확인합니다.

이 게시글에서는 Integration with Cohere Embed through Amazon Bedrock 을 사용하여 임베딩 작업을 진행하도록 하겠습니다. 만약 다른 모델이 필요하다면 SageMaker와 연동하여 더 많은 모델을 OpenSearch와 통합할 수 있습니다. 자세한 내용이 궁금하시다면 ‘Power neural search with AI/ML connectors in Amazon OpenSearch Service’ 게시글을 참고하시기 바랍니다.

Configure domain을 클릭하고 도메인이 VPC 내 설치된 경우 Configure VPC domain을 선택합니다. 만약 Public에서 사용하는 환경이라면, Configure public domain을 선택할 수 있습니다.

  • Amazon OpenSearch Endpoint: OpenSearch Dashboards URL이 아니라, Domain endpoint를 입력함에 유의합니다.
  • Lambda Invoke OpenSearch ML Commons Role Name: 이 게시글의 사전준비 사항에서 OpenSearch의 Backend Role로 등록한 LambdaInvokeOpenSearchMLCommonsRole 가 이 stack에서 생성됩니다. 원하는 경우 이름을 변경할 수 있지만, 사전준비 사항에도 바꿔줘야 합니다.

자신이 만든 도메인 설정에 알맞게 선택하면 CloudFormation으로 이동하여 아래의 구성을 배포하게 됩니다.

CloudFormation으로 생성된 AWS Lambda는 OpenSearch Service에 연결되어 커넥터를 생성하고 모델을 등록하는 과정을 자동으로 진행합니다. 수행되는 작업이 궁금하시다면 bedrock_connector_titan_embedding_blueprint를 참고하시기 바랍니다. CloudFormation 수행이 완료되면 Outputs 에서 BedrockEndpoint, ConnectorId, ModelId를 확인할 수 있습니다. 이를 활용해 OpenSearch Dashboards에서 Ingest Pipeline과 Search Pipeline을 만들어 임베딩(Embedding)을 자동화 해보도록 하겠습니다.

Step 2: 배포된 모델 확인

모델이 정상적으로 Amazon OpenSearch Service에 등록되었는지 확인하려면 OpenSearch Dashboards의 Dev Tools에서 REST API를 사용하면 됩니다.

GET _plugins REST API에서 배포된 모델의 상세 정보를 확인 할 수 있습니다.

GET _plugins/_ml/models/<modelid>

응답의 DEPLOYED 상태는 모델이 OpenSearch 도메인에 성공적으로 배포되었음을 나타냅니다. 만약 해당 기능에 어려움을 느낀다면 OpenSearch Dashboards의 Machine Learning 메뉴에서 배포된 모델의 상세 정보를 확인할 수 있습니다.

Step 3: Ingest Pipeline 생성

Ingest pipelines은 문서가 인덱스로 색인될 때 문서에 적용되는 일련의 프로세서입니다. 파이프라인의 각 프로세서는 데이터 필터링, 변환, 보강과 같은 작업을 수행할 수 있습니다. 이 게시글에서는 text_embedding 프로세서를 활용하도록 하겠습니다. 이 외에 다른 프로세서를 확인하고 싶으시다면 ingest processors를 참고하시기 바랍니다.

passage_textpassage_embedding은 Step4에서 생성할 인덱스의 필드명입니다. 이 passage_text 필드에 텍스트가 들어오면, passage_embedding 필드로 0aKyCY8B7aMN5RBvXV01 모델을 이용해서 텍스트를 임베딩하라는 의미입니다.

PUT /_ingest/pipeline/bedrock-cohere-ingest-pipeline
{
  "description": "Test Cohere Embedding pipeline",
  "processors": [
    {
      "text_embedding": {
        "model_id": "0aKyCY8B7aMN5RBvXV01",
        "field_map": {
          "passage_text": "passage_embedding"
        }
      }
    }
  ]
}

Step 4: 인덱스(Index )생성

이전 단계에서 생성한 ingest pipeline을 활용해서 인덱스를 만들어 보도록 하겠습니다. 이때, 인덱스의 기본 파이프라인으로 이전에 생성한 bedrock-cohere-ingest-pipeline 사용할 수 있습니다.

참고로, 백터 검색을 위해 settings"index.knn": true 로 구성하였고, 앞서 설명한 것처럼 Ingest시 데이터를 입력받을 passage_text 필드와 default_pipeline으로 임베딩된 벡터가 들어갈 passage_embedding 도 구성하였습니다. typeknn_vector로 지정해야 벡터를 인덱싱할 수 있으며, dimension은 임베딩하는 모델에 따라 값이 달라지게 됩니다. method의 경우 여러분이 원하는 enginespace_type, name, parameters를 지정할 수 있습니다. 자세한 사항은 이 게시물의 범위를 넘어서기 때문에 이곳을 참고해 주십시오.

PUT /bedrock-nlp-index-v0.2
{
  "settings": {
    "index.knn": true,
    "default_pipeline": "bedrock-cohere-ingest-pipeline"
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "text"
      },
      "passage_embedding": {
        "type": "knn_vector",
        "dimension": 1024,
        "method": {
          "engine": "lucene",
          "space_type": "l2",
          "name": "hnsw",
          "parameters": {}
        }
      },
      "passage_text": {
        "type": "text"
      }
    }
  }
}

Step 5: 샘플(Sample) 데이터를 이용한 색인

이제 테스트를 위하여 샘플 데이터를 생성하고 검색을 수행해 보도록 하겠습니다. 데이터는 json 포맷의 10개 문장으로 이루어져 있습니다. 아래의 샘플 데이터를 활용하여 sample-data.json 파일을 만들고 bulk API로 색인합니다. 아래 opensearch-domain-end-pointusername:password는 각자의 환경에 맞게 변경하세요.

curl -XPOST -u 'username:password' 'https://opensearch-domain-end-point/_bulk' --data-binary @sample-data.json -H 'Content-Type: application/json'
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "아침 일찍 일어나서 창문을 열자 신선한 바람이 방안으로 스며들었다.", "id": "c1"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "요즘 도시에서는 자연을 접하기 어려워 공원에서 시간을 보내는 것이 새로운 취미가 되었다.", "id": "c2"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "그녀는 오래된 서점의 구석에서 흥미로운 책을 발견하고는 시간 가는 줄 몰랐다.", "id": "c3"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "해질녘 해변을 거닐다 보면 일상의 스트레스에서 잠시 벗어날 수 있어 좋다.", "id": "c4"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "친구와 오랜만에 만나 함께 영화를 보고 저녁 식사를 했다.", "id": "c5"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "올해의 목표는 새로운 언어를 배우고 다양한 문화를 경험하는 것이다.", "id": "c6"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}
{"passage_text": "기술의 발전으로 우리의 일상생활이 훨씬 편리해졌지만, 때로는 그로 인한 부작용도 발생한다.", "id": "c7"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "책상 위에 쌓인 책들 사이에서 필요한 자료를 찾기 위해 몇 시간을 보냈다.", "id": "c8"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "저녁마다 산책을 하며 주변의 소리와 향기에 집중하려고 노력한다.", "id": "c9"}
{"index": {"_index": "bedrock-nlp-index-v0.2"}}
{"passage_text": "전 세계의 다양한 요리를 맛보는 것은 그 나라의 문화를 이해하는 데 도움이 된다.", "id": "c10"}

Step 6: 검색 결과 확인

색인이 완료되면 데이터셋을 실제로 검색해보며 정상적으로 백터화 되었는지 확인해 보도록 하겠습니다. 우선, 간단한 검색 쿼리로 모든 데이터를 가져와 필요한 백터가 생성되었는지 확인해 보겠습니다. 아래 스크린샷과 같이 knn_vectors 로 정의된 필드 passage_embedding에 정상적으로 백터화되어 색인된 것을 확인할 수 있습니다.

지금까지는 데이터를 벡터화하여 색인하는 과정에서 임베딩을 간단하게 하는 법을 알아보았습니다. 검색하는 시점에서도 사용자가 자연어를 입력하면 임베딩 모델을 사용해서 이를 벡터화하고, 인덱스에 이미 색인된 벡터들과의 유사도를 비교해야합니다. 이때, neural 기능을 활용하여 임베딩을 위한 파이프라인을 추가로 만들지 않고 간편하게 Semantic 검색을 수행할 수 있습니다.

아래 요청에서는 결과값에 벡터를 굳이 표현할 필요가 없으므로 excludes 를 이용해서 벡터가 있는 필드를 제외하였습니다. model_id 는 색인 시점에 사용한 모델과 같은 model_id를 재활용할 수 있습니다.

GET /bedrock-nlp-index-v0.2/_search
{
  "_source": {
    "excludes": [
      "passage_embedding"
    ]
  },
  "query": {
    "neural": {
      "passage_embedding": {
        "query_text": "자료를 찾는 방법",
        "model_id": "0aKyCY8B7aMN5RBvXV01",
        "k": 5
      }
    }
  }
}

정리

이 게시물의 단계에 따라 기능 검증이 완료 되었다면 CloudFormation으로 생성된 리소스를 삭제하여 비용을 절감할 수 있습니다. 아래의 단계를 수행하세요.

  1. CloudFormation 콘솔에서 스택 세부 정보 페이지로 이동합니다.
  2. Delete를 선택합니다.
  3. OpenSearch 콘솔로 이동합니다.
  4. 생성한 Domain의 세부정보로 이동합니다
  5. Delete를 선택합니다.

결론

RAG모델을 구성 함에 있어 데이터 색인과 검색 파이프라인은 매우 큰 비중을 차지하고 있는 부분입니다.

OpenSearch 커넥터를 활용하면, 데이터 색인과 검색을 위한 백엔드 서버 애플리케이션에서 별도의 임베딩 로직을 구현할 필요가 없어집니다. 이는 Ingest Pipeline과 함께 내장된 모델을 사용하여 데이터를 자동으로 임베딩하고, 이를 기반으로 Semantic 검색을 수행할 수 있게 해줍니다. 결과적으로, 이러한 접근 방식은 시스템의 복잡성을 크게 줄이면서도 검색 품질을 향상시킵니다. 특히, 백엔드 서버에서 백터화를 위한 추가적인 서버 리소스 할당이나 개발 작업 없이도 효율적이고 간편하게 Semantic 검색을 구현할 수 있다는 점에서 매우 유리합니다.

이로써 개발자들은 더욱 집중적으로 애플리케이션의 다른 중요 기능들을 개선하는 데 시간을 할애할 수 있게 되며, 전반적인 시스템 유지보수도 간소화할 수 있습니다. 이러한 커넥터에 대해 더 자세히 알고 싶으시다면 Amazon OpenSearch Service ML 커넥터용 AWS 서비스, 시맨틱 검색을 위한 AWS CloudFormation 템플릿 통합, 외부 ML 플랫폼용 커넥터 만들기를 참조하세요.

[1] OpenSearch에서는 외부 인증 시스템과의 연결을 위해 백엔드 역할(Backend role)을 사용합니다. 이 게시물에서는 Lambda가 OpenSearch에 접근해서 여러 작업을 하므로, 이 Lambda가 가지고 있는 역할을 OpenSearch에 연결(mapping)합니다.
[2] OpenSearch에서도 자체적으로 권한관리를 하는데, ml_full_acess의 경우 OpenSearch에서 ML 작업에 필요한 전체 권한을 모아놓은 역할입니다.

Sewoong Kim

Sewoong Kim

김세웅 클라우드 아키텍트는 AWS Professional Services 팀의 일원으로서 컨테이너와 서버리스를 중심으로 AWS 기반의 서비스를 구성하고자 하는 고객들께 클라우드 환경에 최적화된 아키텍처를 구성하고 컨설팅하며 지원하는 역할을 수행하고 있습니다.

Sung-il Kim

Sung-il Kim

김성일 Sr Analytics Specialist SA는 데이터 엔지니어와 소프트웨어 엔지니어로 다양한 서비스를 설계하고 개발을 경험하였습니다. 현재는 아마존 웹서비스(AWS)에서 분석 서비스를 잘 활용할 수 있도록 엔터프라이즈 고객을 대상으로 기술적인 도움을 드리고 있습니다.