<?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/integrations</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/integrations</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/integrations.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 19:52:36 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Kibana Dashboards API: 정식 출시 전에 50개 이상의 팀이 테스트한 모든 패널 유형을 위한 안정적인 계약]]></title>
    <description><![CDATA[Kibana 대시보드를 코드로 관리하세요. Git에 커밋하고, 환경 간에 승격하며, Kibana API와 Terraform을 사용해 배포를 자동화할 수 있습니다.]]></description>
    <content:encoded><![CDATA[<p><a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards">Kibana Dashboards 및 Visualizations API</a>는 Elastic 9.5에서 프로덕션 환경에 사용할 수 있으며, 모든 구독 등급에서 제공되고 완전한 이전 버전과의 호환성을 지원합니다. 대시보드를 JSON으로 정의하고 Git에 커밋한 후, 지속적 통합 및 지속적 배포(CI/CD) 파이프라인, <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">Terraform</a> 또는 이미 사용 중인 도구를 통해 여러 환경에 배포할 수 있습니다.  50개가 넘는 팀이 <a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">9.4의 기술 미리보기</a> 기간에 API를 테스트했으며, 일부는 이미 프로덕션에서 사용하고 있습니다. 버전 9.5에서는 <a href="https://dashboardsapispec.kibana.dev/tags.html">태그</a>용 새 엔드포인트도 기술 미리 보기로 추가됩니다. <a href="https://dashboardsapispec.kibana.dev/markdowns.html">Markdown</a> 및 <a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links">Links</a> 패널 엔드포인트는 현재 Elastic Cloud Serverless에서 제공되며 9.6에 추가될 예정입니다.</p><h2>Kibana Dashboards API에서 이전 버전과의 호환성이 의미하는 사항</h2><p>기술 미리 보기 기간에는 릴리스 간에 API 형태가 변경될 수 있었습니다.[1] 이제는 그렇지 않습니다. 정식 출시(GA)는 다음을 의미합니다.</p><ul><li><p><strong>완전한 이전 버전과의 호환성.</strong> 시간이 지남에 따라 새 필드와 패널 유형이 추가되지만, 기존 필드와 동작은 변경되지 않습니다. 향후 호환성이 깨지는 변경 사항은 매우 신중하게 검토되며, 새로운 주요 스택 버전에서만 도입됩니다.</p></li><li><p><strong>프로덕션 준비 및 완전한 지원.</strong> API에는 Elastic의 완전한 지원 보증이 적용됩니다. 자동화된 배포, 환경 승격 및 프로그래밍 방식의 대시보드 관리에 이 API를 프로덕션 환경에서 안심하고 사용할 수 있습니다.</p></li></ul><h2>태그, Markdown 및 Links 패널을 위한 새로운 Kibana API 엔드포인트</h2><p>Elastic 9.5에서는 대시보드를 분류하고 필터링할 수 있는 <a href="https://dashboardsapispec.kibana.dev/tags.html"><strong>태그</strong></a>용 새로운 독립형 엔드포인트도 도입합니다. 이제 전용 CRUD 엔드포인트를 통해 태그를 프로그래밍 방식으로 관리할 수 있어, 여러 환경에서 대규모로 대시보드를 더 쉽게 구성할 수 있습니다.	</p><p>새로운 <a href="https://dashboardsapispec.kibana.dev/markdowns.html"><strong>Markdown</strong></a> 및 <a href="https://dashboardsapispec.kibana.dev/links.html#tag/Links"><strong>Links</strong></a> 패널 엔드포인트는 현재 Serverless에서 제공되며, 다음 스택 릴리스인 9.6에 추가될 예정입니다.</p><h2>Kibana Dashboards API는 어떤 패널 유형을 지원하나요?</h2><p>Dashboards API는 9.5의 모든 <em>by-value</em> 패널을 지원합니다. by-value 패널은 재사용을 위해 저장된 라이브러리 패널과 달리 대시보드에 직접 정의된 패널입니다. 지원되는 모든 패널 유형에는 타입이 지정되고 검증된 스키마가 있습니다.</p><p><strong>패널 유형</strong></p><p><strong>상태</strong></p><p>XY 차트</p><p>지원됨</p><p>메트릭</p><p>지원됨</p><p>파이</p><p>지원됨</p><p>게이지</p><p>지원됨</p><p>히트맵</p><p>지원됨</p><p>데이터 테이블</p><p>지원됨</p><p>트리맵</p><p>지원됨</p><p>Discover 세션</p><p>지원됨</p><p>컨트롤</p><p>지원됨</p><p>Markdown</p><p>지원됨</p><p>Links</p><p>지원됨</p><p>ML 패널</p><p>지원됨</p><p>Observability 패널</p><p>지원됨</p><p>Maps</p><p>출시 예정</p><p>Vega</p><p>출시 예정</p><h2>Kibana 대시보드를 코드로 관리하는 방법</h2><p>Dashboards API를 사용하면 코드 기반의 대시보드를 위한 전체 워크플로우를 구현할 수 있습니다. 대시보드를 차이 비교가 가능한 깔끔한 JSON으로 내보내고, 이를 단일 정보 원천으로 Git에 커밋하며, 풀 리퀘스트에서 변경 사항을 검토하고, 개발·스테이징·프로덕션 전반에 동일한 정의를 배포할 수 있습니다. 대시보드를 코드로 관리하기 시작했다면 Git을 단일 정보 원천으로 취급해야 합니다. UI에서 직접 변경한 내용은 다음 배포 시 덮어써집니다.</p><p>대시보드를 스페이스, 클러스터 또는 단계 간에 이동할 때 가장 큰 과제는 대시보드가 데이터 뷰와 라이브러리 시각화 같은 객체를 ID로 참조한다는 점입니다. 이러한 ID는 자동 생성되며 환경마다 다르므로, 한 환경에서 내보낸 대시보드가 다른 환경에는 존재하지 않는 객체를 가리킬 수 있습니다. 이를 처리하는 방법은 자동화 수준이 높은 순서부터 낮은 순서까지 다음 세 가지입니다.</p><ul><li><p><strong>Terraform 사용.</strong> <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">Elastic Stack Terraform provider</a>는 각 리소스를 추적하고 환경별 ID를 자동으로 매핑하므로, 대시보드를 개발 환경에서 프로덕션으로 승격할 때 참조가 일관되게 유지됩니다.</p></li><li><p><strong>by-value </strong><a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql"><strong>Elasticsearch Query Language (ES|QL) 패널</strong></a><strong> 정의.</strong> 가장 이식성이 높은 패널 생성 방법은 대시보드에서 ES|QL을 직접 사용해 시각화를 정의하는 것입니다. <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql-kibana">ES|QL</a> 쿼리는 쿼리 내에 지정한 인덱스에서 읽어 오므로, 패널에는 데이터 뷰나 라이브러리 객체에 대한 외부 참조가 없습니다. 그 결과 완전히 독립적이고 이식 가능한 대시보드가 만들어집니다..</p></li><li><p><strong>일치하는 ID 할당.</strong> 데이터 뷰나 라이브러리 시각화와 같은 저장된 객체를 참조하는 경우, ID를 자동 생성하는 POST 대신 PUT(업서트)을 사용하여 선택한 ID로 객체를 생성하세요. logs-prod와 같이 사람이 읽을 수 있는 ID를 사용하면 여러 환경에서 쉽게 재사용하고 식별할 수 있습니다.</p></li></ul><p>이러한 이식성 패턴과 코드 기반의 대시보드를 위한 전체 워크플로우에 관한 자세한 안내는 <a href="https://www.elastic.co/docs/explore-analyze/dashboards/manage-dashboards-as-code#dashboards-as-code-portability">코드 기반의 대시보드 관리</a> 설명서를 참조하세요.</p><h3>PUT을 사용하여 Dashboards API로 Kibana 대시보드 생성</h3><p>다음은 POST 대신 PUT을 사용해 대시보드 이름인 service-health-overview를 사용자 지정 ID로 할당하고, 메트릭 패널을 포함한 대시보드를 생성하는 간단한 예입니다. 동일한 로직은 라이브러리에 저장되는 독립형 시각화를 만들 때도 적용됩니다.</p>PUT kbn:/api/dashboards/service-health-overview
{
  "title": "Service health overview",
  "description": "Key service metrics — managed via API",
  "tags": [
    "production",
    "sre-team"
  ],
  "panels": [
    {
      "type": "vis",
      "grid": {
        "x": 0,
        "y": 0,
        "w": 12,
        "h": 8
      },
      "config": {
        "title": "Error rate (5xx)",
        "type": "metric",
        "data_source": {
          "type": "esql",
          "query": "FROM logs-* | WHERE http.response.status_code &gt;= 500 | STATS error_rate=count(*) BY host.name"
        },
        "metrics": [
          {
            "type": "primary",
            "column": "count"
          }
        ]
      }
    }
  ]
}<h2>Kibana Dashboards API 로드맵: Maps, Vega 및 독립형 엔드포인트</h2><p>Elastic은 API 기능 범위를 활발히 확장하고 있습니다. 다음으로는 Maps 및 Vega 패널 지원을 추가하고, 이들 패널의 타입이 지정된 스키마를 제공합니다. 또한 기존 대시보드 패널 지원 범위를 넘어 Discover 세션용 독립형 CRUD 엔드포인트와 Vega, Maps 및 Annotations용 독립형 CRUD 엔드포인트도 구축하고 있습니다. 이 엔드포인트는 대시보드 수명 주기와 분리됩니다.</p><p>전체 스키마 정의는 <a href="https://dashboardsapispec.kibana.dev/dashboards#tag/Dashboards">Dashboards API 설명서</a>에서 확인하세요. Terraform 사용자는 <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard">Elastic Stack Terraform provider</a>를 통해 정식 출시된 Dashboards API를 사용할 수 있습니다.</p><h2>참고</h2><ol><li><p>핵심 엔드포인트는 기술 미리보기와 비교해 변경되지 않았습니다. 9.4를 대상으로 통합을 구축했다면 9.5에서도 작동합니다. 호환성이 깨지는 변경 사항은 대시보드 목록 표시 및 기간 단위 형식에 영향을 주는 사소한 변경 두 가지뿐이며, <a href="https://www.elastic.co/docs/release-notes/kibana/breaking-changes">여기</a>에서 설명합니다.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/dashboards-as-code-kibana-api</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/dashboards-as-code-kibana-api</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[개발자 경험]]></category>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ed7e33de291f255/6a730619c8b7ac02b251f9d3/image1.png" length="0" type="image/png"/>
    <pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[5분 이내 온프레미스 구축: 이제 온프레미스 배포에 Jina 임베딩 모델 사용 가능]]></title>
    <description><![CDATA[순위 재지정기를 포함한 28개 모든 Jina AI 모델이 텔레메트리와 라이선스 서버 없이 즉시 배포 가능한 Docker 컨테이너로 제공됩니다. OpenAI, Cohere, Voyage AI 및 Elastic Inference Service API와 바로 호환됩니다.]]></description>
    <content:encoded><![CDATA[<p>28개의 모든 Jina AI 임베딩 및 순위 재지정 모델이 이제 <a href="https://www.elastic.co/kr/search-labs/blog/jina-embeddings-v5-omni-all-media-one-index">jina-embeddings-v5-omni</a><a href="https://www.elastic.co/kr/search-labs/blog/jina-embeddings-v5-omni-all-media-one-index"> </a>및 <a href="https://www.elastic.co/kr/search-labs/tutorials/jina-tutorial/jina-reranker-v3">jina-reranker-v3</a>를 포함하여 온프레미스 배포를 위한 완전한 오프라인 Docker 컨테이너로 제공됩니다. 하나를 다운로드하여 온프레미스 에어갭 또는 방화벽 시스템으로 전송하면 5분 이내에 로컬 추론이 실행됩니다. 컨테이너는 완전히 독립적이며 외부 연결이 이루어지지 않습니다. Hugging Face나 모델 레지스트리 호출도 발생하지 않습니다. 또한 라이선스 서버, 텔레메트리 또는 로깅 엔드포인트도 없습니다. 이는 규제 대상 산업, 데이터 주권 요구사항 또는 인터넷 액세스를 신뢰할 수 없거나 전혀 사용할 수 없는 환경에서 제3자 AI 서비스에 대한 의존성을 제거합니다. Jina 온프레미스는 Elastic Inference Service(EIS), OpenAI, Cohere, Voyage AI 및 Gemini API 스키마를 지원하므로 기존 애플리케이션이 코드 변경 없이 작동합니다.</p><p>가장 강력한 AI 모델이 웹 API를 통한 액세스로 원격 클라우드 설치 환경에서 실행되면 보안, 서비스 가용성 및 가격 안정성을 AI 서비스 제공업체에 맡겨야 합니다. 점점 더 강력하고 정교해지며 리소스 집약적인 AI 사용에 맞춰 신뢰성, 프라이버시, 관리 가능한 비용 및 우수한 데이터 거버넌스에 대한 타당한 요구사항을 조화시키기가 어렵습니다.</p><p>정부 규제, 법원 판결 및 타인의 이익을 고려한 비즈니스 결정으로 인해 최근 특정 서비스에 대한 액세스가 제한되는 결과가 발생했습니다. 다른 서비스로 전환할 수 있다 해도, AI 모델은 원하는 때에 언제든지 교체할 수 있는 구성 요소가 아닙니다. 시맨틱 임베딩을 사용하는 애플리케이션은 쿼리 작업 시점에도 데이터 수집 시점과 동일한 모델에 액세스할 수 있어야 합니다. 임베딩 모델에 액세스하지 못하면 검색 시스템이 중단됩니다.</p><p>AI 가격 모델은 이러한 위험을 가중시킵니다. 주요 AI 공급업체의 최근 재무 공시를 보면, 고객이 향후 가격 인상 가능성을 우려할 만한 충분한 이유가 있습니다. 예측할 수 없는 비용이 발생하는 제품에 대한 의존은 명확한 수익을 창출하지 못할 수 있는 자본 집약적인 AI 투자에 더 많은 위험을 더합니다.</p><p>Jina 온프레미스는 이러한 당면 과제에 대한 Elastic의 해답입니다.</p><h2>온프레미스 AI가 필요한 대상</h2><p>AI 모델의 로컬 호스팅 및 직접 제어는 다양한 기술적 요구 사항, 업계 요구 사항, 비즈니스 이해관계를 지원합니다.</p><p>로컬 설치는 AI 서비스 제공업체에 지불하는 비용을 줄여 주지만, 조직에 하드웨어 및 안정적인 액세스 비용 부담을 줍니다. 사용량에 따라 단순히 비용이 더 저렴할 수도 있습니다. 하지만 자체 AI 운영을 고려해야 하는 시급한 이유가 더 있습니다. 아래에 설명된 문제 중 하나라도 해당한다면 Jina 온프레미스와 같은 로컬 AI 솔루션을 고려해 보세요. 이 목록은 일부만 정리한 것입니다.</p><p>사용 사례</p><p>온프레미스를 선택해야 하는 이유</p><p>예</p><p>에어갭 / 높은 보안성</p><p>아웃바운드 데이터 전송 없음, 완전한 네트워크 격리</p><p>국방, 정보, 기밀 연구</p><p>규제 준수</p><p>데이터 주권, 국경 간 전송 또는 제3자 노출 없음</p><p>의료(건강보험의 양도 및 책임에 관한 법률[HIPAA]), 금융, EU 기업(일반 개인정보 보호법[GDPR])</p><p>지연 시간에 민감</p><p>네트워크 의존성 없음, 연결 실패를 허용하지 않음</p><p>로봇공학, 엣지 컴퓨팅, 차량, 선박</p><p>비용 예측 가능성</p><p>고정 인프라 비용 vs. 향후 요금이 불확실한 토큰당 요금제</p><p>대용량 연속 추론 워크로드</p><p>책임 감소</p><p>제3자 데이터 노출 없음, 법적 권한과 주의 의무 유지관리</p><p>법무법인, 정부 기관</p><h3>에어갭 및 방화벽 시스템에 온프레미스 AI가 필요한 이유</h3><p>에어갭 및 방화벽으로 보호된 시스템은 외부 AI API를 사용할 수 없습니다. Jina 온프레미스는 아웃바운드 연결 없이 전적으로 자체 인프라 내에서 실행됩니다.</p><p>특히 민감한 데이터를 관리하는 조직의 경우, 보안 및 프라이버시 고려 사항이 무엇보다 중요합니다. 보안 조치가 불충분하거나 외국 정부의 요구를 받을 수 있는 원격 제3자에게 민감한 데이터를 즉시 넘겨준다면, 해당 데이터를 보호하기 위해 투자해도 거의 소용이 없습니다.</p><p>민감한 데이터를 처리하는 조직의 직원은 안전한 데이터 처리에 대한 교육을 받는 경우가 많지만, 이들 모두 해당 데이터를 처리하는 동안 인터넷의 모든 페이지를 열 수 있는 웹 브라우저를 사용하고 있다면 이는 그다지 효과적이지 않습니다. 격리는 에어갭이나 매우 제한적인 방화벽을 통해 사용할 수 있는 가장 효과적인 보안 조치이지만, 이로 인해 어떤 종류의 외부 서비스든 사용하기가 어려워집니다.</p><h3>지연 시간에 민감한 고가용성 시스템을 위한 온프레미스 AI</h3><p>서비스형 소프트웨어와 클라우드 컴퓨팅은 자체 컴퓨터에서 접근성이 높고 신뢰할 수 있는 서비스를 제공하는 비용과 타인에게 문제를 아웃소싱하는 비용을 절충한 방식입니다. 하지만 가변적인 지연 시간, 중단, 그리고 문제가 발생했을 때 제어력을 완전히 상실하는 문제가 동반됩니다. AI 서비스도 예외는 아닙니다. 임베딩 모델에 액세스할 수 없을 때 검색 시스템이 오프라인 상태가 된다면, 더 이상 좋은 절충안으로 여겨지지 않을 것입니다.</p><p>또한, 외부 AI에 의존하는 것은 쉽게 예견하거나 관리할 수 없는 위험을 항상 수반합니다. 정치적 사건, 악천후 또는 해저 광케이블 위로 닻을 끄는 선박의 여파로 예고 없이 인터넷 액세스 및 네트워크 대기 시간이 저하될 수 있습니다. 정부는 수출 금지 조치를 활용하여 AI 모델에 대한 접근을 갑자기 차단할 수 있으며, 최근 실제로 그러한 사례가 발생했습니다. AI 서비스 제공업체는 최신 모델로의 전환을 유도하기 위해 때때로 기존 모델을 철수하기도 합니다. 외부 서비스의 유연성 및 관리 가능한 비용은 의존성에 따른 위험과 균형을 이루어야 합니다.</p><h3>GDPR, HIPAA 및 데이터 주권 규정 준수를 위한 온프레미스 AI</h3><p>개인 데이터를 수집하는 조직에는 점점 더 엄격한 규정이 적용되며, 관할권에 따라 종종 다르기도 하고 모순된 요구 사항이 있을 수도 있습니다. 특히, <a href="https://www.hhs.gov/hipaa/for-professionals/privacy/laws-regulations/index.html">HIPAA 규칙</a>은 미국 의료 제공자에게 매우 엄격한 데이터 보호를 부과하며, <a href="https://laws-lois.justice.gc.ca/eng/acts/p-8.6/">캐나다</a>, <a href="https://gdpr-info.eu/">유럽 연합</a> 및 <a href="https://www.japaneselawtranslation.go.jp/en/laws/view/4241">많은 아시아 관할권</a>의 강력한 일반 데이터 보호법은 개인정보를 다루는 모든 기업이 이를 안전하게 처리하고 다른 당사자 또는 다른 관할권으로 해당 데이터를 전송하는 것을 제한하도록 요구합니다. 이러한 규칙은 해당 관할권에 고객이 있는 경우 외국 기관에도 의무를 부과할 수 있습니다. 금융 기관은 한층 더 엄격한 규칙을 적용받는 경우가 많으며, 다른 형태의 범죄 활동으로부터 보호해야 하는 것과 마찬가지로 정보 보안에 대해 동일한 직접적 책임을 집니다.</p><p>규정 준수는 제3자 AI 서비스와 양립이 불가능할 수 있으며, 특히 해당 서비스를 사용할 때 국경 간 데이터 전송이 수반되는 경우 더욱 그러합니다.</p><p>또한 최근 사례에서 드러났듯이, 국제 클라우드 운영업체가 해외 정부의 압력을 받을 때 데이터 저장소의 물리적 위치를 제한하는 규칙은 신뢰할 수 있는 보호 출처가 되지 않을 수 있습니다. 현지 법률은 관할권 간에 충돌할 수 있으며, 이에 따라 현지 데이터 저장 및 처리가 필요하게 되어 제3자 서비스를 사용할 수 없게 될 수 있습니다. 어떤 경우에는 AI 시스템을 포함하여 프로세스의 모든 부분을 사내로 가져오는 것만이 유일한 솔루션입니다.</p><h3>제3자 데이터 전송으로 인한 AI 책임 위험</h3><p>데이터 보호법과 민감한 데이터에 대해 인정되는 주의 의무는 일상적으로 법적 책임에 영향을 미치며, 때로는 매우 심각한 영향을 미치기도 합니다. 여러분이 제3자 서비스 제공업체에서 데이터를 처리하는 데 대한 법적 책임을 질 수 있습니다. 법원과 법적 절차는 안전하지 않은 서비스 제공업체로부터 사후에 일부 보호 조치를 제공할 수 있지만, 그러한 구제책은 국가 안보 주체, 법 집행 기관 또는 범죄 해커를 대상으로는 이용 가능하지 않거나 일반적으로 효과적이지 않습니다.</p><p>정부와 관련해, 국경을 넘는 클라우드 서비스 제공업체가 민감한 국가 정보를 국외 행위자에게 공개한 사례가 이미 있습니다.</p><p>하지만 외국 정부나 해커에 대한 걱정이 없고 외부 AI 서비스 제공업체 자체가 안전하다고 하더라도, 단지 외부에 있다는 사실만으로도 법적 책임이 발생할 수 있습니다.</p><p>예를 들어 대부분의 관할권에서 변호사와 의뢰인의 의사소통은 특별한 법적 보호를 받으며, 법률 사무소는 이러한 정보를 기록하거나 저장할 때 엄격한 책임을 집니다. 미국에서 이러한 “변호사-의뢰인 특권”은 매우 유명하여 영화 및 TV 줄거리의 핵심 요소로 등장합니다. 그러나 이러한 특권을 상실하게 되는 원인 중 하나는 특권 대상이 아닌 사람과 정보를 주고받는 것이며, 최근 동향에 따르면 외부 AI 서비스 제공업체가 이에 해당할 수 있습니다.</p><p>적어도 미국에서는 인덱싱 서비스를 제공하는 임베딩 모델과 같이 인터넷 API를 통해 제3자 AI 서비스를 사용하는 것만으로도 중요한 기밀 유지 규정을 위반할 가능성이 있습니다. 법률 사무소는 보안 침해가 발생하지 않더라도 외부에 호스팅된 소프트웨어를 사용하는 것만으로도 소송을 당하거나, 징계를 받거나, 변호사 자격을 박탈당할 수 있습니다.</p><h3>오프라인, 엣지, 물리적으로 격리된 시스템을 위한 온프레미스 AI</h3><p>컴퓨터 시스템을 보안상의 이유로만 격리하는 것은 아닙니다. 예를 들어, 이동 중인 차량은 필수 기능을 위해 인터넷 액세스를 이용할 수 없습니다. 선박과 항공기에 있는 광범위한 온보드 컴퓨터 시스템은 인터넷 연결 없이 작동해야 하므로 외부 AI 서비스를 사용할 수 없습니다. 해상 플랫폼, 오지의 원격 시설, 북극, 남극, 글로벌 네트워크에 대한 적절한 물리적 연결이 없는 작은 섬의 컴퓨터 서비스는 모두 필요한 모든 서비스를 로컬에서 호스팅함으로써 이점을 얻는 설치 사례입니다. 엔터프라이즈 컴퓨팅에서 AI의 역할이 커짐에 따라 이러한 한계를 해결하는 것이 더욱 중요해집니다.</p><p>로봇 공학 및 기타 공간 제약적이거나 외부 세계에 초점을 맞춘 사용 사례(예: 물류 관리 시스템 또는 슈퍼마켓 계산대 등)와 같은 물리적 시스템에 AI를 새롭게 적용하는 경우 글로벌 인터넷에 연결될 수 있지만 연결 실패나 지연 시간 급증이 허용되지 않습니다. 작동을 위해 AI 시스템이 필요한 경우, 이 AI 시스템은 가능한 한 로컬 환경에서 작동하며 신뢰할 수 있어야 합니다.</p><h2>온프레미스 AI가 필요하지 않은 경우</h2><p>원격 소프트웨어 서비스와 오프사이트 AI에는 분명 이점이 있습니다. AI 모델을 실행하려면 악명 높게 수명이 짧고 전력 소비가 심한 고가의 프로세서가 필요할 수 있습니다. 시장 요인과 외부 경제 충격으로 인해 현재 고품질 하드웨어에 대한 접근이 특히 어렵습니다. 이러한 상황에서는 로컬 AI의 막대한 자본 비용을 지원하기보다 토큰 단위로 비용을 지불하여 외부 API를 사용하는 것이 합리적일 수 있습니다.</p><p>외부 API는 간헐적인 사용자에게 가장 적합합니다. 항상 온라인 상태를 유지해야 하는 검색 시스템을 운영하기보다 주로 분석을 위해 데이터를 배치 처리하는 데 AI 모델을 사용하는 경우, 자본 집약적인 하드웨어 및 로컬 설치에 투자하는 것은 거의 의미가 없습니다.</p><p>또한, 신뢰성과 접근성을 위해 클라우드에 호스팅된 전자상거래 웹사이트와 같이 데이터 처리가 이미 클라우드 기반으로 이루어지는 경우, 동일한 클라우드 인프라에 위치한 AI 서비스를 사용하는 것이 자체 라이선스 AI 모델 배포를 도입하는 것보다 비용 대비 더 뛰어난 가치를 제공할 수 있습니다. 이미 클라우드 서비스 제공업체에 의존하고 있으므로 해당 업체의 AI 서비스에 의존한다고 해서 위험이 크게 증가하지는 않습니다.</p><p>사용 사례가 이러한 설명에 부합하는 경우, 요구 사항에 맞춰 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>, <a href="https://aws.amazon.com/marketplace/seller-profile?id=seller-stch2ludm6vgy">AWS Marketplace</a> 및 <a href="https://console.cloud.google.com/marketplace/browse?q=jina">Google Cloud Platform</a>에서 Jina AI 모델을 이용할 수 있습니다.</p><p>아래 표는 주요 고려 요소를 요약한 것입니다. 해답은 데이터, 인프라 및 사용 패턴에 따라 달라집니다.</p><p>요소</p><p>온프레미스 권장</p><p>클라우드 API 권장</p><p>사용 패턴</p><p>지속적 또는 대용량 추론</p><p>간헐적 또는 배치 처리</p><p>데이터 민감도</p><p>규제 대상, 주권 또는 기밀</p><p>국경 간 또는 제3자 제한 없음</p><p>네트워크 환경</p><p>에어갭, 방화벽 또는 불안정</p><p>안정적인 상시 접속 인터넷</p><p>기존 인프라</p><p>GPU 하드웨어를 소유하고 있거나 조달할 수 있음</p><p>이미 동일 위치에 배치된 AI와 함께 클라우드에 호스팅됨</p><p>비용 모델</p><p>고정 하드웨어 + 라이선스, 확장 시 예측 가능</p><p>토큰당, 낮은 초기 비용, 가변적인 장기 비용</p><p>지연 시간 허용 범위</p><p>없음(로보틱스, 엣지, 실시간)</p><p>네트워크 변동성 허용</p><p>운영 책임</p><p>팀에서 하드웨어 및 가용성 관리</p><p>제공업체가 하드웨어 및 업데이트를 관리, 사용자는 통합 관리</p><p>이전 섹션에서 강조된 문제 중 해당하는 사항을 고려하여, 특정 상황과 사용 사례에 비추어 비용과 이점을 고려해야 합니다. 비용 편익 분석은 시간이 지남에 따라 분명 변화할 것입니다. 단기적으로도 AI 산업의 미래나 하드웨어 가격은 예측할 수 없습니다.</p><h2>Jina 온프레미스 소개</h2><p>로컬 AI 서비스를 활용할 수 있는 사용자를 대상으로 Jina AI의 고성능 모델을 위한 완전한 독립형 설치 제품군인 <a href="https://github.com/jina-ai/jina-on-prem/wiki/">Jina 온프레미스</a>를 출시합니다.</p><p>Jina AI의 모델은 <a href="https://mteb-leaderboard.hf.space/benchmark/MTEB(Multilingual%2C%20v2)">크기가 몇 배나 큰</a> 임베딩 모델의 정확도에 비견되며, 컴퓨팅 비용, 메모리 공간 및 하드웨어 요구 사항을 줄여줍니다. 따라서 AI를 온프레미스에 유지하고자 하거나 유지해야 하는 사용자에게 이상적인 선택입니다. 상업용 라이선스는 모든 규모의 사용 사례에 맞게 확장 가능하고 비례하여 가격이 책정된 솔루션과 함께 제공됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt190865fb3ebde472/6a6a33d0065b162508701ff9/02559ceca556a26c53eb703ae87d421452b27251-1374x1400.png" alt="MMTEB Multilingual v2 leaderboard showing Jina AI embedding model rankings: jina-embeddings-v5-omni-small and jina-embeddings-v5-text-small ranked 13th, jina-embeddings-v5-omni-nano and jina-embeddings-v5-text-nano ranked 19th, competing against models from Microsoft, Google, Tencent, NVIDIA and Qwen" /><h3>Jina 온프레미스가 지원하는 API 스키마</h3><ul><li><p>로컬 설치를 위한 전체 종속성 컬렉션으로 제공되거나 몇 분 만에 설치하고 실행할 수 있는 <a href="https://www.docker.com/">Docker 컨테이너</a>로 제공됩니다.</p></li><li><p>Jina 온프레미스 설치 환경에서는 외부 시스템을 호출하지 <em>않습니다</em>.</p><ul><li><p>Hugging Face Hub나 어떠한 모델 레지스트리도 호출하지 않습니다(HF_HUB_OFFLINE=1 및 TRANSFORMERS_OFFLINE=1이 기본으로 포함됨).</p></li><li><p>라이선스 서버가 없습니다.</p></li><li><p>텔레메트리 또는 로깅 엔드포인트가 없습니다.</p></li></ul></li><li><p>CPU 및 GPU 하드웨어를 모두 지원하며, GPU 자동 감지 기능을 제공합니다.</p></li><li><p>최신 <a href="https://www.elastic.co/kr/search-labs/blog/jina-embeddings-v5-omni-all-media-one-index">jina-embeddings-v5-omni</a> 멀티모달 임베딩 모델 및 <a href="https://www.elastic.co/kr/search-labs/tutorials/jina-tutorial/jina-reranker-v3">jina-reranker-v3</a>를 포함하여 28개의 모든 Jina AI 모델을 사용할 수 있습니다.</p></li><li><p><a href="https://jina.ai/api-dashboard">Jina API</a>, OpenAI, Cohere, Voyage AI, Gemini 등 표준 AI API 스키마를 통한 액세스가 제공됩니다. Jina 온프레미스는 이러한 스키마를 기반으로 구축된 애플리케이션을 위한 드롭인 솔루션입니다.</p></li><li><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>에서 제공하는 모델을 드롭인 방식으로 대체할 수 있습니다. Jina 온프레미스는 <a href="https://www.elastic.co/kr/blog/deploy-elastic-air-gapped-disconnected-environments">에어갭 Elastic 배포</a>와 직접 통합됩니다.</p></li></ul><h2>Jina AI 온프레미스 모델의 하드웨어 요구 사항</h2><p>하드웨어 요구 사항은 Jina 모델마다 다릅니다. 아래 표는 GPU 설정을 사용하는 최신 모델에 대한 권장 사항을 보여줍니다. v5 임베딩 모델에는 A100이 권장되지만, NVIDIA L4 GPU보다 강력한 GPU는 필요하지 않습니다. 당사의 최신 임베딩 모델에는 현재 최소 8GB의 VRAM이 필요합니다.</p><p>모델</p><p>최소 VRAM</p><p>권장 GPU</p><p>jina-embeddings-v5-text-nano</p><p>2GB</p><p>T4 / L4</p><p>jina-embeddings-v5-text-small</p><p>3GB</p><p>L4 / A10G</p><p>jina-embeddings-v5-omni-small</p><p>8GB</p><p>L4 / A10G / A100</p><p>jina-reranker-v3</p><p>3GB</p><p>L4</p><p>jina-clip-v2</p><p>4GB</p><p>L4</p><p>jina-code-embeddings-1.5b</p><p>4GB</p><p>L4</p><p>ReaderLM-v2</p><p>4GB</p><p>L4</p><p>한 번에 둘 이상의 모델을 사용하는 경우 VRAM 요구 사항이 증가합니다. 자세한 내용은 <a href="https://github.com/jina-ai/jina-on-prem/wiki/Sizing-And-Hardware">사이징 및 하드웨어 페이지</a>를 참조하세요.</p><h2>Docker를 사용하여 Jina 온프레미스를 설치하는 방법</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20265d09e2d8d0f4/6a6a33d1065b162105701ffd/ada9881af407168298b1940f8537ad71a5411c89-1999x1200.png" alt="" /><p>가장 빠르게 시작하는 방법은 <a href="https://www.docker.com/get-started/">Docker를 설치</a>하고(아직 설치하지 않은 경우) <a href="https://github.com/jina-ai/jina-on-prem/wiki/QuickStart">Jina 온프레미스 빠른 시작</a> 페이지의 지침을 따르는 것입니다.</p><p>28개의 모든 Jina 모델을 위한 사전 구성된 Docker 컨테이너가 있습니다. 하나를 다운로드하여 설치 대상에 전송하면 5분 이내에 Jina AI 모델을 실행할 수 있습니다.</p><p>멀티모달 또는 사용자 지정 빌드를 사용하거나 컨테이너 외부 설치를 위한 전체 의존성 세트를 다운로드하려면 <a href="https://github.com/jina-ai/jina-on-prem/wiki/Bundling-Guide">번들링 가이드</a>에 설명된 단계를 따르세요.</p><p>Jina 온프레미스 설치는 모든 Jina API 및 EIS 기능과 OpenAI, Cohere, Voyage AI 및 Gemini API를 통한 임베딩 생성을 지원하므로 표준 인터페이스를 사용하여 기존 애플리케이션에 통합할 수 있습니다. 자세한 내용은 <a href="https://github.com/jina-ai/jina-on-prem/wiki/API-Reference">API 문서</a>를 참조하세요.</p><p>Jina 온프레미스와 함께 설치된 모델을 포함한 Jina 모델은 다양한 라이선스 조건으로 제공되며, 최신 모델은 <a href="https://creativecommons.org/licenses/by-nc/4.0/deed.en">CC BY-NC 4.0</a> 라이선스에 따라 비상업적 용도로 무료로 사용할 수 있습니다. 상업적 용도로 Jina 온프레미스 라이선스를 이용하려면 <a href="https://www.elastic.co/kr/contact">Elastic 영업팀</a>에 문의해 주시기 바랍니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/on-prem-ai-jina-embedding-models</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/on-prem-ai-jina-embedding-models</guid>
    <category><![CDATA[Jina AI]]></category>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[Scott Martens]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17731ab0c6ec66f6/6a6a33d140a4941014ca5c9a/09bc6dac4e6a86c7877f8ed78d68f5d581aeffa9-1999x1200.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[강력한 기능을 더한 Elasticsearch: 네이티브 Prometheus API 지원 추가]]></title>
    <description><![CDATA[기본 ProMQL, 탐색 및 메타데이터 엔드포인트를 통해 Prometheus 호환 클라이언트에서 직접 Elasticsearch를 쿼리하세요. Prometheus Remote Write로 Elasticsearch에 데이터를 보낼 수 있습니다.]]></description>
    <content:encoded><![CDATA[<p>Prometheus 호환 클라이언트를 Elasticsearch로 지정하고 기존 메트릭에 PromQL을 직접 실행하세요. Elasticsearch는 Prometheus Remote Write, OpenTelemetry 또는 벌크 API를 통해 수집된 메트릭에 작동하는 기술 미리 보기로 기본 Prometheus 쿼리, 탐색 및 메타데이터 엔드포인트를 추가합니다. API는 Elasticsearch의 시계열 데이터 스트림(TSDS)에서 실행되므로 별도의 Prometheus 전용 저장 공간 계층을 운영할 필요가 없습니다.</p><p>이 게시물은 쿼리, 탐색 및 메타데이터 엔드포인트가 이전의 인제스트 및 쿼리 작업을 기반으로 해당 API 표면을 형성하는 방법을 설명합니다. 동반 게시물에서 개별 항목을 더 깊이 있게 다룹니다.</p><ul><li><p><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">ES|QL의 네이티브 PromQL 지원</a>에서는 PromQL 쿼리가 ES|QL 실행 계획으로 변환되는 방법을 설명합니다.</p></li><li><p><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Remote Write로 Prometheus 메트릭을 Elasticsearch로 전송</a>에서는 수집 설정을 설명합니다.</p></li><li><p><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">Elasticsearch에서 Prometheus Remote Write 수집이 작동하는 방식</a>에서는 Remote Write의 내부 작동 방식을 설명합니다.</p></li></ul><p>이는 여전히 진행 중인 작업입니다. 아래 섹션에서는 현재 지원되는 부분과 계속 발전 중인 부분을 설명합니다.</p><h2>API 인터페이스 범위</h2><p>현재 Prometheus와 호환되는 API 인터페이스 범위는 세 가지 그룹으로 나뉩니다.</p><h3>쿼리 엔드포인트</h3><p>쿼리 엔드포인트를 통해 Prometheus 호환 클라이언트는 PromQL 표현식을 평가할 수 있습니다.</p><ul><li><p><code>GET /_prometheus/api/v1/query_range</code> 시간 범위 내에서 PromQL 표현식을 평가합니다(행렬 결과).</p></li><li><p><code>GET /_prometheus/api/v1/query</code> 특정 시점에서 평가합니다(벡터 결과). 현재는 마지막 샘플을 반환하는 단기 범위 쿼리로 구현되어 있습니다.</p></li></ul><p>현재 쿼리 엔드포인트에는 GET만 지원됩니다. 일부 클라이언트는 기본적으로 POST로 설정되어 있으므로 GET을 사용하도록 구성해야 할 수도 있습니다. Prometheus POST 규칙은 <code>application/x-www-form-urlencoded</code> 본문을 사용하며, Elasticsearch의 HTTP 레이어는 CSRF 보호 조치로 요청이 핸들러에 도달하기 전에 이를 거부합니다.</p><p>PromQL 지원 현황에 대한 자세한 내용은 <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">ES|QL의 PromQL 관련 동반 게시물</a>을 참조하세요.</p><h3>메타데이터 엔드포인트</h3><p>메타데이터 엔드포인트는 클라이언트가 자동 완성, 가변 드롭다운 및 메트릭 탐색에 필요한 검색 정보를 제공합니다.</p><p>시리즈, 레이블 및 레이블 값 엔드포인트는 모두 <code>match[]</code> 선택자와 시간 범위(<code>start</code>/<code>end</code>)을(를) 허용합니다. <code>match[]</code> 매개변수는 <code>http_requests_total{job="api"}</code> 같은 Prometheus 시리즈 선택자를 사용하여 일치하는 시계열로 응답을 제한합니다. 이렇게 하면 많은 수의 메트릭이 있는 클러스터에서 응답을 빠르고 관련성 있게 유지할 수 있습니다. 그 예는 다음과 같습니다.</p>GET /_prometheus/api/v1/series?match[]=http_requests_total{job="api"}GET /_prometheus/api/v1/labels?match[]=http_requests_totalGET /_prometheus/api/v1/label/instance/values?match[]=http_requests_total{job="api"}<p>첫 번째는 <code>job="api"</code>, 전체 레이블 세트가 있는 <code>http_requests_total</code> 에 대한 모든 시리즈를 반환합니다. 두 번째는 <code>http_requests_total</code> 시리즈에 존재하는 레이블 이름만 반환합니다. 세 번째는 일치하는 시리즈에 나타나는 <code>instance</code> 값만 반환합니다.</p><p><code>GET /_prometheus/api/v1/metadata</code> 이는 다릅니다. 각 메트릭의 유형과 단위를 반환하며, 선택적으로 <code>metric</code> 매개변수를 통해 이름으로 필터링할 수 있습니다.</p>GET /_prometheus/api/v1/metadata?metric=http_requests_total<p><code>match[]</code> 선택자나 시간 범위는 허용하지 않습니다. Prometheus에서 메타데이터는 활성 스크랩 대상( 해당 대상이 노출하는 <code>HELP</code>, <code>TYPE</code>, <code>UNIT</code> 줄)에서 수집되므로 응답에는 데이터 스캔이 포함되지 않습니다. Elasticsearch에는 이와 같은 전용 메타데이터 저장소가 없기 때문에 현재 구현에서는 지난 24시간 동안의 시계열 데이터를 방문하여 메트릭 메타데이터를 검색합니다. 이렇게 하면 전체 인덱스 스캔 없이도 쿼리를 빠르게 처리할 수 있습니다. 24시간 룩백은 현재 수정되었습니다. Prometheus 메타데이터 API는 Elasticsearch가 사용자 조정을 위해 사용할 수 있는 <code>start</code> 또는 <code>end</code> 매개변수를 노출하지 않습니다.</p><p>메타데이터 엔드포인트가 내부적으로 작동하는 방식, 그리고 이를 구동하는 <code>TS_INFO</code> 및 <code>METRICS_INFO</code> 명령을 포함하여 자세한 내용은 <a href="https://www.elastic.co/search-labs/blog//elasticsearch-native-prometheus-api#ts-info-and-metrics-info">아래</a>에서 설명합니다.</p><h3>인덱스 사전 필터링</h3><p>모든 쿼리 및 메타데이터 엔드포인트는 <code>/_prometheus/</code> 뒤에 <code>{index}</code> 경로 세그먼트(선택 사항)를 허용합니다.</p>GET /_prometheus/metrics-prod-*/api/v1/query_range?query=up&amp;start=...&amp;end=...<p>이렇게 하면 표현식 평가가 시작되기 전에 쿼리가 실행되는 Elasticsearch 인덱스가 제한됩니다. 팀이나 환경 전체에 걸쳐 많은 데이터 스트림이 있는 클러스터에서 이렇게 하면 관련 없는 인덱스를 스캔하는 것을 방지할 수 있으며 쿼리 지연 시간을 크게 줄일 수 있습니다. 인덱스 패턴별로 별도의 데이터 소스를 구성하여 팀에 자체 메트릭에 대한 범위 지정 액세스를 제공할 수 있습니다.</p><h3>Remote Write 참고 사항</h3><p>수집을 위해 다음과 같이 Elasticsearch는 표준 Prometheus Remote Write 엔드포인트도 노출합니다.</p><ul><li><p><code>POST /_prometheus/api/v1/write</code> Prometheus Remote Write v1 프로토콜을 통해 시계열 데이터를 수집합니다.</p></li></ul><p>Remote Write는 별도의 Prometheus 전용 저장 공간 레이어가 아닌 Elasticsearch의 기존 시계열 데이터 스트림(TSDS)에 씁니다. Prometheus 레이블은 TSDS 차원이 되고 메트릭 이름은 인덱스 매핑에서 필드가 됩니다. <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">Remote Write 아키텍처 게시물</a>에서는 메트릭 유형이 추론되는 방법과 <code>labels.</code> 접두사로 레이블을 저장하는 방법을 포함해 전체 매핑을 자세히 설명합니다.</p><h3>참여 방법</h3><p>내부적으로 모든 엔드포인트는 동일한 방식으로 작동합니다. 수신되는 HTTP 매개변수를 구문 분석하고, ES|QL 쿼리 계획을 수립하고, 시계열 데이터 스트림에 대해 쿼리를 실행한 다음, 열 형식의 결과를 Prometheus 클라이언트가 예상하는 JSON 형식으로 다시 변환합니다.</p><h2>TS_INFO 및 METRICS_INFO</h2><p>메타데이터 엔드포인트는 모든 데이터 요소를 스캔하지 않고도 수백만 개의 시계열 데이터에 걸쳐 "어떤 레이블이 존재합니까?" 또는 "어떤 메트릭 유형이 정의되어 있습니까?"와 같은 질문에 답해야 합니다.</p><p>내부적으로 Prometheus 메타데이터 엔드포인트는 <code>METRICS_INFO</code> 와(과)<code>TS_INFO</code> 라는 두 가지 새로운 처리 명령을 중심으로 ES|QL 계획을 수립하여 이러한 질문에 답합니다. 이러한 명령은 Prometheus API를 사용하기 위해 직접 사용할 필요는 없지만 메타데이터 응답의 핵심 실행 단위입니다. 두 명령 모두 모든 샘플을 스캔하는 대신 시계열당 하나의 문서만 방문하여 메타데이터를 추출하는 방식으로 작동합니다. 즉, 데이터 요소의 수가 아니라 고유한 시계열의 수에 따라 비용이 달라집니다.</p><p><code>METRICS_INFO</code> 각 고유 메트릭별로 이름, 유형, 단위 및 관련 차원 필드를 포함하는 행을 하나씩 반환합니다. <code>TS_INFO</code>은(는) 더 세분화되어 (메트릭, 시계열) 조합별로 행을 하나씩 반환하며, 실제 차원 값은 JSON 객체로 포함됩니다.</p><p><code>TS_INFO</code> 및 <code>METRICS_INFO</code>에 대한 자세한 블로그 게시물이 곧 올라올 예정이며, 2단계 실행 모델, 확장 방법, Prometheus API를 넘어 ES|QL 쿼리에서 직접 사용하는 방법을 설명합니다.</p><h3>메타데이터 엔드포인트의 사용하는 방법</h3><p>각 메타데이터 엔드포인트는 이러한 명령 중 하나를 핵심으로 사용하여 ES|QL 실행 계획을 구성합니다.</p><p><code>/api/v1/labels</code> 그리고 <code>/api/v1/series</code> 은(는) 시계열별 세부 정보(어떤 레이블이 있는지, 어떤 차원 값이 각 시리즈를 식별하는지)가 필요하므로 <code>TS_INFO</code> 을(를) 사용하고, <code>/api/v1/metadata</code> 와 <code>/api/v1/label/__name__/values</code> 은(는) 메트릭별 정보(메트릭 이름, 유형, 단위)만 필요하므로 <code>METRICS_INFO</code> 을(를) 사용합니다.</p><p><code>/api/v1/label/{name}/values</code> 일반 레이블( <code>__name__</code> 이외의 모든 레이블)은 두 명령을 사용하지 않습니다. <code>job</code> 또는 <code>instance</code> 같은 일반 레이블은 인덱스의 실제 차원 필드이므로 엔드포인트에서 그룹별 집계를 사용하여 직접 쿼리할 수 있습니다. <code>match[]</code> 선택자가 제공되면 집계가 실행되기 전에 시계열을 필터링하는 <code>WHERE</code> 절로 변환됩니다.</p><p><code>__name__</code> 레이블은 항상 차원 필드로 존재하는 것은 아니므로 다른 전략이 필요합니다. Prometheus Remote Write는 <code>labels.__name__</code> 을(를) 저장하지만, 다른 경로(OpenTelemetry, 벌크 API)를 통해 수집된 메트릭에는 이 레이블이 없습니다. 메트릭 이름은 필드 이름 자체에 인코딩됩니다(예: <code>metrics.http_requests_total</code>). 인덱스 매핑을 보고 필드 이름을 열거할 수 있지만, 매핑만으로는 어떤 메트릭에 어떤 차원이 있는지 알 수 없으며 <code>match[]</code> 선택자의 레이블 값으로 필터링할 수 없습니다. <code>METRICS_INFO</code> 은(는) 두 가지 모두 가능합니다. 즉, 업스트림 <code>WHERE</code> 필터를 준수하면서 인덱스 전체에서 메트릭 이름을 열거합니다.</p><p>모든 경우에 API 레이어는 <code>labels.</code> 및 <code>metrics.</code> 저장 공간 접두사를 제거하고, 해당 접두사가 없는 Prometheus가 아닌 메트릭의 경우<code>__name__</code> (으)로 합성하는 등 Prometheus 규칙으로 다시 변환을 처리합니다.</p><h2>결론</h2><p>결과: 모든 Prometheus 호환 클라이언트는 이미 이해하고 있는 엔드포인트를 통해 Elasticsearch 메트릭을 쿼리하고 탐색할 수 있습니다. Remote Write 메트릭, OpenTelemetry 메트릭, 다른 경로를 통해 인덱싱된 지표는 모두 동일한 API를 통해 표시되며, 동일한 TSDS 인덱스로 뒷받침됩니다.</p><p>여기서 언급된 모든 Prometheus API는 오늘 Elasticsearch Serverless에서 기술 미리 보기로 제공됩니다. 자체 관리 클러스터 및 Elastic Cloud Hosted 배포의 경우, <code>GET /_prometheus/api/v1/metadata</code>을(를) 제외하고 Elasticsearch 9.4에서 기술 미리 보기로 제공됩니다. 로컬에서 실험하려면 <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">start-local</a>을 사용하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-native-prometheus-api</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-native-prometheus-api</guid>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12b4e100d5bbb7f0/6a16f7a22b835ff747f4afdd/c7b333bd73e8a1f4e18486b2d692ba742788dcfd-1376x768.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[TypeScript로 Elasticsearch MCP 서버 생성]]></title>
    <description><![CDATA[TypeScript와 Claude Desktop을 사용하여 Elasticsearch MCP 서버를 생성하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch에서 대규모 지식 기반을 다룰 때, 정보를 찾아내는 것은 첫 관문을 넘긴 것에 불과합니다. 엔지니어는 종종 여러 문서에서 결과를 종합하고, 요약을 작성하며, 답변을 출처까지 추적해야 합니다. 모델 컨텍스트 프로토콜(MCP)은 이를 달성하기 위해 Elasticsearch를 거대 언어 모델(LLM) 기반 애플리케이션과 연결하는 표준화된 방법을 제공합니다. Elastic은 Elastic Agent Builder(기능 중 <a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">MCP 엔드포인트</a> 포함)와 같은 공식 솔루션을 제공하지만, 사용자 지정 MCP 서버를 구축하면 검색 논리, 결과 형식, 검색된 콘텐츠가 종합, 요약, 인용을 위해 LLM에 전달되는 방식을 완전히 제어할 수 있습니다.</p><p>이 글에서는 사용자 지정 Elasticsearch MCP 서버 구축의 장점을 살펴보고, Elasticsearch를 LLM 기반 애플리케이션에 연결하는 TypeScript로 서버를 생성하는 방법을 보여드리겠습니다.</p><h2>사용자 지정 Elasticsearch MCP 서버를 구축해야 하는 이유는 무엇입니까?</h2><p>Elastic은 <a href="https://www.elastic.co/docs/solutions/search/mcp">MCP 서버</a>에 대한 몇 가지 대안을 제공합니다.</p><ul><li><p><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">Elasticsearch 9.2 이상 버전용 Elastic Agent Builder MCP 서버</a></p></li><li><p><a href="https://github.com/elastic/mcp-server-elasticsearch?tab=readme-ov-file#elasticsearch-mcp-server">구버전용 Elasticsearch MCP 서버(Python)</a></p></li></ul><p>MCP 서버가 Elasticsearch와 상호 작용하는 방식을 더 세밀하게 제어하고 싶다면, 직접 사용자 지정 서버를 구축하여 요구 사항에 딱 맞게 최적화할 수 있는 유연성을 확보할 수 있습니다. 예를 들어, Agent Builder의 MCP 엔드포인트는 Elasticsearch 쿼리 언어(ES|QL) 쿼리로 제한되지만, 사용자 지정 서버를 사용하면 전체 쿼리 DSL을 사용할 수 있습니다. 또한 결과를 LLM으로 전달되기 전에 결과의 서식을 지정하는 방법을 제어할 수 있으며, 이번 튜토리얼에서 다룰 OpenAI 기반 요약 기능과 같은 추가적인 처리 단계를 통합할 수도 있습니다.</p><p>이 글을 마칠 때쯤이면, Elasticsearch 인덱스에 저장된 정보를 검색하고, 요약하며, 인용을 제공하는 TypeScript로 된 MCP 서버를 갖게 됩니다. 검색에는 Elasticsearch를, 요약 및 인용 생성에는 OpenAI <code>gpt-4o-mini</code> 모델을 사용하며, 사용자 쿼리를 받고 응답을 제공하는 MCP 클라이언트와 UI로는 Claude Desktop을 사용할 것입니다. 최종적으로 엔지니어가 조직 내 기술 문서 전반에서 모범 사례를 발견하고 종합할 수 있도록 돕는 내부 지식 어시스턴트를 구축하게 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltad9133cb083ad352/6a170c19b0367d411e72bd5b/ec5771a874cf9740d4cac6888622cbe8cd6aede7-1999x1133.png" alt="TypeScript와 Claude Desktop을 사용하여 Elastic MCP 서버를 생성합니다." /><h2>필수 구성 요소:</h2><ul><li><p>Node.js 20+</p></li><li><p>Elasticsearch</p></li><li><p>OpenAI API 키</p></li><li><p>Claude Desktop</p></li></ul><h3>MCP란 무엇입니까?</h3><p><a href="https://www.elastic.co/what-is/mcp">MCP</a>는 <a href="https://www.anthropic.com/news/model-context-protocol">Anthropic</a>에서 만든 오픈 표준으로, LLM과 Elasticsearch와 같은 외부 시스템 간에 안전한 양방향 연결을 제공합니다. MCP의 현황에 대한 자세한 내용은 <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">이 글</a>에서 확인할 수 있습니다.</p><p>MCP 환경은 광범위한 사용 사례를 지원하는 서버들이 등장하며 <a href="https://www.elastic.co/search-labs/blog/mcp-current-state#mcp-project-updates:-transport,-elicitation,-and-structured-tooling">매일 진화</a>하고 있습니다. 게다가, 이 글에서 보여 드릴 것처럼 자신만의 맞춤형 MCP 서버를 구축하는 것도 매우 쉽습니다.</p><h3>MCP 클라이언트</h3><p><a href="https://modelcontextprotocol.io/clients">사용 가능한 MCP 클라이언트 목록</a>은 매우 방대하며, 각 클라이언트에는 저마다의 특징과 제한 사항이 있습니다. 단순함과 대중성을 고려하여 <a href="https://claude.ai/download">Claude Desktop</a>을 MCP 클라이언트로 사용하겠습니다. Claude Desktop은 사용자가 자연어로 질문을 던지는 채팅 인터페이스 역할을 하며, MCP 서버에 노출된 도구를 자동으로 호출하여 문서를 검색하고 요약을 생성합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06fd7a02042094e1/6a170c1b14b2700024e3c651/66eb0b11473347b6cf2d85718251eeac38d6249d-1999x1491.png" alt="‘커피 한 잔과 함께하는 Claude 타임인가요? 오늘은 어떻게 도와드릴까요?' 라는 문구가 적힌 Claude 4.5 Sonnet 페이지입니다." /><h2>Elasticsearch MCP 서버 생성하기</h2><p><a href="https://github.com/modelcontextprotocol/typescript-sdk">TypeScript SDK</a>를 사용하면, 사용자 쿼리 입력을 기반으로 Elasticsearch 데이터를 쿼리하는 방법을 이해하는 서버를 쉽게 만들 수 있습니다.</p><p>이 글에서는 Elasticsearch MCP 서버를 Claude Desktop 클라이언트와 통합하는 단계를 설명합니다.</p><ol><li><p><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#configure-mcp-server-for-elasticsearch">Elasticsearch용 MCP 서버를 구성합니다.</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#load-the-mcp-server-into-claude-desktop">MCP 서버를 Claude Desktop에 로드합니다.</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#test-it-out">테스트해 보세요.</a></p></li></ol><h3>Elasticsearch용 MCP 서버 구성</h3><p>시작하려면 Node 애플리케이션을 초기화하십시오:</p>npm init -y<p>이렇게 하면 <code>package.json</code> 파일이 생성되며, 이를 통해 이 애플리케이션에 필요한 의존성을 설치하기 시작할 수 있습니다.</p>npm install @elastic/elasticsearch @modelcontextprotocol/sdk openai zod &amp;&amp; npm install --save-dev ts-node @types/node typescript<ul><li><p><strong>@elastic/elasticsearch</strong> 패키지를 통해 Elasticsearch Node.js 라이브러리에 액세스할 수 있습니다.</p></li><li><p><strong>@modelcontextprotocol/sdk</strong>는 MCP 서버 생성 및 관리, 도구 등록, MCP 클라이언트와의 통신 처리를 위한 핵심 도구를 제공합니다.</p></li><li><p><strong>openai</strong>를 사용하면 OpenAI 모델과 상호 작용하여 요약이나 자연어 응답을 생성할 수 있습니다.</p></li><li><p><a href="https://zod.dev/"><strong>zod</strong></a>는각 도구의 입력 및 출력 데이터에 대해 구조화된 스키마를 정의하고 검증하는 것을 돕습니다.</p></li></ul><p><code>ts-node</code>, <code>@types/node</code>, <code>typescript</code> 는 개발 중에 코드의 타입을 지정하고 스크립트를 컴파일하는 데 사용됩니다.</p><h4>데이터셋 설정</h4><p>Claude Desktop이 MCP 서버를 통해 쿼리할 수 있는 데이터를 제공하기 위해, 가상의 <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/dataset.json">내부 지식 기반 데이터 세트</a>를 사용하겠습니다. 이 데이터 세트의 문서는 다음과 같습니다.</p>{
    "id": 5,
    "title": "Logging Standards for Microservices",
    "content": "Consistent logging across microservices helps with debugging and tracing. Use structured JSON logs and include request IDs and timestamps. Avoid logging sensitive information. Centralize logs in Elasticsearch or a similar system. Configure log rotation to prevent storage issues and ensure logs are searchable for at least 30 days.",
    "tags": ["logging", "microservices", "standards"]
}<p>데이터를 수집하기 위해, Elasticsearch에 인덱스를 생성하고 데이터 세트를 로드하는 스크립트를 준비했습니다. <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/setup.ts">여기서</a> 확인하실 수 있습니다.</p><h4>MCP 서버</h4><p><a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/index.ts"><code>index.ts</code></a>(이)라는 이름의 파일을 생성하고, 의존성을 가져오고 환경 변수를 처리하기 위해 다음 코드를 추가하세요.</p>// index.ts
import { z } from "zod";
import { Client } from "@elastic/elasticsearch";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import OpenAI from "openai";

const ELASTICSEARCH_ENDPOINT =
  process.env.ELASTICSEARCH_ENDPOINT ?? "http://localhost:9200";
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY ?? "";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "";
const INDEX = "documents";<p>또한, Elasticsearch와 OpenAI 호출을 처리할 클라이언트들을 초기화해 보겠습니다.</p>const openai = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

const _client = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
});<p>구현을 더 견고하게 만들고 입력 및 출력 데이터의 구조를 보장하기 위해, <a href="https://zod.dev/"><code>zod</code></a>(을)를 사용하여 스키마를 정의하겠습니다. 이를 통해 런타임에 데이터를 검증하고, 오류를 조기에 발견하며, 도구의 응답을 프로그램 방식으로 더 쉽게 처리할 수 있습니다.</p>const DocumentSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
});

const SearchResultSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
  score: z.number(),
});

type Document = z.infer&lt;typeof DocumentSchema&gt;;
type SearchResult = z.infer&lt;typeof SearchResultSchema&gt;;<p>구조화된 출력을 자세히 알아보려면 <a href="https://www.elastic.co/search-labs/blog/structured-outputs-elasticsearch-guide">여기</a>를 참조하세요.</p><p>이제 MCP 서버를 초기화해 보겠습니다.</p>const server = new McpServer({
  name: "Elasticsearch RAG MCP",
  description:
    "A RAG server using Elasticsearch. Provides tools for document search, result summarization, and source citation.",
  version: "1.0.0",
});<h4>MCP 도구 정의</h4><p>모든 구성이 완료되면, MCP 서버가 외부에 제공할 도구를 작성하기 시작할 수 있습니다. 이 서버는 두 가지 도구를 외부에 제공합니다.</p><ul><li><p><strong><code>search_docs</code></strong><strong>: </strong>전체 텍스트 검색을 사용하여 Elasticsearch에서 문서를 검색합니다.</p></li><li><p><strong><code>summarize_and_cite</code></strong><strong>:</strong> 사용자의 질문에 답하기 위해, 이전에 검색된 문서들로부터 정보를 요약하고 종합합니다. 이 도구는 또한 출처 문서를 참조하는 인용 정보를 추가합니다.</p></li></ul><p>이 도구들은 함께 작동하여 간단한 '검색 후 요약' 워크플로우를 형성합니다. 하나의 도구가 관련 문서를 가져오면, 다른 도구가 해당 문서들을 바탕으로 인용구가 포함된 요약 응답을 생성하는 방식입니다.</p><h4>도구 응답 형식</h4><p>각 도구는 임의의 입력 매개변수를 허용할 수 있지만, 다음과 같은 구조로 응답해야 합니다.</p><ul><li><p><strong>내용:</strong> 비정형 형식으로 된 도구의 응답입니다. 이 필드는 일반적으로 텍스트, 이미지, 오디오, 링크 또는 임베딩을 반환하는 데 사용됩니다. 이 애플리케이션의 경우 도구가 생성한 정보를 포함한 서식 있는 텍스트를 반환하는 데 사용됩니다.</p></li><li><p><strong>structuredContent: </strong>각 도구의 결과를 구조화된 형식으로 제공하기 위해 사용되는 선택적 반환 값입니다. 이는 프로그램 방식의 처리에 유용합니다. 비록 이 MCP 서버에서는 사용되지 않지만, 다른 도구를 개발하거나 결과를 프로그램 방식으로 처리하고자 할 때 유용하게 활용될 수 있습니다.</p></li></ul><p>그 구조를 염두에 두고, 각 도구에 대해 자세히 살펴보겠습니다.</p><h4>Search_docs 도구</h4><p>이 도구는 사용자의 쿼리를 기반으로 가장 관련성 높은 문서들을 검색하기 위해 Elasticsearch 인덱스에서 <a href="https://www.elastic.co/docs/solutions/search/full-text">전체 텍스트 검색</a>을 수행합니다. 또한 주요 일치 항목을 강조하고, 연관성 점수와 함께 빠른 개요를 제공합니다.</p>server.registerTool(
  "search_docs",
  {
    title: "Search Documents",
    description:
      "Search for documents in Elasticsearch using full-text search. Returns the most relevant documents with their content, title, tags, and relevance score.",
    inputSchema: {
      query: z
        .string()
        .describe("The search query terms to find relevant documents"),
      max_results: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of results to return"),
    },
    outputSchema: {
      results: z.array(SearchResultSchema),
      total: z.number(),
    },
  },
  async ({ query, max_results }) =&gt; {
    if (!query) {
      return {
        content: [
          {
            type: "text",
            text: "Query parameter is required",
          },
        ],
        isError: true,
      };
    }

    try {
      const response = await _client.search({
        index: INDEX,
        size: max_results,
        query: {
          bool: {
            must: [
              {
                multi_match: {
                  query: query,
                  fields: ["title^2", "content", "tags"],
                  fuzziness: "AUTO",
                },
              },
            ],
            should: [
              {
                match_phrase: {
                  title: {
                    query: query,
                    boost: 2,
                  },
                },
              },
            ],
          },
        },
        highlight: {
          fields: {
            title: {},
            content: {},
          },
        },
      });

      const results: SearchResult[] = response.hits.hits.map((hit: any) =&gt; {
        const source = hit._source as Document;

        return {
          id: source.id,
          title: source.title,
          content: source.content,
          tags: source.tags,
          score: hit._score ?? 0,
        };
      });

      const contentText = results
        .map(
          (r, i) =&gt;
            `[${i + 1}] ${r.title} (score: ${r.score.toFixed(
              2,
            )})\n${r.content.substring(0, 200)}...`,
        )
        .join("\n\n");

      const totalHits =
        typeof response.hits.total === "number"
          ? response.hits.total
          : (response.hits.total?.value ?? 0);

      return {
        content: [
          {
            type: "text",
            text: `Found ${results.length} relevant documents:\n\n${contentText}`,
          },
        ],
        structuredContent: {
          results: results,
          total: totalHits,
        },
      };
    } catch (error: any) {
      console.log("Error during search:", error);

      return {
        content: [
          {
            type: "text",
            text: `Error searching documents: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);<p><em>We configure </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-fuzzy-query"><em><code>fuzziness</code></em></a><em><code>: “AUTO”</code></em><em> to have a variable typo tolerance based on the length of the token that’s being analyzed. We also set </em><em><code>title^2</code></em><em> to increase the score of the documents where the match happens on the title 필드.</em></p><h4>summarize_and_cite 도구</h4><p>이 도구는 이전 검색에서 가져온 문서들을 바탕으로 요약을 생성합니다. 사용자의 질문에 답하기 위해 OpenAI의 <code>gpt-4o-mini</code> 모델을 사용하여 가장 관련성 높은 정보를 종합하며, 검색 결과에서 직접 도출된 응답을 제공합니다. 요약과 더불어, 사용된 출처 문서들에 대한 인용 메타데이터도 함께 반환합니다.</p>server.registerTool(
  "summarize_and_cite",
  {
    title: "Summarize and Cite",
    description:
      "Summarize the provided search results to answer a question and return citation metadata for the sources used.",
    inputSchema: {
      results: z
        .array(SearchResultSchema)
        .describe("Array of search results from search_docs"),
      question: z.string().describe("The question to answer"),
      max_length: z
        .number()
        .optional()
        .default(500)
        .describe("Maximum length of the summary in characters"),
      max_docs: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of documents to include in the context"),
    },
    outputSchema: {
      summary: z.string(),
      sources_used: z.number(),
      citations: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
          tags: z.array(z.string()),
          relevance_score: z.number(),
        })
      ),
    },
  },
  async ({ results, question, max_length, max_docs }) =&gt; {
    if (!results || results.length === 0 || !question) {
      return {
        content: [
          {
            type: "text",
            text: "Both results and question parameters are required, and results must not be empty",
          },
        ],
        isError: true,
      };
    }

    try {
      const used = results.slice(0, max_docs);

      const context = used
        .map(
          (r: SearchResult, i: number) =&gt;
            `[Document ${i + 1}: ${r.title}]\\n${r.content}`
        )
        .join("\n\n---\n\n");

      // Generate summary with OpenAI
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
          {
            role: "system",
            content:
              "You are a helpful assistant that answers questions based on provided documents. Synthesize information from the documents to answer the user's question accurately and concisely. If the documents don't contain relevant information, say so.",
          },
          {
            role: "user",
            content: `Question: ${question}\\n\\nRelevant Documents:\\n${context}`,
          },
        ],
        max_tokens: Math.min(Math.ceil(max_length / 4), 1000),
        temperature: 0.3,
      });

      const summaryText =
        completion.choices[0]?.message?.content ?? "No summary generated.";

      const citations = used.map((r: SearchResult) =&gt; ({
        id: r.id,
        title: r.title,
        tags: r.tags,
        relevance_score: r.score,
      }));

      const citationText = citations
        .map(
          (c: any, i: number) =&gt;
            `[${i + 1}] ID: ${c.id}, Title: "${c.title}", Tags: ${c.tags.join(
              ", ",
            )}, Score: ${c.relevance_score.toFixed(2)}`,
        )
        .join("\n");

      const combinedText = `Summary:\\n\\n${summaryText}\\n\\nSources used (${citations.length}):\\n\\n${citationText}`;

      return {
        content: [
          {
            type: "text",
            text: combinedText,
          },
        ],
        structuredContent: {
          summary: summaryText,
          sources_used: citations.length,
          citations: citations,
        },
      };
    } catch (error: any) {
      return {
        content: [
          {
            type: "text",
            text: `Error generating summary and citations: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);<p>마지막으로, <a href="https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#stdio">stdio</a>를 사용하여 서버를 시작해야 합니다. 이는 MCP 클라이언트가 서버의 표준 입력과 표준 출력 스트림을 읽고 씀으로써 통신하게 된다는 것을 의미합니다. stdio는 가장 단순한 전송 옵션이며, 클라이언트를 통해 하위 프로세스로 실행되는 로컬 MCP 서버에 적합합니다. 파일 끝에 다음 코드를 추가합니다.</p>const transport = new StdioServerTransport();
server.connect(transport);<p>이제 다음 명령어를 사용하여 프로젝트를 컴파일하십시오:</p>npx tsc index.ts --target ES2022 --module node16 --moduleResolution node16 --outDir ./dist --strict --esModuleInterop<p>이렇게 하면 <code>dist</code> 폴더가 생성되고, 그 안에 <code>index.js</code> 파일이 생성됩니다.</p><h3>MCP 서버를 Claude Desktop에 로드</h3><p>Claude Desktop에서 MCP 서버를 구성하려면 <a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers">이 가이드</a>를 따르세요. Claude 구성 파일에서 다음 값들을 설정해야 합니다.</p>{
  "mcpServers": {
    "elasticsearch-rag-mcp": {
      "command": "node",
      "args": [   "/Users/user-name/app-dir/dist/index.js"
      ],
      "env": {
        "ELASTICSEARCH_ENDPOINT": "your-endpoint-here",
        "ELASTICSEARCH_API_KEY": "your-api-key-here",
        "OPENAI_API_KEY": "your-openai-key-here"
      }
    }
  }
}<p><code>args</code> 값은 <code>dist</code> 폴더 안에 있는 컴파일된 파일을 가리켜야 합니다. 또한 코드에 정의된 것과 똑같은 이름으로 구성 파일 내에 환경 변수를 설정해야 합니다.</p><h3>테스트해 보기</h3><p>각 도구를 실행하기 전에, <strong>검색 및 도구</strong>를 클릭하여 도구들이 활성화되어 있는지 확인하세요. 여기에서 각 도구를 개별적으로 활성화하거나 비활성화할 수도 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt395a7337021f9820/6a170c1c67045bb74d45c228/172981c2a54adabc70d5819013c3007670935605-1999x1002.png" alt="‘좋은 오후입니다, 제프. 오늘은 어떻게 도와드릴까요?’ 라는 문구가 적힌 Claude 4.5 Sonnet 페이지입니다." /><p>마지막으로 Claude Desktop 채팅에서 MCP 서버를 테스트하고 질문을 시작하십시오:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4ac458dc0206271/6a170c1e66c4f91328f8c072/03654c0f8c53c714f801fba8b25747071179209b-1999x1353.png" alt="인증 방식 및 역할 기반 액세스 제어에 관한 문서를 찾는 Claude Desktop 채팅창의 사용자 검색 요청과 그에 대한 Claude의 응답입니다." /><p>'<strong>인증 방법 및 역할 기반 액세스 제어에 관한 문서를 검색해 줘</strong>'라는 질문에 대해, <code>search_docs</code> 도구가 실행되어 다음과 같은 결과를 반환합니다.</p>Most Relevant Documents:
Access Control and Role Management (highest relevance) - This document covers role-based access control (RBAC) principles, including ensuring users only have necessary permissions, regular auditing of user roles, revoking inactive accounts, and implementing just-in-time access for sensitive operations.
User Authentication with OAuth 2.0 - This document explains OAuth 2.0 authentication, which enables secure delegated access without credential sharing. It covers configuring identity providers, token management with limited scope and lifetime, and secure storage of refresh tokens.
Container Security Guidelines - While primarily about container security, this document touches on access control aspects like running containers as non-root users and avoiding embedded credentials.
Incident Response Playbook - This mentions role assignment during incidents (incident commander, communications lead, etc.), which relates to access control in emergency scenarios.
Logging Standards for Microservices - This document includes guidance on avoiding logging sensitive information, which is relevant to authentication security.<p>응답 내용은 다음과 같습니다. '좋습니다! 인증 방식 및 역할 기반 액세스 제어에 관한 관련 문서 5개를 찾았습니다. 검색된 내용은 다음과 같습니다.'</p><p>도구 호출은 응답 페이로드의 일부로 소스 문서들을 반환하며, 이 문서들은 나중에 인용구를 생성하는 데 사용됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbaf48a9468770ce2/6a170c21509168bffee1bb14/25ff4c7e9563d99752f95540dafdc7fd211a66e3-800x530.gif" alt="인증 방식 및 역할 기반 액세스 제어에 관한 5개의 관련 문서를 포함하는 스크롤형 응답의 Claude 4.5 Sonnet 페이지입니다." /><p>한 번의 상호 작용 내에서 여러 도구를 연결하여 사용할 수도 있습니다. 이 경우, Claude Desktop은 사용자의 질문을 분석한 뒤, 관련 문서를 가져오기 위해 먼저 <code>search_docs</code>(을)를 호출하고, 그 결과를 <code>summarize_and_cite</code>에 전달하여 최종 답변을 생성해야 한다고 판단합니다. 이 모든 과정은 별도의 사용자 프롬프트 없이 이루어집니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta46ff45274e64192/6a170c230c4857a91501aac1/e6a8a46acb4236e77058f18bcd2f0737b5882c05-1999x1101.png" alt="'제프가 돌아왔네요.'라는 문구와 함께, '우리 시스템 전반의 인증 및 액세스 제어를 개선하기 위한 핵심 권장 사항이 뭐야? 참고 문헌 포함해 줘.'라는 사용자의 새로운 질문이 표시된 Claude Desktop 채팅 화면입니다." /><p>이 경우, '<strong>우리 시스템 전반의 인증 및 액세스 제어를 개선하기 위한 핵심 권장 사항이 뭐야? 참고 문헌 포함해 줘.</strong>'라는 쿼리에 대한 답변입니다. 다음과 같은 결과를 얻었습니다.</p>Based on the documentation, here are the main recommendations to improve authentication and access control across your systems:

Key Recommendations

1. Implement Role-Based Access Control (RBAC)
Ensure users have only the permissions necessary for their job functions. This principle of least privilege minimizes security risks by limiting access to what's truly needed. [1]

2. Regular Access Audits
Conduct regular audits of user roles and promptly revoke access for inactive accounts. This helps prevent unauthorized access from dormant or compromised accounts. [1]

3. Just-in-Time (JIT) Access
Implement just-in-time access for sensitive operations, where permissions are granted only when needed and for a limited duration. This reduces the attack surface by minimizing standing privileges. [1]

4. OAuth 2.0 for Secure Authentication
Use OAuth 2.0 to enable secure delegated access without sharing user credentials. This protocol allows third-party applications to access resources on behalf of users without exposing passwords. [2]

5. Token Security and Management
Configure your identity provider to issue tokens with limited scope and lifetime

Store refresh tokens securely
Validate access tokens consistently to maintain security [2]
References

Access Control and Role Management (Tags: security, access-control)
User Authentication with OAuth 2.0 (Tags: authentication, oauth)
These recommendations work together to create a defense-in-depth approach, where multiple security layers protect your systems from unauthorized access.<p>이전 단계와 마찬가지로, 이 질문에 대한 각 도구의 응답을 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8f633c518e708a99/6a170c25ab7f082991db9ed6/cb606d356b2f7d5e4878a5eff71bc881869ac0ee-800x585.gif" alt="'우리 시스템 전반의 인증 및 액세스 제어를 개선하기 위한 핵심 권장 사항이 뭐야? 참고 문헌 포함해 줘'라는 질문에 대한 각 도구의 응답을 포함하는 스크롤형 텍스트의 Claude Desktop 채팅 페이지입니다." /><p><em>참고: 각 도구 사용 승인 여부를 묻는 하위 메뉴가 나타나면 </em><em><strong>항상 허용</strong></em><em> 또는 </em><em><strong>한 번 허용</strong></em><em>을 선택하십시오.</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6627ee0bff1862df/6a170c266f7f040f6f91488c/aea942ba9b0037526ea215bec65690f1a5c3099c-1522x250.png" alt="사용자가 선택할 수 있는 '항상 허용' 및 '한 번만 허용' 옵션이 표시된 Claude Desktop 화면입니다." /><h2>결론</h2><p>MCP 서버는 로컬 및 원격 애플리케이션 모두를 위한 LLM 도구 표준화를 향한 중요한 진전을 의미합니다. 완전한 호환성을 구현하기 위해 아직 작업 중이지만, 이를 향해 빠르게 나아가고 있습니다.</p><p>이 글에서 Elasticsearch를 LLM 기반 애플리케이션에 연결하는 사용자 지정 MCP 서버를 TypeScript로 구축하는 방법을 배웠습니다. 서버는 두 가지 도구를 제공합니다. Query DSL을 사용하여 관련 문서를 가져오는 <code>search_docs</code>(와)과, OpenAI 모델을 통해 인용구가 포함된 요약을 생성하고 Claude Desktop을 클라이언트 UI로 사용하는 <code>summarize_and_cite</code>입니다.</p><p>다양한 클라이언트와 서버 제공 업체 간의 호환성 미래는 매우 유망해 보입니다. 다음 단계로는 에이전트에 더 많은 기능과 유연성을 추가하는 과정이 포함됩니다. 검색 템플릿을 사용하여 쿼리를 매개변수화함으로써 정확도와 유연성을 얻는 방법에 대한 실용적인 <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">글</a>을 읽어 보실 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5600198cb47666a5/6a170c28509168ce3ae1bb18/0bb24c05fff391f42070c2883182ea6fe9cb9680-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 추론 API와 Hugging Face 모델 함께 사용하기]]></title>
    <description><![CDATA[추론 엔드포인트를 사용하여 Elasticsearch를 Hugging Face 모델에 연결하고, 시맨틱 검색 및 채팅 완성을 갖춘 다국어 블로그 추천 시스템을 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>최근 업데이트에서 Elasticsearch는 <a href="https://endpoints.huggingface.co/">Hugging Face Inference Service</a>에 호스팅된 모델과 연결할 수 있는 네이티브 통합 기능을 도입했습니다. 이 게시물에서는 대규모 언어 모델(LLM)을 사용하여 간단한 API 호출을 통해 이 통합을 구성하고 추론을 수행하는 방법을 살펴보겠습니다. 리소스 사용량과 답변 품질 간의 균형이 잘 잡힌 경량 범용 모델인 <a href="https://huggingface.co/HuggingFaceTB/SmolLM3-3B">SmolLM3-3B</a>를 사용하겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9094997548bd70f8/6a170d6a839dfa0ad6dcff54/7ddadf1976421a860a7d62087239adb9150d808b-1999x1388.png" alt="산점도는 모델 크기(매개변수 십억 단위)를 x-축으로, 승률(백분율)을 y-축으로 하여 여러 소규모 생성형 언어 모델을 나타냅니다. SmolLM3-3B는 비슷한 크기의 다른 모델보다 높은 승률로 효율성 추세의 최상위권에 자리하고 있습니다." /><h2>필수 구성 요소</h2><ul><li><p><strong>Elasticsearch 9.3 또는 Elastic Cloud Serverless: </strong> <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">이 지침</a>을 따라 클라우드 배포를 생성하거나, <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart#local-dev-quick-start"><code>start-local</code></a> 퀵스타트를 사용할 수 있습니다.</p></li><li><p><strong>Python 3.12: </strong>Python을 <a href="https://www.python.org/">여기</a>에서 다운로드하세요.</p></li><li><p><strong>Hugging Face </strong><a href="https://huggingface.co/docs/hub/en/security-tokens">액세스 토큰</a>.</p></li></ul><h2>Hugging Face 추론 엔드포인트를 사용하여 채팅 완료 수행하기</h2><p>먼저, Elasticsearch를 Hugging Face <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">엔드포인트</a>에 연결하여 블로그 게시물 모음에서 AI 기반 추천을 생성하는 실용적인 예제를 구축할 것입니다. 앱 지식 기반 시스템을 위해, 회사 블로그 기사의 데이터 세트를 사용할 것입니다. 이 데이터 세트에는 귀중하지만 종종 탐색하기 어려운 정보가 포함되어 있습니다.</p><p>이 엔드포인트를 사용하면 <a href="https://www.elastic.co/docs/solutions/search/semantic-search">시맨틱 검색</a>을 통해 주어진 쿼리에 가장 적합한 문서를 검색할 수 있으며, Hugging Face LLM이 해당 결과를 바탕으로 문맥에 맞는 짧은 추천 결과를 생성합니다.</p><p>구축할 정보 흐름에 대한 개괄적인 내용을 살펴보겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf217b7b7db4e1e6c/6a170d6ca929cf8022ae0a3b/1dfbc2323438feaaa42e13ab242dd1f7166f74aa-1200x676.png" alt="시맨틱 검색 결과를 추론 엔드포인트에 공급하여 문서 추천을 반환하는 Elasticsearch 인덱스를 보여주는 흐름도." /><p>이 기사에서는 <strong>SmolLM3-3B</strong>의 컴팩트한 크기와 강력한 다국어 추론 및 도구 호출 기능을 결합하는 능력을 테스트할 것입니다. 검색 쿼리를 기반으로 일치하는 모든 콘텐츠(영어 및 스페인어)를 LLM으로 전송하고, 검색 쿼리와 결과를 바탕으로 맞춤형 설명이 포함된 추천 기사 목록을 생성합니다.</p><p>AI 추천 생성 시스템이 포함된 기사 사이트의 UI는 다음과 같을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20e69b9a06fecd65/6a170d6e839dfa6f97dcff58/8d3b86b212f28ff279f2da67a33e6134039f0e4e-1999x949.png" alt="AI 추천 생성 시스템이 포함된 기사 사이트의 UI로, 세 가지 예시가 나열되어 있으며, 텍스트는 영어로, 제목은 영어 또는 스페인어로 되어 있습니다." /><p>이 애플리케이션의 전체 구현은 연결된 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/notebook.ipynb">노트북</a>에서 확인하실 수 있습니다.</p><h3>Elasticsearch 추론 엔드포인트 구성하기</h3><p>Elasticsearch <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">Hugging Face 추론 엔드포인트</a>를 사용하려면 Hugging Face API 키와 실행 중인 Hugging Face 엔드포인트 URL라는 두 가지 중요한 요소가 필요합니다. 다음과 같이 보여야 합니다.</p>PUT _inference/chat_completions/hugging-face-smollm3-3b
{
    "service": "hugging_face",
    "service_settings": {
        "api_key": "hugging-face-access-token", 
        "url": "url-endpoint" 
    }
}<p>Elasticsearch의 Hugging Face 추론 엔드포인트는 <code>text_embedding</code>, <code>completion</code>, <code>chat_completion</code>, <code>rerank</code> 등 다양한 작업 유형을 지원합니다. 이 블로그 글에서는 검색 결과와 시스템 프롬프트를 바탕으로 대화형 추천을 생성하는 모델이 필요하기 때문에 <code>chat_completion</code>를 사용합니다. 이 엔드포인트를 통해 Elasticsearch API를 사용하여 Elasticsearch에서 직접 채팅 완료를 간단하게 수행할 수 있습니다.</p>POST _inference/chat_completion/hugging-face-smollm3-3b/_stream
{
  "messages": [
      { "role": "user", "content": "&lt;user prompt&gt;" }
  ]
}<p>이것은 애플리케이션의 핵심 역할을 하며, 프롬프트와 모델을 통과할 검색 결과를 받습니다. 이론을 다뤘으니 이제 애플리케이션 구현을 시작해 보겠습니다.</p><h4>Hugging Face에서 추론 엔드포인트 설정하기</h4><p>Hugging Face 모델을 배포하기 위해 <a href="https://huggingface.co/inference-endpoints/dedicated">Hugging Face 원클릭 배포</a>를 사용할 것입니다. 이는 모델 엔드포인트를 배포하기 위한 쉽고 빠른 서비스입니다. 이 서비스는 유료 서비스이므로 이용 시 추가 비용이 발생할 수 있습니다. 이 단계에서는 기사 추천을 생성하는 데 사용될 모델 인스턴스를 생성합니다.</p><p>원클릭 카탈로그에서 모델을 선택할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7bdfa43d6766324/6a170d6fb339d59e5476a039/b816e9fba1fe172687bf58f5143fb1f838c1077f-549x331.png" alt="'smoll3'로 필터링된 모델 카탈로그의 인터페이스 화면으로, 텍스트 생성, vLLM, GPU 1× NVIDIA L4, 가격 $0.8의 'smollm3-3b'라는 모델 1개와 모든 Hugging Face 모델로 검색을 확장할 것을 제안하는 메모가 표시되어 있습니다." /><p><strong>SmolLM3-3B</strong> 모델을 선택합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb0a2e6ffd7deb20/6a170d710c48574b7401aafc/610d3aba0429f3666c2df3616d513eb6a4397c0c-502x478.png" alt="SmolLM3‑3B 모델의 엔드포인트를 생성하기 위한 인터페이스로, 모델 이름, &quot;Hugging Face에서 확인됨&quot; 메모, 엔드포인트 이름 필드, 실행 중인 복제본당 시간당 $0.80의 비용, cURL 옵션 및 &quot;엔드포인트 생성&quot; 버튼을 표시합니다." /><p>여기에서 Hugging Face 엔드포인트 URL을 가져옵니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25714021711ed6ff/6a170d72c1e8a54853f88336/025094ddb2cfbd1f0f216a5ec4e119b0f4fa2c42-646x328.png" alt="&quot;smollm3‑3b‑pnz&quot;라는 이름의 Hugging Face 추론 엔드포인트의 대시보드 화면으로, 녹색으로 표시된 실행 중 상태, 활성 복제본 1개, 지난 1시간 동안 요청 0건, 탐색 탭 및 표시된 엔드포인트 URL이 나타납니다." /><p>Elasticsearch <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">Hugging Face 추론 엔드포인트 설명서</a>에서 언급했듯이, 텍스트 생성에는 OpenAI API와 호환되는 모델이 필요합니다. 그러므로 Hugging Face 엔드포인트 URL에 <code>/v1/chat/completions</code> 하위 경로를 추가해야 합니다. 최종 결과는 다음과 같습니다.</p>https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions<p>이렇게 준비되면 Python 노트북에서 코딩을 시작할 수 있습니다.</p><h4>Hugging Face API 키 생성하기</h4><p><a href="https://huggingface.co/join">Hugging Face 계정</a>을 만들고 <a href="https://huggingface.co/docs/hub/en/security-tokens#user-access-tokens">다음 안내</a>에 따라 API 토큰을 받습니다. <em>세분화</em>(특정 리소스에만 액세스를 제공하므로 프로덕션에 권장), 읽기(<em>읽기</em> 전용 액세스), <em>쓰기</em>(읽기 및 쓰기 액세스용)의 세 가지 토큰 유형 중 선택할 수 있습니다. 이 튜토리얼에서는 추론 엔드포인트만 호출하면 되므로 읽기 토큰으로 충분합니다. 다음 단계를 위해 이 키를 저장해 두세요.</p><h4>Elasticsearch 추론 엔드포인트 설정</h4><p>먼저, Elasticsearch Python 클라이언트를 선언해 보겠습니다.</p>os.environ["ELASTICSEARCH_API_KEY"] = "your-elasticsearch-api-key"
os.environ["ELASTICSEARCH_URL"] = "https://xxxx.us-central1.gcp.cloud.es.io:443"

es_client = Elasticsearch(
    os.environ["ELASTICSEARCH_URL"], api_key=os.environ["ELASTICSEARCH_API_KEY"]
)<p>다음으로, Hugging Face 모델을 사용하는 Elasticsearch 추론 엔드포인트를 생성해 보겠습니다. 이 엔드포인트를 통해 블로그 게시물과 모델에 전달된 프롬프트를 기반으로 응답을 생성할 수 있습니다.</p>INFERENCE_ENDPOINT_ID = "smollm3-3b-pnz"

os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"] = (
 "https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions"
)
os.environ["HUGGING_FACE_API_KEY"] = "hf_xxxxx"

resp = es_client.inference.put(
        task_type="chat_completion",
        inference_id=INFERENCE_ENDPOINT_ID,
        body={
            "service": "hugging_face",
            "service_settings": {
                "api_key": os.environ["HUGGING_FACE_API_KEY"],
                "url": os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"],
            },
        },
    )<h3>데이터 세트</h3><p>데이터 세트에는 전체 워크플로우에서 사용되는 다국어 콘텐츠 세트를 나타내는 쿼리될 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/dataset.json">블로그 게시물</a>이 포함되어 있습니다.</p>// Articles dataset document example: 
{
    "id": "6",
    "title": "Complete guide to the new API: Endpoints and examples",
    "author": "Tomas Hernandez",
    "date": "2025-11-06",
    "category": "tutorial",
    "content": "This guide describes in detail all endpoints of the new API v2. It includes code examples in Python, JavaScript, and cURL for each endpoint. We cover authentication, resource creation, queries, updates, and deletion. We also explain error handling, rate limiting, and best practices. Complete documentation is available on our developer portal."
  }<h4>Elasticsearch 매핑</h4><p>데이터 세트가 정의되었으므로, 이제 블로그 게시물 구조에 적합한 데이터 스키마를 생성해야 합니다. 다음 <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">인덱스 매핑</a>은 Elasticsearch에 데이터를 저장하는 데 사용됩니다.</p>INDEX_NAME = "blog-posts"

mapping = {
    "mappings": {
        "properties": {
            "id": {"type": "keyword"},
            "title": {
                "type": "object",
                "properties": {
                    "original": {
                        "type": "text",
                        "copy_to": "semantic_field",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                    "translated_title": {
                        "type": "text",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                },
            },
            "author": {"type": "keyword", "copy_to": "semantic_field"},
            "category": {"type": "keyword", "copy_to": "semantic_field"},
            "content": {"type": "text", "copy_to": "semantic_field"},
            "date": {"type": "date"},
            "semantic_field": {"type": "semantic_text"},
        }
    }
}


es_client.indices.create(index=INDEX_NAME, body=mapping)<p>여기에서 데이터가 어떻게 구성되어 있는지 더욱 명확하게 확인할 수 있습니다. 자연어를 기반으로 결과를 검색하는 데 시맨틱 검색을 사용하고, <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_text</code></a> 필드로 복사합니다. 또한 <code>title</code> 필드에는 두 개의 하위 필드가 있습니다. <code>original</code> 하위 필드는 기사의 원래 언어에 따라 영어 또는 스페인어로 제목을 저장하며, <code>translated_title</code> 하위 필드는 스페인어 기사에만 존재하고 원래 제목의 영어 번역을 포함합니다.</p><h3>데이터 수집</h3><p>다음 코드 스니펫은 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/bulk_examples">벌크 API</a>를 사용하여 블로그 게시물 데이터 세트를 Elasticsearch로 수집합니다.</p>def build_data(json_file, index_name):
    with open(json_file, "r") as f:
        data = json.load(f)

    for doc in data:
        action = {"_index": index_name, "_source": doc}
        yield action


try:
    success, failed = helpers.bulk(
        es_client,
        build_data("dataset.json", INDEX_NAME),
    )
    print(f"{success} documents indexed successfully")

    if failed:
        print(f"Errors: {failed}")
except Exception as e:
    print(f"Error: {str(e)}")<p>이제 기사들이 Elasticsearch에 수집되었으니, <code>semantic_text</code> 필드에 대해 검색할 수 있는 함수를 만들어야 합니다.</p>def perform_semantic_search(query_text, index_name=INDEX_NAME, size=5):
    try:
        query = {
            "query": {
                "match": {
                    "semantic_field": {
                        "query": query_text,
                    }
                }
            },
            "size": size,
        }

        response = es_client.search(index=index_name, body=query)
        hits = response["hits"]["hits"]

        return hits
    except Exception as e:
        print(f"Semantic search error: {str(e)}")
        return []<p>추론 엔드포인트를 호출하는 함수도 필요합니다. 이 경우 <strong><code>chat_completion</code></strong>작업 유형을 사용하여 엔드포인트를 호출하여 스트리밍 응답을 받습니다.</p>def stream_chat_completion(messages: list, inference_id: str = INFERENCE_ENDPOINT_ID):
    url = f"{ELASTICSEARCH_URL}/_inference/chat_completion/{inference_id}/_stream"
    payload = {"messages": messages}
    headers = {
        "Authorization": f"ApiKey {ELASTICSEARCH_API_KEY}",
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(url, json=payload, headers=headers, stream=True)
        response.raise_for_status()

        for line in response.iter_lines(decode_unicode=True):
            if line:
                line = line.strip()

                if line.startswith("event:"):
                    continue

                if line.startswith("data: "):
                    data_content = line[6:]

                    if not data_content.strip() or data_content.strip() == "[DONE]":
                        continue

                    try:
                        chunk_data = json.loads(data_content)

                        if "choices" in chunk_data and len(chunk_data["choices"]) &gt; 0:
                            choice = chunk_data["choices"][0]
                            if "delta" in choice and "content" in choice["delta"]:
                                content = choice["delta"]["content"]
                                if content:
                                    yield content

                    except json.JSONDecodeError as json_err:
                        print(f"\nJSON decode error: {json_err}")
                        print(f"Problematic data: {data_content}")
                        continue

    except requests.exceptions.RequestException as e:
        yield f"Error: {str(e)}"<p>이제 의미 탐색 함수와 <code>chat_completions</code> 추론 엔드포인트, 추천 엔드포인트를 호출하여 카드에 할당될 데이터를 생성할 수 있습니다.</p>def recommend_articles(search_query, index_name=INDEX_NAME, max_articles=5):
    print(f"\n{'='*80}")
    print(f"🔍 Search Query: {search_query}")
    print(f"{'='*80}\n")

    articles = perform_semantic_search(search_query, index_name, size=max_articles)

    if not articles:
        print("❌ No relevant articles found.")
        return None, None

    print(f"✅ Found {len(articles)} relevant articles\n")

    # Build context with found articles
    context = "Available blog articles:\n\n"
    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)
        context += f"Article {i}:\n"
        context += f"- Title: {source.get('title', 'N/A')}\n"
        context += f"- Author: {source.get('author', 'N/A')}\n"
        context += f"- Category: {source.get('category', 'N/A')}\n"
        context += f"- Date: {source.get('date', 'N/A')}\n"
        context += f"- Content: {source.get('content', 'N/A')}\n\n"

    system_prompt = """You are an expert content curator that recommends blog articles.

    Write recommendations in a conversational style starting with phrases like:
    - "If you're interested in [topic], this article..."
    - "This post complements your search with..."
    - "For those looking into [topic], this article provides..."


    FORMAT REQUIREMENTS:
    - Return ONLY a JSON array
    - Each element must have EXACTLY these three fields: "article_number", "title", "recommendation"
    - If the original title is in spanish, use the "translated_title" subfield in the "title" field

    Keep each recommendation concise (2-3 sentences max) and focused on VALUE to the reader.

    EXAMPLE OF CORRECT FORMAT:
    [
        {"article_number": 1, "title": "Article title in english", "recommendation": "If you are interested in [topic], this article provides..."},
        {"article_number": 2, "title": "Article title in english", "recommendation": " for those looking into [topic], this article provides..."}
    ]

    Return ONLY the JSON array following this exact structure."""

    user_prompt = f"""Search query: "{search_query}"

    Generate recommendations for the following articles: {context}
    """

    messages = [
        {"role": "system", "content": "/no_think"},
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]

    # LLM generation
    print(f"{'='*80}")
    print("🤖 Generating personalized recommendations...\n")

    full_response = ""

    for chunk in stream_chat_completion(messages):
        print(chunk, end="", flush=True)
        full_response += chunk

    return context, articles, full_response<p>마지막으로, 정보를 추출하여 인쇄할 수 있도록 서식을 지정해야 합니다.</p>def display_recommendation_cards(articles, recommendations_text):
    print("\n" + "=" * 100)
    print("📇 RECOMMENDED ARTICLES".center(100))
    print("=" * 100 + "\n")

    # Parse JSON recommendations - clean tags and extract JSON
    recommendations_list = []
    try:

        # Clean up &lt;think&gt; tags
        cleaned_text = re.sub(
            r"&lt;think&gt;.*?&lt;/think&gt;", "", recommendations_text, flags=re.DOTALL
        )
        # Remove markdown code blocks ( ... ``` or ``` ... ```)
        cleaned_text = re.sub(r"```(?:json)?", "", cleaned_text)
        cleaned_text = cleaned_text.strip()

        parsed = json.loads(cleaned_text)

        # Extract recommendations from list format
        for item in parsed:
            article_number = item.get("article_number")
            title = item.get("title", "")
            rec_text = item.get("recommendation", "")

            if article_number and rec_text:
                recommendations_list.append(
                    {
                        "article_number": article_number,
                        "title": title,
                        "recommendation": rec_text,
                    }
                )
    except json.JSONDecodeError as e:
        print(f"⚠️  Could not parse recommendations as JSON: {e}")
        return

    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)

        # Card border
        print("┌" + "─" * 98 + "┐")

        # Find recommendation and title for this article number
        recommendation = None
        title = None
        for rec in recommendations_list:
            if rec.get("article_number") == i:
                recommendation = rec.get("recommendation")
                title = rec.get("title")
                break

        # Print title
        title_lines = textwrap.wrap(f"📌 {title}", width=94)
        for line in title_lines:
            print(f"│  {line}".ljust(99) + "│")

        # Card border
        print("├" + "─" * 98 + "┤")

        # Print recommendation
        if recommendation:
            recommendation_lines = textwrap.wrap(recommendation, width=94)
            for line in recommendation_lines:
                print(f"│  {line}".ljust(99) + "│")

        # Card bottom
        print("└" + "─" * 98 + "┘")<p>보안 블로그 게시물에 대해 질문하여 이를 테스트해 보겠습니다.</p>search_query = "Security and vulnerabilities"

context, articles, recommendations = recommend_articles(search_query)

print("\nElasticsearch context:\n", context)

# Display visual cards
display_recommendation_cards(articles, recommendations)<p>여기서 워크플로우가 생성한 콘솔의 카드를 볼 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4aa221a08a51aeb3/6a170d7460084be1413c45d6/730d35212594bb3db30447c3ea7e2a92857287b7-1999x1515.png" alt="&quot;추천 기사&quot;라는 제목의 섹션은 인증 시스템 취약점, 마이그레이션 위험, REST API v2 성능 및 인증 개선, 알림 시스템 변경, 그리고 새로운 API에 대한 완전한 가이드를 포함한 다섯 개의 상자형 기사 요약을 보여줍니다." /><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/results.md">이 파일에서</a> 모든 히트와 LLM 응답을 포함한 전체 결과를 확인하실 수 있습니다.</p><p>"보안 및 취약점"과 관련된 기사를 찾고 있습니다. 이 질문은 Elasticsearch에 저장된 문서에 대한 검색 쿼리로 사용됩니다. 검색된 결과는 모델로 전달되어 해당 콘텐츠를 기반으로 추천을 생성합니다. 보시다시피, 이 모델은 독자가 클릭하도록 동기를 부여할 수 있는 매력적인 짧은 텍스트를 훌륭하게 생성했습니다.</p><h2>결론</h2><p>이 예시는 Elasticsearch와 Hugging Face를 결합하여 AI 애플리케이션을 위한 빠르고 효율적인 중앙 집중식 시스템을 만드는 방법을 보여줍니다. 이 접근 방식은 수동 작업을 줄이고 Hugging Face의 광범위한 모델 카탈로그 덕분에 유연성을 제공합니다. SmolLM3-3B를 사용하면 특히 소형 다국어 모델이 시맨틱 검색과 결합될 때 여전히 의미 있는 추론과 콘텐츠 생성을 제공할 수 있음을 보여줍니다. 이러한 도구들을 함께 사용하면 지능형 콘텐츠 분석 및 다국어 애플리케이션 구축을 위한 확장성 있는 효과적인 기반을 마련할 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hugging-face-elasticsearch-inference-api</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hugging-face-elasticsearch-inference-api</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f961af4cb26ec97/6a170d767d8d6790c770e790/1417d6ff033712206c9bd4bcc22074ee3437ce96-1999x1125.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch용 Gemini CLI 확장 프로그램(도구 및 기술 포함)]]></title>
    <description><![CDATA[개발자 및 에이전트 워크플로우에서 Elasticsearch 데이터를 검색, 조회 및 분석할 수 있는 Google Gemini CLI용 Elastic 확장 프로그램을 소개합니다.
]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/elasticsearch">Elasticsearch</a>와 <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>의 모든 기능을 AI 개발 워크플로우에 직접 통합할 수 있는 Google Gemini CLI용 Elastic 확장 기능을 출시합니다. 이 확장 프로그램은 Elasticsearch와 상호 작용하기 위한 최근 개발된 몇 가지 에이전트 스킬도 제공합니다.</p><p>확장 프로그램은 <a href="https://github.com/elastic/gemini-cli-elasticsearch">여기</a>에서 오픈 소스 프로젝트로 제공됩니다.</p><h2>Gemini CLI 소개 및 설치 방법</h2><p><a href="https://geminicli.com/">Gemini CLI</a>는 Google의 Gemini 모델을 명령줄로 직접 가져오는 오픈 소스 AI 에이전트입니다. 개발자가 터미널을 통해 AI와 상호 작용하여 코드 생성, 파일 편집, 셸 명령 실행, 웹 정보 검색 등의 작업을 수행할 수 있습니다.</p><p>Gemini CLI는 일반적인 채팅 인터페이스와 달리 로컬 개발 환경과 통합되어 프로젝트 컨텍스트를 이해하고, 파일을 수정하고, 빌드나 테스트를 실행하고, 워크플로우를 터미널 내에서 직접 자동화할 수 있습니다. 이러한 특징 덕분에 개발자, 사이트 신뢰성 엔지니어(SRE)뿐만 아니라 명령줄 워크플로우를 벗어나지 않고 AI 기반 코딩 및 자동화를 원하는 엔지니어에게 유용합니다.</p><p>Gemini CLI는 여러 패키지 관리자를 사용하여 설치할 수 있습니다. 가장 일반적인 방법은 npm을 사용하는 것입니다:</p>npm install -g @google/gemini-cli<p>다른 설치 옵션을 알고 싶다면 <a href="https://geminicli.com/docs/get-started/installation/">공식 설치 페이지</a>를 참조하세요.</p><p>설치 후, 다음 명령어로 CLI를 실행하세요.</p>gemini<p>그림 1과 같은 화면이 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ff43abe550941b4/6a17072ba29299fc2ad00fa4/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png" alt="Gemini CLI 스크린샷" /><h2>Elasticsearch를 구성합니다</h2><p>Elasticsearch 인스턴스가 실행 중이어야 합니다. 모델 컨텍스트 프로토콜(MCP) 서버를 사용하려면 Kibana 9.3+ 버전이 설치되어 있어야 합니다. 아래 설명된 Elasticsearch 쿼리 언어(ES|QL) 스킬(<code>esql</code>)을 사용하려면 Kibana가 없어도 됩니다.</p><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a>에서 무료 체험을 활성화하거나 <a href="https://github.com/elastic/start-local"><code>start-local</code></a> 스크립트를 사용해 로컬에 설치할 수 있습니다.</p>curl -fsSL https://elastic.co/start-local | sh<p>이 명령을 실행하면 컴퓨터에 Elasticsearch와 Kibana가 설치되고 Gemini CLI 설정에 사용할 API 키가 생성됩니다.</p><p>API 키는 이전 명령의 출력 결과로 표시되며 <strong>.env</strong> 파일로 <strong><code>elastic-start-local</code></strong> 폴더에 저장됩니다.</p><p>온프레미스 Elasticsearch를 사용 중인 경우(예: <code>start-local</code> 사용), Elastic Agent Builder를 MCP와 함께 사용하려면 대규모 언어 모델(LLM)을 연결해야 합니다. <a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/llm-connectors">이 문서 페이지</a>를 읽으면 다양한 옵션을 파악할 수 있습니다.</p><p>Elastic Cloud(또는 서버리스)를 사용하는 경우 이미 사전 구축된 LLM 연결이 제공됩니다.</p><h2>Elasticsearch 확장 프로그램 설치</h2><p>Elasticsearch 확장 프로그램을 Gemini CLI에 설치하려면 다음 명령어를 사용하세요.</p>gemini extensions install https://github.com/elastic/gemini-cli-elasticsearch<p>Gemini를 열고 다음 명령어를 실행하면 확장 프로그램이 성공적으로 설치되었는지 확인할 수 있습니다.</p>/extensions list<p>Elasticsearch 확장 프로그램을 사용할 수 있는지 확인할 수 있습니다.</p><p>MCP 통합을 사용하려면 Elasticsearch 9.3 이상 버전이 설치되어 있어야 합니다. <a href="https://www.elastic.co/kibana">Kibana</a>에서 가져온 MCP 서버 URL이 필요합니다.</p><ul><li><p>에이전트에서 MCP 서버 URL 가져오기 &gt; 모든 도구 보기 &gt; MCP 관리 &gt; MCP 서버 URL 복사</p></li><li><p>표시되는 URL: https://your-kibana-instance/api/agent_builder/mcp</p></li></ul><p>Elasticsearch 엔드포인트 URL이 필요합니다. 일반적으로 이것은 Kibana Elasticsearch 페이지 상단에 보고됩니다. <code>start-local</code>로 Elasticsearch를 실행 중인 경우, <code>start-local</code> .env 파일의 <code>ES_LOCAL_URL</code> 키에 이미 엔드포인트가 있습니다.</p><p>API 키도 필요합니다. Elasticsearch를 <code>start-local</code>로 실행 중인 경우, 이미 <code>ES_LOCAL_API_KEY</code>가 <code>start-local</code>.env 파일에 있습니다. 그렇지 않은 경우, <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">여기</a>에 설명된 대로 Kibana 인터페이스를 사용하여 API 키를 생성할 수 있습니다.</p><ul><li><p>Kibana: Stack Management &gt; Security &gt; API 키 &gt; API 키 생성</p></li><li><p>API 키에 대해 읽기 권한만 설정하여 <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions#grant-access-with-roles">여기</a>에 보고된 대로 <code>feature_agentBuilder.read</code> 권한을 활성화할 것을 권장합니다.</p></li><li><p>인코딩된 API 키 값을 복사합니다.</p></li></ul><p>셸에서 필요한 환경 변수를 설정하세요.</p>export ELASTIC_URL="your-elasticsearch-url"
export ELASTIC_MCP_URL="your-elasticsearch-mcp-url"
export ELASTIC_API_KEY="your-encoded-api-key"<h2>예제 데이터 세트 설치하기</h2><p>Kibana에서 제공되는 <strong>eCommerce orders</strong> 데이터 세트를 설치할 수 있습니다. 이 데이터 세트에는 <strong><code>kibana_sample_data_ecommerce</code></strong>라는 단일 인덱스가 포함되어 있으며 전자상거래 웹사이트의 4,675개 주문 정보를 담고 있습니다. 각 주문에는 다음과 같은 정보가 포함되어 있습니다.</p><ul><li><p>고객 정보(이름, ID, 생년월일, 이메일 등)</p></li><li><p>주문 날짜</p></li><li><p>주문 ID</p></li><li><p>제품(가격, 수량, ID, 카테고리, 할인, 기타 정보를 포함한 전체 제품 목록)</p></li><li><p>SKU.</p></li><li><p>총액(세전, 세후)</p></li><li><p>총 수량</p></li><li><p>지리 정보(도시, 국가, 대륙, 위치, 지역).</p></li></ul><p>샘플 데이터를 설치하려면 Kibana의 <strong>통합</strong> 페이지를 열고(상단 검색창에서 '통합'을 검색) <strong>"Sample Data"</strong>를 설치하세요. 자세한 내용은 <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">여기</a> 설명서를 확인하세요.</p><p>이 글의 목표는 Gemini CLI를 설정해 Elasticsearch에 연결하고 <strong><code>kibana_sample_data_ecommerce</code></strong> 인덱스와 손쉽게 상호작용하는 방법을 보여주는 것입니다.</p><h2>Elasticsearch MCP를 사용하는 방법</h2><p>Gemini에서 다음 명령을 사용하여 연결을 확인할 수 있습니다:</p>/mcp list<p>그림 2와 같이 <strong><code>elastic-agent-builder</code></strong>가 활성화된 것을 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt52b85e7255360f3b/6a17072da929cf33d3ae08f5/1508423bc1d1bc3c04a1cb01e2d59495a3516ed1-1465x844.png" alt="도구 목록이 포함된 `elastic-agent-builder` MCP 서버입니다." /><p>Elasticsearch는 기본 도구 세트를 제공합니다. <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/tools/builtin-tools-reference">여기</a>에서 설명을 참조하세요.</p><p>이 도구로 Elasticsearch와 상호 작용하며 다음과 같은 질문을 할 수 있습니다.</p><ul><li><p><code>Give me the list of all the indexes available in Elasticsearch.</code></p></li><li><p><code>How many customers are based in the USA in the kibana_sample_data_ecommerce index of Elasticsearch?</code></p></li></ul><p>질문에 따라 Gemini는 하나 이상의 도구를 사용하여 답변을 시도합니다.</p><h2>/elastic 명령어</h2><p>Gemini CLI의 Elasticsearch 확장 프로그램에서<strong><code>/elastic</code></strong> 명령어를 추가했습니다.</p><p><strong><code>/help</code></strong> 명령을 실행하면 사용 가능한 모든 <code>/elastic</code> 옵션이 표시됩니다(그림 3).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt741c7451ecab10d2/6a17072ea6c2b9ccd6e79643/5b2a0727ce7a04354878dd048253d3f4d062324b-1983x230.png" alt="사용 가능한 `/elastic` 명령어입니다." /><p>이 명령어들은 <code>elastic-agent-builder</code> MCP 서버의 특정 도구를 직접 실행하고자 할 때 유용할 수 있습니다. 예를 들어 다음 명령어를 사용하면 <code>kibana_sample_data_ecommerce</code>의 매핑을 얻을 수 있습니다.</p>/elastic:get-mapping kibana_sample_data_ecommerce<p>이러한 명령은 Gemini 모델에 의존하여 실행할 도구를 결정하는 대신, 특정 도구를 실행하는 바로가기 역할을 합니다.</p><h2>Elasticsearch 스킬 사용 방법</h2><p>이 확장 기능에는 <a href="https://github.com/elastic/gemini-cli-elasticsearch/tree/main/skills/esql">ES|QL용 에이전트 기술</a>과 Elasticsearch에서 사용할 수 있는 <a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">Elasticsearch 쿼리 언어</a>가 함께 제공됩니다. <a href="https://agentskills.io/home">Agent Skills</a>는 Gemini CLI와 같은 AI 코딩 에이전트에 특정 작업에 대한 사용자 지정 지침을 제공하는 개방형 형식입니다. <em>점진적 공개</em>라는 개념을 사용하여 초기 시스템 프롬프트에 스킬에 대한 간략한 설명만 추가합니다. 에이전트에게 Elasticsearch 쿼리와 같은 작업을 수행하도록 요청하면 에이전트는 해당 요청을 관련 스킬과 연결하고 자세한 지침을 동적으로 불러옵니다. 이는 토큰 예산을 관리하면서 AI에 필요한 컨텍스트를 정확하게 제공하는 효율적인 방법입니다.</p><p><strong><code>esql</code></strong><strong> 스킬</strong>은 Gemini CLI가 클러스터에 직접 ES|QL 쿼리를 작성하고 실행할 수 있도록 설계되었습니다. ES|QL은 데이터 탐색, 로그 분석 및 집계를 매우 직관적으로 수행할 수 있는 강력한 파이프 쿼리 언어입니다. 이 스킬을 활성화하면 ES|QL 구문을 찾아볼 필요가 없습니다. Gemini CLI에 데이터에 대한 자연어 질문을 입력하기만 하면, 에이전트가 나머지 작업을 처리합니다.</p><p>실행은 터미널에서 실행되는 간단한 <a href="https://curl.se/">curl</a> 명령어를 사용하여 수행됩니다. 이것이 가능한 것은 Elasticsearch가 어떤 아키텍처에도 쉽게 통합할 수 있는 풍부한 REST API 세트를 제공하기 때문입니다.</p><p><strong><code>esql</code></strong><strong> 스킬 제공 내용:</strong></p><ul><li><p><strong>인덱스 및 스키마 검색:</strong> 에이전트가 스킬의 기본 제공 도구를 사용하여 사용 가능한 인덱스를 나열하고 필드 매핑을 가져올 수 있습니다. 예를 들어 전자상거래 데이터세트에 대한 쿼리를 작성하기 전에 에이전트가 <strong><code>kibana_sample_data_ecommerce</code></strong> 스키마 검사를 실행해서 사용 가능한 필드(예: <strong><code>taxful_total_price</code></strong> 또는 <strong><code>category</code></strong>)를 파악할 수 있습니다.</p></li><li><p><strong>원활한 자연어 번역:</strong> 이 스킬은 에이전트에 단순한 참조 매뉴얼을 넘어 사용자 의도를 해석할 수 있는 구체적인 가이드를 제공합니다. '서비스별 평균 응답 시간 표시'와 같은 자연어 요청을 입력하면 에이전트가 스킬에 내장된 패턴 매칭 기능을 사용하여 사용자가 입력한 내용을 올바른 ES|QL 집계, 필터 및 명령으로 즉시 변환합니다.</p></li><li><p><strong>자체 수정:</strong> 만일 타입 불일치 또는 구문 오류로 인해 쿼리가 실패하면, 스킬은 생성된 쿼리와 정확한 Elasticsearch 오류 메시지를 반환하여 에이전트가 즉시 쿼리를 수정하고 사용자가 개입하지 않아도 다시 시도할 수 있게 됩니다.</p></li></ul><p><code>esql</code> 스킬은 <code>elastic-agent-builder</code> MCP 서버에서도 도구로 제공되므로 이 서버를 잠시 비활성화해야 합니다. 다음 명령어를 사용하여 비활성화할 수 있습니다.</p>/mcp disable elastic-agent-builder<p>그런 다음 Gemini CLI에 다음과 같은 프롬프트를 입력하기만 하면 됩니다.</p>Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index<p>에이전트가 수행하는 작업:</p><ul><li><p><code>esql</code> 스킬의 필요성을 인식해야 합니다.</p></li><li><p><strong><code>kibana_sample_data_ecommerce</code></strong> 스키마를 확인하세요.</p></li><li><p><code>FROM kibana_sample_data_ecommerce | STATS total_revenue = SUM(taxful_total_price) BY category.keyword | SORT total_revenue DESC | LIMIT 5</code> 등 ES|QL 쿼리를 구성합니다.</p></li><li><p>Elasticsearch API에 대해 쿼리 작업을 실행하세요.</p></li><li><p>터미널에서 바로 최종 답을 제시합니다.</p></li></ul><p>여기서는 이전 프롬프트에 대한 Gemini의 응답 사례를 보고했습니다.</p>───────────────────────────────────────────────────────────
&gt; Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index   ───────────────────────────────────────────────────────────
✓  Activate Skill "esql": Interact with Elasticsearch using ES|QL and curl. Use when querying, managing indices,  …
Skill esql activated. Resources loaded from /home/user/.gemini/extensions/elasticsearch/skills/esql: 
Showing up to 200 items (files + folders).                                                                     /home/user/.gemini/extensions/elasticsearch/skills/esql/                                                            ├───SKILL.md 
└───references/
├───esql-reference.md 
├───generation-tips.md 
└───query-patterns.md

✦ I will start by exploring the mapping of the kibana_sample_data_ecommerce index to identify the fields for product category and sales revenue. Then, I will construct and execute an ES|QL query to determine the top 5 product categories by total sales revenue.

✓  Shell curl -s "${ELASTIC_URL%/}/kibana_sample_data_ecommerce/_mapping" -H "Authorization: ApiKey $(printenv ELAS…                                                                                                                       {  
   "kibana_sample_data_ecommerce": { 
     "mappings": { 
       "properties": {
         "category": { 
           "type": "text", 
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         }, 
         "currency": {
           "type": "keyword"
         }, 

✦ Okay, I have the mapping. The category is products.category.keyword and sales revenue is taxful_total_price. I'll now execute the ES|QL query to get the top 5 categories by revenue.

✓  Shell curl -s -X POST "${ELASTIC_URL%/}/_query" \  -H "Authorization: ApiKey $(printenv ELASTIC_API_KEY)" \  -H …
{ 
   "took": 62,
   "is_partial": false, 
   "completion_time_in_millis": 1772805644164,
   "documents_found": 4675,
   "values_loaded": 12084, 
   "start_time_in_millis": 1772805644102, 
   "expiration_time_in_millis": 1773237644087,  
   "columns": [ 
     { 
       "name": "total_revenue",    
       "type": "double"  
     }, 
     {   
       "name": "products.category.keyword",

✦ The query for top 5 product categories by revenue was successful. I'll format the returned JSON values into a   user-friendly list.

 ✓  Shell 
                             
 Top 5 product categories by total sales revenue: 
 1. Men's Clothing: 149393.91
 2. Women's Clothing: 135099.91
 3. Women's Shoes: 105479.17
 4. Men's Shoes: 91797.92
 5. Women's Accessories: 60830.31 

✦ I've found the answer; now I'll format it for clarity.

 ✓  Shell 

 Here are the top 5 product categories by total sales revenue:                                                        
                                                                      
 | Rank | Category              | Total Revenue | 
 |------|-----------------------|---------------| 
 | 1    | Men's Clothing        | 149,393.91    |
 | 2    | Women's Clothing      | 135,099.91    |
 | 3    | Women's Shoes         | 105,479.17    |
 | 4    | Men's Shoes           | 91,797.92     | 
 | 5    | Women's Accessories   | 60,830.31     | 

✦ I've formatted and presented the answer. I'm finished.
 ───────────────────────────────────────────────────────────<p>Gemini 모델이 최종 응답을 도출하는 모든 단계를 보여주는 방식은 흥미로운 부분입니다. 여기에서 모델의 추론 과정에 대한 스킬의 영향력을 확실하게 확인할 수 있습니다. 모델이 스킬을 사용하거나 셸 명령을 실행해야 한다고 처음 인식할 때, 휴먼 인 더 루프 방식을 사용하여 권한을 요청합니다.</p><p><code>esql</code> 스킬은 스키마 발견, 쿼리 생성 및 실행과 같은 복잡한 작업을 처리함으로써 답을 얻는 과정의 메커니즘이 아닌 답 자체에만 집중할 수 있도록 도와줍니다. 필요한 데이터를 제대로 된 형식으로 터미널에서 바로 얻을 수 있습니다. 단 한 줄의 구문도 작성하거나 다른 애플리케이션으로 컨텍스트를 전환할 필요도 없습니다.</p><h2>결론</h2><p>이 글에서는 최근에 출시한 Gemini CLI용 Elasticsearch 확장 기능을 소개했습니다. 이 확장 프로그램을 사용하면 Gemini와 Elastic Agent Builder에서 제공하는 Elasticsearch MCP 서버(버전 9.3.0부터 사용 가능)를 이용하여 Elasticsearch 인스턴스와 상호 작용할 수 있습니다. <code>/elastic</code> 명령어도 사용할 수 있습니다.</p><p>더불어, 이 확장에는 사용자의 요청을 자연어에서 ES|QL 쿼리로 변환하는 <code>esql</code> 스킬도 포함되어 있습니다. 이 기술은 MCP 서버를 사용할 수 없는 경우 특히 유용합니다. 기본 통신이 터미널에서 실행되는 간단한 curl 명령에 의해 이루어지기 때문입니다. Elasticsearch는 어떤 프로젝트에도 쉽게 통합될 수 있는 풍부한 REST API 세트를 제공합니다. 이는 에이전틱 AI 애플리케이션을 개발할 때 특히 유용합니다.</p><p>Gemini CLI 확장 기능에 대한 자세한 내용은 <a href="https://github.com/elastic/gemini-cli-elasticsearch">여기</a> 프로젝트 리포지토리에서 확인하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gemini-cli-extension-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gemini-cli-extension-elasticsearch</guid>
    <category><![CDATA[통합]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Walter Rafelsberger,Enrico Zimuel]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ff43abe550941b4/6a17072ba29299fc2ad00fa4/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png" length="0" type="image/png"/>
    <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Jina 모델, 그 기능 및 Elasticsearch에서의 사용에 대한 소개]]></title>
    <description><![CDATA[Jina 멀티모달 임베딩, Reranker v3 및 시맨틱 임베딩 모델을 탐색하고, 이를 Elasticsearch에서 기본적으로 사용하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic의 Jina는 애플리케이션과 비즈니스 프로세스 자동화를 위한 검색 기반 모델을 제공합니다. 이러한 모델은 Elasticsearch 애플리케이션과 혁신적인 AI 프로젝트에 AI를 도입하기 위한 핵심 기능을 제공합니다.</p><p>Jina 모델은 정보 처리, 정리, 검색을 지원하도록 설계된 세 가지 큰 카테고리로 나뉩니다.</p><ul><li><p>시맨틱 임베딩 모델</p></li><li><p>모델 순위 재지정</p></li><li><p>소규모 생성형 언어 모델</p></li></ul><h2>시맨틱 임베딩 모델</h2><p>시맨틱 임베딩의 핵심 아이디어는 AI 모델이 입력의 의미적 측면을 고차원 공간의 기하학적 형태로 표현하는 방법을 학습할 수 있다는 것입니다.</p><p>시맨틱 임베딩은 고차원 공간에 있는 점(엄밀히 말하면 <em>벡터</em>)으로 생각할 수 있습니다. 임베딩 모델은 일부 디지털 데이터(어떤 것이든 가능하지만 대부분 텍스트나 이미지)를 입력으로 받아 해당 고차원 점의 위치를 일련의 숫자 좌표로 출력하는 신경망입니다. 모델이 제대로 작동한다면 두 시맨틱 임베딩 사이의 거리는 해당 디지털 객체가 동일한 의미를 갖는 정도에 비례합니다.</p><p>검색 애플리케이션에서 이것이 얼마나 중요한지 이해하려면 '개'라는 단어와 '고양이'라는 단어에 대한 임베딩을 공간의 점으로 상상해 보세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbad74e5d8292a60e/6a17db73abe0f2114edfe8d2/802cf9bbcb82180d3fc91009f9f62027eee8f031-615x615.png" alt="" /><p>좋은 임베딩 모델은 '고양이'라는 단어에 대해 '개'보다 '고양이'에 훨씬 가까운 임베딩을 생성해야 하며, '개'는 거의 같은 의미이므로 '고양이'보다 '개'에 훨씬 가까운 임베딩을 가져야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb2b25691801a881/6a17db747b54f946d28b37a5/bce49daf9a31b8fb7ce1c6ef7ae4e8117a4e8b33-615x615.png" alt="" /><p>모델이 다국어인 경우 '고양이'와 '개'의 번역에 대해 동일한 결과를 기대할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt976ba40be7776449/6a17db75be6086c2bd0045f2/ce4d030385324526cbd7539140e0e634d939371c-615x615.png" alt="" /><p>임베딩 모델은 사물 간 의미의 유사성 또는 유사성을 임베딩 간의 공간적 관계로 변환합니다. 위의 그림은 화면에서 볼 수 있도록 2차원으로만 표현했지만, 모델을 임베드하면 수십에서 수천 개의 차원을 가진 벡터가 만들어집니다. 이를 통해 전체 텍스트의 미묘한 의미를 인코딩할 수 있으며, 수천 단어 이상의 문서에 대해 수백 또는 수천 개의 차원을 가진 공간에 지점을 할당할 수 있습니다.</p><h2>멀티모달 임베딩</h2><p>멀티모달 모델은 시맨틱 임베딩의 개념을 텍스트 이외의 것, 특히 이미지로 확장합니다. 사진에 대한 임베딩은 사진에 대한 충실한 설명을 임베딩하는 것과 비슷할 것으로 예상합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt66dc8895485734ec/6a17db77b1e11318d279f155/1ac6aef5b1423e5fe4853e8a547a74e66b0885c2-615x615.png" alt="" /><p>시맨틱 임베딩은 다양한 용도를 가지고 있습니다. 무엇보다도 효율적인 분류기를 구축하고, 데이터 클러스터링을 수행하고, 데이터 중복 제거 및 데이터 다양성 조사와 같은 다양한 작업을 수행하는 데 사용할 수 있으며, 이는 모두 수작업으로 관리하기에는 너무 많은 데이터를 다루는 빅데이터 애플리케이션에 중요한 작업입니다.</p><p>임베딩의 가장 큰 직접적인 용도는 정보 검색입니다. Elasticsearch는 임베딩을 키로 사용하여 검색 객체를 저장할 수 있습니다. 쿼리는 임베딩 벡터로 변환되고 검색은 쿼리 임베딩에 가장 가까운 키가 있는 저장된 객체를 반환합니다.</p><p>전통적인 <em>벡터 기반 검색</em>(때때로 <em>희소 벡터 검색</em>이라고 함)이 문서와 쿼리에서 단어나 메타데이터를 기반으로 한 벡터를 사용하는 반면, <em>임베딩 기반 검색</em>(또한 <em>밀집 벡터 검색</em>이라고 함)은 단어가 아닌 AI가 평가한 의미를 사용합니다. 따라서 일반적으로 기존 검색 방법보다 훨씬 더 유연하고 정확합니다.</p><h2>Matryoshka 표현 학습</h2><p>임베딩의 차원 수와 임베딩에 포함된 숫자의 정밀도는 성능에 상당한 영향을 미칩니다. 매우 높은 차원의 공간과 매우 정밀한 숫자는 매우 상세하고 복잡한 정보를 표현할 수 있지만, 학습과 실행에 더 많은 비용이 드는 더 큰 AI 모델을 필요로 합니다. 벡터를 생성하려면 더 많은 저장 공간이 필요하고 벡터 사이의 거리를 계산하는 데 더 많은 컴퓨팅 사이클이 필요합니다. 시맨틱 임베딩 모델을 사용하는 것은 정밀도와 자원 소비 간의 중요한 절충을 포함합니다.</p><p>사용자의 유연성을 극대화하기 위해 Jina 모델은 <a href="https://arxiv.org/abs/2205.13147">마트료시카 표현 학습</a>이라는 기법으로 훈련됩니다. 이렇게 하면 모델이 가장 중요한 의미적 구분을 임베딩 벡터의 첫 번째 차원에 미리 로드하므로 상위 차원을 잘라내도 여전히 좋은 성능을 얻을 수 있습니다.</p><p>실제로 이는 Jina 모델 사용자가 임베딩에 원하는 차원 수를 선택할 수 있음을 의미합니다. 차원을 적게 선택하면 정밀도가 감소하지만, 성능 저하는 미미합니다. 대부분의 작업에서 Jina 모델의 성능 지표는 임베딩 크기를 50% 줄일 때마다 1~2% 감소하며, 크기가 약 95% 감소할 때까지 이러한 경향이 지속됩니다.</p><h2>비대칭 검색</h2><p>시맨틱 유사성은 일반적으로 대칭적으로 측정됩니다. '고양이'와 '개'를 비교할 때 얻는 값은 '개'와 '고양이'를 비교할 때 얻는 값과 동일합니다. 그러나 정보 검색에 임베딩을 사용할 때는 대칭을 깨고 검색 객체를 인코딩하는 방식과 다르게 쿼리를 인코딩하면 더 잘 작동합니다.</p><p>이는 임베딩 모델을 훈련하는 방식 때문입니다. 훈련 데이터에는 단어와 같은 동일한 요소가 다양한 맥락에서 나타나는 사례들이 포함되어 있으며, 모델은 요소 간의 맥락적 유사점과 차이점을 비교하여 의미를 학습합니다.</p><p>예를 들어 '동물'이라는 단어가 '고양이' 또는 '개'와 같은 문맥에서 많이 나타나지 않으므로 '동물'에 대한 임베딩이 '고양이' 또는 '개'와 특별히 가깝지 않을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf219074e18a6290a/6a17db78be6086cf060045f6/9a33163405af6c71ee7f4ba8ebc86af39e295a69-615x615.png" alt="" /><p>따라서 '동물'을 검색하면 목표와는 정반대로 고양이와 개에 관한 문서가 검색될 가능성이 줄어듭니다. 따라서 '동물'이 검색 대상일 때와 쿼리일 때를 다르게 인코딩합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33438b4964001467/6a17db79b1e113101c79f159/363992d4f0affba7937c0c8a9f82c9a531fcd3ba-615x615.png" alt="" /><p><em>비대칭 검색</em>이란 쿼리에 다른 모델을 사용하거나, 저장된 데이터를 한 가지 방식으로 인코딩하고 쿼리를 다른 방식으로 인코딩하도록 임베딩 모델을 특별히 학습시키는 것을 의미합니다.</p><h2>멀티벡터 임베딩</h2><p>단일 임베딩은 인덱스 데이터베이스의 기본 프레임워크에 적합하기 때문에 정보 검색에 유용합니다. 즉, 검색 키로 단일 임베딩 벡터를 사용하여 검색 대상 객체를 저장합니다. 사용자가 문서 저장소를 쿼리하면 쿼리가 임베딩 벡터로 변환되고 쿼리 임베딩에 가장 가까운 키(고차원 임베딩 공간에서)를 가진 문서가 후보 일치 항목으로 검색됩니다.</p><p>멀티벡터 임베딩은 약간 다르게 작동합니다. 쿼리와 전체 저장된 객체를 나타내는 고정 길이 벡터를 생성하는 대신, 쿼리의 작은 부분을 나타내는 임베딩 시퀀스를 생성합니다. 구성 요소는 일반적으로 텍스트의 경우 토큰 또는 단어이며, 시각적 데이터의 경우 이미지 타일입니다. 이러한 임베딩은 해당 부분의 의미를 그 컨텍스트 내에서 반영합니다.</p><p>예를 들어 다음 문장을 생각해 보세요.</p><ul><li><p>그녀는 상냥한 마음씨를 가졌습니다.</p></li><li><p>그녀는 마음이 바뀌었습니다.</p></li><li><p>그녀는 심장 마비를 일으켰습니다.</p></li></ul><p>표면적으로는 매우 비슷해 보이지만 멀티벡터 모델은 'heart'의 각 인스턴스에 대해 매우 다른 임베딩을 생성하여 전체 문장의 맥락에서 각각이 어떻게 다른 의미를 갖는지를 표현합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c81f089771e6029/6a17db7b7f6f157601c099ec/a33e60c8d8ee3d312bca8375ca2a8b0a0cd40ba9-615x615.png" alt="" /><p>멀티벡터 임베딩을 통해 두 개체를 비교하려면 한 멀티벡터 임베딩의 각 부분을 다른 멀티벡터 임베딩의 각 부분과 비교하고 그 사이의 최소 거리를 합산하는 모따기 거리를 측정하는 경우가 많습니다. 아래에서 설명하는 Jina Rerankers를 포함한 다른 시스템들은 이들을 이들의 유사성을 평가하도록 특별히 훈련된 AI 모델에 입력합니다. 멀티벡터 임베딩은 단일 벡터 임베딩보다 훨씬 더 자세한 정보를 포함하기 때문에 일반적으로 두 접근 방식 모두 단일 벡터 임베딩을 비교하는 것보다 정밀도가 높습니다.</p><p>그러나 멀티벡터 임베딩은 색인에 적합하지 않습니다. 다음 섹션에서 설명된 <code>jina-colbert-v2</code> 모델과 같이 순위 재지정 작업에서 자주 사용됩니다.</p><h2>Jina 임베딩 모델</h2><h3>Jina 임베딩 v4</h3><p><a href="https://jina.ai/news/jina-embeddings-v4-universal-embeddings-for-multimodal-multilingual-retrieval/"><strong>jina-embeddings-v4</strong></a>는 38억(3.8x10⁹) 매개변수의 다국어 및 멀티모달 임베딩 모델로, 다양한 널리 사용되는 언어의 이미지와 텍스트를 지원합니다. 시각적 지식과 언어 지식을 활용하여 두 작업의 성능을 향상시키는 새로운 아키텍처를 사용하여 이미지 검색, 특히 <a href="https://huggingface.co/tasks/visual-document-retrieval">시각적 문서 검색</a>에서 탁월한 성능을 발휘합니다. 이는 차트, 슬라이드, 맵, 스크린샷, 페이지 스캔 및 다이어그램과 같은 이미지를 처리한다는 것을 의미합니다. 이러한 이미지는 일반적인 종류의 이미지로, 종종 중요한 내장 텍스트가 포함되어 있으며, 실제 장면의 사진으로 훈련된 컴퓨터 비전 모델의 범위를 벗어납니다.</p><p>컴팩트한 <a href="https://huggingface.co/docs/peft/en/package_reference/lora">로우랭크 적응(LoRA) 어댑터</a>를 사용하여 여러 가지 작업에 맞게 이 모델을 최적화했습니다. 이를 통해 메모리나 프로세싱의 추가 비용을 최소화하면서 여러 작업의 성능 저하 없이 단일 모델을 여러 작업에 특화하도록 훈련할 수 있습니다.</p><p>주요 기능은 다음과 같습니다:</p><ul><li><p>시각적 문서 검색의 최첨단 성능과 함께 다국어 텍스트 및 일반 이미지 성능은 훨씬 더 큰 모델을 능가합니다.</p></li><li><p>큰 입력 컨텍스트 크기 지원: 32,768토큰은 대략 두 줄짜리 영어 텍스트 80페이지에 해당하며, 20메가픽셀은 4,500 x 4,500픽셀 이미지에 해당합니다.</p></li><li><p>임베딩 크기는 최대 2048개에서 최소 128개까지 사용자가 선택할 수 있습니다. 경험적으로 이 임계값 이하에서는 성능이 급격히 저하되는 것으로 나타났습니다.</p></li><li><p>단일 임베딩과 멀티벡터 임베딩을 모두 지원합니다. 텍스트의 경우, 멀티벡터 출력은 각 입력 토큰에 대해 128차원 임베딩 하나로 구성됩니다. 이미지의 경우, 이미지를 커버하는 데 필요한 각 28x28 픽셀 타일에 대해 하나의 128차원 임베딩을 생성합니다.</p></li><li><p>비대칭 검색을 위한 최적화는 특별히 이를 위해 훈련된 LoRA 어댑터 한 쌍을 통해 이루어집니다.</p></li><li><p>시맨틱 유사도 계산에 최적화된 LoRA 어댑터입니다.</p></li><li><p>LoRA 어댑터를 통해서도 컴퓨터 프로그래밍 언어 및 IT 프레임워크를 특별히 지원합니다.</p></li></ul><p>광범위한 일반 검색, 자연어 이해 및 AI 분석 작업을 위한 범용 다목적 도구로 사용할 수 있도록 <code>jina-embeddings-v4</code>를 개발했습니다. 기능에 비해 비교적 작은 모델이지만 배포하는 데 상당한 리소스가 필요하며 클라우드 API를 통해 사용하거나 대용량 환경에서 사용하기에 가장 적합합니다.</p><h3>Jina 임베딩 v3</h3><p><a href="https://jina.ai/news/jina-embeddings-v3-a-frontier-multilingual-embedding-model/"><strong>jina-embeddings-v3</strong></a>는 6억 개 미만의 매개변수를 가진 소규모 고성능 다국어 텍스트 전용 임베딩 모델입니다. 최대 8192개의 텍스트 입력 토큰을 지원하며, 기본 1024개부터 최대 64개까지 사용자가 선택한 크기의 단일 벡터 임베딩을 출력합니다.</p><p>정보 검색 및 의미적 유사성뿐만 아니라 감정 분석 및 콘텐츠 조정과 같은 분류 작업, 뉴스 집계 및 추천과 같은 클러스터링 작업 등 다양한 텍스트 작업에 대해 <code>jina-embeddings-v3</code>를 학습시켰습니다. <code>jina-embeddings-v4</code>와 마찬가지로 이 모델은 다음 사용 범주에 특화된 LoRA 어댑터를 제공합니다.</p><ul><li><p>비대칭 검색</p></li><li><p>의미적 유사성</p></li><li><p>분류</p></li><li><p>클러스터링</p></li></ul><p><code>jina-embeddings-v3</code> 입력 컨텍스트 크기가 크게 줄어든 <code>jina-embeddings-v4</code> 모델보다 훨씬 작지만 운영 비용은 더 적게 듭니다. 그럼에도 불구하고 텍스트에만 해당되긴 하지만 성능 경쟁력이 매우 뛰어나고 많은 사용 사례에서 더 나은 선택입니다.</p><h3>Jina 코드 임베딩</h3><p>Jina의 특수 코드 임베딩 모델인 <a href="https://jina.ai/models/jina-code-embeddings-1.5b"><strong>jina-code-embeddings(0.5b 및 1.5b)</strong></a>는 15가지 프로그래밍 체계와 프레임워크, 그리고 컴퓨팅 및 정보기술 관련 영어 텍스트를 지원합니다. 각각 5억(0.5x10⁹) 및 15억(1.5x10⁹) 크기의 소규모 모델입니다. 두 모델 모두 최대 32,768개의 토큰 입력 컨텍스트 크기를 지원하며, 작은 모델의 경우 896개에서 64개까지, 큰 모델의 경우 1536개에서 128개까지 사용자가 출력 임베딩 크기를 선택할 수 있습니다.</p><p>이러한 모델은 <a href="https://arxiv.org/abs/2101.00190">접두사 튜닝</a>을 사용하여 LoRA 어댑터 대신 5가지 작업별 특화를 위한 비대칭 검색을 지원합니다.</p><ul><li><p><strong>코드-코드.</strong> 프로그래밍 언어 전반에서 유사한 코드를 검색합니다. 이는 코드 정렬, 코드 중복 제거, 이식 및 리팩토링 지원에 사용됩니다.</p></li><li><p><strong>자연어-코드.</strong> 코드를 검색하여 자연어 쿼리, 댓글, 설명 및 문서와 일치시킵니다.</p></li><li><p><strong>코드-자연어.</strong>코드를 문서 또는 기타 자연어 텍스트와 일치시킵니다.</p></li><li><p><strong>코드-코드 완성.</strong> 관련 코드를 제안하여 기존 코드를 완료하거나 향상시킵니다.</p></li><li><p><strong>기술 관련 Q&amp;A.</strong> 정보 기술에 관한 질문에 대한 자연어 답변을 식별합니다. 이는 기술 지원 사용 사례에 이상적입니다.</p></li></ul><p>이러한 모델은 컴퓨터 문서화 및 프로그래밍 자료와 관련된 작업에서 상대적으로 적은 컴퓨팅 비용으로 우수한 성능을 제공합니다. 개발 환경 및 코드 어시스턴트에 통합하는 데 적합합니다.</p><h3>Jina ColBERT v2</h3><p><a href="https://jina.ai/models/jina-colbert-v2"><strong>jina-colbert-v2</strong></a>는 5억 6천만 개의 매개변수를 가진 멀티벡터 텍스트 임베딩 모델입니다. 다국어 지원, 89개 언어의 자료를 사용하여 학습되었으며 다양한 임베딩 크기와 비대칭 검색을 지원합니다.</p><p>앞서 언급했듯이, 멀티벡터 임베딩은 색인하는 데에는 적합하지 않지만 다른 검색 전략의 결과 정확도를 높이는 데 매우 유용합니다. <code>jina-colbert-v2</code>를 사용하여 멀티벡터 임베딩을 미리 계산한 다음 쿼리 시 검색 후보의 순위를 재조정하는 데 사용할 수 있습니다. 이 접근 방식은 다음 섹션의 재순위 모델 중 하나를 사용하는 것보다 정확도는 떨어지지만 모든 쿼리와 후보 일치에 대해 전체 AI 모델을 호출하는 대신 저장된 멀티벡터 임베딩을 비교하기 때문에 훨씬 더 효율적입니다. 이는 순위 재지정 모델을 사용할 때의 지연 시간과 계산 부하가 너무 큰 사용 사례나 비교할 후보의 수가 순위 재지정 모델에 너무 많은 경우에 이상적으로 적합합니다.</p><p>이 모델은 입력 토큰당 하나씩 일련의 임베딩을 출력하며, 사용자는 128차원, 96차원 또는 64차원 임베딩의 토큰 임베딩을 선택할 수 있습니다. 후보 텍스트 매칭은 8,192개 토큰으로 제한됩니다. 쿼리는 비대칭적으로 인코딩되므로, 사용자는 텍스트가 쿼리인지 후보 일치인지 지정해야 하며, 쿼리 수를 32개의 토큰으로 제한해야 합니다.</p><h3>Jina CLIP v2</h3><p><a href="https://jina.ai/news/jina-clip-v2-multilingual-multimodal-embeddings-for-text-and-images/"><strong>jina-clip-v2</strong></a>는 9억 개의 매개변수를 가진 멀티모달 임베딩 모델로, 텍스트가 이미지의 내용을 설명할 경우 텍스트와 이미지가 서로 가까운 임베딩을 생성하도록 학습되었습니다. 주요 용도는 텍스처 쿼리를 기반으로 이미지를 검색하는 것이지만, 텍스트 전용 모델로도 고성능을 발휘하여 사용자 비용을 절감할 수 있습니다. 텍스트-텍스트 및 텍스트-이미지 검색을 위해 별도의 모델이 필요하지 않기 때문입니다.</p><p>이 모델은 8,192개의 토큰으로 구성된 텍스트 입력 컨텍스트를 지원하며, 이미지는 임베딩을 생성하기 전에 512x512 픽셀로 크기가 조정됩니다.</p><p>대조적 언어-이미지 사전 훈련(CLIP) 아키텍처는 훈련 및 운영이 쉽고 매우 컴팩트한 모델을 생성할 수 있지만, 몇 가지 근본적인 한계가 있습니다. 그들은 한 매체에서 얻은 지식을 다른 매체에서의 성능 향상에 사용할 수 없습니다. 한 매체에서 다른 매체로 사용하여 성능을 향상시킬 수 없습니다. 따라서 '개'와 '고양이'라는 단어가 '자동차'보다 의미상 서로 가깝다는 것은 알 수 있지만, 개 그림과 고양이 그림이 자동차 그림보다 더 관련이 있다는 것을 반드시 알지는 못합니다.</p><p>또한 <em>양식 격차</em>라는 문제도 있습니다. 개에 대한 텍스트 임베딩은 개 사진 임베딩보다 고양이에 대한 텍스트 임베딩에 더 가깝게 느껴질 가능성이 높습니다. 이러한 제한 때문에 CLIP을 텍스트-이미지 검색 모델 또는 텍스트 전용 모델로 사용하는 것이 좋지만, 단일 쿼리에서 둘을 혼합하지 않는 것이 좋습니다.</p><h2>모델 순위 재지정</h2><p>순위 재지정 모델은 하나 이상의 후보 일치 항목과 쿼리를 모델에 입력으로 받아 이를 직접 비교하여 훨씬 더 높은 정밀도의 일치 항목을 생성합니다.</p><p>원칙적으로, 각 쿼리를 저장된 각 문서와 비교하여 정보 검색을 위해 직접 순위 재지정 도구를 사용할 수 있지만, 이는 매우 계산 비용이 많이 들고 가장 작은 컬렉션을 제외하고는 비실용적입니다. 결과적으로, 순위 재지정 도구는 임베딩 기반 검색이나 다른 검색 알고리즘과 같은 다른 방법을 통해 찾은 비교적 짧은 후보 목록을 평가하는 데 사용되는 경향이 있습니다. 순위 재지정 모델은 하이브리드 및 연합 검색 체계에 이상적으로 적합합니다. 여기서 검색을 수행한다는 것은 쿼리가 별도의 검색 시스템으로 전송되어 각각 고유한 데이터 세트를 가지고 각기 다른 결과를 반환할 수 있음을 의미합니다. 다양한 결과를 하나의 고품질 결과로 병합하는 데 매우 효과적입니다.</p><p>임베딩 기반 검색은 저장된 모든 데이터를 재색인하고 검색 결과에 대한 사용자 기대치를 변경해야 하므로 상당한 노력이 필요합니다. 기존 검색 체계에 리랭커를 추가하면 AI의 많은 이점을 추가할 수 있으며, 전체 검색 솔루션을 다시 설계할 필요 없이 이를 달성할 수 있습니다.</p><h2>Jina 순위 재지정 모델</h2><h3>Jina 순위 재지정 m0</h3><p><a href="https://jina.ai/models/jina-reranker-m0/"><strong>jina-reranker-m0</strong></a>는 24억(2.4x10⁹)개의 매개변수를 가진 다중 모드 순위 재지정 도구로, 텍스트 쿼리와 텍스트 및/또는 이미지로 구성된 후보 매치를 지원합니다. 이 모델은 시각적 문서 검색의 선도적 모델로, PDF 저장소, 텍스트 스캔, 스크린샷 및 텍스트 또는 반구조화된 정보를 포함한 기타 컴퓨터 생성 또는 수정 이미지뿐만 아니라 텍스트 문서와 이미지로 구성된 혼합 데이터에도 이상적인 솔루션입니다.</p><p>이 모델은 단일 쿼리와 일치하는 후보를 입력받아 점수를 반환합니다. 동일한 쿼리를 다른 후보와 함께 사용하면 점수를 비교하여 순위를 매기는 데 사용할 수 있습니다. 쿼리 텍스트와 후보 텍스트 또는 이미지를 포함하여 최대 10,240개 토큰의 총 입력 크기를 지원합니다. 이미지를 덮는 데 필요한 28x28 픽셀 타일 하나하나가 입력 크기 계산을 위한 토큰으로 간주됩니다.</p><h3>Jina 순위 재지정 v3</h3><p><a href="https://jina.ai/models/jina-reranker-v3/"><strong>jina-reranker-v3</strong></a>는 비슷한 크기의 모델을 위한 최첨단 성능을 갖춘 6억 개의 매개변수 텍스트 순위 재지정 도구입니다. <code>jina-reranker-m0</code>와 달리, 단일 쿼리와 최대 64개의 후보 매칭 목록을 받아 순위 순서를 반환합니다. 쿼리와 모든 텍스트 후보를 포함하여 131,000개의 토큰으로 구성된 입력 컨텍스트가 있습니다.</p><h3>Jina 순위 재지정 v2</h3><p><a href="https://jina.ai/models/jina-reranker-v2"><strong>jina-reranker-v2-base-multilingual</strong></a>은 함수 호출 및 SQL 쿼리를 지원하도록 설계된 추가 기능을 갖춘 매우 컴팩트한 범용 순위 재지정 도구입니다. 3억 개 미만의 매개변수를 포함하며, 빠르고 효율적이며 정확한 다국어 텍스트 순위 재지정을 제공하며, 텍스트 쿼리에 맞는 SQL 테이블과 외부 함수 선택 지원도 추가하여 에이전트 사용 사례에 적합합니다.</p><h2>소규모 생성형 언어 모델</h2><p>생성형 언어 모델은 텍스트 또는 멀티미디어 입력을 받아 텍스트 출력으로 응답하는 OpenAI의 ChatGPT, Google Gemini, Anthropic의 Claude와 같은 모델입니다. <em>대규모</em> 언어 모델(LLM)과 <em>소규모</em> 언어 모델(SLM)을 구분하는 명확한 경계는 없지만, 최고급 LLM을 개발, 운영 및 사용하는 데 따르는 실질적인 문제는 잘 알려져 있습니다. 가장 잘 알려진 것들은 공개적으로 배포되지 않았기 때문에 우리는 그 크기를 추정할 수만 있지만, ChatGPT, Gemini 및 Claude는 1~3조(1~3x10¹²) 매개변수 범위 내에 있을 것으로 예상됩니다.</p><p>이러한 모델을 실행하는 것은, 심지어 공개적으로 이용 가능하더라도, 기존 하드웨어의 범위를 훨씬 넘어서며, 방대한 병렬 어레이로 구성된 최첨단 칩을 필요로 합니다. LLM에는 유료 API를 통해 액세스할 수 있지만, 이는 상당한 비용이 발생하고 높은 대기 시간을 가지며 데이터 보호, 디지털 주권 및 클라우드 재환원에 대한 요구 사항과 일치하기 어렵습니다. 또한 이 정도 규모의 모델을 교육하고 사용자 지정하는 데 드는 비용도 상당할 수 있습니다.</p><p>그 결과, 대형 LLM의 모든 기능은 부족할 수 있지만 저렴한 비용으로 특정 종류의 작업을 잘 수행할 수 있는 소규모 모델을 개발하기 위해 많은 연구가 진행되었습니다. 기업은 보통 특정 문제를 해결하기 위해 소프트웨어를 배포하며, AI 소프트웨어도 다르지 않습니다. 따라서 SLM 기반 솔루션이 LLM 기반 솔루션보다 나은 경우가 많습니다. 일반적으로 상용 하드웨어에서 실행할 수 있고, 더 빠르며 실행에 필요한 에너지를 덜 소비하고, 훨씬 더 쉽게 사용자 정의할 수 있습니다.</p><p>AI를 실용적인 검색 솔루션에 가장 효과적으로 도입할 수 있는 방법에 집중하면서 Jina의 SLM 제품군은 성장하고 있습니다.</p><h2>Jina SLMs</h2><h3>ReaderLM v2</h3><p><a href="https://jina.ai/models/ReaderLM-v2"><strong>ReaderLM-v2</strong></a>는 사용자가 제공한 JSON 스키마와 자연어 명령어에 따라 HTML을 Markdown 또는 JSON으로 변환하는 생성형 언어 모델입니다.</p><p>데이터 전처리 및 정규화는 디지털 데이터에 대한 효과적인 검색 솔루션을 개발하는 데 필수적인 부분이지만, 실제 세계의 데이터, 특히 웹에서 파생된 정보는 종종 혼란스럽고, 단순한 변환 전략은 매우 취약한 것으로 드러나는 경우가 많습니다. 대신, <code>ReaderLM-v2</code> 웹페이지의 DOM 트리 덤프의 혼란을 이해하고 유용한 요소를 강력하게 식별할 수 있는 지능형 AI 모델 솔루션을 제공합니다.</p><p>15억(1.5x10⁹)개의 매개변수로, 최첨단 LLM보다 세 자릿수 차이로 작지만 이 특정 작업에서는 동등한 수준의 성능을 발휘합니다.</p><h3>Jina VLM</h3><p><a href="https://jina.ai/models/jina-vlm"><strong>jina-vlm</strong></a>은 이미지에 대한 자연어 질문에 답하도록 훈련된 24억 (2.4x10⁹)개의 매개변수 생성형 언어 모델입니다. 그것은 스캔, 스크린샷, 슬라이드, 다이어그램 및 유사한 비자연적 이미지 데이터에 대한 질문에 답변하는 시각적 문서 분석을 매우 강력하게 지원합니다.</p><p>그 예는 다음과 같습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt124b9932e01dcd40/6a17db7d4202291eca29f4bc/adfa1420d079ca4fd5582eef4349b1265b378e76-950x500.png" alt="" /><p>이미지 속 텍스트를 읽는 데도 매우 능숙합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a862a9c9a0e42ce/6a17db7fb1e1133a2979f15d/ea3956e7ad86f8e171841cab2c28c8b3498da1d4-1002x500.png" alt="" /><p>하지만 <code>jina-vlm</code>의 진정한 강점은 정보성 이미지와 인공 이미지의 콘텐츠를 이해하는 것입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt761cf621ea32e4ae/6a17db8163baff7730741b26/f68606f9d2d99e2cd616d4ff81db3574dc4e26a5-1020x700.png" alt="" /><p>또는:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4df4d31e574df7e3/6a17db82e3179134e02d56e9/297e85e7e78f296388a02301e1e08fed70827423-1000x500.png" alt="" /><p><code>jina-vlm</code> 자동 캡션 생성, 제품 설명, 이미지 대체 텍스트, 시각 장애인을 위한 접근성 애플리케이션에 적합합니다. 또한 검색 증강 생성(RAG) 시스템이 시각 정보를 사용하고 AI 에이전트가 사람의 도움 없이 이미지를 처리할 수 있는 가능성을 열어줍니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/jina-models-elasticsearch-guide</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/jina-models-elasticsearch-guide</guid>
    <category><![CDATA[통합]]></category>
    <category><![CDATA[Jina AI]]></category>
    <dc:creator><![CDATA[Scott Martens]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta03919124faf767a/6a17db84ec0f89b8fe5a64d8/407b4c862b51ebdfc7f26db4e25950a65caf1673-656x442.png" length="0" type="image/png"/>
    <pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mastra와 Elasticsearch를 사용하여 시맨틱 리콜 기능을 갖춘 지식 에이전트 구축하기]]></title>
    <description><![CDATA[메모리 및 정보 검색을 위한 벡터 저장소로 Mastra와 Elasticsearch를 사용해 시맨틱 리콜 기능을 갖춘 지식 에이전트를 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">컨텍스트 엔지니어링은</a> 안정적인 AI 에이전트 및 아키텍처를 구축하는 데 있어 점점 더 중요해지고 있습니다. 모델이 점점 더 개선됨에 따라 그 효과와 신뢰성은 학습된 데이터보다는 올바른 맥락에 얼마나 잘 근거를 두고 있는지에 따라 달라집니다. 가장 관련성 높은 정보를 적시에 검색하고 적용할 수 있는 에이전트가 정확하고 신뢰할 수 있는 결과물을 만들어낼 가능성이 훨씬 높습니다.</p><p>이 블로그에서는 <a href="https://mastra.ai/">Mastra를</a> 사용해 사용자가 말한 내용을 기억하고 나중에 관련 정보를 불러올 수 있는 지식 에이전트를 구축하는 데 Elasticsearch를 메모리 및 검색 백엔드로 사용하겠습니다. 동일한 개념을 실제 사용 사례로 쉽게 확장하여 지원 상담원이 과거의 대화와 해결 방법을 기억할 수 있어 특정 사용자에게 맞춤형 응답을 제공하거나 이전 컨텍스트를 기반으로 더 빠르게 해결책을 제시할 수 있다고 생각하면 됩니다.</p><p>여기를 따라 단계별로 구축하는 방법을 알아보세요. 길을 잃었거나 완성된 예제를 실행하고 싶다면 <a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">여기에서</a> 리포지토리를 확인하세요.</p><h2>마스트라란 무엇인가요?</h2><p>Mastra는 추론, 메모리 및 도구에 대한 교체 가능한 부품으로 AI 에이전트를 구축하기 위한 오픈 소스 TypeScript 프레임워크입니다. <a href="https://mastra.ai/docs/memory/semantic-recall">시맨틱 리콜</a> 기능을 통해 상담원은 메시지를 벡터 데이터베이스에 임베딩으로 저장하여 과거 상호작용을 기억하고 검색할 수 있습니다. 이를 통해 상담원은 장기적인 대화 맥락과 연속성을 유지할 수 있습니다. Elasticsearch는 효율적인 고밀도 벡터 검색을 지원하기 때문에 이 기능을 활성화하는 데 탁월한 벡터 저장소입니다. 시맨틱 리콜이 트리거되면 에이전트는 관련 과거 메시지를 모델의 컨텍스트 창으로 가져와서 모델이 검색된 컨텍스트를 추론 및 응답의 기초로 사용할 수 있도록 합니다.</p><h2>시작하기 위해 필요한 사항</h2><ul><li><p>노드 v18+</p></li><li><p>Elasticsearch(버전 8.15 이상)</p></li><li><p>Elasticsearch API 키</p></li><li><p><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">OpenAI API 키</a></p></li></ul><p>참고: 데모에서는 OpenAI 공급자를 사용하므로 이 공급자가 필요하지만, Mastra는 다른 AI SDK 및 커뮤니티 모델 공급자를 지원하므로 설정에 따라 쉽게 교체할 수 있습니다.</p><h2>Mastra 프로젝트 구축</h2><p>Mastra에 내장된 CLI를 사용하여 프로젝트의 스캐폴딩을 제공하겠습니다. 명령을 실행합니다:</p>npm create mastra@latest<p>다음과 같은 일련의 프롬프트가 표시됩니다:</p><p>1. 프로젝트 이름을 지정합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87f941f654d03827/6a16f7af67045b214d45bfa1/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" alt="Mastra 앱에서 프롬프트 이름 지정하기" /><p>2. 이 기본값을 그대로 사용해도 되므로 비워두셔도 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbb3d4f27435cac/6a16f7b0cdacbf29497d27de/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" alt="프롬프트 파일을 어디에 보관할지 마스트라에게 알려주기" /><p>3. 이 프로젝트에서는 OpenAI에서 제공하는 모델을 사용합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f654f6cb9397e94/6a16f7b2964cea899a08b942/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" alt="Mastra에서 OpenAI가 제공하는 모델 선택하기" /><p>4. 모든 환경 변수를 이후 단계에서 구성할 '.env' 파일에 저장하므로 '지금은 건너뛰기' 옵션을 선택합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff106117521a3519/6a16f7b3c1e8a5031af880d8/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" alt="OpenAI 키에 대해 지금은 건너뛰기를 선택합니다." /><p>5. 이 옵션을 건너뛸 수도 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda7d9c51c3878d7/6a16f7b450916809dbe1b892/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" alt="" /><p>초기화가 완료되면 다음 단계로 넘어갈 수 있습니다.</p><h3>종속성 설치</h3><p>다음으로 몇 가지 종속성을 설치해야 합니다:</p>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv<ul><li><p><code>ai</code> - 자바스크립트/타입스크립트에서 AI 모델, 프롬프트 및 워크플로를 관리할 수 있는 도구를 제공하는 핵심 AI SDK 패키지입니다. Mastra는 Vercel의 <a href="https://ai-sdk.dev/">AI SDK를</a> 기반으로 구축되었으므로 에이전트와의 모델 상호 작용을 활성화하려면 이 종속성이 필요합니다.</p></li><li><p><code>@ai-sdk/openai</code> - AI SDK를 OpenAI 모델(예: GPT-4, GPT-4o 등)에 연결하여 OpenAI API 키를 사용하여 API 호출을 가능하게 하는 플러그인입니다.</p></li><li><p><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Node.js용 공식 Elasticsearch 클라이언트</a>, 인덱싱, 검색, 벡터 작업을 위해 Elastic Cloud 또는 로컬 클러스터에 연결하는 데 사용됩니다.</p></li><li><p><code>dotenv</code> - .env에서 환경 변수를 로드합니다. 파일을 process.env에 추가합니다, API 키와 Elasticsearch 엔드포인트와 같은 자격 증명을 안전하게 삽입할 수 있습니다.</p></li></ul><h3>환경 변수 구성</h3><p>프로젝트 루트 디렉터리에 <code>.env</code> 파일이 없는 경우 이 파일을 만듭니다. 또는 <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">리포지토리에</a> 제공한 예제 <code>.env</code> 를 복사하여 이름을 바꿀 수 있습니다. 이 파일에서 다음 변수를 추가할 수 있습니다:</p>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<p>이것으로 기본 설정이 끝났습니다. 여기에서 이미 상담원 구축 및 오케스트레이션을 시작할 수 있습니다. 여기서 한 걸음 더 나아가 저장 및 벡터 검색 레이어로 Elasticsearch를 추가하겠습니다.</p><h2>벡터 저장소로 Elasticsearch 추가하기</h2><p><code>stores</code> 이라는 새 폴더를 만들고 그 안에 이 <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">파일을</a> 추가합니다. Mastra와 Elastic이 공식 Elasticsearch 벡터 저장소 통합을 출시하기 전에 <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a>(Mastra CTO)가 초기 프로토타입 클래스( <code>ElasticVector</code>)를 공유했습니다. 간단히 말해, Mastra의 메모리 추상화와 Elasticsearch의 고밀도 벡터 기능을 연결하여 개발자가 에이전트를 위한 벡터 데이터베이스로 Elasticsearch를 도입할 수 있습니다.</p><p>통합의 중요한 부분을 자세히 살펴보겠습니다:</p><h3>Elasticsearch 클라이언트 수집</h3><p>이 섹션에서는 <code>ElasticVector</code> 클래스를 정의하고 표준 배포와 서버리스 배포를 모두 지원하는 Elasticsearch 클라이언트 연결을 설정합니다.</p>export interface ElasticVectorConfig extends ClientOptions {
    /**
     * Explicitly specify if connecting to Elasticsearch Serverless.
     * If not provided, will be auto-detected on first use.
     */
    isServerless?: boolean;
    
    /**
     * Maximum documents to count accurately when describing indices.
     * Higher values provide accurate counts but may impact performance on large indices.
     * 
     * @default 10000
     */
    maxCountAccuracy?: number;
}

export class ElasticVector extends MastraVector {
    private client: Client;
    private isServerless: boolean | undefined;
    private deploymentChecked: boolean = false;
    private readonly maxCountAccuracy: number;

    constructor(config: ElasticVectorConfig) {
        super();
        this.client = new Client(config);
        this.isServerless = config.isServerless;
        this.maxCountAccuracy = config.maxCountAccuracy ?? 10000;
    }
}<ul><li><p><code>ElasticVectorConfig extends ClientOptions</code>: 이렇게 하면 모든 Elasticsearch 클라이언트 옵션(예: <code>node</code>, <code>auth</code>, <code>requestTimeout</code>)을 상속하고 사용자 정의 속성을 추가하는 새로운 구성 인터페이스가 생성됩니다. 즉, 사용자는 서버리스 전용 옵션과 함께 유효한 모든 Elasticsearch 구성을 전달할 수 있습니다.</p></li><li><p><code>extends MastraVector</code>: 이를 통해 <code>ElasticVector</code> 은 모든 벡터 스토어 통합이 준수하는 공통 인터페이스인 Mastra의 기본 <code>MastraVector</code> 클래스에서 상속할 수 있습니다. 이렇게 하면 Elasticsearch가 에이전트의 관점에서 다른 모든 Mastra 벡터 백엔드처럼 작동합니다.</p></li><li><p><code>private client: Client</code>: Elasticsearch JavaScript 클라이언트의 인스턴스를 보관하는 개인 속성입니다. 이렇게 하면 클래스가 클러스터와 직접 대화할 수 있습니다.</p></li><li><p><code>isServerless</code> 및 <code>deploymentChecked</code>: 이러한 속성은 함께 작동하여 서버리스 또는 표준 Elasticsearch 배포에 연결되어 있는지 여부를 감지하고 캐시합니다. 이 감지는 처음 사용할 때 자동으로 수행되거나 명시적으로 구성할 수 있습니다.</p></li><li><p><code>constructor(config: ClientOptions)</code>: 이 생성자는 구성 객체(Elasticsearch 자격 증명 및 선택적 서버리스 설정이 포함된)를 가져와서 <code>this.client = new Client(config)</code> 줄에서 클라이언트를 초기화하는 데 사용합니다.</p></li><li><p><code>super()</code>: 이것은 Mastra의 기본 생성자를 호출하므로 로깅, 유효성 검사 헬퍼 및 기타 내부 훅을 상속합니다.</p></li></ul><p>이 시점에서 Mastra는 다음과 같은 새로운 벡터 스토어가 있다는 것을 알고 있습니다. <code>ElasticVector</code></p><h3>배포 유형 감지</h3><p>인덱스를 생성하기 전에 어댑터는 표준 Elasticsearch를 사용 중인지 아니면 Elasticsearch 서버리스를 사용 중인지 자동으로 감지합니다. 서버리스 배포에서는 수동 샤드 구성을 허용하지 않기 때문에 이 점이 중요합니다.</p>private async detectServerless(): Promise&lt;boolean&gt; {
    // Return cached result if already detected
    if (this.deploymentChecked) {
        return this.isServerless ?? false;
    }

    // Use explicit configuration if provided
    if (this.isServerless !== undefined) {
        this.deploymentChecked = true;
        this.logger?.info(
            `Using explicit deployment type: ${this.isServerless ? 'Serverless' : 'Standard'}`
        );
        return this.isServerless;
    }

    try {
        const info = await this.client.info();
        
        // Primary detection: build flavor (most reliable)
        const isBuildFlavorServerless = info.version?.build_flavor === 'serverless';
        
        // Secondary detection: tagline (fallback)
        const isTaglineServerless = info.tagline?.toLowerCase().includes('serverless') ?? false;
        
        this.isServerless = isBuildFlavorServerless || isTaglineServerless;
        this.deploymentChecked = true;
        
        this.logger?.info(
            `Auto-detected ${this.isServerless ? 'Serverless' : 'Standard'} Elasticsearch deployment`,
            { 
                buildFlavor: info.version?.build_flavor, 
                version: info.version?.number,
                detectionMethod: isBuildFlavorServerless ? 'build_flavor' : 'tagline'
            }
        );
        
        return this.isServerless;
    } catch (error) {
        this.logger?.warn(
            'Could not auto-detect deployment type, assuming Standard Elasticsearch. ' +
            'Set isServerless: true explicitly in config if using Serverless.',
            { error: error instanceof Error ? error.message : String(error) }
        );
        this.isServerless = false;
        this.deploymentChecked = true;
        return false;
    }
}<p>무슨 일이 일어나고 있나요?</p><ul><li><p>먼저 구성에서 <code>isServerless</code> 을 명시적으로 설정했는지 확인합니다(자동 감지 건너뛰기).</p></li><li><p>Elasticsearch의 <code>info()</code> API를 호출하여 클러스터 정보를 가져옵니다.</p></li><li><p><code>build_flavor field</code> (서버리스 배포는 <code>serverless</code>)를 확인합니다.</p></li><li><p>빌드 플레이버를 사용할 수 없는 경우 태그 라인 확인으로 돌아가기</p></li><li><p>반복되는 API 호출을 방지하기 위해 결과 캐시</p></li><li><p>탐지에 실패하면 표준 배포로 기본 설정됩니다.</p></li></ul><p> 사용 예시:</p>// Option 1: Auto-detect (recommended)
const vector = new ElasticVector({
    node: 'https://your-cluster.es.cloud',
    auth: { apiKey: 'your-api-key' }
});
// Detection happens automatically on first index operation

// Option 2: Explicit configuration (faster startup)
const vector = new ElasticVector({
    node: 'https://your-serverless.es.cloud',
    auth: { apiKey: 'your-api-key' },
    isServerless: true  // Skips auto-detection
});<h3>Elasticsearch에서 "메모리" 저장소 만들기</h3><p>아래 함수는 임베딩을 저장하기 위한 Elasticsearch 인덱스를 설정합니다. 인덱스가 이미 존재하는지 확인합니다. 그렇지 않은 경우 임베딩 및 사용자 정의 유사성 메트릭을 저장할 <code>dense_vector</code> 필드가 포함된 아래 매핑을 사용하여 매핑을 생성합니다.</p><p>몇 가지 주의해야 할 사항:</p><ul><li><p><code>dimension</code> 매개변수는 각 임베딩 벡터의 길이로, 사용 중인 임베딩 모델에 따라 달라집니다. 이 경우, <code>1536</code> 크기의 벡터를 출력하는 OpenAI의 <code>text-embedding-3-small</code> 모델을 사용하여 임베딩을 생성하겠습니다. 이 값을 기본값으로 사용하겠습니다.</p></li><li><p>아래 매핑에 사용된 <code>similarity</code> 변수는 <code>metric</code> 매개변수의 값을 받아 선택한 거리 메트릭에 대해 Elasticsearch 호환 키워드로 변환하는 도우미 함수 c<code>onst similarity = this.mapMetricToSimilarity(metric)</code> 에서 정의됩니다.</p><ul><li><p>예를 들어: 예를 들어, Mastra는 <code>cosine</code>, <code>euclidean</code>, <code>dotproduct</code> 와 같은 벡터 유사성에 대한 일반적인 용어를 사용합니다. <code>euclidean</code> 메트릭을 Elasticsearch 매핑에 직접 전달하면, Elasticsearch는 <code>l2_norm</code> 키워드가 유클리드 거리를 나타낼 것으로 예상하기 때문에 오류가 발생합니다.</p></li></ul></li><li><p>서버리스 호환성: 서버리스 배포를 위한 샤드 및 복제본 설정은 Elasticsearch 서버리스에서 자동으로 관리되므로 코드에서 자동으로 생략됩니다.</p></li></ul>async createIndex(params: CreateIndexParams): Promise&lt;void&gt; {
    const { indexName, dimension = 1536, metric = 'cosine' } = params;

    try {
        const exists = await this.client.indices.exists({ index: indexName });

        if (exists) {
            try {
                await this.validateExistingIndex(indexName, dimension, metric);
                this.logger?.info(`Index "${indexName}" already exists and is valid`);
                return;
            } catch (validationError) {
                throw new Error(
                    `Index "${indexName}" exists but does not match the required configuration: ${
                        validationError instanceof Error ? validationError.message : String(validationError)
                    }`
                );
            }
        }

        const isServerless = await this.detectServerless();
        const similarity = this.mapMetricToSimilarity(metric);

        const indexConfig: any = {
            index: indexName,
            mappings: {
                properties: {
                    vector: {
                        type: 'dense_vector',
                        dims: dimension,
                        index: true,
                        similarity: similarity,
                    },
                    metadata: {
                        type: 'object',
                        enabled: true,
                        dynamic: true, // Allows flexible metadata structures
                    },
                },
            },
        };

        // Only configure shards/replicas for non-serverless deployments
        // Serverless manages infrastructure automatically
        if (!isServerless) {
            indexConfig.settings = {
                number_of_shards: 1,
                number_of_replicas: 0, // Increase for production HA deployments
            };
        }

        await this.client.indices.create(indexConfig);

        this.logger?.info(
            `Created ${isServerless ? 'Serverless' : 'Standard'} Elasticsearch index "${indexName}"`,
            { dimension, metric, similarity }
        );
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to create index "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to create index "${indexName}": ${errorMessage}`);
    }
}<h3>상호 작용 후 새 메모리 또는 노트 저장하기</h3><p>이 함수는 각 상호 작용 후에 생성된 새 임베딩을 메타데이터와 함께 가져온 다음 Elastic의 <code>bulk</code> API를 사용하여 인덱스에 삽입하거나 업데이트합니다. <code>bulk</code> API는 여러 개의 쓰기 작업을 단일 요청으로 그룹화하여 인덱싱 성능을 개선함으로써 에이전트의 메모리가 계속 증가함에 따라 업데이트가 효율적으로 유지되도록 합니다.</p>async upsert(params: UpsertVectorParams): Promise&lt;string[]&gt; {
    const { indexName, vectors, metadata = [], ids } = params;

    try {
        // Generate unique IDs if not provided
        const vectorIds = ids || vectors.map((_, i) =&gt; 
            `vec_${Date.now()}_${i}_${Math.random().toString(36).substr(2, 9)}`
        );

        const operations = vectors.flatMap((vec, index) =&gt; [
            { index: { _index: indexName, _id: vectorIds[index] } },
            {
                vector: vec,
                metadata: metadata[index] || {},
            },
        ]);

        const response = await this.client.bulk({
            refresh: true,
            operations,
        });

        if (response.errors) {
            const erroredItems = response.items.filter((item: any) =&gt; item.index?.error);
            const erroredIds = erroredItems.map((item: any) =&gt; item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) =&gt; ({
                id: item.index?._id,
                error: item.index?.error?.reason || item.index?.error,
                type: item.index?.error?.type
            }));
            
            const errorMessage = `Failed to upsert ${erroredIds.length}/${vectors.length} vectors`;
            console.error(`${errorMessage}. Sample errors:`, JSON.stringify(errorDetails, null, 2));
            this.logger?.error(errorMessage, { 
                failedCount: erroredIds.length, 
                totalCount: vectors.length,
                sampleErrors: errorDetails 
            });
            
            // Still return successfully inserted IDs
            const successfulIds = vectorIds.filter((id, idx) =&gt; 
                !erroredIds.includes(id)
            );
            
            if (successfulIds.length === 0) {
                throw new Error(`${errorMessage}. All operations failed. See logs for details.`);
            }
            
            return successfulIds;
        }

        this.logger?.info(`Successfully upserted ${vectors.length} vectors to "${indexName}"`);
        return vectorIds;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
    }
}<h3>시맨틱 리콜을 위해 유사한 벡터 쿼리하기</h3><p>이 기능은 시맨틱 리콜 기능의 핵심입니다. 에이전트는 벡터 검색을 사용하여 인덱스 내에서 유사한 저장된 임베딩을 찾습니다.</p>async query(params: QueryVectorParams&lt;any&gt;): Promise&lt;QueryResult[]&gt; {
    const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;

    try {
        const knnQuery: any = {
            field: 'vector',
            query_vector: queryVector,
            k: topK,
            num_candidates: Math.max(topK * 10, 100), // Search more candidates for better recall
        };

        // Apply metadata filters if provided
        if (filter) {
            knnQuery.filter = this.buildElasticFilter(filter);
        }

        const sourceFields = ['metadata'];
        if (includeVector) {
            sourceFields.push('vector');
        }

        const response = await this.client.search({
            index: indexName,
            knn: knnQuery,
            size: topK,
            _source: sourceFields,
        });

        const results = response.hits.hits.map((hit: any) =&gt; ({
            id: hit._id,
            score: hit._score || 0,
            metadata: hit._source?.metadata || {},
            vector: includeVector ? hit._source?.vector : undefined,
        }));

        this.logger?.debug(`Query returned ${results.length} results from "${indexName}"`);
        return results;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
    }
}<p>내부를 들여다보세요:</p><ul><li><p>Elasticsearch에서 <code>knn</code> API를 사용하여 <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k-nearest neighbors) 쿼리를 실행합니다.</p></li><li><p>입력 쿼리 벡터와 유사한 상위 K개의 벡터를 검색합니다.</p></li><li><p>선택적으로 메타데이터 필터를 적용하여 결과 범위를 좁힐 수 있습니다(예: 특정 카테고리 또는 시간 범위 내에서만 검색).</p></li><li><p>문서 ID, 유사도 점수, 저장된 메타데이터를 포함한 구조화된 결과를 반환합니다.</p></li></ul><h2>지식창고 만들기</h2><p>이제 <code>ElasticVector</code> 통합을 통해 Mastra와 Elasticsearch 간의 연결을 확인했으니, 지식 에이전트 자체를 생성해 보겠습니다.</p><p><code>agents</code> 폴더 안에 <code>knowledge-agent.ts</code> 이라는 파일을 만듭니다. 환경 변수를 연결하고 Elasticsearch 클라이언트를 초기화하는 것으로 시작할 수 있습니다.</p>import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { Client } from '@elastic/elasticsearch';
import { ElasticVector } from '../stores/elastic-store';
import dotenv from "dotenv";

dotenv.config();

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;

//Error check for undefined credentials
if (!ELASTICSEARCH_ENDPOINT || !ELASTICSEARCH_API_KEY) {
  throw new Error('Missing Elasticsearch credentials');
}

//Check to see if a connection can be established
const testClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { 
    apiKey: ELASTICSEARCH_API_KEY 
  },
});

try {
  await testClient.ping();
  console.log('Connected to Elasticsearch successfully');
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error('Failed to connect to Elasticsearch:', error.message);
  } else {
    console.error('Failed to connect to Elasticsearch:', error);
  }
  process.exit(1);
}
//Initialize the Elasticsearch vector store
const vectorStore = new ElasticVector({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
//Optional: Explicitly set to true if using Elasticsearch Serverless to skip auto-detection and improve startup time
//isServerless: true,
});<p>여기, 우리:</p><ul><li><p><code>dotenv</code> 을 사용하여 <code>.env</code> 파일에서 변수를 로드합니다.</p></li><li><p>Elasticsearch 자격 증명이 올바르게 주입되고 있는지 확인하면 클라이언트에 성공적으로 연결할 수 있습니다.</p></li><li><p><code>ElasticVector</code> 생성자에 Elasticsearch 엔드포인트와 API 키를 전달하여 앞서 정의한 벡터 저장소의 인스턴스를 생성합니다.</p></li><li><p>선택적으로 Elasticsearch 서버리스를 사용하는 경우 <code>isServerless: true</code> 을 지정합니다. 이렇게 하면 자동 감지 단계를 건너뛰고 시작 시간이 단축됩니다. 이 옵션을 생략하면 처음 사용할 때 어댑터가 자동으로 배포 유형을 감지합니다.</p></li></ul><p>다음으로 Mastra의 <code>Agent</code> 클래스를 사용하여 에이전트를 정의할 수 있습니다.</p>export const knowledgeAgent = new Agent({
    name: 'KnowledgeAgent',
    instructions: 'You are a helpful knowledge assistant.',
    model: openai('gpt-4o'),
    memory: new Memory({

        vector: vectorStore,

        //embedder used to create embeddings for each message
        embedder: 'openai/text-embedding-3-small',

        //set semantic recall options
        options: {
            semanticRecall: {
                topK: 3, // retrieve 3 similar messages
                messageRange: 2, // include 2 messages before/after each match
                scope: 'resource',
            },
        },
    }),
});<p>정의할 수 있는 필드는 다음과 같습니다:</p><ul><li><p><code>name</code> 및 <code>instructions</code>: 아이덴티티와 기본 기능을 부여합니다.</p></li><li><p><code>model</code>: <code>@ai-sdk/openai</code> 패키지를 통해 OpenAI의 <code>gpt-4o</code> 를 사용하고 있습니다.</p></li><li><p><code>memory</code>:</p><ul><li><p><code>vector</code>: Elasticsearch 저장소를 가리키므로 임베딩이 저장되고 거기에서 검색됩니다.</p></li><li><p><code>embedder</code>: 임베딩 생성에 사용할 모델</p></li><li><p><code>semanticRecall</code> 옵션은 리콜 작동 방식을 결정합니다:</p><ul><li><p><code>topK</code>: 검색할 의미적으로 유사한 메시지 수입니다.</p></li><li><p><code>messageRange</code>: 각 경기에 포함할 대화 분량입니다.</p></li><li><p><code>scope</code>: 메모리 경계를 정의합니다.</p></li></ul></li></ul></li></ul><p>거의 다 끝났습니다. 새로 생성된 에이전트를 Mastra 구성에 추가하기만 하면 됩니다. <a href="http://index.ts/"><code>index.ts</code></a> 라는 파일에서 지식 에이전트를 가져와서 <code>agents</code> 필드에 삽입합니다.</p>export const mastra = new Mastra({
  agents: { knowledgeAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },
});<p>다른 필드에는 다음이 포함됩니다:</p><ul><li><p><code>storage</code>: 실행 기록, 통합 가시성 메트릭, 점수 및 캐시를 위한 Mastra의 내부 데이터 저장소입니다. Mastra 스토리지에 대한 자세한 내용은 <a href="https://mastra.ai/docs/server-db/storage">여기를</a> 참조하세요.</p></li><li><p><code>logger</code>: Mastra는 경량 구조화된 JSON 로거인 <a href="https://github.com/pinojs/pino">Pino를</a> 사용합니다. 상담원 시작 및 중지, 도구 호출 및 결과, 오류, LLM 응답 시간 등의 이벤트를 캡처합니다.</p></li><li><p><code>observability</code>: 상담원의 AI 추적 및 실행 가시성을 제어합니다. 추적합니다:</p><ul><li><p>각 추론 단계의 시작/종료</p></li><li><p>어떤 모델 또는 도구를 사용했는지.</p></li><li><p>입력 및 출력.</p></li><li><p>점수 및 평가</p></li></ul></li></ul><h3>Mastra Studio로 에이전트 테스트</h3><p>축하합니다! 여기까지 왔다면 이 에이전트를 실행하여 시맨틱 리콜 기능을 테스트할 준비가 된 것입니다. 다행히도 Mastra는 기본 제공 채팅 UI를 제공하므로 자체적으로 구축할 필요가 없습니다.</p><p>Mastra 개발 서버를 시작하려면 터미널을 열고 다음 명령을 실행합니다:</p>npm run dev<p>서버를 처음 번들링하고 시작하면 플레이그라운드의 주소가 제공됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="Playground의 서버 주소" /><p>이 주소를 브라우저에 붙여넣으면 마스트라 스튜디오로 이동합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="플레이그라운드 주소를 붙여넣어 마스트라 스튜디오에 액세스하기" /><p><code>knowledgeAgent</code> 옵션을 선택하고 채팅을 시작합니다.</p><p>모든 것이 올바르게 연결되었는지 간단히 테스트하려면 "팀은 10월의 판매 실적이 12% 증가했다고 발표했습니다%, 주로 기업 리뉴얼에 힘입은 것입니다."와 같은 정보를 입력합니다. 다음 단계는 미드 마켓 고객으로 범위를 넓히는 것입니다." 다음으로 새 채팅을 시작하고 "다음에 어떤 고객 세그먼트에 집중해야 한다고 했나요?"와 같은 질문을 하세요. 지식 상담원은 첫 번째 채팅에서 제공한 정보를 기억할 수 있어야 합니다. 다음과 같은 응답이 표시되어야 합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfec3266e81a7213b/6a16f7b92b835f6f70f4afe2/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" alt="Mastra Studio에서 지식 상담원과 채팅하기 - 상담원이 정보를 불러올 수 있습니다." /><p>이와 같은 응답을 보면 에이전트가 이전 메시지를 Elasticsearch에 임베딩으로 성공적으로 저장하고 나중에 벡터 검색을 사용하여 검색했다는 뜻입니다.</p><h3>상담원의 장기 기억 저장소 검사하기</h3><p>Mastra Studio의 상담원 구성에서 <code>memory</code> 탭으로 이동합니다. 이를 통해 상담원이 시간이 지남에 따라 학습한 내용을 확인할 수 있습니다. Elasticsearch에 포함되고 저장되는 모든 메시지, 응답, 상호 작용은 이 장기 기억의 일부가 됩니다. 과거 상호작용을 의미론적으로 검색하여 상담원이 이전에 학습한 정보나 컨텍스트를 빠르게 찾을 수 있습니다. 이는 기본적으로 에이전트가 시맨틱 리콜 중에 사용하는 것과 동일한 메커니즘이지만, 여기서 직접 검사할 수 있습니다. 아래 예시에서는 '판매'라는 용어를 검색하여 판매에 관한 내용이 포함된 모든 상호작용을 반환하고 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte428134d7bf2a43a/6a16f7bbb0367d185872bad3/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" alt="지식 에이전트 장기 기억 저장소를 검사하는 방법" /><h2>결론</h2><p>Mastra와 Elasticsearch를 연결하면 컨텍스트 엔지니어링의 핵심 계층인 메모리를 에이전트에게 제공할 수 있습니다. 시맨틱 리콜을 통해 상담원은 시간이 지남에 따라 컨텍스트를 구축하여 학습한 내용을 기반으로 응답할 수 있습니다. 이는 보다 정확하고 안정적이며 자연스러운 상호작용을 의미합니다.</p><p>이 초기 통합은 시작에 불과합니다. 여기서 동일한 패턴으로 과거 티켓을 기억하는 지원 상담원, 관련 문서를 검색하는 내부 봇, 대화 중에 고객 세부 정보를 기억할 수 있는 AI 어시스턴트 등을 만들 수 있습니다. 또한, 가까운 시일 내에 이 페어링이 더욱 원활하게 이루어질 수 있도록 공식적인 Mastra 통합을 위해 노력하고 있습니다.</p><p>여러분이 다음에 무엇을 만들지 기대가 됩니다. 한 번 사용해보시고 <a href="https://mastra.ai/">Mastra와</a> 그 메모리 기능을 살펴보고 발견한 내용을 커뮤니티와 자유롭게 공유하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[개발자 경험]]></category>
    <category><![CDATA[통합]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09afdbff05603865/6a16f7bd839dfabbf2dcfcb5/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>