semantic_text 하이브리드

원본 보기

semantic_text 하이브리드

이 튜토리얼에서는 semantic_text 필드 타입과 어휘 검색용 text 필드를 함께 사용하는 하이브리드 검색을 단계별로 살펴봅니다. 끝까지 따라 하면 다음을 할 수 있습니다.

  • 하이브리드 검색을 위해 텍스트 내용과 벡터 임베딩을 모두 저장할 수 있는 인덱스 매핑 만들기
  • 동일한 텍스트가 시맨틱 검색용으로 임베딩되면서 전문 검색에도 사용되도록 문서 인제스트하기
  • retriever 또는 ES|QL로 하이브리드 쿼리 실행하기

하이브리드 검색에서 시맨틱 검색은 의미를 기준으로, 어휘 검색은 텍스트 유사도를 기준으로 점수를 매깁니다. 둘을 결합하면 각각을 단독으로 쓸 때보다 더 견고한 순위를 얻는 경우가 많습니다.

Elastic Stack에서 하이브리드 검색을 사용하는 권장 방법은 semantic_text 워크플로를 따르는 것입니다. 임베딩용 추론 인제스트 파이프라인을 직접 구성할 필요가 없으면서도 키워드 방식 매칭을 위한 전용 text 필드를 그대로 유지할 수 있습니다.

Tip

이 튜토리얼의 curl 예제를 실행하려면 다음 환경 변수를 설정하세요.

export ELASTICSEARCH_URL="your-elasticsearch-url"
export API_KEY="your-api-key"
		

API 키를 생성하려면 전역 검색창에서 API keys를 검색하세요. 엔드포인트와 자격 증명을 찾는 방법 자세히 보기.

대상 인덱스에는 시맨틱 검색용 임베딩과 전문 검색용 원본 텍스트 필드가 모두 담깁니다. 이러한 구조 덕분에 시맨틱 검색과 전문 검색을 결합할 수 있습니다.

추론은 Elastic Inference Service에서 실행하거나 직접 운영하는 머신 러닝 노드에서 실행할 수 있습니다.

Tip

대규모 밀집 벡터 배포에서는 BBQ 같은 양자화 전략으로 메모리 사용량을 줄일 수 있습니다. 자세한 내용은 벡터 스토리지 최적화를 참고하세요.

이 예제에서는 Elastic Inference Service를 사용해 하이브리드 검색용 인덱스를 만듭니다. 임베딩은 semantic_text 필드 타입의 기본 추론 모델로 생성됩니다.

				PUT semantic-embeddings
					{
  "mappings": {
    "properties": {
      "content_embedding": {
        "type": "semantic_text"
      },
      "content": {
        "type": "text",
        "copy_to": "content_embedding"
      }
    }
  }
}
		
  1. 시맨틱 검색용으로 생성된 임베딩을 담을 필드의 이름입니다.
  2. 임베딩을 담을 필드는 semantic_text 필드입니다. inference_id를 지정하지 않았으므로 기본 추론 엔드포인트가 사용됩니다.
  3. 어휘 검색용 원본 텍스트를 담을 필드의 이름입니다.
  4. content 필드에 저장된 텍스트 데이터는 content_embedding으로 복사되어 추론 엔드포인트에서 처리됩니다.
curl -X PUT "${ELASTICSEARCH_URL}/semantic-embeddings" \
     -H "Content-Type: application/json" \
     -H "Authorization: ApiKey ${API_KEY}" \
     -d '{
       "mappings": {
         "properties": {
           "content_embedding": {
             "type": "semantic_text"
           },
           "content": {
             "type": "text",
             "copy_to": "content_embedding"
           }
         }
       }
     }'
		
  1. 시맨틱 검색용으로 생성된 임베딩을 담을 필드의 이름입니다.
  2. 임베딩을 담을 필드는 semantic_text 필드입니다. inference_id를 지정하지 않았으므로 기본 추론 엔드포인트가 사용됩니다.
  3. 어휘 검색용 원본 텍스트를 담을 필드의 이름입니다.
  4. content 필드에 저장된 텍스트 데이터는 content_embedding으로 복사되어 추론 엔드포인트에서 처리됩니다.
Important

운영 환경에서는 semantic_text 필드에 inference_id를 명시적으로 지정하는 것을 권장합니다. 기본 엔드포인트는 버전과 배포 유형에 따라 달라질 수 있으며, 그 결과 임베딩 모델이 뒤섞이거나 순위 결과가 일관되지 않는 등 문제가 발생할 수 있습니다.

아래는 .elser-2-elasticsearch 추론 엔드포인트와 함께 직접 운영하는 ML 노드를 사용해 인덱스 매핑을 만드는 예제입니다.

				PUT semantic-embeddings
					{
  "mappings": {
    "properties": {
      "content_embedding": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "content": {
        "type": "text",
        "copy_to": "content_embedding"
      }
    }
  }
}
		
  1. 시맨틱 검색용으로 생성된 임베딩을 담을 필드의 이름입니다.
  2. 임베딩을 담을 필드는 semantic_text 필드입니다.
  3. elasticsearch 서비스용 .elser-2-elasticsearch 사전 구성 추론 엔드포인트가 사용됩니다.
  4. 어휘 검색용 원본 텍스트를 담을 필드의 이름입니다.
  5. content 필드에 저장된 텍스트 데이터는 content_embedding으로 복사되어 추론 엔드포인트에서 처리됩니다.
curl -X PUT "${ELASTICSEARCH_URL}/semantic-embeddings" \
     -H "Content-Type: application/json" \
     -H "Authorization: ApiKey ${API_KEY}" \
     -d '{
       "mappings": {
         "properties": {
           "content_embedding": {
             "type": "semantic_text",
             "inference_id": ".elser-2-elasticsearch"
           },
           "content": {
             "type": "text",
             "copy_to": "content_embedding"
           }
         }
       }
     }'
		
  1. 시맨틱 검색용으로 생성된 임베딩을 담을 필드의 이름입니다.
  2. 임베딩을 담을 필드는 semantic_text 필드입니다.
  3. elasticsearch 서비스용 .elser-2-elasticsearch 사전 구성 추론 엔드포인트가 사용됩니다.
  4. 어휘 검색용 원본 텍스트를 담을 필드의 이름입니다.
  5. content 필드에 저장된 텍스트 데이터는 content_embedding으로 복사되어 추론 엔드포인트에서 처리됩니다.
응답 예시

인덱스 매핑을 준비했으면 이제 데이터를 추가할 수 있습니다. content 필드만 채우면 됩니다. Elasticsearch는 그 값을 어휘 검색용 text로 저장하고, copy_to가 같은 값을 content_embedding 필드로 복제합니다. content_embeddingsemantic_text 타입이므로 Elasticsearch가 해당 값을 추론 엔드포인트로 보내고 그 결과 임베딩을 저장합니다.

_bulk API로 동일한 샘플 문서를 인제스트하세요.

				POST _bulk
					{ "index": { "_index": "semantic-embeddings", "_id": "1" } }
{ "content": "After running, cool down with light cardio for a few minutes to lower your heart rate and reduce muscle soreness." }
{ "index": { "_index": "semantic-embeddings", "_id": "2" } }
{ "content": "Marathon plans stress weekly mileage; carb loading before a race does not replace recovery between hard sessions." }
{ "index": { "_index": "semantic-embeddings", "_id": "3" } }
{ "content": "Tune cluster performance by monitoring thread pools and refresh interval." }
		
curl -X POST "${ELASTICSEARCH_URL}/_bulk" \
     -H "Content-Type: application/x-ndjson" \
     -H "Authorization: ApiKey ${API_KEY}" \
     --data-binary @- << 'EOF'
{ "index": { "_index": "semantic-embeddings", "_id": "1" } }
{ "content": "After running, cool down with light cardio for a few minutes to lower your heart rate and reduce muscle soreness." }
{ "index": { "_index": "semantic-embeddings", "_id": "2" } }
{ "content": "Marathon plans stress weekly mileage; carb loading before a race does not replace recovery between hard sessions." }
{ "index": { "_index": "semantic-embeddings", "_id": "3" } }
{ "content": "Tune cluster performance by monitoring thread pools and refresh interval." }
EOF
		
응답 예시

오류가 발생하면 인덱스 매핑과 추론 엔드포인트가 올바르게 구성되어 있는지 확인하세요.

이제 인덱스에 데이터가 있으므로 content에 대한 어휘 매칭과 content_embedding에 대한 벡터 검색을 결합한 하이브리드 검색을 실행할 수 있습니다. retriever 또는 ES|QL 구문 중에서 선택할 수 있습니다.

retriever 방식과 ES|QL 방식 모두 content의 어휘 매칭과 content_embedding의 시맨틱 매칭을 융합한 점수 순으로 히트를 반환합니다. 두 신호 모두에서 일치하는 구절이 가장 높은 순위를 차지하고, 그다음이 한쪽에서만 일치하는 구절입니다.

Note

semantic_text 데이터를 쿼리하고 검색하는 권장 방법은 semantic_text 필드 검색 및 조회를 참고하세요.

Retriever는 어휘 검색과 시맨틱 검색 같은 서로 다른 검색 전략을 하나의 _search 요청 안에서 정의하고 결합하는 구조화된 방법을 제공합니다. 이 예제는 두 개의 standard retriever를 병합하는 RRF retriever를 사용합니다. 하나는 content에 대해 어휘 match를 실행하고, 다른 하나는 시맨틱 검색을 위해 content_embeddingmatch를 실행합니다.

				GET semantic-embeddings/_search
					{
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "match": {
                "content": "How to avoid muscle soreness while running?"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "match": {
                "content_embedding": "How to avoid muscle soreness while running?"
              }
            }
          }
        }
      ]
    }
  }
}
		
  1. 첫 번째 standard retriever는 전통적인 어휘 검색을 나타냅니다.
  2. 지정한 문구로 content 필드에 대해 어휘 검색을 수행합니다.
  3. 두 번째 standard retriever는 content_embeddingmatch 쿼리를 실행하며, 해당 필드 타입에서는 이것이 시맨틱 검색으로 동작합니다.
  4. 어휘 검색 분기와 동일한 자연어 문구를 사용합니다. Elasticsearch는 용어 중복만이 아니라 시맨틱 검색을 통해 content_embedding의 점수를 산출합니다.
curl -X GET "${ELASTICSEARCH_URL}/semantic-embeddings/_search" \
     -H "Content-Type: application/json" \
     -H "Authorization: ApiKey ${API_KEY}" \
     -d '{
       "retriever": {
         "rrf": {
           "retrievers": [
             {
               "standard": {
                 "query": {
                   "match": {
                     "content": "How to avoid muscle soreness while running?"
                   }
                 }
               }
             },
             {
               "standard": {
                 "query": {
                  "match": {
                    "content_embedding": "How to avoid muscle soreness while running?"
                  }
                 }
               }
             }
           ]
         }
       }
     }'
		
응답 예시

ES|QL은 어휘 검색과 시맨틱 검색을 모두 지원하는 파이프 방식 쿼리 언어입니다. 덕분에 키워드 매칭, 벡터 검색, 스코어링, 결과 처리를 하나의 쿼리에서 결합할 수 있습니다.

				POST /_query?format=txt
					{
  "query": """
    FROM semantic-embeddings METADATA _score
    | WHERE content: "muscle soreness running?" OR match(content_embedding, "How to avoid muscle soreness while running?", { "boost": 0.75 })
    | KEEP content, content_embedding
    | SORT _score DESC
    | LIMIT 1000
  """
}
		
  1. METADATA _score 절은 각 문서의 관련성 점수를 반환합니다.
  2. match(:) 연산자content에서 키워드를 매칭합니다. match()는 부스트 0.75content_embedding에 시맨틱 검색을 실행합니다.
  3. KEEP은 텍스트 형식 응답에 사용할 contentcontent_embedding 컬럼을 선택합니다.
  4. 점수 내림차순으로 정렬하고 결과를 1000개로 제한합니다.
curl -X POST "${ELASTICSEARCH_URL}/_query?format=txt" \
     -H "Content-Type: application/json" \
     -H "Authorization: ApiKey ${API_KEY}" \
     -d '{
       "query": "FROM semantic-embeddings METADATA _score | WHERE content: \"muscle soreness running?\" OR match(content_embedding, \"How to avoid muscle soreness while running?\", { \"boost\": 0.75 }) | KEEP content, content_embedding | SORT _score DESC | LIMIT 1000"
     }'
		
응답 예시