<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[하이브리드 검색 - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[하이브리드 검색 - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/kr/search-labs/blog/category/hybrid-search</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/hybrid-search</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/hybrid-search.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 09:34:29 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch 벡터 데이터베이스: 몇 분 만에 출시하고, 경제적인 수천억 개 규모의 확장]]></title>
    <description><![CDATA[최적화된 기본 설정, 서드파티 및 네이티브 Jina AI 모델, 관리형 GPU 추론을 기본 제공하여 하 이브리드 검색의 까다로운 부분을 이미 해결했습니다. 인프라가 아닌 신속하고 확장 가능한 AI 앱을 구축하세요.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch는 전 세계에서 벡터 워크로드용으로 가장 널리 배포된 플랫폼 중 하나로, GitHub, Docusign, Seismic 등 수많은 기업의 시맨틱 검색, 검색 증강 생성(RAG) 및 추천 기능을 지원합니다. 오늘 벡터 기반 애플리케이션에 최적화된 새로운 서버리스 오퍼링인 Elasticsearch Vector Database를 발표합니다. 문서와 쿼리만 제공하면 인프라와 함께 임베딩 및 인덱스 튜닝을 처리해 드립니다. 또한 비용을 저렴하고 확장 가능하게 유지합니다. </p><p>신규 사용자에게 이는 고품질 벡터 검색을 실행할 수 있는 가장 빠른 방법입니다. 이미 Elasticsearch를 사용 중인 경우, 별도의 시스템을 도입하지 않아도 새로운 기능을 통해 데이터가 이미 존재하는 플랫폼에서 바로 벡터 검색을 이용할 수 있습니다. Elasticsearch 벡터 데이터베이스는 대규모 언어 모델(LLM)의 기반 구축부터 AI 에이전트에 검색 및 메모리 기능 제공, 수천억 개의 벡터 처리에 이르기까지 다양한 시나리오를 지원합니다. <a href="https://cloud.elastic.co/registration?onboarding_token=vector">새 프로젝트를 생성</a>하고 몇 분 안에 시작해 보세요.</p><h2>하나의 엔진, 모든 벡터 사용 사례</h2><p>Elasticsearch 벡터 데이터베이스는 벡터를 사용하여 애플리케이션을 구축하는 모든 사용자를 위해 구축되었습니다.</p><ul><li><p><strong>RAG:</strong> 밀집 및 희소 벡터 검색으로 LLM에 적합한 컨텍스트를 검색하거나, 벡터 검색과 어휘 검색을 모두 결합한 하이브리드 검색을 사용하십시오. 생성 품질은 검색 품질에 따라 향상됩니다.</p></li><li><p><strong>AI 에이전트:</strong> 다단계 에이전트 루프에서 요구하는 짧은 지연 시간으로 문서 및 대화 메모리에 대한 빠르고 필터링된 검색 기능을 에이전트에 제공하세요.</p></li><li><p><strong>시맨틱 검색:</strong> 파이프라인 코드 없이 단 하나의 필드 유형으로 키워드가 아닌 의미를 기반으로 매칭합니다.</p></li><li><p><strong>추천 및 유사성:</strong> 제품, 이미지 또는 보유한 모든 콘텐츠 전반에서 대규모로 최근접 이웃을 찾습니다.</p></li></ul><h2>벡터 워크로드에 필요한 모든 것을 기본 최적화 상태로 제공</h2><p>벡터 기반 애플리케이션을 구축하려면 여러 개의 개별 요소를 연결해야 합니다. 즉, 임베딩 모델을 설정하고 호스팅하며, 이를 통해 문서를 색인하고, 벡터를 효율적으로 저장하고, 각 쿼리에 임베딩 모델을 적용하고, 벡터 저장소와 일치시키고, 마지막으로 일치 항목에 해당하는 문서를 검색해야 합니다. Elasticsearch 벡터 데이터베이스는 추가 구성이나 설정 없이 이 모든 작업을 처리합니다.</p><h3>vectordb_document 인덱스 모드를 사용한 벡터 색인</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a> 인덱스 모드는 벡터 우선 워크로드를 위해 특별히 설계된 새로운 인덱스 구성으로, 기본적으로 활성화되어 있으므로 전문가가 선택할 설정을 사용할 수 있습니다. 활성화되는 기능은 다음과 같습니다.</p><ul><li><p><strong>기본적으로 bfloat16 적용:</strong> 벡터는 재현율에 거의 영향을 주지 않으면서 float32의 절반 크기로 저장되므로, 양자화를 적용하기 전부터 디스크 사용 공간을 대략 절반으로 줄여 줍니다.</p></li><li><p><strong>소스 벡터 제외:</strong> Elasticsearch에서는 임베딩이 이미 검색에 사용되는 인덱스 구조에 존재하므로, _source에 두 번째 원시 사본을 보관하는 것은 저장 공간을 늘리고 결과를 가져오는 속도를 늦출 뿐입니다. 중복을 제외하여 응답이 더 빠르게 반환되며 저장하는 양도 줄어듭니다.</p></li><li><p><strong>적절한 파일을 캐시에 미리 로드:</strong> 벡터 쿼리가 가장 먼저 사용하는 데이터 구조를 미리 메모리에 워밍업하므로 첫 번째 쿼리부터 천 번째 쿼리까지도 번개처럼 빠르게 실행됩니다.</p></li><li><p><strong>병렬 병합:</strong> 병합을 통해 세그먼트가 더 체계적인 벡터 구조로 통합되므로 재현율과 지연 시간이 모두 개선되며, 이러한 병합을 멀티스레드로 실행하면 더 빠르게 도달할 수 있습니다.</p></li></ul><h3>벡터 저장 공간, 압축 및 자동 튜닝</h3><ul><li><p>벡터가 자동으로 압축됩니다.<a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch"> 더 나은 이진 양자화(BBQ)</a>는 재현율을 유지하면서 벡터 메모리 공간을 최대 32배까지 줄이고, DiskBBQ는 대규모 워크로드의 메모리 요구 사항을 더욱 줄여줍니다.<a href="https://www.elastic.co/kr/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/vector-quantization-auto-calibration-diskbbq">자동 보정</a>을 선택하면 각 세그먼트의 양자화가 데이터에 맞게 조정되고, 데이터가 변화함에 따라 병합할 때마다 다시 조정됩니다. 18개 데이터 세트에서 테스트한 결과, 초당 쿼리 수(QPS)는 평균 16.7% 향상되었으며 대부분의 데이터 세트에서 재현율도 개선되었습니다.</p></li></ul><h3>관리형 GPU 추론 기반 임베딩</h3><ul><li><p>기본 <a href="https://www.elastic.co/kr/jina-search-models">Jina AI 임베딩 및 순위 재지정 모델</a>로 임베딩을 생성하거나, 모델 서버를 운영할 필요 없이 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service(EIS)</a>를 통해 관리형 GPU에서 타사 모델을 가져오세요. 자체 모델을 선호하신다면 자체 호스팅도 가능합니다.</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> 필드 유형은 청킹 및 임베딩과 함께 쿼리를 자동으로 처리하므로, 시장에서 시맨틱 검색으로 가는 가장 간단한 경로입니다. </p></li></ul><h3>하이브리드 검색 및 필터링된 벡터 검색</h3><ul><li><p><a href="https://www.elastic.co/kr/elasticsearch/hybrid-search">하이브리드 검색</a>이 기본 제공되어 단일 쿼리에서 전체 텍스트 검색과 벡터 검색을 결합할 수 있습니다. 상호 순위 결합(RRF) 또는 원하는 다른 결합 메커니즘을 사용하여 결과를 혼합할 수 있습니다. 벡터 검색은 일반적으로 하이브리드 검색에서 제대로 구성하기 가장 까다로운 부분입니다. Elasticsearch 벡터 데이터베이스를 사용하면 이 문제를 쉽게 해결할 수 있으며 전체 하이브리드 스택이 더욱 향상됩니다. </p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/filtered-hnsw-knn-search">필터링된 벡터 검색</a>을 사용하면 재현율을 저하시키는 사후 처리가 아니라 벡터 검색 자체의 일부로 메타데이터 필터를 적용할 수 있습니다.</p></li></ul><h3>첫날부터 엔터프라이즈</h3><p>또한 역할 기반 액세스 제어(RBAC), 감사 로깅, 순수 벡터 데이터베이스에는 일반적으로 부족한 규정 준수 인증도 이용할 수 있습니다.</p><h2>확장 시에도 경제적이고 예측 가능</h2><p>Elasticsearch 벡터 데이터베이스는 규모가 확장되어도 경제적인 비용을 유지하도록 구축되었습니다. 저장 공간을 선형적으로 유지하고 메모리 사용량을 낮게 유지하는 BBQ 및 DiskBBQ 압축 기술 덕분에 수천억 개의 벡터로 확장하더라도 비용이 급증하지 않습니다. 또한 실제로 지불하는 비용은 여러분이 이미 알고 있는 수치, 즉 저장하는 데이터 양, 인덱싱하는 데이터 양, 필요한 검색 용량을 기반으로 산정됩니다. 문서 수, 벡터 차원 및 쿼리 부하를 예측하면 프로젝트를 생성하기 전에 지불할 비용을 산출할 수 있습니다. 또한 월말에 청구서의 항목을 한 줄 한 줄 명확하게 파악할 수 있습니다. 불투명한 컴퓨팅 단위가 없으며 백그라운드 작업에 예상치 못한 요금도 청구되지 않습니다.</p><h2>Elasticsearch 벡터 데이터베이스 시작하기</h2><h3>서버리스 벡터 데이터베이스 프로젝트 만들기</h3><p><a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud에서 새로운 서버리스 벡터 데이터베이스 프로젝트</a>를 생성하세요. 데이터를 엔드포인트로 지정하면 인덱싱 준비가 완료됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>semantic_text를 사용한 인덱스 생성</h3><p>벡터 인덱스 모드는 벡터 구성을 처리합니다. semantic_text를 사용하면 관리형 GPU 추론에서 임베딩 및 청킹 설정과 인덱스 설정이 관리되므로 구축할 임베딩 파이프라인이 필요하지 않습니다.</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>문서 수집</h3><p>텍스트를 색인하면 임베딩이 자동으로 생성됩니다.</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>시맨틱 검색 쿼리 실행</h3><p>방금 생성한 동일한 시맨틱 필드에 대해 쿼리 작업을 수행합니다.</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>그리고 결과가 반환됩니다.</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>시맨틱 검색은 시작에 불과합니다. 완전한 텍스트 기반 쿼리를 실행하거나 두 가지를 결합하여 하이브리드 쿼리로 만들 수 있습니다. 완전한 제어를 위해 자체 벡터 쿼리를 작성할 수도 있습니다. 전체 지침은 문서의 <a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">시맨틱 검색 빠른 시작</a>을 참조하세요.</p><h2>Elasticsearch 벡터 검색의 다음 단계</h2><p>이미 다음 개선 사항을 진행 중입니다.</p><ul><li><p><strong>향상된 멀티테넌트 처리:</strong> 테넌트별로 데이터를 분리하여 유지해야 하는 경우, 더 적은 코드로 더 빠르게 처리할 수 있는 방법을 제공해 드립니다.</p></li><li><p><strong>자동 인덱스 최적화:</strong> '새 인덱스'부터 '완전히 최적화된 인덱스'까지, 가능한 한 적은 조정으로 가능합니다.</p></li><li><p><strong>지속적인 인프라 개선:</strong> 항상 최고의 처리량과 가장 빠른 응답을 얻을 수 있도록 벡터 데이터베이스의 설정 및 인프라를 지속적으로 튜닝합니다.</p></li></ul><h2>Elastic Cloud Serverless에서 Elasticsearch 벡터 데이터베이스 써보기</h2><p>프로덕션급 기본 설정이 튜닝을 대신 처리하여, 빈 프로젝트에서 단 몇 분 만에 하이브리드 필터링 벡터 쿼리를 구축해 보세요. 인프라가 아닌 신속하고 확장 가능한 AI 앱을 구축하세요.</p><p><a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a>에서 시작하거나 <a href="https://www.elastic.co/docs/solutions/vector-database">전체 설명서 </a>및 <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">API 참조를 살펴보십시오.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 검색 리콜을 측정하고 개선하는 방법: 하이브리드 검색을 통해 0.43에서 0.75로 향상하기]]></title>
    <description><![CDATA[BM25 어휘 검색과 Jina AI 벡터 임베딩을 결합하여 Elasticsearch에서 검색 회상률을 측정하고 개선하는 방법을 알아보고, rank_eval API를 사용해 실제 숫자로 개선 효과를 검증하세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">어휘 검색</a>은 <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">BM25 순위 알고리즘</a>을 사용하여 저렴하고, 빠르며, 다양한 쿼리에 매우 효과적입니다. 하지만 문서와 토큰을 공유하지 않는 쿼리는 사각지대에 빠집니다. 이 글에서는 BM25의 부족한 부분을 정확히 측정해 보겠습니다. Elasticsearch의 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">순위 평가 API</a>(<code>rank_eval</code>)를 사용하고, <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a>(EIS)를 통해 <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">Jina AI 임베딩</a>을 추가하여 격차를 좁힐 것입니다. 리콜 점수가 <code>0.43</code>에서 <code>0.75</code> 로 올라가는 것을 보면 그 이유를 확인하실 수 있습니다.</p><h2>리콜이란 무엇입니까?</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">리콜</a> 척도는 사용자가 실제로 원하는 문서가 검색 결과 어딘가에 나타나는 정도를 <code>0</code>에서 <code>1</code>까지의 범위로 측정합니다. 만약 쿼리에 3개의 제품이 표시되어야 하는데 검색 결과 상위 10개 제품 중 2개만 표시되는 경우, 해당 쿼리에 대해 <code>recall@10 = 0.67</code>을 입력합니다. 이는 집합 기반 메트릭이므로 <em>k</em> 결과 내에서 관련 문서의 위치는 중요하지 않습니다. 10번 위치에 있는 관련 문서는 1번 위치에 있는 문서와 동일하게 계산됩니다. 리콜률이 높다는 것은 관련성 있는 결과를 놓치지 않고 있다는 뜻입니다.</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="모든 관련 문서와 BM25에서 검색한 상위 10개 결과 간의 중복을 보여주어 Recall@10이 어떻게 계산되는지 설명하며, 그 결과 Recall@10 점수가 0.40임을 보여주는 벤 다이어그램." /><p>다이어그램은 모든 관련 문서(왼쪽)와 BM25가 실제로 검색한 문서(상위 10개, 오른쪽)의 두 가지 세트를 보여줍니다. 오직 교차점만이 리콜에 포함되며, <code>prod_1</code>과 <code>prod_2</code>는 발견되었지만, <code>prod_3</code>, <code>prod_4</code>, <code>prod_6</code>은 완전히 누락되었습니다. 결과: <code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>.</p><h2>필수 구성 요소</h2><p>리콜이 어떻게 작동하는지에 대해 자세히 알아보겠습니다. 이 데모에서는 Python을 사용합니다. 제공된 노트북(<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>)을 통해 따라 해보실 수 있습니다. 모든 코드 블록은 실행할 준비가 된 셀입니다.</p><p>제공된 코드는 다음을 사용합니다.</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>Elasticsearch 자격 증명이 포함된 <code>.env</code> 파일</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>데이터 세트</h2><p>신발, 전자제품, 공구 등의 카테고리를 아우르는 1,000개의 제품 카탈로그.</p><p>각 문서에는 네 개의 필드가 있습니다.</p><p>필드</p><p>유형</p><p>`title`</p><p>텍스트</p><p>`설명`</p><p>텍스트</p><p>`브랜드`</p><p>키워드</p><p>'카테고리`</p><p>키워드</p><p>데이터 세트는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>에서 로드되었습니다.</p><h2>어휘 검색의 힘과 한계</h2><p>BM25는 Elasticsearch 및 대부분의 검색 엔진에서 기본 순위 알고리즘으로 사용됩니다. 쿼리 용어가 문서에 나타나는 빈도에 따라 점수를 매기며, 이는 문서 길이와 전체 인덱스에서 이러한 용어의 빈도에 따라 조정됩니다. 소문자 정규화, 어간 제거, 중단어 제거 등의 <a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">분석</a> 기능을 상단에서 사용할 수 있습니다. '러닝화'를 검색하면 '러닝화'와 '실행'이 모두 검색 결과에 나타날 가능성이 높습니다.</p><p>이는 대규모 쿼리 클래스에 적합합니다.</p><ul><li><p>'러닝화'를 입력하면 제목에 토큰이 정확히 일치하는 제품이 즉시 검색됩니다.</p></li><li><p>'블루투스 스피커'는 토큰이 문자 그대로 나타나기 때문에 휴대용 오디오 제품을 표시합니다.</p></li></ul><p>결과는 결정론적이고 설명 가능합니다. 문서가 높은 순위를 차지한 이유는 쿼리 용어가 문서에 포함되어 있기 때문입니다. 디버깅 관련성은 간단합니다.</p><h3>부족한 부분</h3><p>이제 동일한 카탈로그에 대해 다음 쿼리를 실행해 보겠습니다.</p><ul><li><p><strong>'스킨케어 루틴':</strong> '루틴'이라는 단어는 제품 제목에 나타나지 않습니다. BM25는 '스킨케어'와 부분적으로 일치시킬 수 있지만, 페이스 세럼, 바디 오일, 보습제 등은 '비타민 C', '레티놀', 또는 '브라이트닝' 등 검색어와 겹치지 않는 용어를 사용하여 설명됩니다. 완전한 스킨케어 루틴을 구성하는 제품들은 인덱스에 흩어져 있으며, 이를 연결할 공유 토큰이 없습니다.</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

  ID: B08XMPKJ1L, Score: 5.2699, Title: Bio-Oil Skincare Body Oil (Natural) Serum for Scars and Stretchmarks, Face and Body Moisturizer Hydrates Skin, with Organic Jojoba Oil and Vitamin E, For All Skin Types, 6.7 oz

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>'반려동물 여행용 액세서리':</strong> 이는 제품 카테고리가 아닌 사용 사례 그룹입니다. 반려견 슬링 캐리어, 반려동물 카시트, 여행용 케이지는 모두 관련성이 있지만 '여행용 액세서리'보다는 휴대성, 안전성, 편안함에 대한 설명이 더 많습니다. BM25는 '반려동물'을 광범위하게 매칭하지만, 여행 관련 제품을 나머지 반려동물 카탈로그와 구분하는 신호가 없습니다.</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

ID: B07TNPHYNV, Score: 6.6455, Title: 40 Pieces Christmas Bronze Jingle Bells Craft Small Bells

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>이것은 <strong>리콜 문제</strong>입니다. 관련 문서가 인덱스에 존재합니다. 그러나 사용자의 입력 내용과 문서의 내용이 충분히 일치하지 않기 때문에 BM25가 해당 내용을 찾지 못합니다.</p><p>동의어를 추가하면 알려진 경우에 도움이 됩니다. 하지만 사용자가 의사를 표현하는 모든 방법을 열거할 수는 없습니다. 이것이 벡터가 중요한 이유입니다.</p><h2>리콜을 측정해야 하는 이유</h2><p>문제를 해결하기 전에 그것을 정량화해야 합니다.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a>는 사용자가 실제로 원하는 문서가 검색 결과 위치에 관계없이 얼마나 많이 나타나는지를 측정합니다. 공식적으로</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a>는 상위 k개의 결과와 실제 연관성이 있는 결과의 수를 측정합니다.</p>Precision@k = (relevant documents in top k) / k<p>정확도가 높다는 것은 반환되는 결과가 좋다는 것을 의미합니다. 전자상거래에서 관련 제품을 놓치는 것(낮은 리콜)은 약간 불완전한 결과(낮은 정확도)를 보여주는 것보다 더 나쁠 수 있습니다. 제품이 드러나지 않으면 판매 손실로 연결되기 때문입니다.</p><p>Elasticsearch의 <code>rank_eval</code> API를 사용하면 두 가지를 체계적으로 측정할 수 있습니다. 사용자가 각각 등급이 매겨진 문서 세트가 포함된 쿼리 목록을 제공하면 Elasticsearch가 모든 쿼리에 대해 메트릭을 계산합니다.</p><h2>평가 설정</h2><p><code>rank_eval</code> API에는 관련성 등급(0 = 관련성 없음, 1 = 관련성 있음, 2 = 매우 관련성 있음)과 함께 각 쿼리에 대한 관련 문서 매핑인 <strong>평가 데이터 세트</strong>가 필요합니다.</p><p>노트북에서 이것은 <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">심사 목록입니다</a>:</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

    # Query 2: "skincare routine" intent-based, "routine" never appears in product titles
    {"query_id": "q2", "doc_id": "B08XMPKJ1L", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BN3WQB92", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BT7B7P5T", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00NPA2WEY", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B06XX6DS3P", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B07PDRD1KT", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B074J7869B", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B08JV31QW4", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00K3TVJMQ", "grade": 1, "query": "skincare routine"},

    # Query 3: "study desk setup" intent-based, products are desks/stands/organizers
    {"query_id": "q3", "doc_id": "B08CS35J2T", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B09B3LFDXJ", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B07W58LMND", "grade": 1, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B0CHYDX91L", "grade": 1, "query": "study desk setup"},

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>이 조합은 의도적으로 구성된 것입니다.<code>q1</code>은 BM25가 잘 처리하는 쿼리(제품 제목의 정확한 토큰 포함)이고, <code>q2</code>, <code>q3</code>, <code>q4</code>는 사용자의 의도를 특정 제품 키워드가 아닌 개념으로 표현하는 의도 기반 쿼리입니다.</p><h2>BM25 기준 리콜 측정</h2><p>먼저, Elasticsearch 클라이언트를 설정하고 원시 텍스트 데이터를 색인합니다.</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

es = Elasticsearch(
    os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY")
)

INDEX_NAME = "ecommerce-products"<p>이제 BM25에 대한 <code>rank_eval</code> 요청을 생성합니다. 목록의 각 요청은 쿼리와 그 등급을 결합합니다.</p>judgments_df = pd.DataFrame(judgments)

bm25_requests = []
for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    bm25_requests.append({
        "id": query_id,
        "request": {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": ["title", "description"]
                }
            }
        },
        "ratings": ratings,
    })

bm25_eval = {
    "requests": bm25_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>결과:</p>BM25 Recall@10: 0.43<p><code>0.43</code> 이는 네 개의 쿼리 모두에서 BM25가 찾아야 하는 문서 중 43%만을 찾았다는 의미입니다. 부족한 부분은 의도 기반 쿼리에 집중되어 있습니다. '스킨케어 루틴'은 제품 제목에 '루틴'이 포함되지 않는 페이스 세럼과 바디 오일을 놓치고, '여행용 반려동물 액세서리'는 주제와 다른 반려동물 제품을 검색하는 반면, 휴대성과 안전성 측면을 강조해서 설명하는 캐리어와 케이지는 정작 빠져 있습니다.</p><p>여기가 기준이 됩니다. 이제 넘어야 할 목표가 생겼습니다.</p><h2>Jina 임베딩으로 벡터 검색 추가</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> 문서와 쿼리를 고차원 벡터로 인코딩합니다. 고차원 벡터는 수백에서 수천 개의 수치 값으로 구성되며, 각 값은 해당 데이터의 특정한 특징을 인코딩합니다. 비슷한 의미를 가진 문서들은 단어가 공유되지 않더라도 벡터 공간에서 서로 가깝게 배치됩니다. '헬스 기구'와 '덤벨 세트'는 개념이 관련되어 있기 때문에 가까이에 배치될 것입니다. 하이브리드 검색을 지원하여 의미론적 이해와 키워드 정확도를 모두 제공하는 Elasticsearch를 벡터 데이터베이스로 선택했습니다.</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>는 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">추론 API</a>를 통해 모델 임베딩을 즉시 지원하는 기능을 포함하고 있습니다.</p><h3>1단계: Jina 임베딩 v5를 추론 엔드포인트로 사용</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>클러스터에 GPU 리소스가 있는 경우(Elastic Cloud 및 Elasticsearch 9.3 이상에서 사용 가능), 임베딩이 GPU에서 생성되어 CPU 추론보다 훨씬 빠르며, 과거에 대규모 벡터 처리를 비용 부담으로 만들었던 성능 트레이드오프 문제를 해소합니다.</p><p>왜 Jina 임베딩을 사용할까요? <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a>는 32,000개 이상의 토큰 컨텍스트 창을 가진 다국어 모델(119개 이상의 언어)로, 작업별 <a href="https://arxiv.org/abs/2106.09685">LoRA(Low-Rank Adaptation) 어댑터</a>를 지원합니다. 바로 사용할 수 있는 짧은 제품 설명에 적합합니다. <code>jina-embeddings-v5-text</code> 모델에 대한 자세한 내용은 <a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">여기</a>에서 확인하실 수 있습니다.</p><h3>2단계: 시맨틱 필드로 인덱스 생성</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>여기서는 <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> 필드 유형이 핵심입니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a>보다 더 높은 수준의 추상화입니다. 추론 엔드포인트를 가리키면 Elasticsearch가 자동으로 임베딩 생성을 처리합니다.</p><p><code>title</code>과 <code>description</code>의 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> 속성은 두 필드의 콘텐츠가 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a>로 흘러들어가 임베딩되므로, 단일 벡터가 전체 제품 표현을 캡처합니다.</p><h3>3단계: 제품 인덱스 생성</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

    success, failed = helpers.bulk(es, actions, raise_on_error=False)
    if failed:
        for error in failed:
            print(f"Error: {error}")
    else:
        print(f"Successfully indexed {success} documents")

bulk_index(products, INDEX_NAME)<p>색인 시점에 Elasticsearch는 각 문서에 대해 추론 엔드포인트를 호출하고, 결과 임베딩을 <code>semantic_field</code>에 저장합니다. 사용자 측에서 추가 코드를 작성할 필요가 없습니다.</p><h2>하이브리드 검색: BM25 및 벡터와 RRF의 결합</h2><p>벡터를 추가하면 리콜이 향상되지만, 벡터만 사용하면 정확한 일치 쿼리에서 정확도를 잃을 위험이 있습니다. '러닝화'와 같은 용어는 여전히 문자 자체가 일치하는 결과가 우선 순위에 표시되어야 합니다. 하이브리드 검색은 정확성을 보장하기 위해 어휘 기반 검색 요소를 유지합니다.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">상호 순위 결합</a>(RRF)을 사용한 하이브리드 검색은 두 가지 장점을 모두 유지합니다.</p><ul><li><p>BM25는 정확하거나 거의 정확한 쿼리를 높은 정확도로 처리합니다.</p></li><li><p>의미 검색은 의도 기반 및 다국어 쿼리를 높은 리콜로 처리합니다.</p></li><li><p>RRF는 두 개의 순위 목록을 단일 순위로 결합합니다.</p></li></ul><p>RRF 공식은 각 문서의 결과 목록 순위에 따라 점수를 부여합니다.</p>score = sum(1 / (rank_constant + rank))<p>두 목록 모두에서 높은 순위를 차지한 문서가 더 높은 합산 점수를 받습니다. <code>rank_constant</code>는 하위 등급의 문서가 받는 가중치를 조절합니다.</p>hybrid_requests = []

for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    hybrid_requests.append({
        "id": query_id,
        "request": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "multi_match": {
                                        "query": query_text,
                                        "fields": ["title", "description"],
                                    }
                                }
                            }
                        },
                        {
                            "standard": {
                                "query": {
                                    "match": {
                                        "semantic_field": {"query": query_text}
                                    }
                                }
                            }
                        },
                    ],
                    "rank_window_size": 50,
                    "rank_constant": 5,
                }
            }
        },
        "ratings": ratings,
    })

hybrid_eval = {
    "requests": hybrid_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>결과:</p>Hybrid Recall@10: 0.75<p>하이브리드는 BM25(<code>0.43</code>)에 비해 성능이 크게 향상되었으며, '러닝화'와 같은 정확한 일치 쿼리에 대한 정확확도를 유지합니다.</p><h2>결과: 전후 비교</h2><p>세 가지 접근 방식을 모두 비교한 전체 내용은 다음과 같습니다.</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

recall_metric = {"recall": {"k": 10, "relevant_rating_threshold": 1}}

comparison_data = []
for method_name, requests in methods.items():
    result = es.rank_eval(
        index=INDEX_NAME,
        body={"requests": requests, "metric": recall_metric}
    )
    comparison_data.append({
        "method": method_name,
        "recall@10": result.body["metric_score"]
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>결과:</p><p>메서드</p><p>Recall@10</p><p>BM25(어휘 검색)</p><p>0.43</p><p>하이브리드(BM25 + 벡터)</p><p>0.75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="BM25 어휘 검색과 BM25와 벡터를 결합한 하이브리드 검색의 Recall@10을 비교한 막대 차트에서 하이브리드 검색이 훨씬 더 높은 리콜률을 달성했음을 확인할 수 있습니다." /><p>쿼리별로 분석해 보겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="4개의 제품 쿼리에서 BM25 어휘 검색과 하이브리드 검색의 Recall@10을 비교한 그룹화된 막대 차트는 각 쿼리에서 하이브리드 검색이 어휘 검색보다 일관되게 우수한 성능을 보였음을 증명합니다." /><h2>결론</h2><p>이 포스팅에서는 사용자가 정확한 쿼리를 입력할 때 BM25 어휘 검색이 신뢰할 수 있지만, 키워드보다는 의도에 따라 검색할 때 리콜이 떨어진다는 것을 보았습니다. <code>rank_eval</code>을 사용하여 재현 가능한 기준선을 설정하고 실제 숫자로 그 차이를 측정했습니다. 그 후, Jina 임베딩을 기반으로 하는 <code>semantic_text</code> 필드를 추가하고 평가를 다시 실행했습니다. 결과적으로, 하이브리드 검색은 리콜을 <code>0.43</code>에서 <code>0.75</code>로 높이면서도 정확한 일치 쿼리의 정확도를 유지했지만, 실제 마진은 쿼리 구성에 따라 달라집니다.</p><p>이 패턴은 이 예제 이상으로 확장됩니다. 사용자의 실제 쿼리에서 판단을 수집하고 <code>rank_eval</code>을 기준선으로 실행한 다음 <code>semantic_text</code>를 추가하고 다시 측정하세요. 어떤 부분이 얼마나 개선되었는지 정확히 알 수 있습니다.</p><h2>다음 단계</h2><ul><li><p>리콜과 벡터 검색에 대해 자세히 알아보기: Jeff Vestal의 <a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">리콜 및 벡터 검색 양자화</a></p></li><li><p>상위 결과의 정확도를 높이기 위해 재순위 추가</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Elasticsearch 하이브리드 검색 문서</a> 살펴보기</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"><code>rank_eval</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">API</a>에 대해 자세히 알아보기</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch를 통한 엔티티 해석, 4부: 최종 과제]]></title>
    <description><![CDATA[지름길을 방지하도록 설계된 고도로 다양한 '궁극의 과제' 데이터 세트에서 엔티티 해석 문제를 해결하고 평가합니다.]]></description>
    <content:encoded><![CDATA[<p>이제 두 가지 방식으로 구현된 지능형 엔티티 해석을 확인했습니다. 두 접근 방식 모두 동일한 방식으로 시작합니다. 즉, 엔티티 준비 및 추출을 거쳐 Elasticsearch로 후보 검색이 진행됩니다. 그 후, 프롬프트 기반 JSON 생성 또는 함수 호출을 통해 대규모 언어 모델(LLM)을 사용하여 후보를 평가하며, 모델이 판단에 대한 투명한 설명을 제공하도록 요구합니다.</p><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">이전 게시물</a>에서 앞서 보았듯이, 함수 호출이 제공하는 일관성은 단순한 최적화가 아닌 필수 사항입니다. 평가 루프에서 구조적 오류를 제거하자 표준 시나리오(예: 티어 4 데이터 세트)의 결과가 크게 향상되었습니다.</p><p>하지만 아직 답변이 필요한 질문도 분명히 남아 있습니다.</p><p><em>상황이 아주 복잡해질 때도 이 접근 방식이 여전히 효과가 있을까요?</em></p><p>실제 엔티티 해석은 단순한 경우로 인해 실패하는 일이 거의 없습니다. 이름이 언어, 문화, 문자 체계, 시대, 조직 경계를 넘나들 때 실패하게 됩니다. 사람들이 이름 대신 직함으로 언급되거나, 회사가 이름을 바꾸거나, 음역이 일관되지 않거나, 철자가 아니라 컨텍스트만이 실제 세계의 엔티티와 연결되는 유일한 요소일 때 실패하게 됩니다.</p><p>그래서 이 시리즈의 마지막 포스팅에서 시스템에 '<strong>궁극의 도전</strong>'이라는 과정을 진행했습니다.</p><h2>이것이 궁극적인 도전인 이유는 무엇입니까?</h2><p>이전 평가에서는 점점 더 복잡한 데이터 세트를 사용하여 시스템을 테스트했습니다. 이전 게시물에서 논의된 티어 4에 도달했을 때는 이미 별명, 직함, 다국어 이름 및 의미 참조의 혼합을 다루고 있었습니다. 해당 테스트 결과, 아키텍처 자체는 견고했음에도 특히 잘못된 JSON 형식과 같은 신뢰성 문제로 인해 재현율이 저해되는 것으로 나타났습니다.</p><p>함수 호출이 구현됨에 따라 마침내 안정적인 기반을 마련할 수 있었습니다. 덕분에 더 흥미로운 질문을 할 기회가 생겼습니다.</p><p><em>하나의 통합 파이프라인이 </em><em><strong>여러 종류의</strong></em><em> 엔티티 해석 문제를 동시에 처리할 수 있을까요?</em></p><p>궁극의 과제 데이터 세트는 바로 그러한 차원을 시험하기 위해 설계되었습니다.</p><p>별명이나 음역과 같은 단일한 문제에 집중하는 대신, 이 데이터 세트는 다음과 같은 <strong>50개 이상의 다양한 과제 유형</strong>을 결합합니다.</p><ul><li><p>문화적 지명 관습</p></li><li><p>직함 기반 참조</p></li><li><p>비즈니스 관계와 역사적 이름 변경 사항.</p></li><li><p>다국어 및 교차 스크립트 언급</p></li><li><p>위의 여러 가지가 혼합된 복합 과제입니다.</p></li></ul><p>무엇보다 중요한 것은 이것이 단 하나의 제한적인 사용 사례에 대한 최적화가 아니라는 것입니다. 엔티티 간에 규칙이 변경될 때 <em>설계 패턴</em>이 유지되는지 테스트하는 것이 중요한 부분입니다.</p><h2>데이터 세트 개요</h2><p>궁극의 과제 데이터 세트는 다음과 같이 구성됩니다.</p><ul><li><p>사람, 조직, 기관이 포함된 <strong>50개 엔티티</strong>.</p></li><li><p>다양한 구조와 언어적 복잡성을 지닌 <strong>약 60개의 기사</strong>.</p></li><li><p><strong>51개의 뚜렷한 과제 카테고리</strong>가 다음과 같이 넓게 그룹화됩니다.</p><ul><li><p>문화적 지명 관습</p></li><li><p>직함 및 전문적 맥락.</p></li><li><p>비즈니스 및 조직적 관계</p></li><li><p>다국어 및 음역 문제.</p></li><li><p>결합 및 극단 시나리오</p></li></ul></li></ul><p>이 시리즈의 초반부에서 생성형 AI(GenAI)를 사용하여 데이터 세트를 생성하는 것에 장단점이 있다는 것을 확인했습니다. 생성형 AI가 없으면 충분히 크고 다양한 테스트 데이터를 구성하는 것이 매우 어려워집니다. 그러나 제어하지 않으면 모델이 작업을 지나치게 쉽게 만드는 경향이 있습니다.</p><p>초기 세대 합격 예시를 보면 모델이 블라디미르 푸틴의 명시적 별칭으로 '러시아 대통령'과 같은 구문을 포함시켰다는 사실이 발견되었습니다. 현재로서는 합리적으로 보일 수 있지만, 이는 상황별 해결 테스트의 목적을 무효화합니다. 기사가 1990년대 러시아에 대해 논의하고 있다면 어떻게 될까요? 시스템은 하드코딩된 별칭에 의존하지 않고 컨텍스트에서 올바른 엔티티를 추론해야 합니다.</p><p>그러한 이유로, 이 데이터 세트는 <strong>지름길이 통하지 않도록</strong> 의도적으로 설계되었습니다. 별칭은 시스템이 의미를 유추해야 할 때 명시적으로 나열되지 않습니다. 설명적 구문이 엔티티에 사전 연결되어 있지 않습니다. 정확한 일치는 단순한 로컬 텍스트가 아닌 문서 수준의 컨텍스트에 의존하기도 합니다.</p><p><strong>중요 사항:</strong> 다양한 시나리오에서 시스템의 기능을 시연하는 것이지만 이는 교육용 프로토타입임을 알려드립니다. 실제 제재 대상 엔티티 모니터링을 처리하는 프로덕션 시스템에는 추가적인 검증, 규정 준수 점검, 감사 추적 및 민감한 사용 사례에 대한 전문적인 처리가 필요합니다.</p><h2>이러한 시나리오가 어려운 이유</h2><p>이 시리즈의 첫 번째 게시물에서 “새로운 Swift 업데이트가 도착했습니다!”라는 간단하지만 모호한 예를 소개했습니다. 문제는 'Swift'가 상황에 따라 실제 세계의 여러 엔티티로 해석될 수 있다는 것입니다. 이 예시는 더 큰 진실을 드러냅니다. 자연어가 본질적으로 모호하다는 것이죠.</p><p>따라서 엔티티 해석은 단순히 스트링 일치 문제가 아닙니다. 인간은 일상적으로 공유된 지식, 문화적 규범, 상황적 맥락에 의존하여 참조를 해결하며, 그렇게 하고 있다는 사실을 거의 인지하지 못합니다.</p><p>고려해야 할 일반적인 케이스:</p><ul><li><p>지정학 및 시대적 컨텍스트가 없다면 '대통령'과 같은 직함은 의미가 없습니다.</p></li><li><p>회사 이름은 기사 작성 시기에 따라 모회사, 자회사 또는 이전 브랜드를 가리킬 수 있습니다.</p></li><li><p>사람의 이름은 언어와 문화에 따라 다른 순서, 문자 체계 또는 음역으로 표현될 수 있습니다.</p></li><li><p>동일한 문구가 다른 컨텍스트에서 다른 엔티티를 정당하게 참조할 수 있으며, 시스템은 일치 항목을 수락하는 것만큼 자신 있게 <em>거부</em>할 수 있어야 합니다.</p></li></ul><p>이러한 모든 상황을 깔끔하게 처리할 수 있는 단일 규칙 세트는 없습니다. 그래서 이 프로토타입은 우려 사항을 매우 철저하게 분리합니다.</p><ul><li><p>Elasticsearch는 후보 공간을 효율적이고 투명하게 좁힙니다.</p></li><li><p>LLM은 판단이 필요하고 스스로 설명해야 하는 경우에만 사용됩니다.</p></li><li><p>검색과 추론은 여전히 뚜렷하게 구분됩니다.</p></li></ul><p>과제 유형이 다양해질수록 이러한 분리가 더욱 중요해집니다.</p><h2>시스템이 특별 사례 없이 다양성을 처리하는 방식</h2><p>이 평가에서 가장 흥미로운 결과 중 하나는 변하지 <em>않은</em> 부분입니다.</p><ul><li><p>일본어 이름에 대한 특별한 로직을 추가하지 <strong>않았습니다</strong>.</p></li><li><p>아랍어 부칭에 대한 사용자 지정 규칙을 추가하지 <strong>않았습니다</strong>.</p></li><li><p>과거 회사 이름에 하드코딩된 매핑을 추가하지 <strong>않았습니다</strong> .</p></li></ul><p>대신 이 시스템은 이 시리즈에서 앞서 소개된 것과 동일한 핵심 요소에 의존했습니다.</p><ul><li><p>시맨틱 검색을 위해 인덱싱된 컨텍스트가 풍부한 엔티티</p></li><li><p>Elasticsearch 내 하이브리드 검색(정확성, 별칭, 의미론적 검색)</p></li><li><p>잘 정의된 소규모 후보 일치 집합</p></li><li><p>함수 호출 및 최소 스키마에 의해 제한되는 LLM 판단</p></li></ul><p>이는 시스템의 유연성이 점점 늘어나는 규칙 집합이 아니라 <strong>표현과 구조</strong>에서 온다는 것을 시사합니다.</p><p>시스템이 성공하는 경우는 올바른 후보가 검색되고, LLM이 참조가 특정 엔티티에 매핑되거나 매핑되지 않는 이유를 설명할 충분한 맥락을 제공하기 때문입니다.</p><h2>결과: 성능은 어떠했습니까?</h2><p>궁극의 과제 데이터 세트에서 시스템은 다음과 같은 종합적인 결과를 도출했습니다.</p><ul><li><p><strong>정밀도:</strong> ~91%</p></li><li><p><strong>재현율:</strong> ~86%</p></li><li><p><strong>F1 점수:</strong> ~89%</p></li><li><p><strong>LLM 합격률:</strong> ~72%</p></li></ul><h3>도전 과제 유형 전반에 걸친 성과</h3><p>과제 유형별로 결과를 분석하면 강점과 한계가 드러납니다.</p><p><strong>가장 강한 성능(100% F1 점수)</strong>은 다음과 같은 영역에서 관찰되었습니다.</p><ul><li><p>스크립트 간 매칭(키릴 문자, 한국어, 중국어 사업체)</p></li><li><p>히브리어 시나리오(부칭, 전문 직함, 종교 직함, 음역)</p></li><li><p>비즈니스 계층(항공우주, 다각적인 제조, 다사업부 기업)</p></li><li><p>전문직 칭호(학문적, 군사적, 정치적, 종교적).</p></li><li><p>여러 문자 체계를 포함하는 결합된 일본어 시나리오입니다.</p></li></ul><p><strong>강력한 성능(80–99% F1 점수)</strong> 포함 사항:</p><ul><li><p>국제 정치인(98%)</p></li><li><p>과거 이름 변경 내역(90%)</p></li><li><p>복잡한 비즈니스 계층(89%)</p></li><li><p>일본 회사 이름(93%)</p></li><li><p>교차 문자 체계 음역(86%)</p></li><li><p>아랍어 애칭(86%)</p></li></ul><p><strong>더 까다로운 영역</strong> 포함:</p><ul><li><p>고급 음역(중국어, 한국어): 0% F1.</p></li><li><p>특정 일본어 시나리오(경어법, 이름 순서, 쓰기 체계 변형): ~67% F1.</p></li><li><p>일부 아랍어 시나리오(회사명, 기관 참조): ~40% F1.</p></li></ul><p>여기서 중요한 것은 이 사례에서 시스템이 어려움을 겪은 <em>이유</em>입니다. 이러한 실패는 전반적인 접근 방식이 고장났기 때문이 아니라 특정 구성 요소, 특히 특정 다국어 시나리오에서 시맨틱 검색에 사용되는 고밀도 벡터 모델의 한계로 인한 것이었습니다.</p><p>검색과 판단이 명확하게 분리되어 있기 때문에, 성능 향상을 위해 시스템을 다시 작성할 필요가 없습니다. 보다 뛰어난 다국어 임베딩 모델로 교체하거나, 엔티티 컨텍스트를 강화하거나, 검색 전략을 개선하면 핵심 아키텍처를 변경하지 않고도 이러한 카테고리 전반에 걸쳐 결과를 향상시킬 수 있습니다.</p><p>아키텍처 관점에서는 그것이 진정한 성공 지표입니다.</p><h2>디자인에 대해 알 수 있는 내용</h2><p>시리즈를 되돌아보면, 몇 가지 패턴이 눈에 띕니다.</p><ul><li><p><strong>영리한 매칭보다 준비가 더 중요합니다. </strong>사전에 컨텍스트로 엔티티를 보강하면 이후 모호성을 크게 줄일 수 있습니다.</p></li><li><p><strong>LLM은 검색자가 아니라 심사 위원으로서의 가치가 가장 뛰어납니다.</strong>검색하라고 요청하는 것보다 일치하는 <em>이유</em>를 설명해달라고 요청하는 것이 훨씬 효과적입니다.</p></li><li><p><strong>신뢰성이 정확성을 뒷받침합니다. </strong>함수 호출은 JSON을 정리하는 것뿐만 아니라, 검색 단계에서 이미 잠재되어 있던 기억력을 끌어내는 역할을 했습니다.</p></li><li><p><strong>일반화가 전문화보다 우수합니다.</strong>잘 선택한 소규모 추상화를 통해 사용자 정의 로직 없이 수십 가지 과제 유형을 처리할 수 있었습니다.</p></li></ul><p>이것이 바로 프로토타입이 의도적으로 Elasticsearch 네이티브 방식으로 설계되었고, LLM 사용 방식에 있어서도 의도적으로 보수적인 접근 방식을 취한 이유입니다. 목표는 검색을 대체하는 것이 아니라, 의미가 중요한 상황에서 검색을 설명 가능하게 만드는 것입니다.</p><h2>결론</h2><p>궁극의 과제는 완벽한 지표를 추구하기 위한 것이 아니라, 더 근본적인 질문에 답하기 위한 것입니다.</p><p><em>투명한 검색 우선의 LLM 기반 아키텍처가 규칙이나 블랙박스로 전락하지 않고 실제 세계의 엔티티 모호성을 처리할 수 있을까요?</em></p><p>이 교육용 프로토타입의 경우 프로덕션 강화, 규정 준수, 모니터링 및 데이터 품질과 관련된 명확한 주의사항이 있지만 그 답은 '예'입니다. 엔티티 일치가 이루어진 <em>이유</em>를 정당화해야 하는 시스템을 구축하는 경우, 이 패턴을 진지하게 고려할 가치가 있습니다. 이 시리즈를 통해 엔티티 해결이 불가사의하지 할 이유가 없다는 것을 알게 되셨기를 바랍니다. 문제를 제대로 분리하면 엔티티 해결을 이해하고, 측정하고, 개선할 수 있습니다.</p><p>이 작업은 또한 더 광범위한 아키텍처 패턴을 제안합니다. 이를 통해 고전적인 Retrieval-Augmented Generation(RAG) 방식의 미미하지만 중요한 진화가 드러납니다. 검색이 생성에 직접 정보를 제공하게 하는 대신, 명시적인 평가 단계를 도입했습니다. LLM이 먼저 사용되어 검색된 후보를 심사하고 무결성을 확인하며 승인된 결과만 생성을 보강하는 것이 허용됩니다. 이를 Generation-Augmented Retrieval-Augmented Generation with Evaluation, 즉 GARAGE라고 생각하시면 됩니다. 이렇게 좋은 약어를 마다할 사람은 없겠죠?</p><p>이 패턴이 다른 어떤 사용 사례에 도움이 될 수 있을까요? 신뢰, 투명성 및 변호 가능한 추론이 필요한 시스템이 당연히 후보가 될 것입니다. 이 분야의 향후 작업은 여기서 본 결과만큼이나 매력적일 것입니다. 커뮤니티가 다음에 어떤 방향으로 나아갈지 정말 기대됩니다.</p><h2>다음 단계: 직접 사용해 보기</h2><p>궁극의 과제가 실제로 작동하는 모습을 확인하고 싶으세요? 실제 구현, 자세한 설명 및 실습 예제가 포함된 <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>궁극의 과제 노트북</strong></a>을 확인해 보세요.</p><p>완전한 엔티티 해석 파이프라인은 프로덕션 환경에 필요한 핵심 개념과 아키텍처를 보여줍니다. 이를 기반으로 모든 과정에서 투명성과 설명 가능성을 유지하는 동시에 뉴스 기사를 모니터링하고, 엔티티 언급을 추적하며, 어떤 엔티티가 어떤 기사에 등장하는지에 대한 질문에 답하는 시스템을 구축할 수 있습니다.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 및 LLM을 사용한 엔터티 해석, 2부: LLM 판단 및 시맨틱 검색을 사용한 엔터티 매칭]]></title>
    <description><![CDATA[Elasticsearch에서 엔터티 해석을 위해 시맨틱 검색과 투명한 LLM 판단 사용]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">1부</a>에서는 감시 목록을 준비하고 엔터티 언급을 추출했습니다. 이제 "언급이 실제로 가리키는 엔터티는 무엇인가?"라는 어려운 질문에 답할 준비가 되었습니다. 이 시리즈의 첫 번째 블로그에서 엔터티 해석이 필요한 이유를 설명한 예시, "Swift 업데이트 도착!"으로 돌아가 봅시다. 이 헤드라인이 조금 더 많은 맥락과 함께 제공된다고 생각해 보세요.</p><ol><li><p>Swift 업데이트 도착! 개발자들은 새로운 기능을 사용해 보고 싶어 합니다.</p></li><li><p>Swift 업데이트 도착! 새 앨범이 다음 달에 발매될 예정입니다.</p></li></ol><p>이 추가 맥락을 통해 "Swift"라는 이름을 올바른 엔터티로 해석할 수 있습니다.</p><p>이전 게시물에서는 <a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">감시 목록</a>을 설정하고 엔터티를 추가 맥락으로 보강했습니다. 위의 예시를 보면, 목록에 최소한 다음 두 개의 엔터티, "Taylor Swift"와 "Swift 프로그래밍 언어"가 있어야 합니다. 텍스트에서 엔터티 언급을 추출하는 방법도 다뤘습니다. 이 두 예시 모두 "Swift"를 추출합니다. 보강된 감시 목록과 추출된 엔터티라는 재료가 갖추어졌으니, 이제 주요 주제인 엔터티 매칭을 다룰 준비가 되었습니다.</p><p><strong>유의 사항:</strong> 이것은 엔터티 매칭 개념을 교육하기 위해 설계된 교육용 프로토타입입니다. 프로덕션 시스템에서는 다양한 대규모 언어 모델(LLM), 사용자 지정 매칭 규칙, 특수 판단 파이프라인 또는 여러 매칭 전략을 결합한 앙상블 접근 방식을 사용할 수 있습니다.</p><h2>문제: 매칭이 어려운 이유</h2><p>인간의 언어는 놀라운 것입니다. 가장 흥미로운 속성 중 하나는 끝없는 창의성입니다. 우리는 무한하게 많은 수의 새로운 문장을 생성하고 이해할 수 있습니다. 그렇다면 엔터티 해석에서 정확한 일치가 드문 것이 과연 놀라운 일일까요? 작성자들은 가능한 한 창의적으로 되려고 노력합니다. 엔터티가 언급될 때마다 전체 이름을 쓰고 읽어야 한다면 매우 지루할 것입니다. 정확한 일치는 간편하지만, 실제로는 엔터티 해석을 위해 더 정교한 접근 방식이 필요합니다. 인간 작성자의 무한한 창의성을 최소한 어느 정도는 처리할 수 있을 만큼 강력한 접근 방식이 필요합니다. 그래서 우리는 문제를 두 단계로 분리합니다. Elasticsearch를 사용하여 가능성 있는 후보를 대규모로 검색하고, 그런 다음 LLM을 사용하여 이러한 후보가 실제로 동일한 실제 엔터티를 참조하는지 판단하는 것입니다.</p><h2>해결책: 투명한 LLM 판단을 사용한 3단계 매칭</h2><p>우리는 컴퓨터 사용 방식에서 패러다임 전환의 중심에 있습니다. 인터넷의 등장으로 로컬 컴퓨팅에서 글로벌 연결 네트워크로 전환된 것처럼, 생성형 AI는 콘텐츠, 코드 및 정보 생성 방식을 근본적으로 변화시키고 있습니다. 실제로, 이 시리즈에 포함된 교육용 프로토타입에서는 작성자의 세심한 프롬프트에 따라 거의 전적으로 LLM을 통한 "바이브 코딩" 방식을 사용했습니다. LLM이 인간 언어에 내재한 생산성에 도달했거나 도달할 것이라는 의미는 아니지만, 이제 엔터티 해석에 도움이 되는 강력한 리소스를 갖게 되었다는 것은 명백합니다.</p><p>생성형 AI에서 흔히 사용하는 패턴은 Retrieval-Augmented Generation(RAG)입니다. 여기서 <em>검색(Retrieval)</em>은 엔터티 후보를 검색하는 것(답변 생성이 아님)을 의미하며, LLM은 매칭 평가와 설명에만 엄격히 사용됩니다. LLM에 엔드 투 엔드 엔터티 해석을 <em>요청할 수도 있지만</em>, 시간과 비용 면에서 비용이 많이 듭니다. RAG는 LLM에 더 효율적으로 맥락을 제공하는 방식을 사용함으로써 LLM이 엔터티 해석을 효율적으로 지원하도록 돕습니다.</p><p>RAG의 검색 부분에서는 다시 Elasticsearch를 사용합니다. 먼저 정확한 매칭, 별칭 매칭, 그리고 키워드와 시맨틱 검색을 결합한 하이브리드 검색을 조합하여 잠재적 매칭을 찾습니다. 이 잠재적 매칭을 찾으면 LLM에 보내 판단을 받습니다. LLM은 최종 매칭 평가자 역할을 합니다. 또한 LLM은 그 추론을 설명하게 하는데, 이는 다른 엔터티 해석 시스템과의 중요한 차별점입니다. 이러한 설명이 없으면 엔터티 해석은 블랙박스와도 같습니다. 설명이 있어야 매칭이 타당한 이유를 알 수 있습니다.</p><h2>핵심 개념: 3단계 매칭, 하이브리드 검색, 투명한 LLM 판단</h2><p><strong>3단계 매칭이란?</strong> 이 프로젝트의 시작 시점에 우리는 시맨틱 검색이 시스템의 중요한 부분이 될 것이라고 가정했지만, 모든 매칭이 그러한 정교한 검색을 요구하지는 않습니다. 일치하는 항목을 효율적으로 찾기 위해서, 문제에 대해 점진적인 접근 방식을 취합니다. 먼저 키워드 검색을 사용하여 정확히 일치하는 항목을 확인합니다. 정확히 일치하는 항목을 찾으면 작업이 완료되고 다음 단계로 넘어갈 수 있습니다. 정확히 일치하는 항목이 없으면, 별칭 매칭으로 전환합니다. 프로토타입에서는 간편함을 위해 별칭 매칭에서도 키워드를 사용한 정확한 일치 방식을 사용합니다. 프로덕션 환경에서는 이 단계를 정규화, 음역 규칙, 퍼지 매칭 또는 큐레이션된 별칭 테이블로 확장할 수 있습니다. 처음 두 단계에서 잠재적인 일치 항목을 찾지 못하면, Elasticsearch의 하이브리드 검색과 상호 순위 결합(RRF)을 통해 시맨틱 검색을 사용할 차례입니다.</p><p><strong>하이브리드 검색이란?</strong> Elasticsearch에서는 시맨틱 검색을 사용하여 맥락을 고려한 의미 있는 일치 항목을 찾을 수 있습니다. Elasticsearch는 벡터 검색 및 하이브리드 검색에 널리 사용됩니다. 시맨틱 유사성은 의미 파악에 효과적이지만, 구조화된 필터링(예: 시간 범위, 위치 또는 식별자 기준)을 대체할 수 없으며 정확한 일치 항목이 있을 경우에는 불필요한 경우가 많습니다. Elasticsearch는 시맨틱 검색이 맞지 않는 작업에 뛰어난 어휘 검색으로 두각을 나타냈습니다. 두 접근법을 모두 활용하기 위해 단일 하이브리드 쿼리에서 어휘 검색과 시맨틱 검색을 사용합니다. 그런 다음 결과를 병합해서 RRF로 일치 가능성이 가장 높은 항목을 찾습니다. 프로토타입에서는 상위 두 결과가 LLM 판단을 위해 보낼 수 있는 잠재적 일치 항목이 됩니다.</p><p><strong>LLM 판단이 필요한 이유?</strong> LLM의 판단과 설명을 통해 시스템은 모호성과 맥락을 투명하게 처리할 수 있습니다. 이는 맥락에 따라 여러 개체를 지칭할 수도 있는 "The President" 같은 사례에 매우 중요할 뿐 아니라, 별명이나 문화적 변형 같은 항목도 시스템에서 잘 작동하도록 해줍니다. 마지막으로, 제재 목록에서 엔터티를 식별할 때와 같이 중대한 작업을 고려할 때 시스템을 신뢰하기 위해서는 일치 항목이 수락된 이유를 알아야 합니다. 결정적으로, LLM은 전체 말뭉치를 검색하는 것이 아니라 Elasticsearch가 반환한 작은 후보 집합만 평가합니다.</p><h2>실제 결과: LLM 추론을 사용한 매칭</h2><p>자연어 처리 작업에서 가장 큰 과제 중 하나는 기대 결과를 알려주는 문서, 즉 "정답지"를 만드는 것입니다. 이것 없이는 작업에서 시스템의 성능을 판단하기가 거의 불가능하지만, 이러한 문서를 작성하는 과정은 어려울 수 있습니다. 엔터티 해석 프로토타입 개발을 위해, 테스트에 사용할 데이터를 설정하는 데 다시 한번 생성형 AI의 도움을 받았습니다.</p><p>먼저 별명과 음역법 같은 여러 과제 유형을 정의한 후, 시스템에 점차 더 커지고 어려워지는 티어형 데이터 세트 모음을 만들도록 LLM에 요청했습니다. 데이터 세트 생성은 기대만큼 간단하지 않았습니다. LLM은 정답을 너무 쉽게 찾도록 만들어서 "속임수"를 쓰는 경향이 강했습니다. 예를 들어, 과제 유형 중 하나는 의미론적 맥락에 초점을 맞췄습니다. 이 유형에는 "러시아 작가(Russian author)"를 "레프 톨스토이(Leo Tolstoy)"로 해석하는 것과 같은 것들이 포함되었습니다. LLM은 "러시아 작가(Russian author)"를 "레프 톨스토이(Leo Tolstoy)"의 별칭으로 잘못 표기했으며, 이로 인해 일치 항목을 찾기 위한 하이브리드 검색의 필요성이 없어졌습니다.</p><p>이와 같은 문제를 해결하기 위해 여러 차례 리팩토링을 거친 후, 5개의 데이터 세트 티어를 사용하게 되었습니다. 티어 1~4는 점점 더 커지고 더 많은 유형의 과제를 포함했습니다. 티어 5는 모든 과제 유형 중에서 가장 까다로운 예제들로 구성된 "궁극의 도전 과제" 데이터 세트였습니다. 모든 테스트 데이터는 <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">종합 평가 디렉터리</a>에서 확인할 수 있습니다.</p><p>프롬프트 기반 엔터티 해석 접근 방식을 평가하기 위해 티어 4 데이터 세트에 집중했습니다. 중요한 점은 이 평가가 통제된 실험으로 수행되어 엔터티 매칭 품질에 집중할 수 있었다는 것입니다. 감시 목록 데이터는 맥락 정보로 사전 보강되었으며, 엔터티는 문서에서 미리 추출되었습니다. 이를 통해 추출 정확도보다는 매칭에 초점을 맞춰 평가할 수 있었습니다. 이를 통해 매칭 품질에 집중합니다. 엔드 투 엔드 성능은 추가적으로 추출 재현율 및 보강 품질에 따라 달라질 수 있습니다.</p><h3>평가 데이터 세트</h3><p>티어 4 평가 데이터 세트는 시스템의 기능을 종합적으로 테스트합니다.[1]</p><ul><li><p><strong>감시 목록 엔터티:</strong> 다양한 유형의 엔터티 66개(사람, 조직, 위치).</p></li><li><p><strong>테스트 문서:</strong> 실제 엔터티 해석 시나리오를 다룬 문서 69개.</p></li><li><p><strong>예상 일치 항목:</strong> 모든 문서에서 예상 엔터티 일치 항목 206개.</p></li><li><p><strong>과제 유형: </strong>엔터티 해석의 다양한 측면을 테스트하는 과제 유형 15가지.</p></li></ul><p>데이터 세트에 포함된 과제 유형은 다음과 같습니다.</p><ul><li><p><strong>별명:</strong> "Bob Smith" → "Robert Smith"(7개 문서).</p></li><li><p><strong>직함 및 경칭:</strong> "Dr. Sarah Williams" → "Sarah Williams"(5개 문서).</p></li><li><p><strong>의미론적 맥락:</strong> "Russian author" → "Leo Tolstoy"(8개 문서).</p></li><li><p><strong>다국어 이름:</strong> 여러 스크립트로 이름 처리(6개 문서).</p></li><li><p><strong>비즈니스 엔터티:</strong> 법인명 변형(7개 문서).</p></li><li><p><strong>임원 참조: </strong>"Microsoft CEO" → "Satya Nadella"(5개 문서).</p></li><li><p><strong>정치 지도자:</strong> 직함 기반 참조(5개 문서).</p></li><li><p><strong>이니셜:</strong> "J. Smith" → "John Smith"(3개 문서).</p></li><li><p><strong>이름 순서 변형:</strong> 다양한 이름 순서 규칙(3개 문서).</p></li><li><p><strong>잘린 이름:</strong> 부분 이름 일치(3개 문서).</p></li><li><p><strong>이름 분할:</strong> 텍스트에 걸쳐 이름 분할(3개 문서).</p></li><li><p><strong>누락된 공백/하이픈:</strong> 서식 변형(2개 문서).</p></li><li><p><strong>음역:</strong> 교차 스크립트 이름 매칭(2개 문서).</p></li><li><p><strong>결합된 과제:</strong> 하나의 문서에 여러 과제(6개 문서).</p></li><li><p><strong>복잡한 비즈니스:</strong> 계층적 비즈니스 관계(5개 문서).</p></li></ul><p>프롬프트 기반 엔터티 해석이 어떻게 수행되었는지 살펴보겠습니다.</p><h3>전반적인 성능</h3><p>결과는 LLM 기반 매치 평가에 많은 가능성이 있음을 보여주지만, 동시에 심각한 신뢰성 문제도 드러났습니다. 각 후보 쌍은 LLM에 의해 평가되어야 하므로, 검색이 잘 작동하더라도 구조화된 출력에 오류가 있으면 허용률과 재현율을 억제할 수 있습니다.</p><p>메트릭</p><p>값</p><p>정밀도</p><p>83.8%</p><p>재현율</p><p>62.6%</p><p>F1 점수</p><p>71.7%</p><p>검색된 총 일치 항목 수</p><p>344</p><p>LLM 합격률</p><p>44.8%</p><p>오류율</p><p>30.2%</p><h3>오류율 문제</h3><p>앞에서 설명한 대로, 프로토타입에서 가장 먼저 한 일은 Elasticsearch를 사용하여 잠재적인 일치 쌍을 생성하는 것입니다. 이러한 잠재적 일치 항목 각각은 LLM에 의해 평가되어야 합니다. 모든 일치 항목을 효율적으로 처리하기 위해 LLM 호출을 배치 처리합니다. 이렇게 하면 API 비용과 지연 시간이 줄어들지만, 출력에 잘못된 형식의 JSON이 포함될 위험도 높아집니다. 배치 크기가 증가함에 따라 JSON은 더 길고 복잡해지므로 LLM이 유효하지 않은 JSON을 생성할 가능성이 높아집니다. 여기에서 30% 오류율이 발생합니다. 평가에서는 요청당 5개의 일치 항목을 배치 크기로 사용했습니다. 이처럼 보수적인 배치 크기를 사용했음에도 불구하고 여전히 JSON 구문 분석 오류가 발생하여 평가 결과가 크게 왜곡됩니다.</p><h2>다음 단계: LLM 통합 최적화</h2><p>이제 시맨틱 검색과 LLM 판단을 사용하여 엔터티를 매칭했으므로 완전한 엔터티 해석 파이프라인을 갖추게 되었습니다. 하지만 이 접근 방식은 모델의 판단은 정확하지만 출력 결과가 사용 불가능할 때 새로운 오류 모드를 불러옵니다. 안정성과 비용 효율성을 높이기 위해 LLM 통합을 최적화할 수 있습니다. 다음 게시물에서는 구조화된 출력을 위해 함수 호출을 사용하는 방법을 탐구할 것입니다. 이는 구조와 유형 안전성을 보장하면서 오류와 비용을 줄여줍니다.</p><h2>직접 사용해 보기</h2><p>엔터티 매칭을 직접 확인해 보고 싶으신가요? 실제 구현, 자세한 설명 및 실제 예제가 포함된 <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">엔터티 매칭 노트북</a>을 확인해 보세요. 이 노트북은 3단계 검색, RRF를 사용한 하이브리드 검색, 그리고 추론을 포함한 LLM 기반 판단을 통해 엔터티를 매칭하는 방법을 정확하게 보여줍니다.</p><p><strong>유의 사항:</strong> 이것은 개념을 교육하기 위해 설계된 교육용 프로토타입입니다. 프로덕션 시스템을 구축할 때는 이 학습 중심 프로토타입에서 다루지 않는 모델 선택, 비용 최적화, 지연 시간 요구 사항, 품질 검증, 오류 처리 및 모니터링과 같은 추가 요소를 고려하세요.</p><h2>참고</h2><ol><li><p>이 데이터 세트는 교육용으로 설계된 합성 데이터 세트입니다. 실제 문제를 어느 정도 반영하지만 특정 프로덕션 환경을 대표하는 것은 아닙니다.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefc59243d9990405/6a17056ab339d5778f769ebf/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[최소 점수를 사용하여 시맨틱 정밀도 보장]]></title>
    <description><![CDATA[최소 점수 임계값을 사용하여 시맨틱 정밀도를 향상하세요. 이 글에서는 시맨틱 검색 및 하이브리드 검색에 대한 구체적인 예시를 제공합니다. ]]></description>
    <content:encoded><![CDATA[<p>시맨틱 검색은 검색 정확도 측면에서 무궁무진한 가능성을 열어주었습니다. ELSER, E5, Jina Embedding v4와 같은 고품질의 희소 및 밀집 모델은 키워드 일치가 아닌 단어의 의미를 기반으로 관련성 높은 결과를 제공합니다. 그러나 시맨틱 검색은 때때로 검색 결과의 끝부분이나 인덱스에 관련 결과가 부족한 쿼리에 대해 관련성이 떨어지는 결과를 반환할 수 있습니다. 이러한 희소 및 밀집 모델의 속성으로 인해 사용자가 혼란을 겪거나 대규모 언어 모델(LLM)에서 귀중한 토큰이 낭비될 수 있습니다.</p><p>이 글에서는 최소 점수 매개변수를 사용하여 시맨틱 검색 결과의 정밀도를 높이는 방법을 알아봅니다. 이 블로그 게시물에 제공된 예시를 테스트해 보고 싶다면 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">관련 Jupyter 노트북</a>으로 이동하세요.</p><h2>배경: 정밀도와 재현율</h2><p>검색 정확도에서 <em>정밀도</em>와 <em>재현율</em>은 핵심 개념입니다. 아직 익숙하지 않은 독자라면 관련 내용을 알아보기를 강력히 권장합니다. 다음은 간략한 내용입니다.</p><ul><li><p><strong>정밀도: </strong>사용자와 관련된 반환된 검색 결과의 비율.</p></li><li><p><strong>재현율: </strong>검색 결과 집합에 포함된 코퍼스 내 모든 관련 문서의 비율.</p></li></ul><p>다시 말해 정밀도는 <strong>오직</strong> 관련성 있는 결과만 반환하는 것이고, 재현율은 관련성 있는 <strong>모든</strong> 결과를 반환하는 것입니다. 짐작하시겠지만, 이 두 가지는 종종 상충되는 요구 사항입니다. 시맨틱 검색은 재현율은 매우 높지만, 정밀도는 떨어지는 경향이 있습니다. 이 속성을 해결하는 방법을 알아보려면 계속 읽어보세요.</p><h2>최소 점수 매개변수 소개</h2><p>'min_score' 매개변수를 사용하면 최소 점수를 설정하여 정밀도를 향상할 수 있으며, 이 매개변수는 정의된 임계값보다 낮은 점수를 가진 일치 항목을 제거하여 결과 집합을 잘라냅니다. 다음은 간단한 예시입니다.</p>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}<h2>점수 정규화</h2><p>최소 점수를 설정하는 것도 좋지만, 모든 시맨틱 모델이 정적 임계값에 적합한 점수를 반환하는 것은 아닙니다. 예를 들어 ELSER는 제한이 없는 점수를 반환합니다. <a href="https://huggingface.co/intfloat/e5-small#faq">일부</a> 밀집 모델 점수는 밀집도가 높으며 특정 쿼리의 컨텍스트에서만 의미가 있습니다.</p><p>대부분의 시맨틱 검색 사례의 경우 'min_score'를 적용하기 전에 정규화 접근 방식을 사용하는 것이 좋습니다. 정규화는 문서 점수가 정의된 간격 내에 있도록 보장합니다. Elasticsearch 검색기는 'l2_norm'과 'minmax'라는 두 가지 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">정규화 도구</a>를 제공합니다. 가장 일반적으로 사용되는 것은 'minmax'입니다. 이해하기 쉽고 많은 시나리오에서 잘 작동하기 때문입니다. 'minmax'의 주요 속성은 다음과 같습니다.</p><ul><li><p>문서 점수는 0–1점 사이로 분포됩니다.</p></li><li><p>가장 높은 점수를 받은 문서는 항상 1점으로 처리됩니다.</p></li><li><p>가장 낮은 점수를 받은 문서는 항상 0점으로 처리됩니다.</p><ul><li><p>이로 인해 키워드 검색에 적합하지 않을 수 있습니다. 자세한 내용은 '하이브리드 검색' 섹션을 참조하세요.</p></li></ul></li></ul><p>다음은 <code>min_score</code> 을(를) 사용하여 정규화된 시맨틱 쿼리의 예입니다. 검색 결과 목록을 100개부터 시작하는 더 긴 목록으로 반환할 수 있도록 순위 창 크기를 500으로 늘렸습니다.</p>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}<p>이 크기는 실제 프로덕션 환경에서 일반적으로 사용되는 값보다 크게 설정되었습니다. 이는 검색 결과의 품질을 검사하고 결과를 조정하기 위한 것입니다.</p><h2>선형 검색기를 사용한 하이브리드 검색</h2><p>하이브리드 검색의 경우 가장 간단한 접근 방식은 모든 점수를 정규화하고 가중치를 부여한 다음 최소 점수를 적용하는 것입니다. 합이 1인 가중치를 선택하면 총점이 0–1 범위 내에 유지됩니다. 이렇게 하면 최종 점수를 쉽게 파악하고 <code>min_score</code> 을(를) 조정할 수 있습니다. 다음은 그 예입니다.</p>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}<h2>RRF를 사용한 하이브리드 검색</h2><p>BM25에서는 종종 <code>AND</code> 연산자나 <code>minimum_should_match</code> 와(과) 같은 다른 수단을 통해 정밀도를 제어하기도 합니다. 또한 단일하고 정확하며 희귀한 용어로 구성된 쿼리는 자연스럽게 검색 결과 수가 적고, 그 결과가 모두 관련성이 높은 경우가 많습니다. 이는 다음과 같은 결과로 이어질 수 있습니다.</p><ul><li><p>BM25 검색기에서 결과 뒷부분에 있는 결과는 절대 BM25 점수가 최고 점수에 근접하더라도 낮은 정규화 점수를 받게 됩니다.</p></li><li><p>매우 낮은 BM25 점수를 시맨틱 점수에 더하면 총점이 시맨틱 점수와 거의 같아집니다.</p></li><li><p>BM25 점수 기여도가 부족하면 <code>min_score threshold</code> 에 의해 문서가 폐기될 수 있습니다.</p></li></ul><p>이에 대한 해결책으로 상호 순위 융합(RRF)을 사용하여 BM25와 시맨틱 결과를 결합할 수 있습니다. RRF는 서로 다른 검색 알고리즘의 점수를 비교하는 문제를 해결하기 위해 각 결과 집합에서의 위치에 초점을 맞춥니다. 이 시나리오에서는 <code>min_score</code> 이(가) 시맨틱 검색기에만 적용됩니다.</p>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}<h2>결론</h2><p><code>min_score</code>을(를) 사용하여 시맨틱 검색 알고리즘의 높은 재현율로 인해 발생하는 결과 집합의 오탐 수를 줄이는 방법을 보여주었습니다. 검색기에 대해 자세히 알아보려면 이 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">블로그 게시물</a>과 <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">Elasticsearch 설명서</a>를 참조하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</guid>
    <category><![CDATA[정확도]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4a3fba607900049/6a170e0fcdacbf8fe17d2a7a/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch로 ChatGPT 커넥터를 구축해 GitHub 문제 쿼리하기]]></title>
    <description><![CDATA[사용자 정의 ChatGPT 커넥터를 구축하고, 하이브리드 검색을 활용해 내부 GitHub 문제를 쿼리하는 Elasticsearch MCP 서버를 배포하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>최근 OpenAI는 프로/비즈니스/엔터프라이즈 및 에듀 요금제에서 ChatGPT를 위한 <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">사용자 정의 커넥터</a> 기능을 발표했습니다. Gmail, GitHub, 드롭박스 등에서 데이터를 활용하기 위한 기본 제공 커넥터 외에도 MCP 서버를 사용하여 사용자 정의 커넥터를 만들 수 있습니다.</p><p>맞춤형 커넥터를 사용하면 기존 ChatGPT 커넥터를 Elasticsearch와 같은 추가 데이터 소스와 결합하여 포괄적인 답변을 얻을 수 있습니다.</p><p>이문서에서는 내부 GitHub 문제와 풀 요청에 대한 정보를 포함하는 Elasticsearch 인덱스에 ChatGPT를 연결하는 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 서버를 구축합니다. 이 기능을 통해 Elasticsearch 데이터를 사용하여 자연어 쿼리에 응답할 수 있습니다.</p><p>Google Colab에서 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>를 사용하여 MCP 서버를 배포하고, ngrok을 통해 ChatGPT가 연결할 수 있는 공개 URL을 얻어 복잡한 인프라 설정을 간소화합니다.</p><p>MCP와 에코시스템에 대한 종합적인 개요는 <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">MCP의 현재 상태</a>를 참조하세요.</p><h2>필수 구성 요소</h2><p>시작하려면 다음이 필요합니다.</p><ul><li><p>Elasticsearch 클러스터 (8.X 이상)</p></li><li><p>인덱스에 대한 읽기 권한이 있는 Elasticsearch API 키</p></li><li><p>Google 계정 (Google Colab용)</p></li><li><p>Ngrok 계정 (무료 요금제 사용 가능)</p></li><li><p>프로/엔터프라이즈/비즈니스 또는 에듀 요금제로 등록된 ChatGPT 계정</p></li></ul><h2>ChatGPT MCP 커넥터 요구사항 이해하기</h2><p>ChatGPT MCP 커넥터를 사용하려면 <code>search</code> 와 <code>fetch</code>, 두 가지를 구현해야 합니다. 자세한 내용은 <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI 문서</a>를 참조하세요.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">검색 툴</a></h3><p>사용자 쿼리를 기반으로 Elasticsearch 인덱스에서 관련 결과 목록을 반환합니다.</p><h4>수신 내용:</h4><ul><li><p>사용자의 자연어 쿼리가 포함된 단일 스트링입니다.</p></li><li><p>예: 'Elasticsearch 마이그레이션과 관련된 문제 찾기'</p></li></ul><h4>반환값: </h4><ul><li><p>결과 객체 배열을 포함하는 <code>result</code> 키를 가진 객체입니다. 각 결과에는 다음이 포함됩니다.</p><ul><li><p><code>id</code> - 고유 문서 식별자</p></li><li><p><code>title</code> - 이슈 또는 PR 제목</p></li><li><p><code>url</code> - 문제/PR에 대한 링크</p></li></ul></li></ul><h4>구현에서:</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">가져오기 도구</a></h3><p>특정 문서의 전체 내용을 가져옵니다.</p><h4>수신 내용:</h4><ul><li><p>검색 결과에서 Elasticsearch 문서 ID를 포함하는 단일 스트링</p></li><li><p>예: 'PR-578의 세부 정보를 가져와'</p></li></ul><h4>반환값:</h4><ul><li><p>다음 항목을 포함한 완전한 문서 객체입니다.</p><ul><li><p><code>id</code> - 고유 문서 식별자</p></li><li><p><code>title</code> - 이슈 또는 PR 제목</p></li><li><p><code>text</code> - 전체 문제/PR 설명 및 세부 정보</p></li><li><p><code>url</code> - 문제/PR에 대한 링크</p></li><li><p><code>type</code> - 문서 유형(문제, pull_request)</p></li><li><p><code>status</code> - 현재 상태(오픈, 진행 중, 해결 완료)</p></li><li><p><code>priority</code> - 우선순위 수준(낮음, 중간, 높음, 긴급)</p></li><li><p><code>assignee</code> - 문제/PR 담당자</p></li><li><p><code>created_date</code> - 생성된 시점</p></li><li><p><code>resolved_date</code> - 해결된 시점(해당되는 경우)</p></li><li><p><code>labels</code> - 문서에 연결된 태그</p></li><li><p><code>related_pr</code> - 관련 풀 요청 ID</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>참고</strong>: 이 예에서는 모든 필드가 루트 레벨에 있는 플랫 구조를 사용합니다. OpenAI 요구 사항은 유연하며 중첩된 메타데이터 개체도 지원합니다.</p><h2>GitHub 이슈 및 PR 데이터 세트</h2><p>이 튜토리얼에서는 문제와 풀 요청이 포함된 내부 GitHub 데이터 집합을 사용합니다. 이는 ChatGPT를 통해 비공개 내부 데이터를 쿼리하는 시나리오입니다.</p><p>데이터 세트는 <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">여기</a>에서 찾을 수 있습니다. 그리고 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">벌크 API</a>를 사용해 데이터의 색인을 업데이트할 것입니다.</p><p>이 데이터 세트에는 다음이 포함되어 있습니다.</p><ul><li><p>설명, 상태, 우선순위 및 담당자와 관련된 문제</p></li><li><p>코드 변경, 리뷰, 배포 정보가 포함된 풀 요청</p></li><li><p>문제와 PR 간의 관계 (예: PR-578이 ISSUE-1889를 수정함)</p></li><li><p>라벨, 날짜 및 기타 메타데이터</p></li></ul><h3>인덱스 매핑</h3><p>인덱스는 <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">매핑</a>을 사용해 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>와의 하이브리드 검색을 지원합니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a>은 시맨틱 검색에 사용되며, 다른 필드는 키워드 검색을 지원합니다.</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>MCP 서버 구축</h2><p>저희 MCP 서버는 더 나은 결과를 위해 하이브리드 검색을 사용하여 시맨틱과 텍스트 매칭을 결합하는 OpenAI 사양을 따르는 두 가지 도구를 구현합니다.</p><h3>검색 툴</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>(상호 순위 융합)을 사용하여 의미론적 검색과 텍스트 매칭을 결합한 하이브리드 검색을 사용합니다.</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>주요 사항:</h3><ul><li><p><strong>RRF를 사용한 하이브리드 검색:</strong> 시맨틱 검색(ELSER)과 텍스트 검색(BM25)을 결합하여 더 나은 결과를 제공합니다.</p></li><li><p><strong>다중 일치 쿼리:</strong> 부스팅(title^3, text^2, assignee^2)을 사용하여 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">여러 필드에 걸쳐 검색합니다</a>. 캐럿 기호(^)는 관련성 점수를 곱하여 콘텐츠보다 제목의 일치 항목에 우선순위를 부여합니다.</p></li><li><p><strong>퍼지 매칭:</strong> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a>는 대략적인 매칭을 허용함으로써 오타와 맞춤법 오류를 처리합니다.</p></li><li><p><strong>상호 순위 결합(RRF) 파라미터 튜닝:</strong></p><ul><li><p><code>rank_window_size: 50</code> - 병합하기 전에 각 검색기(시맨틱 및 텍스트)에서 고려할 상위 결과의 수를 지정합니다.</p></li><li><p><code>rank_constant: 60</code> - 이 값은 개별 결과 집합의 문서가 최종 순위 결과에 미치는 영향을 결정합니다.</p></li></ul></li><li><p><strong>필수 필드만 반환: </strong> OpenAI 사양에 따라 <code>id</code>, <code>title</code>, <code>url</code> 만 반환되며, 불필요한 추가 필드를 노출하지 않습니다.</p></li></ul><h3>가져오기 도구</h3><p>문서가 있는 경우 문서 ID로 문서 세부 정보를 검색합니다.</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>주요 사항:</h3><ul><li><p><strong>문서 ID 필드로 검색:</strong> 사용자 정의 <code>id</code> 필드에서 용어 쿼리를 사용합니다.</p></li><li><p><strong>전체 문서 반환:</strong> 모든 콘텐츠가 포함된 전체 <code>text</code> 필드를 포함합니다.</p></li><li><p><strong>플랫 구조:</strong> 모든 필드가 루트 수준에 있으며, Elasticsearch의 문서 구조와 일치합니다.</p></li></ul><h2>Google Colab에 배포</h2><p>Google Colab을 사용하여 MCP 서버를 실행하고 ngrok을 사용해 외부에 공개함으로써 ChatGPT가 연결할 수 있도록 합니다.</p><h3>1단계: Google Colab 노트북 열기</h3><p>사전 구성된 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP for ChatGPT</a> 노트북에 액세스합니다.</p><h3>2단계: 자격 증명을 구성하세요</h3><p>세 가지 정보를 준비해야 합니다.</p><ul><li><p><strong>Elasticsearch URL:</strong> <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">Elasticsearch 클러스터 URL</a>입니다.</p></li><li><p><strong>Elasticsearch API 키:</strong> <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">API 키</a>로 인덱스에 대한 읽기 액세스 권한을 부여합니다.</p></li><li><p><strong>Ngrok 인증 토큰:</strong> <a href="https://ngrok.com/">ngrok</a>에서 무료로 제공하는 토큰입니다. ngrok을 사용하여 MCP URL을 인터넷에 노출하여 ChatGPT가 연결할 수 있도록 합니다.</p></li></ul><h4>ngrok 토큰 받기</h4><ol><li><p><a href="https://ngrok.com/">ngrok</a>에서 무료 계정 가입</p></li><li><p><a href="https://dashboard.ngrok.com/">ngrok 대시보드</a>로 이동</p></li><li><p>인증 토큰을 복사하세요</p></li></ol><h4>Google Colab에 시크릿 추가</h4><p>Google Colab 노트북에서:</p><ol><li><p>왼쪽 사이드바에서 <strong>키 아이콘</strong>을 클릭하여 <strong>시크릿</strong>을 엽니다.</p></li><li><p>이 세 가지 시크릿을 추가합니다.</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. 각 시크릿에 대한 노트북 접근 활성화</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="Google Colab에 시크릿 추가" /><h3>3단계: 노트북 실행</h3><ol><li><p><strong>런타임</strong>을 클릭한 다음 <strong>모두 실행</strong>을 클릭하여 모든 셀을 실행합니다.</p></li><li><p>서버가 시작될 때까지 기다립니다(약 30초).</p></li><li><p>공개 ngrok URL을 표시하는 출력을 찾습니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. 출력은 다음과 같이 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="Google Colab에서 노트북을 실행한 출력" /><h2>ChatGPT에 연결</h2><p>이제 MCP 서버를 ChatGPT 계정에 연결합니다.</p><ol><li><p>ChatGPT를 열고 <strong>설정</strong>으로 이동합니다.</p></li><li><p><strong>커넥터</strong>로 이동합니다.프로 계정을 사용하는 경우 커넥터에서 <a href="https://platform.openai.com/docs/guides/developer-mode">개발자 모드</a>를 사용 설정해야 합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="MPC 서버를 ChatGPT 계정에 연결하기" /><p><em>ChatGPT 엔터프라이즈 또는 비즈니스 버전을 사용하는 경우, 커넥터를 작업 환경에 게시해야 합니다.</em></p><p>3. <strong>만들기</strong>를 클릭합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="ChatGPT에 커넥터 추가" /><p><em><strong>참고</strong></em><em>: 비즈니스, 엔터프라이즈, 에듀 작업 공간에서는 작업 공간 소유자, 관리자 및 해당 설정이 활성화된 사용자(엔터프라이즈/에듀)만 사용자 정의 커넥터를 추가할 수 있습니다. 일반 회원 역할을 가진 사용자는 사용자 정의 커넥터를 직접 추가할 수 없습니다.</em></p><p><em>소유자 또는 관리자 사용자가 커넥터를 추가하고 활성화하면 작업 공간의 모든 구성원이 이를 사용할 수 있습니다.</em></p><p>4. 필요한 정보를 입력하고 <code>/sse/</code>로 끝나는 ngrok URL을 입력합니다. 'sse"' 뒤의 '/'에 주의하세요. 이것 없이는 작동하지 않습니다.</p><ul><li><p><strong>이름:</strong> Elasticsearch MCP</p></li><li><p><strong>설명: </strong>GitHub 내부 정보를 검색하고 가져오기 위한 사용자 정의 MCP입니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="Elastic MCP 커넥터 생성 " /><p>5. <strong>생성</strong> 을 눌러 사용자 정의 MCP를 저장하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="생성을 클릭하여 사용자 정의 MCP 커넥터 저장" /><p>서버가 실행 중이면 즉시 연결됩니다. Elasticsearch API 키가 서버에 구성되어 있으므로 추가 인증이 필요하지 않습니다.</p><h2>MCP 서버 테스트</h2><p>질문을 하기 전에 ChatGPT가 사용할 커넥터를 선택해야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="ChatGPT에서 사용할 커넥터 선택" /><h3>프롬프트 1: 문제 검색</h3><p>질문: '<strong>Elasticsearch 마이그레이션과 관련된 문제를 찾아'</strong>라고 요청하고 작업 도구 호출을 확인하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="ChatGPT에 'Elasticsearch 마이그레이션과 관련된 문제 찾기'를 요청하고 작업 도구 호출을 확인합니다." /><p>ChatGPT가 사용자의 쿼리를 사용하여 <code>search</code> 도구를 호출합니다. 사용 가능한 도구를 찾고 Elasticsearch 도구를 호출할 준비를 하며, 도구에 대해 조치를 취하기 전에 사용자에게 확인하는 것을 볼 수 있습니다.</p><h4>도구 호출 요청:</h4>{
  "query": "Elasticsearch migration issues"
}<h4>도구 응답:</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT는 결과를 처리하여 자연스러운 대화 형식으로 표시합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="ChatGPT가 도구 호출 요청 및 도구 호출 응답의 결과를 처리하는 방법" /><h3>비하인드 스토리</h3><h4>프롬프트: 'Elasticsearch 마이그레이션 관련 문제를 찾아'</h4><p>1. ChatGPT 호출 <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch가 하이브리드 검색 수행</p><ul><li><p><strong>시맨틱 검색</strong>은 '업그레이드' 및 '<em>버전 호환성</em>'과 같은 개념을 이해합니다.</p></li><li><p><strong>텍스트 검색</strong>은 '<em>Elasticsearch</em>' 및 '마이그레이션'과 정확히 일치하는 결과를 찾습니다.</p></li><li><p><strong>RRF</strong>는 두 가지 접근 방식의 결과를 결합하고 순위를 매깁니다.</p></li></ul><p>3. <code>id</code>, <code>title</code>와 일치하는 상위 10개의 이벤트를 반환합니다, <code>url</code></p><p>4. ChatGPT는 '<em>ISSUE-1712: Elasticsearch 7.x에서 8.x로 마이그레이션</em>'을 가장 관련성 있는 결과로 식별합니다</p><h3>프롬프트 2: 전체 세부 정보 확인</h3><p>문의: <em><strong>'ISSUE-1889의 세부 정보를 보여줘'</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT는 사용자가 특정 문제에 대한 자세한 정보를 원한다는 것을 인식하고 가져오기 도구를 호출한 후, 도구에 대해 조치를 취하기 전에 사용자에게 확인합니다." /><p>ChatGPT는 사용자가 특정 문제에 대한 자세한 정보를 원한다는 것을 인식하고 <code>fetch</code> 도구를 호출한 후, 도구에 대해 조치를 취하기 전에 사용자에게 확인합니다.</p><h4>도구 호출 요청:</h4>{
  "id": "ISSUE-1889"
}<h4>도구 응답:</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT는 정보를 종합하여 명확하게 제시합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="ChatGPT가 정보를 종합하고 제시하는 방법 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="ChatGPT가 정보를 제시하는 방법" /><h3>비하인드 스토리</h3><h4>프롬프트: 'ISSUE-1889의 세부 정보를 가져와'</h4><ol><li><p>ChatGPT 호출 <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch가 전체 문서를 검색합니다.</p></li><li><p>모든 필드가 루트 수준에 포함된 전체 문서를 반환합니다.</p></li><li><p>ChatGPT는 정보를 종합하고 적절한 출처를 제시하며 응답합니다.</p></li></ol><h2>결론</h2><p>이 문서에서는 전용 <strong>검색</strong> 및 <strong>가져오기</strong> MCP 도구를 사용하여 ChatGPT를 Elasticsearch에 연결하고, 비공개 데이터에 대한 자연어 쿼리를 가능하게 하는 사용자 정의 MCP 서버를 구축했습니다.</p><p>이 MCP 패턴은 자연어로 쿼리하려는 모든 Elasticsearch 인덱스, 문서, 제품, 로그 또는 기타 데이터에 적용할 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[번거로움 없는 하이브리드 검색: 검색기를 사용한 하이브리드 검색 간소화]]></title>
    <description><![CDATA[선형 및 RRF 검색기를 위한 다중 필드 쿼리 형식을 사용해 Elasticsearch에서 하이브리드 검색을 간소화하는 방법을 살펴보고, Elasticsearch 인덱스에 대한 사전 지식 없이도 쿼리를 생성하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/what-is/hybrid-search">하이브리드 검색은</a> <a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">어휘 검색의</a> 정확성과 속도에 시맨틱 <a href="https://www.elastic.co/what-is/semantic-search">검색의</a> 자연어 기능을 결합한 강력한 검색 방식으로 널리 알려져 있습니다. 그러나 실제로 적용하는 것은 까다로울 수 있으며, 종종 인덱스에 대한 깊은 지식이 필요하고 간단한 구성이 아닌 장황한 쿼리를 구성해야 합니다. 이 블로그에서는 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">선형 및 RRF 검색기를 위한 다중 필드 쿼리 형식이</a> 어떻게 하이브리드 검색을 더 간단하고 접근하기 쉽게 만들어 일반적인 골칫거리를 없애고 그 모든 기능을 더 쉽게 활용할 수 있게 해주는지 살펴봅니다. 또한 다중 필드 쿼리 형식을 통해 인덱스에 대한 사전 지식 없이도 하이브리드 검색 쿼리를 수행할 수 있는 방법을 검토합니다.</p><h2>점수 범위 문제</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3521a6558cd477ca/6a17f174445de94d3c4d0225/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" alt="" /><p>하이브리드 검색이 어려운 주요 이유 중 하나인 다양한 점수 범위를 살펴보기 위해 무대를 설정해 보겠습니다. 우리의 오랜 친구 <a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">BM25는</a> 무한한 점수를 만들어냅니다. 즉, BM25는 0에 가까운 점수부터 (이론적으로) 무한대까지 다양한 점수를 생성할 수 있습니다. 반대로 <code>dense_vector</code> 필드에 대한 쿼리는 0과 1 사이의 점수를 생성합니다. 이 문제를 더욱 악화시키는 <code>semantic_text</code> 은 임베딩을 색인하는 데 사용되는 필드 유형을 난독화하므로 색인 및 추론 엔드포인트 구성에 대한 자세한 지식이 없으면 쿼리의 점수 범위를 알기 어려울 수 있습니다. 이는 어휘 검색 결과와 의미 검색 결과를 통합하려고 할 때 문제가 되는데, 의미 검색 결과가 더 관련성이 높더라도 어휘 검색 결과가 의미 검색 결과보다 우선할 수 있기 때문입니다. 이 문제에 대한 일반적인 해결책은 결과를 인터리빙하기 전에 점수를 정규화하는 것입니다. 이를 위한 두 가지 도구, <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">선형</a> 검색기와 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a> 검색기가 있습니다.
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09ed6bf3d25066bd/6a17f1750b0bedbd70dd3686/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" alt="선형/알고리즘을 사용한 검색 결과와 사용하지 않은 검색 결과 비교" /><p><strong>RRF</strong> 검색기는 문서 순위를 관련성 측정값으로 사용하고 점수를 버리는 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF 알고리즘을</a> 적용합니다. 점수는 고려되지 않으므로 점수 범위 불일치는 문제가 되지 않습니다.</p><p><strong>리니어</strong> 리트리버는 선형 조합을 사용하여 문서의 최종 점수를 결정합니다. 여기에는 문서에 대한 각 구성 요소 쿼리의 점수를 가져와서 정규화한 다음 합산하여 총 점수를 생성하는 작업이 포함됩니다. 수학적으로 이 연산은 다음과 같이 표현할 수 있습니다:</p>Total Score = 𝚺(N(Sx))<p>여기서 <code>N</code> 은 정규화 함수이고 SX 는 쿼리 X 의 점수입니다. 정규화 함수는 각 쿼리의 점수를 동일한 범위를 사용하도록 변환하기 때문에 여기서 핵심적인 역할을 합니다. 리니어 리트리버에 대해 자세히 알아보려면 <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">여기를</a> 참조하세요.</p><h2>분석하기</h2><p>사용자는 이러한 도구를 사용하여 효과적인 하이브리드 검색을 구현할 수 있지만 색인에 대한 약간의 지식이 필요합니다. 두 개의 필드가 있는 인덱스를 쿼리하는 리니어 리트리버의 예를 살펴보겠습니다:</p>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { &lt;1&gt;
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { &lt;2&gt;
        "type": "text"
      }
    }
  }
}<p>1. <code>semantic_text_field</code> 는 텍스트 임베딩 모델인 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5를</a> 사용하는 <code>semantic_text</code> 필드입니다.</p><p><code>text_field</code> 는 표준 <code>text</code> 필드입니다.</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { &lt;1&gt;
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>1. <code>semantic_text</code> 필드에서 <code>match</code> 쿼리를 사용하며, <a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!">Elasticsearch 8.18/9.0에서 지원이 추가되었습니다</a>.</p><p>
쿼리를 구성할 때 <code>semantic_text_field</code> 은 텍스트 임베딩 모델을 사용하므로 이 쿼리에 대한 모든 쿼리는 0에서 1 사이의 점수를 생성한다는 점을 염두에 두어야 합니다. 또한 <code>text_field</code> 은 표준 <code>text</code> 필드이므로 이 필드로 쿼리하면 무제한 점수가 생성된다는 점도 알아야 합니다. 적절한 연관성을 가진 결과 집합을 만들려면 쿼리 점수를 결합하기 전에 정규화하는 리트리버를 사용해야 합니다. 이 예에서는 각 쿼리의 점수를 0과 1 사이의 값으로 정규화하는 <code>minmax</code> 정규화와 함께 선형 검색기를 사용합니다.</p><p>이 예제의 쿼리 구성은 두 개의 필드만 관련되어 있기 때문에 매우 간단합니다. 그러나 더 많은 필드와 다양한 유형이 추가되면 매우 빠르게 복잡해질 수 있습니다. 이는 효과적인 하이브리드 검색 쿼리를 작성하려면 쿼리 대상 인덱스에 대한 심층적인 지식이 필요한 경우가 많으므로 구성 요소 쿼리 점수를 조합하기 전에 적절하게 정규화해야 한다는 것을 보여줍니다. 이는 하이브리드 검색의 광범위한 채택을 가로막는 장벽이 됩니다.</p><h3>쿼리 그룹화</h3><p>예제를 확장해 보겠습니다: 하나의 <code>text</code> 필드와 두 개의 <code>semantic_text</code> 필드를 쿼리하려면 어떻게 해야 할까요? 다음과 같이 쿼리를 작성할 수 있습니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>겉으로 보기에는 좋아 보이지만 잠재적인 문제가 있습니다. 이제 <code>semantic_text</code> 필드 매치가 전체 점수의 ⅔를 차지합니다:</p>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)<p>이는 불균형한 점수를 만들기 때문에 원하지 않을 수도 있습니다. 필드가 3개만 있는 이 예제에서는 그 영향이 눈에 띄지 않을 수 있지만, 더 많은 필드가 쿼리되면 문제가 됩니다. 예를 들어, 대부분의 인덱스에는 시맨틱 필드보다 훨씬 더 많은 어휘 필드가 포함되어 있습니다(예 <code>dense_vector</code>, <code>sparse_vector</code>, 또는 <code>semantic_text</code>). 위의 패턴을 사용하여 어휘 필드 9개와 의미 필드 1개가 있는 인덱스를 쿼리한다면 어떨까요? 어휘 일치 항목이 점수의 90% 을 차지하여 의미론적 검색의 효과를 무색하게 만들었습니다.</p><p>이 문제를 해결하는 일반적인 방법은 쿼리를 어휘 및 의미 범주로 그룹화하고 두 범주에 균등하게 가중치를 부여하는 것입니다. 이렇게 하면 어느 한 카테고리가 총점을 지배하는 것을 방지할 수 있습니다.</p><p>이를 실천에 옮겨 보겠습니다. 이 예제에서 리니어 리트리버를 사용할 때 그룹화된 쿼리 접근 방식은 어떤 모습일까요?</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>와, 점점 장황해지네요! 전체 쿼리를 살펴보기 위해 위아래로 여러 번 스크롤해야 했을 수도 있습니다! 여기서는 두 가지 수준의 정규화를 사용하여 쿼리 그룹을 생성합니다. 수학적으로는 다음과 같이 표현할 수 있습니다:</p>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)<p>이 두 번째 정규화 수준은 <code>semantic_text</code> 필드와 <code>text</code> 필드에 대한 쿼리의 가중치가 균등하게 적용되도록 합니다. 이 예제에서는 어휘 필드가 하나뿐이므로 <code>text_field</code> 에 대한 두 번째 수준 정규화를 생략하여 <em>더</em> 자세한 설명을 생략했습니다.</p><p>이 쿼리 구조는 이미 다루기 힘든데다 세 개의 필드만 쿼리하고 있습니다. 더 많은 필드를 쿼리할수록 숙련된 검색 실무자라도 관리하기가 점점 더 어려워집니다.</p><h2>다중 필드 쿼리 형식</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d9007d279f0da29/6a17f17714d90c39cf79b6f5/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" alt="" /><p>이 모든 것을 간소화하기 위해 Elasticsearch 8.19, 9.1 및 <a href="https://www.elastic.co/cloud/serverless">서버리스에서 선형</a> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">및 RRF 검색기에 대한 다중 필드 쿼리 형식을 추가했습니다.</a> 이제 위와 동일한 쿼리를 수행할 수 있습니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>쿼리가 55줄에서 단 9줄로 줄어듭니다! Elasticsearch는 인덱스 매핑을 자동으로 사용합니다:</p><ul><li><p>쿼리되는 각 필드의 유형을 결정합니다.</p></li><li><p>각 필드를 어휘 또는 의미론적 범주로 그룹화합니다.</p></li><li><p>최종 점수에서 각 카테고리에 균등하게 가중치를 부여합니다.</p></li></ul><p>이를 통해 누구나 인덱스나 사용된 추론 엔드포인트에 대한 세부 정보를 몰라도 효과적인 하이브리드 검색 쿼리를 실행할 수 있습니다.</p><p>RRF를 사용하는 경우 순위가 관련성의 프록시로 사용되므로 <code>normalizer</code> 을 생략할 수 있습니다:</p>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}<h2>필드별 부스팅</h2><p>리니어 리트리버를 사용할 때 필드별 부스트를 적용하여 특정 필드에서 경기의 중요도를 조정할 수 있습니다. 예를 들어 <code>semantic_text</code> 필드 2개와 <code>text</code> 필드 2개 등 4개의 필드를 쿼리한다고 가정해 보겠습니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>기본적으로 각 필드는 해당 그룹(어휘 또는 의미)에서 동일하게 가중치가 부여됩니다. 점수 분석은 다음과 같습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc6e197e5ff7b68d/6a17f179505ac32834ad8c46/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" alt="쿼리 그룹 및 필드 점수 비교" /><p>즉, 각 필드는 총 점수의 25% %입니다.</p><p><code>field^boost</code> 구문을 사용하여 모든 필드에 필드별 부스트를 추가할 수 있습니다. <code>semantic_text_field_1</code> 와 <code>text_field_1</code> 에 2의 부스트를 적용해 보겠습니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>이제 점수 분석은 다음과 같습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccc9ed7d6e865a5e/6a17f17abe6086a31d004882/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" alt="참조 및 하이브리드 검색으로 변경된 필드 가중치" /><p>각 쿼리 그룹은 여전히 동일하게 가중치가 적용되지만 이제 그룹 내 필드 가중치가 변경되었습니다:</p><ul><li><p><code>semantic_text_field_1</code> 시맨틱 쿼리 그룹 점수는 66%, 총 점수는 33% 입니다.</p></li><li><p><code>text_field_1</code> 는 어휘 쿼리 그룹 점수 66%, 총 점수 33% 입니다.</p></li></ul><p>ℹ️ 필드별 부스트가 적용되어도 총 점수 범위는 변경되지 않습니다. 이는 점수 정규화의 의도된 부작용으로, 어휘 및 의미론적 쿼리 점수가 서로 직접 비교 가능한 상태로 유지되도록 합니다.</p><p>ℹ️ 필드별 부스팅은 Elasticsearch 9.2+의 RRF 리트리버와 함께 사용할 수도 있습니다.</p><h3>와일드카드 해상도</h3><p><code>fields</code> 매개변수에 <code>*</code> 와일드카드를 사용하여 여러 필드를 일치시킬 수 있습니다. 위의 예를 계속 이어서, 이 쿼리는 기능적으로<code>emantic_text_field_1</code>, <code>semantic_text_field_2</code>, <code>text_field_1</code> 을 명시적으로 쿼리하는 것과 동일합니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p><code>*_field_1</code> 패턴이 <code>text_field_1</code> 및 <code>semantic_text_field_1</code> 와 모두 일치한다는 점이 흥미롭습니다. 이는 자동으로 처리되며, 각 필드가 명시적으로 쿼리된 것처럼 쿼리가 실행됩니다. <code>semantic_text_field_1</code> 이 두 패턴과 모두 일치해도 괜찮습니다. 모든 필드 이름 일치 항목은 쿼리 실행 전에 중복이 제거됩니다.</p><p>와일드카드는 다양한 방법으로 사용할 수 있습니다:</p><ul><li><p>접두사 일치(예: <code>*_text_field</code>)</p></li><li><p>인라인 매칭(예: <code>semantic_*_field</code>)</p></li><li><p>접미사 일치(예: <code>semantic_text_field_*</code>)</p></li></ul><p><code>*_text_field_*</code> 와 같이 여러 와일드카드를 사용하여 위의 조합을 적용할 수도 있습니다.</p><h3>기본 쿼리 필드</h3><p>다중 필드 쿼리 형식을 사용하면 전혀 모르는 인덱스를 쿼리할 수도 있습니다. <code>fields</code> 매개변수를 생략하면 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">index.query.default_field 인덱스 설정으로</a> 지정된 모든 필드를 쿼리합니다:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>기본적으로 <code>index.query.default_field</code> 은 <code>*</code> 으로 설정되어 있습니다. 이 와일드카드는 용어 쿼리를 지원하는 인덱스의 모든 필드 유형(대부분)으로 확인합니다. 예외는 있습니다:</p><ul><li><p><code>dense_vector</code> 필드</p></li><li><p><code>rank_vector</code> 필드</p></li><li><p>지오메트리 필드: <code>geo_point</code>, <code>shape</code></p></li></ul><p>이 기능은 타사에서 제공하는 색인에 대해 하이브리드 검색 쿼리를 수행하려는 경우에 특히 유용합니다. 다중 필드 쿼리 형식을 사용하면 간단한 방법으로 적절한 쿼리를 실행할 수 있습니다. <code>fields</code> 매개변수만 제외하면 해당되는 모든 필드가 쿼리됩니다.</p><h2>결론</h2><p>점수 범위 문제로 인해 효과적인 하이브리드 검색을 구현하기가 어려울 수 있으며, 특히 쿼리되는 인덱스나 사용 중인 추론 엔드포인트에 대한 인사이트가 제한적인 경우 더욱 그렇습니다. 선형 및 RRF 검색기를 위한 다중 필드 쿼리 형식은 자동화된 쿼리 그룹화 기반의 하이브리드 검색 방식을 간단하고 접근하기 쉬운 API로 패키징하여 이러한 어려움을 덜어줍니다. 필드별 부스팅, 와일드카드 해상도 및 기본 쿼리 필드와 같은 추가 기능을 통해 다양한 사용 사례를 포괄하는 기능을 확장할 수 있습니다.</p><h2>지금 바로 다중 필드 쿼리 형식을 사용해 보세요.</h2><p><a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">무료</a> 체험 판으로 완전 <a href="https://www.elastic.co/cloud/serverless">관리형 Elasticsearch 서버리스</a> 프로젝트에서 다중 필드 쿼리 형식의 선형 및 RRF 검색기를 확인해 보세요. 8.19 &amp; 9.1부터 스택 버전으로도 사용할 수 있습니다.</p><p>명령 한 번으로 로컬 환경에서 몇 분 안에 시작할 수 있습니다:</p>curl -fsSL https://elastic.co/start-local | sh<p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[정확도]]></category>
    <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89e066372c549595/6a17f17c3e9e457c38ba1583/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[맥락을 위한 검색 - 3부: 맥락 엔지니어링에서 하이브리드 검색의 힘]]></title>
    <description><![CDATA[컨텍스트 엔지니어링과 하이브리드 검색을 사용하여 집계, RBAC 및 비콘텐츠 신호로 AI 출력 정확도를 개선하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>지금까지 하이브리드 검색<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">(1부)</a>과 컨텍스트 엔지니어링<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">(2부)</a>에 대해 살펴봤는데, 이제 이 두 가지가 어떻게 함께 작동하여 RAG 및 에이전트 AI 운영에 타겟팅된 컨텍스트를 제공하는 데 가장 큰 효과를 가져오는지 살펴보겠습니다.</p><h2>검색은 죽지 않았고, 단지 이동했을 뿐입니다.</h2><p>따라서 주로 텍스트 상자를 통해 문맥을 검색하고 반환된 정보(문맥)를 사용하여 직접 답변을 구성하는 방식에서 이제는 자연어를 사용하여 상담원에게 원하는 것을 말하면 자동으로 검색하여 답변을 작성하는 방식으로 전환했습니다. 기술 업계의 많은 사람들이 이러한 변화를 지적하며 "검색은 죽었다"고 선언하고 있지만(물론 SEO와 애드워즈 세계는 <a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">확실히 변화하고</a> 있습니다 <a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">.</a> 누구세요?) 검색은 여전히 에이전트 운영에 절대적으로 중요하며, 지금은 대부분 도구를 통해 보이지 않는 곳에서 수행될 뿐입니다.</p><p>이전에는 사용자가 주관적인 관련성의 주요 중재자였습니다. 사용자마다 검색을 실행하는 이유가 다르고, 개인적인 경험에 따라 결과의 상대적 정확도가 달라집니다. 에이전트가 우리와 동일한(또는 더 나은) 결론에 도달할 수 있다고 믿으려면 에이전트가 액세스할 수 있는 컨텍스트 정보가 우리의 주관적인 의도에 최대한 가깝도록 보장해야 합니다. 우리는 그 목표를 향해 LLM을 제공하는 맥락을 설계해야 합니다!</p><h2>하이브리드 검색 검색을 통한 컨텍스트 생성</h2><p>1부에서 다시 한 번 말씀드리지만, Elastic의 하이브리드 검색은 기존 키워드 기반 검색의 강점(구문 유연성, 키워드 정밀도, 관련성 점수)과 벡터 유사성 검색의 의미론적 이해를 결합하고 다양한 재순위 지정 기술을 제공합니다. 이 시너지 효과(이 단어의 진정한 용도는 찾아볼 수 없습니다!) 를 사용하면 콘텐츠를 타겟팅하는 방식에 훨씬 더 미묘한 차이가 있는 쿼리를 통해 연관성이 높은 결과를 얻을 수 있습니다. 검색 단계 <em>중 하나로</em> 주관적 연관성을 적용할 수 있다는 것뿐만 아니라, 실제로는 1단계 검색에 다른 모든 모드와 함께 연관성 점수를 한 번에 포함할 수 있다는 것입니다.</p><h3>뛰어난 정확도 &amp; 효율성</h3><p>분산 검색, 검색 및 순위 재지정을 제공할 수 있는 데이터 플랫폼을 기본 컨텍스트 검색 엔진으로 사용하는 것은 매우 합리적입니다. 고급 쿼리 구문을 사용하여 주관적 의도의 누락된 구성 요소를 추가하고, 반환된 문맥 정보의 가치를 흐리게 하거나 방해할 수 있는 콘텐츠를 필터링할 수 있습니다. 사용 가능한 개별 구문 옵션 중에서 선택하거나 각 유형의 데이터를 가장 잘 이해하는 방식으로 타겟팅하는 단일 검색으로 모달리티를 결합한 다음 순위를 재조정하여 결합/재배열할 수 있습니다. 원하는 필드/값만 포함하도록 응답을 필터링하여 불필요한 데이터를 차단할 수 있습니다. 상담원 서비스에서는 이러한 타겟팅 유연성을 통해 컨텍스트를 검색하는 방식이 매우 정확한 툴을 구축할 수 있습니다.</p><h3>컨텍스트 세분화(집계 및 비콘텐츠 신호)</h3><p>집계는 도구가 컨텍스트 창에 제공하는 콘텐츠를 구성하는 데 특히 유용할 수 있습니다. 집계는 자연스럽게 반환된 컨텍스트 데이터의 형태에 대한 수치 기반 사실을 제공하므로, LLM이 더 쉽고 정확하게 추론할 수 있습니다. 집계는 계층적으로 중첩될 수 있기 때문에 LLM에 다단계 세부 정보를 쉽게 추가하여 보다 미묘한 차이를 파악할 수 있습니다. 집계는 컨텍스트 창 크기를 관리하는 데도 도움이 됩니다. 10만 개의 문서에 대한 쿼리 결과를 수백 개의 집계된 인사이트 토큰으로 쉽게 줄일 수 있습니다.</p><p>비콘텐츠 신호는 인기도, 신선도, 지리적 위치, 카테고리, 호스트 다양성, 가격대 등 결과의 추가적인 특성을 나타내는 데이터의 내재적 지표로, 현재 보고 있는 내용에 대한 더 큰 그림을 알려줍니다. 이러한 정보는 상담원이 수신한 컨텍스트의 중요도를 평가하는 데 유용할 수 있습니다. 몇 가지 간단한 예시를 통해 이를 가장 잘 설명할 수 있습니다:</p><ul><li><p><strong>최근에 게시된 인기 콘텐츠 강화하기</strong> - 문서에 대한 지식창고가 있다고 가정해 보세요. 사용자의 검색어와 관련된 문서를 찾고 싶지만, 최근 문서이면서 다른 사용자가 도움이 되었다고 판단한 문서(예: "좋아요" 수가 많은 문서)도 부스팅하고 싶을 수 있습니다. 이 시나리오에서는 하이브리드 검색을 사용하여 관련성 있는 문서를 찾은 다음 게시 날짜와 인기도를 조합하여 순위를 재조정할 수 있습니다.</p></li><li><p><strong>판매 및 재고 조정 기능이 있는 전자상거래 검색</strong> - 전자상거래 환경에서는 고객에게 검색어와 일치하는 제품을 표시하는 동시에 잘 팔리고 재고가 있는 제품을 홍보하고 싶을 수 있습니다. 또한 재고가 적은 제품의 순위를 낮춰 고객의 불만을 피할 수도 있습니다.</p></li><li><p><strong>버그 트래커에서 심각도가 높은 이슈 우선 순위 지정하기</strong> - 소프트웨어 개발팀의 경우 이슈를 검색할 때 심각도가 높고 우선 순위가 높으며 최근에 업데이트된 이슈를 먼저 표시하는 것이 중요합니다. '중요도' 및 '가장 많이 논의된' 등의 비신호를 사용하여 다양한 요소를 독립적으로 평가하여 가장 중요하고 활발하게 논의된 이슈가 맨 위에 표시되도록 할 수 있습니다.</p></li></ul><p>이러한 예제 쿼리 등은 함께 제공되는 Elasticsearch Labs <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">콘텐츠 페이지에서</a> 확인할 수 있습니다.</p><h3>보안 시행</h3><p>컨텍스트 엔지니어링을 위해 Elastic과 같은 검색 기반 속도 계층을 활용할 때의 중요한 장점은 기본 제공 보안 프레임워크입니다. Elastic의 플랫폼은 세분화된 역할 기반 액세스 제어(RBAC)와 속성 기반 액세스 제어(ABAC)를 통해 에이전트 및 생성 AI 작업에 제공되는 컨텍스트가 민감한 개인 보유 정보를 존중하고 보호하도록 보장합니다. 즉, 쿼리가 효율적으로 처리될 뿐만 아니라 요청을 시작한 상담원이나 사용자의 특정 권한에 따라 결과가 필터링됩니다.</p><p>에이전트는 인증된 사용자로 실행되므로 플랫폼에 내장된 보안 기능을 통해 보안이 암시적으로 적용됩니다:</p><ul><li><p><strong>세분화된 권한:</strong> 문서, 필드 또는 용어 수준에서 액세스 권한을 정의하여 AI 에이전트가 볼 권한이 있는 데이터만 받도록 하세요.</p></li><li><p><strong>역할 기반 액세스 제어(RBAC):</strong> 에이전트 또는 사용자에게 역할을 할당하여 정의된 책임에 따라 특정 데이터 세트 또는 기능에 대한 액세스 권한을 부여합니다.</p></li><li><p><strong>속성 기반 액세스 제어(ABAC):</strong> 데이터, 사용자 또는 환경의 속성을 기반으로 동적 액세스 정책을 구현하여 고도로 적응력이 뛰어나고 상황에 맞는 보안을 구현할 수 있습니다.</p></li><li><p><strong>문서 수준 보안(DLS) 및 필드 수준 보안(FLS):</strong> 이러한 기능은 검색된 문서 내에서도 승인된 부분만 볼 수 있도록 하여 민감한 정보가 노출되는 것을 방지합니다.</p></li><li><p><strong>엔터프라이즈 보안과 통합:</strong> 기존 ID 관리 시스템(예: LDAP, SAML, OIDC)과 원활하게 통합하여 조직 전체에 일관된 보안 정책을 적용할 수 있습니다.</p></li></ul><p>이러한 보안 조치를 컨텍스트 검색 메커니즘에 직접 통합함으로써 Elastic은 보안 게이트키퍼 역할을 수행하여 AI 에이전트가 정의된 데이터 경계 내에서 작동하도록 보장하고 무단 데이터 노출을 방지하며 데이터 개인 정보 보호 규정을 준수하도록 유지합니다. 이는 기밀 또는 독점 정보를 처리하는 에이전트 AI 시스템에 대한 신뢰를 구축하는 데 가장 중요한 요소입니다.</p><p>추가로, 엔터프라이즈 데이터 소스에서 통합 데이터 속도 계층을 사용하면 에이전트 도구가 생성하는 리포지토리의 예기치 않은 임시 쿼리 부하를 완화할 수 있습니다. 한 곳에서 모든 것을 거의 실시간으로 검색하고 보안 및 거버넌스 제어를 적용할 수 있습니다.</p><h2>하이브리드 검색 기반 도구</h2><p>컨텍스트 엔지니어링의 추구를 가속화하는 Elastic 플랫폼의 몇 가지 핵심 기능( <a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">계속 추가될</a> 예정)이 있습니다. 여기서 가장 중요한 것은 플랫폼이 AI 생태계가 발전함에 따라 유연하게 적응, 변경, 확장할 수 있는 다양한 방법을 제공한다는 점입니다.</p><h3>에이전트 빌더 소개</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">에이전트 빌더는</a> Elastic에 이미 저장되어 있는 데이터와 채팅할 수 있도록 구축된 에이전트 AI 도구 영역에 처음으로 진출한 제품입니다. 에이전트 빌더는 사용자가 Kibana 내에서 자신만의 에이전트와 도구를 생성하고 관리할 수 있는 채팅 인터페이스를 제공합니다. 기본 제공 MCP 및 A2A 서버, 프로그래밍 방식의 API, Elasticsearch 인덱스를 쿼리 및 탐색하고 자연어로부터 ES|QL 쿼리를 생성하기 위한 사전 구축된 시스템 도구 세트가 함께 제공됩니다. 에이전트 빌더를 사용하면 표현식 <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> 쿼리 구문을 통해 에이전트에게 반환되는 컨텍스트 데이터를 타겟팅하고 조각하는 사용자 지정 도구를 만들 수 있습니다.</p><p>ES|QL은 하이브리드 검색을 어떻게 수행하나요? <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">핵심 기능은 semantic_text</a> 필드 유형과 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"></a><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FORK/FUSE</a> 명령의 조합을 통해 수행됩니다(FUSE는 기본적으로 각 포크의 결과를 병합하는 데 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF를 사용합니다).</a> 다음은 가상의 제품 검색에 대한 간단한 예제입니다:</p>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type<p>위의 예제에서 각 FORK 브랜치에 포함된 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a> 절은 반드시 필요한 것은 아니며, 특정 검색 결과가 어떤 검색 방식에서 반환되었는지 추적하는 방법을 보여주기 위해 포함되었을 뿐입니다.</p><h3>템플릿 검색</h3><p>자체 외부 에이전트 도구를 Elastic 배포로 가리키고 싶다고 가정해 보겠습니다. 또한 ES|QL 대신 다단계 검색기를 사용하거나 개발한 기존 DSL 구문을 재사용하고 쿼리가 허용하는 입력, 검색 실행에 사용되는 구문 및 출력에 반환되는 필드를 제어할 수 있기를 원합니다. <a href="https://www.elastic.co/docs/solutions/search/search-templates">검색 템플릿을</a> 사용하면 일반적인 검색 패턴에 대해 미리 정의된 구조를 정의하여 데이터 검색의 효율성과 일관성을 개선할 수 있습니다. 이는 상용구 코드를 표준화하고 검색 로직의 빠른 반복을 가능하게 하므로 검색 API와 상호 작용하는 에이전트 도구에 특히 유용합니다. 이러한 요소 중 하나를 조정해야 하는 경우 검색 템플릿을 업데이트하기만 하면 변경 사항이 바로 적용됩니다. 에이전트 도구에서 작동하는 검색 템플릿의 예를 찾고 계신다면, 외부 MCP 서버에서 도구 호출 뒤에 검색 템플릿을 활용하는 Elasticsearch Labs 블로그 '<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">지능형 검색을 위한 MCP</a>'를 살펴보시기 바랍니다.</p><h3>통합 워크플로(FTW!)</h3><p>새로운 에이전트 AI 세계에서 가장 어려운 점 중 하나는 반자율적이고 자기 주도적인 '추론' 에이전트의 비결정적 특성입니다. 컨텍스트 엔지니어링은 에이전트 AI의 중요한 분야로, 에이전트가 생성할 수 있는 결론의 범위를 우리가 알고 있는 사실에 근거하여 좁히는 데 도움이 되는 기술입니다. 매우 정확하고 관련성이 높은 컨텍스트 창이 있더라도 (수치적 사실의 영역을 벗어나면) 상담원의 응답이 완전히 반복 가능하고 신뢰할 수 있다는 확신을 줄 수 있는 부분이 여전히 부족합니다.</p><p>상담원에게 동일한 요청을 여러 번 실행하면 응답에 약간의 차이가 <em>있을 뿐</em> <em>본질적으로</em> 동일한 답변이 나올 수 있습니다. 이는 보통 눈에 띄지 않을 정도로 단순한 쿼리의 경우 괜찮으며 컨텍스트 엔지니어링 기법을 사용하여 결과물을 구체화할 수 있습니다. 하지만 상담원에게 요청하는 작업이 복잡해짐에 따라 하나 이상의 하위 작업으로 인해 최종 결과가 약간 달라질 수 있는 변수가 발생할 가능성이 커지고 있습니다. 상담원 간 커뮤니케이션에 더 많이 의존하기 시작하면 이러한 차이는 더욱 심해질 것이며, 이러한 차이는 누적될 것입니다. 이는 상담원이 상호작용하는 툴이 컨텍스트 데이터를 정확하게 타겟팅할 수 있도록 매우 유연하고 조정이 가능해야 하며, 예상 출력 형식으로 응답해야 한다는 점을 다시 한 번 강조합니다. 또한 많은 사용 사례에서 에이전트와 툴의 상호 작용을 지시해야 할 필요가 있음을 나타내며, 바로 여기에서 워크플로우가 등장합니다!</p><p>Elastic은 곧 플랫폼의 핵심에 완전히 사용자 정의 가능한 워크플로우를 내장할 예정입니다. 이러한 워크플로는 상담원 및 툴과 양방향으로 작동할 수 있으므로 워크플로는 상담원 및 툴을 호출할 수 있고, 상담원 및 툴은 워크플로를 호출할 수 있게 됩니다. 이러한 기능이 모든 데이터가 있는 동일한 검색 AI 플랫폼에 완전히 통합되어 워크플로우를 혁신적으로 변화시킬 수 있는 잠재력은 매우 흥미롭습니다! 곧 출시됩니다!</p><h3>통합 메모리 뱅크로서의 Elastic</h3><p>실시간에 가까운 검색을 위해 만들어진 분산 데이터 플랫폼이기 때문에, Elastic은 에이전트 AI 시스템을 위한 장기 메모리 기능을 자연스럽게 수행합니다. 기본 제공되는 상담원 빌더 채팅 환경을 통해 단기 기억 및 채팅 기록을 추적하고 관리할 수도 있습니다. 그리고 전체 플랫폼이 API 우선이기 때문에, 에이전트의 컨텍스트 창을 압도할 수 있는 도구의 컨텍스트 출력을 유지(그리고 나중에 참조할 수 있도록)하기 위한 플랫폼으로 Elastic을 매우 쉽게 활용할 수 있습니다. 이 기술은 컨텍스트 엔지니어링 업계에서 "<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">메모 작성</a>"이라고도 불립니다.</p><p>동일한 검색 플랫폼에서 단기 메모리와 장기 메모리를 모두 사용하면 많은 본질적인 이점을 얻을 수 있습니다. 채팅 기록과 지속적 문맥 반응을 향후 채팅 상호작용에 시맨틱 영향력의 일부로 사용하거나 위협 분석을 수행하거나 자주 반복되는 도구 호출에서 자동으로 생성되는 지속적 데이터 제품을 만들 수 있다고 상상해 보세요... 가능성은 무궁무진합니다!</p><h2>결론</h2><p>대규모 언어 모델의 등장으로 콘텐츠를 매칭하는 방식과 데이터를 조사하는 방법이 바뀌었습니다. 사람이 직접 조사하고, 맥락을 고려하고, 논리적 추론을 통해 질문에 답하는 현재의 세상에서 에이전트 AI를 통해 이러한 단계가 대부분 자동화되는 세상으로 빠르게 전환되고 있습니다. 생성된 답변을 신뢰할 수 있으려면 상담원이 답변을  생성할 때 <em>가장 관련성이 높은 모든</em> 정보(주관적 관련성 요소 포함)를 고려했다는 확신이 있어야 합니다. 에이전트 AI를 신뢰할 수 있게 만드는 기본 방법은 RAG 및 컨텍스트 엔지니어링 기술을 통해 추가 컨텍스트를 검색하는 도구를 기반으로 하는 것이지만, 이러한 도구가 <em>초기 검색을</em> 수행하는 방식은 응답의 정확성에 매우 중요할 수 있습니다.</p><p>Elastic Search AI 플랫폼은 정확성, 성능, 확장성 측면에서 에이전트 AI를 지원하는 여러 기본 제공 기능과 함께 하이브리드 검색의 유연성과 이점을 제공합니다. 즉, Elastic은 컨텍스트 엔지니어링의 여러 측면을 위한 환상적인 플랫폼입니다! 검색 플랫폼을 통해 컨텍스트 검색을 표준화함으로써 여러 측면에서 에이전트 도구 운영을 간소화하며, '느려야 빨리 간다'는 모순처럼 컨텍스트 생성 계층에서의 간소화는 더 빠르고 더 신뢰할 수 있는 에이전트 AI를 의미합니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a203a316f0e22e/6a170932b339d58ebc769f5f/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[맥락을 위한 검색 - 1부: 하이브리드 검색과 맥락 엔지니어링의 진화]]></title>
    <description><![CDATA[하이브리드 검색과 컨텍스트 엔지니어링이 어휘 기반에서 어떻게 진화하여 차세대 에이전트 AI 워크플로우를 지원하는지 살펴보세요.]]></description>
    <content:encoded><![CDATA[<h2>새로운 에이전트 AI 세상</h2><p>다른 많은 사람들과 마찬가지로 저도 AI 기능이 발전하는 속도에 아찔함과 놀라움을 동시에 느낍니다. 대규모 언어 모델(LLM)과 벡터 검색을 통해 우리는 더 이상 키워드를 찾아 헤매지 않아도 되는 시맨틱 혁명을 맞이하게 되었습니다. 그런 다음 LLM은 채팅 인터페이스를 사용하여 자연어 요청을 방대한 지식 기반을 쉽게 사용할 수 있는 요약으로 변환하는 응답으로 변환하는 새로운 데이터 상호 작용 방법을 보여주었습니다. 우리는 지금 (이미!) 수신 요청을 의미론적으로 이해하고, 수행해야 할 단계를 추론한 다음, 해당 목표를 달성하기 위해 반복적으로 작업을 실행할 수 있는 도구를 선택할 수 있는 '에이전트 AI' 워크플로우의 형태로 자동화된 LLM 기반 로직의 시작을 알 수 있습니다.</p><p>에이전트 AI의 잠재력으로 인해 우리는 주로 '프롬프트 엔지니어링'을 사용하여 생성형 AI 상호작용을 형성하는 것에서 벗어나 에이전트 도구가 응답을 생성할 때 고려해야 하는 가장 관련성이 높고 효율적인 추가 정보를 얻을 수 있도록 돕는 방법, 즉 '맥락 엔지니어링'이 다음 개척 분야로 진화해야 합니다. 하이브리드 검색은 관련 컨텍스트를 표시하는 가장 강력하고 유연한 수단이며, Elastic의 검색 AI 플랫폼은 서비스 중인 데이터를 컨텍스트 엔지니어링에 활용할 수 있는 완전히 새로운 방법을 열어줍니다. 이 글에서는 LLM이 정보 검색의 세계를 어떻게 변화시켰는지 두 가지 각도에서 살펴본 다음, 더 나은 결과를 위해 어떻게 협력할 수 있는지에 대해 논의해 보겠습니다. 다뤄야 할 내용이 꽤 많습니다...</p><h2>1부: LLM이 검색을 바꾼 방법</h2><p>LLM이 정보에 액세스하고 검색하는 방식을 어떻게 변화시켰는지에 대한 관점에서 시작하겠습니다.</p><h3>어휘 유산</h3><p>우리는 모두 오랫동안 다소 제한적인 어휘 검색의 세계에서 (최선을 다해) 살아왔습니다. 검색은 새로운 프로젝트를 조사하거나 시작할 때마다 가장 먼저 찾는 도구로, 최근까지만 해도 어휘 검색 엔진이 이해할 수 있는 방식으로 쿼리를 표현하는 것은 전적으로 사용자의 몫이었습니다. 어휘 검색은 콘텐츠가 비정형인지 정형인지에 관계없이 문서 말뭉치에서 찾은 키워드에 어떤 형태의 쿼리 용어를 일치시키는 데 의존합니다. 어휘 검색이 문서를 히트로 반환하려면 해당 키워드와 일치하거나 동의어 목록이나 사전과 같이 개념적 연결을 위해 제어된 어휘가 있어야 합니다.</p>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}<p><em>어휘 </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query"><em>다중 일치</em></a><em> 쿼리</em>예제</p><p>적어도 검색 엔진은 관련성 점수가 있는 히트를 반환하는 기능이 있습니다. 검색 엔진은 색인된 데이터를 효과적으로 타겟팅할 수 있는 다양한 쿼리 구문 옵션과 사용자의 쿼리 구문 의도에 따라 결과를 점수화하는 기본 제공 관련성 알고리즘을 제공합니다. 검색 엔진은 수십 년간 발전해 온 관련성 순위 알고리즘의 이점을 활용하여 검색어와의 관련성에 따라 점수를 매기고 정렬된 결과를 제공할 수 있는 효율적인 데이터 검색 플랫폼이 되었습니다. SQL을 데이터 검색의 주요 방법으로 사용하는 데이터베이스 및 기타 시스템은 여기서 불리한 점이 있습니다. 데이터베이스 쿼리에는 관련성 개념이 없기 때문에 결과를 알파벳순 또는 숫자순으로 정렬하는 것이 최선입니다. 좋은 소식은 이러한 키워드로 모든 히트(리콜)를 얻을 수 있지만, 검색한 <em>이유</em> (정확도)에 비해 반드시 유용한 순서로 검색되는 것은 아니라는 점입니다. 이는 곧 살펴보겠지만 중요한 포인트입니다...</p><h3>(시맨틱) 용을 입력합니다.</h3><p>키워드 검색의 대안으로 정보를 벡터로 표현할 수 있는 가능성은 <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">꽤 오래전부터</a> 연구되어 왔습니다. 벡터는 용어와 가중치를 숫자로 표현하기 때문에 학습 도메인에서 용어가 서로 어떻게 연관되는지에 대한 언어 모델의 이해를 바탕으로 개념을 수학적으로 가깝게 만들 수 있기 때문에 키워드만 사용하는 콘텐츠 매칭 모드에서 벗어날 수 있다는 점에서 많은 가능성을 가지고 있습니다. 범용 벡터 검색이 오래 지연된 것은 모델이 대부분 특정 도메인에 국한되어 있고, 용어가 다양한 맥락에서 나타낼 수 있는 다양한 개념을 충분히 이해하기에 충분히 크지 않았기 때문이었습니다.</p><p>벡터 검색이 실용화되기 시작한 것은 몇 년 전, 훨씬 더 많은 양의 데이터를 학습할 수 있는 대규모 언어 모델(LLM)이 등장하면서( <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">트랜스포머와</a> <a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">주의력을</a> 사용해) LLM의 크기와 깊이 덕분에 벡터가 의미론적 의미를 실제로 포착할 수 있는 충분한 뉘앙스를 저장할 수 있게 되었을 때였습니다. 이해의 깊이가 갑자기 증가함에 따라 LLM은 이전에는 잠겨 있던 수많은 자연어 처리(NLP) 기능을 제공할 수 있게 되었으며, 가장 영향력 있는 기능은 아마도 지금까지의 시퀀스 내용을 바탕으로 시퀀스에서 가장 가능성이 높은 다음 용어를 추론하는 기능일 것입니다. 추론은 제너레이티브 AI에 인간에 가까운 텍스트 생성 능력을 부여하는 과정입니다. AI가 생성한 텍스트는 학습 데이터 내에서 용어가 어떻게 연관되어 있는지에 대한 LLM의 이해를 기반으로 하며, 요청의 문구를 사용하여 용어가 나타날 수 있는 다양한 문맥을 명확히 구분합니다.</p><p>생성형 AI는 마법과도 같지만, 품질과 정확성에서 오류를 일으키는 LLM에는 흔히 환각이라고 불리는 한계가 <em>있습니다</em>. 환각은 LLM이 사실에 근거한 답변을 할 수 있는 정보에 접근할 수 없거나 올바른 맥락으로 안내되지 않을 때 발생하므로, 도움이 되는 대신 자신감 있고 그럴듯하게 들리는 답변을 지어내게 됩니다. 그 원인 중 하나는 LLM이 다양한 정보의 넓은 도메인 내에서 언어 사용법을 학습하지만, 특정 시점에 학습을 중단해야 하므로 이해에 적시성 요소가 있어 모델이 학습을 중단한 시점까지만 정확한 정보를 알 수 있다는 점입니다. 환각의 또 다른 요인은 모델이 일반적으로 비공개 데이터(공개 인터넷에서 사용할 수 없는 데이터)에 대해 알지 못한다는 점이며, 이러한 데이터에 특정 용어와 명명법이 포함되어 있는 경우 특히 중요합니다.</p><h3>벡터 데이터베이스</h3><p>LLM은 텍스트 임베딩이라는 기술을 사용하여 콘텐츠를 모델 공간에 벡터화하는데, 이는 학습을 기반으로 모델의 세계관 내에 콘텐츠의 의미적 의미를 <a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">임베딩하거나</a> 매핑하는 것을 말합니다. 임베드할 콘텐츠를 준비하고 처리하는 데에는 <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">청킹과</a> 토큰화(및 <a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">하위 단어 토큰화</a>) 등 몇 가지 단계가 있습니다. 그 결과 일반적으로 벡터 공간 내에서 해당 콘텐츠의 의미에 대한 모델의 이해를 나타내는 고밀도 벡터 집합이 생성됩니다. 청킹은 임베딩을 생성하기 위한 모델의 처리 제약 조건에 콘텐츠를 맞추는 동시에 문장 및 단락 표시기와 같은 의미적 구성을 사용하여 관련 텍스트를 청크로 그룹화하기 위한 정확하지 않은 프로세스입니다.</p><p>청킹이 필요하면 개별 청크가 같은 문서의 다른 청크와 완전히 연결되지 않기 때문에 임베디드 문서에서 약간의 의미 손실이 발생할 수 있습니다. 신경망의 고유한 불투명성은 이러한 손실을 더욱 악화시킬 수 있습니다. LLM은 학습 중에 만들어진 용어와 개념 간의 연결이 비결정적이며 인간이 해석할 수 없는 진정한 '블랙박스'입니다. 이는 설명 가능성, 반복성, 무의식적 편견, 잠재적으로 신뢰와 정확성 상실 등의 문제로 이어집니다. 하지만 쿼리할 때 특정 키워드에 얽매이지 않고 아이디어를 의미적으로 연결할 수 있는 기능은 매우 강력합니다:</p>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} <p><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query"><em>시맨틱</em></a><em> 쿼리 예시</em></p><p>벡터 데이터베이스는 검색 엔진이 아니라 데이터베이스라는 점에서 고려해야 할 문제가 하나 더 있습니다! <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">벡터 유사성 검색이</a> 수행되면 쿼리 용어가 인코딩되어 모델의 벡터 공간 내에서 일련의 (임베딩) 좌표 집합을 찾습니다. 그런 다음 이러한 좌표를 과녁으로 사용하여 과녁에 '가장 가까운 이웃'인 문서를 찾습니다. 즉, 문서의 순위(또는 결과 내 배치)는 쿼리 좌표에서 해당 문서 좌표의 계산된 유사성 <em>거리에</em> 따라 결정됩니다. 어떤 방향으로 랭킹을 우선시해야 하며, 가능한 컨텍스트 중 사용자의 의도에 가장 가까운 컨텍스트는 무엇인가요? 제가 비유한 이미지는 영화 <a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">스타게이트의</a> 한 장면으로, 교차하는 6개의 좌표점이 목적지(과녁)를 알려주지만 사용자의 주관적인 의도를 나타내는 출발점의 좌표인 '7번째 기호'를 모르면 목적지에 도달할 수 없는 상황입니다. 따라서 벡터의 상대적 순위가 계속 확장되고 차별화되지 않은 유사성 영역에 기반하는 대신, 표현 구문과 관련성 점수를 통해 쿼리의 주관적 의도를 고려하면 눈금이 매겨진 주관적 관련성의 <em>원통형과</em> 유사한 결과를 얻을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb49ae330d3de1fc1/6a17053ee8fbce089639fb46/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" alt="주관적 관련성이 점수로 표시된 원통입니다." /><p>LLM의 추론 기능은 쿼리에 대해 가장 가능성이 높은 컨텍스트를 <em>식별하는</em> 데 도움이 될 수 있지만, 문제는 <em>도움이 없으면</em> 수신 쿼리의 좌표는 모델이 원래 학습된 방식에 <em>의해서만</em> 결정될 수 있다는 점입니다.</p><p>어떤 면에서 벡터 유사도는 엄격한 키워드 검색과는 정반대의 극단이라고 할 수 있는데, 용어 불일치 문제를 극복할 수 있다는 것이 강점이지만 <a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">거의 결함에</a> 가깝다고 할 수 있습니다: LLM은 관련 개념을 구분하기보다는 통합하는 경향이 있습니다. 벡터 유사도는 콘텐츠를 의미론적으로 일치시키는 능력을 향상시키지만, 모델에서 충분히 명확하지 않은 정확한 키워드와 특정 세부 사항을 간과할 수 있기 때문에 정확성을 보장하지는 않습니다. 벡터 유사도 검색은 그 자체로도 강력하지만, 벡터 데이터베이스에서 검색한 결과를 다른 검색 방법의 결과와 연관시킬 수 있는 방법이 필요합니다.</p><h3>순위 재조정 기술</h3><p>이제 결과 집합의 점수를 다시 매기거나 통합된 순위 순서로 정규화하는 리랭킹이라는 일반적인 기법을 언급할 때입니다. 재랭크가 필요한 이유는 여러 소스의 결과 또는 순위/채점 메커니즘이 다른 검색 방법(또는 전혀 없는 SQL!) 때문일 수도 있고, 의미론적이지 않은 소스의 결과를 사용자의 쿼리에 의미론적으로 맞추기 위해 재랭크가 사용될 수도 있습니다. 재랭크는 2단계 작업으로, 어떤 <em>초기 검색</em> 방법(예를 들어 SQL, 어휘 검색, 벡터 검색)의 순서를 다른 채점 방법으로 다시 지정합니다.</p><p><a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">학습을 통한 순위 지정(LTR)</a> 및 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">상호 순위 융합(RRF</a> ) 등 여러 가지 접근 방식을 사용할 수 있습니다. LTR은 검색 결과 기능(좋아요, 평점, 클릭 등)을 캡처하고 이를 사용하여 결과를 점수화하고 부스트 또는 편향시키는 데 유용합니다. RRF는 다양한 쿼리 양식에서 반환된 결과를 병합하는 데 적합합니다(예 어휘 및 벡터 데이터베이스 검색)을 하나의 결과 목록으로 통합합니다. Elastic은 또한 <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">선형 재순위화</a> 방법을 사용하여 점수를 조정할 수 있는 유연성도 제공합니다.</p><p>그러나 가장 효과적인 재순위 조정 기법 중 하나는 <a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">시맨틱 재순위</a> 조정으로, LLM의 시맨틱 이해를 사용하여 쿼리와 결과의 벡터 임베딩을 함께 분석한 다음 관련성 점수/채점을 적용하여 최종 순위를 결정합니다. 물론 시맨틱 재랭크에는 재랭크 모델에 대한 연결이 필요하며, Elasticsearch는 기본 제공 모델(Elastic<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank"></a> Rerank), <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning">가져온 타사</a> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere">모델 또는 Cohere나</a> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai">Google Vertex AI</a> 같은 외부 호스팅 서비스를 활용하는 <strong>재랭크</strong> 엔드포인트를 생성할 수 있는 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">추론 API를</a> 제공합니다. 그런 다음 <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">검색</a> 쿼리 추상화 구문을 통해 다시 순위를 매길 수 있습니다:</p>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}<p><em>다단계 리트리버 순위 재조정 작업 예시</em></p><p>멋지지 않나요? 서로 다른 소스의 결과에 대해 재랭킹을 수행하여 모든 유형의 콘텐츠에 대한 의미론적 이해에 근접할 수 있습니다... 의미론적 재랭킹은 처리 시간뿐만 아니라 계산 비용이 많이 들 수 있으므로 제한된 수의 결과에 대해서만 실현 가능하게 수행할 수 있으므로 초기 결과를 검색하는 <em>방법이</em> 중요합니다.</p><h3>컨텍스트 검색 방법의 중요성</h3><p>주관적 의도는 결과의 정확성을 결정하고 관련성을 점수화할 때 중요한 요소입니다. 쿼리 수행에 대한 사용자의 의도를 고려할 수 있는 기능(유연한 구문 또는 2단계 재랭킹을 통해 표현됨)이 없으면 모델 공간 내에 이미 인코딩된 기존 컨텍스트 중에서 선택할 수 밖에 없습니다. 일반적으로 이러한 컨텍스트 부족 문제를 해결하는 방법은 <a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">검색 증강 생성(RAG)</a>과 같은 기술을 사용하는 것입니다. RAG가 작동하는 방식은 상황에 맞는 데이터에 대한 사전 쿼리에서 반환된 추가 관련 용어를 포함하여 쿼리의 좌표를 효과적으로 이동하는 것입니다. 따라서 추가 컨텍스트를 제공하는 엔진과 <em>검색을</em> 수행하는 초기 방법이 컨텍스트의 정확성에 더욱 중요해집니다!</p><p>다양한 컨텍스트 검색 방법과 이러한 방법이 RAG 작업에 어떤 도움이 되거나 해가 되는지 살펴보겠습니다:</p><ul><li><p><strong>검색 엔진이 없는 하이브리드 검색 검색은 여전히 주관적인 연관성이 부족합니다.</strong> RAG를 제공하는 플랫폼이 주로 SQL 기반인 경우(대부분의 '데이터 레이크' 플랫폼 포함), 초기 검색 단계에서 정확도 점수가 부족합니다. 많은 데이터 레이크 플랫폼이 자체 버전의 하이브리드 검색(검색이 아닌)을 제공하며, 일반적으로 SQL 기반 검색과 벡터 데이터베이스 결과에 시맨틱 리랭크 및 RRF와 같은 리랭크 기법을 결합합니다. 단순 정렬은 주관적 순위를 매기기에는 분명히 불충분하지만, 2단계 시맨틱 재랭크 작업의 기초로 사용하더라도 1단계 검색으로서의 SQL은 검색 시 결과를 점수화하는 방법 없이 '상위 k' 히트에 대해서만 시맨틱 재랭크가 수행될 때 문제가 됩니다 - 실제로 <em>최상의</em> 결과가 상위 결과라고 보장할 수 있는 방법은 무엇일까요?</p></li><li><p><strong>벡터 유사성만으로는 RAG에 충분하지</strong> 않습니다. 이는 임베딩의 손실, 순진한 청킹 방법, 유사성 계산 방식, 주관적 의도라는 중요한 요소가 누락된 문제 등 복합적인 문제 때문이었습니다. RAG의 주요 목표 중 하나는 생성 AI의 상호작용을 객관적인 진실에 근거하여 환각을 방지하고 학습 중에 알지 못했던 개인 정보를 LLM에 알려주는 것입니다. RAG를 통해 제공되는 추가 컨텍스트를 사용하여 당면한 질문에 답하는 데 가장 중요한 연결과 세부 사항을 고려하도록 LLM을 제한하고 지시할 수 있습니다. 이를 위해서는 의미론적 접근 방식과 어휘적 접근 방식을 <em>모두</em> 사용해야 합니다.</p></li><li><p><strong>파일 기반 grep/레거시 RAG.</strong> 에이전트 AI <a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">세계에서는</a> 외부 검색 플랫폼이 아닌 RAG용 grep 및 정규식을 통해 로컬 파일에 액세스하는 크게 확대된 컨텍스트 창을 사용하는 것을 지적하는 의견도 있습니다. 훨씬 더 큰 컨텍스트 창을 사용할 수 있게 되면 LLM은 관련 정보를 수집하기 위해 단편적인 정보와 여러 검색 방법/플랫폼에 의존하지 않고 자신의 사고 공간 내에서 개념을 연결할 수 있게 될 것입니다. 이론적으로는 전체 문서가 문서 세그먼트보다 더 완전한 그림을 제공하지만, 이는 소규모 데이터 도메인(예: <a href="https://en.wikipedia.org/wiki/Vibe_coding">바이브코딩을</a> 위해 파일을 제공할 때)에서만 작동할 수 있으며, 그 경우에도 초기 검색 방식은 키워드만 일치하는 모든 문서를 스캔하는 것입니다.</p></li></ul><p><strong>검색은 검색 그 이상입니다</strong></p><p>검색 엔진은 가능한 한 빠르고 유연하게 쿼리를 수행하도록 특별히 설계되었습니다. 내부적으로는 다양한 종류의 데이터를 해당 데이터 유형에 맞는 방식으로 저장하고 검색하기 위해 특수 데이터 구조를 활용합니다. Elasticsearch는 비정형/전체 텍스트 어휘 검색(일치, 구문, 근접, 다중 일치), 빠른 키워드(정확히 일치) 검색 및 필터링, 숫자 범위, 날짜, IP 주소 등 거의 모든 유형의 데이터에 대해 최적화된 저장과 쿼리를 제공하며, 문서 구조를 저장하는 방식이 매우 유연합니다(예. 중첩되거나 평평해진 문서). Elasticsearch는 또한 희소 벡터 유형과 고밀도 벡터 유형을 모두 저장하고 쿼리할 수 있는 기본 벡터 데이터베이스이며, 검색 충실도를 유지하면서 벡터화된 콘텐츠와 관련된 속도, 확장성 및 비용을 개선하는 혁신적인 방법(예: <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization(BBQ)</a> &amp; <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>)을 계속 모색하고 있습니다. 또한 Elasticsearch 플랫폼은 기본으로 데이터 복원력과 고가용성을 제공하며, <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">검색 가능한 스냅샷과</a> 같은 데이터 수명 주기 관리 기능을 통해 자주 액세스하지 않거나 장기 보존 데이터를 비용 효율적인 개체 스토리지에 보관하면서도 여전히 완벽하게 검색할 수 있습니다.</p><h3>하이브리드 검색은 모든 면에서 최고입니다.</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">하이브리드 검색</a> (하이브리드 검색뿐만 아니라!) 는 기존 어휘 검색의 강점과 LLM의 의미론적 이해 및 벡터 유사도 검색을 결합합니다. 이러한 시너지를 통해 검색 엔진이 제공하는 유연한 쿼리 구문 옵션, 의도 중심 구문 옵션 및 관련성 점수, 멀티모달 데이터 검색, 필터링, 집계, 편향성 등 검색 엔진이 제공하는 모든 기능을 통해 <em>검색</em> 단계에서 관련성이 높은 결과를 타겟팅할 수 있습니다. <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> 및 다단계 <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">검색기와</a> 같은 검색 구문을 사용하면 기존 검색과 시맨틱 검색, 필터, 여러 순위 재조정 기술을 하나의 요청에 유연하게 결합할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ee9512910856ee3/6a17053fcdacbf2ea97d291e/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" alt="하이브리드 검색이 작동하는 방식" /><p>하이브리드 검색의 가장 큰 장점 중 하나는 쿼리가 여러 가지 데이터 유형에 대해 동시에 특수 구문을 사용할 수 있다는 점입니다. 이러한 다양한 쿼리 구문은 결과를 <em>찾는</em> 데만 사용할 수 있을 뿐만 아니라 결과에 <em>대한</em> 필터나 집계로도 사용할 수 있습니다. 예를 들어, 다른 구문과 자주 결합되는 가장 일반적인 쿼리 유형 중 하나는 <a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">지리공간 분석입니다</a>. 특정 지점에서 지정된 거리 내의 지리적 좌표가 있는 결과를 쿼리하거나, 지역별 결과 집계 또는 구역 내/외의 이동을 추적하고 경고하기 위한 집계를 요청하는 등의 작업을 수행할 수 있습니다. 하이브리드 검색을 사용하면 구문을 유연하게 조합하여 가장 정확한 방식으로 결과를 타겟팅하고 컨텍스트에 가장 가까운 콘텐츠를 검색할 수 있습니다.</p><h2>인터미션</h2><p>이 첫 번째 파트에서는 벡터 검색이 데이터를 검색하는 방식을 어떻게 변화시켰는지 이야기하고, 데이터와 상호 작용하는 데 사용하는 쿼리 메커니즘에 LLM이 가져온 변화의 무대를 마련합니다. LLM이 맥락을 잃지 않고 이해할 수 있도록 여러 부분으로 나눠서 설명해야 한다고 가정해 보겠습니다... ;-) <a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">2부: 에이전트 AI와 컨텍스트 엔지니어링의 필요성에서</a> <em>이것이 중요한 이유에</em> 대해 자세히 알아보고, 3부에서는 하이브리드 검색에 대한 논의로 돌아가겠습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[정확도]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49e39872984c8eb/6a1705410e2e49e42c419fd5/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch와 SigLIP-2로 산봉우리에 대한 멀티모달 검색 ]]></title>
    <description><![CDATA[SigLIP-2 임베딩과 Elasticsearch kNN 벡터 검색을 사용해 텍스트 대 이미지 및 이미지 대 이미지 다중 모드 검색을 구현하는 방법을 알아보세요. 프로젝트 초점: 에베레스트 트레킹에서 아마다블람 산 정상 사진 찾기.]]></description>
    <content:encoded><![CDATA[<p>사진 앨범을 의미별로 검색하고 싶었던 적이 있나요? "파란색 재킷을 입고 벤치에 앉아 있는 내 사진 보여줘", "에베레스트산 사진 보여줘", "사케와 초밥" 등의 검색어를 사용해 보세요. 커피 한 잔(또는 좋아하는 음료)을 들고 계속 읽으세요. 이 블로그에서는 멀티모달 하이브리드 검색 애플리케이션을 구축하는 방법을 설명합니다. 멀티모달이란 앱이 단어뿐만 아니라 텍스트, 이미지, 오디오 등 다양한 종류의 입력을 이해하고 검색할 수 있다는 뜻입니다. 하이브리드란 키워드 매칭, kNN 벡터 검색, 지오펜싱과 같은 기술을 결합하여 더 선명한 결과를 제공하는 것을 의미합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="에베레스트 산 등반에서 찍은 다양한 산봉우리 사진 라이브러리." /><p>이를 위해 Google의 SigLIP-2를 사용해 이미지와 텍스트 모두에 대한 벡터 임베딩을 생성하고 이를 Elasticsearch 벡터 데이터베이스에 저장합니다. 쿼리 시 텍스트 또는 이미지와 같은 검색 입력을 임베딩으로 변환하고 빠른 kNN 벡터 검색을 실행하여 결과를 검색합니다. 이 설정을 통해 텍스트 대 이미지 및 이미지 대 이미지 검색을 효율적으로 수행할 수 있습니다. 스트림릿 UI는 텍스트 기반 검색을 통해 앨범에서 일치하는 사진을 찾아서 볼 수 있을 뿐만 아니라 업로드된 이미지에서 산봉우리를 식별하고 사진 앨범에서 해당 산의 다른 사진을 볼 수 있는 프론트엔드를 제공함으로써 이 프로젝트에 활기를 불어넣었습니다.
또한 검색 정확도를 개선하기 위해 취한 조치와 실용적인 팁과 요령에 대해서도 설명합니다. 더 자세히 살펴볼 수 있도록 <a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub 리포지토리와</a> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab 노트북을</a> 제공합니다.</p><h2>시작 방법</h2><p>이 블로그 게시물은 에베레스트 베이스캠프 트레킹에서 찍은 아마 다블람 산의 모든 사진을 보여 달라는 10살짜리 아이의 요청에 영감을 받아 작성했습니다. 사진첩을 훑어보면서 이름을 알 수 없는 다른 산봉우리도 몇 개 더 찾아달라는 요청을 받았습니다.</p><p>이를 통해 재미있는 컴퓨터 비전 프로젝트가 될 수 있겠다는 생각이 들었습니다. 우리가 달성하고자 했던 목표:</p><ul><li><p>이름으로 산봉우리 사진 찾기</p></li><li><p>이미지에서 산봉우리 이름을 맞추고 사진 앨범에서 비슷한 봉우리를 찾습니다.</p></li><li><p>컨셉 쿼리가 작동하도록 하기<em>(사람</em>, <em>강</em>, <em>기도 깃발</em> <em>등)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="아마 다블람 산 " /><h2>드림팀 구성: SigLIP-2, Elasticsearch &amp; Streamlit</h2><p>이 작업을 수행하려면 텍스트('Ama Dablam')와 이미지(내 앨범의 사진)를 모두 의미 있게 비교할 수 있는 벡터, 즉 동일한 벡터 공간으로 변환해야 한다는 것이 금방 분명해졌습니다. 이렇게 하면 검색은 "가장 가까운 이웃을 찾는 것"에 불과합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch &amp; Streamlit- 드림팀." /><p>이미지 임베딩을 생성하기 위해 다국어<a href="https://huggingface.co/blog/vlms-2025"> 비전 언어 인코더를</a> 사용하므로 산 사진과 "Ama Dablam"과 같은 문구가 동일한 벡터 공간에 배치됩니다.</p><p>최근 Google에서 출시한 <a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2가</strong></a> 여기에 잘 맞습니다. 작업별 교육( <strong>제로 샷</strong> 설정) 없이 임베딩을 생성할 수 있으며, 라벨이 없는 사진과 이름과 언어가 다른 봉우리라는 사용 사례에 적합하게 작동합니다. 텍스트 ↔ 이미지 매칭을 위해 학습되었기 때문에 쿼리 언어나 철자가 다르더라도 트레킹에서 찍은 산 사진과 짧은 텍스트 프롬프트가 임베딩으로 비슷하게 표시됩니다.</p><p>SigLIP-2는 강력한 속도 대비 품질 균형을 제공하고, 다양한 입력 해상도를 지원하며, CPU와 GPU 모두에서 실행됩니다. SigLIP-2는 기존 CLIP과 같은 이전 모델에 비해 야외 촬영에 더욱 견고하게 설계되었습니다. 테스트하는 동안 SigLIP-2는 일관되게 신뢰할 수 있는 결과를 생성했습니다. 또한 지원도 매우 잘 되어 있어 이 프로젝트의 확실한 선택이 될 것입니다.</p><p>다음으로 임베딩과 파워 검색을 저장할 벡터 데이터베이스가 필요합니다. 이미지 임베딩에 대한 코사인 kNN 검색을 지원할 뿐만 아니라 단일 쿼리에서 지오펜스 및 텍스트 필터를 적용할 수 있어야 합니다. Elasticsearch는 벡터(dense_vector 필드의 HNSW kNN)를 매우 잘 처리하고 텍스트, 벡터, 위치 기반 쿼리를 결합하는 하이브리드 검색을 지원하며 필터링과 정렬 기능을 기본으로 제공합니다. 또한 수평으로 확장할 수 있어 몇 장의 사진에서 수천 장으로 쉽게 늘릴 수 있습니다. 공식 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">Elasticsearch Python 클라이언트는</a> 배관을 단순하게 유지하며 프로젝트와 깔끔하게 통합됩니다. 마지막으로 검색 쿼리를 입력하고 결과를 볼 수 있는 경량 프론트엔드가 필요합니다. 파이썬 기반의 빠른 데모를 원한다면 Streamlit이 적합합니다. 파일 업로드, 반응형 이미지 그리드, 정렬 및 지오펜싱을 위한 드롭다운 메뉴 등 우리에게 필요한 기본 요소를 제공합니다. 로컬에서 쉽게 복제하고 실행할 수 있으며 Colab 노트북에서도 작동합니다.</p><h2>구현</h2><h3>Elasticsearch 인덱싱 설계 및 인덱싱 전략</h3><p>이 프로젝트에는 <code>peaks_catalog</code> 와 <code>photos</code> 의 두 가지 인덱스를 사용할 것입니다.</p><h4>Peaks_catalog 인덱스</h4><p>이 색인은 에베레스트 베이스캠프 트레킹 중에 볼 수 있는 주요 산봉우리를 간결하게 정리한 카탈로그 역할을 합니다. 이 색인에 포함된 각 문서는 에베레스트 산과 같은 하나의 산봉우리에 해당합니다. 각 산봉우리 문서에는 이름/별칭, 위도-경도 좌표(선택 사항), SigLIP-2 텍스트 프롬프트(+ 참조 이미지 옵션)를 혼합하여 구축한 단일 프로토타입 벡터가 저장됩니다.</p><p><strong>인덱스 매핑:</strong></p><p>필드</p><p>유형</p><p>예</p><p>목적/참고 사항</p><p>벡터/인덱싱</p><p>id</p><p>키워드</p><p>아마다블람</p><p>안정적인 슬러그/ID</p><p>-</p><p>이름</p><p>텍스트 + 키워드 하위 필드</p><p>["아마다블람","아마다블람"]</p><p>별칭/다국어 이름; 정확한 필터를 위한 names.raw</p><p>-</p><p>latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>위도/경도 조합의 피크 GPS 좌표(선택 사항)</p><p>-</p><p>elev_m</p><p>정수</p><p>6812</p><p>고도(선택 사항)</p><p>-</p><p>text_embed</p><p>dense_vector</p><p>768</p><p>이 피크에 대한 혼합 프로토타입(프롬프트 및 선택적으로 1~3개의 참조 이미지)</p><p>index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}</p><p>이 색인은 주로 이미지에서 산봉우리를 식별하는 등 이미지 대 이미지 검색에 사용됩니다. 또한 이 인덱스를 사용하여 텍스트-이미지 검색 결과를 개선합니다.</p><p><code>peaks_catalog</code> 요약하면, "어떤 산입니까?" 라는 질문을 가장 가까운 이웃에 초점을 맞춘 문제로 변환하여 이미지 데이터의 복잡성에서 개념적 이해를 효과적으로 분리하는 것입니다.</p><p><strong>peaks_catalog 인덱스의 인덱싱 전략: </strong>EBC 트레킹 중 가장 눈에 띄는 봉우리 목록을 만드는 것부터 시작합니다. 각 봉우리에 대해 지리적 위치, 이름, 동의어, 고도를 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml 파일에</a> 저장합니다. 다음 단계는 각 피크에 대한 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">임베딩을 생성하여</a> <code>text_embed</code> 필드에 저장하는 것입니다. 강력한 임베딩을 생성하기 위해 다음 기술을 사용합니다:</p><ul><li><p>다음을 사용하여 텍스트 프로토타입을 만듭니다:</p><ul><li><p>봉우리 이름</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">프롬프트 앙상블</a> (여러 개의 다른 프롬프트를 사용하여 동일한 질문에 답하기) 등을 예로 들 수 있습니다:</p><ul><li><p>"네팔 히말라야의 산봉우리 {name} 의 자연 사진"</p></li><li><p>"{name} 쿰부 지역의 랜드마크 봉우리, 고산 풍경"</p></li><li><p>"{name} 산 정상, 눈, 바위 능선"</p></li></ul></li><li><p>선택적 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">안티 콘셉트</a> (SigLIP-2에 일치하지 않을 대상을 알려줌): '그림, 일러스트, 포스터, 지도, 로고'에 대해 작은 벡터를 빼서 실제 사진에 편향되도록 합니다.</p></li></ul></li><li><p>피크의 참조 이미지가 제공된 경우 선택적으로 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">이미지 프로토타입을 생성합니다</a>.</p></li></ul><p>그런 다음 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">텍스트와 이미지 프로토타입을 혼합하여</a> 최종 임베딩을 생성합니다. 마지막으로 모든 필수 필드가 포함된 문서가 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">색인됩니다</a>:</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p><code>peaks_catalog</code> 색인의 샘플 문서입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Elasticsearch의 peaks_catalog 인덱스에 있는 샘플 문서입니다." /><h4>사진 색인</h4><p>이 기본 색인은 앨범의 모든 사진에 대한 자세한 정보를 저장합니다. 각 문서는 다음 정보를 포함하는 단일 사진을 나타냅니다:</p><ul><li><p>사진 앨범에서 사진의 상대 경로입니다. 검색 UI에서 일치하는 이미지를 보거나 이미지를 로드하는 데 사용할 수 있습니다.</p></li><li><p>사진의 GPS 및 시간 정보.</p></li><li><p>SigLIP-2에서 생성된 이미지 인코딩을 위한 고밀도 벡터입니다.</p></li><li><p><code>predicted_peaks</code> 를 사용하면 피크 이름을 기준으로 필터링할 수 있습니다.

<strong>인덱스 매핑</strong></p></li></ul><p>필드</p><p>유형</p><p>예</p><p>목적/참고 사항</p><p>벡터 / 인덱싱</p><p>경로</p><p>키워드</p><p>데이터/이미지/IMG_1234.HEIC</p><p>UI에서 썸네일/전체 이미지를 여는 방법</p><p>-</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>SigLIP-2 이미지 임베딩</p><p>index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}</p><p>예측된 피크</p><p>키워드</p><p>["아마다블람","푸모리"]</p><p>인덱스 시점의 Top-K 추측(저렴한 UX 필터/패싯)</p><p>-</p><p>gps</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>지리적 필터 사용</p><p>-</p><p>shot_time</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>캡처 시간: 정렬/필터링</p><p>-</p><p><strong>사진 색인 색인 전략: </strong>앨범의 각 사진에 대해 다음을 수행합니다:
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">이미지 메타데이터에서 이미지</a> <code>shot_time</code> 및 <code>gps</code> 정보를 추출합니다.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 이미지 임베딩</a>: 이미지를 모델에 전달하고 벡터를 L2 정규화합니다. <code>clip_image</code> 필드에 임베딩을 저장합니다.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">피크를 예측하여</a> <code>predicted_peaks</code> 필드에 저장합니다. 이를 위해 먼저 이전 단계에서 생성된 사진의 이미지 벡터를 가져온 다음 <code>peaks_catalog</code> 인덱스의 text_embed 필드에 대해 빠른 kNN 검색을 실행합니다. 상위 3~4개 봉우리는 유지하고 나머지는 무시합니다.</p></li><li><p>이미지 이름과 경로에 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">해시를</a> 수행하여 <code>_id</code> 필드를 계산합니다. 이렇게 하면 여러 번 실행한 후에도 중복이 발생하지 않습니다.</p></li></ul><p>사진의 모든 필드를 결정한 후에는 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">일괄</a> 색인을 사용하여 사진 문서를 일괄 색인합니다:</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>사진 색인의 샘플 문서입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Elasticsearch의 사진 색인에서 가져온 샘플 문서입니다." /><p>요약하자면, 사진 인덱스는 앨범에 있는 모든 사진을 빠르고 필터링이 가능하며 kNN으로 저장할 수 있는 저장소입니다. 매핑은 일부러 최소화하여 빠르게 검색하고, 깔끔하게 표시하고, 공간과 시간별로 결과를 분류할 수 있는 구조로만 구성했습니다. 이 인덱스는 두 가지 검색 사용 사례를 모두 지원합니다. 두 인덱스를 생성하는 Python 스크립트는 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">여기에서</a> 찾을 수 있습니다.</p><p>아래의 Kibana 지도 시각화에서는 사진 앨범의 문서를 녹색 점으로, <code>peaks_catalog</code> 인덱스의 산봉우리를 빨간색 삼각형으로 표시하며, 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="사진 앨범의 문서를 녹색 점으로, peaks_catalog 인덱스의 산봉우리를 빨간색 삼각형으로 표시하는 Kibana 지도 시각화에서 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다." /><h2>검색 사용 사례</h2><p><strong>이름으로 검색(텍스트-이미지):</strong> 이 기능을 사용하면 텍스트 검색을 통해 산봉우리 사진(및 '기도 깃발'과 같은 추상적인 개념까지)을 찾을 수 있습니다. 이를 위해 텍스트 입력은 SigLIP-2를 사용하여 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">텍스트 벡터로 변환됩니다</a>. 강력한 텍스트 벡터 생성을 위해 인덱스에서 텍스트 임베딩을 생성할 때와 동일한 전략을 <code>peaks_catalog</code> 사용합니다. <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"></a> 즉, 텍스트 입력을 작은<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 프롬프트</a> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"></a>앙상블과 결합하고, 작은 반개념 벡터를 뺀 다음 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">L2 정규화를</a> 적용하여 최종 쿼리 벡터를 생성하는 것입니다. 그런 다음 <code>photos.clip_image</code> 필드에서 kNN <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">쿼리를</a> 실행하여 가장 가까운 이미지를 찾기 위해 코사인 유사성을 기반으로 가장 일치하는 상위 피크를 검색합니다. 선택적으로 지역 및 날짜 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">필터를</a> 적용하거나 <code>photos.predicted_peaks</code> 용어 필터를 쿼리의 일부로 적용하여 검색 결과의 연관성을 높일 수 있습니다(아래 쿼리 예시 참조). 이렇게 하면 트레킹에서 실제로 보이지 않는 유사 봉우리를 제외하는 데 도움이 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Elasticsearch에서 다중 모드, 이름별 검색(텍스트-이미지 변환)이 작동하는 방식." /><p><strong>지리적 필터를 사용한 Elasticsearch 쿼리:</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>이미지로 검색(이미지 대 이미지):</strong> 이 기능을 사용하면 사진에서 산을 식별하고 사진 앨범 내에서 같은 산의 다른 이미지를 찾을 수 있습니다. 이미지가 업로드되면 SigLIP-2 이미지 인코더가 이미지를 처리하여 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">이미지 벡터를</a> 생성합니다. 그런 다음 <code>peaks_catalog.text_embed</code> 필드에서 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 검색을</a> 수행하여 가장 일치하는 피크 이름을 식별합니다. 그 후, 일치하는 피크 이름에서 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">텍스트 벡터를 생성하고</a> 사진 인덱스에서 또 다른 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">kNN 검색을</a> 수행하여 해당 사진을 찾습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch에서 이미지별(이미지 대 이미지) 멀티모달 검색이 작동하는 방식." /><p><strong>Elasticsearch 쿼리:</strong></p><p>1단계: 일치하는 피크 이름 찾기</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>2단계: <code>photos</code> 색인에서 검색을 수행하여 일치하는 사진을 찾습니다(텍스트-이미지 검색 사용 사례에 표시된 것과 동일한 쿼리):</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>스트림라이트 UI</h2><p>모든 것을 하나로 모으기 위해 두 가지 검색 사용 사례를 모두 수행할 수 있는 간단한 Streamlit UI를 만들었습니다. 왼쪽 레일에는 스크롤 가능한 피크 목록( <code>photos.predicted_peaks</code> 에서 집계됨)이 체크박스와 미니맵/지리 필터와 함께 표시됩니다. 상단에는 <strong>이름으로 검색하기</strong> 상자와 <strong>사진 업로드에서 식별하기</strong> 버튼이 있습니다. 가운데 창에는 반응형 썸네일 그리드가 있어 kNN 점수, 예상 피크 배지, 캡처 시간을 보여줍니다. 각 이미지에는 전체 해상도 미리 보기를 위한 <strong>이미지 보기</strong> 버튼이 포함되어 있습니다.</p><p><strong>이미지를 업로드하여 검색합니다:</strong> 피크를 예측하고 사진 앨범에서 일치하는 피크를 찾습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="아마다블람 산의 봉우리를 텍스트에서 이미지로, 이미지에서 이미지로 멀티모드 검색할 수 있는 간결한 UI를 제공합니다." /><p><strong>텍스트로 검색</strong>: 텍스트에서 앨범에서 일치하는 피크 찾기</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="산봉우리 라이브러리에서 텍스트로 검색하여 에베레스트산 봉우리를 검색하는 방법을 알아보세요." /><h2>결론</h2><p><em><strong>아마 </strong></em>다블람<em> 사진만</em> <em>볼 수 있나요?</em> 를 작고 작동하는 <strong>멀티모달 검색</strong> 시스템으로 전환했습니다. 우리는 원시 트레킹 사진을 찍어 <strong>SigLIP-2 임베딩으로</strong> 변환하고, <strong>Elasticsearch를</strong> 사용해 벡터를 통한 빠른 <strong>kNN과</strong> 간단한 지리적/시간 필터를 통해 <em>의미별로</em> 적합한 이미지를 표시했습니다. 그 과정에서 저희는 혼합된 프로토타입의 작은 <code>peaks_catalog</code> 인덱스(식별용)와 이미지 벡터 및 EXIF의 확장 가능한 <code>photos</code> 인덱스(검색용) 등 두 가지 인덱스를 사용하여 문제를 분리했습니다. 실용적이고 재현 가능하며 쉽게 확장할 수 있습니다.</p><p>튜닝을 원한다면 몇 가지 설정으로 조정할 수 있습니다:</p><ul><li><p><strong>쿼리 시간 설정:</strong> <code>k</code> (반환할 이웃 수) 및 <code>num_candidates</code> (최종 점수 산출 전 검색 범위). 이러한 설정은 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">여기</a> 블로그에서 설명합니다.</p></li><li><p><strong>인덱스 시간 설정:</strong> <code>m</code> (그래프 연결성) 및 <code>ef_construction</code> (빌드 시간 정확도 대 메모리). 쿼리의 경우 <code>ef_search</code> 을 너무 높게 설정하면 일반적으로 약간의 지연 시간 절충을 통해 더 나은 리콜을 얻을 수 있습니다. 이러한 설정에 대한 자세한 내용은 <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">이 블로그를</a> 참조하세요.</p></li></ul><p>앞으로 <strong>멀티모달</strong> 및 <strong>다국어</strong> 검색을 위한 기본 모델/랭커가 곧 Elastic 생태계에 출시될 예정이므로 이미지/텍스트 검색과 하이브리드 랭킹이 더욱 강력해질 것입니다.<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> ir.elastic.co+1</a></p><p>직접 체험해보고 싶으신가요?</p><ul><li><p><strong>GitHub 리포지토리:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab 빠른 시작:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>이것으로 우리의 여정은 끝났고 이제 돌아올 시간입니다. 도움이 되었기를 바라며, 이 기능을 중단(또는 개선)하신다면 어떤 점이 달라졌는지 알려주시기 바랍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch용 에이전트 AI 도구 개선 실험]]></title>
    <description><![CDATA[확장 가능한 RAG 최적화를 위해 선형 검색기, 하이브리드 검색, semantic_text를 결합하여 반복적인 실험을 통해 Elasticsearch의 AI 에이전트 워크플로우를 개선한 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>요즘 다른 모든 사람들과 마찬가지로 Elastic도 Chat, 에이전트, RAG에 올인하고 있습니다. 검색 부서에서는 최근 에이전트 빌더와 도구 레지스트리를 개발 중이며, 모두 Elasticsearch에서 데이터와 '채팅'하는 것을 간단하게 만들기 위한 것입니다.</p><p>이러한 노력의 '큰 그림'에 <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">대해 자세히 알아보려면 Elasticsearch로 AI 에이전트 워크플로우 구축</a> <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">블로그 또는 첫 번째 Elastic 에이전트를 읽어보세요: 단일 쿼리에서 AI 기반 채팅까지에서</a> 보다 실용적인 입문서를 읽어보세요.</p><p>하지만 이 블로그에서는 채팅을 시작할 때 가장 먼저 일어나는 일 중 하나를 조금 더 자세히 살펴보고 최근 개선된 몇 가지 사항을 안내해드리려고 합니다.</p><h2>여기서 무슨 일이 일어나고 있나요?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>Elasticsearch 데이터와 채팅할 때 기본 AI 에이전트가 이 표준 플로우를 안내합니다:</p><ol><li><p>프롬프트를 확인합니다.</p></li><li><p>해당 프롬프트에 대한 답변이 포함되어 있을 가능성이 높은 인덱스를 식별합니다.</p></li><li><p>프롬프트에 따라 해당 인덱스에 대한 쿼리를 생성합니다.</p></li><li><p>해당 쿼리로 해당 인덱스를 검색합니다.</p></li><li><p>결과를 종합합니다.</p></li><li><p>결과가 프롬프트를 해결할 수 있나요? 그렇다면 응답하세요. 그렇지 않다면 반복하되 다른 것을 시도하세요.</p></li></ol><p>검색 증강 세대(RAG)에 불과하기 때문에 너무 새롭지 않을 것입니다. 예상대로 응답의 품질은 초기 검색 결과의 관련성에 따라 크게 달라집니다. 따라서 응답 품질을 개선하기 위해 노력하면서 3단계에서 생성하고 4단계에서 실행하는 쿼리에 매우 세심한 주의를 기울이고 있습니다. 그리고 흥미로운 패턴을 발견했습니다.</p><p>첫 번째 응답이 '나쁨'인 경우가 종종 있었는데, 이는 쿼리를 잘못 실행했기 때문이 아니었습니다. 쿼리할 <em>인덱스를 잘못 선택했기</em> 때문입니다. 3단계와 4단계는 보통 2단계가 문제가 되지 않았습니다.</p><h2>우리가 뭘 하고 있었나요?</h2><p>초기 구현은 간단했습니다. 저희는 <code>_cat/indices</code> 을 통해 사용 가능한 모든 인덱스를 나열한 다음, 이 인덱스 중 사용자의 메시지/질문/프롬프트에 가장 적합한 인덱스를 식별하도록 LLM에 요청하는 도구(index_explorer라고 함)를 구축했습니다. 이 <a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">원본 구현은 여기에서</a> 확인할 수 있습니다.</p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

*The natural language query is:* ${nlQuery}

*List of indices:*
${indices.map((index) =&gt; `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.<p>얼마나 잘 작동했나요? 확실하지 않았습니다! 잘 작동하지 <em>않는</em> 사례는 분명 있었지만, 현재 상태를 정량화하는 것이 첫 번째 과제였습니다.</p><h2>기준선 설정</h2><h3>데이터에서 시작됩니다.</h3><p>우리에게 필요했던 것은 사용자 프롬프트와 기존 인덱스 세트가 주어졌을 때 올바른 인덱스를 선택하는 도구의 효율성을 측정하기 위한 골든 데이터 세트였습니다. 그런 데이터 세트가 없었기 때문에 저희가 직접 생성했습니다.</p><p>인정합니다: 이것이 '모범 사례'는 아니라는 것을 알고 있습니다. 하지만 때로는 자전거를 타는 것보다 앞으로 나아가는 것이 더 나을 때도 있습니다. <a href="https://www.elastic.co/about/our-source-code#progress-perfection">진행, 심플한 완벽함</a>.</p><p><a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">이 프롬프트를</a> 사용하여 여러 다른 도메인에 대한 시드 인덱스를 생성했습니다. 그런 다음 생성된 각 도메인에 대해<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2"> 이 프롬프트를</a> 사용하여 몇 가지 인덱스를 더 생성했습니다(여기서 목표는 하드 네거티브와 분류하기 어려운 예시로 LLM에 혼란을 심어주는 것입니다). 다음으로, 생성된 각 인덱스와 그 설명을 수동으로 편집했습니다. 마지막으로 <a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">이 프롬프트를</a> 사용하여 테스트 쿼리를 생성했는데, 다음과 같은 샘플 데이터가 남았습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd9cd78154195e3/6a17f117dbb4fff7b5fb57d2/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" alt="" /><p>와 같은 테스트 사례:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadf30a0aeafd56ef/6a17f1192f4a5c160ffa89eb/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" alt="" /><h3>테스트 하네스 제작하기</h3><p>여기서부터의 과정은 매우 간단했습니다. 가능한 도구를 스크립트로 작성하세요:</p><ol><li><p>대상 Elasticsearch 클러스터로 클린 슬레이트를 설정하세요.</p></li><li><p>대상 데이터 세트에 정의된 모든 인덱스를 생성합니다.</p></li><li><p>각 테스트 시나리오에 대해 i<code>ndex_explorer</code> 도구를 실행합니다(편리하게도 <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">도구 실행 API가</a> 있습니다).</p></li><li><p>결과 인덱스와 예상 인덱스를 비교하고 결과를 캡처합니다.</p></li><li><p>모든 테스트 시나리오를 완료한 후 결과를 표로 작성합니다.</p></li></ol><h3>설문조사에 따르면...</h3><p>초기 결과는 당연히 평범했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73367741359e258d/6a17f11a505ac39749ad8c40/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" alt="" /><p>전반적으로 77.14% 올바른 인덱스를 식별하는 데 정확합니다. 그리고 이것은 모든 인덱스에 의미적으로 의미 있는 좋은 이름이 있는 '최상의 경우'의 시나리오입니다. PUT test2/_doc/foo {...}`를 해본 사람이라면 누구나 인덱스에 항상 의미 있는 이름이 있는 것은 아니라는 것을 알고 있습니다.</p><p>따라서 우리는 기준선을 가지고 있으며 개선의 여지가 많이 있음을 보여줍니다. 이제 과학이 필요한 시간입니다! 🧪</p><h2>실험</h2><h3>가설 1: 매핑이 도움이 될 것입니다.</h3><p>여기서 목표는 원래 프롬프트와 관련된 데이터를 포함할 인덱스를 식별하는 것입니다. 그리고 인덱스에 포함된 데이터를 가장 잘 설명하는 부분은 인덱스의 <em>매핑입니다</em>. 인덱스 콘텐츠의 샘플을 가져오지 않더라도 인덱스에 double 유형의 가격 필드가 있다는 것은 데이터가 판매할 상품을 나타낸다는 것을 의미합니다. 텍스트 유형의 작성자 필드는 일부 구조화되지 않은 언어 데이터를 의미합니다. 이 두 가지를 합치면 데이터가 책/이야기/시라는 것을 암시할 수 있습니다. 인덱스의 속성을 아는 것만으로도 많은 의미론적 단서를 얻을 수 있습니다. 그래서 로컬 브랜치에서 '.index_explorer`를 조정했습니다. 도구를 사용하여 인덱스의 전체 매핑(이름과 함께)을 LLM에 전송하여 결정을 내릴 수 있습니다. </p><p>결과(Kibana 로그에서 가져온):</p>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>이 도구의 초기 개발자는 이러한 문제를 예상하고 있었습니다. 인덱스의 매핑은 정보를 얻을 수 있는 금광이지만, 상당히 장황한 JSON 블록이기도 합니다. 그리고 수많은 인덱스(평가 데이터 세트는 20개를 정의함)를 비교하는 현실적인 시나리오에서는 이러한 JSON 블롭이 합쳐집니다. 따라서 모든 옵션에 대한 인덱스 이름뿐만 아니라 각 옵션의 전체 매핑이 아닌 더 많은 컨텍스트를 LLM에 제공하여 결정에 도움을 주고자 합니다.</p><h3>가설 2: 절충안으로 '플랫화' 매핑(필드 목록) 사용</h3><p>우리는 인덱스 작성자가 의미론적으로 의미 있는 인덱스 이름을 사용한다는 가정에서 시작했습니다. 이 가정을 필드 이름으로도 확장하면 어떨까요? 이전 실험은 실패했는데, 그 이유는 JSON 매핑에 복잡한 메타데이터와 상용구가 많이 포함되어 있었기 때문입니다.</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>예를 들어, 위의 블록은 236자이며 Elasticsearch 매핑에서 단 하나의 필드만 정의합니다. 반면 "description_text" 문자열은 16자에 불과합니다. 이는 문자 수가 거의 15배 증가한 것이지만, 해당 필드가 사용 가능한 데이터에 대해 의미하는 바를 설명하는 데 있어 의미 있는 의미 개선은 없습니다. 모든 인덱스에 대한 매핑을 가져오되, LLM으로 보내기 전에 필드 이름 목록으로만 '플랫화'하면 어떨까요?</p><p>저희도 사용해 보았습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>정말 멋지네요! 전반적으로 개선되었습니다. 하지만 더 잘할 수 있을까요?</p><h3>가설 3: 매핑 _meta의 설명</h3><p>추가 컨텍스트 없이 필드 이름만으로 그렇게 많은 점프가 발생했다면, 아마도 상당한 컨텍스트를 추가하는 것이 더 좋을 것입니다! 모든 인덱스에 반드시 설명을 첨부해야 하는 것은 아니지만, 매핑의 _meta 객체에 모든 종류의 인덱스 수준 메타데이터를 추가할 수 있습니다. 생성된 인덱스로 돌아가서 데이터 세트의 모든 인덱스에 대한 설명을 추가했습니다. 설명이 지나치게 길지 않다면 전체 매핑보다 적은 토큰을 사용하고 인덱스에 포함된 데이터에 대한 훨씬 더 나은 인사이트를 제공해야 합니다. 실험을 통해 이 가설을 검증했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>소폭의 개선으로 이제 &gt;90% 전반적으로 정확해졌습니다.</p><h3>가설 4: 합이 부분보다 큼</h3><p>필드 이름을 사용하면 결과가 향상되었습니다. 설명을 통해 결과가 향상되었습니다. 따라서 설명과 필드 이름을 <em>모두 </em>활용하면 더 나은 결과를 얻을 수 있겠죠?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>데이터는 "아니오"라고 응답했습니다(이전 실험에서 변경 사항 없음). 여기서 유력한 이론은 설명이 처음부터 인덱스 필드/매핑에서 생성되었기 때문에 이 두 컨텍스트 간에 서로 다른 정보가 충분하지 않아 결합할 때 '새로운' 것을 추가하는 데 도움이 되지 않는다는 것입니다. 또한 20개의 테스트 지수에 대해 전송하는 페이로드가 상당히 커지고 있습니다. 지금까지 우리가 따라온 사고방식은 확장할 수 없습니다. 사실, 지금까지의 실험 중 어떤 것도 수백, 수천 개의 인덱스가 있는 Elasticsearch 클러스터에서는 작동하지 않을 것이라고 믿을 만한 충분한 이유가 있습니다. 인덱스의 총 수가 증가함에 따라 LLM으로 전송되는 메시지 크기를 선형적으로 증가시키는 접근 방식은 일반화할 수 있는 전략이 아닐 수 있습니다.</p><p>우리에게 정말 필요한 것은 수많은 후보를 가장 관련성이 높은 옵션으로 좁히는 데 도움이 되는 접근 방식입니다....</p><p>여기에는 검색 문제가 있습니다.</p><h3>가설 5: 시맨틱 검색을 통한 선택</h3><p>인덱스 이름에 의미론적 의미가 있는 경우, 벡터로 저장하여 의미론적으로 검색할 수 있습니다.</p><p>인덱스의 필드 이름에 의미론적 의미가 있는 경우, 이를 벡터로 저장하고 의미론적으로 검색할 수 있습니다.</p><p>인덱스에 의미론적 의미가 있는 설명이 있는 경우, 이 역시 벡터로 저장하고 의미론적으로 검색할 수 있습니다.</p><p>오늘날 Elasticsearch 인덱스는 이러한 정보를 검색할 수 없지만(어쩌면 그렇게 해야 할지도 모릅니다!), 그 차이를 해결할 수 있는<a href="https://github.com/elastic/connectors/pull/3638"> 무언가를 함께 해킹하는</a> 것은 꽤나 사소한 일이었습니다. Elastic의 커넥터 프레임워크를 사용해 클러스터의 모든 인덱스에 대한 문서를 출력하는 커넥터를 구축했습니다. 출력 문서는 다음과 같은 형태가 됩니다:</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>저는 이 문서들을 수동으로 매핑을 정의한 새 인덱스로 보냈습니다:</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>이렇게 하면 의미론적 의미를 가진 다른 모든 필드가 청크업되어 색인되는 단일 semantic_content 필드가 생성됩니다. 이 인덱스를 검색하는 것은 사소한 일이 됩니다:</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>수정된 <code>index_explorer</code> 도구는 이제 LLM에 요청할 필요 없이 주어진 쿼리에 대해 단일 임베딩을 요청하고 효율적인 벡터 검색 작업을 수행할 수 있으므로 <em>훨씬</em> 더 빨라졌습니다. 상위 히트를 선택한 지표로 삼은 결과 다음과 같은 결과를 얻었습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>이 접근 방식은 확장 가능합니다. 이 접근 방식은 효율적입니다. 하지만 이 접근 방식은 기준선보다 겨우 나은 수준입니다. 하지만 이는 놀라운 일이 아닙니다. 검색 접근 방식이 매우 순진하기 때문입니다. 뉘앙스가 없습니다. 인덱스의 이름과 설명이 인덱스에 포함된 임의의 필드 이름보다 더 많은 가중치를 가져야 한다는 인식이 없습니다. 동의어 일치보다 정확한 어휘 일치에 가중치를 부여하는 어포던스는 없습니다. 그러나 고도로 미묘한 쿼리를 작성하려면 현재 데이터에 대해 많은 것을 가정해야 합니다. 지금까지 인덱스와 필드 이름에 의미론적 의미가 있다는 큰 가정을 해 보았지만, 한 걸음 더 나아가서 <em>얼마나 많은</em> 의미를 가지고 있으며 서로 어떻게 연관되어 있는지 가정해 볼 필요가 있습니다. 이렇게 하지 않으면 최상의 일치 항목을 최고의 결과로 확실하게 식별할 수는 없지만, 상위 N개의 결과 중 어딘가에 최상의 일치 항목이 있다고 말할 수 있습니다. 우리는 의미론적 정보가 존재하는 맥락에서 의미론적 정보를 소비하고, 의미론적으로 구별되는 방식으로 자신을 표현할 수 있는 다른 개체와 비교하여 그 둘을 판단할 수 있는 무언가가 필요합니다. LLM처럼요.</p><h3>가설 6: 후보 세트 감소</h3><p>이 외에도 여러 가지 실험이 있었지만, 핵심적인 돌파구는 시맨틱 검색만으로 최적의 일치 항목을 고르려는 욕구를 버리고 대신 시맨틱 검색을 필터로 활용하여 LLM의 고려 대상에서 관련 없는 인덱스를 걸러내는 것이었습니다. <a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">검색을</a> 위해 선형 검색기, 하이브리드 검색과 RRF, <code>semantic_text</code> 를 결합하여 상위 5개 일치하는 인덱스로 결과를 제한했습니다.</p><p>그런 다음 각 일치 항목에 대해 인덱스의 이름, 설명 및 필드 이름을 LLM용 메시지에 추가했습니다. 결과는 환상적이었습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>역대 실험 중 가장 높은 정확도! 또한 이 접근 방식은 총 인덱스 수에 비례하여 메시지 크기가 증가하지 않기 때문에 훨씬 더 확장성이 뛰어납니다.</p><h2>결과</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>첫 번째 분명한 결과는 우리의 기준선을 개선할 <em>수</em> 있다는 것이었습니다. 지금 생각하면 당연해 보이지만 실험을 시작하기 전에는 <code>index_explorer</code> 도구를 완전히 버리고 검색 공간을 제한하기 위해 사용자의 명시적 설정에 의존해야 하는지에 대해 진지한 논의가 있었습니다. 여전히 실행 가능하고 유효한 옵션이지만, 이 연구는 이러한 사용자 입력을 사용할 수 없는 경우 인덱스 선택을 자동화하는 방향으로 나아갈 수 있는 유망한 경로가 있음을 보여줍니다.</p><p>다음으로 분명한 결과는 문제에 더 많은 설명 문자를 던지는 것만으로는 수익이 줄어든다는 것이었습니다. 이 연구 이전에는 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">필드 수준 메타데이터를</a> 저장하기 위해 Elasticsearch의 기능을 확장하는 데 투자해야 할지 고민하고 있었습니다. 현재 <code>meta</code> 값은 50자로 제한되어 있으며, 필드의 의미적 이해를 도출하기 위해서는 이 값을 늘려야 한다는 가정이 있었습니다. 이는 분명히 사실이 아니며, LLM은 필드 이름만으로 상당히 잘 작동하는 것 같습니다. 나중에 더 조사할 수 있지만 더 이상 시급하다고 생각하지 않습니다.</p><p>반대로, 이는 '검색 가능한' 인덱스 메타데이터의 중요성에 대한 명확한 증거가 되었습니다. 이 실험을 위해 저희는 인덱스 오브 인덱스를 해킹했습니다. 그러나 이것은 Elasticsearch에 직접 구축하거나, 관리할 API를 구축하거나, 최소한 관련 규칙을 수립하는 것을 검토할 수 있는 부분입니다. 여러 옵션을 검토하고 내부적으로 논의 중이니 계속 지켜봐 주시기 바랍니다.</p><p>마지막으로, 이러한 노력을 통해 시간을 들여 실험하고 데이터 기반 의사 결정을 내리는 것이 얼마나 가치 있는 일인지 확인했습니다. 실제로 에이전트 빌더 제품에 강력한 제품 내 평가 기능이 필요하다는 것을 재확인하는 데 도움이 되었습니다. 인덱스를 선택하는 도구만을 위한 전체 테스트 하네스를 구축해야 한다면, 고객은 반복적으로 조정할 때 사용자 지정 도구를 정성적으로 평가할 수 있는 방법이 반드시 필요합니다.</p><p>앞으로 무엇을 만들게 될지 기대가 되며, 여러분도 기대가 되시길 바랍니다!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[하이브리드 검색 재조명: Elasticsearch의 리니어 리트리버를 소개합니다!]]></title>
    <description><![CDATA[리니어 리트리버가 가중치 점수와 최소 최대 정규화를 활용하여 보다 정확하고 일관된 랭킹을 위해 하이브리드 검색을 향상시키는 방법을 알아보고 그 사용법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kr/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">이전 블로그</a> 게시물에서 복잡한 랭킹 파이프라인을 생성할 수 있도록 처음부터 다시 설계된 검색어 프레임워크를 소개했습니다. 또한 상호 순위 융합(RRF) 검색기가 서로 다른 쿼리의 결과를 병합하여 하이브리드 검색을 가능하게 하는 방법도 살펴봤습니다. RRF는 구현하기 쉽지만, 실제 점수를 무시하고 순전히 상대적인 순위에만 초점을 맞춘다는 한계가 있습니다. 따라서 미세 조정과 최적화가 어렵습니다.</p><h2>리니어 리트리버를 만나보세요!</h2><p>이 게시물에서는 하이브리드 검색을 지원하는 최신 기능인 <a href="https://www.elastic.co/kr/docs/solutions/search/retrievers-overview#retrievers-overview-types"><code>linear</code></a> <a href="https://www.elastic.co/kr/docs/solutions/search/retrievers-overview#retrievers-overview-types">리트리버를</a> 소개합니다! <code>rrf</code> 과 달리 <code>linear</code> 검색기는 문서와 일치하는 모든 쿼리에 대해 가중 합계를 계산합니다. 이 접근 방식은 결과 집합 내에서 각 문서의 상대적 중요도를 유지하면서 각 쿼리가 최종 점수에 미치는 영향을 정밀하게 제어할 수 있습니다. 그 결과, 하이브리드 검색을 보다 직관적이고 유연하게 미세 조정할 수 있는 방법을 제공합니다.</p><p>최종 점수가 계산될 리니어 리트리버를 정의합니다:</p><p>간단합니다:</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}<p>얼마나 간단하고 직관적인지 아시나요? (그리고 <code>rrf</code> 와 정말 비슷합니다!) 이 구성을 사용하면 상대 순위에만 의존하는 <code>rrf</code> 과 달리 각 쿼리 유형이 최종 순위에 기여하는 정도를 정밀하게 제어할 수 있습니다.</p><p>한 가지 주의할 점은 사용된 유사성 측정지표에 따라 <code>knn</code> 점수가 엄격하게 제한될 수 있다는 점입니다. 예를 들어 코사인 유사도 또는 단위 정규화된 벡터의 도트 곱을 사용하면 점수는 항상 <code>[0, 1]</code> 범위 내에 있습니다. 반면 <code>bm25</code> 점수는 예측 가능성이 낮고 범위가 명확하게 정의되어 있지 않습니다.</p><h2>점수 확장: kNN 대 BM25</h2><p>하이브리드 검색의 한 가지 문제점은 검색기마다 다른 척도로 점수를 산출한다는 점입니다. 예를 들어 다음 시나리오를 생각해 보세요:</p><p>쿼리 A 점수:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>쿼리 B 점수:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>0.63</p><p>0.01</p><p>0.3</p><p>0.4</p><p><code>kNN</code> 점수는 0에서 1 사이의 범위인 반면 <code>bm25</code> 점수는 매우 다양합니다. 이러한 차이로 인해 결과 결합을 위한 정적 최적 가중치를 설정하기가 까다롭습니다.</p><h2>구원의 정규화: MinMax 정규화 도구</h2><p>이 문제를 해결하기 위해 다음 공식을 사용하여 각 쿼리에 대해 독립적으로 점수를 <code>[0, 1]</code> 범위로 확장하는 <code>minmax</code> 정규화기(선택 사항)를 도입했습니다:</p><p>이렇게 하면 쿼리 결과 집합 내에서 각 문서의 상대적 중요도가 유지되므로 서로 다른 검색기의 점수를 쉽게 결합할 수 있습니다. 정규화를 사용하면 점수는 다음과 같이 됩니다:</p><p>쿼리 A 점수:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.01</p><p>0.005</p><p>0.000</p><p>쿼리 B 점수:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.000</p><p>0.465</p><p>0.645</p><p>이제 모든 점수는 <code>[0, 1]</code> 범위에 속하며, 절대 점수 대신 결과의 (쿼리 대비) 중요도를 파악하고 쿼리 간 일관성을 유지하므로 가중치 합계를 최적화하는 것이 훨씬 더 간단해졌습니다.</p><h2>리니어 리트리버 예시 </h2><p>이제 예제를 통해 위의 내용이 어떻게 보이는지, <code>linear</code> 리트리버가 <code>rrf</code> 의 몇 가지 단점을 어떻게 해결하는지 살펴보도록 하겠습니다. RRF는 상대적인 순위에만 의존하며 실제 점수 차이는 고려하지 않습니다. 예를 들어 다음과 같은 점수가 주어집니다:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>RRF 점수</p><p>0.03226</p><p>0.03252</p><p>0.03200</p><p>0.03125</p><p>rrf는 문서의 순위를 매깁니다:</p><p>그러나 doc1은 다른 문서보다 <code>bm25</code> 점수가 훨씬 높은데, <code>rrf</code> 은 상대적인 순위만 보기 때문에 이를 포착하지 못합니다. 정규화와 결합된 <code>linear</code> 리트리버는 점수와 그 차이를 모두 정확하게 설명하여 보다 의미 있는 순위를 생성합니다:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1</p><p>0.01</p><p>0.005</p><p>0</p><p>위에서 볼 수 있듯이 doc1의 높은 순위와 <code>score</code> 의 <code>bm25</code> 이 적절히 고려되어 최종 점수에 반영되었습니다. 또한 모든 점수는 이제 <code>[0, 1]</code> 범위 내에 있으므로 훨씬 더 직관적인 방식으로 비교하고 결합할 수 있으며 오프라인 최적화 프로세스도 구축할 수 있습니다.</p><h2>모든 것을 종합하기</h2><p>정규화를 통해 <code>linear</code> 검색기를 최대한 활용하려면 검색 요청은 다음과 같이 표시되어야 합니다:</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}<p>이 접근 방식은 <code>linear</code> 리트리버의 유연성과 직관적인 채점 방식을 유지하면서 MinMax 정규화를 통해 일관된 점수 확장을 보장하는 두 가지 장점을 결합한 것입니다.</p><p>모든 리트리버와 마찬가지로 <code>linear</code> 리트리버는 설명 기능, 일치 항목 강조 표시, 필드 축소 등을 지원하여 계층적 리트리버 트리의 모든 레벨에 통합할 수 있습니다.</p><h2>리니어 리트리버를 선택해야 하는 시기 및 리니어 리트리버가 차이를 만드는 이유</h2><p><code>linear</code> 리트리버:</p><ul><li><p>단순한 순위가 아닌 실제 점수를 활용하여 상대적 중요도를 유지합니다.</p></li><li><p>다양한 쿼리의 가중치 기여도를 사용하여 미세 조정할 수 있습니다.</p></li><li><p>정규화를 사용하여 일관성을 향상시켜 하이브리드 검색을 더욱 강력하고 예측 가능하게 만듭니다.</p></li></ul><h2>결론</h2><p><code>linear</code> 리트리버는 이미 Elasticsearch 서버리스와 8.18 및 9.0 릴리즈에서 사용할 수 있습니다! 더 많은 예제와 구성 매개변수는 문서에서도 확인할 수 있습니다. 직접 사용해보고 하이브리드 검색 환경을 개선하는 방법을 알아보세요. 여러분의 피드백을 기다리겠습니다. 즐거운 검색 되세요!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</guid>
    <category><![CDATA[정확도]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Panagiotis Bailis]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte58a751cbcc1153d/6a17e3c76317305c4f585a10/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>