<?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[에이전틱 AI - 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[에이전틱 AI - 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/agentic-ai</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/agentic-ai</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/agentic-ai.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 00:42:03 GMT</lastBuildDate>
  <item>
    <title><![CDATA[137,000명, 인간 결정 0건: Elasticsearch를 사용한 에이전틱 재난 대응]]></title>
    <description><![CDATA[허리케인이 닥쳤을 때 Kibana 탐지 규칙, 워크플로우, AI 에이전트로 디스패처 없이 7개 기지에 걸쳐 137,000명의 군 인력을 자동으로 재배치한 방법을 확인해 보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic이 사람의 개입(휴먼 인 더 루프) 없이 7개 기지에 걸친 군 병력 137,000명의 자동 대피를 조율했습니다. 카테고리 4 허리케인이 햄프턴 로즈 해안을 강타했습니다. Elasticsearch의 지리 공간 보강 기능은 인덱스 시점에 영향 구역 내의 모든 기지를 식별합니다. Kibana 탐지 규칙이 실행됩니다. 워크플로우가 AI 에이전트 대화를 시작합니다. 에이전트는 용량, 거리 및 군종 호환성을 종합적으로 추론한 후, 단 한 번의 처리 과정으로 16건의 대피 및 수집 알림을 발송합니다. 원시 GDACS 이벤트에서 조율된 조치까지, 자동으로 이뤄집니다.</p><p>매년 자연재해로 인해 응급 상황 담당자, 군 지휘관, 공공 안전 담당자들은 촉박한 시간 내에 중대한 결정을 내려야 합니다. 이러한 결정은 전통적으로 전화 연락망, 스프레드시트, 수십 명에게 분산된 제도적 지식에 의존합니다. 조정에 따른 오버헤드만으로도 중요한 시간이 소요됩니다.</p><p>이 게시물은 위협을 탐지하고, 물류를 추론하며, 자동으로 조처하는 재난 대응에 있어 Elastic이 반응형 에이전틱 조정 시스템을 지원하는 방법을 보여줍니다. 이를 구체화하기 위해 다음과 같은 시뮬레이션을 구축했습니다. 햄프턴 로즈 해안선을 위협하는 가상의 카테고리 4 허리케인이 7개 군사 기지에 걸쳐 137,000명 이상의 인원에 대한 자동 재배치를 트리거합니다.</p><p><strong>면책 조항:</strong> <strong>이는 시연 목적으로 제작된 전적으로 가상의 시나리오입니다. </strong>허리케인 ELARA-26은 존재하지 않습니다. 기지 위치는 실제 공개 지리 데이터(미 국방부[DoD] 군사 기지, 훈련장 및 훈련 지역[MIRTA] 데이터 세트)를 기반으로 하지만 인원수, 수용 인원, 자산, 연락처 이메일, 임무 프로필과 같은 모든 작전 데이터는 완전히 허구입니다. 이 데모의 어떤 내용도 실제 군사 준비 태세, 역량 또는 작전 절차를 반영하지 않습니다.</p><h2>자동화된 재난 대응에 지리공간 및 에이전틱 조정이 필요한 이유</h2><p>자연재해가 중요 인프라를 위협하면 다음과 같은 즉각적인 조정 과제가 발생합니다.</p><ul><li><p>어떤 시설이 영향 구역에 있습니까?</p></li><li><p>몇 명의 인원이 이동해야 합니까?</p></li><li><p>어디로 이동할 수 있으며, 해당 시설에 수용 능력이 있습니까?</p></li><li><p>지금 누구에게 알려야 합니까?</p></li></ul><p>이 질문들은 기다려 주지 않습니다. 답도 마찬가지입니다.</p><h2>파이프라인 배포: 필수 구성 요소 및 설정</h2><p><a href="https://github.com/tehbooom/elastic_natural_disaster/blob/main/README.md">예제 리포지토리의 여기</a>에 있는 지침에 따라 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/connect-self-managed-cluster-to-eis#set-up-eis-with-cloud-connect">Cloud Connect</a>를 통해 Elastic Inference Service(EIS)를 사용하는 로컬 Elastic 클러스터를 배포하세요.</p><h2>Elasticsearch 에이전틱 재난 대응 파이프라인 작동 원리</h2><p>파이프라인에는 엔드 투 엔드 방식으로 함께 작동하는 7개의 계층이 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf09bfae87ab35bec/6a4693ef31bdbbe3ef8b33ae/61814cddea0409162fb057c2113e0a496c105238-1999x275.png" alt="Pipeline flowchart Alt text: Horizontal flowchart with seven labeled boxes connected by arrows: GDACS feed, ingest pipeline, enrich (geo_shape), detection rule, workflow, AI agent, and email." /><ol><li><p><strong>데이터 수집:</strong> Elasticsearch로 전송된 Global Disaster Alert and Coordination System(GDACS) 재난 이벤트</p></li><li><p><strong>수집 파이프라인</strong>: GeoJSON이 수집되어 Elastic Common Schema (ECS)로 정규화됩니다.</p></li><li><p><strong>지리 공간 보강:</strong> 이벤트의 영향 영역 폴리곤이 인덱싱된 군사 기지 경계와 매칭됩니다.</p></li><li><p><strong>경보:</strong> 재난이 기지과 교차할 때 Kibana 탐지 규칙이 실행됩니다.</p></li><li><p><strong>워크플로우 자동화:</strong> 경보는 AI 에이전트 대화를 시작하는 Kibana 워크플로우를 트리거합니다.</p></li><li><p><strong>AI 추론:</strong> 에이전트는 영향을 받는 시설, 해당 시설의 자산 및 가장 가까운 지원 시설을 분석하여 모든 자산과 인력의 재배치를 결정합니다.</p></li><li><p><strong>이메일 알림</strong>: 에이전트는 입출입 인력 및/또는 자산에 대해 모든 수신자에게 이메일을 발송합니다.</p></li></ol><p>각 계층을 살펴보겠습니다.</p><h2>1단계: 지리적 경계가 있는 군사 기지 색인</h2><p>기반은 <a href="https://source.coop/seerai/hifld/military-installations-ranges-and-training-areas-mirta-dod-sites---boundaries">source.coop/seerai/hifld</a>의 DoD MIRTA 데이터 세트입니다. 이 데이터 세트는 각 기지에 대해 Point 유형의 geo_shape을 제공하며, 전체 경계 폴리곤이 아닌 중심 좌표를 제공합니다.</p><p>mitra-facilities 인덱스의 각 기지 문서는 MIRTA가 제공하는 범위를 넘어 운영 프로필 데이터(모두 가상)로 보강됩니다.</p>{
  "entity_name": "Naval Station Norfolk",
  "branch_of_service": "Navy",
  "mission_function_type": "fleet_support",
  "personnel_count": 50000,
  "housing_capacity": 55000,
  "temporary_housing_capacity": 10000,
  "logistics_capabilities": ["fuel", "airlift", "sealift", "medical"],
  "available_assets": [
    { "type": "helicopters", "count": 24 },
    { "type": "transport_vehicles", "count": 150 }
  ],
  "contact_email": "norfolk.ops@navy.mil.gov.fake",
  "operational_status": "act",
  "is_joint_base": false,
  "entity_geo_location": { "type": "polygon", "coordinates": [...] }
}<p>이 풍부한 인덱스는 AI 에이전트가 단순히 "인근 기지는 다음과 같습니다"가 아니라 "가용 수용 인원, 호환되는 임무 유형, 유입되는 자산을 수용할 물류를 갖춘 기지는 다음과 같습니다"와 같은 지능적인 할당 결정을 내릴 수 있도록 지원합니다.</p><h2>2단계: GDACS 이벤트 수집 및 정규화</h2><p>GDACS는 지진, 열대 저기압, 홍수, 산불, 화산 및 가뭄에 대한 실시간 GeoJSON을 게시합니다. Elastic은 이 피드를 데이터 스트림(logs-gdacs.events-*)으로 수집합니다. 여기에 원시 GeoJSON을 ECS 필드로 정규화하는 사용자 지정 수집 파이프라인이 사용됩니다.</p><p>GDACS 수집 파이프라인은 주목할 만한 몇 가지 작업을 수행합니다.</p><p><strong>지오메트리 추출:</strong> 중심점은 지도에 표시할 수 있도록 geo_point로 저장되고, 영향 폴리곤은 gdacs.affected_area에 geo_shape으로 저장되며, 이 필드는 나중에 교차 쿼리에 사용됩니다.</p><p><strong>심각도 정규화:</strong> 재난 유형마다 심각도 척도가 다릅니다. 열대성 저기압은 풍속 km/h로 측정되며, 지진은 리히터 규모로 측정됩니다. 파이프라인은 모든 재난을 정규화된 0–100 점수로 맵핑합니다.</p>// 수집 파이프라인의 Painless 스니펫
if (type == 'TC') {
  norm = Math.min(100.0, Math.max(0.0, (val - 40.0) / 2.6));
} else if (type == 'EQ') {
  norm = Math.min(100.0, Math.max(0.0, (val - 4.0) * 20.0));
}<p>정규화된 심각도 점수는 탐지 규칙에서 경보 심각도 매핑에 사용되는 severity_level 레이블(low, medium, high, critical)에 매핑됩니다.</p><p><strong>ECS 정렬:</strong> event.kind: alert, event.category: threat, event.start/event.end에 매핑된 타임스탬프, 및 중복 제거를 위한 안정적인 핑거프린트 기반 _id.</p><h2>3단계: 지리공간 보강: 인덱스 시점에 영향을 받는 시설 찾기</h2><p>Elasticsearch의 geo_match enrich 정책은 인덱스 시 재해 폴리곤을 모든 기지 경계와 매칭하므로 쿼리 시 조인이 필요하지 않습니다. 검색 시 쿼리를 실행하는 대신, 수집 파이프라인의 <strong>enrich 프로세서</strong>를 사용하여 <em>문서가 색인될 때</em> 재난의 영향 폴리곤을 모든 기지 경계와 매칭합니다.</p><p>enrich 정책은 geo_match 정책입니다.</p>{
  "geo_match": {
    "indices": "mitra-facilities",
    "match_field": "entity_geo_location",
    "enrich_fields": [
      "entity_name",
      "entity_type",
      "entity_station_number",
      "entity_geo_city_name",
      "entity_geo_region_name"
    ]
  }
}<p>프로세서는 수집 파이프라인의 끝에서 실행됩니다.</p>{
  "enrich": {
    "policy_name": "facilities-geo",
    "field": "gdacs.affected_area",
    "target_field": "affected_facilities",
    "shape_relation": "INTERSECTS",
    "max_matches": 128
  }
}<p>INTERSECTS는 재난 폴리곤과 경계가 닿거나 겹치는 모든 기지는 물론 부분적인 교차까지도 포착합니다. 그 결과 모든 GDACS 이벤트 문서는 영향 구역에 어떤 기지가 있는지 정확히 알려주는 affected_facilities 중첩 배열과 함께 저장됩니다. 조인 쿼리는 필요하지 않습니다.</p><h2>4단계: 탐지 규칙: 시설 영향에 대한 경보</h2><p>Kibana 탐지 규칙은 logs-gdacs.events-* 데이터 스트림을 모니터링하며 GDACS 이벤트가 하나 이상의 영향을 받는 시설로 보강될 때 실행됩니다.</p>쿼리: affected_facilities: { entity_name: * }<p>이 규칙은 매시간 일정으로 실행되며(now-1h부터 now까지의 기간 대상), 동적 심각도 매핑을 사용합니다. gdacs.severity_level 필드는 수집 파이프라인에서 계산되어 경보 심각도를 자동으로 구동합니다.</p><p>경보 심각도 또한 필드 매핑을 통해 위험 점수를 결정합니다.</p>"risk_score_mapping": [
  {
    "field": "gdacs.normalized_severity",
    "operator": "equals",
    "value": ""
  }
]<p>규칙이 실행되면 기지 이름, 유형 및 위치로 보강된 affected_facilities 배열을 포함한 전체 경보 컨텍스트가 Kibana 워크플로우로 다운스트림 전달됩니다.</p><h2>5단계: 워크플로우 자동화: 경고를 에이전트에 연결</h2><p>Kibana 워크플로우는 탐지에서 응답으로의 인계를 처리합니다. 자연재해 응답 워크플로우는 경보에 의해 트리거됩니다.</p>triggers:
  - type: alert
steps:
  - name: start_convo
    type: kibana.request
    with:
      method: "POST"
      path: "/api/agent_builder/converse"
      body:
        agent_id: "mitra.response"
        input: "신규 자연재해 경보: {{ event.alerts | json }}"<p>전체 경보 페이로드(재해 유형, 심각도, 영향 지역 및 영향을 받은 기지 목록)가 초기 컨텍스트로 AI 에이전트에 전달됩니다. 그다음부터는 에이전트가 이어받아 처리합니다.</p><h2>6단계: AI 에이전트: 데이터에서 조율된 조치로</h2><p>mitra.response 에이전트는 전체 경보 페이로드를 받아 단일 에이전트 루프 내에서 범위를 평가하고, 수용 시설을 찾고, 인력을 배정하며, 대피 및 수용 알림을 발송하는 등의 모든 작업을 사람의 개입 없이 수행합니다.</p><p>에이전트는 두 가지 도구를 사용할 수 있습니다.</p><ul><li><p><strong>mitra.nearest_facility</strong>는 geo_shape 쿼리를 사용하여 mitra-facilities 인덱스에 쿼리 작업을 수행하고, 주어진 좌표로부터의 거리를 기준으로 정렬하여 가용 용량이 있는 인근 활성 기지를 최대 50개까지 반환합니다.</p></li><li><p><strong>mitra.send_email</strong>은 시설 객체의 JSON 배열을 순회하고 형식이 지정된 대피 또는 수용 알림을 발송합니다.</p></li></ul><p>에이전트의 지침 세트는 명확한 워크플로우를 정의합니다.</p><ol><li><p><strong>상황을 평가합니다.</strong> 경보의 구문을 분석하고, 영향을 받는 시설을 식별하며, 재난 범위를 결정합니다.</p></li><li><p><strong>이전해야 할 항목을 목록화합니다.</strong> 인원수, 중요 자산, 시설별 수용 요건 등입니다.</p></li><li><p><strong>대상 기지를 찾습니다.</strong> mitra.nearest_facility를 영향을 받는 각 기지에 호출하고 위험 구역에 여전히 있는 기지를 필터링하여 제외합니다.</p></li><li><p><strong>할당 결정을 내립니다.</strong> 단일 대 다중 시설 솔루션, 군종 호환성, 수용 인원, 자산 지원을 면밀히 추론합니다.</p></li><li><p><strong>조정 이메일을 발송합니다.</strong> 출발 시설에 대피 명령을 발송하고 수용 시설에 수용 통지를 발송합니다.</p></li><li><p><strong>요약 보고를 생성합니다. </strong>검토를 위해 영향을 받는 모든 시설, 총 인원, 이동된 자산, 대상 시설 및 모든 우려 사항에 대한 간단한 요약을 채팅에 생성합니다.</p></li></ol><p>에이전트의 할당 로직은 실제 제약 조건을 따릅니다. 수용 인원을 초과하지 않고, 가능한 경우 동일 군종 내 재배치를 우선하며, 여러 군종에서 초과 인원이 발생할 경우 합동 기지를 활용하고, 이동 시간을 최소화할 수 있도록 거리를 우선적으로 고려합니다.</p><h3>최근접 시설 도구</h3><p>기본 워크플로우 쿼리는 원형 필터가 포함된 geo_shape와 _geo_distance 정렬을 사용합니다.</p>"query": {
  "bool": {
    "filter": [
      {
        "geo_shape": {
          "entity_geo_location": {
            "shape": {
              "type": "circle",
              "coordinates": [{{ inputs.lon }}, {{ inputs.lat }}],
              "radius": "5000km"
            },
            "relation": "intersects"
          }
        }
      },
      { "term": { "operational_status.keyword": "act" } }
    ]
  }
},
"sort": [
  {
    "_geo_distance": {
      "entity_geo_point": { "lat": {{ inputs.lat }}, "lon": {{ inputs.lon }} },
      "order": "asc",
      "unit": "km"
    }
  }
],
"script_fields": {
  "available_capacity": {
    "script": {
      "source": "Math.max(0, doc['housing_capacity'].value - doc['personnel_count'].value)"
    }
  }
}<p>가용 인원은 수용 인원에서 현재 인원수를 뺀 값을 계산하는 스크립트 필드를 통해 쿼리 시 계산됩니다. 에이전트는 한도를 초과하지 않고 여러 대상에 인원을 할당하기 위해 이를 사용합니다.</p><h2>허리케인 ELARA-26: 137,000명 인력에 대한 에이전틱 조율, 엔드 투 엔드</h2><p>허리케인 ELARA-26은 버지니아주 햄프턴 로즈 지역에 상륙할 것으로 예상되는 카테고리 4 폭풍(최대 풍속 213km/h)입니다. GDACS 이벤트가 수집되면, 영향 받는 지역의 폴리곤이 해당 지역의 7개 주요 군사 기지과 교차합니다. 탐지 규칙이 실행됩니다. 워크플로우가 에이전트 대화를 시작합니다.</p><p>단일 에이전틱 루프 내에서 에이전트가 완료한 작업:</p><ul><li><p>영향 구역 내에서 총 137,372명의 인원이 포함된 시설 7곳을 식별했습니다.</p></li><li><p>폭풍 경로 외부의 수용 시설을 찾기 위해 mitra.nearest_facility를 호출했습니다.</p></li><li><p>가용 수용 인원 및 거리를 기준으로 9개 수용 시설에 인력을 분산 배치했습니다.</p></li><li><p>영향을 받은 7개 기지 모두에 대피 명령을 생성하여 발송했습니다.</p></li><li><p>9개 수용 시설 모두에 접수 알림을 생성하여 발송했습니다.</p></li><li><p>아래와 유사한 전체 조정 요약이 생성되었습니다.</p></li></ul><p><strong>대피한 시설:</strong></p><p>시설</p><p>인력(명)</p><p>노퍽 해군 기지</p><p>50,000</p><p>리틀 크릭-포트 스토리 합동원정기지</p><p>18,000</p><p>오시아나 해군 항공 기지</p><p>15,355</p><p>오세아나 해군 항공 기지 댐 넥 별관</p><p>17,509</p><p>NG 주 군사 보호구역 캠프 펜들턴</p><p>9,707</p><p>랭글리-유스티스 합동기지</p><p>15,000</p><p>요크타운 해군 무기 기지</p><p>11,801</p><p><strong>수용 시설:</strong></p><p>시설</p><p>거리</p><p>유입 인력(명)</p><p>포트 그렉-아담스</p><p>97km</p><p>~40,000</p><p>콴티코 해병대 기지</p><p>148km</p><p>~30,000</p><p>인디언 헤드 해군 지원 기지</p><p>151km</p><p>~30,000</p><p>앤드루스 합동기지</p><p>180km</p><p>~30,000</p><p>패턱센트 리버 해군 항공기지</p><p>141km</p><p>~10,000</p><p>NG MTA 캠프 버트너</p><p>174km</p><p>~5,000</p><p>NG 베서니 비치 훈련장</p><p>209km</p><p>~4,707</p><p>리바나 스테이션</p><p>140km</p><p>~7,500</p><p>국방 종합 보급 센터</p><p>22km</p><p>~6,000</p><p>재배치된 자산에는 수송 차량, 헬리콥터, 순찰정, 의료 부대, 공병 차량, 발전기, 급수 트레일러, 대피소 키트 및 통신 시스템이 포함됩니다.</p><h3>자동화된 이메일 알림</h3><p>에이전트가 할당 계획을 확정한 후, mitra.send_email을 호출하여 단 한 번의 처리 과정으로 16개의 이메일을 발송했습니다. 즉, 영향을 받는 7개 모든 기지에 대한 대피 명령과 9개 모든 수용 기지에 입소 통지가 발송된 것입니다. 각 메시지에는 도착 기지, 유입 인원수, 이동할 자산 및 조정 담당 연락처가 포함되었습니다. 전화 연락망을 거치느라 몇 시간이나 걸렸을 작업이 에이전트의 추론이 완료되는 즉시 자동으로 완료되었습니다.</p><h3>RAG 및 정책 그라운딩을 통한 에이전틱 재난 대응 확장</h3><p>이 데모는 인원 수, 거리, 운영 상태와 같은 정형 데이터만을 기반으로 합니다. Elastic의 시맨틱 검색 및 검색 증강 생성(RAG) 기능은 두 가지 추가 기능을 통해 에이전트를 훨씬 더 스마트하게 만들 수 있습니다.</p><p><strong>과거 응답 검색:</strong> 과거 사후 조치 보고서, 연방재난관리청(FEMA) 인시던트 요약 및 재난 대응 기록을 벡터 임베딩으로 인덱싱합니다. 새 이벤트가 발생하면 에이전트는 유사한 이벤트가 어떻게 처리되었는지 시맨틱 검색하여, 인원 계산만이 아닌 조직 지식을 바탕으로 할당 결정을 내릴 수 있도록 합니다.</p><p><strong>정책 및 교리 기반:</strong> 국방부 비상 관리 지침, 기지 운영 연속성 계획 및 지휘관 지침을 인덱싱합니다. 에이전트는 응답에 적용되는 실제 정책을 검색하고 인용할 수 있으므로, 모든 결정이 추론이 아닌 교리에 근거하도록 보장합니다.</p><p>둘 다 동일한 Elastic 네이티브 접근 방식을 따릅니다, 추론 파이프라인이 인덱스 생성 시점에 임베딩을 생성하고, 시맨틱 검색 도구가 에이전트에 노출됩니다. 조정 파이프라인은 동일하게 유지됩니다. 에이전트는 계속 더 똑똑해집니다.</p><h2>Elasticsearch가 에이전틱 공공 부문 응답에 적합한 플랫폼인 이유</h2><p>Elasticsearch는 챗봇이 아닙니다. 대시보드도 아닙니다. 위협을 탐지하고, 복잡한 물류 문제를 추론하며, 인간의 개입 없이 137,000명의 이동을 조율한 반응형 에이전틱 워크플로우 시스템입니다. 이러한 결과는 기반이 되는 모든 기능이 단일 통합 플랫폼에 존재하기 때문에 가능합니다.</p><p>Elasticsearch의 지리 공간 지원(geo_point, geo_shape, 보강 정책 및 거리 기반 정렬)은 교차점 탐지와 시설 조회를 대규모로 가능하게 하는 공간 추론을 처리합니다. 시맨틱 검색과 벡터 임베딩은 에이전트를 사실에 기반하게 하여, AI 추론이 환각된 가정이 아닌 실제 데이터에 기반하도록 보장합니다. Kibana의 탐지 엔진, Workflows, Agent Builder 및 Agent Builder 도구는 외부 연결 코드 없이 원시 이벤트에서 조정된 작업으로 이어지는 파이프라인에 모든 것을 연결합니다.</p><p>Elastic만큼 이 모든 것을 통합하는 플랫폼은 없습니다. 실시간 색인, 지리 공간 정밀도, 시맨틱 검색 및 에이전트 기반 오케스트레이션을 모두 하나의 스택에서 제공하고 엔터프라이즈급 보안 및 통합 가시성을 기본 제공한다는 점은 이러한 기능 중 하나는 잘 수행하지만 나머지는 직접 통합해야 하는 도구와 Elastic의 차별점입니다.</p><h2>비상 관리, 소방, 법 집행 및 공중 보건을 위한 에이전틱 지리공간 응답</h2><p>사람, 시설, 실시간 이벤트가 교차하는 모든 곳에 동일한 아키텍처가 적용됩니다. 특정 데이터는 변경됩니다. 파이프라인은 변경되지 않습니다.</p><p><strong>비상 관리:</strong> FEMA 및 주 비상관리국은 대피소 위치, 대기 구역, 취약 계층을 수신되는 국립기상청(NWS) 악천후 폴리곤과 매핑하여 폭풍이 상륙하기 전에 자동화된 자원 사전 배치를 트리거할 수 있습니다.</p><p><strong>소방 및 응급 의료 서비스:</strong> 소방서는 산불 경계선 또는 건물 화재 클러스터에 유닛 위치 및 대응 영역을 오버레이하여, 적절한 장비를 갖춘 가장 가까운 가용 유닛으로 상호 지원 요청을 자동으로 라우팅할 수 있습니다.</p><p><strong>법 집행:</strong> 기관은 수동 분류를 기다리지 않고도 활성 인시던트 위치를 학교 구역, 중요 인프라, 경찰관 위치와 연관시켜 위치 기반 봉쇄 알림이나 리소스 배치를 트리거할 수 있습니다.</p><p><strong>공립학교 안전:</strong> 교육구는 캠퍼스 경계를 기준으로 실시간 위협 피드를 모니터링할 수 있습니다. 위협이 학교 경계와 교차하는 경우, 에이전트는 디스패처가 전화를 받기도 전에 관리 부서에 즉시 알리고, 봉쇄 통신을 시작하며, 법 집행 기관의 응답을 조율할 수 있습니다.</p><p><strong>공중 보건:</strong> 보건 당국은 질병 감시 데이터 또는 환경 위험 구역을 진료소 위치, 인구 밀도 레이어, 공급 창고 재고와 대조하여 리소스가 가장 필요한 곳으로 전달할 수 있습니다.</p><p>부문</p><p>사용 사례</p><p>Elastic 기능</p><p>비상 관리</p><p>NWS 악천후 폴리곤과 대피소 위치 매칭</p><p>geo_shape 보강 + Kibana 워크플로우</p><p>소방 및 EMS</p><p>산불 경계에 유닛 위치 오버레이</p><p>위치 기반 정보 라우팅 + 가장 가까운 시설 쿼리</p><p>법 집행</p><p>인시던트를 학교 구역 및 경찰관 위치에 상호 연관</p><p>위치 인식 경보 규칙 + 에이전트 디스패치</p><p>공립학교 안전</p><p>캠퍼스 경계에 대한 위협 피드 모니터링</p><p>탐지 규칙 + 자동화된 알림</p><p>공중 보건</p><p>위험 구역을 진료소 위치 및 보급소에 매칭</p><p>시맨틱 검색+지리공간 보강</p><p>데이터는 시나리오마다 다릅니다. 수집, 인덱스 시 보강, 교차점 탐지, 에이전틱 응답 트리거 및 조치라는 기본 패턴은 모두 동일합니다. Elastic은 공공 부문 조직에 한 번 구축한 후 어디서나 적용할 수 있는 플랫폼을 제공합니다.</p><p><em>이 게시물에서 설명된 모든 기능이나 성능의 출시와 일정은 Elastic의 단독 재량에 따라 결정됩니다. 현재 제공되지 않는 기능이나 성능은 예정된 시간에 출시되지 않을 수도 있으며 아예 제공되지 않을 수도 있습니다.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-agentic-disaster-response</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-agentic-disaster-response</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Kibana]]></category>
    <dc:creator><![CDATA[Alec Carpenter]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt969cad2694920de4/6a4693f37746672ad42675b5/cb292a501835472598dee30bef25c77afc54db6c-720x420.png" length="0" type="image/png"/>
    <pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mastra와 Elasticsearch로 에이전틱 AI 애플리케이션을 구축하는 방법]]></title>
    <description><![CDATA[실제 예제를 통해 Mastra와 Elasticsearch로 에이전틱 AI 애플리케이션을 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 글에서는 <a href="https://mastra.ai/">Mastra</a> TypeScript 프레임워크를 사용해 <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a>와 상호 작용하는 에이전틱 애플리케이션을 구축하는 방법을 자세히 살펴보겠습니다.</p><p>Elastic은 최근에 Elasticsearch를 벡터 데이터베이스로 지원하도록 추가하여 <a href="https://github.com/mastra-ai/mastra">mastra-ai/mastra</a> 오픈 소스 프로젝트에 기여했습니다. 이 새로운 기능을 통해 Mastra에서 Elasticsearch를 네이티브로 사용하여 임베딩을 저장할 수 있습니다. 벡터 기능 외에도 Elasticsearch는 컨텍스트 엔지니어링 요구 사항을 충족하기 위한 다양한 고급 기능을 제공합니다. (예: <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">하이브리드 검색 및 순위 재지정</a>)</p><p>이 글에서는 Elasticsearch를 사용하여 Retrieval-Augmented Generation(RAG) 아키텍처를 구현하는 에이전트 생성 과정을 자세히 설명합니다. Elasticsearch에 저장된 SF 영화 데이터 코퍼스와 상호 작용하는 에이전틱 접근법을 사용하는 데모 프로젝트를 선보일 것입니다. 이 프로젝트는 <a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a>에서 확인할 수 있습니다.</p><h2>Mastra</h2><p>Mastra는 에이전틱 AI 애플리케이션을 생성하기 위한 TypeScript 프레임워크입니다.</p><p>Mastra의 프로젝트 구조는 다음과 같습니다:</p>src/
├── mastra/
│   ├── agents/
│   │   └── weather-agent.ts
│   ├── tools/
│   │   └── weather-tool.ts
│   ├── workflows/
│   │   └── weather-workflow.ts
│   ├── scorers/
│   │   └── weather-scorer.ts
│   └── index.ts
├── .env.example
├── package.json
└── tsconfig.json<p>Mastra에서는 <a href="https://mastra.ai/docs/agents/overview">에이전트</a>, <a href="https://mastra.ai/docs/agents/using-tools">도구</a>, <a href="https://mastra.ai/docs/workflows/overview">워크플로우</a> 및 <a href="https://mastra.ai/docs/evals/overview">점수</a>를 구축할 수 있습니다.</p><p><strong>에이전트</strong>는 메시지를 입력으로 받아들이고 응답을 출력으로 생성하는 클래스입니다. 에이전트는 도구, 대형 언어 모델 (LLM), 메모리를 사용할 수 있습니다(그림 1).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f7484f7501997dc/6a170417cdacbffb5a7d28f6/f6aca2dcc7fcc45d25e06681649be1b2b7eb6781-706x721.png" alt="Mastra에서 에이전트가 작동하는 방식을 보여 주는 다이어그램." /><p>에이전트의 <strong>도구</strong>를 사용하면 웹 API와 통신하거나 Elasticsearch 쿼리와 같은 내부 작업을 수행하는 등 '외부 세계'와 상호 작용할 수 있습니다. <strong>메모리</strong> 구성 요소는 과거 입력과 출력을 포함하여 대화 기록을 저장하는 데 중요한 역할을 합니다. 이렇게 저장된 컨텍스트를 통해 에이전트는 과거 상호 작용을 활용하여 향후 질문에 대해 더 정보에 입각하고 정확도 높은 응답을 제공할 수 있습니다.</p><p><strong>워크플로우</strong>를 사용하면 단일 에이전트의 추론에 의존하는 대신 명확하고 구조화된 단계를 통해 복잡한 작업 순서를 정의할 수 있습니다(그림 2). 이를 통해 작업을 어떻게 세분화하는지, 데이터가 작업 간에 어떻게 이동하는지, 그리고 언제 무엇이 실행되는지를 완벽하게 제어할 수 있습니다. 워크플로우는 기본적으로 내장된 실행 엔진을 사용하여 실행되거나 <a href="https://mastra.ai/docs/deployment/workflow-runners">워크플로우 러너</a>에 배포할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd82ea661569e1018/6a170419dc55de3adde00cc9/0dce161cf7891207015dc87532b5b90df1822432-880x252.png" alt="Mastra의 워크플로우 예시입니다." /><p>Mastra에서는 모델 등급, 규칙 기반 및 통계적 방법을 사용하여 에이전트 출력을 평가하는 자동화된 테스트인 점수를 정의할 수 있습니다. 채점기는 <em>점수</em>를 반환합니다. 이 점수는 출력물이 평가 기준을 얼마나 잘 충족하는지를 정량화한 수치 값(일반적으로 0에서 1 사이)입니다. 이러한 점수를 통해 성능을 객관적으로 추적하고, 다양한 접근 방식을 비교하며, AI 시스템에서 개선이 필요한 영역을 식별할 수 있습니다. 채점기는 자신만의 프롬프트와 점수 매기기 기능으로 사용자 지정할 수 있습니다.</p><h2>Elasticsearch</h2><p>데모 프로젝트를 실행하려면 Elasticsearch 인스턴스가 실행 중이어야 합니다. <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가 설치되고 Mastra 통합 구성에 사용할 API 키가 생성됩니다.</p><p>API 키는 이전 명령어의 출력 결과로 표시되며, elastic-start-local 폴더 안에 있는 <strong>.env</strong> 파일에 저장됩니다.</p><h2>데모 설치 및 구성</h2><p>데모 프로젝트의 소스 코드가 포함된 <a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a> 리포지토리를 생성했습니다. 리포지토리에 기재된 예제는 Elasticsearch에서 문서를 검색하기 위한 RAG 아키텍처를 구현하는 에이전트를 Mastra에서 생성하는 방법을 보여 줍니다.</p><p>Elastic은 데모를 위해 SF 영화에 대한 데이터 세트를 제공했습니다. <a href="https://www.kaggle.com/datasets/rajugc/imdb-movies-dataset-based-on-genre/versions/2?select=scifi.csv">Kaggle</a>의 IMDb 데이터 세트에서 500개의 영화를 추출했습니다.</p><p>첫 번째 단계는 다음 명령어를 사용하여 npm으로 프로젝트의 의존성을 설치하는 것입니다.</p>npm install<p>그런 다음 설정 정보가 담길 <strong>.env</strong> 파일을 구성해야 합니다. 다음 명령을 사용하여 <strong>.env.example</strong> 파일에서 구조를 복사하여 이 파일을 생성할 수 있습니다.</p>cp .env.example .env<p>이제 누락된 정보를 추가하여 .env 파일을 편집할 수 있습니다.</p>OPENAI_API_KEY=
ELASTICSEARCH_URL=
ELASTICSEARCH_API_KEY=
ELASTICSEARCH_INDEX_NAME=scifi-movies<p>Elasticsearch 인덱스의 이름은 <strong><code>scifi-movies</code></strong>입니다. 원한다면 env 변수 <code>ELASTICSEARCH_INDEX_NAME</code>을(를) 사용하여 변경할 수 있습니다.</p><p>OpenAI를 임베딩 서비스로 사용했으므로 <code>OPENAI_API_KEY</code> env 변수에 OpenAI용 API 키를 제공해야 합니다.</p><p>이 예시에서 사용된 임베딩 모델은 <a href="https://developers.openai.com/api/docs/models/text-embedding-3-small">openai/text-embedding-3-small</a>이며, 임베딩 차원은 1,536입니다.</p><p>최종 답변 생성을 위해 비용을 절감할 수 있는 <a href="https://developers.openai.com/api/docs/models/gpt-5-nano">openai/gpt-5-nano</a> 모델을 사용했습니다.</p><p>RAG 아키텍처를 사용하면 답변의 근거를 찾는 무거운 작업을 검색 구성 요소(이 경우 Elasticsearch)가 처리하기 때문에, 상대적으로 성능이 낮고(따라서 저렴한) 최종 LLM 모델을 사용할 수 있습니다.</p><p>소형 LLM은 두 가지 주요 작업만 담당합니다.</p><ul><li><p><strong>쿼리 재구성/임베딩:</strong> 사용자의 자연어 질문을 의미론적 검색이 가능하도록 벡터 임베딩으로 변환합니다.</p></li><li><p><strong>답변 합성:</strong> 정확도가 높고 검색된 컨텍스트 덩어리(문서/동영상)를 가져와서 제공된 프롬프트 지침에 따라 일관성 있고 사람이 읽을 수 있는 최종 답변으로 합성합니다.</p></li></ul><p>RAG 프로세스는 답변에 필요한 <strong>정확한 사실적 컨텍스트를 제공하므로</strong> 최종 LLM은 거대하거나 매우 복잡할 필요가 없으며, 자체 매개변수 내에서 필요한 지식을 모두 보유할 필요도 없습니다.(이는 보통 크고 비싼 모델들이 맡는 잘하는 것입니다.) 본질적으로 모델은 자체가 거대한 지식 기반 역할을 하는 것이 아니라, Elasticsearch가 제공한 정보를 바탕으로 정교하게 요약하고 형식을 지정하는 역할을 수행하는 것입니다. 이렇게 하면 비용 및 지연 최적화에 <code>gpt-5-nano</code> 같은 모델을 사용할 수 있습니다.</p><p>.env 파일 설정을 마친 후, 다음 명령어를 사용하여 영화 데이터를 Elasticsearch으로 수집할 수 있습니다.</p>npx tsx src/utility/store.ts<p>다음과 같은 출력 내용이 나타나야 합니다.</p>🚀 Starting ingestion of 500 movies from 500_scifi_movies.jsonl...
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 1/500 (0%) | ok:1 | fail:0 | chunks:1 | eta:19m 33s | current:Capricorn One
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 2/500 (0%) | ok:2 | fail:0 | chunks:2 | eta:10m 32s | current:Doghouse
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 3/500 (1%) | ok:3 | fail:0 | chunks:3 | eta:7m 33s | current:Dinocroc
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 4/500 (1%) | ok:4 | fail:0 | chunks:7 | eta:6m 10s | current:Back to the Future           
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 5/500 (1%) | ok:5 | fail:0 | chunks:9 | eta:5m 14s | current:The Projected Man            
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 6/500 (1%) | ok:6 | fail:0 | chunks:11 | eta:4m 41s | current:I, Robot
...
✅ Ingestion complete in 1m 46s. Success: 500, Failed: 0, Chunks: 693.<p>SF 영화 인덱스의 매핑에는 다음 필드가 포함되어 있습니다.</p><ul><li><p><strong>임베딩</strong>, 1,536 차원의 dense_vector, 코사인 유사도.</p></li><li><p><strong>설명</strong>, 영화에 대한 설명이 포함된 텍스트.</p></li><li><p><strong>감독</strong>, 감독의 이름이 포함된 텍스트.</p></li><li><p><strong>제목</strong>, 영화의 제목이 포함된 텍스트.</p></li></ul><p>제목과 설명을 사용하여 임베딩을 생성했습니다. 제목과 설명은 별개의 필드이지만, 이 둘을 연결함으로써 생성된 임베딩 벡터가 영화의 고유한 정체성(제목)과 풍부한 서술적 컨텍스트(설명)을 모두 포착할 수 있게 됩니다. 결과적으로 더욱 정확하고 포괄적인 의미론적 검색이 가능해집니다. 이렇게 결합된 입력은 임베딩 모델에 유사도 매칭을 위한 문서 내용의 더 나은 단일 표현을 제공합니다.</p><h2>데모를 실행하십시오</h2><p>다음 명령어를 사용하여 데모를 실행할 수 있습니다.</p>npm run dev<p>이 명령어를 실행하면 Mastra Studio에 접속할 수 있는 웹 애플리케이션이 <strong>localhost:4111</strong>에서 시작됩니다(그림 3).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a539df677a33b36/6a17041a47d49c36842d88a0/1567e309df21a12bcf1dfef4429f82342549956c-1705x1079.png" alt="Elasticsearch Agent 예시가 포함된 Mastra Studio의 스크린샷입니다." /><p><a href="https://mastra.ai/docs/getting-started/studio">Mastra Studio</a>는 에이전트를 구축하고 테스트할 수 있는 대화형 UI를 제공하며, Mastra 애플리케이션을 로컬 서비스로 활용할 수 있도록 REST API도 함께 지원합니다. 이를 통해 통합에 대한 걱정 없이 즉시 구축을 시작할 수 있습니다.</p><p><strong>Elasticsearch Agent</strong>를 제공했으며, 이는 Elasticsearch를 사용하여 의미 검색을 실행하는 도구로 Mastra의 <a href="https://mastra.ai/reference/tools/vector-query-tool">createVectorQueryTool</a>을 사용합니다. 이 에이전트는 RAG 접근법을 사용하여 사용자 질문에 답하기 위한 관련 문서(즉, 영화)를 검색합니다.</p><p>이 에이전트는 다음 프롬프트를 사용합니다.</p>You are a helpful assistant that answers questions based on the provided context.
Follow these steps for each response:

1. First, carefully analyze the retrieved context chunks and identify key information.
2. Break down your thinking process about how the retrieved information relates to the query.
3. Draw conclusions based only on the evidence in the retrieved context.
4. If the retrieved chunks don't contain enough information, explicitly state what's missing.

Format your response as:
THOUGHT PROCESS:
- Step 1: [Initial analysis of retrieved chunks]
- Step 2: [Reasoning based on chunks]

FINAL ANSWER:
[Your concise answer based on the retrieved context]

Important: When asked to answer a question, please base your answer only on the context provided in the tool. 
If the context doesn't contain enough information to fully answer the question, please state that explicitly and stop it.
Do not add more information than what is present in the retrieved chunks.
Remember: Explain how you're using the retrieved information to reach your conclusions.<p><code>Mastra Studio &gt; Agents</code> 메뉴를 클릭하고 <strong>Elasticsearch Agent</strong>를 선택하면, 채팅 시스템을 통해 에이전트를 테스트할 수 있습니다. 예를 들어, 다음과 같은 질문으로 SF 영화에 관한 정보를 요청할 수 있습니다.</p><p><em>UFO에 관한 영화 또는 TV 시리즈 5편을 찾아 줘</em>.</p><p>에이전트가 vectorQueryTool을 실행하는 것을 확인할 수 있습니다. 실행된 도구를 클릭하여 입력과 출력을 확인할 수 있습니다. 실행이 완료되면 LLM은 Elasticsearch의 SF 영화 인덱스에서 제공된 맥락을 바탕으로 질문에 답변을 제공합니다(그림 4).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda92a9b6e56528d3/6a17041c2b835f9724f4b0d6/d9998d4f687984de98845dae52d1288166abf448-1344x1071.png" alt=" Elasticsearch Agent를 사용하여 생성된 LLM의 응답입니다." /><p>Mastra는 내부적으로 다음 단계를 실행합니다.</p><ol><li><p><strong>벡터 변환:</strong> 사용자의 질문인 <em>UFO에 관한 영화 또는 TV 시리즈 5편을 찾아 줘</em>는 OpenAI의 <code>openai/text-embedding-3-small</code> 모델을 사용하여 벡터 임베딩으로 변환됩니다.</p></li><li><p><strong>벡터 검색:</strong> 이 임베딩은 벡터 검색을 통해 Elasticsearch를 쿼리하는 데 사용됩니다.</p></li><li><p><strong>결과 검색:</strong> Elasticsearch는 쿼리와 관련성이 높은(즉, 사용자의 쿼리 벡터와 가장 가까운 벡터를 가진) 10편의 영화를 반환합니다.</p></li><li><p><strong>답변 생성:</strong> 검색된 영화와 원래 사용자 질문은 LLM, 구체적으로<code>openai/gpt-5-nano</code>에 전송됩니다. LLM은 이 정보를 처리하여 최종 답변을 생성하며, 이때 결과를 5개 보여달라는 사용자의 요청 사항이 정확히 반영되도록 합니다.</p></li></ol><h2>The Elasticsearch Agent</h2><p>여기에서는 Elasticsearch Agent의 소스 코드를 기재했습니다.</p>import { Agent } from "@mastra/core/agent";
import { ElasticSearchVector } from '@mastra/elasticsearch';
import { createVectorQueryTool } from '@mastra/rag';
import { ModelRouterEmbeddingModel } from "@mastra/core/llm";
import { Memory } from "@mastra/memory";

const es_url = process.env.ELASTICSEARCH_URL;
const es_apikey = process.env.ELASTICSEARCH_API_KEY;
const es_index_name = process.env.ELASTICSEARCH_INDEX_NAME;
const prompt = 'insert here the previous prompt';

const esVector = new ElasticSearchVector({
  id: 'elasticsearch-vector',
  url: es_url,
  auth: {
    apiKey : es_apikey
  }
});

const vectorQueryTool = createVectorQueryTool({
  vectorStore: esVector,
  indexName: es_index_name,
  model: new ModelRouterEmbeddingModel("openai/text-embedding-3-small")
});

export const elasticsearchAgent = new Agent({
  id: "elasticsearch-agent",
  name: "Elasticsearch Agent",
  instructions: prompt,
  model: 'openai/gpt-5-nano',
  tools: { vectorQueryTool },
  memory: new Memory(),
});<p><strong>vectorQueryTool</strong>은 RAG 예시의 검색 부분을 구현하기 위해 호출되는 도구입니다. Elastic 측에서 Mastra에 기여한 <a href="https://mastra.ai/reference/vectors/elasticsearch">ElasticSearchVector</a> 구현을 사용합니다.</p><p>에이전트는 VectorQueryTool, 프롬프트, 메모리를 사용하는 에이전트 클래스의 객체입니다. 보시다시피, Elasticsearch를 에이전트에 연결하기 위해 필요한 코드는 매우 간결합니다.</p><h2>결론</h2><p>이 글에서는 Mastra 프레임워크와 Elasticsearch를 통합하여 정교한 에이전트형 AI 애플리케이션을 구축하는 과정이 얼마나 단순하고 강력한지 살펴보았습니다. 특히, Elasticsearch에 인덱싱된 SF 영화 데이터 코퍼스에 대해 의미 검색을 수행할 수 있는 RAG 에이전트를 만드는 방법을 자세히 살펴보았습니다.</p><p>핵심적인 특징은 Elastic이 Mastra 오픈소스 프로젝트에 직접 기여했다는 점이며, 이를 통해 Elasticsearch를 벡터 저장소로 사용하는 네이티브 지원이 가능해졌습니다. 이러한 통합은 <strong>Elasticsearch Agent</strong> 소스 코드에서 확인할 수 있듯이 진입 장벽을 크게 낮춥니다. <code>ElasticSearchVector</code> 및 <code>createVectorQueryTool</code>(을)를 사용하면, 최소한의 구성 코드 몇 줄로 Elasticsearch를 에이전트에 연결하기 위한 전체 설정이 완료됩니다.</p><p>Elasticsearch는 결과의 정확도를 향상하고자 여러 고급 기능을 제공합니다. 예를 들어, <a href="https://www.elastic.co/elasticsearch/hybrid-search">하이브리드 검색</a>은 어휘 검색과 벡터 검색을 결합하여 정확도를 크게 향상합니다. 또 다른 흥미로운 기능은 하이브리드 검색의 마지막 단계에 적용할 수 있는 최신 <a href="https://www.elastic.co/search-labs/tutorials/jina-tutorial/jina-reranker-v3">Jina 모델</a>을 활용한 순위 재지정입니다. 이러한 기술에 대해 자세히 알아보려면 Elasticsearch Labs의 다음 글을 참조하세요.</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">Elasticsearch 하이브리드 검색</a>, 발렌틴 크레타즈(Valentin Crettaz) 저</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/jina-models-elasticsearch-guide">Jina 모델, 그 기능 및 Elasticsearch에서의 사용법에 대한 소개</a>, 스콧 마르텐스(Scott Martens) 저</p></li></ul><p>제공된 예시를 살펴보고 Mastra 및 Elasticsearch로 자신만의 데이터 기반 에이전트를 구축해 보시기 바랍니다. Mastra에 대한 더 자세한 정보는 <a href="https://mastra.ai/docs">여기</a>에서 공식 문서를 통해 확인하실 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-agentic-ai-applications-mastra-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-agentic-ai-applications-mastra-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Enrico Zimuel]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt083181bab7c0e2d0/6a17041eacf0880b70be99f5/ab30baf2f908534840c5d71a46705773807baf54-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 08 Apr 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[셸 도구는 컨텍스트 엔지니어링을 위한 만능 해결책이 아닙니다]]></title>
    <description><![CDATA[컨텍스트 엔지니어링을 위해 존재하는 컨텍스트 검색 도구와 그 작동 방식, 장단점에 대해 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>에이전트에게 가장 중요한 도구는 자체 컨텍스트를 구축하는 데 사용할 수 있는 검색 도구입니다. 최근 <a href="https://www.llamaindex.ai/blog/files-are-all-you-need">LlamaIndex</a>와 <a href="https://x.com/hwchase17/status/2011814697889316930">LangChain</a>의 게시물은 <em>컨텍스트 엔지니어링을 위해 에이전트에게 셸 도구와 파일 시스템만으로 충분할까?</em>라는 논의를 불러일으켰습니다.안타깝게도 논의는 파일 시스템 대 데이터베이스라는 잘못된 초점으로 빠르게 흘러갔습니다.</p><p>이 글에서는 <em>에이전트가 자체 컨텍스트를 구축하는 데 필요한 적절한 검색 인터페이스는 무엇인가?</em>라는 질문에 다시 초점을 맞춥니다. 먼저 셸 도구와 전용 데이터베이스 도구 간의 절충점을 다룹니다. 이를 통해 에이전트의 요구에 맞는 인터페이스를 찾는 데 유용한 실용적인 프레임워크를 제공합니다.</p><h2>'컨텍스트 구축'이란 에이전트에게 실제로 무슨 의미일까요?</h2><p>초기 <a href="https://www.elastic.co/what-is/retrieval-augmented-generation">검색 증강 생성(RAG) 파이프라인</a>에서는 개발자가 고정된 검색 파이프라인을 설계했고, 대규모 언어 모델(LLM)은 컨텍스트의 수동적인 수신자였습니다. 이는 근본적인 한계였습니다. 필요한지 여부에 관계없이 모든 쿼리에서 컨텍스트가 검색되었지만 실제로 도움이 되는지 확인하지 못했습니다.</p><p>에이전트 기반 RAG로의 전환으로 이제 에이전트는 자체 컨텍스트를 구축하기 위한 일련의 검색 도구에 접근할 수 있게 되었습니다. 예를 들어, Claude Code[1]와 Cursor[2] 모두 에이전트가 다양한 검색 도구 간에 선택할 수 있도록 하고, 작업에 실제로 필요한 내용에 따라 이를 연결된 쿼리로 조합할 수도 있습니다.</p><h2>컨텍스트 엔지니어링을 위한 검색 인터페이스에는 어떤 것이 있나요?</h2><p>컨텍스트는 웹, 로컬 파일 시스템 또는 데이터베이스와 같은 다양한 위치에 존재할 수 있습니다. 에이전트는 다양한 도구를 통해 이러한 맥락에서 벗어난 데이터 소스 각각과 상호 작용할 수 있습니다.</p><ul><li><p><strong>셸 도구는</strong>셸 명령을 실행하고 로컬 파일 시스템에 접근할 수 있습니다. 기본 제공 셸 도구의 예로는 <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool">Claude API의 bash 도구</a>, <a href="https://docs.openclaw.ai/tools/exec">OpenClaw의 exec 도구</a>, 그리고 <a href="https://docs.langchain.com/oss/python/integrations/tools/bash">LangChain의 shell 도구</a>입니다.</p></li><li><p><strong>전용 데이터베이스 도구</strong>는 MCP(모델 컨텍스트 프로토콜) 서버의 도구(예: <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCP 서버</a>) 또는 사용자 정의 도구(예: <code>run_esql(query)</code> 또는 <code>db_list_index()</code>)로, 데이터베이스를 쿼리할 수 있습니다.</p></li><li><p><strong>전용 파일 검색 도구</strong>는 (완전한 셸 액세스 권한 없이도) 로컬 파일(또는 업로드된 파일)을 검색하고 읽을 수 있습니다. 기본 제공 파일 검색 도구의 예로는 <a href="https://ai.google.dev/gemini-api/docs/file-search">Gemini API의 파일 검색 도구</a> 또는 <a href="https://developers.openai.com/api/docs/guides/tools-file-search">OpenAI의 파일 검색 도구</a>가 있습니다.</p></li><li><p><strong>웹 검색 도구</strong>는 웹에서 정보를 검색할 수 있습니다.</p></li><li><p><strong>메모리 도구</strong>는 (저장 방식에 관계없이) 장기 기억을 저장하고 불러올 수 있습니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2c5d083815149773/6a170acb964cea61a108bb80/115f20c8ded259e508f51524b2c06bdc702d70ab-1999x1050.png" alt="에이전트가 로컬 파일, 독점 데이터, 웹 및 장기 메모리에 접근하기 위해 다양한 컨텍스트 검색 도구를 사용하는 방법을 보여주는 다이어그램입니다." /><p>보시다시피, 셸 도구는 다재다능하며 다음과 같은 다양한 데이터 소스에서 컨텍스트를 검색하는 데 사용할 수 있습니다.</p><ul><li><p><strong>파일 시스템:</strong> 에이전트는 디렉터리 구조를 탐색하고(ls, find), 관련 콘텐츠를 검색하고(grep, cat), 충분한 컨텍스트를 구축할 때까지 이 과정을 반복합니다.</p></li><li><p><strong>데이터베이스:</strong> 에이전트는 데이터베이스 명령줄 인터페이스 (CLI) 도구(예: <a href="https://www.elastic.co/docs/reference/query-languages/sql/sql-cli"><code>elasticsearch-sql-cli</code></a>)를 사용하거나, curl을 통해 HTTP API를 호출하거나, 스크립트를 실행할 수 있습니다. 이는 올바른 도구 사용을 안내하기 위해 에이전트 컨텍스트에 삽입된 재사용 가능하고 문서화된 예시인 에이전트 스킬과 함께 사용하면 특히 유용합니다(예: <a href="https://github.com/elastic/agent-skills">Elastic Agent</a> Skills for Elasticsearch).</p></li><li><p><strong>웹: </strong>에이전트는 검색 제공업체의 API를 통해 curl 명령을 사용하여 웹 검색을 실행할 수 있습니다.</p></li></ul><p>그러나 셸 도구는 직접적인 시스템 액세스를 제공하므로 격리된 샌드박스 환경에서 실행하고 모든 실행된 명령을 로깅하는 등의 안전 조치가 필요합니다.</p><h2>각 검색 인터페이스의 사용 시점</h2><p>적합한 검색 인터페이스는 데이터, 쿼리 패턴 및 사용 사례에 따라 달라집니다. 이 섹션은 실용적인 출발점이 됩니다.</p><h3>파일 시스템이 데이터베이스를 쓸모없게 만들지는 않습니다</h3><p>파일 시스템과 데이터베이스의 논쟁은 저장 공간 계층에 관한 것이 아닙니다. 예를 들어, LangChain은 <a href="https://x.com/hwchase17/status/2011814697889316930">자체 메모리 시스템</a>이 실제로 실제 파일 시스템에 메모리를 저장하지 않는다고 설명합니다. 대신 데이터베이스에 메모리를 저장하고 이를 에이전트에게 파일 세트로 <em>표현합니다</em>[3].</p><p>파일 시스템은 코딩 에이전트와 같은 파일 기반 사용 사례에 매우 적합합니다. 또한 임시 메모장이나 작업 메모리로 사용하기에 적합하며 동시성이 문제가 되지 않는 단일 사용자 또는 단일 에이전트 시나리오에 적합합니다. 이러한 경우 물리적 파일 시스템 또는 데이터를 파일 시스템으로 표현하는 것은 목적에 맞춰진 인터페이스에 약속하기 전에 유연성을 제공합니다.</p><p>그러나 파일 시스템 저장 공간에는 실제적인 단점이 있습니다. 예를 들어 약한 동시성, 수동 스키마 적용, 원자적 트랜잭션 등이 있습니다. 이러한 단점은 애플리케이션을 확장해야 하거나 다중 에이전트 시나리오로 전환해야 할 때 더욱 분명해집니다. 이러한 단점을 무시하는 사람은 프로덕션 데이터베이스가 이미 제공하는 트랜잭션 안전이나 액세스 제어를 뒷받침하는 수십 년의 엔지니어링 없이 <a href="https://dx.tips/oops-database">더 나쁜 데이터베이스를 고통스럽게 재창조</a>해야 할 운명에 처하게 됩니다. 또한 대부분의 기업 환경에서는 이미 비즈니스에 중요한 데이터가 저장되어 있는 데이터베이스가 존재하기 때문에 데이터베이스 사용 여부를 선택할 필요가 없습니다.</p><h3>셸 도구 + 파일 시스템</h3><p>셸 도구는 파일 시스템 검색의 자연스러운 출발점입니다. 현재 코딩 에이전트가 해당 분야의 발전을 주도하고 있습니다. 로컬 파일의 코드를 다루기 때문에 본질적으로 파일 중심의 사용 사례입니다. 따라서 LLM은 코딩 작업을 위해 훈련 후 단계에서 미세 조정됩니다. 그렇기 때문에 많은 LLM은 코드를 작성하는 것뿐만 아니라 셸 명령을 사용하고 파일 시스템을 탐색하는 데도 능숙합니다.</p><p><code>ls</code> 및 <code>grep</code>과 같이 기본 제공 CLI가 있는 셸 도구를 사용하면 파일을 효과적으로 찾을 수 있습니다. grep에서는 " <code>matplotlib</code>를 가져오는 모든 파일을 찾으세요" 같은 쿼리가 빠르고 정확하며 저렴합니다. 하지만 에이전트가 "우리 앱은 인증 실패를 어떻게 처리하나요?"와 같은 개념적인 쿼리를 처리해야 하는 경우 grep을 사용한 패턴 매칭은 금방 한계에 부딪힐 수 있습니다. 명령줄에 의미 검색 기능을 제공하는 몇 가지 대안이 이러한 격차를 메우기 위해 등장했으며, 여기에는 <a href="https://github.com/jina-ai/jina-grep-cli"><code>jina-grep</code></a>이 포함됩니다.</p><p>그러나 grep과 많은 시맨틱 검색 대안들은 코퍼스 전체에서 O(n) 시간 복잡도로 실행됩니다. 코드베이스를 대상으로 하는 사용 사례에서는 이 방식이 괜찮을 수 있습니다. 그러나 데이터가 증가하면 지연이 눈에 띄게 나타납니다. 이 경우 성능을 유지하기 위해 인덱싱된 데이터 저장소가 필요합니다.</p><h3>셸 도구 + 데이터베이스</h3><p>데이터에 시맨틱 검색이나 하이브리드 검색과 같은 더 많은 검색 기능을 추가하는 또 다른 방법은 Cursor의 예처럼 데이터를 데이터베이스에 저장하는 것입니다. 또한 데이터에 복잡한 관계형 조인이나 집계가 필요한 경우 데이터베이스 인터페이스는 필수적입니다.</p><p>데이터가 파일 시스템이 아닌 데이터베이스에 있을 때, 셸 도구는 특정 사용 사례에서 경량 데이터베이스 인터페이스 역할을 할 수 있습니다. 쿼리가 CLI나 curl 호출로 간단하다면, 전용 데이터베이스 도구는 불필요한 복잡성을 더할 수 있습니다.</p><p>이러한 접근 방식은 초기 탐색 단계에서도 적합합니다. 아직 어떤 쿼리 패턴이 개발될지 모를 때 유용합니다. 이 경우, 에이전트 스킬은 목적에 맞춰 제작된 도구에 의존하지 않고도 에이전트에게 올바르게 쿼리할 수 있는 충분한 구조를 제공할 수 있습니다. 하지만 에이전트가 반복적인 작업을 위해 데이터베이스를 쿼리하는 올바른 방법을 파악하는 데 여러 번의 반복이 필요한 경우, 셸 도구를 인터페이스로 사용하는 데 따른 토큰 오버헤드가 추가 도구를 사용하지 않음으로써 얻는 단순성이라는 이점을 더 이상 정당화하지 못합니다.</p><h3>전용 데이터베이스 도구</h3><p>특히 반복되는 쿼리 패턴이 구조화되거나 분석적일 때 전용 데이터베이스 도구가 필요해집니다. <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Vercel과 Braintrust의 블로그 게시물</a>은 고객 지원 티켓 및 영업 통화 기록과 같은 반정형 데이터에 대한 실제 검색 작업에서 서로 다른 검색 도구 세트를 갖춘 에이전트를 비교했습니다(예: "미해결 이슈 중 '보안'을 언급한 것은 몇 개인가요?" 또는 "누군가 버그를 보고하고 나중에 누군가 이를 수정한다고 주장하는 PR을 제출한 이슈를 찾으세요?") [4].</p><p>전용 데이터베이스 도구를 사용한 에이전트는 토큰을 더 적게 사용하고, 더 빠르며, 셸 도구와 파일 시스템만 사용한 에이전트보다 실수를 더 적게 했습니다. 이를 통해 알 수 있는 교훈은 쿼리가 반정형 데이터에 대한 분석적 추론을 필요로 할 때 직접적인 데이터베이스 도구가 올바른 선택이라는 것입니다.</p><h3>검색 인터페이스 결합하기</h3><p>모든 쿼리를 완벽하게 처리할 수 있는 단일 검색 인터페이스는 없습니다. 예를 들어, Cursor는 셸 도구(grep을 통한 검색용)와 시맨틱 검색 도구를 결합하여 에이전트가 사용자 프롬프트에 따라 적합한 도구를 선택할 수 있도록 합니다. 그들은 에이전트가 특정 기호나 스트링을 매칭하기 위해 grep을 선택하고, 개념적 또는 행동 질문에 대해서는 시맨틱 검색을 선택하며, 탐색적 작업에는 둘 다를 사용한다고 보고합니다.</p><p>Vercel 실험 보고서는 동일한 결과를 보여줍니다. 셸 도구와 전용 데이터베이스 도구 모두에 접근할 수 있는 하이브리드 에이전트가 먼저 전용 데이터베이스 도구를 사용한 다음 파일 시스템을 grep하여 결과를 확인함으로써 테스트된 모든 에이전트 중 최고의 성능을 달성했습니다. 하지만 이 접근 방식은 도구 선택과 검증을 위한 추론에 더 많은 토큰과 시간을 사용합니다.</p><p>두 예시의 패턴은 동일합니다. 컴포지션이 단일 인터페이스를 능가하지만 컴포지션에는 추가 비용과 지연 시간이 발생한다는 단점이 있습니다.</p><h2>적합한 도구 세트를 찾기 위한 실용적인 권장 사항</h2><p>적합한 검색 인터페이스는 간결하고 목적에 부합하며 에이전트의 실제 쿼리 패턴에 맞춰 특화되어 있어야 합니다. 현재 가장 좋은 방법은 수백 개의 MCP 도구를 사용하는 에이전트 대신 가능한 한 적은 수의 도구를 사용하는 에이전트를 보유하는 것입니다. 이는 가능한 모든 도구를 미리 노출할 경우 발생하는 단점이 컨텍스트 윈도우를 비대화시키고 에이전트가 실제로 사용할 도구에 대해 혼란스럽게 만든다는 것입니다. 예를 들어, Claude Code는 보고된 바에 따르면 약 20개의 도구만 가지고 있습니다.</p><p>대신, 점진적 공개의 개념은 최소한의 도구 세트로 시작하고 에이전트가 필요할 때만 추가 기능을 발견하도록 하는 것입니다. Anthropic[5] 및 Cursor[6]의 연구에 따르면 이 접근 방식은 47%–85% 사이의 토큰 절감 효과를 가져옵니다. 예를 들어, Claude Code는 이를 직접 구현하여 에이전트가 API나 데이터베이스를 쿼리하는 방법을 점진적으로 발견할 수 있도록 하며, 이러한 지식이 모든 LLM 호출에서 컨텍스트를 소비하지 않도록 합니다.</p><p>에이전트의 쿼리 패턴에 익숙해지면, 에이전트가 기본적으로 액세스할 수 있는 검색 도구 세트를 다시 검토할 수 있습니다. 이 절충점을 고려할 때 유용한 방법은 <a href="https://www.elastic.co/search-labs/blog/database-retrieval-tools-context-engineering#building-the-right-database-retrieval-tools-%5C(%E2%80%9Clow-floor,-high-ceiling%E2%80%9D%5C">'낮은 바닥, 높은 천장' 원칙</a>에 다라 어떤 도구를 선택할지 결정하는 것입니다. '높은 천장' 도구는 에이전트의 잠재력을 제한하지 않습니다. 예를 들어, 다목적 셸 도구를 사용하면 에이전트가 애매한 쿼리를 포함한 전체 데이터베이스 쿼리를 작성할 수 있지만, 추론 오버헤드, 더 높은 지연 시간, 더 낮은 신뢰성이라는 대가가 따릅니다.</p><p>'낮은 바닥' 도구는 그 반대입니다. 이들은 특정 쿼리를 처리하는 전문화된 도구로, 에이전트가 최소한의 추론 오버헤드로 즉시 접근할 수 있어 비용을 낮추고 신뢰성을 높입니다. 그러나 초기 엔지니어링이 필요하고, 모든 가능한 쿼리를 다룰 수 없으며, 에이전트가 올바른 도구를 선택하기 더 어렵게 만들 수 있습니다.</p><p>각 도구를 스펙트럼으로 생각해 보세요. '낮은 바닥' 도구는 에이전트가 올바르게 사용하기 쉽지만 범위가 좁습니다. '높은 천장' 도구는 다용도이지만 효과적으로 사용하기 위해서는 더 많은 추론이 필요합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72deecc6781e3499/6a170acd5091682f4fe1baba/e6d1b973be4b0a0a25c99c74f02a47e98395a3f7-1200x630.png" alt="세 가지 에이전트 설계 접근 방식(높은 바닥/높은 천장, 낮은 바닥/낮은 천장, 낮은 바닥/높은 천장)을 비교하는 다이어그램으로, 다양한 도구 전략이 에이전트가 모호하고, 다목적이며, 예측 가능한 쿼리를 처리하는 방식에 어떤 영향을 미치는지 보여줍니다." /><p>대부분의 에이전트는 다양한 검색 도구의 조합이 필요합니다. 하지만 각 도구마다 추가 기능이 필요합니다. 다목적 검색 도구(예: <code>search_database()</code> 도구 또는 셸 도구)로 시작하는 것이 좋습니다. 그런 다음 보안 목적으로 이미 보관하고 있는 명령 로그를 재사용하여 도구 호출, 재시도 및 사용자 쿼리당 호출 횟수를 포함하여 에이전트가 실제로 수행하는 작업을 추적하세요. 그리고 쿼리 패턴이 반복되거나 실패하는 것을 발견하면 이를 위한 전용 도구를 구축해야 한다는 신호입니다.</p><h2>요약</h2><p>파일시스템 대 데이터베이스 논쟁은 엔지니어들이 묻고 있는 <em>에이전트가 자체 컨텍스트를 구축하는 데 필요한 적절한 검색 인터페이스는 무엇인가?</em>라는 실제 질문에서 주의를 분산시키고 있습니다. 정답은 '<em>하나도 없다</em>'일 가능성이 높습니다.</p><p>셸 도구는 다양한 외부 소스와 상호 작용할 수 있는 다용도 도구이므로 좋은 출발점이 될 수 있습니다. 하지만 구조화된 분석 쿼리가 필요한 사용 사례에서는 전용 데이터베이스 도구보다 효율성과 정확성이 떨어집니다.</p><p>목표는 에이전트의 실제 쿼리 패턴을 효과적으로 처리할 수 있는 최소한의 검색 도구 세트를 찾는 것입니다. 셸 도구를 사용하여 에이전트가 실제로 수행하는 작업을 기록하세요. 쿼리 패턴이 반복적으로 실패하는 경우, 전문화된 도구를 개발할 시점입니다.</p><h2>참고 자료</h2><p>1. Thariq(Anthropic). <a href="https://x.com/trq212/status/2027463795355095314">Claude Code 구축에서 얻은 교훈: 에이전트처럼 보기</a>(2026).</p><p>2. Cursor: 문서. <a href="https://cursor.com/docs/agent/tools/search">시맨틱 및 에이전트 검색</a>(2026).</p><p>3. Harrison Chase(LangChain). <a href="https://x.com/hwchase17/status/2011814697889316930">Agent Builder의 메모리 시스템 구축 방법</a>(2026).</p><p>4. Ankur Goyal(Braintrust) 및 Andrew Qu(Vercel). <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">"bash만으로 충분한지" 테스트하기</a>(2026).</p><p>5. Anthropic. <a href="https://www.anthropic.com/engineering/advanced-tool-use">Claude 개발자 플랫폼의 고급 도구 활용 소개</a>(2025).</p><p>6. Cursor. <a href="https://cursor.com/blog/dynamic-context-discovery">동적 컨텍스트 검색</a>(2026년).</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-tools-context-engineering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-tools-context-engineering</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Leonie Monigatti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b9bbbff55c09fa4/6a170acecdacbff1167d29fd/f91e4d07915ba7bf3b7abf15fac8fab3350f7df2-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 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[Agent Skills for Elastic: AI 에이전트를 Elastic 전문가로 전환하기]]></title>
    <description><![CDATA[Elastic Agent Skills로 AI 코딩 에이전트에게 지식을 제공하세요. 데이터를 쿼리하고 시각화하며 보안 및 자동화까지 수행합니다.]]></description>
    <content:encoded><![CDATA[<p>특화된 플랫폼에서 AI 코딩 에이전트를 사용해 본 개발자, 사이트 신뢰성 엔지니어(SRE), 혹은 분석가라면 누구나 한 번쯤 같은 장벽에 부딪힌 적이 있을 것입니다. 에이전트에게 쿼리 작성을 요청하거나 알림 설정 또는 특정 사안을 조사해 달라고 하면 그럴듯하게 답변은 하지만 정확하지는 않은 경우가 있습니다. Elastic은 여기서 큰 강점을 가지고 있습니다. 10년 넘게 축적된 문서, 블로그 게시물 및 커뮤니티 답변 덕분에 AI 에이전트는 이미 다른 데이터 플랫폼보다 Elastic을 더 잘 알고 있습니다. 하지만 그 방대한 지식에는 노이즈도 섞여 있습니다. 최신 API 사이에 이미 사용 중단된 API가 나열됩니다. 이 때문에 낡은 패턴들이 마치 모범 사례인 것처럼 제안되곤 합니다. 에이전트는 3단계 전 버전에서나 통했을 방식을 마치 정답인 양 자신 있게 내놓습니다. 학습 데이터 안에서는 여전히 성공적인 사례로 남아 있기 때문입니다. 결국 사용자는 문서 내용을 수동으로 컨텍스트에 입력하고 AI가 만들어낸 잘못된 구문을 수정하는 등 에이전트와 함께 일하기보다 에이전트를 수습하는 데 더 많은 시간을 할애하게 됩니다. 더 안타까운 점은 사용자가 필요하지 않아서가 아니라 에이전트가 그 존재를 몰라서 고급 기능들이 아예 활용되지 못한다는 것입니다.</p><p>이것이 바로 Elastic이 <a href="https://github.com/elastic/agent-skills">Elastic Agent Skills</a>를 오픈 소스로 공개하는 이유입니다. 이는 Elasticsearch, Kibana, Elastic Observability 및 Elastic Security를 위한 네이티브 플랫폼 전문 지식입니다. 현재 사용 중인 에이전트 런타임에 이 스킬들을 추가해 보세요. 수많은 구문을 추측만 하던 '범용' 수준의 에이전트를 Elastic 엔지니어링 팀만큼의 아키텍처 표준을 구사하는 전문가로 업그레이드할 수 있습니다. 이번 초기 기술 프리뷰 버전은 <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>와 최상의 호환성을 갖춘 스킬을 중심으로 제공됩니다. 하지만 빠른 시일 내에 이전 스택 버전에 대한 지원도 넓혀나갈 예정입니다.</p><p>또한 Elastic은 이 문제를 양방향에서 해결하고 있습니다. 우선 Elastic 플랫폼 내의 에이전트를 위해, 이제 정식 버전으로 출시된 <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Elastic Agent Builder</a>를 제공합니다. 이를 통해 데이터 접근 권한을 상속받고 내장된 검색 및 분석 도구를 사용하며 대시보드, 알림 및 조사와 연동되는 AI 에이전트를 생성하고 대화할 수 있습니다. Elastic은 플랫폼 내에서 최상의 에이전트 중심 경험을 구현하기 위해 전력을 다하고 있습니다. 하지만 모든 에이전트가 Elastic 내부에서만 활동하는 것은 아닙니다. 여러분의 팀은 이미 Cursor, Claude Code 또는 다른 런타임을 사용하고 있을 것이며 이 에이전트들 역시 Elastic을 제대로 활용할 수 있어야 합니다. 바로 그 지점에서 Agent Skills가 핵심적인 역할을 수행합니다.</p><h2>AI 에이전트가 전문 플랫폼에서 어려움을 겪는 이유</h2><p>대규모 언어 모델(LLM)은 다방면에 능한 팔방미인과 같습니다. 학습 데이터의 예제가 풍부하다 보니 Python을 작성하거나 Kubernetes 매니페스트를 설명하고 React 컴포넌트를 리팩토링하는 일을 능숙하게 해냅니다. 그러나 전용 쿼리 언어와 방대한 API 체계 그리고 해당 도메인의 특수한 설계 원칙을 준수해야 하는 전문 플랫폼 작업에서는 예외 없이 한계를 드러내고 맙니다.</p><p>Elasticsearch의 경우 이러한 간극은 다음과 같이 구체적으로 나타납니다.</p><ul><li><p><strong>Elasticsearch 쿼리 언어(ES|QL)는 새로운 영역입니다.</strong> LLM은 SQL 위주로 집중 학습되었지만 ES|QL은 구문과 함수, 의미 체계가 전혀 다른 파이프 기반의 쿼리 언어입니다. 이 때문에 에이전트는 겉보기엔 그럴듯하지만 실제로는 구문 분석이 되지 않는 쿼리를 작성하는 경우가 많습니다. <code>WHERE</code>을 <code>| WHERE</code>으로 착각하거나 존재하지 않는 함수를 임의로 만들어내고 파이프 중심의 쿼리 구성 방식을 완전히 놓치기도 합니다.</p></li><li><p><strong>API 체계는 방대하고 복잡합니다.</strong> Elasticsearch, Kibana 및 Elastic Security는 검색, 수집, 알림, 탐지 규칙, 케이스 관리, 대시보드 등 전반에 걸쳐 수백 개의 API를 제공합니다. 보편적인 학습 데이터만 갖춘 에이전트는 어떤 엔드포인트를 호출할지, 요청 본문은 어떻게 구성할지, 응답은 어떻게 처리할지를 추측에 의존할 수밖에 없습니다. 그리고 이러한 추측이 빈번하게 빗나가면서 결국 사용자의 신뢰를 떨어뜨리게 됩니다.</p></li><li><p><strong>학습 데이터에는 실무 노하우가 반영되어 있지 않습니다.</strong> 언제 <code>semantic_text</code>를 사용하고 언제 커스텀 임베딩 파이프라인을 구축할지, 10GB 크기의 CSV 수집 파이프라인은 어떻게 구성해야 할지, 혹은 <a href="https://www.elastic.co/docs/solutions/security/detect-and-alert/mitre-attandckr-coverage">MITRE ATT&amp;CK</a> 기술에 적합한 탐지 규칙 구문은 무엇인지와 같은 고민은 학습 데이터만으로 해결하기 어렵습니다. 범용 에이전트에는 잘 정리되고 신뢰할 수 있는 구조를 갖춘 Elastic 전용 지식이 기본적으로 탑재되어 있지 않기 때문입니다. 에이전트가 직접 정보를 찾아낸다 하더라도 가공되지 않은 문서는 숙련된 전문가가 가진 상황별 판단이나 실무 노하우까지 모두 담아내지는 못합니다.</p></li></ul><p>그 결과 개발자는 직접 코드를 짤 때보다 에이전트의 결과물을 수정하는 데 더 많은 시간을 허비하게 됩니다. 이는 우리가 기대했던 경험이 전혀 아닙니다.</p><h2>에이전트 스킬: 에이전트를 위해 패키징된 플랫폼 지식</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2099e0ccdf446fee/6a17074bd7c022e3e1de63d4/8d16ec00d16e70a916c5eef0aaa23fcc735b7186-1067x1280.png" alt="npx skills add elastic/agent-skills" /><p>Agent Skills는 에이전트 런타임이 동적으로 로드할 수 있는 지침, 스크립트 및 참조 자료가 포함된 독립적인 디렉터리입니다. 스킬이 활성화되면 에이전트는 적절한 시점에 적절한 컨텍스트(쿼리 구문, API 패턴, 검증 논리, 실제 실행 사례 등)에 접근할 수 있게 되며 이를 통해 첫 시도만으로도 작업을 정확하게 완료할 수 있습니다.</p><p>각 스킬은 오픈 소스인 <a href="https://agentskills.io">agentskills.io </a>사양을 따릅니다. 이는 메타데이터와 구조화된 지침이 담긴 <code>SKILL.md</code> 파일이 포함된 폴더 형태입니다. 독자적인 포맷도 벤더 종속성도 없습니다. 스킬은 Cursor, Claude Code, GitHub Copilot, Windsurf, Gemini CLI, Cline, Codex를 비롯하여 <a href="https://agentskills.io">다양한</a> 에이전트 런타임에 걸쳐 작동합니다.</p><h3>초기 v0.1.0 릴리스 포함 내용</h3><p>첫 번째 스킬 세트는 Elastic Stack의 다섯 가지 영역을 아우릅니다.</p><ul><li><p>Elasticsearch API와 상호 작용(검색, 인덱싱, 클러스터 관리)</p></li><li><p>Kibana 콘텐츠 구축 및 관리(대시보드, 알림, 커넥터 등)</p></li><li><p>Elastic Observability를 위한 도메인 전문성</p></li><li><p>Elastic Security를 위한 도메인 전문성</p></li><li><p>Agent Builder 내에서 효과적인 에이전트 만들기</p></li></ul><h3>스킬의 결합성</h3><p>스킬은 거대한 단일 구조가 아닙니다. 설계 단계부터 모듈화되어 있습니다. 에이전트는 현재 수행 중인 작업에 필요한 스킬만 로드합니다. ES|QL 쿼리를 작성 중인가요? ES|QL 스킬이 활성화됩니다. 그 결과로 대시보드를 만들어야 하나요? 대시보드 스킬이 작업을 넘겨받습니다. 애플리케이션의 상태를 평가 중인가요? 서비스 상태 스킬이 작동합니다. 보안 알림을 조사 중인가요? 분석 스킬이 조사의 진행 단계에 따라 케이스 관리 및 대응 스킬로 이어집니다.</p><p>이러한 결합성은 모든 것을 한꺼번에 해결하려는 거대한 단일 프롬프트가 필요 없음을 의미합니다. 각 스킬은 해당 도메인에 필요한 만큼의 컨텍스트만을 담고 있으며 그 이상도 그 이하도 아닙니다.</p><h2>검색 및 AI 애플리케이션을 구축하는 개발자를 위한 스킬</h2><p>Elasticsearch에 데이터를 로드하거나 쿼리를 작성하고 인덱스를 마이그레이션할 때 에이전트 스킬은 코드를 생성하고 오류에 부딪힌 뒤 원인을 찾기 위해 문서를 검색해야 하는 과정을 줄여줍니다.</p><p>에이전트에게 CSV 파일 로드를 요청하면 에이전트는 백프레셔를 처리하고 데이터에서 매핑을 추론하는 스트리밍 수집 도구를 사용합니다. 이는 대용량 파일을 처리하자마자 메모리 부족을 일으키는 임시방편의 수동 _bulk 루프가 아닙니다. ES|QL로 쿼리하도록 요청하면 에이전트는 실제 인덱스 이름과 필드 스키마를 찾아낸 뒤 정확한 구문, 적절한 집계, 버전별 기능을 반영한 유효한 파이프 쿼리를 작성합니다. 세 번씩 디버깅을 반복해야 하는 SQL의 흉내만 낸 추측성 답변이 아닙니다. 클러스터 간 재인덱싱을 요청하면 에이전트는 명시적 매핑으로 대상 인덱스를 생성하고 처리량 최적화를 위한 설정 튜닝까지 수행합니다. 또한 작업을 비동기로 실행하고 완료 후 운영 설정을 복구하는 전체 운영 워크플로우를 따릅니다. 숙련된 운영자가 수행할 단계들을 절반이나 건너뛰는 단순한 _reindex 호출이 아닙니다.</p><p>사용자가 직접 수정해야 하는 그럴싸한 시작점만 제공하는 에이전트 대신 운영 방식을 내재화하여 실행 가능한 결과물을 만드는 에이전트를 얻게 됩니다.</p><p><strong>Elastic Agent Skills 사용에 따른 주요 변화 예시</strong></p><p>Eval</p><p>스킬 적용 시 변화된 내용</p><p>es-audit-query-failed-logins</p><p>일반적인 검색 대신 스킬에 정의된 감사 로그 쿼리 패턴을 사용함</p><p>es-authz-role-mapping-ldap</p><p>정확한 역할 매핑 API 호출 구조를 출력함</p><p>esql-기본 쿼리</p><p>쿼리 DSL을 통한 ES|QL 파이프 구문 작성</p><p>esql 오류 처리</p><p>필드 이름을 추측하는 대신 스키마를 우선적으로 확인함</p><p>esql-schema-discovery</p><p>인덱스 이름을 절대 임의로 추측하지 않음</p><p>es-ingest-csv-with-infer</p><p>--infer-mappings를 단독 사용하고 빈 인덱스를 유발하는 --source-format csv와의 조합을 피함</p><p>es-ingest-json-file</p><p>대용량 파일을 처리할 수 있는 견고한 수집 방식을 사용함</p><p>es-reindex-local-async</p><p>복제본: 0 및 refresh_interval: "-1" 설정으로 대상 인덱스를 먼저 생성한 뒤 비동기 재인덱싱 수행함. 기본 모델은 준비 단계를 건너뜀</p><p>es-security-403-privileges</p><p>일반적인 조언 대신 권한 오류에 대한 스킬의 진단 워크플로우를 따름</p><h2>보안 팀을 위한 스킬</h2><p>보안 팀은 매일 알림 심사, 탐지 규칙 최적화, 케이스 관리와 같은 동일한 운영 워크플로우를 반복합니다. Agent Skills는 이러한 절차적 지식을 내재화하여 AI 에이전트가 정확한 필드 이름으로 적절한 API를 올바른 순서에 따라 호출하며 워크플로우를 수행할 수 있게 합니다. IDE를 벗어나지 않고 초기 설정부터 실제 데이터가 완비된 Elastic Security 환경을 구축하는 실습 과정은 <a href="https://www.elastic.co/security-labs/agent-skills-elastic-security">AI 에이전트로 Elastic Security 시작하기</a> 가이드를 참조하세요.</p><h2>통합 가시성 및 운영 팀을 위한 스킬</h2><p>Elastic Observability를 위한 새로운 Agent Skills는 복잡한 시스템의 계측, SLO 관리, 복잡한 데이터 선별, 서비스 상태 평가 등에 드는 운영상의 수고를 줄여줍니다. AI 에이전트에 Elastic의 네이티브 전문 지식을 직접 내재화함으로써 운영 팀은 간단한 자연어만으로도 복잡한 통합 가시성 워크플로우를 실행할 수 있습니다. 이를 통해 SRE 및 운영 팀은 장애를 더 빠르게 해결하고 시스템의 신뢰성을 더욱 쉽게 유지할 수 있습니다. 자세한 내용은 <a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">블로그</a>에서 확인해 보세요.</p><h2>오픈 소스, 오픈 사양, 커뮤니티 중심</h2><p>Elastic은 에이전트의 지식이 공개되어야 한다고 믿기에 Agent Skills를 Apache 2.0 라이선스로 출시합니다. 각 스킬이 따르는 <a href="https://agentskills.io">agentskills.io</a> 사양은 Elastic의 독자적인 포맷이 아닌 오픈 표준입니다. Elastic은 에이전트 스킬이 폐쇄적인 환경이 아닌 커뮤니티가 함께 만들어가는 노력이 되기를 바랍니다.</p><h2>더 큰 그림의 일부</h2><p>Agent Skills는 Elasticsearch를 현존하는 가장 에이전트 친화적인 데이터 플랫폼으로 만들기 위한 광범위한 이니셔티브의 일환입니다. Elasticsearch 플랫폼 기반의 에이전트를 위해 <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Agent Builder</a>는 데이터 액세스 제어 및 권한을 상속받아 한 단계 더 진화된 기능을 제공합니다. 또한 검색 및 분석을 위한 내장 및 커스텀 도구를 제공하며 사용자가 대시보드, 알림 및 조사 워크플로우와 함께 컨텍스트에 맞게 에이전트와 상호 작용할 수 있도록 지원합니다. 마지막으로 Agent Builder에 스킬 지원 기능이 곧 추가될 예정입니다. 이를 통해 개발자는 Elastic Agent Skills뿐만 아니라 다른 소스의 스킬까지 유연하게 활용하여 Elasticsearch 플랫폼에서 보안이 강화되고 컨텍스트가 풍부한 채팅 및 자동화를 구현할 수 있게 됩니다.</p><p>Elastic 외부의 다양한 환경에서 운영되는 에이전트를 위해 Elastic은 다음과 같은 오픈 생태계에 투자하고 있습니다.</p><ul><li><p><strong>모델 컨텍스트 프로토콜(MCP) 서버 확장:</strong> 현재의 검색, ES|QL, 인덱스 작업을 넘어 더 많은 도구를 사용할 수 있도록 Agent Builder의 <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">MCP 엔드포인트</a>를 확장합니다.</p></li><li><p><strong>인증 방식 개선:</strong> 수동으로 API 키를 복사하고 붙여넣는 과정을 없애는 것을 목표로 에이전트가 더욱 쉽고 안전하게 연결할 수 있도록 개선합니다.</p></li><li><p><strong>LLM이 이해하기 쉬운 문서:</strong> 에이전트가 스스로 Elastic API를 탐색하고 이해할 수 있도록 <code>llms.txt</code> 및 <code>AGENTS.md</code> 파일을 게시합니다.</p></li><li><p><strong>에이전트 워크플로우용 명령줄 인터페이스(CLI):</strong> 연결 관리 및 일반적인 작업을 에이전트 친화적으로 만들어 주는 명령줄 도구를 제공합니다.</p></li></ul><p>스킬은 현재 바로 사용할 수 있는 계층이며 나머지도 곧 추가될 예정입니다.</p><h2>시작하기</h2><p><strong>시작하기 전에:</strong> AI 코딩 에이전트는 실제 자격 증명과 실제 셸 액세스 권한으로 작동하며 대개 에이전트를 실행하는 사용자의 모든 권한을 가집니다. 이러한 에이전트가 보안 워크플로우에 투입될 경우 그 위험성은 더욱 커집니다. 탐지 로직, 대응 조치 및 민감한 텔레메트리에 대한 접근 권한을 자동화된 시스템에 넘겨주는 셈이기 때문입니다. 조직마다 위험 수용 범위는 다릅니다. AI 기반 보안 워크플로우를 활성화하기 전에 <strong>에이전트가 어떤 데이터에 접근할 수 있는지, 어떤 작업을 수행할 수 있는지 그리고 예기치 않게 동작할 경우 어떤 영향을 미칠지 평가해야 합니다</strong>.</p><p>Elastic Agent Skills를 에이전트 런타임에 설치합니다.</p><p><code>npx skills add elastic/agent-skills</code></p><p>이 명령은 설치된 에이전트 런타임을 자동으로 감지하여 올바른 구성 디렉터리에 스킬을 배치합니다. 이후 에이전트는 해당 스킬을 자동으로 인식합니다.</p><p>또한 <a href="https://github.com/elastic/agent-skills">스킬 카탈로그</a>를 직접 탐색하여 스킬 폴더를 에이전트의 구성 디렉터리에 복사함으로써 개별 스킬을 수동으로 설치할 수도 있습니다.</p><p>아직 Elasticsearch 클러스터가 없으신가요? <a href="https://cloud.elastic.co/registration">Elastic Cloud 무료 체험판</a>을 시작해 보세요. 약 1분이면 모든 설정이 완료된 환경을 준비할 수 있습니다.</p><p><strong>프로젝트 살펴보기:</strong></p><ul><li><p><a href="https://github.com/elastic/agent-skills">Agent Skills 저장소</a></p></li><li><p><a href="https://agentskills.io">agentskills.io 사양</a></p></li><li><p><a href="https://www.elastic.co/docs">Elasticsearch 문서</a></p></li><li><p><a href="https://cloud.elastic.co/registration">Elastic Cloud 무료 체험판</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-skills-elastic</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-skills-elastic</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI 도구 ]]></category>
    <dc:creator><![CDATA[Graham Hudgins,Matt Ryan]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd233e8cf5c66c88/6a17074dc1e8a59502f8822a/09e64953819083168a9ecef0888c7f8bde1a43bd-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[공통 표현식 언어(CEL): CEL 입력이 Elastic 에이전트 통합에서 데이터 수집을 개선하는 방법]]></title>
    <description><![CDATA[공통 표현식 언어(CEL)가 다른 프로그래밍 언어와 어떻게 다른지, Filebeat의 CEL 입력을 위해 어떻게 확장되었는지, 또 Elastic Agent 통합에서 데이터 수집 논리를 표현하는 데 얼마나 유연하게 활용할 수 있는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>사용자는 Elastic Agent <a href="https://www.elastic.co/integrations">통합</a>을 통해 다양한 소스에서 Elasticsearch로 데이터를 수집할 수 있습니다. 수집 논리, 인제스트 파이프라인, 대시보드 및 기타 아티팩트를 Kibana 웹 인터페이스에서 설치 및 관리 가능한 패키지로 결합할 수 있습니다.</p><p>통합은 데이터 수집을 수행하도록 <a href="https://www.elastic.co/docs/reference/beats/filebeat/configuration-filebeat-options">Filebeat 입력</a>을 구성합니다. HTTP API에서 데이터를 수집하는 데 종종 <a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-httpjson">HTTP JSON 입력</a>이 사용되었습니다. 그러나 기본적인 목록 조회 API도 세부 사항에서 크게 차이가 날 수 있으며, HTTP JSON 입력의 YAML 기반 변환 모델에서는 필요한 수집 논리를 표현하는 것이 불편하거나 불가능한 경우가 있습니다.</p><p><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">공통 표현식 언어(CEL) 입력</a>은 HTTP API와 보다 유연한 상호 작용을 촉진하기 위해 도입되었습니다. <a href="https://cel.dev/">CEL</a>은 빠르고 안전하며 확장 가능한 방식으로 조건과 데이터 변환을 표현해야 하는 애플리케이션에 내장되도록 설계된 언어입니다. 통합 빌더는 CEL 입력을 통해 설정을 읽고, 자체 상태를 추적하고, 요청을 생성하고, 응답을 처리하고, 궁극적으로 수집할 준비가 된 이벤트를 반환하는 하나의 표현식을 작성할 수 있습니다.</p><p>이 글에서는 CEL이 다른 프로그래밍 언어와 어떻게 다른지, CEL 입력을 위해 어떻게 확장되었는지, 또 데이터 수집 논리를 표현하는 데 어떤 유연성과 장점을 제공하는지 살펴보겠습니다.</p><h2>CEL과 입력에서의 작동 원리</h2><p>CEL은 표현식 언어입니다. 진술이 아닙니다. CEL을 작성할 때는 명령문으로 무엇을 할지 지시하는 것이 아니라, 표현식으로 어떤 값을 생성할지 정의합니다. 모든 CEL 표현식은 값을 생성하며, 더 작은 표현식을 결합하여 복잡한 규칙에 따른 결과를 만들어낼 수 있습니다. 다른 언어에서 명령문으로 작성되는 기능을 표현식으로 구현하는 방법을 나중에 살펴보겠습니다.</p><p>CEL은 의도적으로 튜링 완전 언어가 아닙니다. 무제한 루프를 허용하지 않습니다. 매크로를 사용하여 리스트와 맵을 처리하는 방법을 후에 살펴보겠지만, 무한 루프를 방지함으로써 개별 표현식에 대해 예측 가능하고 제한된 실행 시간을 보장합니다.</p><p>CEL 입력은 CEL 프로그램(표현식)과 몇 가지 초기 상태로 구성됩니다. 상태는 프로그램에 대한 입력으로 제공되고, 프로그램은 평가를 통해 출력 상태를 생성합니다. 출력 상태에 이벤트 목록이 포함되어 있으면 해당 이벤트가 제거되고 게시됩니다. 나머지 출력 상태는 다음 평가를 위한 입력으로 사용됩니다. 출력 상태에 하나 이상의 이벤트와 플래그 <code>want_more: true</code>가 포함되어 있으면 즉시 다음 평가가 수행되고, 포함되어 있지 않은 경우에는 구성된 시간 동안 대기한 후 진행됩니다. 다음은 입력의 제어 흐름을 간략하게 나타낸 다이어그램입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ec4ea57bfc2a2ff/6a17059f2b835f7d58f4b115/42671541f97e2dba808fd53969fe12f517917f9a-1600x529.png" alt="CEL(공통 표현식 언어) 입력 제어 흐름" /><p>각 평가의 출력은 입력이 실행되는 동안 다음 평가의 입력으로 전달됩니다. "<code>cursor</code>" 키 아래의 출력 데이터는 디스크에 보존되어 입력이 재시작된 후 다시 로드되지만, 나머지 상태는 재시작 시 보존되지 않습니다.</p><p>CEL 언어 자체는 기능이 제한적이며 부작용을 방지하지만, 확장 가능합니다. <a href="https://github.com/google/cel-go">cel-go</a> 구현은 선택적 구문 및 유형과 같은 기능을 추가합니다. <a href="https://github.com/elastic/mito">Mito</a> 라이브러리는 cel-go를 기반으로 빌드되어 HTTP 요청 기능을 포함한 더 많은 기능을 추가합니다. CEL 입력은 Mito의 CEL 버전을 사용합니다.</p><h2>Mito와 함께 작동</h2><p>CEL 입력을 사용하여 통합을 빌드하거나 디버깅할 때는 CEL 프로그램이 주어진 입력 상태에 대해 생성할 출력 상태를 이해하는 것이 가장 중요합니다. 개발 중에는 전체 Elastic 스택 환경에서 입력을 통해 CEL 프로그램을 실행하는 것이 번거로울 수 있습니다. Mito의 명령줄 도구를 사용하면 더 빠른 피드백 루프를 구현해 CEL 프로그램을 직접 실행하고 주어진 입력에 대한 출력 결과를 바로 확인할 수 있습니다.</p><p>Mito는 Go 언어로 작성되었으며 다음과 같이 설치할 수 있습니다.</p>go install github.com/elastic/mito/cmd/mito@latest<p>Mito로 CEL 프로그램을 실행할 때 일반적으로 두 개의 파일을 제공해야 합니다. 하나는 초기 입력 상태를 포함한 JSON 파일이고, 다른 하나는 CEL 프로그램의 소스 코드가 포함된 파일입니다.</p>mito -data state.json src.cel<p>쉽게 복사하여 붙여넣을 수 있도록 이 글의 예시는 각 파일의 내용을 <code>&lt;(echo '...content...')</code> 로 감싸서 셸이 임시 파일을 즉시 생성하도록 하는 단일 명령으로 작성되었습니다. 자체 개발 시에는 실제 파일을 다루는 것이 더 쉬울 수 있습니다.</p><h2>GitHub에서 이슈 데이터 가져오기</h2><p>다음 예시에서는 <a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues">GitHub API</a>에서 이슈 데이터를 가져오는 전체 CEL 프로그램을 볼 수 있습니다. 초기 입력 상태에는 API 엔드포인트의 URL과 페이지네이션 처리 방식에 대한 정보가 포함되어 있습니다. CEL 프로그램은 입력 상태의 데이터를 사용하여 요청을 생성합니다. 응답을 디코딩하고, 그로부터 이벤트를 생성한 다음, 이를 출력 상태의 일부로 반환합니다.</p>mito -data &lt;(echo '
  {
    "url": "https://api.github.com/repos/elastic/integrations/issues",
    "per_page": 3,
    "max_pages": 3
  }
') &lt;(echo '
  int(state.?cursor.page.orValue(1)).as(page,
    (
      state.url + "?" + {
        "state": ["all"],
        "sort": ["created"],
        "direction": ["asc"],
        "per_page": [string(state.per_page)],
        "page": [string(page)],
      }.format_query()
    ).as(full_url,
      request("GET", full_url).with({
        "Header": {
          "Accept": ["application/vnd.github+json"],
          "X-GitHub-Api-Version": ["2022-11-28"],
        }
      }).do_request().as(resp,
        resp.Body.decode_json().as(data,
          state.with({
            "events": data.map(i, {
              "html_url": i.html_url,
              "title": i.title,
              "created_at": i.created_at,
            }),
            "cursor": { "page": page + 1 },
            "want_more": size(data) == state.per_page &amp;&amp; page &lt; state.max_pages,
          })
        )
      )
    )
  )
')<p>첫 번째 평가는 다음과 같은 출력을 생성합니다.</p>{
  "cursor": {
    "page": 2
  },
  "events": [
    {
      "created_at": "2018-09-14T09:47:35Z",
      "html_url": "https://github.com/elastic/integrations/issues/3250",
      "title": "Increase support of log formats in haproxy filebeat module"
    },
    {
      "created_at": "2019-02-06T12:37:37Z",
      "html_url": "https://github.com/elastic/integrations/issues/487",
      "title": "ETCD Metricbeat module needs polishing and grooming"
    },
    {
      "created_at": "2019-08-13T11:33:11Z",
      "html_url": "https://github.com/elastic/integrations/pull/1",
      "title": "Initial structure"
    }
  ],
  "max_pages": 3,
  "per_page": 3,
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "want_more": true
}<p>이벤트가 제거되고 CEL 입력에서 실행되면 수집을 위해 게시됩니다. 나머지 출력 결과는 다음 CEL 프로그램 평가의 입력 상태로 제공됩니다.</p><p></p><p>CEL 프로그램의 작동 방식을 이해하기 위해, 몇 가지 쉬운 CEL 예시시를 살펴보면서CEL 입력이 작동하는 방식에 대해 자세히 알아보겠습니다.</p><h2>CEL 기본 사항</h2><p>CEL 언어에는 문이 없고 표현식만 있습니다. 모든 성공적인 CEL 표현식은 최종 값으로 평가됩니다. 다음은 출력과 함께 작성할 수 있는 가장 간단한 CEL 표현식 중 하나입니다.</p>mito &lt;(echo '
  "hello" + " " + "world"
')"hello world"<p>간단한 표현식은 대개 직관적입니다. 수학적 연산은 같은 유형의 값(예시: <code>int</code>를 가진 <code>int</code>)에만 지원되므로 필요에 따라 (여기에서는<code>int</code> 에서 <code>double</code>로) 유형을 변환합니다.</p>mito &lt;(echo '
  double((1 + 2) * (3 + 4)) / 2.0
')10.5<p>CEL 언어에는 변수가 없지만, Mito의 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-As__Macro_-Collections"><code>as</code></a> 매크로를 이용해 표현식에 이름을 부여하고 더 큰 표현식에서 사용할 수 있습니다. 이 예시에서 <code>(1 + 1)</code> 표현식은 <code>2</code> 값으로 평가되고 <code>.as(n, ...)</code>는 해당 값에 <code>n</code> 라는 이름을 부여하여 <code>"one plus one is "+string(n)</code> 표현식에서 사용합니다.</p>mito &lt;(echo '
  (1 + 1).as(n, "one plus one is "+string(n))
')"one plus one is 2"<p>또한 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-With-Collections"><code>with</code></a> 를 사용한 예처럼 맵에 정보를 축적하고 나중에 표현식에서 사용할 수도 있습니다.</p>mito &lt;(echo '
  { "key": "value" }.with({ "key2": "value2" }).as(data,
    {
      "data": data,
      "size": size(data),
    }
  )
'){
  "data": {
    "key": "value",
    "key2": "value2"
  },
  "size": 2
}<p>이 예시를 다시 살펴보세요. 중첩된 부분 <code>({ "data": data, "size": size(data), })</code>은 최종 값의 형태를 제공합니다. <code>"data"</code> 및 <code>"size"</code> 키를 포함한 맵입니다. 이러한 키의 값은 표현식의 외부 부분에 정의된 <code>data</code>에 따라 달라집니다. CEL 표현식을 안쪽에서 바깥쪽으로 읽으면 반환되는 내용을 빠르게 파악하는 데 도움이 됩니다.</p><p>CEL에는 <code>if</code>와 같은 제어 흐름 문이 없지만, 삼항 연산자를 사용해 조건부 분기를 수행할 수 있습니다:</p>mito &lt;(echo '
  1 + 1 &lt; 12 ? "few" : "many"
')"few"<p>CEL은 튜링 완전 언어가 아니기 때문에 무한 루프와 재귀가 지원되지 않습니다. 따라서 실행 시간을 예측할 수 있으며 , 이는 입력 데이터의 크기와 표현식의 복잡성에 비례합니다.</p><p>개별 CEL 표현식에서는 무한 루프가 불가능하지만, <a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros"><code>map</code></a> 과 같은 매크로를 사용해 목록과 맵을 처리할 수 있습니다.</p>mito &lt;(echo '
  [1, 2, 3].map(x, x * 2)
')[2, 4, 6]<p>이 섹션에서는 다음과 같은 내용을 다룹니다.</p><ul><li><p>스트링, 숫자, 리스트, 맵</p></li><li><p>스트링 연결</p></li><li><p>수학 연산</p></li><li><p>유형 캐스팅</p></li><li><p>조건문</p></li><li><p>하위 표현식의 이름 지정</p></li><li><p>컬렉션 처리</p></li></ul><p>다음으로 HTTP 요청을 하는 방법을 살펴보겠습니다.</p><h2>요청</h2><p>Mito는 CEL을 확장해 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#HTTP">HTTP 요청</a>을 만드는 기능을 추가합니다.</p>mito &lt;(echo '
  get("https://example.com").as(resp, string(resp.Body))
')"&lt;!doctype html&gt;&lt;html lang=\"en\"&gt;&lt;head&gt;&lt;title&gt;Example Domain&lt;/title&gt;..."<p>실행되기 전에 명시적으로 요청을 구성할 수 있습니다. 이를 통해 다양한 HTTP 메서드를 사용하고 헤더와 본문을 추가할 수 있습니다.</p><p>이 예시에서는 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Format_Query-HTTP"><code>format_query</code></a>의 도움을 받아 URL을 만들고, 요청에 헤더를 추가한 뒤, 응답 본문을 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Decode_JSON-JSON"><code>decode_json</code></a>으로 구문 분석합니다. <code>-log_requests</code> 옵션이 주어지면 Mito는 각 요청 및 응답에 대한 자세한 정보를 JSON 형식으로 로그에 기록합니다.</p>mito -log_requests &lt;(echo '
  request("GET",
    "https://postman-echo.com/get?" + {
        "q": ["query value"]
     }.format_query()
  ).with({
    "Header": { "Accept": ["application/json"] }
  }).do_request().as(resp, {
    "status": resp.StatusCode,
    "data": resp.Body.decode_json(),
  })
'){"time":"...","level":"INFO","msg":"HTTP request",...}
{"time":"...","level":"INFO","msg":"HTTP response",...}
{
  "data": {
    "args": {
      "q": "query value"
    },
    "headers": {
      "accept": "application/json",
      "accept-encoding": "gzip, br",
      "host": "postman-echo.com",
      "user-agent": "Go-http-client/2.0",
      "x-forwarded-proto": "https"
    },
    "url": "https://postman-echo.com/get?q=query+value"
  },
  "status": 200
}<h2>상태 관리 및 평가</h2><p>이제 요청 방법과 원하는 출력 상태를 생성하는 데 필요한 CEL 기본 사항을 알아보았으니 출력 상태에 무엇을 넣어야 하는지, 그리고 이것이 이후 처리를 어떻게 지시하는지 자세히 살펴보겠습니다.</p><p>통합의 CEL 프로그램은 출력 상태가 다음 평가의 입력으로 적합하게 사용될 수 있도록 해야 합니다. 구성은 초기 상태를 설정하며, 적절한 변경이 있을 경우 출력에서 이를 반복해야 합니다. 이를 수행하는 쉬운 방법은 <code>state.with({ ... })</code> 를 사용해 일부 재정의와 함께 상태 맵을 반복하는 것입니다. 작은 프로그램에서는 전체 프로그램을 <code>state.with()</code>로 감싸서 상태 전파가 출력 데이터를 생성하는 각 분기(예시: 성공, 오류)에서 반복되지 않도록 하는 패턴이 자주 사용됩니다.</p><p>상태 값이 초기 입력 상태에 하드코딩되지 않고 평가를 통해 초기화되는 경우, 프로그램은 초기값을 설정하기 전에 기존 값이 있는지 확인해야 합니다. <a href="https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes">선택적 구문 및 유형</a>에 대한 지원이 이러한 문제를 해결하는 데 도움이 될 수 있습니다. 맵 키에서 필드 이름 앞에 물음표를 사용하면 액세스가 선택 사항이 됩니다. 값으로 확인될 수도 있고 확인되지 않을 수도 있지만, 추가 선택적 액세스가 가능하며 값이 없는 경우 기본값을 쉽게 제공할 수 있습니다:
</p>mito -data &lt;(echo '{}') &lt;(echo '
  int(state.?counter.orValue(0)).as(counter,
    state.with({
      "counter": counter + 1,
      "want_more": counter + 1 &lt; 3,
    })
  )
'){ "counter": 1, "want_more": true }
{ "counter": 2, "want_more": true }
{ "counter": 3, "want_more": false }<p>이 예시의 상태에서 읽은 카운터 값은 JSON과 JavaScript의 <code>Number</code> 유형이 정한 관례에 따라 부동소수점 숫자로 직렬화되어 있으므로 <code>int</code>로 캐스팅됩니다. 또한 여기서 <code>"want_more": true</code>는 Mito에 의해 존중되지만, CEL 입력에서 실행될 때는 출력에 이벤트가 포함될 때만 평가가 반복된다는 점에 유의해야 합니다.</p><p>CEL 입력으로 실행되는 CEL 프로그램의 경우 출력 맵에 <code>"events"</code> 키를 반환해야 합니다. 이 값은 이벤트 맵의 목록, 빈 목록 또는 단일 이벤트 맵일 수 있습니다. 단일 이벤트 사례는 일반적으로 오류 처리에 사용됩니다. 이벤트는 입력에 의해 게시되지만 해당 값도 로그에 기록되며, <code>error.message</code> 값이 설정되면 해당 값이 통합의 Fleet 상태를 업데이트하는 데 사용됩니다. 프로그램에서 오류가 아닌 단일 이벤트를 생성하는 경우 목록으로 래핑하는 것이 가장 좋습니다.</p><p>앞서 살펴본 GitHub 이슈 프로그램의 결과를 다시 한 번 살펴보세요.</p>{
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "per_page": 3,
  "max_pages": 3,
  "cursor": {
    "page": 2
  },
  "events": [
    { ... },
    { ... },
    { ... }
  ],
  "want_more": true
}<p>이 프로그램은 다음과 같은 방법으로 상태를 효과적으로 관리했습니다.</p><ul><li><p><code>url</code>, <code>per_page</code>, <code>max_pages</code>에서 초기 상태 값을 반복합니다.</p></li><li><p><code>cursor.page</code>에서 재시작 후 지속되어야 할 상태를 추가합니다.</p></li><li><p><code>events</code> 목록에 게시할 준비가 된 이벤트를 반환합니다.</p></li><li><p><code>want_more: true</code>를 사용해 즉각적인 재평가를 요청합니다.</p></li></ul><p>선택적 액세스 및 상태 관리, CEL 기본 사항 및 HTTP 요청에 대해 배워보았으니, 이제 전체 GitHub 이슈 프로그램을 읽을 수 있습니다. Mito로 실행하고 몇 가지 변경 사항을 실험해 보세요.</p><h2>검토 및 리소스</h2><p>이 글에서는 CEL 언어가 무엇인지, 그리고 Mito 라이브러리에서 CEL 입력에 사용하기 위해 어떻게 확장되었는지 알아봤습니다. GitHub API에서 이슈 정보를 가져오는 예제 프로그램에서 CEL의 유연성을 살펴보고, 초기 상태의 설정 액세스, HTTP API와의 상호작용, 수집할 이벤트 반환, 이후 프로그램 실행을 위한 상태 관리 등 해당 프로그램을 이해하는 데 필요한 모든 세부 사항을 살펴봤습니다.</p><p>CEL 입력을 사용하여 통합 기능을 구축하고 자세히 알아보려면 다음과 같은 여러 자료를 살펴보시기 바랍니다.</p><ul><li><p><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">CEL 입력 - Filebeat 설명서</a></p></li><li><p><a href="https://pkg.go.dev/github.com/elastic/mito">Mito 설명서</a></p></li><li><p><a href="https://cel.dev/">공통 표현식 언어 - cel.dev 웹사이트</a></p></li><li><p><a href="https://www.elastic.co/docs/extend/integrations">통합 생성 - Elastic 문서</a></p></li></ul><p>CEL 입력을 활용한 통합 구축에 있어 가장 유용한 리소스 중 하나는 GitHub에서 찾아볼 수 있는 기존 Elastic 통합의 CEL 코드입니다.</p><p><a href="https://github.com/search?q=repo%3Aelastic%2Fintegrations+path%3A**%2Fcel.yml.hbs&amp;type=code"><code>cel.yml.hbs</code></a><a href="https://github.com/search?q=repo%3Aelastic%2Fintegrations+path%3A**%2Fcel.yml.hbs&amp;type=code"> Elastic 통합 리포지토리에 저장된 파일 - GitHub</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/common-expression-language-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/common-expression-language-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Chris Berkhout]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt330db607ffb818f9/6a1705a08b73cb8502189f4c/985c50bfabee3348494eb4307f0b3375a97a0644-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[채팅창을 넘어선 Agent Builder: Augmented Infrastructure 소개]]></title>
    <description><![CDATA[Elastic Agent Builder with Augmented Infrastructurer에 대해 알아보세요. 이 AI 에이전트는 증강 운영, 증강 개발 및 증강 합성을 지원합니다.]]></description>
    <content:encoded><![CDATA[<p><strong>빈말이 아닙니다. 직접 구현합니다.</strong></p><p>우리는 모두 AI 에이전트의 부상을 목격했습니다. AI는 텍스트를 요약하고, 코드 스니펫을 작성하고, 문서를 기반으로 질문에 답하는 데 탁월합니다. 하지만 DevOps 및 사이트 안정성 엔지니어링(SRE) 담당자에게는 답답한 한계가 있었습니다. 대부분의 에이전트는 콜센터 패러다임에 갇혀 있어 읽고, 생각하고, 채팅할 수는 있지만, 관리해야 할 인프라에 직접 접근하여 조작할 수는 없다는 것입니다.</p><p>최근 해커톤 프로젝트에서 Elastic은 이러한 한계를 뛰어넘기로 결정했습니다.</p><p>이에 <strong>Augmented Infrastructure</strong>를 구축했습니다. 이는 단순히 조언만 제공하는 것이 아니라 실시간 환경을 생성, 배포, 모니터링 및 수정하는 인프라 코파일럿입니다.</p><h2><strong>문제: 복사, 형식 변경, 붙여 넣기</strong></h2><p>표준 에이전트는 독립적으로 작동합니다. 앱이 다운되어 회사에 5백만 달러의 손실이 발생하면 표준 에이전트는 문제를 해결하는 방법에 대한 런북을 읽어줄 수 있습니다. 하지만 실제 작업은 여전히 <em>사용자</em>가 직접 해야 합니다. 코드를 복사하고, 환경에 맞게 형식을 변경한 다음, 터미널에 붙여 넣어야 합니다.</p><p>Kubernetes에 대해 <em>이야기하는 것</em>과 Kubernetes를 <em>구성하는 것</em>의 차이를 이해하는 에이전트가 필요했습니다.</p><h2><strong>엔진: Elastic Agent Builder란 무엇입니까?</strong></h2><p>이를 구축하기 위해 처음부터 모든 것을 새로 시작한 것은 아닙니다. <a href="https://www.elastic.co/kr/elasticsearch/agent-builder"><strong>Elastic Agent Builder</strong></a>를 기반으로 구축했습니다. Elastic Agent Builder는 에이전트를 신속하게 개발할 수 있도록 설계된 프레임워크로, 대규모 언어 모델(LLM)(데모에서는 Google Gemini를 사용)과 Elasticsearch에 저장된 개인 데이터 간의 연결 고리 역할을 합니다.</p><p>Agent Builder는 문서나 로그와 같은 내부 데이터를 기반으로 대화형 AI를 구현하는 데 사용할 수 있습니다. 하지만 Agent Builder의 가장 강력한 기능은 <strong>도구</strong>를 할당할 수 있다는 점입니다. 이러한 도구를 통해 LLM은 채팅 인터페이스를 벗어나 특정 작업을 수행할 수 있습니다. 이 기능을 최대한 활용하면 Agent Builder를 강력한 자동화 도구로 탈바꿈시킬 수 있다는 사실을 깨달았습니다.</p><h2><strong>작동시키기: 첫 번째 버전 구축</strong></h2><p>프로젝트를 시작할 때 에이전트가 외부 세계를 변화시킬 수 있도록 만들고 싶었습니다. 그래서 이런 아이디어를 떠올렸습니다. (에이전트가 호스트에서 생각할 수 있는 모든 명령을 실행하는) '실행기' 소프트웨어를 구축하면 어떨까? 그리고 만약 실행기, Elastic Agent Builder, 그리고 사용자가 3자 호출을 하면 어떨까?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec9d20da8c41a898/6a170704dc55debd4ce00d43/8dc8317c1301b8eb7b89438529e8d8d17411c95a-1024x559.png" alt="Agent Builder with Augmented Infrastructure architecture" /><p>먼저 Augmented Infrastructure 실행기라는 Python 프로젝트를 구축했는데, 이는 기본적으로 Elastic Agent Builder 대화 API를 매초마다 쿼리하고 당사가 만든 특수 구문을 확인하는 while(true) 루프였습니다.</p>{
 "tool_name": "my_tool",
 "tool_arguments": "\{stringified json arguments\}"
 }<p>그런 다음 새로운 도구 호출 구문에 대해 알려주도록 프롬프트를 업데이트했습니다. Bill은 Python으로 모델 컨텍스트 프로토콜(MCP) 서버를 구축하는 데 가장 많이 사용되는 프레임워크인 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>의 유지 관리자입니다. Bill은 FastMCP 클라이언트와 이 새로운 실행기 소프트웨어를 사용하여 MCP 서버를 마운트하고 실행기에서 해당 도구를 사용할 수 있도록 하는 작업을 시작했습니다. 에이전트가 이를 확인하면 도구 호출을 실행하고 마치 사용자가 결과를 보낸 것처럼 결과가 대화창에 다시 POST됩니다. 이렇게 하면 LLM이 결과에 응답하게 되고, 그렇게 진행이 가능해집니다!</p><p>매우 훌륭했지만 다음과 같이 두 가지 주요 문제가 있었습니다.</p><ol><li><p>에이전트가 이 모든 JSON을 사용자와의 대화로 바로 내보냅니다.</p></li><li><p>대화 API를 통해 메시지를 볼 수 있는 가장 빠른 시점이 대화 라운드가 완료된 시점(즉, LLM이 응답한 시점)입니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0596217e962f8464/6a17070647d49c3fef2d890c/7b3755aeae17722ff1bb9677712293e9195f96a0-1058x1034.png" alt="Issue when building agent with augment infrastructure" /><p>그래서 이를 백그라운드로 옮기는 방법을 파악하기 위해 노력했습니다.</p><p>그다음, 에이전트에 call_external_tool이라는 도구를 제공하고 tool_name과 문자열화된 JSON 도구 인수라는 두 개의 인수를 전달하도록 변경했습니다. 이 외부 도구 호출은 아무것도 반환하지 않지만, 중요한 것은 대화 API에 대한 GET 요청에서 확인할 수 있다는 점입니다. 그런 다음 실행기에 Elasticsearch에 직접 문서를 작성할 수 있는 권한을 부여했고, Elastic Agent Builder 에이전트는 필요에 따라 해당 문서를 검색할 수 있습니다. 에이전트는 항상 사용자 메시지에 응답하여 작동하므로, 에이전트가 결과를 찾고 처리를 계속할 수 있도록 사용자 메시지로 에이전트를 시작해야 합니다. 그래서 다음과 같이 에이전트가 채팅에 간단한 메시지를 삽입하여 대화를 재개하도록 했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta22be3c67ad2ff1f/6a170708cdacbf0ae87d295b/61ff59a57c68ed5fad492d19c0580644113a507d-1600x1321.png" alt="Agent Builder with Augmented Infrastructure demostration" /><p>이제 외부 도구 호출이 가능해졌습니다. 하지만 위에서 언급한 두 번째 문제 때문에 마지막 시작 단계를 없애야 했습니다. 그렇지 않으면 외부 도구를 호출할 때마다 결과를 얻기 위해 전체 대화 라운드를 거쳐야 했기 때문입니다.</p><h2><strong>효율적으로 만들기: 워크플로우 소개</strong></h2><p>Agent Builder 에이전트는 Elasticsearch 쿼리 언어(ES|QL) 및 인덱스 검색 도구 호출 외에도 Elastic 워크플로우 기반 도구를 호출할 수 있습니다. Elastic 워크플로우는 임의의 작업 순서와 로직을 실행할 수 있는 유연하고 관리하기 쉬운 방법을 제공합니다. 본 예시에서는 워크플로우가 외부 도구에 대한 Elasticsearch 요청을 저장하고 결과를 폴링할 ID를 반환하기만 하면 됩니다. 따라서 다음과 같은 간단한 워크플로우 정의가 생성됩니다.</p>name: ai-tool-call
enabled: true
triggers:
  - type: manual
inputs:
  - name: runner_id
    type: string
  - name: tool_calls
    type: string

steps:
  - name: store_request
    type: elasticsearch.create
    with:
      index: distributed-tool-requests
      id: "{{inputs.runner_id}}_{{ execution.id }}"
      document:
        request_id: "{{ execution.id }}"
        runner_id: "{{inputs.runner_id}}"
        tool_call: "{{inputs.tool_calls}}"
        status: "처리되지 않음"

  - name: output_result
    type: console
    with:
      message: "도구 호출됨. 실행 id: {{ execution.id }}. 이 ID를 사용하여 결과를 조회하세요."<p>이를 통해 도구 호출 요청이 대화에 기록되는 것에 의존하는 대신, 실행기는 새로운 외부 도구 요청에 대해 Elasticsearch distributed-tool-requests 인덱스를 폴링하고 제공된 execution.id를 사용하여 결과를 다른 Elasticsearch 인덱스에 보고할 수 있습니다.</p><p>이는 다음과 같이 위에서 언급한 두 가지 주요 문제를 해결합니다.</p><ol><li><p>대화 기록이 더 이상 외부 도구 호출에 위한 페이로드로 인해 복잡해지지 않습니다.</p></li><li><p>실행기는 대화 기록 대신 Elasticsearch 인덱스를 폴링하므로, 외부 도구 요청을 표시하기 위해 대화 라운드가 완료될 때까지 차단되지 않습니다.</p></li></ol><p>두 번째는 외부 도구 호출 처리가 대화 라운드가 완료된 시점이 아니라 에이전트의 사고 단계 내에서 시작된다는 큰 장점이 있습니다. 이를 통해 시스템 프롬프트에서 LLM에 외부 도구 결과가 나올 때까지 지속적으로 폴링하도록 지시할 수 있으며, 시작 메시지를 표시할 필요가 없습니다. 전반적으로 이러한 방식은 대화 흐름을 더욱 자연스럽게 만들어 줍니다. LLM은 단일 대화 라운드 내에서 여러 외부 도구 요청을 처리할 수 있으므로(도구 요청당 별도의 대화 라운드가 필요하지 않음), 더 복잡한 사용자 요청을 한 번에 처리할 수 있습니다.</p><h2><strong>한곳에 모으기</strong></h2><p>LLM과 서버 랙 간의 격차를 해소하기 위해 다음과 같이 Agent Builder의 도구 기능을 사용하여 특정 아키텍처를 개발했습니다.</p><ol><li><p><strong>Augmented Infrastructure 실행기:</strong> 대상 환경(서버, Kubernetes 클러스터, 클라우드 계정) 내부에 경량 실행기를 배포했습니다. 이러한 실행기는 각 실행기만 접근 가능한 보안 엔드포인트와 보안 정보를 사용하여 Elastic에 직접 연결됩니다.</p></li><li><p><strong>ES|QL 검색:</strong> 코파일럿은 Elastic의 <strong>ES|QL</strong>을 사용하여 하이브리드 검색을 수행합니다. 단순히 지식을 검색하는 것이 아니라 <em>기능</em>을 검색합니다. 연결된 실행기에 쿼리하여 사용 가능한 도구(예: list_ec2_instances, install_helm_chart)를 확인합니다.</p></li><li><p><strong>워크플로우 실행:</strong> 에이전트가 작업 과정을 결정하면 구조화된 워크플로우를 생성합니다.</p></li><li><p><strong>피드백 루프:</strong> 실행기는 로컬에서 명령을 실행하고 그 결과를 다시 Elasticsearch에 보고합니다. 코파일럿은 인덱스에서 결과를 읽고 다음 단계를 결정합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9726199693a10c5c/6a17070ae8fbced43a39fb9a/76be256da722c1965971fc506502768bd890f0c4-1290x1076.png" alt="Architecture using Agent Builder’s tool capabilities with Augmented Infrastructure" /><h2><strong>데모: 중단에서 통합 가시성으로</strong></h2><p>이 동영상에서는 이 아키텍처의 강력한 성능을 보여주는 두 가지 서로 다른 시나리오를 소개했습니다.</p><h3><strong>시나리오 1: DevOps 지원</strong></h3><p>Kubernetes 클러스터의 사각지대 때문에 5백만 달러 규모의 장애가 발생하여 패닉 상태에 빠진 사용자의 이야기로 시작했습니다.</p><ul><li><p><strong>요청:</strong> "이런 일이 다시 발생하지 않도록 하려면 어떻게 해야 하나요?"</p></li><li><p><strong>작업:</strong> 에이전트는 단순히 튜토리얼만 제공하지 않았습니다. 클러스터 식별, 필요한 네임스페이스 생성, Kubernetes 보안 정보 생성, OpenTelemetry Operator 설치, 실시간 APM 대시보드에 대한 링크 즉시 제공을 수행했습니다.</p></li><li><p><strong>결과:</strong> 사용자가 단 한 줄의 YAML도 작성하지 않고도 완전한 Kubernetes 통합 가시성과 애플리케이션 인사이트를 얻을 수 있습니다.</p></li></ul><h3><strong>시나리오 2: 보안 인계</strong></h3><p>인프라 보안의 기본 원칙은 보이지 않는 것은 보호할 수 없다는 것입니다. DevOps 복구 작업을 수행하는 동안 에이전트는 환경의 보안을 개선할 기회를 포착합니다.</p><p>이전 Elastic Observability 관련 조사에서 발생한 경보를 통해 보안 담당자가 인프라와 직접 소통하는 방법을 시연합니다. 첫째, 클라우드 환경의 자산과 리소스를 열거하고, 둘째, 환경 보안을 보장하는 데 필요한 도구를 배포하는 방법을 보여줍니다.</p><ul><li><p><strong>탐색:</strong> 코파일럿은 보안 담당자를 위해 AWS 리소스를 열거하고 중요한 문제점을 발견했습니다. 해당 문제점은 엔드포인트 보호가 누락된 공용 엔드포인트가 있는 Amazon Elastic Compute Cloud(EC2) 인스턴스와 Amazon Elastic Kubernetes Service(EKS) 클러스터입니다.</p></li><li><p><strong>조치:</strong> 간단한 승인으로 코파일럿은 <strong>Elastic Security</strong> <strong>확장 탐지 및 응답(XDR), 클라우드 탐지 및 응답(CDR)</strong>을 취약한 자산에 배포하여 환경을 실시간으로 보호했습니다.</p></li><li><p><strong>결과:</strong> 완벽한 런타임 보안을 통해 배포된 AWS 자산 및 리소스를 보호합니다.</p></li></ul><h2><strong>미래: 모든 것이 증강되는 세상</strong></h2><p>이 프로젝트는 Elastic Agent Builder가 분산 운영의 핵심 역할을 할 수 있음을 입증합니다. 이는 인프라에만 국한되지 않습니다. 실행기 기술은 다음과 같은 분야에서도 활용될 수 있습니다.</p><ul><li><p><strong>증강 합성:</strong> 글로벌 실행기 전반의 TLS 오류 진단.</p></li><li><p><strong>증강 개발:</strong> 풀 리퀘스트 생성 및 프론트엔드 서비스에서 CAPTCHA 구현.</p></li><li><p><strong>증강 운영:</strong> 중단 발생 시 DNS 리졸버 자동 재구성.</p></li></ul><h2><strong>직접 사용해 보기</strong></h2><p>AI의 미래는 단순히 채팅 지원에 그치는 것이 아니라, <strong>Augmented Infrastructure</strong>에 관한 것이라고 생각합니다. 즉, 고객과 함께 배포, 수정, 관찰, 보호할 수 있는 파트너를 확보하는 것입니다.</p><p>지금 바로 코드를 확인하고 분산 실행기(<a href="https://github.com/strawgate/augmented-infrastructure">GitHub</a>)와 Elastic Agent Builder를 <a href="https://cloud.elastic.co/">Elastic Cloud Serverless</a> 에서 직접 사용해 보세요!</p><ul><li><p>Elastic Cloud에서 서버리스 프로젝트를 생성하세요.</p></li><li><p>실행기에 코드를 배포하세요.</p></li><li><p>실행기를 설정하세요.</p></li><li><p>mcp.json을 구성하세요.</p></li><li><p>실행기를 시작하면 에이전트와 그 도구가 자동으로 생성됩니다.</p></li><li><p>분산 실행기에 대한 추론, 계획 및 실행을 수행할 수 있는 에이전트와 채팅하세요!</p></li></ul><p><strong>팀: </strong><em>알렉스, 빌, 길, 그레이엄, 노리</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI 도구 ]]></category>
    <dc:creator><![CDATA[Alexander Wert,Bill Easton,Gil Raphaelli,Graham Hudgins,Norrie Taylor]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6de9245ad57ccc00/6a17070cdc55deaa39e00d48/e08daf78f328e826f39d06329f6a5487f75d178d-1272x700.png" length="0" type="image/png"/>
    <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Agent Builder 정식 출시: 컨텍스트 기반 에이전트, 몇 분 만에 구축 가능]]></title>
    <description><![CDATA[Agent Builder가 이제 정식 출시되었습니다. 컨텍스트 기반 AI 에이전트를 신속하게 개발할 수 있는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic Cloud Serverless 및 곧 출시될 9.3 릴리스에서 Agent Builder가 정식 출시됨을 기쁜 마음으로 알려 드립니다. Agent Builder는 Elasticsearch의 역량을 컨텍스트 엔지니어링 플랫폼으로 활용하여 컨텍스트에 중점을 둔 데이터 중심의 AI 에이전트를 신속하게 개발할 수 있도록 합니다.</p><p>에이전트는 효율성 향상과 더 나은 고객 경험을 제공할 수 있는 잠재력으로 인해 주목받고 있습니다. 그러나 실제로는 에이전트에게 적절한 컨텍스트를 제공하기가 쉽지 않으며, 특히 복잡하고 비정형적인 엔터프라이즈 데이터를 처리할 때는 더욱 그렇습니다. 개발자는 도구, 프롬프트, 상태, 추론 논리, 모델을 관리해야 하며, 특히 비즈니스 소스에서 관련 컨텍스트를 검색하여 정확한 결과와 작업을 제공해야 합니다. Elastic Agent Builder는 안전하고 신뢰할 수 있는 컨텍스트 기반 에이전트를 개발하기 위한 핵심 구성 요소를 제공합니다.</p><h2>Agent Builder의 핵심 기능</h2><p>Agent Builder는 검색 정확도와 검색 증강 생성 분야에 대한 Elastic의 지속적인 투자와 Elasticsearch를 최적의 벡터 데이터베이스로 만들기 위한 노력을 통해, 맥락적이고 데이터에 초점을 둔 AI 에이전트 개발을 단순화합니다.</p><p>Agent Builder로 할 수 있는 일은 다음과 같습니다.</p><ul><li><p>질문에 답하고, 분석을 수행하며, Elasticsearch에 있는 모든 데이터를 기반으로 조사까지 진행할 수 있는 기본 제공 대화형 에이전트를 즉시 시작할 수 있습니다.</p></li><li><p>복잡하고 비정형적인 데이터에서 출발해, 설정 기반의 개발 환경을 갖춘 사용자 정의 에이전트로 신속히 전환할 수 있습니다.</p></li><li><p>기본 제공 ES|QL이나 사용자 정의 도구를 활용해 업계 최상급의 하이브리드 검색 정확도를 적용함으로써, 컨텍스트의 품질과 에이전트의 안정성을 높일 수 있습니다.</p></li><li><p>복잡한 워크플로우를 재사용 가능한 도구로 실행하여 데이터 강화, 기록 갱신, 메시지 발송 등 다양한 규칙 기반 자동화를 구현할 수 있습니다(미리보기 버전).</p></li><li><p>워크플로우 및 MCP를 사용하여 Elasticsearch 외부의 데이터 소스에 연결하고 에이전트에 대한 컨텍스트를 상호 연관시키고 결합할 수 있습니다.</p></li><li><p>MCP 기반의 기본 제공·사용자 정의 도구를 통해 다양한 에이전틱 및 애플리케이션 프레임워크와 연동할 수 있고, 외부 MCP 연결(미리보기 버전), A2A 지원, 완전한 API 지원을 제공합니다.</p></li><li><p>Agent Builder의 기능을 더욱 확장하려면 복잡한 문서 처리에는 LlamaIndex를, 보안성과 구조화를 갖춘 도구 액세스에는 Arcade.dev를 연동할 수 있습니다.</p></li></ul><p>Agent Builder의 기능성을 한층 강화하고자, 새로운 규칙 기반 자동화 기능인 Elastic Workflows를 기술 미리보기로 선보입니다. 조직 차원의 작업에서 에이전트는 특정 비즈니스 로직을 구현하기 위해 규칙 기반 조치의 확실성과 신뢰성이 필요한 경우가 종종 있습니다. Elastic Workflows는 에이전트가 내부·외부 시스템을 오케스트레이션해 조치를 실행하고, 데이터와 컨텍스트를 수집 및 가공할 수 있는 단순한 선언형 방법을 제공합니다. 워크플로는 완벽하게 조합 가능하고, 이벤트 중심 구조와 높은 유연성을 갖추고 있으며, MCP를 통해 에이전트에 도구로 노출될 수 있습니다.</p><h2>데이터에서 에이전트로 단 몇 분 만에 전환</h2><p>에이전트 개발은 분산된 데이터 저장소를 통합하고, 수동 파이프라인을 구축하며, 쿼리를 튜닝하고, 복잡한 오케스트레이션을 관리해야 해 초기 단계에서 몇 주가 걸리기도 합니다. Agent Builder는 별도의 데이터 저장소, 벡터 데이터베이스, RAG 파이프라인, 검색 계층, 쿼리 변환기, 도구 오케스트레이터가 필요 없도록 해 주어 에이전트 개발 시간을 단축하고, 에이전트 로직과 애플리케이션 제공에 집중할 수 있게 합니다.</p><p>Agent Builder는 Elasticsearch 플랫폼의 기본 구성 요소를 네이티브로 통합해 에이전트 개발을 빠르게 만듭니다.</p><ul><li><p>인덱싱된 데이터를 대상으로 즉시 대화하고 추론할 수 있는 기본 제공 대화형 에이전트를 통해 바로 시작할 수 있습니다.</p></li><li><p>Kibana, API, MCP, A2A를 통한 대화형 접근을 활용해 에이전트를 애플리케이션, 대시보드 또는 CI/CD 시스템에 통합할 수 있습니다.</p></li><li><p>기본 제공 도구를 활용해 데이터 구조를 이해하고, 적절한 인덱스를 선택하며, 최적화된 하이브리드·시맨틱·구조화 쿼리를 생성하고, 자연어 프롬프트를 기반으로 ES|QL을 사용해 시각화를 유연하게 구성할 수 있습니다.</p></li></ul><p>더 깊이 살펴보려면 전 과정의 <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">실습형 단계별 안내를</a> 활용해 보세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8def92028138672/6a17e086af47b60cd8cdde96/b55b63eae40f72952967cc8f3ea4df4cd62d7d70-1080x608.gif" alt="Elastic Agent Builder 단계별 안내" /><h2>컨텍스트 엔지니어링을 위한 완벽한 데이터 플랫폼인 Elasticsearch 기반 구축</h2><p>AI 에이전트의 경우, 효과적인 추론을 제공하고 환각의 위험을 줄이기 위해서는 컨텍스트의 품질이 매우 중요합니다. 많은 기업용 AI 에이전트에게 있어 작업을 수행하는 데 필요한 비즈니스 데이터는 가장 중요한 컨텍스트 정보입니다. 대규모 확장이 가능한 데이터 저장소이자 벡터 데이터베이스이며 검색 정확도 분야의 선두 주자인 Elasticsearch는 이미 강력한 컨텍스트 엔지니어링 핵심 기능을 다수 제공하고 있습니다. 컨텍스트 엔지니어링은 단순한 검색 증강 생성을 넘어, 데이터 조회·순위화·필터링·표현 방식을 조정 및 확장할 수 있게 하여 에이전트가 받는 잡음과 모호함을 줄이는 데 도움이 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4c10c1d09e9f81e/6a17e087577262feb31bcb4b/419b9b6f13739e0a8983249d8ac31478e73dac89-1600x901.png" alt="Agent Builder 다이어그램" /><p>Elasticsearch는 어휘 검색, 벡터 검색, 구조화 필터링을 결합한 컨텍스트 엔진을 제공하여 모델이 관련성 높고 정확한 컨텍스트에서 동작하도록 보장해 실질적으로 <a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">LLM 성능을 향상시킵니다</a>. 이 기능은 에이전트형 검색을 기반으로 하며, 적절한 인덱스를 자동으로 선택하고 자연어를 컨텍스트에 최적화된 쿼리로 변환하는 기본 제공 도구와 검색 로직이 이를 지원합니다.</p><p>Agent Builder를 사용하면 정확도와 순위화를 제어하는 기능을 통해 에이전트가 가장 유용한 컨텍스트를 우선적으로 받도록 할 수 있으며, 점수 매기기·순위화·필터링 로직을 세밀하게 조정할 수 있습니다. Elasticsearch는 불명확한 검색 동작에 의존하는 대신, 무엇이 중요한지, 왜 중요한지, 그리고 어떻게 우선순위를 둘지를 직접 제어할 수 있게 합니다. 이 모든 것은 텍스트, 벡터, 메타데이터, 로그 등 모든 데이터를 하나의 플랫폼에서 저장·확장할 수 있는 확장형 데이터 플랫폼인 Elasticsearch를 기반으로 하여, 에이전트용 컨텍스트 관리를 더욱 용이하게 합니다.</p><h2>재사용 가능한 도구로 복잡한 워크플로우 실행</h2><p>AI 에이전트가 복잡한 작업에서 추론을 수행하더라도, 자동화의 상당 부분은 특정 비즈니스 로직을 적용하는 규칙 기반 조치를 안정적으로 실행하는 데 의존합니다. Elastic Workflows는 내부 및 외부 시스템을 오케스트레이션하여 작업을 실행하고 컨텍스트 또는 데이터를 수집한 뒤, 이를 에이전트에 통합할 수 있도록 하는 단순한 선언형 방법을 제공합니다. 워크플로우는 YAML로 정의되며, 완전한 조합 구조를 지원하여 작업 성격에 따라 단순하게도, 복잡하게도 구성할 수 있습니다. 이를 통해 에이전트는 Elasticsearch 플랫폼과 다양한 솔루션, 나아가 서드파티 애플리케이션까지 아우르며 효율적으로 동작할 수 있습니다.</p><p>워크플로우를 Agent Builder와 통합하는 과정은 세 단계로 진행할 수 있습니다(전제 조건: <a href="https://github.com/elastic/workflows">여기</a> 안내된 자세한 내용에 따라 워크플로우 활성화).</p><p>1. 기본 제공 자동 완성 및 테스트 기능을 갖춘 간단한 YAML 기반 편집기를 사용하여 새 워크플로우를 생성하고 저장합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00585158429a3395/6a17e089e317916b122d5740/308888bf3d2fa013f9391a55be6a6fbd458b6dac-1600x998.png" alt="Agent Builder 워크플로우" /><p>2. Agent Builder에서 “워크플로우” 타입의 새 도구를 생성한 뒤, 에이전트가 해당 워크플로우 도구를 사용할 시점을 결정하는 데 도움이 되도록 설명을 입력합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt874b6a1ce3a2ac34/6a17e08be9ea87b1dea9c4d9/c04810d30d226112c3610bd58e208607b213fc3d-1600x945.png" alt="Agent Builder에서 새 도구 생성하기" /><p>3. 사용자 정의 에이전트에 워크플로우 도구를 추가합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94a31cb60ef11ce6/6a17e08daf47b61f0dcdde9a/724cd4ac93c46efb0d339fd140e5caf138f8150f-1600x948.png" alt="사용자 정의 에이전트에 워크플로우 도구를 추가합니다." /><p>4. 끝입니다! 이제 에이전트가 대화 도중에 워크플로우를 호출할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5143f401a06e8ba2/6a17e08fdbb4ffcfc6fb55de/8dfdd726ab89e31c48b79372650ce33946713dca-1600x929.png" alt="Elastic Agent Builder를 사용해 AI 에이전트를 생성했습니다" /><h2>나만의 에이전트, 나만의 규칙</h2><p>Agent Builder는 하나의 개발 패러다임만을 강요하지 않습니다. 대신 데이터, 정확도, 모델, 상호 운용성, 보안, 에이전트 설계 전반을 완전히 제어할 수 있는 개방적이고 유연한 에이전트 개발 방식을 지원하도록 설계되었습니다.</p><p>사용자 지정 에이전트 정의를 사용하면 에이전트가 사용할 수 있는 도구를 명확히 지정하고, 사용자 지정 시스템 프롬프트를 삽입하며, 에이전트 지침을 세밀하게 조정하고 보안 범위를 설정할 수 있습니다. 에이전트는 특정 모델에 종속되지 않으므로, 하나의 제공업체에 의존하지 않고 기본 제공 모델부터 외부 에코시스템의 LLM까지 원하는 대로 자유롭게 설정할 수 있습니다.</p><p>도메인별 로직(예: 특정 인덱스 필터, ES|QL 조인, 분석 파이프라인)을 캡슐화한 확장형 도구를 구축하고, 프로덕션 환경에서 안전하게 사용하도록 제약을 설정할 수 있습니다. 완전한 API 지원으로 다른 에이전트형 프레임워크와의 상호 운용이 가능하며, 모델 컨텍스트 프로토콜(MCP)을 기본적으로 지원합니다. A2A 통합을 통해 Elastic 에이전트를 다른 프레임워크, 서비스, 클라이언트 앱에 노출할 수 있으며, 동일한 데이터와 컨텍스트 엔지니어링 로직을 여러 통합 환경에서 재사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt309a0b3dd4cc367b/6a17e090ec0f8932045a6550/5e903ba24ffb3f40231e901f63bd494c89cb7757-1600x1004.png" alt="Elastic Agent Builder를 사용하여 AI 에이전트 구성하기" /><p>Agent Builder는 유연하고 개방적인 개발을 지원하며, 널리 사용되는 에이전트 프레임워크 및 플랫폼과 쉽게 통합되도록 설계되었습니다. 효과적인 에이전트를 제공하려면 이러한 통합이 필수적인 요소가 될 수 있습니다. <strong>Arcade.dev의 공동 창립자인 Sam Partee</strong>의 말처럼,</p><p><em>"오늘날 에이전트형 시스템이 실패하는 이유는 AI를 도구와 데이터에 연결하는 과정이 복잡하기 때문입니다. Arcade.dev와 연동된 Elastic Agent Builder는 에이전트의 컨텍스트 조회, 추론, 실행 과정을 개발자가 체계적이고 안전하게 관리할 수 있는 방식을 제공하여, 데모 수준의 에이전트를 프로덕션 등급으로 끌어올릴 수 있게 합니다.</em></p><p>Agent Builder는 복잡한 데이터 처리를 위해 Elasticsearch의 확장성을 활용하기도 합니다. <strong>LlamaIndex CEO인 Jerry Liu</strong>의 말처럼,</p><p><em>"비정형 데이터 소스에서 엔터프라이즈 컨텍스트를 끌어내는 것이 효과적인 에이전트를 구축하는 데 있어 핵심입니다. LlamaIndex의 복잡한 문서 처리 기능과 결합된 Elastic Agent Builder는 핵심 컨텍스트 계층을 강화해, 팀이 데이터를 검색·가공·준비할 수 있도록 지원하며 에이전트가 보다 정확하게 추론하고 더 나은 결과를 제공하도록 합니다.</em></p><h2>무엇을 구축할 수 있나요?</h2><p>Agent Builder는 이미 다양한 사용 사례에 사용되고 있습니다. 아래에는 에이전트 개발을 시작하는 데 참고할 수 있는 몇 가지 예시와 아키텍처가 소개되어 있습니다.</p><ul><li><p><strong>인프라 자동화: </strong>지원 업무 상황에서 에이전트는 읽고, 생각하고, 대화하는 데 활용되어 왔지만, 지금까지는 실제로 관리 대상인 인프라에 직접 접근해 제어하지는 못했습니다. Elastic의 엔지니어링 팀은 해커톤의 일환으로 <a href="https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure">자동화된 인프라 관리</a>를 위한 에이전트를 구축했습니다. 이 에이전트는 애플리케이션 인프라 문제를 적극적으로 조사하고 자동화된 조치를 취합니다. 인프라 로그를 지능적으로 이해한 결과를 바탕으로 워크플로우를 사용하여 설정을 최적화하고, 문제에 대응하며, 리소스를 확장합니다.</p></li><li><p><strong>보안 위협 분석: </strong>Elastic Agent Builder, MCP, Elasticsearch를 사용하여 보안 취약점 에이전트가 개발되었습니다. 내부 보안 데이터와 외부 위협 인텔리전스를 연관시켜 위협 분석을 자동화합니다. 이 에이전트는 과거 인시던트 및 설정 정보를 대상으로 시맨틱 검색을 수행하고, 실시간 인터넷 데이터로 결과를 강화한 뒤 LLM 추론을 적용해 환경적 관련성을 평가하고 위험 우선순위를 정하며 실행 가능한 대응 방안을 도출합니다. <a href="https://www.elastic.co/search-labs/blog/agent-builder-mcp-reference-architecture-elasticsearch">참조 아키텍처</a><strong>를 확인하세요</strong>.</p></li><li><p><strong>기술 고객 지원: </strong>에이전트는 사례 요약부터 문제 중복 정리와 신규 문제 생성, 고급 기술 분석까지 여러 지원 작업을 처리할 수 있습니다. Agent Builder는 다단계 하이브리드 검색을 통해 가장 관련성 높은 문제와 해결책, 절차만을 찾아내고, 근본 원인 가설과 조치 계획을 수립할 수 있도록 지원합니다. Agent Builder는 복잡한 <a href="https://www.elastic.co/blog/generative-ai-customer-support-elastic-support-assistant">지원 시스템</a>의 아키텍처를 단순화하고 제공까지 걸리는 시간을 단축할 수 있습니다.</p></li><li><p><strong>제품 및 콘텐츠 탐색:</strong> Agent Builder는 <a href="https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder">대화형 환경에서 복잡한 제품 카탈로그를 공개하는</a> 과정을 간소화하는 동시에, 조직이 자체 비즈니스 로직과 요구 사항을 유연하게 반영할 수 있도록 합니다.</p></li><li><p><strong>직접 구축:</strong> 2026년 1월 22일부터 2월 27일까지 진행되는 <a href="https://elasticsearch.devpost.com/">Agent Builder 해커톤</a>에 참여하세요. 커뮤니티와 협력하여 검색, 워크플로우, 도구, 추론을 통합한 컨텍스트 중심의 다단계 AI 에이전트를 만들고, 현실 세계의 작업을 자동화할 수 있습니다*</p></li></ul><h2>지금 바로 맞춤형 에이전트를 구축해 보세요</h2><p><a href="https://cloud.elastic.co/registration?onboarding_token=search&amp;pg=en-enterprise-search-page">Elastic Cloud 체험판</a>으로 시작하고, 설명서를 <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">여기</a>에서 확인해 보세요. 기존 고객의 경우, Agent Builder는 Cloud Serverless와 Elastic Cloud Hosted, 자체 관리형의 Enterprise 티어에서 사용할 수 있습니다.</p><p>* 해커톤의 전체 약관, 조건, 참가 자격 요건을 확인하려면 <a href="https://elasticsearch.devpost.com/rules">여기를 클릭하세요</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <dc:creator><![CDATA[Anish Mathur,Evan Castle]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5ffa581514d8b8c/6a17e092dbb4fff61afb55e2/6840eb7dbb884055ab0e965dcfd614fec54936af-2210x1440.png" length="0" type="image/png"/>
    <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Agent Builder를 사용해 음성 에이전트 구축]]></title>
    <description><![CDATA[음성 에이전트의 작동 방식과 Elastic Agent Builder 및 LiveKit을 사용하여 음성 에이전트를 구축하는 방법을 살펴보세요.]]></description>
    <content:encoded><![CDATA[<p>AI는 유리 상자 안에 갇혀 있습니다. 사용자가 명령어를 입력하면 텍스트로 답할 뿐이었습니다. 유용하긴 했지만, 화면 너머 움직이는 누군가를 지켜보는 것처럼 거리감이 느껴졌습니다. 하지만 2026년인 올해, 비즈니스는 이 유리창을 깨고 AI 에이전트를 제품 속으로 직접 끌어들여 진정한 가치를 창출하는 해가 될 것입니다.</p><p>그 유리 상자를 유리 깨뜨리는 방법 중 하나는 <em>음성 에이전트</em>의 도입입니다. 음성 에이전트는 사람의 음성을 인식하고 컴퓨터로 생성된 오디오를 합성하는 AI 에이전트를 말합니다. 저지연 전사, 빠른 대규모 언어 모델(LLM), 그리고 사람이 말하는 것처럼 들리는 텍스트-음성 변환 모델의 등장으로 이러한 것이 가능해졌습니다.</p><p>음성 에이전트가 진정으로 가치 있는 존재가 되려면 비즈니스 데이터에 접근할 수 있어야 합니다. 이 블로그에서는 음성 에이전트가 어떻게 작동하는지 알아보고 <a href="https://livekit.io/">LiveKit</a>과 <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>를 사용하여 가상의 야외 스포츠 장비 상점인 ElasticSport에 음성 에이전트를 구축해 보겠습니다. 음성 에이전트는 문맥을 인식하고 데이터와 함께 작동합니다.</p><h2>참여 방법</h2><p>음성 에이전트 분야에는 크게 두 가지 패러다임이 있습니다. 첫 번째는 음성 간 변환(speech-to-speech) 모델을 사용하는 방식이고, 두 번째는 음성-텍스트(speech-to-text) 변환, LLM, 텍스트-음성(text-to-speech) 변환으로 구성된 음성 파이프라인을 사용하는 방식입니다. 음성 간 변환 모델도 나름의 장점이 있지만, 음성 파이프라인은 사용되는 기술과 문맥 관리 방식을 훨씬 더 세밀하게 사용자 맞춤화할 수 있으며, 에이전트의 행동을 제어할 수 있다는 이점이 있습니다. 본문에서는 음성 파이프라인 모델을 중점적으로 다루겠습니다.</p><h3>주요 구성 요소</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbeb09a3743f38d/6a17de9caf47b67330cdde7d/b237501903f9c3a71fe1b7755c3990e40c5495c8-1600x653.png" alt="Elastic Agent Builder를 사용하여​ AI 음성 에이전트를 구축하기 위한 아키텍처" /><h4>전사(음성-텍스트 변환)</h4><p>전사는 음성 파이프라인의 진입점입니다. 전사 구성 요소는 원시 오디오 프레임을 입력으로 받아 음성을 텍스트로 전사한 후 그 텍스트를 출력합니다. 전사된 텍스트는 사용자의 발화가 끝났음을 감지할 때까지 버퍼에 저장되며, 발화가 종료되는 시점에 LLM 생성이 시작됩니다. 다양한 제3자 제공 업체에서 저지연 전사를 제공합니다. 선택 시 지연 시간과 전사 정확도를 고려하고, 스트리밍 전사를 지원하는지 확인하세요.</p><p></p><p>제3자 API 예시: <a href="https://www.assemblyai.com/">AssemblyAI</a>, <a href="https://deepgram.com/product/speech-to-text">Deepgram</a>, <a href="https://platform.openai.com/docs/guides/realtime-transcription">OpenAI</a>, <a href="https://elevenlabs.io/speech-to-text">ElevenLabs</a></p><h4>대화 차례 감지</h4><p>대화 차례 감지는 화자가 말을 마치고 응답 생성을 시작해야 할 시점을 감지하는 파이프라인 구성 요소입니다. 이를 수행하는 일반적인 방법 중 하나는 <a href="https://github.com/snakers4/silero-vad">Silero VAD</a>와 같은 음성 활동 탐지(VAD) 모델을 사용하는 것입니다. VAD는 오디오 에너지 수치를 사용하여 오디오에 음성이 포함되어 있는지와 음성이 종료된 시점을 감지합니다. 그러나 VAD만으로는 일시 중지와 발화 종료의 차이를 식별할 수 없습니다. 이 때문에 중간 잠정 전사(interim transcript) 또는 원시 오디오를 기반으로 화자가 말을 마쳤는지 예측하는 발화 종료 모델과 결합하는 경우가 많습니다.</p><p>예시(Hugging Face): <a href="https://huggingface.co/livekit/turn-detector">livekit/turn-detector</a>, <a href="https://huggingface.co/pipecat-ai/smart-turn-v3">pipecat-ai/smart-turn-v3</a></p><h4>에이전트</h4><p>에이전트는 음성 파이프라인의 핵심입니다. 의도를 파악하고, 적절한 문맥을 수집하고, 텍스트 형식으로 답변을 구성하는 역할을 담당합니다. <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>는 기본 제공 추론 기능, 도구 라이브러리, 워크플로우 통합을 사용해 기업 내부 데이터를 활용하면서 외부 서비스와 상호 작용할 수 있는 에이전트를 만들 수 있습니다.</p><h4>LLM(텍스트 간 변환)</h4><p>Elastic Agent Builder용 LLM을 선택할 때 고려해야 할 주요 특징은 LLM 추론 벤치마크와 첫 번째 토큰 생성 시간(TTFT) 두 가지입니다.</p><p>추론 벤치마크는 LLM이 얼마나 정확한 응답을 생성할 수 있는지를 나타냅니다. 고려해야 할 벤치마크는 연속 대화(multiturn) 대화 준수도와 지능 벤치마크를 평가하는 것으로, 각각 MT-Bench와 Humanity's Last Exam 데이터 세트가 있습니다.</p><p>TTFT 벤치마크는 모델이 첫 번째 출력 토큰을 얼마나 빠르게 생성하는지 평가합니다. 다른 유형의 지연 벤치마크도 있지만, TTFT는 음성 에이전트에게 특히 중요합니다. 첫 번째 토큰이 수신되자마자 음성 합성을 시작할 수 있기 때문인데, 이는 대화 차례 사이의 지연 시간을 줄여 주어 훨씬 자연스러운 대화 경험을 만들어 줍니다</p><p>보통 이 두 가지 요소 중 하나를 선택하면 다른 하나는 어느 정도 포기해야 합니다. 속도가 빠른 모델은 대개 추론 능력이 떨어지는 경향이 있기 때문입니다.</p><p>예시(Hugging Face): <a href="https://huggingface.co/openai/gpt-oss-20b">openai/gpt-oss-20b</a>, <a href="https://huggingface.co/openai/gpt-oss-120b">openai/gpt-oss-120b</a></p><h4>합성(텍스트-음성 변환)</h4><p>파이프라인의 마지막 부분은 텍스트-음성 변환 모델입니다. 이 구성 요소는 LLM에서 출력된 텍스트를 들을 수 있는 음성으로 변환하는 역할을 합니다. LLM과 마찬가지로 지연 시간은 텍스트-음성 변환 제공 업체를 선택할 때 주의해야 할 특성입니다. 텍스트 음성 변환 지연 시간은 첫 바이트까지의 시간(TTFB)으로 측정됩니다. 이는 첫 번째 오디오 바이트가 수신될 때까지 걸리는 시간을 의미합니다. TTFB가 낮으면 대화 차례 사이의 지연 시간도 줄어듭니다.</p><p>예시: <a href="https://elevenlabs.io/text-to-speech-api">ElevenLabs</a>, <a href="https://cartesia.ai/sonic">Cartesia</a>, <a href="https://www.rime.ai/">Rime</a></p><h4>음성 파이프라인 구축</h4><p>Elastic Agent Builder는 여러 가지 수준에서 음성 파이프라인에 통합될 수 있습니다.</p><ol><li><p>Agent Builder 도구만 해당: 음성-텍스트 변환 → LLM(Agent Builder 도구 사용) → 텍스트-음성 변환</p></li><li><p>MCP 방식의 Agent Builder: 음성-텍스트 변환 → LLM(MCP를 통해 Agent Builder에 접근) → 텍스트-음성 변환</p></li><li><p>Agent Builder를 핵심으로 사용: 음성-텍스트 변환 → Agent Builder → 텍스트-음성 변환</p></li></ol><p>이 프로젝트에서는 Agent Builder를 핵심 접근 방식으로 선택했습니다. 이러한 접근 방식을 통해 Agent Builder와 워크플로우의 전체 기능을 사용할 수 있습니다. 이 프로젝트는 LiveKit을 사용하여 음성-텍스트 변환, 발화 차례 감지 및 텍스트-음성 변환을 오케스트레이션하고, Agent Builder와 직접 통합되는 맞춤형 LLM 노드를 구현합니다.</p><h2>Elastic 지원 음성 에이전트</h2><p>ElasticSport라는 가상의 스포츠 매장에 맞춤형 지원 음성 에이전트를 구축 해 보겠습니다. 고객은 헬프라인에 전화해 제품 추천을 요청하고, 제품 세부 정보를 찾고, 주문 상태를 확인하고, 주문 정보를 문자 메시지로 받을 수 있습니다. 이렇게 하려면 먼저 맞춤형 에이전트를 구성하고 Elasticsearch 쿼리 언어(ES|QL) 쿼리 및 워크플로우를 실행하기 위한 도구를 생성해야 합니다.</p><h3>에이전트 구성</h3><h4>프롬프트</h4><p>프롬프트는 에이전트가 어떤 성격을 가져야 하고 어떻게 응답해야 하는지 지시합니다. 중요한 것은, 응답이 오디오로 제대로 합성되고 오해가 발생하더라도 유연하게 해결될 수 있도록 하는 몇 가지 음성별 프롬프트가 있다는 점입니다.</p>You are a Sales Assistant at ElasticSport, an outdoor sport shop specialized in hiking and winter equipment. 

[Profile]
- name: Iva
- company: ElasticSport
- role: Sales Assistant
- language: en-GB
- description: ElasticSport virtual sales assistant

[Context]
- Ask clarifying questions to understand the context.
- Use available tools to answer the user's question.
- Use the knowledge base to retrieve general information

[Style]
- Be informative and comprehensive.
- Maintain a professional, friendly and polite tone.
- Mimic human behavior and speech patterns.
- Be concise. Do not over explain initially

[Response Guideline]
- Present dates in spelled-out month date format (e.g., January fifteenth, two thousand and twenty-four).
- Avoid the use of unpronounceable punctuation such as bullet points, tables, emojis.
- Respond in plain text, avoid any formatting.
- Spell out numbers as words for more natural-sounding speech.
- Respond in short and concise sentences. Responses should be 1 or 2 sentences long.

[ERROR RECOVERY]
### Misunderstanding Protocol
1. Acknowledge potential misunderstanding
2. Request specific clarification<h4>워크플로우</h4><p>Twilio의 메시징 API를 통해 SMS를 보내기 위한 작은 워크플로우를 추가해 보겠습니다. 해당 워크플로우는 맞춤형 에이전트에게 하나의 '도구' 형태로 제공되며, 이를 통해 에이전트가 통화 중에 발신자에게 SMS 문자를 보낼 수 있는 사용자 경험을 구현할 수 있습니다. 예를 들어 발신자는 "<em>X</em> 에 대한 자세한 내용을 문자로 보내 줄 수 있나요?"라고 물어볼 수 있습니다.</p>name: send sms
enabled: true
triggers:
  - type: manual
inputs:
  - name: message
    type: string
    description: The message to send to the phone number.

  - name: phone_number
    type: string
    description: The phone number to send the message to.

consts:
  TWILIO_ACCOUNT: "****"
  BASIC_AUTH: "****"
  FROM_PHONE_NNUMBER: "****"
steps:
  - name: http_step
    type: http
    with:
      url: https://api.twilio.com/2010-04-01/Accounts/{{consts.TWILIO_ACCOUNT}}/Messages.json
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
        Authorization: Basic {{consts.BASIC_AUTH | base64_encode}}
      body: From={{consts.FROM_PHONE_NNUMBER}}&amp;To={{inputs.phone_number}}&amp;Body={{inputs.message}}
      timeout: 30s<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt960a9395fb0985bf/6a17de9e4b055d1dff4320f0/b057e71b0a7c50eb3da47cd4f95e77ec7b4c6126-1600x1245.png" alt="Elastic Agent 빌더를 사용하여 AI 음성 에이전트용 새 도구를 만드세요." /><h4>ES|QL 도구</h4><p>다음 도구를 사용하여 에이전트가 실제 데이터를 기반으로 관련 응답을 제공할 수 있습니다. 예시 리포지토리에는 제품, 주문, 지식 기반 데이터 세트로 Kibana를 초기화하는 설정 스크립트가 포함되어 있습니다.</p><ul><li><p><strong>Product.search</strong></p></li></ul><p>제품 데이터 세트에는 65개의 가상 제품이 포함되어 있습니다. 다음은 예시 문서입니다.</p>{
      "sku": "ort3M7k",
      "name": "Ortovox Free Rider 26 Backpack",
      "price": 189,
      "currency": "USD",
      "image": "https://via.placeholder.com/150",
      "description": "The Ortovox Free Rider 26 is a technical freeride backpack with a dedicated safety compartment and diagonal ski carry system. Perfect for backcountry missions.\n\nKey Features:\n- 26L capacity\n- Diagonal ski carry system\n- Safety equipment compartment\n- Helmet holder\n- Hydration system compatible",
      "category": "Accessories",
      "subCategory": "Backpacks",
      "brand": "Ortovox",
      "sizes": ["One Size"],
      "colors": ["Black", "Blue", "Orange"],
      "materials": ["Nylon", "Polyester"]
    }<p>이름과 설명 필드는 <code>semantic_text</code>(으)로 맵핑되어 있어 LLM이 ES|QL을 통해 의미를 검색하여 관련 제품을 검색할 수 있습니다. 하이브리드 검색 쿼리는 두 필드 모두에 걸쳐 의미 일치 작업을 수행하며, 이름 필드 일치에는 부스트를 적용하여 약간 더 높은 가중치를 부여합니다.</p><p>쿼리는 먼저 초기 관련성 점수에 따라 순위가 매겨진 상위 20개의 결과를 검색합니다. 이러한 결과는 <code>.rerank-v1-elasticsearch</code> 추론 모델을 사용해 설명 필드를 기반으로 다시 순위가 매겨지고, 최종적으로 가장 관련성 높은 다섯 가지 제품을 선별합니다.</p>type: ES|QL
toolId: products.search
description: Use this tool to search through the product catalogue by keywords.
query: |
    FROM products
        METADATA _score
      | WHERE
          MATCH(name, ?query, {"boost": 0.6}) OR
            MATCH(description, ?query, {"boost": 0.4})
      | SORT _score DESC
      | LIMIT 20
      | RERANK ?query
            ON description
            WITH {"inference_id": ".rerank-v1-elasticsearch"}
      | LIMIT 5

parameters:
    query: space separated keywords to search for in catalogue<ul><li><p><strong>Knowledgebase.search</strong></p></li></ul><p>지식 기반 데이터 세트에는 다음과 같은 형태의 문서가 포함되어 있으며, 여기서 제목과 내용 필드는 의미 텍스트로 저장됩니다.</p>{
        id: "8273645",
        createdAt: "2025-11-14",
        title: "International Orders",
        content: `International orders are processed through our international shipping partner. Below are the countries we ship to and average delivery times.
        Germany: 3-5 working days
        France: 3-5 working days
        Italy: 3-5 working days
        Spain: 3-5 working days
        United Kingdom: 3-5 working days
        United States: 3-5 working days
        Canada: 3-5 working days
        Australia: 3-5 working days
        New Zealand: 3-5 working days
        `
}<p>이 도구는 <code>product.search</code> 도구와 유사한 쿼리를 사용합니다.</p>type: "ES|QL"
toolId: knowledgebase.search
description: Use this tool to search the knowledgebase.
query: |
  FROM knowledge_base
    METADATA _score
  | WHERE
      MATCH(title, ?query, {"boost": 0.6}) OR
      MATCH(content, ?query, {"boost": 0.4})
  | SORT _score DESC
  | LIMIT 20
  | RERANK ?query
      ON content
      WITH {"inference_id": ".rerank-v1-elasticsearch"}
  | LIMIT 5

parameters:
  query: space separated keywords or natural language phrase to semantically search for in the knowledge base<ul><li><p><strong>Orders.search</strong></p></li></ul><p>마지막으로 추가할 도구는 <code>order_id</code>(으)로 주문을 검색하는 데 사용되는 도구입니다.</p>type: "ES|QL"
toolId: order.search
description: Use this tool to retrieve an order by its ID.
query: |
  FROM orders
    METADATA _score
  | WHERE order_id == ?order_id
  | SORT _score DESC
  | LIMIT 1

parameters:
  order_id: "the ID of the order"<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfaaf9634f27f70d7/6a17dea07f6f15b8d2c09a3d/d22bdd540a95b5a9c2bd5f308620835e8e6f7ecb-1600x1361.png" alt="음성 에이전트 구성" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8f4c704ab96c294a/6a17dea23e03d74af14f2b7e/d91709a50fb5391876b714885242d998b2b21027-1600x1443.png" alt="음성 에이전트 도구" /><p>에이전트를 구성하고 이러한 워크플로우 및 ES|QL 도구를 에이전트에 연결한 후, Kibana 내에서 에이전트를 테스트할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfd2934a9582c04/6a17dea463baff1532741b5e/8691f41624247a6b1352d158c970031e1426ce5e-1600x1056.png" alt="음성 에이전트 테스트" /><p>ElasticSport 지원 에이전트 구축 외에도, 에이전트와 워크플로우 및 도구를 잠재 고객을 발굴하는 영업 에이전트, 주택 수리 서비스 에이전트, 식당 예약 또는 일정 예약 에이전트 등 다른 사용 사례에 맞춰 설정할 수 있습니다.</p><p></p><p>마지막으로, 방금 만든 에이전트를 LiveKit, 텍스트-음성 및 음성-텍스트 변환 모델과 연결합니다. 이 블로그 끝에 링크된 리포지토리에는 LiveKit과 함께 사용할 수 있는 맞춤형 Elastic Agent Builder LLM 노드가 포함되어 있습니다. <code>AGENT_ID</code>을(를) 자신의 것으로 바꾸고 Kibana 인스턴스와 연결하기만 하면 됩니다.</p><h2>시작하기</h2><p><a href="https://github.com/KDKHD/elastic_agent_builder_livekit">여기</a>에서 코드를 확인하고 직접 사용해 보세요. </p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Kenneth Kreindler]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2732d87a324baa78/6a17dea6e9ea873632a9c4cc/43ceabb9e2c0966261c188bd40e03178d5a91e5c-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch로 에이전틱 메모리 관리하기]]></title>
    <description><![CDATA[Elasticsearch를 사용하여 메모리를 관리함으로써 컨텍스트 인식 능력이 뛰어나고 효율적인 에이전트를 생성합니다.]]></description>
    <content:encoded><![CDATA[<p>새롭게 부상하는 <strong>컨텍스트 엔지니어링</strong> 분야에서는 AI 에이전트에 적절한 정보를 적시에 제공하는 것이 매우 중요합니다. 컨텍스트 엔지니어링의 가장 중요한 측면 중 하나는 AI의 <strong>메모리</strong> 관리입니다. 인간과 마찬가지로 AI 시스템도 정보를 기억하기 위해 단기 메모리와 장기 메모리에 모두 의존합니다. 대규모 언어 모델(LLM) 에이전트가 논리적인 대화를 이어가고 사용자 선호도를 기억하며 이전 결과나 응답을 기반으로 구축하려면 효과적인 메모리 메커니즘을 갖춰야 합니다.</p><p>결국 컨텍스트의 모든 요소가 AI의 응답에 영향을 미칩니다. <em>쓰레기를 넣으면 쓰레기가 나온다(Garbage in, garbage out)</em>는 말이 딱 들어맞습니다.</p><p>이 글에서는 AI 에이전트에 단기 메모리와 장기 메모리가 어떤 의미를 갖는지 설명합니다. 특히 다음과 같은 내용을 다룹니다.</p><ul><li><p>단기 메모리와 장기 메모리의 차이</p></li><li><p>Elasticsearch와 같은 벡터 데이터베이스를 사용하는 Retrieval-Augmented Generation(RAG) 기술과의 관계 및 신중한 메모리 관리가 필요한 이유</p></li><li><p>컨텍스트 오버플로 및 컨텍스트 오염 등 메모리 관리 소홀의 위험성</p></li><li><p>컨텍스트 가지치기, 요약, 관련 정보만 검색 등 에이전트의 메모리를 유용하면서도 안전하게 유지하는 모범 사례</p></li><li><p>Elasticsearch를 사용하여 에이전트가 혼동 없이 협업할 수 있도록 멀티 에이전트 시스템에서 메모리를 공유 및 전파하는 방법</p></li></ul><h2>AI 에이전트의 단기 메모리와 장기 메모리 비교</h2><p>AI 에이전트의 <em><strong>단기 메모리</strong></em>는 일반적으로 즉각적인 대화 컨텍스트 또는 상태, 즉 현재 채팅 기록이나 활성 세션의 최근 메시지를 의미합니다. 여기에는 사용자의 최근 쿼리와 최근 주고받은 대화가 포함됩니다. 이는 사람이 대화 중에 기억하는 정보와 매우 유사합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb714ce810d1c472/6a170f321949f782cbe7aaf6/4fbcc6f68055b2bccefc4176297a4ca50056dc0d-764x498.png" alt="단기 및 장기 에이전틱 메모리" /><p>AI 프레임워크는 종종 이러한 임시 메모리를 에이전트 상태의 일부로 유지합니다(예: <a href="https://docs.langchain.com/oss/python/langgraph/persistence#checkpoints">LangGraph의 이 예시</a>처럼 체크포인터를 사용하여 대화 상태를 저장하는 방식). 단기 메모리는 <em><strong>세션 범위로 제한</strong></em>됩니다. 즉, 단일 대화 또는 작업 내에 존재하며 해당 세션이 종료되면 명시적으로 다른 곳에 저장하지 않는 한 재설정되거나 지워집니다. 세션에 종속된 단기 메모리의 예로는 ChatGPT에서 제공하는 <a href="https://help.openai.com/en/articles/8914046-temporary-chat-faq"><strong>임시 채팅</strong></a>기능을 들 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b8680e22d4e1185/6a170f341949f78bbae7aafa/150bdf209cda5ed20b59cddf34e624ad1a8016aa-1100x577.png" alt="AI 프레임워크 메모리" /><p>반면 <em><strong>장기 메모리</strong></em>는 <strong>여러 대화나 세션에 걸쳐</strong> 지속되는 정보를 말합니다. 이는 에이전트가 시간이 지남에 따라 보유하게 되는 지식, 이전에 학습한 사실, 사용자 선호도 또는 영구적으로 기억하도록 지시한 모든 데이터를 말합니다.</p><p>장기 메모리는 보통 즉각적인 컨텍스트 윈도우 밖에 있는 파일이나 벡터 데이터베이스와 같은 외부 소스에서 저장하고 가져오는 방식으로 구현됩니다. 단기 채팅 기록과 달리 장기 메모리는 모든 프롬프트에 자동으로 포함되지 않습니다. 대신 주어진 시나리오에 따라 에이전트가 관련 도구가 호출될 때 이를 <strong>불러오거나</strong> 검색해야 합니다. 실제로 장기 메모리에는 사용자의 프로필 정보, 에이전트가 작성한 이전 답변이나 분석, 에이전트가 쿼리할 수 있는 지식 기반 등이 포함될 수 있습니다.</p><p>예를 들어 여행 플래너 에이전트가 있다면 <em>단기 메모리</em>는 현재 여행 문의(날짜, 목적지, 예산) 및 해당 채팅에서의 후속 질문의 세부 사항을 포함할 것입니다. 반면에 <em>장기 메모리</em>는 사용자의 일반적인 여행 선호도, 과거 여정 및 이전 세션에서 공유된 기타 사실을 저장할 수 있습니다. 사용자가 나중에 다시 방문했을 때, 에이전트는 이러한 장기 데이터 저장소(예: 사용자는 해변과 산을 좋아하고, 평균 예산은 10만 루피이며, 방문하고 싶은 버킷리스트가 있고, 어린이 친화적인 명소보다는 역사와 문화 체험을 선호함)를 활용하여 매번 사용자에게 다시 묻지 않고 맞춤형 서비스를 제공할 수 있습니다.</p><p>단기 메모리(채팅 기록)는 즉각적인 컨텍스트와 연속성을 제공하는 반면, 장기 메모리는 에이전트가 필요할 때 활용할 수 있는 더 넓은 컨텍스트를 제공합니다. 대부분의 고급 AI 에이전트 프레임워크는 이 두 가지를 모두 지원합니다. 최근 대화를 추적하여 컨텍스트를 유지관리합니다. <em>그리고</em> 장기 저장소에서 정보를 조회하거나 해당 저장소에 저장하는 메커니즘을 제공합니다. 단기 메모리를 관리하여 컨텍스트 윈도우 내에서 유지되도록 하고, 장기 메모리를 관리하여 에이전트가 이전 상호작용과 페르소나를 기반으로 답변을 확립할 수 있도록 합니다.</p><h2>컨텍스트 엔지니어링에서 메모리와 RAG</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt98c1514741bea460/6a170f36509168083ce1bbae/46635aa11ceff89b8d6a26ac3e22da52407d82f3-1600x900.png" alt="컨텍스트 엔지니어링에서 메모리와 RAG" /><p><em><strong>실제로 AI 에이전트에 유용한 장기 메모리를 부여하는 방법은 무엇일까요?</strong></em></p><p>장기 메모리를 위한 대표적인 접근 방식 중 하나는 <em><strong>시맨틱 메모리</strong></em>로, <strong>retrieval-augmented generation(RAG)</strong>을 통해 구현되는 경우가 많습니다. 이는 LLM을 Elasticsearch와 같은 외부 지식 저장소 또는 벡터 지원 데이터 저장소와 결합하는 것을 의미합니다. LLM은 프롬프트나 기본 제공 학습에 포함된 정보 이상의 정보가 필요할 때 Elasticsearch를 대상으로 시맨틱 검색을 수행하고 가장 관련성이 높은 결과를 컨텍스트로 프롬프트에 삽입합니다. 이렇게 하면 모델의 유효 컨텍스트에 최근 대화(단기 메모리)뿐만 아니라 즉석에서 가져온 관련 장기 사실도 포함됩니다. 그런 다음 LLM은 자체 추론과 검색된 정보를 기반으로 답변을 구성하여 단기 메모리와 장기 메모리를 효과적으로 결합해 보다 정확하고 컨텍스트에 맞는 응답을 생성합니다.</p><p><strong>Elasticsearch</strong>를 사용하여 AI 에이전트의 장기 메모리를 구현할 수 있습니다. 다음은 Elasticsearch에서 컨텍스트 정보를 검색하여 장기 메모리에 저장하는 방법에 대한 개략적인 예시입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt44f5a6887b0bca32/6a170f37a6c2b9c735e797be/41ccbc7b5171e8170ac300139a963c0708816ba6-1600x900.png" alt="RAG 활용 사례" /><p>이렇게 하면 에이전트가 제한된 프롬프트에 모든 것을 저장하는 대신 관련 데이터를 검색하여 '기억'하기 때문에 <strong>다른 위험을 초래</strong>할 수 있습니다.</p><p><strong>Elasticsearch 또는 벡터 저장소와 함께 RAG를 사용하면 다음과 같이 여러 가지 이점이 있습니다.</strong></p><p>첫째, 학습 컷오프를 넘어 모델에 대한 <strong>지식을 확장합니다</strong>. 에이전트는 LLM이 알지 못할 수도 있는 최신 정보 또는 도메인별 데이터를 검색할 수 있습니다. 최근 이벤트나 전문 주제에 대한 질문이 있을 때 유용합니다.</p><p>둘째, 요청 시 컨텍스트를 검색하면 환각을 줄이는 데 도움이 됩니다. 특히 LLM은 귀하의 특정 사용 사례에 상대적인 독점적 또는 고도로 전문화된 데이터에 대해 훈련되지 않았기 때문에 환각에 노출될 가능성이 매우 높습니다. 최근 OpenAI 논문(<a href="https://arxiv.org/pdf/2509.04664">왜 언어 모델이 환각을 일으키는 이유(Why Language Models Hallucinate)</a>에서 강조된 것처럼 LLM이 평가를 통해 추측하거나 새로운 정보를 창조하는 대신, 이 모델은 Elasticsearch의 사실 참조를 통해 근거를 마련할 수 있습니다. 당연히 LLM은 벡터 저장소 내 데이터의 신뢰성에 의존하여 잘못된 정보를 진정으로 방지하고, 관련 데이터는 핵심 관련성 측정에 따라 검색됩니다.</p><p>셋째, RAG를 사용하면 에이전트가 프롬프트에 담을 수 있는 것보다 훨씬 더 큰 지식 기반을 활용할 수 있습니다. 긴 연구 논문이나 정책 문서와 같은 전체 문서를 컨텍스트 윈도우에 입력하여 과부하를 초래하거나 관련 없는 정보로 인해 모델의 추론이 <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-poisoning">컨텍스트 오염</a>될 위험을 감수하는 대신, RAG는 <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">청킹</a> 방식을 사용합니다. 대용량 문서는 의미론적으로 유의미한 더 작은 조각으로 분할되며, 시스템은 쿼리와 가장 관련성이 높은 몇 개의 조각만 검색합니다. 이렇게 하면 모델은 지식이 풍부해 보이기 위한 수백만 개의 토큰으로 이루어진 컨텍스트가 필요하지 않고, 훨씬 더 큰 코퍼스에서 적절한 청크에만 액세스하기만 하면 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c90f81a56db0a33/6a170f3960084be7ba3c462e/e6897356c9f0940e35a63d005e9cd20bc33e5dd7-1600x931.png" alt="LLM 컨텍스트 엔지니어링의 진화" /><p>LLM 컨텍스트 윈도우가 커짐에 따라(<a href="https://www.anthropic.com/news/1m-context">일부 모델은 이제 수십만, 심지어 수백만 개의 토큰을 지원함</a><em>)</em> RAG가 '사라졌는지'에 대한 논쟁이 제기되었다는 점에 주목할 필요가 있습니다. 모든 데이터를 프롬프트에 넣으면 안 되는 이유가 무엇일까요? 만약 여러분도 같은 생각을 하고 있다면, 제 동료인 제프리 렌기포(Jeffrey Rengifo)와 에두아르트 마틴(Eduard Martin)이 쓴 <a href="https://www.elastic.co/search-labs/blog/rag-vs-long-context-model-llm">더 긴 컨텍스트 ≠ 더 나은 것: RAG가 여전히 중요한 이유(Longer context ≠ better: Why RAG still matters)</a>라는 훌륭한 글을 참고해 보세요. 이렇게 하면 '쓰레기를 넣으면 쓰레기가 나온다'는 문제를 피할 수 있습니다. LLM은 노이즈를 처리하는 대신 중요한 몇 개의 청크에 집중할 수 있습니다.</p><p>즉, Elasticsearch 또는 벡터 저장소를 AI 에이전트 아키텍처에 통합하면 <strong>장기적인 메모리</strong>를 제공합니다. 에이전트는 지식을 외부 저장소에 저장하고 필요할 때 메모리 컨텍스트로 불러옵니다. 이는 각 사용자 쿼리 후에 에이전트가 Elasticsearch에서 관련 정보를 검색한 다음 LLM을 호출하기 전에 프롬프트에 상위 결과를 추가하는 <em>아키텍처</em>로 구현할 수 있습니다. 응답에 유용한 새로운 정보가 포함되어 있으면 장기 저장소에 다시 저장할 수도 있습니다(학습의 피드백 루프 생성). 이러한 검색 기반 메모리를 사용함으로써 에이전트는 모든 프롬프트에 알고 있는 모든 것을 집어넣을 필요 없이 정보를 유지하고 최신 상태로 유지합니다. 컨텍스트 윈도우가 <em>100만 토큰</em>을 지원한다고 하더라도 마찬가지입니다. 이 기술은 정보 검색과 생성형 AI의 강점을 결합한 컨텍스트 엔지니어링의 초석입니다. </p><p>다음은 세션 중 단기 메모리에 대한 LangGraph의 체크포인트 시스템을 사용하여 관리되는 인메모리 대화 상태의 예시입니다. (<a href="https://github.com/someshwaranM/elastic-context-engineering-short-term-long-term-memory">지원되는 컨텍스트 엔지니어링 앱</a>을 참조하세요.)</p># Initialize chat memory (Note: This is in-memory only, not persistent)
memory = MemorySaver()

# Create a LangGraph agent
langgraph_agent = create_react_agent(model=llm, tools=tools, checkpointer=memory)

...
...
# Only process and display checkpoints if verbose mode is enabled
if args.verbose:
    # List all checkpoints that match a given configuration
    checkpoints = memory.list({"configurable": {"thread_id": "1"}})
    # Process the checkpoints
    process_checkpoints(checkpoints)<p><strong>체크포인트</strong>를 저장하는 방법은 다음과 같습니다.</p>Checkpoint:
Timestamp: 2025-12-30T09:19:41.691087+00:00
Checkpoint ID: 1f0e560a-c2fa-69ec-8001-14ee5373f9cf
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)
Agent:  (Message ID: lc_run--019b6e8e-553f-7b52-8796-a8b1fbb206a4-0)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.350507+00:00
Checkpoint ID: 1f0e560a-b631-6a08-8000-7796d108109a
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.349027+00:00
Checkpoint ID: 1f0e560a-b62e-6010-bfff-cbebe1d865f6<p>장기 메모리의 경우, Elasticsearch에서 시맨틱 검색을 수행하여 체크포인트를 요약하고 색인한 후 벡터 임베딩을 사용하여 관련 이전 대화를 검색하는 방법은 다음과 같습니다.</p>Functions: 
retrieve_from_elasticsearch() 

# Enhanced Elasticsearch retrieval with rank_window and verbose display
def retrieve_from_elasticsearch(query: str, k: int = 5, rank_window: int = None) -&gt; tuple[List[Dict[str, Any]], str]:
    """
    Retrieve context from Elasticsearch with score-based ranking
    
    Args:
        query: Search query
        k: Number of results to return
        rank_window: Number of candidates to retrieve before ranking (default: args.rank_window)
        
    Returns:
        Tuple of (retrieved_documents, formatted_context_string)
    """
    if not es_client or not es_index_name:
        return [], "Elasticsearch is not available. Cannot search long-term memory."
    
    if rank_window is None:
        rank_window = args.rank_window
    
    try:
        # Check if index exists and has documents
        if not es_client.indices.exists(index=es_index_name):
            return [], "No previous conversations stored in long-term memory yet."
        
        # Get document count
        try:
            doc_count = es_client.count(index=es_index_name)["count"]
            if doc_count == 0:
                return [], "Long-term memory is empty. No previous conversations to search."
        except Exception as e:
            return [], f"Error checking memory: {str(e)}"
        
        # Generate embedding for the query
        try:
            query_embedding = embeddings.embed_query(query)
        except Exception as e:
            return [], f"Error generating embedding: {str(e)}"
        
        # Perform semantic search using kNN with rank_window
        try:
            search_body = {
                "knn": {
                    "field": "vector",
                    "query_vector": query_embedding,
                    "k": k,
                    "num_candidates": rank_window  # Retrieve more candidates, then rank top k
                },
                "_source": ["text", "content", "message_type", "timestamp", "thread_id"],
                "size": k
            }
            
            response = es_client.search(index=es_index_name, body=search_body)
            
            if not response.get("hits") or len(response["hits"]["hits"]) == 0:
                return [], "No relevant previous conversations found in long-term memory."
            
            # Extract documents with scores
            retrieved_docs = []
            for hit in response["hits"]["hits"]:
                source = hit["_source"]
                score = hit["_score"]
                retrieved_docs.append({
                    "content": source.get("content", source.get("text", "")),
                    "message_type": source.get("message_type", "unknown"),
                    "timestamp": source.get("timestamp", "unknown"),
                    "thread_id": source.get("thread_id", "unknown"),
                    "score": score
                })
            
            # Format context string
            context_parts = []
            for i, doc in enumerate(retrieved_docs, 1):
                context_parts.append(doc["content"])
            
            context_string = "\n\n".join(context_parts)
            
            # Verbose display
            if args.verbose:
                rich.print(f"\n[bold yellow]🔍 RETRIEVAL ANALYSIS[/bold yellow]")
                rich.print("="*80)
                rich.print(f"[blue]Query:[/blue] {query}")
                rich.print(f"[blue]Retrieved:[/blue] {len(retrieved_docs)} documents (from {rank_window} candidates)")
                rich.print(f"[blue]Total context length:[/blue] {len(context_string)} characters\n")
                
                for i, doc in enumerate(retrieved_docs, 1):
                    rich.print(f"[cyan]📄 Document {i} | Score: {doc['score']:.4f} | Type: {doc['message_type']}[/cyan]")
                    rich.print(f"[cyan]   Timestamp: {doc['timestamp']} | Thread: {doc['thread_id']}[/cyan]")
                    content_preview = doc['content'][:200] + "..." if len(doc['content']) &gt; 200 else doc['content']
                    rich.print(f"[cyan]   Content: {content_preview}[/cyan]")
                    rich.print("-" * 80)
            
            return retrieved_docs, context_string
            
        except Exception as e:
            return [], f"Error searching memory: {str(e)}"
            
    except Exception as e:
        return [], f"Error accessing long-term memory: {str(e)}"<p>이제 Elasticsearch에서 LangGraph의 체크포인트를 사용해 단기 메모리와 장기 메모리를 색인하고 가져오는 방법을 알아보았으니, 전체 대화를 색인하고 덤핑하는 것이 왜 위험한지 잠시 시간을 내어 살펴보겠습니다.</p><h2>컨텍스트 메모리를 관리하지 않을 경우의 위험성</h2><p>컨텍스트 엔지니어링과 단기 및 장기 메모리에 대해 많이 이야기하고 있으니, 에이전트의 메모리와 컨텍스트를 제대로 관리하지 않으면 어떤 일이 발생하는지 알아보겠습니다.</p><p>안타깝게도 AI의 컨텍스트가 지나치게 길어지거나 잘못된 정보를 포함할 경우 여러 가지 문제가 발생할 수 있습니다. 컨텍스트 윈도우가 커질수록 다음과 같은 <strong>새로운 오류 유형</strong>이 나타납니다.</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>컨텍스트 누출 및 지식 충돌</strong></p></li><li><p><strong>환각 및 잘못된 정보</strong></p></li></ul><p>컨텍스트 관리가 부실할 때 발생하는 이러한 문제점과 기타 위험 요소를 자세히 살펴보겠습니다.</p><h3>컨텍스트 오염</h3><p><em>컨텍스트 오염</em>이란 부정확하거나 해로운 정보가 컨텍스트에 유입되어 모델의 후속 출력에 악영향을 미치는 현상을 말합니다. 흔한 예로는 모델이 착각한 내용을 사실로 받아들여 대화 기록에 삽입하는 경우가 있습니다. 그러면 모델은 이후 응답에서 해당 오류를 바탕으로 더 나아가 오류를 증폭시킬 수 있습니다. 반복적인 에이전트 루프에서 잘못된 정보가 공유 컨텍스트(예: 에이전트의 작업 노트 요약)에 들어가면 그 정보는 계속해서 강화될 수 있습니다. </p><p><a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">DeepMind 연구진은 Gemini 2.5 보고서</a>(요약본은 <a href="https://www.dbreunig.com/2025/06/17/an-agentic-case-study-playing-pok%C3%A9mon-with-gemini.html">여기</a> 참조)에서 장시간 실행된 <em>포켓몬</em> 게임 에이전트에서 다음과 같은 현상을 관찰했습니다. 에이전트가 잘못된 게임 상태를 환각으로 인식하고, 그 환각이 에이전트의 <em>컨텍스트</em>(목표에 대한 메모리)에 기록되면, 에이전트는 불가능한 목표를 중심으로 <strong>비합리적인 전략</strong>을 세우고 결국 막히게 된다는 것입니다. 다시 말해 오염된 메모리는 에이전트를 잘못된 경로로 무한정 몰아넣을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd56e9e0681f32239/6a170f3b4a531bd79536aa21/3f2facf5aad67613ad557422e09ec23a66adc0ed-1600x1388.png" alt="컨텍스트 오염" /><p>컨텍스트 오염은 의도치 않게(실수로) 발생할 수도 있고, 악의적으로 발생할 수도 있습니다. 예를 들어 프롬프트 주입 공격을 통해 사용자나 제3자가 에이전트가 기억하고 따르는 숨겨진 지침이나 허위 사실을 몰래 삽입하는 경우가 있습니다.</p><p><strong>권장 대응책</strong></p><p><a href="https://www.wiz.io/academy/data-poisoning">Wiz</a>, <a href="https://zerlo.net/en/blog/what-is-llm-data-poisoning">Zerlo</a>, <a href="https://www.anthropic.com/research/small-samples-poison">Anthropic</a>의 사례를 바탕으로 컨텍스트 오염 방지 대책은 LLM의 프롬프트, 컨텍스트 윈도우 또는 검색 파이프라인에 잘못되거나 오해의 소지가 있는 정보가 유입되는 것을 막는 데 중점을 둡니다. 주요 단계는 다음과 같습니다.</p><ul><li><p>컨텍스트를 지속적으로 확인하세요. 시작 프롬프트뿐만 아니라 대화 내용이나 검색된 텍스트에서 의심스럽거나 유해한 내용이 있는지 모니터링하세요.</p></li><li><p>신뢰할 수 있는 출처를 사용하세요. 신뢰도에 따라 문서에 점수를 매기거나 레이블을 지정하여 시스템이 신뢰할 수 있는 정보를 우선시하고 점수가 낮은 데이터는 무시하도록 하세요.</p></li><li><p>비정상적인 데이터를 탐지하세요. 이상하거나 부적절하거나 조작된 콘텐츠를 감지하는 도구를 사용하여 모델이 사용하기 전에 제거하세요.</p></li><li><p>입력 및 출력을 필터링하세요. 유해하거나 오해의 소지가 있는 텍스트가 시스템에 쉽게 유입되거나 모델에 의해 반복되지 않도록 안전장치를 추가하세요.</p></li><li><p>모델을 정제된 데이터로 업데이트하세요. 시스템을 정기적으로 검증된 정보로 갱신하여 눈에 띄지 않게 침투한 불량 데이터를 차단하세요.</p></li><li><p>사람의 개입을 활용하세요(Human-in-the-loop). 중요한 출력물을 사람이 검토하거나 알려진 신뢰할 수 있는 출처와 비교하도록 하세요.</p></li></ul><p>간단한 사용자 습관도 도움이 됩니다. 긴 채팅을 초기화하고, 관련 정보만 공유하고, 복잡한 작업을 작은 단계로 나누고, 모델 외부에서 깔끔한 메모를 유지관리하는 것 등이 그 예입니다.</p><p>이러한 조치를 종합하면 LLM을 컨텍스트 오염으로부터 보호하고 출력의 정확성과 신뢰성을 유지하는 다층적인 방어 체계가 구축됩니다.</p><p>여기서 언급한 대응책이 없다면 에이전트는 공격자가 삽입한 지시(예: 이전 지침또는 사소한 사실 무시)를 기억하여 악의적인 출력을 생성할 수 있습니다.</p><h3>컨텍스트 방해</h3><p><em>컨텍스트 방해</em>는 컨텍스트가 너무 길어져 모델이 학습 중에 습득한 내용을 무시하고 컨텍스트에 과도하게 집중하는 경우를 말합니다. 극단적인 경우, 이는 <a href="https://en.wikipedia.org/wiki/Catastrophic_interference"><em>치명적인 망각과</em></a> 유사합니다. 즉, 모델이 기본 지식을 사실상 '망각'하고 눈앞에 놓인 정보에 지나치게 집착하게 되는 것입니다. 이전 연구에 따르면 LLM은 프롬프트가 극도로 길 때 종종 집중력을 잃는 것으로 나타났습니다.</p><p>예를 들어 Gemini 2.5 에이전트는 100만 토큰 윈도우를 지원했지만, 컨텍스트가 특정 지점(실험에서 10만 토큰 정도)을 넘어서자 새로운 솔루션을 제시하는 대신 <strong>과거의 행동을 반복하는 데 집착하기</strong> 시작했습니다. 말하자면, 에이전트는 방대한 과거 이력에 갇힌 것입니다. 기본 학습 지식을 활용해 새롭고 참신한 전략을 고안하는 대신, 이전 행동의 긴 로그(컨텍스트)를 계속 살펴보고 이를 모방했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91ea0056bbda6e2d/6a170f3d2b835fdd2bf4b2db/e08e5b6d2e8ec7e3511d455985eed3d7fa6241e0-1352x636.png" alt="컨텍스트 방해 " /><p>이는 역효과를 초래합니다. 우리는 모델이 추론을 돕기 위해 관련 컨텍스트를 사용하길 원하지, 모델의 사고 능력을 무시하길 원하지 않습니다. 특히, 방대한 데이터 윈도우를 가진 모델조차도 이러한 <a href="https://research.trychroma.com/context-rot"><em>컨텍스트 왜곡</em></a> 현상을 보입니다. 즉, 토큰이 추가될수록 성능이 불균일하게 저하됩니다. <em>주의력 예산</em>이 있는 것으로 보입니다. 작업 기억이 제한된 인간과 마찬가지로 LLM도 토큰에 집중할 수 있는 용량이 한정되어 있으며, 그 예산이 늘어날수록 정확도와 집중력이 떨어집니다.</p><p>이러한 문제를 완화하기 위해 청킹, 적절한 정보 엔지니어링, 정기적인 컨텍스트 요약, 점수화를 통한 응답의 정확성을 측정하는 평가 및 모니터링 기법을 사용하여 컨텍스트 방해를 방지할 수 있습니다.</p><p>이러한 방법은 모델이 관련 컨텍스트와 기본 학습에 기반을 두도록 하여 주의가 산만해질 위험을 줄이고 전반적인 추론 품질을 향상합니다.</p><h3>컨텍스트 혼동</h3><p><em>컨텍스트 혼동</em>이란 모델이 컨텍스트에 있는 불필요한 콘텐츠를 사용하여 품질이 낮은 응답을 생성하는 것을 말합니다. 대표적인 예로 에이전트에 사용할 수 있는 많은 도구나 API 정의를 제공하는 경우를 들 수 있습니다. 이러한 도구 중 상당수가 현재 작업과 관련이 없더라도, 모델은 컨텍스트에 있다는 이유만으로 부적절하게 사용하려고 시도할 수 있습니다. 실험 결과, 필요하지 않은 도구나 문서를 <em>더 많이</em> 제공하면 오히려 성능이 <em>저하</em>될 수 있다는 사실이 밝혀졌습니다. 에이전트가 잘못된 함수를 호출하거나 관련 없는 텍스트를 참조하는 등의 오류를 범하기 시작하는 것입니다. </p><p>한 사례에서 소형 <strong>Llama 3.1 8B</strong> 모델은 고려해야 할 도구가 46개 주어졌을 때는 작업에 실패했지만, 19개만 주어졌을 때는 성공했습니다. 컨텍스트 길이가 제한 내에 있었음에도 불구하고, 추가 도구는 혼동을 야기했습니다. 근본적인 문제는 프롬프트에 포함된 모든 정보가 모델에 의해 <em>처리</em>된다는 점입니다. 모델이 어떤 정보를 무시해야 하는지 알지 못하면, 그 정보가 모델의 출력에 원치 않는 영향을 미칠 수 있습니다. 관련 없는 정보가 모델의 주의를 분산시켜 잘못된 방향으로 이끌 수 있습니다(예를 들어 관련 없는 문서로 인해 에이전트가 질문과 다른 답변을 할 수 있습니다). 컨텍스트 혼동은 종종 모델이 관련 없는 컨텍스트를 통합하여 품질이 낮은 응답을 생성하는 형태로 나타납니다. 관련 연구 논문 <a href="https://arxiv.org/pdf/2411.15399">적을수록 낫다: 엣지 디바이스에서의 LLM 실행을 위한 함수 호출 최적화(Less is More: Optimizing Function Calling for LLM Execution on Edge Devices)</a>를 참조하세요.</p><p>이는 더 많은 컨텍스트가 항상 더 나은 것은 아니라는 점을 상기시켜 줍니다. 특히 관련성에 맞게 <strong>선별</strong>되지 않았다면 더욱 그렇습니다.</p><h3>컨텍스트 충돌</h3><p><em>컨텍스트 충돌</em>은 <strong>컨텍스트의 일부가 서로 모순되어</strong> 모델의 추론을 방해하는 내부 불일치를 일으킬 때 발생합니다. 에이전트가 충돌하는 여러 정보를 축적하는 경우 충돌이 발생할 수 있습니다. </p><p>예를 들어 에이전트가 두 개의 출처에서 데이터를 가져왔다고 가정해 보겠습니다. 하나는 <em>A 항공편이 오후 5시에 출발한다</em>고 하고, 다른 하나는 <em>A 항공편이 오후 6시에 출발한다</em>고 합니다. 두 사실이 모두 컨텍스트에 포함되면, 성능이 떨어지는 모델은 어느 것이 정확한지 알 수 없습니다. 혼란스러워하거나 부정확한 답변 또는 유사하지 않은 답변을 생성할 수 있습니다.</p><p>컨텍스트 충돌은 모델이 <strong>이전에 시도</strong>했던 답변이 나중에 추가된 정보와 함께 컨텍스트에 남아 있는 다중 턴 대화에서도 자주 발생합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd86976266867c0ed/6a170f3e66c4f9c785f8c105/500d7a80dc8db1923f9b5ca84728eed64fa296f7-1316x580.png" alt="컨텍스트 충돌" /><p>Microsoft와 Salesforce의 <a href="https://arxiv.org/pdf/2505.06120">연구에</a> 따르면 복잡한 쿼리를 여러 번의 챗봇 턴 대화로 나누어 (세부 정보를 점진적으로 추가하여) 답변할 경우, 모든 정보를 한 번에 제공하는 경우에 비해 최종 정확도가 크게 떨어지는 것으로 나타났습니다. 왜 그럴까요? 초기 턴에는 모델의 부분적이거나 부정확한 중간 답변이 포함되어 있고 이러한 답변이 컨텍스트에 남아 있기 때문입니다. 나중에 모델이 모든 정보를 가지고 답변을 시도할 때, 모델의 <em>메모리</em>에는 여전히 이러한 잘못된 답변이 포함되어 있으며, 이는 수정된 정보와 충돌하여 잘못된 방향으로 나아가게 합니다. 본질적으로 대화의 컨텍스트가 스스로 충돌하는 것입니다. 모델은 새로운 정보를 추가한 후에도 적용되지 않는 오래된 컨텍스트(이전 턴의 컨텍스트)를 의도치 않게 사용할 수 있습니다.</p><p>에이전트 시스템에서 컨텍스트 충돌은 특히 위험한데, 에이전트가 서로 다른 도구나 하위 에이전트의 출력을 결합할 수 있기 때문입니다. 이러한 출력이 서로 일치하지 않으면 통합된 컨텍스트가 일관성을 잃게 됩니다. 그러면 에이전트는 모순을 해결하려다 오류가 발생하거나 비합리적인 결과를 생성할 수 있습니다. 컨텍스트 충돌을 방지하려면 컨텍스트를 <strong>최신 상태로 유지하고 일관성을 보장</strong>해야 합니다.예를 들어 오래된 정보를 삭제하거나 업데이트하고, 일관성 검증을 거치지 않은 소스를 혼합해서 사용하지 않아야 합니다.</p><h3>컨텍스트 누출 및 지식 충돌</h3><p>여러 에이전트 또는 사용자가 메모리 저장소를 공유하는 시스템에서는 컨텍스트 간에 정보가 유출될 위험이 있습니다.</p><p>예를 들어 적절한 액세스 제어 없이 두 사용자의 데이터 임베딩이 동일한 벡터 데이터베이스에 저장된 경우, 사용자 A의 쿼리에 응답하는 에이전트가 실수로 사용자 B의 메모리 일부를 가져올 수 있습니다. 이러한 <em><strong>컨텍스트 간 누출</strong></em>은 개인 정보를 노출하거나 응답에 혼란을 초래할 수 있습니다.</p><p><a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">LLM 애플리케이션을 위한 OWASP Top 10</a>에 따르면 멀티테넌트 벡터 데이터베이스는 이러한 누출을 방지해야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte433216805a66d29/6a170f404a531b2c4e36aa25/8f0ccf0b2f7bd6715c14aceee2deffb213d50bd9-1600x936.png" alt="컨텍스트 누출" /><p><a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">LLM08:2025 벡터 및 임베딩 약점</a>에 따르면<em>,</em> 일반적인 위험 중 하나는 컨텍스트 누출입니다.</p><em>여러 사용자 또는 애플리케이션이 동일한 벡터 데이터베이스를 공유하는 멀티테넌트 환경에서는 사용자 또는 쿼리 간에 컨텍스트 누출 위험이 있습니다. 데이터 페더레이션 지식 충돌 오류는 여러 소스의 데이터가 서로 모순될 때 발생할 수 있습니다. 또한 LLM이 학습 과정에서 얻은 기존 지식을 검색 증강을 통해 얻은 새로운 데이터로 대체하지 못할 때도 이러한 오류가 발생할 수 있습니다.</em><p>또 다른 측면은 LLM이 <strong>기본 제공 지식</strong>을 메모리의 새로운 정보로 재정의하는 데 어려움을 겪을 수 있다는 것입니다. 모델이 특정 사실에 기반하여 학습되었는데 검색된 컨텍스트가 그와 반대되는 내용을 담고 있다면 모델은 어떤 것을 신뢰해야 할지 혼란스러워할 수 있습니다. 적절한 설계가 없으면 에이전트가 컨텍스트를 혼동하거나 기존 지식을 새로운 증거로 업데이트하지 못하여 오래되거나 부정확한 답변을 내놓을 수 있습니다.</p><h3><strong>환각 및 잘못된 정보</strong></h3><p><em>환각</em>(LLM이 그럴듯하지만, 틀린 정보를 만드는 것)은 긴 설명이 필요 없는 잘 알려진 문제로, 메모리 관리가 제대로 이루어지지 않으면 더욱 심해질 수 있습니다. </p><p>에이전트의 메모리에 중요한 사실이 누락된 경우, 모델은 <strong>추측으로 그 공백을 채울 수 있으며</strong>, 만약 그 추측이 컨텍스트에 들어가게 되면(컨텍스트를 오염시키면서) 오류가 지속됩니다. </p><p>OWASP LLM 보안 보고서<a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/"><strong>(LLM09:2025 잘못된 정보)</strong></a>는 잘못된 정보를 핵심 취약점으로 지적합니다. LLM은 확신에 찬 듯 보이지만 조작된 답변을 생성할 수 있으며, 사용자는 이를 과신할 수 있습니다. 장기 메모리가 불량하거나 오래된 에이전트는 메모리가 최신 상태로 유지되지 않으면 작년에는 사실이었지만 지금은 거짓인 정보를 확신에 차서 인용할 수 있습니다. </p><p>AI의 출력에 지나치게 의존하는 것(사용자 또는 에이전트 자체가 반복적인 과정에서)은 이러한 문제를 악화시킬 수 있습니다. 메모리에 저장된 정보를 아무도 검증하지 않으면 에이전트는 잘못된 정보를 축적하게 됩니다. 이것이 바로 RAG가 환각을 줄이는 데 자주 사용되는 이유입니다. RAG를 통해 권위 있는 출처를 검색함으로써 모델은 사실을 만들어낼 필요가 없습니다. 하지만 잘못된 문서(예: 잘못된 정보가 포함된 문서)를 검색하거나 초기 환각을 제거하지 않으면 시스템은 해당 잘못된 정보를 전체 작업에 전파할 수 있습니다. </p><p>결론적으로 메모리 관리에 실패하면 <strong>부정확하고 오해의 소지가 있는 출력</strong>으로 이어질 수 있으며, 이는 특히 위험 부담이 큰 경우(예: 금융이나 의료 분야에서 잘못된 조언) 심각한 피해를 초래할 수 있습니다. 에이전트는 컨텍스트에 있는 내용을 무조건 신뢰하는 것이 아니라 메모리 내용을 검증하거나 수정할 수 있는 메커니즘을 갖춰야 합니다.</p><p>요약하자면 AI 에이전트에 무한히 긴 메모리를 제공하거나 가능한 모든 것을 컨텍스트에 넣는 것은 <em>성공의 비결이 아닙니다</em>.</p><h2>LLM 애플리케이션에서의 메모리 관리 모범 사례</h2><p>위에서 언급한 문제점을 방지하고자 개발자와 연구자는 AI 시스템에서 <strong>컨텍스트와 메모리를 관리하는 여러 가지 모범 사례</strong>를 고안했습니다. 이러한 사례는 AI의 작업 컨텍스트를 간결하고 관련성 있으며 최신 상태로 유지하는 것을 목표로 합니다. 다음은 주요 전략 몇 가지와 그 효과를 보여주는 예시입니다.</p><h3>RAG: 타겟팅된 컨텍스트 사용</h3><p>RAG에 대한 내용은 이전 섹션에서 이미 많이 다루었습니다. 이 부분은 실용적인 핵심 사항을 간략하게 다시 한번 상기시켜 드리는 것입니다.</p><ul><li><p>대량 로딩이 아닌 대상 검색을 사용하세요. 전체 문서나 전체 대화 기록을 프롬프트에 입력하는 대신 가장 관련성이 높은 청크만 검색하세요.</p></li><li><p>RAG를 적절한 시기에 메모리를 불러오는 기능으로 활용하세요. 모든 정보를 턴 전반에 가져가지 말고, 필요할 때만 컨텍스트를 가져오세요.</p></li><li><p>관련성 인식 검색 전략을 선호하세요. 상위 k개 시멘틱 검색, 상호 순위 융합 또는 도구 구성 필터링과 같은 접근 방식은 노이즈를 줄이고 근거를 개선하는 데 도움이 됩니다.</p></li><li><p>컨텍스트 윈도우가 크다고 해서 RAG의 필요성이 없어지는 것은 아닙니다. 관련성이 높은 두 갱의 단락이 관련성이 낮은 20페이지 분량의 문서보다 거의 항상 더 효과적입니다.</p></li></ul><p>즉, RAG는 더 많은 컨텍스트를 추가하는 것이 아니라 적절한 컨텍스트를 추가하는 것입니다.</p><h3>도구 구성</h3><p><em>도구 구성</em>은 모델에 특정 작업에 실제로 필요한 도구만 제공하는 것을 의미합니다. 이 용어는 게임에서 유래했는데, 상황에 맞는 구성을 선택하는 것과 같습니다. 도구가 너무 많으면 속도가 느려지고, 잘못된 도구는 실패로 이어집니다. 연구 논문 <a href="https://arxiv.org/abs/2411.15399">적을수록 낫다(Less is more)</a>에 따르면 LM도 마찬가지로 동작합니다. 도구가 약 30개를 넘어서면 설명이 중복되어 모델이 혼란스러워집니다. 100개를 넘어서면 실패가 거의 확실해집니다. 이는 컨텍스트 윈도우 문제가 아니라 컨텍스트 혼동 문제입니다.</p><p>간단하면서도 효과적인 해결책은 <a href="https://arxiv.org/abs/2505.03275"><strong>RAG-MCP</strong></a>입니다. 모든 도구를 프롬프트에 나열하는 대신, 도구 설명을 벡터 데이터베이스에 저장하고 요청 시 가장 관련성이 높은 도구만 불러옵니다. 실제로 이렇게 하면 도구 구성이 간결하고 집중적으로 유지되고 프롬프트 표시 시간이 크게 단축되며 도구 선택 정확도가 최대 3배까지 향상될 수 있습니다.</p><p>소규모 모델일수록 이러한 한계에 더 빨리 부딪힙니다. 연구 결과에 따르면 80억 단위의 모델은 수십 개의 도구를 사용할 때는 제대로 작동하지 않지만, 도구 구성을 줄이면 성공하는 것으로 나타났습니다. 필요한 도구를 추론하여 동적으로 선택하는 방식(때로는 LLM을 먼저 사용하는 방식)은 성능을 44% 향상하면서 전력 소비와 지연 시간도 줄일 수 있습니다. 핵심은 대부분의 에이전트는 몇 개의 도구만 필요하지만, 시스템 규모가 커질수록 도구 구성과 RAG-MCP가 설계에서 가장 중요한 고려 사항이 된다는 것입니다.</p><h3>컨텍스트 가지치기: 채팅 기록 길이 제한</h3><p>대화가 여러 차례 이어지면 누적된 채팅 기록이 너무 커져서 컨텍스트 오버플로가 발생하거나 모델에 방해가 될 수 있습니다. </p><p><em>트리밍</em>이란 대화가 길어짐에 따라 중요도가 낮은 부분을 프로그램적으로 제거하거나 축약하는 것을 의미합니다. 간단한 방법으로는 특정 한계에 도달했을 때 가장 오래된 대화 내용을 삭제하고 최근 <em>N</em>개의 메시지만 남기는 것이 있습니다. 더 정교한 가지치기는 관련 없는 곁가지 이야기나 더 이상 필요하지 않은 이전 지시 사항을 제거할 수 있습니다. 목표는 <strong>컨텍스트 윈도우를 오래된 정보로 어지럽히지 않고 깔끔하게 유지</strong>하는 것입니다. </p><p>예를 들어 에이전트가 10턴 전에 하위 문제를 해결했고 그 이후로 넘어갔다면 더 이상 필요하지 않다고 가정하여 해당 기록 부분을 컨텍스트에서 삭제할 수 있습니다(더 이상 필요하지 않다고 가정). 많은 채팅 기반 구현이 이와 같은 방식을 사용합니다. 즉, 최근 메시지의 롤링 윈도우를 유지관리합니다. </p><p>트리밍은 대화의 앞부분이 요약되었거나 관련성이 없다고 판단되면 해당 부분을 '잊어버리는' 것처럼 간단하게 할 수 있습니다. 이렇게 하면 컨텍스트 오버플로 오류 위험과 <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-distraction"><strong>컨텍스트 방해</strong></a>를 줄여 모델이 오래되거나 주제에서 벗어난 콘텐츠에 현혹되지 않도록 할 수 있습니다. 이는 사람이 한 시간짜리 대화의 모든 단어는 기억하지 못하지만 핵심 내용은 기억하는 것과 매우 유사합니다. </p><p>컨텍스트 가지치기가 헷갈린다면, 저자 드류 브루니그(Drew Breunig)가 <a href="https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html#tool-loadout:~:text=Provence%20is%20fast%2C%20accurate%2C%20simple%20to%20use%2C%20and%20relatively%20small%20%E2%80%93%20only%201.75%20GB.%20You%20can%20call%20it%20in%20a%20few%20lines%2C%20like%20so%3A">여기</a>서 강조했듯이, 질문 답변을 위한 가볍고(1.75GB), 효율적이며 정확한 컨텍스트 가지치기 도구인 Provence (`<a href="https://huggingface.co/naver/provence-reranker-debertav3-v1">naver/provence-reranker-debertav3-v1</a>`) 모델을 사용해 보시면 도움이 될 수 있습니다. 이 모델은 방대한 문서를 특정 쿼리에 가장 관련성이 높은 텍스트만 남기도록 다듬어 줍니다. 특정 간격으로 호출할 수도 있습니다.</p><p>컨텍스트 가지치기를 위해 코드에서 `provence-reranker` 모델을 호출하는 방법은 다음과 같습니다.</p># Context pruning with Provence
def prune_with_provence(query: str, context: str, threshold: Optional[float] = None) -&gt; str:
    """
    Prune context using Provence reranker model
    
    Args:
        query: User's query/question
        context: Original context to prune
        threshold: Relevance threshold (0-1) for Provence reranker.
                   If None, uses args.pruning_threshold.
                   0.1 = conservative (recommended, no performance drop)
                   0.3-0.5 = moderate to aggressive pruning
    
    Returns:
        Pruned context with only relevant sentences
    """
    if provence_model is None:
        return context
    
    if threshold is None:
        threshold = args.pruning_threshold
    
    try:
        # Use Provence's process method
        provence_output = provence_model.process(
            question=query,
            context=context,
            threshold=threshold,
            always_select_title=False,
            enable_warnings=False
        )
        
        # Extract pruned context from output
        pruned_context = provence_output.get('pruned_context', context)
        reranking_score = provence_output.get('reranking_score', 0.0)
        
        # Log statistics
        original_length = len(context)
        pruned_length = len(pruned_context)
        reduction_pct = ((original_length - pruned_length) / original_length * 100) if original_length &gt; 0 else 0
        
        if args.verbose:
            rich.print(f"[cyan]📊 Pruning stats: {pruned_length}/{original_length} chars ({reduction_pct:.1f}% reduction, threshold={threshold:.2f}, rerank_score={reranking_score:.3f})[/cyan]")
        
        return pruned_context if pruned_context else context
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in Provence pruning: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context<p>문장 관련성 점수 계산에는 Provence reranker 모델(`naver/provence-reranker-debertav3-v1`)을 사용합니다. 임계값 기반 필터링을 통해 관련성 임계값 이상의 문장만 남깁니다. 또한 가지치기가 실패할 경우 원래 컨텍스트으로 되돌아가는 폴백(fallback) 메커니즘을 도입했습니다. 마지막으로 상세 모드에서는 통계 로깅을 통해 감소율을 추적합니다.</p><h3>컨텍스트 요약: 이전 정보를 완전히 삭제하는 대신 축약</h3><p><em>요약</em>은 트리밍의 동반자입니다. 기록이나 지식 베이스가 너무 커지면 위의 코드에서 수행한 것처럼 LLM을 사용하여 중요한 요점에 대한 간략한 요약을 생성하고 앞으로 전체 콘텐츠 대신 이 요약을 사용할 수 있습니다.</p><p>예를 들어 AI 어시스턴트가 50턴 대화를 나눴다고 가정해 봅시다. 51턴 대화에서 50턴까지 대화를 한꺼번에 보내는 대신(용량 부족 문제 발생 가능성이 높음), 시스템은 1~40턴까지 대화만 처리하고 모델이 이를 요약하여 단락으로 작성하도록 한 다음, 다음 대화에서는 해당 요약본과 마지막 10턴까지의 대화 내용만 제공할 수 있습니다. 이렇게 하면 모델은 모든 세부 정보를 입력받지 않고도 어떤 내용이 논의되었는지 파악할 수 있습니다. 초기 챗봇 사용자는 "지금까지 나눈 대화를 요약해 줄 수 있나요?"라고 수동으로 질문하고, 요약 내용을 바탕으로 새로운 대화를 이어갔습니다. 이제는 이 작업을 자동화할 수 있습니다. 요약은 컨텍스트 윈도우 공간을 절약할 뿐만 아니라 불필요한 세부 정보를 제거하고 중요한 사실만 유지함으로써 <strong>컨텍스트 혼동/방해</strong>를 줄일 수 있습니다.</p><p>여기서는 OpenAI 모델(다른 LLM 모델도 사용 가능)을 활용하여 모든 관련 정보를 보존하면서 컨텍스트를 축약하고 중복 및 비효율성을 제거하는 방법을 설명합니다.
</p># Context summarization
def summarize_context(query: str, context: str) -&gt; str:
    """
    Summarize context using LLM to reduce duplication and focus on relevant information
    
    Args:
        query: User's query/question
        context: Context to summarize
        
    Returns:
        Summarized context
    """
    try:
        summary_prompt = f"""You are an expert at summarizing conversation context.

Your task: Analyze the provided conversation context and produce a condensed summary that fully answers or supports the user's specific question.

The summary must:
1. Preserve every fact, detail, and information that directly relates to the question
2. Eliminate redundancy and duplicate information
3. Maintain chronological flow when relevant
4. Focus on information that helps answer: "{query}"

Context to summarize:
{context}

Provide a concise summary that preserves all relevant information:"""

        summary = llm.invoke(summary_prompt).content
        
        if args.verbose:
            original_length = len(context)
            summary_length = len(summary)
            reduction_pct = ((original_length - summary_length) / original_length * 100) if original_length &gt; 0 else 0
            rich.print(f"[cyan]📝 Summarization stats: {summary_length}/{original_length} chars ({reduction_pct:.1f}% reduction)[/cyan]")
        
        return summary
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in context summarization: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context<p>중요한 것은 컨텍스트가 요약되면 (요약이 정확하다는 가정 하에) 모델이 사소한 세부 사항이나 과거 오류에 압도될 가능성이 줄어든다는 점입니다. </p><p>하지만 요약은 신중하게 해야 합니다. 요약이 잘못되면 중요한 세부 사항이 누락되거나 오류가 발생할 수도 있습니다. 요약은 본질적으로 모델에 대한 또 다른 프롬프트("이것을 요약하세요")이기 때문에 뉘앙스가 왜곡되거나 사라질 수 있습니다. 가장 좋은 방법은 점진적으로 요약하고 일부 표준적인 사실은 요약하지 않는 것입니다.</p><p>그럼에도 불구하고 이는 매우 유용한 것으로 입증되었습니다. <a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">Gemini 에이전트 시나리오</a>에서 약 10만 토큰마다 컨텍스트를 요약하는 것은 모델의 반복적인 경향을 상쇄하는 방법이었습니다. 요약은 대화 또는 데이터의 압축된 메모리 역할을 합니다. 개발자는 에이전트가 대화 기록이나 긴 문서에 대해 주기적으로 요약 함수(소규모 LLM 또는 전용 루틴)를 호출하도록 구현할 수 있습니다. 결과로 생성된 요약은 프롬프트의 원래 내용을 대체합니다. 이 전략은 컨텍스트를 제한적으로 유지하고 정보를 추출하는 데 널리 사용됩니다.</p><h3>컨텍스트 격리: 가능한 경우 컨텍스트 격리</h3><p>이는 복잡한 에이전트 시스템이나 다단계 워크플로우에서 더욱 중요합니다. 컨텍스트 세분화의 핵심 아이디어는 큰 작업을 각각 고유한 컨텍스트를 가진 더 작고 독립적인 작업으로 나누는 것입니다. 이렇게 하면 모든 것을 포함하는 하나의 거대한 컨텍스트가 누적되는 것을 방지할 수 있습니다. 각 하위 에이전트 또는 하위 작업은 특정 컨텍스트에 집중하여 문제 일부를 해결하고, 상위 에이전트, 감독자 또는 조정자가 결과를 통합합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09d1eac7442aea2b/6a170f42dc55deb10de00ea7/f2de68c3339883d7658e633af3948f29f427e6cf-1600x900.png" alt="컨텍스트 격리" /><p><a href="https://www.anthropic.com/engineering/multi-agent-research-system">Anthropic의 연구 전략은 여러 개의 하위 에이전트를 활용합니다</a>. 각 하위 에이전트는 고유한 컨텍스트 윈도우를 가지고 질문의 서로 다른 측면을 조사하며, 리드 에이전트는 이러한 하위 에이전트가 도출한 결과를 종합적으로 분석합니다. 이러한 병렬적인 모듈식 접근 방식 덕분에 어느 하나의 컨텍스트 윈도우도 과도하게 복잡해지지 않습니다. 또한 관련 없는 정보가 섞일 가능성을 줄이고, 각 스레드는 주제에서 벗어나지 않으며(컨텍스트 혼동 방지), 특정 하위 질문에 답할 때 불필요한 정보를 포함하지 않습니다. 마치 사고 과정 전체가 아닌 결과만 공유하는 별개의 독립적인 사고 스레드를 실행하는 것과 같습니다.</p><p>멀티 에이전트 시스템에서는 이러한 접근 방식이 필수적입니다. 에이전트 A가 작업 A를 처리하고 에이전트 B가 작업 B를 처리하는 경우, 정말 필요한 경우가 아니라면 어느 에이전트도 다른 에이전트의 전체 컨텍스트를 소비할 이유가 없습니다. 대신 에이전트는 필요한 정보만 교환할 수 있습니다. 예를 들어 에이전트 A는 감독 에이전트를 통해 에이전트 B에 조사 결과의 통합 요약을 전달할 수 있으며, 각 하위 에이전트는 자체적인 전용 컨텍스트 스레드를 유지관리합니다. 이러한 구성은 사람의 개입이 필요하지 않으며, 최소한의 제어된 컨텍스트 공유 기능을 갖춘 도구를 사용하는 감독 에이전트에 의존합니다.</p><p>그럼에도 불구하고 에이전트나 도구가 필요한 컨텍스트 중복을 최소화하면서 작동하도록 시스템을 설계하면 명확성과 성능을 크게 향상할 수 있습니다. 이를 <strong>AI를 위한 마이크로서비스</strong>라고 생각하면 됩니다. 각 구성 요소는 고유한 컨텍스트를 처리하고, 단일 컨텍스트 대신 제어된 방식으로 구성 요소 간에 메시지를 전달합니다. 이러한 모범 사례는 종종 함께 사용됩니다. 또한 이를 통해 사소한 기록을 트리밍하고 중요한 이전 메시지나 대화를 요약하며, 상세한 로그를 Elasticsearch에 저장하여 장기적인 컨텍스트를 유지하고 필요할 때 관련 정보를 검색할 수 있는 유연성을 확보할 수 있습니다.</p><p><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=While%20some%20models,to%20the%20LLM">앞서</a> 언급했듯이, 핵심 원칙은 컨텍스트는 제한적이고 귀중한 자원이라는 것입니다. 프롬프트의 모든 토큰은 제 역할을 다해야 하며, 출력의 질 향상에 기여해야 합니다. 메모리에 저장된 요소가 제 역할을 하지 못하고 더 나아가 혼동을 야기하는 경우 그 항목을 제거하거나 요약하거나 제외해야 합니다.</p><p>개발자로서 이제 코드를 프로그래밍하듯이 컨텍스트를 프로그래밍할 수 있습니다. 어떤 정보를 포함할지, 어떤 형식으로 표현할지, 언제 생략하거나 업데이트할지 결정할 수 있습니다. 이러한 방식을 따르면 LLM 에이전트가 앞서 설명한 오류 모드에 빠지지 않고 작업을 수행하는 데 필요한 컨텍스트를 제공할 수 있습니다. 결과적으로 에이전트는 기억할 정보는 기억하고, 기억할 필요 없는 정보는 잊고, 필요한 정보는 적시에 검색할 수 있게 됩니다.</p><h2>결론</h2><p>메모리는 에이전트에 추가하는 것이 아니라 설계하는 것입니다. 단기 메모리는 에이전트의 작업 임시 저장소 역할을 하고, 장기 메모리는 영구적인 지식 저장소 역할을 합니다. RAG는 이 둘을 연결하는 다리 역할을 하며, Elasticsearch와 같은 수동적인 데이터 저장소를 능동적인 재현 메커니즘으로 전환하여 출력을 안정적으로 유지하고 에이전트를 최신 상태로 유지합니다.</p><p>하지만 메모리는 양날의 검과도 같습니다. 컨텍스트를 방치하는 순간 오염, 방해, 혼동, 충돌이 발생하고 공유 시스템에서는 데이터 누출까지 일어날 수 있습니다. 그렇기 때문에 가장 중요한 메모리 작업은 '더 많이 저장'하는 것이 아니라 '더 잘 선별'하는 것입니다. 선택적으로 검색하고 공격적으로 정리하며, 신중하게 요약하고 업무에 꼭 필요한 경우가 아니라면 관련 없는 컨텍스트를 섞지 마세요.</p><p>실제로 훌륭한 컨텍스트 엔지니어링은 훌륭한 시스템 설계와 유사합니다. 즉, 작고 충분한 컨텍스트, 구성 요소 간의 제어된 인터페이스, 그리고 원시 상태와 모델이 실제로 인식해야 하는 정제된 상태를 명확하게 구분하는 것입니다. 제대로 구현하면 모든 것을 기억하는 에이전트를 만드는 것이 아니라, 적시에 적합한 이유로 적절한 정보를 기억하는 에이전트를 만들 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Someshwaran Mohankumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3bad6b045392e641/6a170f43a29299c189d010cc/80907fd072e72d6ec902470b449c9f337957a0d7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Agent Builder와 Strands Agents SDK로 시작하기]]></title>
    <description><![CDATA[Elastic Agent Builder를 활용해 에이전트를 생성하는 방법을 배우고, Strands Agents SDK로 오케스트레이션하는 A2A 프로토콜을 통해 해당 에이전트를 사용하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>AI 에이전트에 대한 아이디어가 있으신가요? 아마 데이터와 관련된 일일 겁니다. 에이전트가 유용한 행동을 시작하려면 결정을 내려야 하고, 그 결정을 제대로 내리려면 적절한 데이터가 필요하기 때문입니다.</p><p>Elastic Agent Builder를 사용하면 데이터 기반 AI 에이전트를 쉽게 구축할 수 있습니다. 이 블로그 게시물에서 그 방법을 알려드리겠습니다. Elastic에 저장된 데이터에 액세스하는 MCP 도구로 에이전트를 생성하는 데 필요한 모든 단계를 살펴본 후, Strands Agents SDK와 그의 Agent2Agent(A2A) 기능을 사용하여 에이전트를 운영해 보겠습니다. <a href="https://strandsagents.com/">Strands Agents SDK</a>는 멀티 에이전트 AI 개발 플랫폼으로, 최소한의 코드만으로 원하는 결과를 얻을 수 있는 에이전틱 앱을 구축할 수 있습니다.</p><p>RPS+ 게임을 플레이하는 AI 에이전트를 만들어 보세요. RPS+는 기존 가위바위보 게임에 새로운 변형을 더한 게임으로, 플레이어에게 몇 가지 추가 선택지를 제공합니다.</p><h2>필수 구성 요소</h2><p>이 블로그 게시물의 단계를 따라 진행하려면 다음과 같은 준비 사항이 필요합니다.</p><ul><li><p>로컬 컴퓨터에서 실행 가능한 텍스트 편집기</p><ul><li><p>이 블로그 게시물의 예제 지침에는 <a href="https://code.visualstudio.com/download">Visual Studio Code</a>를 사용합니다.</p></li></ul></li><li><p>로컬 컴퓨터에 설치된 <a href="https://www.python.org/downloads/">Python 3.10 이상</a></p></li></ul><h2>서버리스 프로젝트 만들기</h2><p>우선 Elastic Agent Builder가 포함된 Elasticsearch Serverless 프로젝트가 필요합니다.</p><p><a href="http://cloud.elastic.co/">cloud.elastic.co</a>로 이동해 새로운 Elasticsearch Serverless 프로젝트를 생성하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3472edce39ec0b81/6a17060e66c4f93c17f8bf57/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif" alt="" /><h2>인덱스 생성 및 데이터 추가</h2><p>다음으로 Elasticsearch 프로젝트에 몇 가지 데이터를 추가해 보겠습니다. Developer Tools를 열고 명령어를 실행하여, 새 인덱스를 만들고 일부 데이터를 삽입할 수 있습니다. 최상단 탐색 메뉴에서 Developer Tools를 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaedaa94068c07a17/6a17060f961e697558c4ce5f/f97d5af077504463155655a9e27c171a7f974f71-1600x879.jpg" alt="" /><p>다음 PUT 명령어를 복사하고 Developer Tools 콘솔의 요청 입력 영역에 붙여넣으세요. 이 명령문은 “game-docs”라는 이름의 Elasticsearch 인덱스를 생성합니다.</p>PUT /game-docs
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { 
        "type": "text"
      },
      "filename": { "type": "keyword" },
      "last_modified": { "type": "date" }
    }
  }
}<p>Developer Tools에서 해당 명령문 오른쪽에 표시되는 <strong>Send request</strong>(요청 전송) 버튼을 클릭하세요. Developer Tools의 응답 영역에서 <em>game-docs</em> 인덱스가 생성되었음을 확인하는 알림이 표시될 겁니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt430c357b479d93af/6a170611a6c2b98191e79624/be0555a1930e4d4f58b7ed8b669c9b702532ed17-1600x880.jpg" alt="" /><p><em>game-docs</em>라는 이름의 인덱스는 현재 만들고 있는 게임의 데이터를 저장하기에 좋은 장소입니다. 그럼 <em>rps+-md</em>라는 이름의 문서를 이 인덱스에 추가하여, 게임에 필요한 모든 데이터를 저장해 보겠습니다. 다음 PUT 명령어를 복사하여 Developer Tools 콘솔에 붙여넣으세요.</p>PUT /game-docs/_doc/rps+-md
{
  "title": "Rock Paper Scissors +",
  "content": "
# Game Name
RPS+

# Starting Prompt
Let's play RPS+ !
---
What do you choose?

# Game Objects
1. Rock 🪨 👊
2. Paper 📜 🖐
3. Scissors ✄ ✌️
4. Light ☼ 👍
5. Dark Energy ☄ 🫱

# Judgement of Victory
* Rock beats Scissors
  * because rocks break scissors
* Paper beats Rock
  * because paper covers rock
* Scissors beat Paper
  * because scissors cut paper
* Rock beats Light
  * because you can build a rock structure to block out light
* Paper beats Light
  * because knowledge stored in files and paper books helps us understand light
* Light beats Dark Energy
  * because light enables humans to lighten up and laugh in the face of dark energy as it causes the eventual heat death of the universe
* Light beats Scissors
  * because light is needed to use scissors safely
* Dark Energy beats Rock
  * because dark energy rocks more than rocks. It rocks rocks and everything else in its expansion of the universe
* Dark Energy beats Paper
  * because humans, with their knowledge stored in files and paper books, can't explain dark energy 
* Scissors beat Dark Energy
  * because a human running with scissors is darker than dark energy

# Invalid Input
I was hoping for an worthy opponent
  - but alas it appears that time has past
  - but alas there's little time for your todo list when [todo:fix this] is so vast

# Cancel Game
The future belongs to the bold. Goodbye..
",
  "filename": "RPS+.md",
  "last_modified": "2025-11-25T12:00:00Z"
}<p>해당 명령문 옆에 있는 <strong>Send request</strong>(요청 전송) 버튼을 클릭하여 실행하고 <em>rps+-md</em> 문서를 game-docs 인덱스에 추가하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64d49e13754d5b25/6a17061214b270524be3c55d/3c01d8a4602de5c33337457591a388a4a4e3fad3-1600x879.jpg" alt="" /><p>이제 쿼리할 데이터가 준비되었습니다. Agent Builder를 사용하면 이 과정이 그 어느 때보다 간단해집니다.</p><p>최상단 탐색 메뉴에서 <strong>Agents</strong>(에이전트)를 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb4d374bf2ba9135c/6a1706147d8d67468570e63e/82dbd2e9a439cabd5a5eea3d0ce005b87df0c3ea-1600x879.jpg" alt="" /><p>그런 다음 기본 Elastic AI 에이전트에게 "What data do I have?(내가 가진 데이터는 무엇인가요?)"라고 묻기만 하면 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0f879cf28772718/6a1706161949f7f25ee7a92d/f7a2f39c9d1486bdf02d9e88a732b540ac2e2cd1-1600x872.gif" alt="" /><p>Elastic AI 에이전트는 데이터를 평가하고 현재 보유한 데이터에 대한 간결한 설명을 제공합니다.</p><h2>도구 만들기</h2><p>Elastic에 데이터가 준비되었으니 이제 활용해 보겠습니다. Agent Builder는 에이전트가 작업에 필요한 올바른 컨텍스트를 갖출 수 있도록 데이터에 접근하게 도와주는 <a href="https://modelcontextprotocol.io/">MCP</a> 도구를 생성하는 기능을 내장하고 있습니다. 게임 데이터를 조회하는 간단한 도구를 만들어 보겠습니다.</p><p>Agent Builder 작업 메뉴를 클릭하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7802a6b94e81440c/6a170618ab7f085287db9db4/0e327c202674dda33bcc0e494d2b588fa8b32e4f-1600x879.png" alt="" /><p>메뉴 옵션에서 <strong>View all tools</strong>(모든 도구 보기)를 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f52ffe114fb6ea7/6a17061a4a531b801b36a884/1ebf58650e9fb56750d3f0b1700fab50b44f9bdf-1600x879.png" alt="" /><p><strong>+ New Tool</strong>(새 도구)을 클릭하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8090769f6c4d1899/6a17061c286714294093e219/6c03a7f28b99ac2d805f34f39948979893316a00-1600x879.png" alt="" /><p><strong>Create Tool</strong>(도구 생성) 양식에서 도구 <strong>유형</strong>으로<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql"><strong>ES|QL</strong></a>을 선택하고 다음 값을 입력하세요.</p><p><strong>Tool ID</strong>(도구 ID)에 다음을 입력하세요.</p>example.get_game_docs<p><strong>Description</strong>(설명)에 다음을 입력하세요.</p>Get RPS+ doc from Elasticsearch game-docs index.<p><strong>Configuration</strong>(구성)의 경우 <strong>ES|QL 쿼리</strong> 텍스트 영역에 다음 쿼리를 입력하세요.</p>FROM game-docs | WHERE filename == "RPS+.md"<p>완성된 <strong>Create tool</strong>(도구 생성) 양식은 다음과 같아야 합니다. <strong>Save</strong>(저장)를 클릭하여 도구를 만드세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77034c305198217a/6a17061e66c4f9e54ef8bf5e/b6c93e344600f319b9d2c3030020cf2d171ac1c4-1600x1312.png" alt="" /><p>이제 새로운 도구가 준비되었습니다. 도구는 단순히 보관해 두기보다는 가치 있게 활용해야겠죠. 새로 만든 사용자 지정 도구를 사용할 수 있는 에이전트를 만들어 보겠습니다.</p><h2>에이전트를 생성하고 도구를 할당하기</h2><p>Agent Builder를 사용하면 매우 간단하게 에이전트를 만들 수 있습니다. 몇 가지 세부 정보와 함께 에이전트 지침을 입력하기만 하면 됩니다. 이제 에이전트를 만들어보겠습니다.</p><p><strong>Manage agents</strong>(에이전트 관리)를 클릭하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaa8a83fc2f3758a9/6a1706201949f71a10e7a931/53934b93db07187e251d4b321cb9ca647e2fd51b-1600x858.png" alt="" /><p><strong> + New agent</strong>(새 에이전트)를 클릭하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3778403c5101a000/6a17062160084be12f3c449e/fae3ad8f31e71a6dfd044e1daa025a4e280b4e68-1600x490.png" alt="" /><p>다음 정보를 <strong>New Agent</strong>(새 에이전트) 양식에 입력하세요.</p><p><strong>Agent ID</strong>(에이전트 ID)에 아래 텍스트를 입력하세요.</p>rps_plus_agent<p><strong>Custom Instructions</strong>(사용자 지정 지침) 텍스트 영역에 다음 지침을 입력하세요.</p>When prompted, if the prompt contains an integer, then select the corresponding numbered item in the list of "Game Objects" from your documents. Otherwise select a random game object. This is your chosen game object for a single round of the game.

# General Game Rules
* 2 players
    - the user: the person playing the game
    - you: the agent playing the game and serving as the game master
* Each player chooses a game object which will be compared and cause them to tie, win or lose.

# Start the game
1. This is the way each new game always starts. You make the first line of your response only the name of your chosen game object. 

2. The remainder of your response should be the "Starting Prompt" text from your documents and generate a list of "Game Objects" for the person playing the game to choose a game object from.  

# End of Game: The game ends in one of the following three outcomes:
1. Invalid Input: If the player responds with an invalid game object choice, respond with variations of the "Invalid Input" text from your documents and then end the game.

2. Tie: The game ends in a tie if the user chooses the same game object as your game object choice.

3. Win or Lose: The game winner is decided based on the "Judgement of Victory" conditions from your documents. Compare the user's game object choice and your game object choice and determine who chose the winning game object.

# Game conclusion
Respond with a declaration of the winner of the game by outputting the corresponding text in the "Judgement of Victory" section of your documents.<p><strong>Display name</strong>(표시 이름)에 아래 텍스트를 입력하세요.</p>RPS+ Agent<p><strong>Display description</strong>(표시 설명)에 아리 텍스트를 입력하세요.</p>An agent that plays the game RPS+<p><strong>Tools</strong>(도구) 탭을 클릭하여 이전에 만든 사용자 지정 도구를 에이전트에 제공하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5b0fe00abdde07c/6a17062314b2704bc4e3c563/1778f64bc3a1b4004998dc3668ef7f666788e193-1600x1390.png" alt="" /><p>앞서 만든 <em>example.get_game_docs</em> 도구만 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2210212e07e06104/6a170625a929cf3277ae08d1/7d734cd80161bcc058817482eb330ffcf1cb567b-1600x1363.png" alt="" /><p><strong>Save</strong>(저장)를 클릭하여 새 에이전트를 만드세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e3afc1918e26f14/6a170627ab7f084746db9db8/c0014faf605ce50c03679ed0d073bd9f3ae7234d-1600x468.png" alt="" /><p>새 에이전트를 테스트해 보겠습니다. 에이전트 목록에서 원하는 에이전트와 채팅을 시작할 수 있는 편리한 링크가 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb4b69dc5971d3a0/6a1706286f7f046840914743/b7d6943ad90a4f68691207caf66b81742e712145-1600x560.png" alt="" /><p>"start game(게임 시작)"을 입력하기만 하면 게임이 시작됩니다. 잘 작동하는군요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5b621d602223dff/6a17062ab339d568a1769ef8/984d008e4cc3f08cc1f101720673b0f7347c066c-1600x874.gif" alt="" /><p>에이전트의 응답 상단에 게임 개체 선택 항목이 표시되는 것을 볼 수 있습니다. 이를 통해 에이전트의 선택을 확인하고 게임이 예상대로 작동하는지 확인할 수 있습니다. 하지만 상대방의 선택을 미리 알면 가위바위보 게임이 재미있지는 않습니다. 게임을 최종 형태로 다듬고 완성도를 높이기 위해 코드로 에이전트를 제어할 수 있는 에이전트 오케스트레이션 플랫폼을 사용할 수 있습니다.</p><p>이제 Strands Agents SDK가 등장합니다.</p><h2>Strands Agents SDK</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73901ec745a97fbf/6a17062c964cea23c808bab3/c195bba6ff2754f5d8fda174a0c1d247bc283710-456x156.png" alt="" /><p>새로운 에이전트 개발 프레임워크를 사용해 보고 싶으시다면, <a href="https://strandsagents.com/latest/">Strands Agents SDK</a>가 시도해 볼 만한 가치가 있습니다. <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands Agents SDK는 AWS에서 오픈 소스 Python</a> 구현으로 출시되었으며(2025년 5월), 이제 <a href="https://dev.to/aws/strands-agents-now-speaks-typescript-a-side-by-side-guide-12b3">Typescript</a> 버전도 제공됩니다.</p><h2>Python에서 Strands Agents SDK 시작하기</h2><p>코딩 준비를 시작해 보죠. 이제 Strands Agents를 사용해 A2A 프로토콜로 <em>RPS+ 에이전트</em>를 제어하는 예제 앱을 복제하고 실행하는 과정을 빠르게 살펴보겠습니다. 플레이어가 선택을 한 후에 에이전트의 선택이 공개되도록 RPS+ 게임을 좀 더 세밀하게 조정해 보겠습니다. 결국 추측과 예상치 못한 결과가 바로 가위바위보와 같은 게임을 재미있게 만드는 요소이기 때문입니다.</p><p>로컬 컴퓨터에서 <a href="https://code.visualstudio.com/download">Visual Studio Code</a>를 열고 새 터미널을 여세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3de752025d62993f/6a17062d0c4857f16501a997/2339cc37c89a3524f2b2a21684bc61dae958e1cf-915x460.jpg" alt="" /><p>새로 열린 터미널에서 다음 명령어를 실행해 Elasticsearch Labs 리포지토리를 복제하세요.</p>git clone https://github.com/elastic/elasticsearch-labs<p>다음 <em>cd</em> 명령어를 실행하여 elasticsearch-labs 디렉토리로 이동하세요.</p>cd elasticsearch-labs<p>이제 다음 명령어를 실행하여 Visual Studio Code에서 리포지토리를 여세요.</p>code .<p>Visual Studio 파일 탐색기에서 <em>supporting-blog-content</em>와 <em>agent-builder-a2a-strands-agents</em> 폴더를 펼친 다음 <em>elastic_agent_builder_a2a_rps+.py</em> 파일을 여세요. Visual Studio Code에서 파일을 열면 다음과 같이 보입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65ef8036a70bcaf1/6a17062f1949f7af36e7a935/d153b19e0e016c701576edb99ccab5af7c554f34-1484x1530.jpg" alt="" /><p>다음은 텍스트 편집기에서 볼 수 있는 <em>elastic_agent_builder_a2a_rps+.py</em> 내용입니다:</p>import asyncio
from dotenv import load_dotenv
from uuid import uuid4
import httpx
import os
import random
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, TextPart

DEFAULT_TIMEOUT = 60  # set request timeout to 1 minute


def create_message(*, role: Role = Role.user, text: str, context_id=None) -&gt; Message:
    return Message(
        kind="message",
        role="user",
        parts=[Part(TextPart(kind="text", text=text))],
        message_id=uuid4().hex,
        context_id=context_id,
    )


async def main():
    load_dotenv()
    a2a_agent_host = os.getenv("ES_AGENT_URL")
    a2a_agent_key = os.getenv("ES_API_KEY")
    custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"}

    async with httpx.AsyncClient(
        timeout=DEFAULT_TIMEOUT, headers=custom_headers
    ) as httpx_client:
        # Get agent card
        resolver = A2ACardResolver(httpx_client=httpx_client, base_url=a2a_agent_host)
        agent_card = await resolver.get_agent_card(
            relative_card_path="/rps_plus_agent.json"
        )
        # Create client using factory
        config = ClientConfig(
            httpx_client=httpx_client,
            streaming=True,
        )
        factory = ClientFactory(config)
        client = factory.create(agent_card)
        # Use the client to communicate with the agent
        print("\nSending 'start game' message to Elastic A2A agent...")
        random_game_object = random.randint(1, 5)
        msg = create_message(text=f"start with game object {random_game_object}")
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                context_id = event.context_id
                response_complete = event.parts[0].root.text
                # Get agent choice from the first line of the response
                parsed_response = response_complete.split("\n", 1)
                agent_choice = parsed_response[0]
                print(parsed_response[1])
        # User choice sent for game results from the agent
        prompt = input("Your Choice  : ")
        msg = create_message(text=prompt, context_id=context_id)
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                print(f"Agent Choice : {agent_choice}")
                print(event.parts[0].root.text)


if __name__ == "__main__":
    asyncio.run(main())<p>이 코드에서 어떤 일이 일어나고 있는지 살펴보도록 하겠습니다. <em><code>main()</code></em> 메서드에서 시작하여, 코드는 에이전트 URL과 API 키에 대한 환경 변수에 접근하는 것으로 시작합니다. 그런 다음 이러한 값을 사용하여 에이전트의 에이전트 카드를 가져오는 데 사용할 수 있는 <em><code>httpx</code></em><code> client</code>를 만듭니다. 그런 다음 클라이언트는 에이전트 카드 세부 정보를 사용하여 '게임 시작' 요청을 에이전트에게 보냅니다. 여기서 한 가지 흥미로운 점은 <code>"start game"</code> 요청의 일부로 <code>random_game_object</code> 값을 포함한다는 점입니다. 이 값은 Python 표준 라이브러리의 <em>random</em> 모듈을 사용하여 생성된 임의의 숫자입니다. 이렇게 하는 이유는 AI 에이전트를 가능하게 하는 강력한 LLM들이 무작위성에 능숙하지 않다는 것이 밝혀졌기 때문입니다. 하지만 걱정 마세요. Python이 해결해 주니까요.</p><p>이어서 코드 설명을 계속하자면, 에이전트가 "start game" 요청에 응답한 후, 코드가 에이전트의 게임 객체 선택을 추출하여 <em>agent_choice</em> 변수에 저장합니다. 나머지 응답 내용은 최종 사용자에게 텍스트로 표시됩니다. 그러면 사용자는 게임 객체를 선택하라는 메시지를 받게 되며, 해당 입력은 에이전트로 전송됩니다. 그런 다음 코드는 에이전트의 게임 객체 선택과 함께 게임 결과에 대한 에이전트의 최종 결정을 표시합니다.</p><h2>에이전트 URL과 API 키를 환경 변수로 설정하기</h2><p>예제 앱이 로컬 컴퓨터에서 실행되므로 Agent Builder 에이전트와 통신하기 위해서는 에이전트의 A2A URL과 API 키를 Strands Agents SDK에 제공해야 합니다. 예제 앱은 <em>.env</em>라는 파일을 사용하여 이러한 값을 저장합니다.</p><p><em>env.example</em> 파일의 복사본을 만들고 새 파일 이름을 <em>.env</em>로 지정하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta17961cbcb42985c/6a170631b0367dc5a072bc55/25ead5f15a17dedb777132a082097cffb06cae4d-1600x843.jpg" alt="" /><p>Elastic Agent Builder로 돌아가서 필요한 두 가지 값을 모두 얻을 수 있습니다.</p><p>페이지 오른쪽 상단의 Agent Builder 작업 메뉴에서 <strong>View all tools</strong>(모든 도구 보기)를 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt140885d7ebfcb969/6a1706327d8d67b17670e646/9c4f4e4a3bd76e11e0a182fa007a2f6aec7777b4-1600x880.jpg" alt="" /><p>Tools(도구) 페이지 상단의 <strong>MCP Server</strong> 드롭다운을 클릭하고 <strong>Copy MCP Server URL</strong>(MCP 서버 URL 복사)를 선택하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc153c2caa27e949b/6a170634a292997793d00f6d/6cde0de678bb6f81bef8a59deffb110ad6c6ce26-1600x882.jpg" alt="" /><p><strong>MCP Server URL</strong>을 <em>.env</em> 파일에 붙여넣으세요. <strong>&lt;YOUR-ELASTIC-AGENT-BUILDER-URL&gt;</strong> 자리 표시자 값은 이 URL로 대체됩니다. 이제 URL을 한 가지 수정해야 합니다. 끝 부분의 텍스트 “mcp”를 “a2a”로 바꿔야 하는데, 이는 <a href="https://a2a-protocol.org/">A2A 프로토콜</a>이 Agent Strands SDK가 Elastic Agent Builder에서 실행 중인 에이전트와 통신하는 데 사용되기 때문입니다.</p><p>편집된 URL은 다음과 같이 표시될 것입니다.</p>https://rps-game-project-12345a.kb.us-east-1.aws.elastic.cloud/api/agent_builder/a2a<p>Elastic Cloud에 있는 동안 얻어야 하는 또 다른 값은 API 키입니다. 최상단 탐색 메뉴에서 <strong>Elasticsearch</strong>를 클릭하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltada5de819f31d8ff/6a170635b339d55ae9769efc/651676b9be65178cdad50b5d24f26441c0bf3f97-1600x549.jpg" alt="" /><p><strong>Copy API Key</strong>(API 키 복사) 버튼을 클릭해 API 키를 복사하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18f85790df00706/6a170637cf4f257145b2d0bd/17f1e2ed5c7682630c71e75b0b09ffb1d9036210-1600x879.jpg" alt="" /><p>이제 Visual Studio Code로 돌아가 <em>.env</em> 파일에 API 키를 붙여넣어 <strong>&lt;YOUR-ELASTIC-API-KEY&gt;</strong> 자리 표시자 텍스트를 대체하세요. <em>.env</em> 파일은 다음과 같이 표시될 것입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92ab4b37cdcca85e/6a1706386f7f0472ed914747/a357947e07f29c8c03382e00c7baedf04a399297-1600x286.jpg" alt="" /><h2>예제 앱 실행하기</h2><p>Visual Studio Code에서 새 터미널을 여세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8702d826849755d0/6a17063a60084b45ca3c44a2/33e1174c68ea1ed47c7fe62ab6a6da657c606f56-1413x711.jpg" alt="" /><p>터미널에서 다음 <em>cd</em> 명령어를 실행하여 시작하세요.</p>cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-strands-agents<p>다음 명령어를 실행하여 Python 가상 환경을 생성하세요.</p>python -m venv .venv<p>사용 중인 컴퓨터의 운영 체제에 따라 다음 명령어를 실행해 가상 환경을 활성화하세요.</p><ul><li><p>macOS/Linux</p></li></ul>source .venv/bin/activate<ul><li><p>Windows</p></li></ul>.venv\Scripts\activate<p>이 예제 앱은 Strands Agents SDK를 사용하며, 이제 SDK를 설치할 차례입니다. 다음 명령어를 실행하여 Strands Agents SDK와 필요한 모든 Python 라이브러리 종속성을 설치하세요.</p>pip install -r requirements.txt<p>이제 실행 준비를 끝내고 카운트다운을 시작할 시간입니다. 이 앱을 시작할 준비가 되었으니, 뒤로 물러나세요. 다음 명령어를 사용하여 실행해 보겠습니다.</p>python elastic_agent_builder_a2a_rps+.py<p>이제 RPS+ 게임을 즐겨보세요. 정말 수고하셨습니다. 행운을 빕니다!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbb3715672995fcfa/6a17063c6234e07b76db195f/041df81fbf1776f09e1243af0a435c4c0af6aca1-1600x948.gif" alt="" /><h2>관련성 있는 컨텍스트를 활용해 AI 앱을 구축하기</h2><p>이제 AI 에이전트를 구축할 수 있는 기술을 갖추게 되었습니다. 또한 Strands Agents SDK와 같은 에이전트 개발 프레임워크에서 A2A를 통해 Elastic Agent Builder 에이전트를 사용하는 것이 얼마나 쉬운지도 확인했습니다. <a href="https://cloud.elastic.co/registration?utm_source=agentic-ai-category&amp;utm_medium=search-labs&amp;utm_campaign=agent-builder">Elastic을 사용하여</a> 사용자 지정 데이터의 관련 컨텍스트와 연결된 AI 에이전트를 만들어 보세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-a2a-strands-agents-guide</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-a2a-strands-agents-guide</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3472edce39ec0b81/6a17060e66c4f93c17f8bf57/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Google MCP Toolbox for Databases, 새롭게 추가된 Elasticsearch 지원]]></title>
    <description><![CDATA[이제 Google MCP Toolbox for Databases에서 Elasticsearch 지원이 추가되었습니다. ES|QL 도구를 활용해 인덱스를 다양한 MCP 클라이언트와 안전하게 통합해 보세요.]]></description>
    <content:encoded><![CDATA[<p>이 글에서는 <a href="https://github.com/elastic/elasticsearch">Elasticsearch</a>와 Google MCP Toolbox를 함께 사용해 Elasticsearch 인덱스에서 정보를 추출하는 간단한 도구를 만드는 방법을 단계별로 살펴봅니다.</p><p>최근 Elastic은 <a href="https://github.com/googleapis/genai-toolbox">Google MCP Toolbox for Databases</a> 오픈소스 프로젝트에 Elasticsearch를 데이터베이스로 사용할 수 있는 지원 기능을 추가했습니다.</p><p>이 새로운 기능을 통해 Google MCP Toolbox로 Elasticsearch에 연결하고 데이터와 직접 "대화하듯" 상호작용할 수 있습니다.</p><h2>Elasticsearch</h2><p>Elasticsearch 인스턴스가 실행 중이어야 합니다. <a href="https://www.elastic.co/cloud">Elastic Cloud</a>에서 무료 체험을 활성화하거나 <a href="https://github.com/elastic/start-local">start-local</a> 스크립트를 사용해 로컬에 설치할 수 있습니다.</p>curl -fsSL https://elastic.co/start-local | sh<p>이 명령을 실행하면 컴퓨터에 Elasticsearch와 Kibana가 설치되고 Google MCP Toolbox 설정에 사용할 API 키가 생성됩니다.</p><p>API 키는 이전 명령의 출력 결과로 표시되며 elastic-start-local 폴더 내 .env 파일에 저장됩니다.</p><h2>예제 데이터 세트 설치하기</h2><p>설치가 완료되면 사용자 이름 <em>elastic</em>과 start-local 스크립트로 생성된 비밀번호(.env 파일에 저장됨)를 사용해 Kibana에 로그인할 수 있습니다.</p><p>Kibana에서 제공되는 <strong>eCommerce orders</strong> 데이터 세트를 설치할 수 있습니다. 이 데이터 세트에는 <strong>kibana_sample_data_ecommerce</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> 페이지를 열고(상단 검색창에서 "통합"을 검색) "Sample Data"를 설치하세요. 자세한 내용은 다음 문서에서 확인할 수 있습니다: <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana</a></p><p>이 글의 목표는 Google MCP Toolbox를 설정해 Elasticsearch에 연결하고 자연어로 <strong>kibana_sample_data_ecommerce</strong> 인덱스와 손쉽게 상호작용하는 방법을 보여주는 것입니다.</p><h2>Google MCP Toolbox</h2><p>Google MCP Toolbox는 애플리케이션과 AI 에이전트가 데이터베이스와 안전하고 효율적으로 상호작용할 수 있도록 설계된 오픈 소스 MCP 서버입니다. 이전에는 'GenAI Toolbox for Databases'로 알려졌으나 <a href="https://www.anthropic.com/news/model-context-protocol">Model Context Protocol</a>(MCP)을 전면 지원하게 되면서 현재의 이름으로 변경되었습니다. 이 프로젝트의 목적은 연결 풀링, 인증, 통합 가시성 등과 같은 다양한 운영 관리 요소들을 백그라운드에서 처리하여 에이전트를 데이터베이스에 연결할 때 기존에 필요하던 번거로운 작업을 줄이는 데 있습니다.</p><p>Toolbox의 핵심은 개발자가 데이터베이스 상호작용을 캡슐화한 재사용 가능한 고수준 도구를 정의할 수 있도록 한다는 점입니다. 이러한 도구는 MCP 호환 클라이언트(예: AI 에이전트)에서 호출할 수 있으며 클라이언트가 저수준 SQL 쿼리를 구현하거나 데이터베이스 연결을 직접 관리할 필요가 없습니다. 이러한 접근 방식은 데이터베이스 인식 에이전트를 구축하는 데 필요한 보일러플레이트 코드(boilerplate code)의 양을 크게 줄여 단 몇 줄의 애플리케이션 로직만으로도 고급 데이터 처리 기능을 통합할 수 있게 합니다. 한 번 정의된 도구는 여러 에이전트, 프레임워크, 언어 간에 손쉽게 공유할 수도 있습니다(그림 1).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte90070297ee83546/6a16fa29964cea694b08b972/137cea290bb70ad5da21853f9a6358cef4cf7451-1248x1056.png" alt="" /><p>Toolbox를 사용할 때의 큰 장점 중 하나는 기본으로 제공되는 보안 모델입니다. OAuth2와 OIDC 같은 인증 흐름을 기본적으로 지원하므로 개발자는 에이전트에서 민감한 데이터베이스 자격 증명을 직접 다루거나 저장하지 않아도 됩니다. 또한 이 플랫폼은 OpenTelemetry를 통해 메트릭과 트레이싱을 포함한 통합 가시성 기능을 제공하며 이는 디버깅, 모니터링 및 프로덕션 배포에 필수적입니다. 종합하면 MCP Toolbox는 MCP를 지원하는 모든 시스템에서 데이터와 상호작용하기 위한 통합되고 안전하며 확장 가능한 인터페이스 역할을 합니다.</p><h2>MCP Toolbox 설치 방법</h2><p>다음 명령어를 사용하여 Linux에서 MCP Toolbox 서버를 설치할 수 있습니다.</p>export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox<p>macOS 또는 Windows에 설치하려면 <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server">여기</a>에 있는 자세한 안내를 따르세요.</p><h2>Toolbox를 Elasticsearch용으로 구성하기</h2><p>Elasticsearch용 MCP Toolbox를 구성하려면 다음과 같이 <strong>tools.yaml</strong> 파일을 생성해야 합니다.</p>sources:
  my-cluster:
    kind: elasticsearch
    addresses:
      - http://localhost:9200
    apikey: &lt;insert-here-api-key&gt;

tools:
  customer-orders:
    kind: elasticsearch-esql
    source: my-cluster
    description: Get the orders made by a customer identified by name.
    query: |
    	FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name, {"operator": "AND"})
    parameters:
      - name: name
        type: string
        description: The customer name.

toolsets:
  elasticsearch-tools:
    - customer-orders<p><strong>&lt;insert-here-api-key&gt;</strong> 값을 유효한 Elasticsearch API 키로 교체해야 합니다. 로컬에서 start-local로 Elasticsearch를 실행하고 있는 경우 생성된 .env 파일의 <strong>ES_LOCAL_API_KEY</strong> 변수에서 API 키를 확인할 수 있습니다. Elastic Cloud를 사용하는 경우 <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">여기</a>에 설명된 절차를 따라 API 키를 생성할 수 있습니다.</p><p>이전 도구에는 Elasticsearch를 위한 다음 ES|QL 쿼리가 포함되어 있습니다:</p><p>ES|QL에 익숙하지 않다면 간단히 이렇게 이해할 수 있습니다. ES|QL은 Elastic이 개발한 쿼리 언어로 SQL과 비슷한 방식으로 하나 이상의 인덱스를 대상으로 데이터를 검색할 수 있습니다. ES|QL에 대한 더 자세한 내용은 <a href="https://www.elastic.co/docs/reference/query-languages/esql">여기</a>에서 공식 문서를 통해 확인할 수 있습니다.</p><p>위의 쿼리는 <strong>?name</strong> 매개변수(물음표는 매개변수를 의미)를 사용해 지정된 고객 이름이 포함된 모든 주문을 <strong>kibana_sample_data_ecommerce</strong> 인덱스에서 검색합니다.</p><p>고객 이름은 앞서 작성한 YAML 구성에서 string 타입과 'The customer name' 설명으로 정의되었습니다.</p><p>이 도구를 사용하면 <em>고객인 Foo는 2025년 10월에 몇 건의 주문을 했나요?</em>와 같은 고객의 주문 관련 질문에 답할 수 있습니다.</p><p>도구와 그 매개변수에 대한 설명은 사용자의 자연어 요청에서 관련 정보를 추출하는 데 매우 중요합니다. 이러한 정보 추출은 대규모 언어 모델(LLM)의 <strong>함수 호출</strong> 기능을 통해 수행됩니다. 실제로 LLM은 필요한 정보를 얻기 위해 실행해야 할 함수(도구)와 해당 함수에 필요한 매개변수를 자동으로 식별할 수 있습니다.</p><p>함수 호출에 대한 자세한 내용은 Ashish Tiwari가 작성한 <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic">OpenAI function calling with Elasticsearch(Elasticsearch와 함께 사용하는 OpenAI 함수 호출)</a>라는 글을 참고하세요.</p><h2>Toolbox 서버 실행하기</h2><p>앞서 작성한 tools.yaml 파일을 사용해 MCP Toolbox를 다음 명령어로 실행할 수 있습니다.</p>./toolbox --tools-file tools.yaml --ui<p><strong> -ui</strong> 매개변수를 사용하면 <a href="http://127.0.0.1:5000/ui">http://127.0.0.1:5000/ui</a>에서 웹 애플리케이션이 실행됩니다(그림 2).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0fb3953fae603572/6a16fa2aa6c2b92763e794fb/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" alt="" /><p><strong>Tools</strong> &gt; <strong>customer-orders</strong>를 선택한 뒤 <strong>name</strong> 매개변수에 고객 이름(예: Gwen Sanders)을 입력하고 <strong>Run Tool</strong> 버튼을 클릭할 수 있습니다. 그림 3에 표시된 것과 같은 JSON 응답이 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca02df8c78cb39ff/6a16fa2c961e6909b5c4cd22/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" alt="" /><p>이로써 설정이 완료되며 MCP Toolbox는 <strong>customer-orders</strong> 도구를 실행해 Elasticsearch와 통신하고 ES|QL 쿼리를 실행할 수 있습니다.</p><h2>Gemini CLI와 함께 MCP Toolbox 사용하기</h2><p>데이터베이스용 MCP Toolbox와의 통신에는 어떤 MCP 클라이언트든 사용할 수 있습니다. 예를 들어 Gemini를 사용하기 위한 명령줄 도구인 <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a>를 사용할 수 있습니다. Gemini CLI는 <a href="https://geminicli.com/docs/get-started/installation/">여기</a>에 안내된 지침에 따라 설치할 수 있습니다.</p><p>Gemini CLI에는 MCP Toolbox용으로 사전 구성된 확장 프로그램이 제공되며 <a href="https://github.com/gemini-cli-extensions/mcp-toolbox">gemini-cli-extensions/mcp-toolbox</a>에서 사용할 수 있습니다. 다음 명령어를 실행해 이 확장 프로그램을 설치할 수 있습니다.</p>gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox<p>설치가 완료되면 MCP Toolbox용 tools.yaml 설정 파일을 저장한 디렉터리로 이동한 뒤 다음과 같이 Gemini CLI를 실행해야 합니다. 이는 Gemini CLI가 MCP Toolbox와 자동으로 구성되기 위해 필요한 단계입니다.</p>gemini<p>그림 4에 표시된 출력 결과를 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7245c10b9e32cd6/6a16fa2d964cea073208b976/0f22df6d3da13c1dc50dcb560414fa7c630eb9a7-1434x341.png" alt="" /><p>다음 명령어로 MCP Toolbox가 연결되어 있는지 확인할 수 있습니다.</p>/mcp list<p><strong>mcp_toolbox</strong> 항목 아래에 <strong>customer-orders</strong> 도구가 나열된 것을 확인할 수 있습니다(그림 5).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte857b42dbe604203/6a16fa2f8b73cb531b189e33/97edbc40de9e44f469f6f3a09427532be167de0e-493x155.png" alt="" /><p>MCP Toolbox가 Gemini CLI에 연결되었다면 이제 ”<em>고객인 Gwen Sanders의 주문 내역을 알려주세요</em>”와 같은 질문을 해볼 수 있습니다. 그러면 Gemini CLI가 mcp_toolbox 서버에서 customer-orders 도구를 실행할 수 있는 권한을 요청합니다(그림 6 참조).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb7ea752c1a39da7/6a16fa30cdacbfdf937d27ff/c052f3b5e49436903b804280c0065f67ee02444b-1432x284.png" alt="" /><p>확인되면 Gemini CLI는 MCP Toolbox에 요청을 보내 JSON 응답을 받은 다음 이를 바탕으로 최종 응답을 구성합니다(그림 7).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9f6e04987137a9f/6a16fa320811ae2297e9fef7/7ea5128f1705951c2757af6da4b456d394d4a080-1432x734.png" alt="" /><p>Gemini CLI의 응답에는 Gwen Sanders가 2개 상품을 한 번만 주문했으며 총액은 132유로라는 내용이 표시됩니다.</p><h2>MCP Toolbox SDKs</h2><p>Google MCP Toolbox는 Go, Python, JavaScript로 작성된 프로그램에서 모든 기능에 접근할 수 있는 SDK도 제공합니다.</p><p>예를 들어 Python SDK는 다음의 GitHub 페이지에서 이용할 수 있습니다: <a href="https://github.com/googleapis/mcp-toolbox-sdk-python">https://github.com/googleapis/mcp-toolbox-sdk-python</a></p><p>MCP Toolbox에 연결하기 위해 간단한 에이전트를 만들어야 합니다. 이를 위해 다음 패키지를 설치해야 합니다.</p>pip install toolbox-core
pip install google-adk<p>그리고 아래 명령어로 새로운 에이전트 프로젝트를 생성합니다.</p>adk create my_agent<p>이 명령을 실행하면 <strong>agent.py</strong> 파일이 포함된 <strong>my_agent</strong>라는 새 디렉터리가 생성됩니다.</p><p>Toolbox에 연결하기 위해 <strong>my_agent/agent.py</strong>를 다음 내용으로 업데이트합니다.</p>from google.adk import Agent
from google.adk.apps import App
from toolbox_core import ToolboxSyncClient

client = ToolboxSyncClient("http://127.0.0.1:5000")

root_agent = Agent(
    name='root_agent',
    model='gemini-2.5-flash',
    instruction="You are a helpful AI assistant designed to search information about a dataset of ecommerce orders.",
    tools=client.load_toolset(),
)

app = App(root_agent=root_agent, name="my_agent")<p>Google API 키를 포함한 <strong>.env</strong> 파일을 생성합니다.</p>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' &gt; my_agent/.env<p>마지막으로 에이전트를 실행하여 결과를 확인할 수 있습니다. 에이전트를 실행하려면 아래 명령어를 입력합니다.</p>adk run my_agent<p>또는 웹 인터페이스로 제공할 수도 있습니다.</p>adk web --port 8000<p>두 경우 모두 Q&amp;A 인터페이스를 통해 MCP Toolbox와 상호작용할 수 있습니다. 예를 들어 이전 예시처럼 <em>고객인 Gwen Sanders의 주문 내역을 보여주세요</em>라고 질문할 수 있습니다.</p><p>다양한 SDK에 대한 자세한 내용은 <a href="https://googleapis.github.io/genai-toolbox/sdks/">이 문서 페이지</a>를 참고하세요.</p><h2>결론</h2><p>이번 글에서는 Google MCP Toolbox for Databases와 Elasticsearch를 통합하는 방법을 시연했습니다. 간단한 YAML 구성 파일만으로 ES|QL 언어를 활용해 자연어 질문을 Elasticsearch 쿼리로 변환하는 도구 세트를 정의할 수 있습니다.</p><p>또한 전자상거래 웹사이트의 주문 데이터를 포함하고 있는 kibana_sample_data_ecommerce 데이터 세트와 상호작용하는 방법을 시연했습니다. 이 설정 파일을 사용하면 MCP Toolbox 서버를 간단히 실행하고 모든 MCP 클라이언트에서 해당 서버에 연결할 수 있습니다.</p><p>마지막으로 Gemini CLI를 클라이언트로 사용해 MCP Toolbox for Databases에 연결하고 Elasticsearch에 저장된 전자상거래 데이터를 조회하는 과정을 시연했습니다. 특정 고객 이름을 기준으로 주문 정보를 조회하는 자연어 쿼리도 직접 실행해 보았습니다.</p><p>MCP 생태계가 계속 성장함에 따라 안전하고 프로덕션 환경에 바로 투입할 수 있는 인프라를 기반으로 한 경량 도구 정의 방식은 적은 노력으로도 점점 더 고도화되고 데이터에 정통한 에이전트를 구축할 수 있는 새로운 기회를 열어 줍니다. 로컬 환경에서 Elastic의 샘플 데이터 세트로 실험하는 경우나 대규모 애플리케이션에 검색 기능을 통합하는 경우에도 MCP Toolbox는 자연어를 통해 Elasticsearch 데이터와 상호작용할 수 있는 신뢰성 높고 확장 가능한 기반을 제공합니다.</p><p>에이전틱 AI 애플리케이션 개발에 대해 더 알고 싶다면 Anish Mathur와 Dana Juratoni가 작성한 <a href="https://search-labs-redesign.vercel.app/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Building AI Agentic Workflows with Elasticsearch(Elasticsearch로 AI 에이전틱 워크플로우 구축하기)</a>라는 글을 참고해 보세요.</p><p>Google MCP Toolbox 관련 추가 정보는 <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/">https://googleapis.github.io/genai-toolbox/getting-started/introduction/</a>에서 확인할 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/google-mcp-toolbox-elasticsearch-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/google-mcp-toolbox-elasticsearch-support</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Enrico Zimuel,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72d49893c51407cf/6a16fa33cf4f2502bab2cf7e/425a48691f436ed47c9bdfaf5d561ac122b2c472-1062x668.png" length="0" type="image/png"/>
    <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangGraph.js와 Elasticsearch를 사용하여 금융 AI 검색 워크플로우 구축]]></title>
    <description><![CDATA[LangGraph.js와 Elasticsearch를 사용하여 자연어 쿼리를 투자 및 시장 분석을 위한 동적인 조건부 필터로 전환하는 AI 기반 금융 검색 워크플로우를 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>AI 검색 애플리케이션을 구축할 때는 여러 작업, 데이터 검색 및 데이터 추출을 원활한 워크플로우로 조정하는 것이 중요합니다. LangGraph는 개발자가 노드 기반 구조를 통해 AI 에이전트를 오케스트레이션하여 이 프로세스를 간소화합니다. 이 포스트에서는 <a href="https://langchain-ai.github.io/langgraphjs/">LangGraph.js</a>를 사용하여 금융 솔루션을 구축해 보겠습니다.</p><h2>LangGraph란?</h2><p><a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a>는 AI 에이전트를 구축하고 워크플로우에서 이를 오케스트레이션하여 AI 지원 애플리케이션을 생성하는 프레임워크입니다. LangGraph에는 작업을 나타내는 함수를 선언하고 워크플로우의 노드로 할당할 수 있는 노드 아키텍처가 있습니다. 여러 노드가 상호작용을 한 결과는 그래프가 됩니다. LangGraph는 모듈형 및 조합 가능한 AI 시스템을 구축하기 위한 도구를 제공하는 광범위한 <a href="https://js.langchain.com/docs/introduction/">LangChain</a> 에코시스템의 일부입니다.</p><p>LangGraph가 유용한 이유를 더 잘 이해하기 위해 LangGraph를 사용하여 문제 상황을 해결해 보겠습니다.</p><h2>솔루션 개요</h2><p>벤처 캐피털 회사에서는 투자자들이 다양한 필터링 옵션을 갖춘 방대한 데이터베이스를 활용할 수 있지만, 기준을 결합하려고 하면 어렵고 시간이 오래 걸립니다. 이로 인해 일부 관련 스타트업이 투자 대상에서 제외될 수 있습니다. 결국 최적의 후보자를 찾는 데 많은 시간을 허비하거나, 심지어 기회를 놓치게 됩니다.</p><p>LangGraph와 Elasticsearch를 사용하면 자연어로 Elasticsearch 검색을 수행할 수 있어 사용자가 수십 개의 필터로 복잡한 요청을 수동으로 작성할 필요가 없습니다. 유연성을 높이기 위해 워크플로우는 사용자 입력에 따라 두 가지 쿼리 유형 중 하나를 자동으로 선택합니다.</p><ul><li><p><strong>투자 중심 쿼리</strong>: <a href="https://www.investopedia.com/articles/personal-finance/102015/series-b-c-funding-what-it-all-means-and-how-it-works.asp">펀딩 라운드</a>, 가치 평가 또는 <a href="https://www.investopedia.com/terms/r/revenue.asp">수익</a>과 같은 스타트업의 금융 및 자금 조달 측면을 대상으로 합니다. <em>예시:</em> "800만~2500만 달러의 시리즈 A 또는 시리즈 B 펀딩을 받고 월 매출이 50만 달러 이상인 스타트업을 찾아주세요."</p></li><li><p><strong>시장 중심 쿼리</strong>: 이는 <a href="https://en.wikipedia.org/wiki/Vertical_market">산업 분야</a>, <a href="https://en.wikipedia.org/wiki/Target_market">지리적 시장</a> 또는 <a href="https://www.investopedia.com/terms/b/businessmodel.asp">비즈니스 모델</a>에 집중하여 특정 부문이나 지역의 기회를 식별하는 데 도움을 줍니다. <em>예시:</em> "샌프란시스코, 뉴욕 또는 보스턴에서 핀테크 및 헬스케어 스타트업을 찾으주세요."</p></li></ul><p>쿼리를 견고하게 유지하기 위해 LLM이 <a href="https://www.elastic.co/docs/solutions/search/search-templates">검색 템플릿</a>을 작성하도록 하고, 전체 <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/querydsl">DSL 쿼리</a> 대신 사용합니다. 이렇게 하면 항상 원하는 쿼리를 얻을 수 있으며, LLM은 매번 필요한 쿼리를 작성할 필요 없이 빈칸만 채우면 됩니다.</p><h2>시작에 필요한 사항</h2><ul><li><p>Elasticsearch APIKey</p></li><li><p>OpenAPI APIKey</p></li><li><p>Node 18 이상 버전</p></li></ul><h2>단계별 지침</h2><p>이 섹션에서는 앱이 어떻게 보일지 살펴보겠습니다. 이를 위해 <a href="https://www.typescriptlang.org/">TypeScript</a>를 사용하겠습니다. TypeScript는 JavaScript의 상위 집합으로, 정적 타입을 추가하여 코드의 안정성, 유지관리 용이성, 안전성을 높이고 기존 JavaScript와의 완벽한 호환성을 유지하면서도 오류를 조기에 발견할 수 있도록 합니다.</p><p>노드의 흐름은 다음과 같습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90db8f03f372608c/6a170986dc55de6e16e00d93/b47d7f238c4964a6febc0de7fe5e68b186f539c3-363x555.png" alt="" /><p>위 이미지는 LangGraph에서 생성되었으며, 노드 간의 실행 순서와 조건부 로직을 정의하는 워크플로우를 나타냅니다.</p><ul><li><p><strong>decideStrategy: </strong>LLM을 사용하여 사용자의 쿼리를 분석하고 두 가지 전문 검색 전략, 즉 투자 중심 또는 시장 중심 중 하나를 결정합니다.</p></li><li><p><strong>prepareInvestmentSearch: </strong>쿼리에서 필터 값을 추출하고 금융 및 자금 조달 관련 매개변수를 강조하는 사전 정의된 템플릿을 작성합니다.</p></li><li><p><strong>prepareMarketSearch</strong>: 필터 값을 추출할 뿐만 아니라 시장, 산업 및 지리적 컨텍스트를 강조하는 매개변수를 동적으로 구축합니다.</p></li><li><p><strong>executeSearch: </strong>구성된 쿼리를 검색 템플릿을 사용하여 Elasticsearch로 전송하고 일치하는 스타트업 문서를 검색합니다.</p></li><li><p><strong>visualizeResults: </strong>최종 결과를 자금 조달, 산업, 수익과 같은 주요 스타트업 속성을 보여주는 명확하고 읽기 쉬운 요약 형식으로 구성합니다.</p></li></ul><p>이 흐름에는 사용자의 입력에 따라 투자 또는 시장 검색 경로를 선택하는 'if' 문 역할을 하는 <a href="https://langchain-ai.github.io/langgraphjs/how-tos/branching/?h=conditional#how-to-create-branches-for-parallel-node-execution">조건부 분기</a>가 포함되어 있습니다. LLM에 의해 구동되는 이러한 결정 로직은 워크플로우를 적응적이고 컨텍스트에 맞게 만들어줍니다. 다음 섹션에서 이 메커니즘에 대해 더 자세히 살펴보겠습니다.</p><h3>LangGraph 상태</h3><p>각 노드를 개별적으로 보기 전에 노드가 어떻게 통신하고 데이터를 공유하는지 이해해야 합니다. 이를 위해 LangGraph를 사용하여 워크플로우 상태를 정의할 수 있습니다. 이는 노드 간에 전달될 공유 상태를 정의합니다.</p><p>상태는 워크플로우 전반에 걸쳐 중간 데이터를 저장하는 공유 컨테이너 역할을 합니다. 사용자의 자연어 쿼리로 시작하여 선택한 검색 전략, Elasticsearch에 준비된 매개변수, 검색 결과, 마지막으로 형식화된 출력을 유지합니다.</p><p>이 구조는 모든 노드가 상태를 읽고 업데이트할 수 있도록 하여 사용자 입력에서 최종 시각화까지 정보의 일관된 흐름을 보장합니다.</p>const VCState = Annotation.Root({
  input: Annotation&lt;string&gt;(), // User's natural language query
  searchStrategy: Annotation&lt;string&gt;(), // Search strategy chosen by LLM
  searchParams: Annotation&lt;any&gt;(), // Prepared search parameters
  results: Annotation&lt;any[]&gt;(), // Search results
  final: Annotation&lt;string&gt;(), // Final formatted response
});<h3>애플리케이션 설정</h3><p>이 섹션의 모든 코드는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch">elasticsearch-labs 리포지토리</a>에서 확인할 수 있습니다.</p><p>앱이 위치할 폴더에서 터미널을 열고 다음 명령어로 Node.js 애플리케이션을 초기화합니다.</p>npm init -y<p>이제 이 프로젝트에 필요한 필수 종속성을 설치할 수 있습니다.</p>npm install @elastic/elasticsearch @langchain/langgraph @langchain/openai @langchain/core dotenv zod &amp;&amp; npm install --save-dev @types/node tsx typescript<ul><li><p><strong><code>@elastic/elasticsearch</code></strong>: 데이터 수집 및 검색과 같은 Elasticsearch 요청을 처리하는 데 도움이 됩니다.</p></li><li><p><strong><code>@langchain/langgraph</code></strong>: 모든 LangGraph 도구를 제공하기 위한 JS 종속성입니다.</p></li><li><p><strong><code>@langchain/openai</code></strong>: LangChain을 위한 OpenAI LLM 클라이언트.</p></li><li><p>@langchain/core: 프롬프트 템플릿 등 LangChain 앱을 위한 기본 구성 요소를 제공합니다.</p></li><li><p><strong><code>dotenv</code></strong>: JavaScript에서 환경 변수를 사용하기 위한 필수 종속성입니다.</p></li><li><p><strong><code>zod</code></strong>: 유형 데이터에 대한 종속성입니다.</p></li></ul><p><code>@types/node</code> <code>tsx</code> <code>typescript</code> TypeScript 코드를 작성하고 실행할 수 있게 해줍니다.</p><p>이제 다음 파일을 생성합니다.</p><ul><li><p><code>elasticsearchSetup</code><a href="http://ingest.ts/"><code>.ts</code></a>: 인덱스 매핑을 생성하고 JSON 파일에서 데이터 세트를 로드한 후 Elasticsearch에 데이터를 수집합니다.</p></li><li><p><a href="http://main.ts/"><code>main.ts</code></a>: LangGraph 애플리케이션을 포함합니다.</p></li><li><p><code>.env</code>: 환경 변수를 저장하기 위한 파일</p></li></ul><p><code>.env</code> 파일에 다음 환경 변수를 추가합시다:</p>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<p>OpenAPI APIKey는 코드에서 직접 사용되지 않으며, 대신 라이브러리 <code>@langchain/openai</code>에서 내부적으로 사용됩니다.</p><p>모든 매핑 생성, 검색 템플릿 생성 및 데이터 세트 수집과 관련된 로직은 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> 파일에서 확인할 수 있습니다. 다음 단계에서는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/main.ts"><code>main.ts</code></a> 파일에 초점을 맞추겠습니다. 또한 데이터 세트를 확인하여 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/dataset.json"><code>dataset.json</code></a> 에서 데이터가 어떻게 보이는지 더 잘 이해할 수 있습니다.</p><h3>LangGraph 앱</h3><p><code>main.ts</code> 파일에서 LangGraph 애플리케이션을 통합하기 위해 필요한 몇 가지 종속성을 가져오겠습니다. 이 파일에는 노드 함수와 상태 선언도 포함해야 합니다. 그래프 선언은 다음 단계에서 <code>main</code> 메서드로 진행됩니다. <code>elasticsearchSetup.ts</code> 파일에는 향후 단계에서 노드 내에서 사용할 Elasticsearch 도우미가 포함됩니다.</p>import { writeFileSync } from "node:fs";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import {
  esClient,
  ingestDocuments,
  createSearchTemplates,
  INDEX_NAME,
  INVESTMENT_FOCUSED_TEMPLATE,
  MARKET_FOCUSED_TEMPLATE,
  createIndex,
} from "./elasticsearchSetup.js";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });<p>앞서 언급한 바와 같이 LLM 클라이언트는 사용자의 질문에 따라 Elasticsearch 검색 템플릿 매개변수를 생성하는 데 사용됩니다.</p>async function saveGraphImage(app: any): Promise&lt;void&gt; {
  try {
    const drawableGraph = app.getGraph();
    const image = await drawableGraph.drawMermaidPng();
    const arrayBuffer = await image.arrayBuffer();

    const filePath = "./workflow_graph.png";
    writeFileSync(filePath, new Uint8Array(arrayBuffer));
    console.log(`📊 Workflow graph saved as: ${filePath}`);
  } catch (error: any) {
    console.log("⚠️  Could not save graph image:", error.message);
  }
}<p>해당 메서드는 그래프 이미지를 png 형식으로 생성하고 <a href="https://mermaid.ink/">Mermaid.INK API</a>를 백그라운드에서 사용합니다. 이는 앱 노드가 스타일이 지정된 시각화와 어떻게 상호작용을 하는지 확인하려는 경우에 유용합니다.</p><h3>LangGraph 노드</h3><p>이제 각 노드를 자세히 살펴보겠습니다.</p><h3>decideSearchStrategy 노드</h3><p><code>decideSearchStrategy</code> 노드는 사용자 입력을 분석하여 투자 중심 검색을 수행할지 시장 중심 검색을 수행할지 결정합니다. 이 노드는 정형 출력 스키마(Zod로 정의됨)가 있는 LLM을 사용해 쿼리 유형을 분류합니다. 결정을 내리기 전에 집계를 사용하여 인덱스에서 사용 가능한 필터를 검색하여 모델이 산업, 위치 및 자금 조달 데이터에 대한 최신 컨텍스트를 갖출 수 있도록 합니다.</p><p>필터 가능한 값을 추출하여 LLM으로 전송하기 위해 <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">집계</a> 쿼리를 사용하여 Elasticsearch 인덱스에서 직접 검색해 보겠습니다. 이 로직은 <code>getAvailableFilters</code> 이라는 메서드에 할당되어 있습니다.</p>async function getAvailableFilters() {
  try {
    const response = await esClient.search({
      index: INDEX_NAME,
      size: 0,
      aggs: {
        industries: {
          terms: { field: "industry", size: 100 },
        },
        locations: {
          terms: { field: "location", size: 100 },
        },
        funding_stages: {
          terms: { field: "funding_stage", size: 20 },
        },
        business_models: {
          terms: { field: "business_model", size: 10 },
        },
        lead_investors: {
          terms: { field: "lead_investor", size: 100 },
        },
        funding_amount_stats: {
          stats: { field: "funding_amount" },
        },
      },
    });

    return response.aggregations;
  } catch (error) {
    console.error("❌ Error getting available filters:", error);
    return {};
  }
}<p>위의 집계 쿼리로 다음과 같은 결과를 얻었습니다.</p>{
  "industries": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "logistics",
        "doc_count": 5
      },
      ...
    ]
  },
  "locations": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "San Francisco, CA",
        "doc_count": 4
      },
      {
        "key": "New York, NY",
        "doc_count": 3
      },
      ...
    ]
  },
  "funding_stages": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Series A",
        "doc_count": 8
      },
      ...
    ]
  },
  "business_models": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "B2B",
        "doc_count": 13
      },
      ...
    ]
  },
  "lead_investors": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Battery Ventures",
        "doc_count": 1
      },
      {
        "key": "Benchmark Capital",
        "doc_count": 1
      },
      ...
    ]
  },
  "funding_amount_stats": {
    "count": 20,
    "min": 4500000,
    "max": 35000000,
    "avg": 14075000,
    "sum": 281500000
  }
}<p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/responses/aggregationsResponse.json">여기</a>에서 모든 결과를 확인하세요.</p><p>두 가지 전략 모두 하이브리드 검색을 사용하여 질문의 정형적인 부분(필터)과 보다 주관적인 부분(시맨틱)을 모두 탐지합니다. 다음은 <a href="https://www.elastic.co/docs/solutions/search/search-templates">검색 템플릿</a>을 사용한 두 쿼리의 예입니다.</p>await esClient.putScript({
      id: INVESTMENT_FOCUSED_TEMPLATE,
      script: {
        lang: "mustache",
        source: `{
          "size": 5,
          "retriever": {
            "rrf": {
              "retrievers": [
                {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "semantic_field",
                        "query": "{{query_text}}"
                      }
                    }
                  }
                },
                {
                  "standard": {
                    "query": {
                      "bool": {
                        "filter": [
                          {"terms": {"funding_stage": {{#join}}{{#toJson}}funding_stage{{/toJson}}{{/join}}}},
                          {"range": {"funding_amount": {"gte": {{funding_amount_gte}}{{#funding_amount_lte}},"lte": {{funding_amount_lte}}{{/funding_amount_lte}}}}},
                          {"terms": {"lead_investor": {{#join}}{{#toJson}}lead_investor{{/toJson}}{{/join}}}},
                          {"range": {"monthly_revenue": {"gte": {{monthly_revenue_gte}}{{#monthly_revenue_lte}},"lte": {{monthly_revenue_lte}}{{/monthly_revenue_lte}}}}}
                        ]
                      }
                    }
                  }
                }
              ],
              "rank_window_size": 100,
              "rank_constant": 20
            }
          }
        }`,
      },
    });<p>자세히 설명된 쿼리는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts#L119"><code>elasticsearchSetup.ts</code></a> 파일에서 확인하세요. 다음 노드에서는 두 쿼리 중 어떤 쿼리를 사용할지 결정합니다.</p>// Node 1: Decide search strategy using LLM
async function decideSearchStrategy(state: typeof VCState.State) {
  // Zod schema for specialized search strategy decision
  const SearchDecisionSchema = z.object({
    search_type: z
      .enum(["investment_focused", "market_focused"])
      .describe("Type of specialized search strategy to use"),
    reasoning: z
      .string()
      .describe("Brief explanation of why this search strategy was chosen"),
  });

  const decisionLLM = llm.withStructuredOutput(SearchDecisionSchema);

  // Get dynamic filters from Elasticsearch
  const availableFilters = await getAvailableFilters();

  const prompt = `Query: "${state.input}"
    Available filters: ${JSON.stringify(availableFilters, null, 2)}

    Choose between two specialized search strategies:
    
    - investment_focused: For queries about funding stages, funding amounts, monthly revenue, lead investors, financial performance
    
    - market_focused: For queries about industries, locations, business models, market segments, geographic markets
    
    Analyze the query intent and choose the most appropriate strategy.
  `;

  try {
    const result = await decisionLLM.invoke(prompt);
    console.log(
      `🤔 Search strategy: ${result.search_type} - ${result.reasoning}`
    );

    return {
      searchStrategy: result.search_type,
    };
  } catch (error: any) {
    console.error("❌ Error in decideSearchStrategy:", error.message);
    return {
      searchStrategy: "investment_focused",
    };
  }
}<h3>prepareInvestmentSearch 및 prepareMarketSearch 노드</h3><p>두 노드 모두 <code>extractFilterValues</code>이라는 공유 도우미 함수를 사용하는데, 이 함수는 LLM을 활용하여 사용자 입력에 언급된 관련 필터(산업, 위치, 자금 조달 단계, 비즈니스 모델 등)를 식별합니다. 이 스키마를 사용하여 <a href="https://www.elastic.co/docs/solutions/search/search-templates">검색 템플릿</a>을 작성하고 있습니다.</p>// Extract all possible filter values from user input
async function extractFilterValues(input: string) {
  const FilterValuesSchema = z.object({
    // Investment-focused filters
    funding_stage: z
      .array(z.string())
      .default([])
      .describe("Funding stage values mentioned in query"),
    funding_amount_gte: z
      .number()
      .default(0)
      .describe("Minimum funding amount in USD"),
    funding_amount_lte: z
      .number()
      .default(100000000)
      .describe("Maximum funding amount in USD"),
    lead_investor: z
      .array(z.string())
      .default([])
      .describe("Lead investor values mentioned in query"),
    monthly_revenue_gte: z
      .number()
      .default(0)
      .describe("Minimum monthly revenue in USD"),
    monthly_revenue_lte: z
      .number()
      .default(10000000)
      .describe("Maximum monthly revenue in USD"),
    industry: z
      .array(z.string())
      .default([])
      .describe("Industry values mentioned in query"),
    location: z
      .array(z.string())
      .default([])
      .describe("Location values mentioned in query"),
    business_model: z
      .array(z.string())
      .default([])
      .describe("Business model values mentioned in query"),
  });

  const extractorLLM = llm.withStructuredOutput(FilterValuesSchema);
  const availableFilters = await getAvailableFilters();

  const extractPrompt = `Extract ALL relevant filter values from: "${input}"
    Available options: ${JSON.stringify(availableFilters, null, 2)}
    Extract only values explicitly mentioned in the query. Leave fields empty if not mentioned.`;

  return await extractorLLM.invoke(extractPrompt);
}<p>탐지된 의도에 따라 워크플로우는 다음 두 가지 경로 중 하나를 선택합니다.</p><p><strong>prepareInvestmentSearch:</strong> 자금 조달 단계, 자금 조달 금액, 투자자 및 갱신 정보 등 금융 지향적 검색 매개변수를 구축합니다. 전체 쿼리 템플릿은 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> 파일에서 확인할 수 있습니다.</p>// Node 2A: Prepare Investment-Focused Search Parameters 
async function prepareInvestmentSearch(state: typeof VCState.State) {
  console.log(
    "💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: INVESTMENT_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing investment-focused params:", error);
    return {
      searchParams: {},
    };
  }
}<p><strong>prepareMarketSearch:</strong> 산업, 지역, 비즈니스 모델에 초점을 맞춘 시장 기반 매개변수를 생성합니다. 전체 쿼리는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> 에서 확인하세요.</p>// Node 2B: Prepare Market-Focused Search Parameters
async function prepareMarketSearch(state: typeof VCState.State) {
  console.log(
    "🔍 Preparing MARKET-FOCUSED search parameters with market emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: MARKET_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing market-focused params:", error);
    return {};
  }
}<h3>executeSearch 노드</h3><p>이 node는 상태에서 생성된 검색 매개변수를 가져와 먼저 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-render-search-template">_render API</a> 를 사용하여 디버깅 목적으로 쿼리를 시각화한 후, 결과를 검색하기 위해 Elasticsearch로 요청을 보냅니다.</p>// Node 3: Execute Search
async function executeSearch(state: typeof VCState.State) {
  const { searchParams } = state;

  try {
    // getting formed query from template for debugging
    const renderedTemplate = await esClient.renderSearchTemplate({
      id: searchParams.template_id,
      params: searchParams,
    });

    console.log(
      "📋 Complete query:",
      JSON.stringify(renderedTemplate.template_output, null, 2)
    );

    const results = await esClient.searchTemplate({
      index: INDEX_NAME,
      id: searchParams.template_id,
      params: searchParams,
    });

    return {
      results: results.hits.hits.map((hit: any) =&gt; hit._source),
    };
  } catch (error: any) {
    console.error(`❌ ${state.searchParams.search_type} search error:`, error);
    return { results: [] };
  }
}<h3>visualizeResults 노드</h3><p>마지막으로, 이 node는 Elasticsearch 결과를 표시합니다.</p>// Node 4: Visualize results
async function visualizeResults(state: typeof VCState.State) {
  const results = state.results || [];

  let formattedResults = `🎯 Found ${results.length} startups matching your criteria:\n\n`;

  results.forEach((startup: any, index: number) =&gt; {
    formattedResults += `${index + 1}. **${startup.company_name}**\n`;
    formattedResults += `   📍 ${startup.location} | 🏢 ${startup.industry} | 💼 ${startup.business_model}\n`;
    formattedResults += `   💰 ${startup.funding_stage} - $${(
      startup.funding_amount / 1000000
    ).toFixed(1)}M\n`;
    formattedResults += `   👥 ${startup.employee_count} employees | 📈 $${(
      startup.monthly_revenue / 1000
    ).toFixed(0)}K MRR\n`;
    formattedResults += `   🏦 Lead: ${startup.lead_investor}\n`;
    formattedResults += `   📝 ${startup.description}\n\n`;
  });

  return {
    final: formattedResults,
  };
}<p>프로그래밍 방식으로 전체 그래프는 다음과 같습니다.</p>  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) =&gt; state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -&gt; RRF template preparation
        market_focused: "prepareMarket", // If market focused -&gt; dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -&gt; execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -&gt; execute
    .addEdge("executeSearch", "visualizeResults") // Execute -&gt; visualize
    .addEdge("visualizeResults", END); // End workflow<p>보시다시피 앱이 다음에 실행할 '경로' 또는 '노드'를 결정하는 조건부 엣지가 있습니다. 이 기능은 워크플로우에서 여러 도구 중 하나를 선택하거나 인간이 개입하는 단계를 포함하는 등 분기 로직이 필요할 때 유용합니다.</p><p>LangGraph의 핵심 기능을 이해하면 다음과 같이 코드가 실행될 애플리케이션을 설정할 수 있습니다.</p><p><code>main</code> 메서드에 모든 것을 통합합니다. 여기서는 변수 워크플로우 아래의 모든 요소를 포함하는 그래프를 선언합니다.</p>async function main() {
  await createIndex();
  await createSearchTemplates();
  await ingestDocuments();

  // Create the workflow graph with shared state
  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) =&gt; state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -&gt; RRF template preparation
        market_focused: "prepareMarket", // If market focused -&gt; dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -&gt; execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -&gt; execute
    .addEdge("executeSearch", "visualizeResults") // Execute -&gt; visualize
    .addEdge("visualizeResults", END); // End workflow


  const app = workflow.compile();

  await saveGraphImage(app);

  const query =
    "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K";

  const marketResult = await app.invoke({ input: query });
  console.log(marketResult.final);
}<p>쿼리 변수는 가상의 검색창에 입력된 사용자 입력을 시뮬레이션합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba7189d5f4e63403/6a1709880e2e49cc3041a076/e8d76909eb2bc1bb62f3ca9a8b3e4b85fcec2893-1600x164.png" alt="" /><p>"800만~2500만 달러의 시리즈 A 또는 시리즈 B 펀딩을 받고 월 매출이 50만 달러 이상인 스타트업을 찾아주세요"라는 자연어 문구에서 모든 필터가 추출됩니다.</p><p>마지막으로 다음과 같이 메인 메서드를 호출합니다.</p>main().catch(console.error);<h3>결과</h3>🔍 Checking if index exists...
🏗️ Creating index...
✅ Index created successfully!
Ingesting documents...
✅ Documents ingested successfully!
✅ Investment-focused template created successfully!
✅ Market-focused template created successfully!

📊 Workflow graph saved as: ./workflow_graph.png

🔍 Query: "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"

🤔 Search strategy: investment_focused - The query specifically seeks profitable fintech startups with defined funding amounts and high monthly revenue, which aligns closely with financial performance metrics and investment-related criteria.

💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "funding_stage": [
                        "Series A",
                        "Series B"
                      ]
                    }
                  },
                  {
                    "range": {
                      "funding_amount": {
                        "gte": 8000000,
                        "lte": 25000000
                      }
                    }
                  },
                  {
                    "terms": {
                      "lead_investor": []
                    }
                  },
                  {
                    "range": {
                      "monthly_revenue": {
                        "gte": 500000,
                        "lte": 0
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 100,
      "rank_constant": 20
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **TechFlow**
   📍 San Francisco, CA | 🏢 logistics | 💼 B2B
   💰 Series A - $8.0M
   👥 45 employees | 📈 $500K MRR
   🏦 Lead: Sequoia Capital
   📝 TechFlow optimizes supply chain operations using AI-powered route optimization and real-time tracking. Founded in 2023, shows remarkable growth with $500K monthly revenue.

2. **DataViz**
   📍 New York, NY | 🏢 enterprise software | 💼 B2B
   💰 Series A - $10.0M
   👥 42 employees | 📈 $450K MRR
   🏦 Lead: Battery Ventures
   📝 DataViz creates intuitive data visualization tools for enterprise customers. No-code platform allows business users to create dashboards without technical expertise.

3. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

4. **UrbanMobility**
   📍 New York, NY | 🏢 logistics | 💼 B2B2C
   💰 Series B - $15.0M
   👥 78 employees | 📈 $750K MRR
   🏦 Lead: Kleiner Perkins
   📝 UrbanMobility revolutionizes urban transportation through autonomous delivery drones and smart logistics hubs. Partners with major retailers for same-day delivery across Manhattan and Brooklyn.

5. **HealthTech Solutions**
   📍 Boston, MA | 🏢 healthcare | 💼 B2B
   💰 Series B - $18.0M
   👥 95 employees | 📈 $900K MRR
   🏦 Lead: General Catalyst
   📝 HealthTech Solutions develops medical devices and software for remote patient monitoring. Comprehensive telehealth platform reducing hospital readmissions by 30%.

✨  Done in 18.80s.<p>전송된 입력에 대해 애플리케이션은 <strong>투자 중심</strong> 경로를 선택합니다. 그 결과, LangGraph 워크플로우에서 생성된 Elasticsearch 쿼리를 확인할 수 있으며, 이 쿼리는 사용자 입력에서 값과 범위를 추출합니다. 추출된 값이 적용된 쿼리가 Elasticsearch로 전송된 것을 볼 수 있으며, 마지막으로 <code>visualizeResults</code> 노드에 의해 형식이 지정된 결과를 확인할 수 있습니다.</p><p>이제 “샌프란시스코, 뉴욕 또는 보스턴에서 핀테크 및 헬스케어 스타트업을 찾아주세요”라는 쿼리를 사용하여 <strong>시장 중심</strong> 노드를 테스트해 보겠습니다.</p>...

🔍 Query: Find fintech and healthcare startups in San Francisco, New York, or Boston

🤔 Search strategy: market_focused - The query is focused on finding fintech startups in San Francisco that are disrupting traditional banking and payment systems, which pertains to specific industries (fintech) and locations (San Francisco). Thus, a market-focused strategy is more appropriate.

🔍 Preparing MARKET-FOCUSED search parameters with market emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find fintech and healthcare startups in San Francisco, New York, or Boston"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "industry": [
                        "fintech",
                        "healthcare"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "location": [
                        "San Francisco, CA",
                        "New York, NY",
                        "Boston, MA"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "business_model": []
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 50,
      "rank_constant": 10
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

2. **CryptoWallet**
   📍 Miami, FL | 🏢 fintech | 💼 B2C
   💰 Series B - $16.0M
   👥 73 employees | 📈 $820K MRR
   🏦 Lead: Coinbase Ventures
   📝 CryptoWallet provides secure digital wallet solutions for cryptocurrency trading and storage. Multi-chain support with enterprise-grade security features.

...

✨  Done in 7.41s.<h2>학습</h2><p>글을 쓰는 과정에서 배운 점은 다음과 같습니다.</p><ul><li><p>LLM에 필터의 정확한 값을 표시해야 합니다. 그렇지 않으면 사용자가 정확한 값을 입력해야 합니다. 카디널리티가 낮을 때는 이 방법이 괜찮지만, 카디널리티가 높을 때는 결과를 필터링할 메커니즘이 필요합니다.</p></li><li><p>검색 템플릿을 사용하면 LLM이 Elasticsearch 쿼리를 작성하게 하는 것보다 훨씬 더 일관되고 더 빠른 결과를 얻을 수 있습니다.</p></li><li><p>조건부 엣지는 여러 변형과 분기 경로를 갖춘 애플리케이션을 구축하는 강력한 메커니즘입니다.</p></li><li><p>정형 출력은 예측 가능하고 형식에 안전한 응답을 적용하므로 LLM으로 정보를 생성할 때 매우 유용합니다. 이렇게 하면 안정성이 향상되고 즉각적인 오해를 줄일 수 있습니다.</p></li></ul><p>하이브리드 검색을 통해 시맨틱 및 정형 검색을 결합하면 정확도와 컨텍스트 이해의 균형을 유지하면서 더 정확하고 관련성 높은 결과를 얻을 수 있습니다.</p><h2>결론</h2><p>이 예시에서는 LangGraph.js를 Elasticsearch와 결합하여 자연어 쿼리를 해석하고 금융 또는 시장 중심의 검색 전략을 선택할 수 있는 동적 워크플로우를 만듭니다. 이러한 접근 방식은 수동 쿼리의 복잡성을 줄이고 벤처 캐피털 분석가의 유연성과 정확성을 향상합니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-workflow-finance-langgraph-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-workflow-finance-langgraph-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt013eba5d152f11f3/6a1709892b835f6784f4b1a6/12b6057d84c6356267cd178a3c6c1a5c61123ece-2000x1256.png" length="0" type="image/png"/>
    <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch로 ChatGPT 커넥터를 구축해 GitHub 문제 쿼리하기]]></title>
    <description><![CDATA[사용자 정의 ChatGPT 커넥터를 구축하고, 하이브리드 검색을 활용해 내부 GitHub 문제를 쿼리하는 Elasticsearch MCP 서버를 배포하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>최근 OpenAI는 프로/비즈니스/엔터프라이즈 및 에듀 요금제에서 ChatGPT를 위한 <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">사용자 정의 커넥터</a> 기능을 발표했습니다. Gmail, GitHub, 드롭박스 등에서 데이터를 활용하기 위한 기본 제공 커넥터 외에도 MCP 서버를 사용하여 사용자 정의 커넥터를 만들 수 있습니다.</p><p>맞춤형 커넥터를 사용하면 기존 ChatGPT 커넥터를 Elasticsearch와 같은 추가 데이터 소스와 결합하여 포괄적인 답변을 얻을 수 있습니다.</p><p>이문서에서는 내부 GitHub 문제와 풀 요청에 대한 정보를 포함하는 Elasticsearch 인덱스에 ChatGPT를 연결하는 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 서버를 구축합니다. 이 기능을 통해 Elasticsearch 데이터를 사용하여 자연어 쿼리에 응답할 수 있습니다.</p><p>Google Colab에서 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>를 사용하여 MCP 서버를 배포하고, ngrok을 통해 ChatGPT가 연결할 수 있는 공개 URL을 얻어 복잡한 인프라 설정을 간소화합니다.</p><p>MCP와 에코시스템에 대한 종합적인 개요는 <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">MCP의 현재 상태</a>를 참조하세요.</p><h2>필수 구성 요소</h2><p>시작하려면 다음이 필요합니다.</p><ul><li><p>Elasticsearch 클러스터 (8.X 이상)</p></li><li><p>인덱스에 대한 읽기 권한이 있는 Elasticsearch API 키</p></li><li><p>Google 계정 (Google Colab용)</p></li><li><p>Ngrok 계정 (무료 요금제 사용 가능)</p></li><li><p>프로/엔터프라이즈/비즈니스 또는 에듀 요금제로 등록된 ChatGPT 계정</p></li></ul><h2>ChatGPT MCP 커넥터 요구사항 이해하기</h2><p>ChatGPT MCP 커넥터를 사용하려면 <code>search</code> 와 <code>fetch</code>, 두 가지를 구현해야 합니다. 자세한 내용은 <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI 문서</a>를 참조하세요.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">검색 툴</a></h3><p>사용자 쿼리를 기반으로 Elasticsearch 인덱스에서 관련 결과 목록을 반환합니다.</p><h4>수신 내용:</h4><ul><li><p>사용자의 자연어 쿼리가 포함된 단일 스트링입니다.</p></li><li><p>예: 'Elasticsearch 마이그레이션과 관련된 문제 찾기'</p></li></ul><h4>반환값: </h4><ul><li><p>결과 객체 배열을 포함하는 <code>result</code> 키를 가진 객체입니다. 각 결과에는 다음이 포함됩니다.</p><ul><li><p><code>id</code> - 고유 문서 식별자</p></li><li><p><code>title</code> - 이슈 또는 PR 제목</p></li><li><p><code>url</code> - 문제/PR에 대한 링크</p></li></ul></li></ul><h4>구현에서:</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">가져오기 도구</a></h3><p>특정 문서의 전체 내용을 가져옵니다.</p><h4>수신 내용:</h4><ul><li><p>검색 결과에서 Elasticsearch 문서 ID를 포함하는 단일 스트링</p></li><li><p>예: 'PR-578의 세부 정보를 가져와'</p></li></ul><h4>반환값:</h4><ul><li><p>다음 항목을 포함한 완전한 문서 객체입니다.</p><ul><li><p><code>id</code> - 고유 문서 식별자</p></li><li><p><code>title</code> - 이슈 또는 PR 제목</p></li><li><p><code>text</code> - 전체 문제/PR 설명 및 세부 정보</p></li><li><p><code>url</code> - 문제/PR에 대한 링크</p></li><li><p><code>type</code> - 문서 유형(문제, pull_request)</p></li><li><p><code>status</code> - 현재 상태(오픈, 진행 중, 해결 완료)</p></li><li><p><code>priority</code> - 우선순위 수준(낮음, 중간, 높음, 긴급)</p></li><li><p><code>assignee</code> - 문제/PR 담당자</p></li><li><p><code>created_date</code> - 생성된 시점</p></li><li><p><code>resolved_date</code> - 해결된 시점(해당되는 경우)</p></li><li><p><code>labels</code> - 문서에 연결된 태그</p></li><li><p><code>related_pr</code> - 관련 풀 요청 ID</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>참고</strong>: 이 예에서는 모든 필드가 루트 레벨에 있는 플랫 구조를 사용합니다. OpenAI 요구 사항은 유연하며 중첩된 메타데이터 개체도 지원합니다.</p><h2>GitHub 이슈 및 PR 데이터 세트</h2><p>이 튜토리얼에서는 문제와 풀 요청이 포함된 내부 GitHub 데이터 집합을 사용합니다. 이는 ChatGPT를 통해 비공개 내부 데이터를 쿼리하는 시나리오입니다.</p><p>데이터 세트는 <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">여기</a>에서 찾을 수 있습니다. 그리고 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">벌크 API</a>를 사용해 데이터의 색인을 업데이트할 것입니다.</p><p>이 데이터 세트에는 다음이 포함되어 있습니다.</p><ul><li><p>설명, 상태, 우선순위 및 담당자와 관련된 문제</p></li><li><p>코드 변경, 리뷰, 배포 정보가 포함된 풀 요청</p></li><li><p>문제와 PR 간의 관계 (예: PR-578이 ISSUE-1889를 수정함)</p></li><li><p>라벨, 날짜 및 기타 메타데이터</p></li></ul><h3>인덱스 매핑</h3><p>인덱스는 <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">매핑</a>을 사용해 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>와의 하이브리드 검색을 지원합니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a>은 시맨틱 검색에 사용되며, 다른 필드는 키워드 검색을 지원합니다.</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>MCP 서버 구축</h2><p>저희 MCP 서버는 더 나은 결과를 위해 하이브리드 검색을 사용하여 시맨틱과 텍스트 매칭을 결합하는 OpenAI 사양을 따르는 두 가지 도구를 구현합니다.</p><h3>검색 툴</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>(상호 순위 융합)을 사용하여 의미론적 검색과 텍스트 매칭을 결합한 하이브리드 검색을 사용합니다.</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

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

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

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

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

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

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

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

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

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

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

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

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>주요 사항:</h3><ul><li><p><strong>문서 ID 필드로 검색:</strong> 사용자 정의 <code>id</code> 필드에서 용어 쿼리를 사용합니다.</p></li><li><p><strong>전체 문서 반환:</strong> 모든 콘텐츠가 포함된 전체 <code>text</code> 필드를 포함합니다.</p></li><li><p><strong>플랫 구조:</strong> 모든 필드가 루트 수준에 있으며, Elasticsearch의 문서 구조와 일치합니다.</p></li></ul><h2>Google Colab에 배포</h2><p>Google Colab을 사용하여 MCP 서버를 실행하고 ngrok을 사용해 외부에 공개함으로써 ChatGPT가 연결할 수 있도록 합니다.</p><h3>1단계: Google Colab 노트북 열기</h3><p>사전 구성된 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP for ChatGPT</a> 노트북에 액세스합니다.</p><h3>2단계: 자격 증명을 구성하세요</h3><p>세 가지 정보를 준비해야 합니다.</p><ul><li><p><strong>Elasticsearch URL:</strong> <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">Elasticsearch 클러스터 URL</a>입니다.</p></li><li><p><strong>Elasticsearch API 키:</strong> <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">API 키</a>로 인덱스에 대한 읽기 액세스 권한을 부여합니다.</p></li><li><p><strong>Ngrok 인증 토큰:</strong> <a href="https://ngrok.com/">ngrok</a>에서 무료로 제공하는 토큰입니다. ngrok을 사용하여 MCP URL을 인터넷에 노출하여 ChatGPT가 연결할 수 있도록 합니다.</p></li></ul><h4>ngrok 토큰 받기</h4><ol><li><p><a href="https://ngrok.com/">ngrok</a>에서 무료 계정 가입</p></li><li><p><a href="https://dashboard.ngrok.com/">ngrok 대시보드</a>로 이동</p></li><li><p>인증 토큰을 복사하세요</p></li></ol><h4>Google Colab에 시크릿 추가</h4><p>Google Colab 노트북에서:</p><ol><li><p>왼쪽 사이드바에서 <strong>키 아이콘</strong>을 클릭하여 <strong>시크릿</strong>을 엽니다.</p></li><li><p>이 세 가지 시크릿을 추가합니다.</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. 각 시크릿에 대한 노트북 접근 활성화</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="Google Colab에 시크릿 추가" /><h3>3단계: 노트북 실행</h3><ol><li><p><strong>런타임</strong>을 클릭한 다음 <strong>모두 실행</strong>을 클릭하여 모든 셀을 실행합니다.</p></li><li><p>서버가 시작될 때까지 기다립니다(약 30초).</p></li><li><p>공개 ngrok URL을 표시하는 출력을 찾습니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. 출력은 다음과 같이 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="Google Colab에서 노트북을 실행한 출력" /><h2>ChatGPT에 연결</h2><p>이제 MCP 서버를 ChatGPT 계정에 연결합니다.</p><ol><li><p>ChatGPT를 열고 <strong>설정</strong>으로 이동합니다.</p></li><li><p><strong>커넥터</strong>로 이동합니다.프로 계정을 사용하는 경우 커넥터에서 <a href="https://platform.openai.com/docs/guides/developer-mode">개발자 모드</a>를 사용 설정해야 합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="MPC 서버를 ChatGPT 계정에 연결하기" /><p><em>ChatGPT 엔터프라이즈 또는 비즈니스 버전을 사용하는 경우, 커넥터를 작업 환경에 게시해야 합니다.</em></p><p>3. <strong>만들기</strong>를 클릭합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="ChatGPT에 커넥터 추가" /><p><em><strong>참고</strong></em><em>: 비즈니스, 엔터프라이즈, 에듀 작업 공간에서는 작업 공간 소유자, 관리자 및 해당 설정이 활성화된 사용자(엔터프라이즈/에듀)만 사용자 정의 커넥터를 추가할 수 있습니다. 일반 회원 역할을 가진 사용자는 사용자 정의 커넥터를 직접 추가할 수 없습니다.</em></p><p><em>소유자 또는 관리자 사용자가 커넥터를 추가하고 활성화하면 작업 공간의 모든 구성원이 이를 사용할 수 있습니다.</em></p><p>4. 필요한 정보를 입력하고 <code>/sse/</code>로 끝나는 ngrok URL을 입력합니다. 'sse"' 뒤의 '/'에 주의하세요. 이것 없이는 작동하지 않습니다.</p><ul><li><p><strong>이름:</strong> Elasticsearch MCP</p></li><li><p><strong>설명: </strong>GitHub 내부 정보를 검색하고 가져오기 위한 사용자 정의 MCP입니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="Elastic MCP 커넥터 생성 " /><p>5. <strong>생성</strong> 을 눌러 사용자 정의 MCP를 저장하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="생성을 클릭하여 사용자 정의 MCP 커넥터 저장" /><p>서버가 실행 중이면 즉시 연결됩니다. Elasticsearch API 키가 서버에 구성되어 있으므로 추가 인증이 필요하지 않습니다.</p><h2>MCP 서버 테스트</h2><p>질문을 하기 전에 ChatGPT가 사용할 커넥터를 선택해야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="ChatGPT에서 사용할 커넥터 선택" /><h3>프롬프트 1: 문제 검색</h3><p>질문: '<strong>Elasticsearch 마이그레이션과 관련된 문제를 찾아'</strong>라고 요청하고 작업 도구 호출을 확인하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="ChatGPT에 'Elasticsearch 마이그레이션과 관련된 문제 찾기'를 요청하고 작업 도구 호출을 확인합니다." /><p>ChatGPT가 사용자의 쿼리를 사용하여 <code>search</code> 도구를 호출합니다. 사용 가능한 도구를 찾고 Elasticsearch 도구를 호출할 준비를 하며, 도구에 대해 조치를 취하기 전에 사용자에게 확인하는 것을 볼 수 있습니다.</p><h4>도구 호출 요청:</h4>{
  "query": "Elasticsearch migration issues"
}<h4>도구 응답:</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT는 결과를 처리하여 자연스러운 대화 형식으로 표시합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="ChatGPT가 도구 호출 요청 및 도구 호출 응답의 결과를 처리하는 방법" /><h3>비하인드 스토리</h3><h4>프롬프트: 'Elasticsearch 마이그레이션 관련 문제를 찾아'</h4><p>1. ChatGPT 호출 <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch가 하이브리드 검색 수행</p><ul><li><p><strong>시맨틱 검색</strong>은 '업그레이드' 및 '<em>버전 호환성</em>'과 같은 개념을 이해합니다.</p></li><li><p><strong>텍스트 검색</strong>은 '<em>Elasticsearch</em>' 및 '마이그레이션'과 정확히 일치하는 결과를 찾습니다.</p></li><li><p><strong>RRF</strong>는 두 가지 접근 방식의 결과를 결합하고 순위를 매깁니다.</p></li></ul><p>3. <code>id</code>, <code>title</code>와 일치하는 상위 10개의 이벤트를 반환합니다, <code>url</code></p><p>4. ChatGPT는 '<em>ISSUE-1712: Elasticsearch 7.x에서 8.x로 마이그레이션</em>'을 가장 관련성 있는 결과로 식별합니다</p><h3>프롬프트 2: 전체 세부 정보 확인</h3><p>문의: <em><strong>'ISSUE-1889의 세부 정보를 보여줘'</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT는 사용자가 특정 문제에 대한 자세한 정보를 원한다는 것을 인식하고 가져오기 도구를 호출한 후, 도구에 대해 조치를 취하기 전에 사용자에게 확인합니다." /><p>ChatGPT는 사용자가 특정 문제에 대한 자세한 정보를 원한다는 것을 인식하고 <code>fetch</code> 도구를 호출한 후, 도구에 대해 조치를 취하기 전에 사용자에게 확인합니다.</p><h4>도구 호출 요청:</h4>{
  "id": "ISSUE-1889"
}<h4>도구 응답:</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT는 정보를 종합하여 명확하게 제시합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="ChatGPT가 정보를 종합하고 제시하는 방법 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="ChatGPT가 정보를 제시하는 방법" /><h3>비하인드 스토리</h3><h4>프롬프트: 'ISSUE-1889의 세부 정보를 가져와'</h4><ol><li><p>ChatGPT 호출 <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch가 전체 문서를 검색합니다.</p></li><li><p>모든 필드가 루트 수준에 포함된 전체 문서를 반환합니다.</p></li><li><p>ChatGPT는 정보를 종합하고 적절한 출처를 제시하며 응답합니다.</p></li></ol><h2>결론</h2><p>이 문서에서는 전용 <strong>검색</strong> 및 <strong>가져오기</strong> MCP 도구를 사용하여 ChatGPT를 Elasticsearch에 연결하고, 비공개 데이터에 대한 자연어 쿼리를 가능하게 하는 사용자 정의 MCP 서버를 구축했습니다.</p><p>이 MCP 패턴은 자연어로 쿼리하려는 모든 Elasticsearch 인덱스, 문서, 제품, 로그 또는 기타 데이터에 적용할 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangChain과 Elasticsearch를 사용하여 에이전트 RAG 어시스턴트 개발하기]]></title>
    <description><![CDATA[적응형 라우팅을 통해 기사에 대한 쿼리에 답변하는 LangChain과 Elasticsearch를 사용하여 에이전트 래그 뉴스 어시스턴트를 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 블로그 게시물에서는 에이전트 RAG 워크플로우의 주요 기능과 일반적인 디자인 패턴에 대해 자세히 설명합니다. 또한 Elasticsearch를 벡터 저장소로 사용하고 LangChain을 사용하여 에이전트 RAG 프레임워크를 구성하는 실습 예제를 통해 이러한 워크플로우를 구현하는 방법을 보여줍니다. 마지막으로 이러한 아키텍처의 설계 및 구현과 관련된 모범 사례와 과제에 대해 간략하게 설명합니다. 이 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Jupyter 노트북을</a> 사용하여 간단한 에이전트 RAG 파이프라인을 만들 수 있습니다.</p><h2>에이전트 RAG 소개</h2><p>검색 증강 생성<a href="https://www.elastic.co/docs/solutions/search/rag">(RAG)</a>은 LLM 기반 애플리케이션의 초석이 되어 모델이 사용자 쿼리를 기반으로 관련 컨텍스트를 검색하여 최적의 답변을 제공할 수 있게 해줍니다. RAG 시스템은 사전 학습된 LLM 지식에 국한되지 않고 API 또는 데이터 저장소의 외부 정보를 활용하여 LLM 응답의 정확성과 컨텍스트를 향상시킵니다. 반면에 AI 에이전트는 자율적으로 작동하여 지정된 목표를 달성하기 위해 의사 결정을 내리고 조치를 취합니다.</p><p>에이전트 RAG는 검색 증강 생성과 에이전트 추론의 강점을 통합한 프레임워크입니다. RAG를 상담원의 의사 결정 프로세스에 통합하여 시스템이 동적으로 데이터 소스를 선택하고, 더 나은 컨텍스트 검색을 위해 쿼리를 구체화하고, 더 정확한 응답을 생성하고, 피드백 루프를 적용하여 출력 품질을 지속적으로 개선할 수 있도록 지원합니다.</p><h2>에이전트 RAG의 주요 기능</h2><p>에이전트 RAG 프레임워크는 기존 RAG 시스템보다 크게 발전한 것입니다. 고정된 검색 프로세스를 따르는 대신 실시간으로 결과를 계획, 실행 및 최적화할 수 있는 동적 에이전트를 활용합니다.</p><p>에이전트 RAG 파이프라인을 구별하는 몇 가지 주요 기능을 살펴보겠습니다:</p><ul><li><p><strong>동적 의사 결정</strong>: 에이전트 RAG는 추론 메커니즘을 사용하여 사용자의 의도를 파악하고 각 쿼리를 가장 관련성이 높은 데이터 소스로 라우팅하여 정확하고 맥락에 맞는 응답을 생성합니다.</p></li><li><p><strong>종합적인 쿼리 분석:</strong> 에이전틱 RAG는 하위 질문과 전반적인 의도를 포함하여 사용자 쿼리를 심층적으로 분석합니다. 쿼리 복잡성을 평가하고 가장 관련성이 높은 데이터 소스를 동적으로 선택하여 정보를 검색함으로써 정확하고 완전한 응답을 보장합니다.</p></li><li><p><strong>다단계 협업</strong>: 이 프레임워크는 전문 에이전트 네트워크를 통해 다단계 협업을 가능하게 합니다. 각 에이전트는 더 큰 목표의 특정 부분을 처리하며, 일관된 결과를 달성하기 위해 순차적으로 또는 동시에 작업합니다.</p></li><li><p><strong>자체 평가 메커니즘</strong>: 에이전트 RAG 파이프라인은 자체 반영을 사용하여 검색된 문서와 생성된 응답을 평가합니다. 검색된 정보가 쿼리에 완전히 부합하는지 확인한 다음 출력물의 정확성, 완전성, 사실적 일관성을 검토할 수 있습니다.</p></li><li><p><strong>외부 도구와의 통합</strong>: 이 워크플로는 외부 API, 데이터베이스 및 실시간 정보 소스와 상호 작용하여 최신 정보를 통합하고 진화하는 데이터에 동적으로 적응할 수 있습니다.</p></li></ul><h2>에이전트 RAG의 워크플로 패턴</h2><p>워크플로 패턴은 에이전트 AI가 안정적이고 효율적인 방식으로 LLM 기반 애플리케이션을 구성, 관리 및 오케스트레이션하는 방법을 정의합니다. 이러한 에이전트 워크플로우를 구현하기 위해 <a href="https://www.langchain.com/">LangChain</a>, <a href="https://www.langchain.com/langgraph">LangGraph</a>, <a href="https://www.crewai.com/">CrewAI</a>, <a href="https://www.llamaindex.ai/">LlamaIndex와</a> 같은 여러 프레임워크와 플랫폼을 사용할 수 있습니다.</p><ol><li><p><strong>순차적 검색 체인</strong>: 순차적 워크플로는 복잡한 작업을 단순하고 정돈된 단계로 나눕니다. 각 단계는 다음 단계의 입력을 개선하여 더 나은 결과로 이어집니다. 예를 들어 고객 프로필을 만들 때 한 상담원이 CRM에서 기본 세부 정보를 가져오고, 다른 상담원이 거래 데이터베이스에서 구매 내역을 검색한 다음, 최종 상담원이 이 정보를 결합하여 추천 또는 보고서를 위한 완전한 프로필을 생성할 수 있습니다.</p></li><li><p><strong>라우팅 검색 체인</strong>: 이 워크플로 패턴에서는 라우터 에이전트가 입력을 분석하여 가장 적합한 프로세스나 데이터 소스로 라우팅합니다. 이 접근 방식은 겹치는 부분이 최소화된 여러 데이터 원본이 존재하는 경우에 특히 효과적입니다. 예를 들어 고객 서비스 시스템에서 라우터 상담원은 기술 문제, 환불, 불만 등 들어오는 요청을 분류한 후 해당 부서로 라우팅하여 효율적으로 처리합니다.</p></li><li><p><strong>병렬 검색 체인</strong>: 이 워크플로 패턴에서는 여러 개의 독립적인 하위 작업이 동시에 실행되고 나중에 그 결과물을 집계하여 최종 응답을 생성합니다. 이 접근 방식은 처리 시간을 크게 단축하고 워크플로 효율성을 높입니다. 예를 들어 고객 서비스 병렬 워크플로우에서 한 상담원은 유사한 과거 요청을 검색하고 다른 상담원은 관련 지식창고 문서를 참조합니다. 그런 다음 애그리게이터는 이러한 출력을 결합하여 종합적인 해상도를 생성합니다.</p></li><li><p><strong>오케스트레이터 워커 체인</strong>: 이 워크플로는 독립적인 하위 작업을 활용한다는 점에서 병렬화와 유사점을 공유합니다. 그러나 중요한 차이점은 오케스트레이터 에이전트의 통합에 있습니다. 이 에이전트는 사용자 쿼리를 분석하여 런타임 중에 하위 작업으로 동적으로 분류하고 정확한 응답을 작성하는 데 필요한 적절한 프로세스 또는 도구를 식별하는 역할을 담당합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1e2e634cf9c94e25/6a17ff81b1e113c9fc79f4e0/ece6fc2403f211556c93e99d5227bfb7053b0c31-1600x1047.png" alt="에이전트 RAG의 워크플로 패턴" /><h2>에이전트 RAG 파이프라인을 처음부터 구축하기</h2><p>에이전트 RAG의 원리를 설명하기 위해 LangChain과 Elasticsearch를 사용해 워크플로우를 설계해 보겠습니다. 이 워크플로에서는 여러 상담원이 협업하여 쿼리를 분석하고 관련 정보를 검색하며 결과를 평가하고 일관된 응답을 생성하는 라우팅 기반 아키텍처를 채택하고 있습니다. 이 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Jupyter 노트북을</a> 참조하여 이 예제를 따라할 수 있습니다.</p><p>워크플로는 라우터 에이전트가 사용자의 쿼리를 분석하여 최적의 검색 방법, 즉 <code>vectorstore</code>, <code>websearch</code>, <code>composite</code> 중 하나를 선택하는 것으로 시작됩니다. 벡터스토어는 기존의 RAG 기반 문서 검색을 처리하고, 웹검색은 벡터스토어에 저장되지 않은 최신 정보를 가져오며, 여러 소스의 정보가 필요한 경우 이 두 가지를 결합하는 복합적인 접근 방식을 사용합니다.</p><p>문서가 적합하다고 판단되면 요약 에이전트가 명확하고 문맥에 맞는 답변을 생성합니다. 그러나 문서가 불충분하거나 관련성이 없는 경우 쿼리 재작성 에이전트가 검색을 개선하기 위해 쿼리를 다시 작성합니다. 그러면 수정된 쿼리가 라우팅 프로세스를 다시 시작하여 시스템이 검색을 개선하고 최종 결과를 향상시킬 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16020333cf6dda91/6a17ff82e8fbceb4d83a1c00/ed8701a7f15558fbf2e967a884b3e770eccb826b-1256x1092.png" alt="에이전트 시스템이 다양한 쿼리로 출력을 구체화하는 방법" /><h3>필수 구성 요소</h3><p>이 워크플로에서는 예제를 효과적으로 실행하기 위해 다음과 같은 핵심 구성 요소를 사용합니다:</p><ul><li><p>Python 3.10</p></li><li><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">주피터 노트북</a></p></li><li><p>Azure OpenAI</p></li><li><p>Elasticsearch</p></li><li><p>LangChain</p></li></ul><p>계속 진행하기 전에 이 예제에 필요한 다음 환경 변수 집합을 구성하라는 메시지가 표시됩니다.</p>AZURE_OPENAI_ENDPOINT="Add your azure openai endpoint"
AZURE_OPENAI_KEY="Add your azure openai key"
AZURE_OPENAI_DEPLOYMENT="gpt-4.1"
AZURE_OPENAI_API_VERSION="Add your azure openai api version"

ES_ENDPOINT = "Add your Elasticsearch ENDPOINT"
ES_API_KEY = "Add your Elasticsearch API KEY"<h3>데이터 소스</h3><p>이 워크플로는 AG 뉴스 데이터 세트의 하위 집합을 사용하여 설명합니다. 이 데이터 세트는 국제, 스포츠, 비즈니스, 과학/기술 등 다양한 카테고리의 뉴스 기사로 구성되어 있습니다.</p>dataset = load_dataset("ag_news", split="train[:1000]")
docs = [
    Document(
        page_content=sample["text"],
        metadata={"category": sample["label"]}
    )
    for sample in dataset
]<p><a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">ElasticsearchStore 모듈은</a> <code>langchain_elasticsearch</code> 에서 벡터 저장소로 활용됩니다. 검색을 위해 Elastic의 독점적인 임베딩 모델인 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER를</a> 사용하는 SparseVectorStrategy를 구현합니다. 벡터 저장소를 시작하기 전에 ELSER 모델이 Elasticsearch 환경에 올바르게 설치 및 배포되었는지 확인해야 합니다.</p>elastic_vectorstore = ElasticsearchStore.from_documents(
    docs,
    es_url=ES_ENDPOINT,
    es_api_key=ES_API_KEY,
    index_name=index_name,
    strategy=SparseVectorStrategy(model_id=".elser_model_2"),
)

elastic_vectorstore.client.indices.refresh(index=index_name)<p>웹 검색 기능은 LangChain 커뮤니티 도구의 <a href="https://python.langchain.com/api_reference/community/tools/langchain_community.tools.ddg_search.tool.DuckDuckGoSearchRun.html">DuckDuckGoSearchRun을</a> 사용하여 구현되어 시스템이 웹에서 실시간 정보를 효율적으로 검색할 수 있습니다. 보다 관련성 높은 결과를 제공할 수 있는 다른 검색 API를 사용하는 것도 고려해 볼 수 있습니다. 이 도구는 API 키 없이도 검색이 가능하기 때문에 선택되었습니다.</p>duckduckgo = DuckDuckGoSearchRun(description= "A custom DuckDuckGo search tool for finding latest news stories.", verbose=True)
def websearch_retriever(query):
    results = duckduckgo.run(f"{query}")
    return results<p>복합 검색기는 여러 소스를 조합해야 하는 쿼리를 위해 설계되었습니다. 웹에서 실시간 데이터를 검색하는 동시에 벡터 스토어에서 과거 뉴스를 참조하여 포괄적이고 맥락에 맞는 정확한 응답을 제공하는 데 사용됩니다.</p>def composite_retriever(query):
    related_docs = vectorstore_retriever(query)
    related_docs += websearch_retriever(query)
    return related_docs<h3>상담원 설정하기</h3><p>다음 단계에서는 이 워크플로 내에서 추론 및 의사 결정 기능을 제공하도록 LLM 에이전트를 정의합니다. 우리가 만들 LLM 체인에는 다음이 포함됩니다: <code>router_chain</code>, <code>grade_docs_chain</code>, <code>rewrite_query_chain</code>, 그리고 <code>summary_chain</code>.</p><p>라우터 에이전트는 LLM 어시스턴트를 사용하여 런타임에 주어진 쿼리에 가장 적합한 데이터 소스를 결정합니다. 채점 에이전트는 검색된 문서의 관련성을 평가합니다. 문서가 관련성이 있다고 판단되면 요약 에이전트로 전달되어 요약을 생성합니다. 그렇지 않으면 쿼리 재작성 에이전트가 쿼리를 재구성하여 라우팅 프로세스로 다시 보내 다른 검색을 시도합니다. <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">노트북의</a> LLM 체인 섹션에서 모든 에이전트에 대한 지침을 찾을 수 있습니다.</p>class RouteQuery(BaseModel):
    datasource: Literal["vectorstore", "websearch", "composite"] = Field(
        ...,
        description="Choose to route the query to web search, vectorstore or composite."
    )

router_prompt = ChatPromptTemplate.from_template("""You are an assistant that decides the best data source for questions based on news articles.
Choose one of the following options:
- 'vectorstore': for general, background, or historical news articles.
- 'websearch': for recent discoveries, 'latest', 'current', or '2025' type queries.
- 'composite': when the question needs both historical and current knowledge on news articles.

Question: {query}

Return one word: 'vectorstore', 'websearch', or 'composite'.
""")
router_structured = llm.with_structured_output(RouteQuery)
router_chain: RunnableSequence = router_prompt | router_structured<p><code>llm.with_structured_output</code> 은 모델의 출력이 <code>RouteQuery</code> 클래스의 BaseModel에 정의된 사전 정의된 스키마를 따르도록 제한하여 결과의 일관성을 보장합니다. 두 번째 줄은 <code>router_prompt</code> 과 <code>router_structured</code> 을 연결하여 <code>RunnableSequence</code> 을 구성하여 입력 프롬프트가 언어 모델에 의해 처리되어 구조화된 스키마 준수 결과를 생성하는 파이프라인을 형성합니다.</p><h3>그래프 노드 정의</h3><p>이 부분에는 시스템의 여러 구성 요소 간에 흐르는 데이터를 나타내는 그래프의 상태를 정의하는 작업이 포함됩니다. 이러한 상태를 명확하게 지정하면 워크플로우의 각 노드가 어떤 정보에 액세스하고 업데이트할 수 있는지 알 수 있습니다.</p>class RAGState(TypedDict):
    query: str
    docs: List[Document]
    router: str
    summary: str
    self_reflection: bool
    retry_count: int = 0<p>상태가 정의되면 다음 단계는 그래프의 노드를 정의하는 것입니다. 노드는 데이터에 대한 특정 연산을 수행하는 그래프의 기능 단위와 같습니다. 파이프라인에는 7개의 서로 다른 노드가 있습니다.</p>def router(state: RAGState):
   router = router_chain.invoke({'query': state["query"]})
   logger.info(f"Router selected the datasource: {router.datasource}")
   logger.info(f"User query: {state['query']}")
   return {"router": router.datasource}

def vectorstore(state: RAGState):
   return {"docs": vectorstore_retriever(state["query"])}

def websearch(state: RAGState):
   return {"docs": websearch_retriever(state["query"])}

def composite(state: RAGState):
   return {"docs": composite_retriever(state["query"])}

def self_reflection(state: RAGState):
   evaluation = grade_docs_chain.invoke(
       {"query": state["query"], "docs": state["docs"]}
   )
   if evaluation.binary_score:
       logger.info(f"Self-reflection passed -- binary_score={evaluation.binary_score}")
   else:
       logger.info(f"Self-reflection failed -- binary_score={evaluation.binary_score}")

   return {
       "self_reflection": evaluation.binary_score,
   }

def query_rewriter(state: RAGState):
   retry_count = state.get("retry_count", 0) + 1
   new_query = rewrite_query_chain.invoke({"query": state["query"]})
   logger.info(f"Query rewritten: {new_query}, retry_count: {retry_count}")
   return {
       "query": new_query,
       "retry_count": retry_count,
   }

def summarize(state: RAGState):
   summary = summarize_chain.run(
       query=state["query"],
       docs=state["docs"],
   )
   return {"summary": summary}<p><code>query_rewriter</code> 노드는 워크플로에서 두 가지 용도로 사용됩니다. 먼저, 자체 반영 에이전트가 평가한 문서가 불충분하거나 관련성이 없다고 판단되는 경우 검색을 개선하기 위해 <code>rewrite_query_chain</code> 을 사용하여 사용자 쿼리를 재작성합니다. 둘째, 쿼리가 재작성된 횟수를 추적하는 카운터 역할을 합니다.</p><p>노드가 호출될 때마다 워크플로 상태에 저장된 <code>retry_count</code> 이 증가합니다. 이 메커니즘은 워크플로우가 무한 루프에 빠지는 것을 방지합니다. <code>retry_count</code> 이 사전 정의된 임계값을 초과하면 시스템은 오류 상태, 기본 응답 또는 사용자가 선택한 기타 사전 정의된 조건으로 폴백할 수 있습니다.</p><h3>그래프 컴파일하기</h3><p>마지막 단계는 그래프의 가장자리를 정의하고 필요한 조건을 추가한 후 컴파일하는 것입니다. 모든 그래프는 워크플로우의 시작점 역할을 하는 지정된 시작 노드에서 시작해야 합니다. 그래프의 에지는 노드 간의 데이터 흐름을 나타내며 두 가지 유형이 있을 수 있습니다:</p><ul><li><p>직선 가장자리: 한 노드에서 다른 노드로의 직접적이고 무조건적인 흐름을 정의합니다. 첫 번째 노드가 작업을 완료할 때마다 워크플로는 직선을 따라 다음 노드로 자동 진행됩니다.</p></li><li><p>조건부 에지: 현재 상태 또는 노드의 계산 결과에 따라 워크플로를 분기할 수 있습니다. 다음 노드는 평가 결과, 라우팅 결정 또는 재시도 횟수 등의 조건에 따라 동적으로 선택됩니다.</p></li></ul>graph.add_edge(START, "router")

def after_router(state: RAGState):
   route = state.get("router", None)
   if route == "vectorstore":
       return "vectorstore"
   elif route == "websearch":
       return "websearch"
   else:
       return "composite"

def after_self_reflection(state: RAGState):
   if state["self_reflection"]:
           return "summarize"
   return "query_rewriter"

def after_query_rewriter(state: RAGState):
   while state['retry_count'] &lt;= 3:
           return "router"
   raise RuntimeError("Maximum retries (3) reached -- evaluation failed.")

graph.add_conditional_edges(
   "router",
   after_router,
   {
       "vectorstore": "vectorstore",
       "websearch": "websearch",
       "composite": "composite"
   }
)

graph.add_edge("vectorstore", "self_reflection")
graph.add_edge("websearch", "self_reflection")
graph.add_edge("composite", "self_reflection")
graph.add_conditional_edges(
   "self_reflection",
   after_self_reflection,
   {
       "summarize": "summarize",
       "query_rewriter": "query_rewriter"
   }
)
graph.add_conditional_edges("query_rewriter", after_query_rewriter, {"router": "router"})
graph.add_edge("summarize", END)
agent=graph.compile()<p>이제 첫 번째 에이전트 RAG 파이프라인이 준비되었으며 컴파일된 에이전트를 사용하여 테스트할 수 있습니다.</p>result = agent.invoke({"query": query1})
logger.info(f"\nFinal Summary:\n: {result['summary']}")<h3>에이전트 RAG 파이프라인 테스트</h3><p>이제 아래와 같이 세 가지 유형의 쿼리를 사용하여 이 파이프라인을 테스트해 보겠습니다. 결과는 다를 수 있으며, 아래 예시는 한 가지 가능한 결과를 보여주는 것일 뿐입니다.</p>query1="What are the latest AI models released this month?"
query2="What technological innovations are discussed in Sci/Tech news?"
query3="Compare a Sci/Tech article from the dataset with a current web article about AI trends."<p>첫 번째 쿼리의 경우 라우터는 <code>websearch</code> 을 데이터 소스로 선택합니다. 쿼리가 자체 반영 평가에 실패하면 출력에 표시된 것처럼 쿼리 재작성 단계로 리디렉션됩니다.</p>INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: What are the latest AI models released this month?
Latest Singapore news, including the city state's relationships with Malaysia and Mahathir, China and Xi Jinping, and the rest of Southeast Asia. 3 days ago · The latest military news, insights and analysis from China. All the latest news, opinions and analysis on Hong Kong, China, Asia and around the world Latest news, in-depth features and opinion on Malaysia, covering politics, economy, society and the Asean member-nation's relationships with China, Singapore, and other Southeast Asian ... Oct 12, 2025 · Brics (an acronym for Brazil, Russia, India, China and South Africa) refers to an association of 10 leading emerging markets. The other member states are Egypt, Ethiopia, ...
INFO     | __main__:self_reflection:31 - Self-reflection failed -- binary_score=False
INFO     | __main__:query_rewriter:40 - Query rewritten: query='Which AI models have been officially released in June 2024?', retry_count: 1
INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: query='Which AI models have been officially released in June 2024?'
Dream Machine is a text-to-video model created by Luma Labs and launched in June 2024 . It generates video output based on user prompts or still images. Dream Machine has been noted for its ability to realistically capture motion... Released in June 2023. In June 2024 , Baidu announced Ernie 4.0 Turbo. In April 2025, Ernie 4.5 Turbo and X1 Turbo were released . These models are optimized for faster response times and lower operational costs.[28][29]. The meaning of QUERY is question, inquiry. How to use query in a sentence. Synonym Discussion of Query. QUERY definition: 1. a question, often expressing doubt about something or looking for an answer from an authority.... Learn more. Query definition: a question; an inquiry.. See examples of QUERY used in a sentence.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:&lt;module&gt;:2 - 
Final Summary:
: In June 2024, two AI models were officially released: Dream Machine, a text-to-video model launched by Luma Labs, and Ernie 4.0 Turbo, announced by Baidu, which is optimized for faster response times and lower operational costs.<p>다음으로 두 번째 쿼리를 통해 <code>vectorstore</code> 검색이 사용되는 예시를 살펴봅니다.</p>INFO     | __main__:router:11 - Router selected the datasource: vectorstore
INFO     | __main__:router:12 - User query: What technological innovations are discussed in Sci/Tech news?
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:&lt;module&gt;:2 - 
Final Summary:
: Recent Sci/Tech news highlights several technological innovations: NASA is collaborating with Silicon Valley firms to build a powerful Linux-based supercomputer to support theoretical research and shuttle engineering; new chromatin transfer techniques have enabled the cloning of cats; cybersecurity advancements are being discussed in relation to protecting personal technology; Princeton University scientists assert that existing technologies can be used immediately to stabilize global warming; and a set of GameBoy micro-games has been recognized for innovation in game design.<p>최종 쿼리는 벡터스토어와 웹 검색을 모두 활용하는 복합 검색으로 진행됩니다.</p>INFO     | __main__:router:11 - Router selected the datasource: composite
INFO     | __main__:router:12 - User query: Compare a Sci/Tech article from the dataset with a current web article about AI trends.
Atlas currently only available on macOS, built on Chromium with planned features like ad-blocking still in development. OpenAI's Atlas browser launched with bold promises of AI -powered web browsing, but early real-world testing reveals a different story. Career-long data are updated to end-of-2024 and single recent year data pertain to citations received during calendar year 2024. The selection is based on the top 100,000 scientists by c-score (with and without self-citations) or a percentile rank of 2% or above in the sub-field. In this article I list 45 AI tools across 21 different categories. After exploring all the available options in each category, I've carefully selected the best tools based on my personal experience. Reading a complex technical article ? Simply highlight confusing terminology and ask "what's this?" to receive instant explanations. compare browsers. Comparison showing traditional browser navigation versus OpenAI Atlas AI -powered workflows. After putting Gemini, ChatGPT, Grok, and DeepSeek through rigorous testing in October 2025, it's clear that there isn't one AI that reigns supreme across all categories.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:&lt;module&gt;:2 - 
Final Summary:
: A Sci/Tech article from the dataset highlights NASA's development of robust artificial intelligence software for planetary rovers, aiming to make them more self-reliant and capable of decision-making during missions. In contrast, a current web article about AI trends focuses on the proliferation of AI-powered tools across various categories, including browsers like OpenAI Atlas, and compares leading models such as Gemini, ChatGPT, Grok, and DeepSeek, noting that no single AI currently excels in all areas. While the NASA article emphasizes specialized AI applications for autonomous robotics in space exploration, the current trends article showcases the broadening impact of AI across consumer and professional technologies, with ongoing competition and rapid innovation among major AI platforms.<p>위의 워크플로우에서 에이전트 RAG는 사용자 쿼리에 대한 정보를 검색할 때 사용할 데이터 소스를 지능적으로 결정하여 응답의 정확성과 관련성을 개선합니다. 에이전트를 테스트하기 위해 추가 예제를 만들고 출력을 검토하여 흥미로운 결과가 나오는지 확인할 수 있습니다.</p><h2>에이전트 RAG 워크플로우 구축을 위한 모범 사례</h2><p>이제 에이전트 RAG의 작동 방식을 이해했으니 이러한 워크플로를 구축하기 위한 몇 가지 모범 사례를 살펴보겠습니다. 이 가이드라인을 따르면 시스템을 효율적이고 쉽게 유지 관리하는 데 도움이 됩니다.</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></ul><h2>도전 과제</h2><p>에이전트 RAG 시스템은 적응성, 정확성, 동적 추론 측면에서 상당한 이점을 제공하지만, 설계 및 구현 단계에서 해결해야 하는 특정 과제를 안고 있기도 합니다. 주요 과제 중 일부는 다음과 같습니다:</p><ul><li><p><strong>복잡한 워크플로</strong>: 더 많은 상담원과 의사 결정 포인트가 추가됨에 따라 전체 워크플로우가 점점 더 복잡해집니다. 이로 인해 런타임에 오류나 장애가 발생할 가능성이 높아질 수 있습니다. 가능하면 중복 상담원과 불필요한 의사 결정 지점을 제거하여 워크플로우를 간소화하는 데 우선순위를 두세요.</p></li><li><p><strong>확장성</strong>: 대규모 데이터 세트와 많은 쿼리 양을 처리하기 위해 에이전트 RAG 시스템을 확장하는 것은 어려울 수 있습니다. 효율적인 인덱싱, 캐싱 및 분산 처리 전략을 통합하여 규모에 맞게 성능을 유지하세요.</p></li><li><p><strong>오케스트레이션 및 컴퓨팅 오버헤드</strong>: 여러 에이전트가 포함된 워크플로를 실행하려면 고급 오케스트레이션이 필요합니다. 여기에는 병목 현상과 충돌을 방지하기 위한 신중한 스케줄링, 종속성 관리, 상담원 조정이 포함되며, 이 모든 것이 전반적인 시스템 복잡성을 가중시킵니다.</p></li><li><p><strong>평가의 복잡성</strong>: 각 단계마다 고유한 평가 전략이 필요하기 때문에 이러한 워크플로를 평가하는 데는 고유한 어려움이 있습니다. 예를 들어 RAG 단계에서는 검색된 문서의 관련성과 완전성을 평가해야 하며, 생성된 요약의 품질과 정확성을 확인해야 합니다. 마찬가지로 쿼리 재구성의 효과는 재작성된 쿼리가 검색 결과를 개선하는지 여부를 판단하기 위한 별도의 평가 로직이 필요합니다.</p></li></ul><h2>결론</h2><p>이 블로그 게시물에서는 에이전트 RAG의 개념을 소개하고 에이전트 AI의 자율 기능을 통합하여 기존 RAG 프레임워크를 개선하는 방법을 강조했습니다. 에이전트 RAG의 핵심 기능을 살펴보고 실제 예제를 통해 이러한 기능을 시연했으며, Elasticsearch를 벡터 저장소로 사용하고 LangChain을 에이전트 프레임워크를 생성하는 뉴스 어시스턴트를 구축했습니다.</p><p>또한 에이전트 RAG 파이프라인을 설계하고 구현할 때 고려해야 할 모범 사례와 주요 과제에 대해서도 논의했습니다. 이러한 인사이트는 개발자가 검색, 추론 및 의사 결정을 효과적으로 결합하는 강력하고 확장 가능하며 효율적인 에이전트 시스템을 만드는 데 도움을 주기 위한 것입니다.</p><h2>그럼 이제 무엇을 해야 할까요?</h2><p>우리가 구축한 워크플로우는 단순하여 개선과 실험을 위한 충분한 여지를 남겨두고 있습니다. 다양한 임베딩 모델을 실험하고 검색 전략을 개선하여 이를 개선할 수 있습니다. 또한 검색된 문서의 우선 순위를 다시 지정하는 에이전트를 통합하면 도움이 될 수 있습니다. 또 다른 탐색 영역은 에이전트 프레임워크에 대한 평가 전략을 개발하는 것으로, 특히 다양한 유형의 프레임워크에 적용 가능한 공통적이고 재사용 가능한 접근 방식을 식별하는 것입니다. 마지막으로, 더 크고 복잡한 데이터 세트에서 이러한 프레임워크를 실험해 보세요.</p><p>그동안 비슷한 실험을 해보신 경험이 있으시다면 공유해 주시면 감사하겠습니다! <a href="https://ela.st/slack">커뮤니티 Slack 채널이나</a> <a href="https://discuss.elastic.co/c/security">토론 포럼을</a> 통해 자유롭게 피드백을 제공하거나 소통하세요.</p><h2>리소스</h2><ul><li><p><a href="https://arxiv.org/abs/2310.11511">Self-RAG: 자기 성찰을 통한 검색, 생성 및 비평 학습하기</a></p></li><li><p><a href="https://arxiv.org/abs/2501.09136">에이전트 검색-증강 세대: 에이전트 RAG에 대한 설문 조사</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agentic-rag-news-assistant-langchain-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-rag-news-assistant-langchain-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Kirti Sodhi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c7a9f3b0d141d5d/6a17ff83fbc5f86686491d15/59dc0077f5dab00561d9f1b1e7dbf8ec3456259e-1600x1047.heif" length="0" type="image/*"/>
    <pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic 에이전트 빌더와 GPT-OSS로 HR용 AI 에이전트 구축하기]]></title>
    <description><![CDATA[Elastic 에이전트 빌더와 GPT-OSS를 사용해 직원 인사 데이터에 대한 자연어 쿼리에 답변할 수 있는 AI 에이전트를 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>소개</h2><p>이 문서에서는 <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS와</a> Elastic 에이전트 빌더를 사용해 HR용 AI 에이전트를 구축하는 방법을 설명합니다. 상담원은 OpenAI, Anthropic 또는 외부 서비스에 데이터를 보내지 않고도 질문에 답변할 수 있습니다.</p><p>LM Studio를 사용하여 GPT-OSS를 로컬로 서비스하고 이를 Elastic Agent Builder에 연결합니다.</p><p>이 글을 마치면 정보와 모델을 완벽하게 제어하면서 직원 데이터에 대한 자연어 질문에 답할 수 있는 맞춤형 AI 에이전트를 보유하게 됩니다.</p><h2>필수 구성 요소</h2><p>이 문서에는 다음이 필요합니다:</p><ul><li><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a> 호스팅 9.2, 서버리스 또는 <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">로컬</a> 배포</p></li><li><p>32GB RAM이 장착된 머신 권장(GPT-OSS 20B의 경우 최소 16GB)</p></li><li><p><a href="https://lmstudio.ai/">LM Studio</a> 설치</p></li><li><p><a href="https://www.docker.com/products/docker-desktop/">도커 데스크톱</a> 설치</p></li></ul><h2>GPT-OSS를 사용하는 이유는 무엇인가요?</h2><p>로컬 LLM을 사용하면 자체 인프라에 배포하고 필요에 맞게 미세 조정할 수 있습니다. 이 모든 기능을 사용하면서 모델과 공유하는 데이터를 제어할 수 있으며, 물론 외부 제공업체에 라이선스 비용을 지불할 필요가 없습니다.</p><p>OpenAI는 개방형 모델 생태계를 위한 노력의 일환으로 2025년 8월 5일에 <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS를 출시했습니다</a>.</p><p>20B 매개변수 모델은 다음을 제공합니다:</p><ul><li><p><strong>도구 사용 기능</strong></p></li><li><p><strong>효율적인 추론</strong></p></li><li><p><strong>OpenAI SDK 호환</strong></p></li><li><p><strong>에이전트 워크플로와 호환</strong></p></li></ul><p>벤치마크 비교:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58fab956edb40412/6a170cfcb0367da43a72bd80/29160e3345352088e8213297630882f252b00c47-1600x680.png" alt="" /><h2>솔루션 아키텍처</h2><p>아키텍처는 전적으로 로컬 컴퓨터에서 실행됩니다. Elastic(Docker에서 실행)은 LM Studio를 통해 로컬 LLM과 직접 통신하며, Elastic 에이전트 빌더는 이 연결을 사용하여 직원 데이터를 쿼리할 수 있는 사용자 정의 AI 에이전트를 생성합니다.</p><p>자세한 내용은 이 <a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">문서를</a> 참조하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80db5bb0a797f51b/6a170cfd0e2e492f2c41a16f/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" alt="" /><h2>HR용 AI 에이전트 구축: 단계</h2><p>구현을 5단계로 나눠서 설명하겠습니다:</p><ol><li><p>로컬 모델로 LM 스튜디오 구성</p></li><li><p>Docker로 로컬 Elastic 배포</p></li><li><p>Elastic에서 OpenAI 커넥터 생성</p></li><li><p>Elasticsearch에 직원 데이터 업로드</p></li><li><p>AI 에이전트 빌드 및 테스트</p></li></ol><h2>1단계: GPT-OSS 20B로 LM Studio 구성하기</h2><p>LM Studio는 컴퓨터에서 로컬로 대규모 언어 모델을 실행할 수 있는 사용자 친화적인 애플리케이션입니다. OpenAI 호환 API 서버를 제공하므로 복잡한 설정 과정 없이 Elastic과 같은 도구와 쉽게 통합할 수 있습니다. 자세한 내용은 <a href="https://lmstudio.ai/docs/app">LM Studio 문서를</a> 참조하세요.</p><p>먼저 공식 웹사이트에서 LM Studio를 다운로드하여 설치합니다. 설치가 완료되면 애플리케이션을 엽니다.</p><h3>LM Studio 인터페이스에서:</h3><ol><li><p>검색 탭으로 이동하여 "GPT-OSS"를 검색합니다.</p></li><li><p>OpenAI에서 <code>openai/gpt-oss-20b</code></p></li><li><p>다운로드를 클릭하세요.</p></li></ol><p>이 모델의 크기는 약 <strong>12.10GB입니다</strong>. 인터넷 연결 상태에 따라 다운로드하는 데 몇 분 정도 걸릴 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dc341a6625e34b7/6a170cff839dfa2eb4dcff44/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" alt="" /><h4>모델이 다운로드되면</h4><ol><li><p>로컬 서버 탭으로 이동합니다.</p></li><li><p>OPENAI/GPT-OSS-20B를 선택합니다.</p></li><li><p>기본 포트 1234 사용</p></li><li><p>오른쪽 패널에서 <strong>로드로 </strong>이동하여 컨텍스트 길이를 <strong>40K</strong> 이상으로 설정합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3704ca1b28465cc4/6a170d00d7c022ed8fde64ef/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" alt="" /><p>5. 서버 시작을 클릭합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9170a4945ff857/6a170d0266c4f9ffadf8c0a6/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" alt="" /><p>서버가 실행 중이면 이 메시지가 표시되어야 합니다.</p>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] -&gt;	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/embeddings
Server started.<h2>2단계: Docker로 로컬 Elastic 배포하기</h2><p>이제 Docker를 사용해 로컬에서 Elasticsearch와 Kibana를 설정하겠습니다. Elastic은 전체 설정 프로세스를 처리하는 편리한 스크립트를 제공합니다. 자세한 내용은 <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">공식 문서를</a> 참조하세요.</p><h3>시작-로컬 스크립트 실행</h3><p>터미널에서 다음 명령을 실행합니다:</p>curl -fsSL https://elastic.co/start-local | sh<p>이 스크립트는</p><ul><li><p>Elasticsearch와 Kibana 다운로드 및 구성하기</p></li><li><p>Docker Compose를 사용하여 두 서비스 모두 시작</p></li><li><p>30일 플래티넘 평가판 라이선스 자동 활성화</p></li></ul><h3>예상 출력</h3><p>다음 메시지가 표시될 때까지 기다렸다가 표시된 비밀번호와 API 키를 저장하세요. Kibana에 액세스하려면 이 키가 필요합니다:</p>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local<h3>Kibana에 액세스</h3><p>브라우저를 열고 다음으로 이동합니다:</p>http://localhost:5601<p>터미널 출력에서 얻은 자격 증명을 사용하여 로그인합니다.</p><h3>상담원 빌더 사용</h3><p>Kibana에 로그인한 후, <strong>관리 </strong>&gt;<strong> AI </strong>&gt;<strong> 에이전트 빌더로 </strong>이동하여 에이전트 빌더를 활성화합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a934bd99fa6a0ce/6a170d046234e019c3db1a5a/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" alt="" /><h2>3단계: Elastic에서 OpenAI 커넥터 만들기</h2><p>이제 로컬 LLM을 사용하도록 Elastic을 구성하겠습니다.</p><h3>액세스 커넥터</h3><ol><li><p>Kibana에서</p></li><li><p><strong>프로젝트 설정으로</strong> 이동 &gt; <strong>관리</strong></p></li><li><p><strong>알림 및 인사이트에서</strong> <strong>커넥터를</strong>선택합니다.</p></li><li><p>커넥터 만들기를 클릭합니다.</p></li></ol><h3>커넥터 구성</h3><p>커넥터 목록에서 <strong>OpenAI를</strong> 선택합니다. LM Studio는 OpenAI SDK를 사용하므로 호환성이 뛰어납니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762023c39781eb78/6a170d06a29299a59ed01087/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" alt="" /><p>이 값으로 필드를 채웁니다:</p><ul><li><p><strong>커넥터 이름: </strong>LM Studio - GPT-OSS 20B</p></li><li><p><strong>OpenAI 제공업체를 선택합니다: </strong>기타(OpenAI 호환 서비스)</p></li><li><p><strong>URL: </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></p></li><li><p><strong>기본 모델: </strong>OPENAI/GPT-OSS-20B</p></li><li><p><strong>API 키:</strong> testkey-123(LM Studio 서버는 인증이 필요하지 않으므로 어떤 텍스트도 작동합니다.)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt980e595f80e2be2e/6a170d086f7f0468a19148cc/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" alt="" /><p>구성을 완료하려면 <strong>저장 &amp; 테스트를</strong> 클릭합니다.</p><p><strong>중요:</strong> 상담원 빌더가 제대로 작동하려면 '<strong>네이티브 함수 호출 사용</strong>'을 켜야 합니다. 이 기능을 활성화하지 않으면 <strong><code>No tool calls found in the response</code></strong> 오류가 발생합니다.</p><h3>연결 테스트</h3><p>Elastic은 자동으로 연결을 테스트합니다. 모든 것이 올바르게 구성되면 다음과 같은 성공 메시지가 표시됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d2e815dd558f881/6a170d090e2e49076541a177/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" alt="" /><p>대응:</p>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}<h2>4단계: 직원 데이터를 Elasticsearch에 업로드하기</h2><p>이제 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">HR 직원 데이터 세트를</a> 업로드하여 상담원이 민감한 데이터로 어떻게 작업하는지 보여드리겠습니다. 이 구조로 가상의 데이터 집합을 생성했습니다.</p><h3>데이터 세트 구조</h3>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}<h3>매핑으로 인덱스 만들기</h3><p>먼저 적절한 매핑으로 인덱스를 생성합니다. 일부 주요 필드에 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> 필드를 사용하여 인덱스에 시맨틱 검색 기능을 사용할 수 있습니다.</p>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}<h3>대량 API로 색인 생성</h3><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">데이터 세트를</a> 복사하여 Kibana의 개발 도구에 붙여넣고 실행합니다:</p>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}<h3>데이터 확인</h3><p>쿼리를 실행하여 확인합니다:</p>GET hr-employees/_search<h2>5단계: AI 에이전트 빌드 및 테스트</h2><p>모든 구성이 완료되었으면 이제 Elastic 에이전트 빌더를 사용해 사용자 정의 AI 에이전트를 빌드할 차례입니다. 자세한 내용은 <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Elastic 설명서를</a> 참조하세요.</p><h3>커넥터 추가</h3><p>새 에이전트를 생성하기 전에, 기본 커넥터가 <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM이므로</a> <code>LM Studio - GPT-OSS 20B</code> 이라는 사용자 정의 커넥터를 사용하도록 에이전트 빌더를 설정해야 합니다. 이를 위해 <strong>프로젝트 설정</strong> &gt; <strong>관리</strong> &gt; <strong>GenAI 설정으로</strong> 이동한 다음, 생성한 프로젝트를 선택하고 <strong>저장을</strong> 클릭합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc42f079c5e756057/6a170d0acf4f2501d9b2d1c7/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" alt="" /><h3>에이전트 빌더 액세스</h3><ol><li><p><strong>상담원으로</strong>이동</p></li><li><p><strong>새 상담원 만들기를</strong>클릭합니다.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8e734817c5a7c6a/6a170d0ca929cf867cae0a34/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" alt="" /><h3>상담원 구성하기</h3><p>새 상담원을 만들 때 필수 필드는 <strong>상담원 ID</strong>, <strong>표시 이름</strong> 및 <strong>표시 지침입니다</strong>.</p><p>하지만 시스템 프롬프트와 비슷하지만 사용자 지정 상담원을 위해 상담원이 어떻게 행동하고 툴과 상호 작용할지 안내하는 사용자 지정 지침과 같은 더 많은 사용자 지정 옵션이 있습니다. 레이블은 상담원, 아바타 색상 및 아바타 심볼을 정리하는 데 도움이 됩니다.</p><p>데이터 집합을 기반으로 상담원에게 선택한 것은

<strong>상담원 ID입니다</strong>: <code>hr_assistant</code></p><p><strong>사용자 지정 지침:</strong></p>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality<p>
레이블: <code>Human Resources</code> 및 <code>GPT-OSS</code></p><p>표시 이름: <code>HR Analytics Assistant</code></p><p>설명을 표시합니다:</p>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23fb011e5b4f4d49/6a170d0e7d8d67f47a70e77f/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" alt="" /><p>여기에 모든 데이터가 있으면 새 상담원 <strong>저장을</strong> 클릭할 수 있습니다.</p><h3>에이전트 테스트</h3><p>이제 직원 데이터에 대해 자연어 질문을 하면 GPT-OSS 20B가 의도를 파악하고 적절한 답변을 생성합니다.</p><h4>프롬프트:</h4>Which employee is the one with the highest salary in the hr-employees index?<h4>답변:</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0c52faacf63b583/6a170d0f0e2e497bfd41a17b/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" alt="" /><p>에이전트 프로세스였습니다:</p><p>1. GPT-OSS 커넥터를 사용하여 질문 이해하기</p><p>2. (기본 제공 도구 또는 사용자 정의 <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL을</a> 사용하여) 적절한 Elasticsearch 쿼리를 생성합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte32a8a7e6363c7f2/6a170d115091680077e1bb44/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" alt="" /><p>3. 일치하는 직원 기록 검색</p><p>4. 적절한 서식을 사용하여 자연어로 결과 표시</p><p>기존의 어휘 검색과 달리 GPT-OSS 기반 에이전트는 의도와 문맥을 이해하므로 정확한 필드 이름이나 쿼리 구문을 몰라도 정보를 쉽게 찾을 수 있습니다. 상담원의 사고 과정에 대한 자세한 내용은 이 <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">문서를</a> 참조하세요.</p><h2>결론</h2><p>이 문서에서는 Elastic의 에이전트 빌더를 사용해 로컬에서 실행 중인 OpenAI GPT-OSS 모델에 연결하기 위해 사용자 정의 AI 에이전트를 구축했습니다. 이 아키텍처는 로컬 머신에 Elastic과 LLM을 모두 배포함으로써 외부 서비스로 정보를 전송하지 않고도 데이터를 완벽하게 제어하면서 생성형 AI 기능을 활용할 수 있게 해줍니다.</p><p>실험으로 GPT-OSS 20B를 사용했지만, 공식적으로 Elastic 에이전트 빌더에 권장되는 모델은 <a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">여기를</a> 참조하세요. 고급 추론 기능이 필요한 경우, 로컬에서 실행하려면 더 높은 사양의 컴퓨터가 필요하지만 복잡한 시나리오에서 더 나은 성능을 발휘하는 <a href="https://huggingface.co/openai/gpt-oss-120b">120B 매개변수 변형도</a> 있습니다. 자세한 내용은 <a href="https://openai.com/open-models/">OpenAI 공식 문서를</a> 참조하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt664f490053e46e6b/6a170d13b0367d2d7e72bd84/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[상위 Elastic 에이전트 빌더 프로젝트와 Cal Hacks 12.0에서 얻은 교훈]]></title>
    <description><![CDATA[Cal Hacks 12.0에서 최고의 Elastic 에이전트 빌더 프로젝트를 살펴보고 서버리스, ES|QL 및 에이전트 아키텍처에 대한 기술적 요점을 자세히 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>몇 주 전, 전 세계에서 2,000명 이상의 참가자가 모인 대규모 오프라인 해커톤 중 하나인 <a href="https://cal-hacks-12-0.devpost.com/">Cal Hacks 12.0을</a> 후원할 수 있는 놀라운 기회를 가졌습니다. 서버리스에서 Elastic 에이전트 빌더를 가장 잘 활용할 수 있는 전용 상금 트랙을 제공했는데, 그 반응은 놀라웠습니다. 단 36시간 만에 산불 인텔리전스 도구 구축부터 StackOverflow 유효성 검사기까지 에이전트 빌더를 창의적인 방식으로 사용한 29개의 제출물이 접수되었습니다.</p><p>인상적인 프로젝트 외에도 Cal Hacks 12.0의 경험은 Stack을 처음 접하는 개발자들의 빠르고 여과 없는 피드백이라는 또 다른 가치 있는 것을 얻게 해 주었습니다. 해커톤은 촉박한 일정, 사전 정보 부족, 예측할 수 없는 장애물(악명 높은 와이파이 중단 등)이 있는 독특한 압박 테스트입니다. 이를 통해 개발자 환경의 장점과 개선이 필요한 부분을 정확히 파악할 수 있습니다. 개발자들이 점점 더 LLM 기반 워크플로우를 통해 새로운 방식으로 Elastic Stack과 상호 작용함에 따라 이는 이제 더욱 중요해졌습니다. 이 블로그 게시물에서는 참가자들이 에이전트 빌더로 구축한 내용과 그 과정에서 배운 점을 자세히 살펴봅니다.</p><h2>수상 프로젝트</h2><h3>1등 에이전트 오버플로</h3><p>스택 오버플로가 LLM 및 에이전트 시대에 맞게 재구축되었습니다.</p><p><a href="https://devpost.com/software/agentoverflow">여기에서</a> 에이전트오버플로에 대해 자세히 알아보세요.</p><p>에이전트오버플로는 대부분의 AI 개발자가 직면하는 문제를 해결합니다: LLM이 환각을 일으키고, 채팅 기록이 사라지고, 개발자가 동일한 문제를 다시 해결하느라 시간을 낭비하는 등의 문제를 해결합니다.</p><p>에이전트오버플로는 실제 문제 해결 쌍을 캡처, 검증 및 재표출하여 개발자가 환각의 소용돌이를 끊고 더 빠르게 출시할 수 있도록 지원합니다.</p><h4>작동 방식:</h4><p><strong>1. JSON - "솔루션 스키마" 공유.</strong></p><p>Claude 공유에서 클릭 한 번으로 다음을 포함하는 구조화된 형식인 공유 솔루션 JSON을 스크랩, 추출 및 조립합니다:</p><ul><li><p>문제</p></li><li><p>컨텍스트</p></li><li><p>코드</p></li><li><p>태그</p></li><li><p>검증된 솔루션 단계.</p></li></ul><p>유효성 검사기(LAVA)가 구조를 검사하고 적용하면 사용자가 한 줄의 추가 컨텍스트를 추가하면 Elasticsearch 내에 저장되고 색인됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7bc35b6d54921e8/6a17f0176df73162760a0fe6/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" alt="&quot;솔루션 공유&quot;를 클릭하면 관련 메타데이터와 함께 현재 세션이 스크랩됩니다." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9967f52007fff99e/6a17f019ec0f8987c45a6701/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" alt="사용자가 웹 프론트엔드를 통해 추가 컨텍스트를 제공하면 JSON이 Elasticsearch에서 색인됩니다." /><p><strong>2. 솔루션 찾기</strong></p><p>문제가 발생하면 <code>Find Solution</code> 을 클릭하면 AgentOverflow가 현재 대화를 스크랩하여 쿼리를 작성하고 이를 사용하여 하이브리드 Elasticsearch 검색을 실행하여 결과를 표시합니다:</p><ul><li><p>순위가 매겨진 커뮤니티 검증 수정 사항</p></li><li><p>원래 문제를 해결한 정확한 안내 메시지</p></li></ul><p>이를 통해 개발자는 현재 세션을 빠르게 복사, 붙여넣기, 차단 해제할 수 있습니다.</p><p><strong>3. MCP - LLM을 위한 컨텍스트 주입</strong></p><p>MCP(모델 컨텍스트 프로토콜)를 통해 Elasticsearch 내에 저장된 구조화된 솔루션에 연결하면 별도의 노이즈 없이 런타임에 높은 신호 컨텍스트(코드, 로그, 구성, 이전 수정 사항)를 LLM에 공급할 수 있습니다.</p><p>에이전트 오버플로에서는 에이전트 빌더와 Elasticsearch를 사용해 관련 컨텍스트를 LLM에 주입하는 구조화된 메모리 계층으로 사용합니다. 이를 통해 수동적인 챗봇에서 상황 인식 문제 해결사로 변신합니다.</p><h3>준우승 MarketMind</h3><p>6개의 Elastic 에이전트가 제공하는 시장 에너지에 대한 실시간 해석 가능한 보기.</p><p>마켓마인드에 대한 자세한 내용은 <a href="https://devpost.com/software/marketmind-b6cy2q">여기를</a> 참조하세요.</p><p>MarketMind는 초보 트레이더에게 파편화된 시장 데이터를 명확한 실시간 신호로 변환하는 플랫폼을 제공함으로써 자리를 잡았습니다. 가격 움직임, 펀더멘털, 심리, 변동성을 여러 도구에 분산하는 대신 MarketMind는 이 모든 정보를 하나의 플랫폼에 통합하여 트레이더가 실행 가능한 인사이트를 얻을 수 있도록 도와줍니다. 이 프로젝트는 에이전트를 구축할 때 복잡한 ES|QL 쿼리를 사용하기도 했습니다.</p><h4>작동 방식:</h4><p><strong>1. 실시간 시장 데이터 수집</strong></p><p>MarketMind는 가격 움직임, 펀더멘털, 심리, 변동성, 위험 지표를 야후 파이낸스로부터 가져옵니다. 이 데이터는 수집되어 여러 개의 Elasticsearch 인덱스로 구성됩니다.</p><p><strong>2. 6명의 전문 에이전트가 시장을 분석합니다.</strong></p><p>에이전트 빌더로 구축된 각 에이전트는 시장의 다른 계층에 초점을 맞춥니다. 이들은 Elasticsearch 인덱스에서 읽고, 자체 도메인별 메트릭을 계산하고, 점수와 추론이 포함된 표준화된 JSON 출력을 생성합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4ba582f9872b65b/6a17f01b7f6f15c2d8c09c1c/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" alt="시장을 분석하는 6가지 전문 GOOGL AI 에이전트" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd86ed3bfe4b8bd2b/6a17f01c5ea30f868164b6ba/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" alt="볼륨 이상 및 재해 감지 분석 기능을 갖춘 GOOGL 전문 에이전트" /><p><strong>3. 통합된 '시장 에너지' 모델로 신호 통합</strong></p><p>합산된 결과는 각 종목 주위에 빛나는 펄스로 표시되어 모멘텀이 형성되고 있는지, 리스크가 상승하고 있는지, 심리가 변화하고 있는지를 보여줍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5af7c7c838308275/6a17f01e42022917b629f6ca/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" alt="GOOGL 전문 에이전트의 통합된 '시장 에너지' 모델" /><p><strong>4. 인사이트 시각화</strong></p><p>프론트엔드는 타입스크립트, SVG 물리 기반 비주얼, 라이브 캔들스틱 차트를 위한 <a href="https://github.com/chartjs">Chart.js를 사용하여</a> <a href="https://github.com/vercel/next.js">React와 Next.js로 구축되었습니다.</a> 이렇게 하면 원시 분석이 실시간으로 실행 가능한 피드백으로 전환됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt775e1880aa7afacc/6a17f01f1d1b83ce1f93e528/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" alt="GOOGL 전문 상담원 분석의 인사이트를 시각화하는 방법" /><h2>기타 흥미로운 프로젝트:</h2><p>다음은 스택의 여러 부분에서 Elastic을 사용한 다른 강력한 경쟁자들입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffe292009e446a70/6a17f0216df731068c0a0fea/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" alt="" /><p><a href="https://cal-hacks-12-0.devpost.com/submissions/search?utf8=%E2%9C%93&amp;prize_filter%5Bprizes%5D%5B%5D=91882">여기에서</a> 트랙에 제출된 프로젝트의 전체 목록을 확인하세요.</p><h2>개발자로부터 배운 점</h2><ul><li><p><strong>에이전트 빌더는 사용자 친화적입니다:</strong></p></li></ul><p>대부분의 팀은 이전에 Elastic을 사용해 본 적이 없었지만 적은 지원으로도 신속하게 에이전트를 구축할 수 있었습니다. 더 많은 안내가 필요한 분들을 위해 워크숍을 열었지만, 대부분 데이터를 수집하고 해당 데이터에 대한 작업을 수행하는 에이전트를 구축할 수 있었습니다.</p><ul><li><p><strong>LLM은 </strong><strong><code>kNN</code></strong><strong> 쿼리에 탁월하지만 여전히 ES|QL 생성에 대한 지침이 필요합니다:</strong></p></li></ul><p>ChatGPT-5에 ES|QL 쿼리를 생성하도록 요청하면 ES|QL과 SQL이 혼합된 잘못된 정보가 반환되는 경우가 많았습니다. 마크다운 파일로 문서를 LLM에 공급하는 것은 실행 가능한 해결 방법인 것 같았습니다.</p><ul><li><p><strong>스냅샷 전용 ES|QL 함수가 문서로 유출되었습니다:</strong></p></li></ul><p>곧 출시될 <code>FIRST</code> 및 <code>LAST</code> 집계 함수는 의도치 않게 ES|QL 문서에 포함되었습니다. 이러한 문서를 ChatGPT에 제공했기 때문에 모델은 아직 서버리스에서 사용할 수 없는 기능임에도 불구하고 이러한 기능을 충실히 사용했습니다. 그룹의 피드백에 힘입어 엔지니어링 팀은 게시된 문서에서 기능을 제거하는 수정 사항을 신속하게 공개하고 병합했습니다<a href="https://github.com/elastic/elasticsearch/pull/137341">(PR #137341)</a>.</p><ul><li><p><strong>서버리스 관련 안내가 누락되었습니다:</strong></p></li></ul><p>한 팀이 조회 모드로 생성되지 않은 인덱스에서 <code>LOOKUP JOIN</code> 을 활성화하려고 시도했습니다. 오류 메시지는 서버리스에 존재하지 않는 명령을 쫓는다는 메시지를 보냈습니다. 이 사실을 제품 팀에 전달했고, 제품 팀은 즉시 서버리스와 관련된 실행 가능한 메시지에 대한 수정 사항을 공개했습니다. 장기적으로는 재색인 복잡성을 완전히 숨기는 것이 비전입니다<a href="https://github.com/elastic/elasticsearch-serverless/issues/4838">(이슈 #4838)</a>.</p><ul><li><p><strong>대면 이벤트의 가치:</strong></p></li></ul><p>온라인 해커톤도 훌륭하지만, 빌더와 어깨를 맞대고 디버깅할 때 얻을 수 있는 빠른 피드백을 따라올 수 있는 것은 없습니다. 다양한 사용 사례에서 에이전트 빌더를 통합하는 팀을 지켜보면서 ES|QL을 사용하는 개발자 경험을 개선할 수 있는 부분을 발견하고 비동기 채널을 통해 문제를 해결하는 것보다 훨씬 더 빠르게 문제를 해결했습니다.</p><h2>결론</h2><p>Cal Hacks 12.0은 주말 동안 멋진 데모를 보여줬을 뿐만 아니라 새로운 개발자들이 Elastic Stack과 어떻게 상호작용하는지에 대한 인사이트도 제공했습니다. 단 36시간 만에 팀들이 에이전트 빌더를 선택하고, Elasticsearch로 데이터를 수집하고, 멀티 에이전트 시스템을 설계하고, 다양한 방식으로 기능을 테스트하는 것을 보았습니다. 이번 행사를 통해 대면 이벤트가 중요한 이유를 다시 한 번 깨달았습니다. 빠른 피드백 루프, 실제 대화, 실습 디버깅을 통해 현재 개발자의 요구 사항을 파악하는 데 도움이 되었습니다. 저희가 배운 내용을 엔지니어링 팀에 다시 전달할 수 있게 되어 기쁩니다. 다음 해커톤에서 뵙겠습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f079179be9832d4/6a17f023631730a69c585b6d/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기: 2부]]></title>
    <description><![CDATA[에이전트 협업을 위한 A2A 프로토콜과 Elasticsearch의 도구 액세스를 위한 MCP를 사용해 특화된 하이브리드 LLM 에이전트 뉴스룸을 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>A2A 및 MCP: 작동 중인 코드</h2><p>이 글은 동일한 에이전트 내에서 A2A와 MCP 아키텍처를 모두 구현하여 두 프레임워크의 고유한 이점을 제대로 활용할 수 있는 이점을 설명한 "Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기!" 글의 후속 글입니다. 직접 데모를 실행하려는 경우 <a href="https://github.com/justincastilla/elastic-newsroom">저장소를</a> 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt232e466d2153c764/6a17f15f631730042d585b8d/7196f004089127f83547b2e5dc3f663205cfcdce-1162x1600.png" alt="A2A &amp; MCP 프로토콜 에이전트 워크플로" /><p>뉴스룸 에이전트가 A2A와 MCP를 사용하여 뉴스 기사를 제작하기 위해 어떻게 협업하는지 살펴보겠습니다. 에이전트가 작동하는 모습을 볼 수 있는 리포지토리는 <a href="https://github.com/justincastilla/elastic-newsroom">여기에서</a> 확인할 수 있습니다.</p><h3>1단계: 스토리 과제</h3><p><strong>뉴스 책임자</strong> (클라이언트 역할)가 스토리를 할당합니다:</p>{
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "assignment": {
      "topic": "Renewable Energy Adoption in Europe",
      "angle": "Policy changes driving solar and wind expansion",
      "target_length": 1200,
      "deadline": "2025-09-30T18:00:00Z"
    }
  }
}<h3>2단계: 리포터가 리서치 요청</h3><p><strong>리포터 에이전트는</strong> 배경 정보가 필요하다는 것을 인식하고 A2A를 통해 <strong>리포터 에이전트에게</strong> 위임합니다:</p>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "fact_gathering",
    "parameters": {
      "queries": [
        "EU renewable energy capacity 2024",
        "Solar installations growth Europe",
        "Wind energy policy changes 2024"
      ],
      "depth": "comprehensive"
    }
  }
}<h3>3단계: 리포터가 아카이브 에이전트에게 기록 컨텍스트를 요청합니다.</h3><p><strong>리포터 에이전트는</strong> 역사적 맥락이 스토리를 강화할 수 있다는 것을 알고 있습니다. A2A를 <strong>통해</strong> (<a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">Elastic의 A2A 에이전트로</a> 구동되는) 아카이브 에이전트에 위임하여 뉴스룸의 Elasticsearch 기반 기사 아카이브를 검색합니다:</p>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "archive_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "search_archive",
    "parameters": {
      "query": "European renewable energy policy changes and adoption trends over past 5 years",
      "focus_areas": ["solar", "wind", "policy", "Germany", "France"],
      "time_range": "2019-2024",
      "result_count": 10
    }
  }
}<h3>4단계: 아카이브 에이전트가 MCP와 함께 Elastic A2A 에이전트 사용</h3><p><strong>아카이브 에이전트는</strong> Elastic의 A2A 에이전트를 사용하며, 이 에이전트는 다시 MCP를 사용해 Elasticsearch 도구에 액세스합니다. 이는 A2A가 에이전트 협업을 지원하는 동시에 MCP가 툴 액세스를 제공하는 하이브리드 아키텍처를 보여줍니다:</p># Archive Agent using Elastic A2A Agent
async def search_historical_articles(self, query_params):
    # The Archive Agent sends a request to Elastic's A2A Agent
    elastic_response = await self.a2a_client.send_request(
        agent="elastic_agent",
        capability="search_and_analyze",
        parameters={
            "natural_language_query": query_params["query"],
            "index_pattern": "newsroom-articles-*",
            "filters": {
                "topics": query_params["focus_areas"],
                "date_range": query_params["time_range"]
            },
            "analysis_type": "trend_analysis"
        }
    )
    
    # Elastic's A2A Agent internally uses MCP tools:
    # - platform.core.search (to find relevant articles)
    # - platform.core.generate_esql (to analyze trends)
    # - platform.core.index_explorer (to identify relevant indices)
    
    return elastic_response<p><strong>아카이브 에이전트는</strong> Elastic의 A2A 에이전트로부터 포괄적인 기록 데이터를 받아 리포터에게 반환합니다:</p>{
  "message_type": "task_response",
  "sender": "archive_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "status": "completed",
    "archive_data": {
      "historical_articles": [
        {
          "title": "Germany's Energiewende: Five Years of Solar Growth",
          "published": "2022-06-15",
          "key_points": [
            "Germany added 7 GW annually 2020-2022",
            "Policy subsidies drove 60% of growth"
          ],
          "relevance_score": 0.94
        },
        {
          "title": "France Balances Nuclear and Renewables",
          "published": "2023-03-20",
          "key_points": [
            "France increased renewable target to 40% by 2030",
            "Solar capacity doubled 2021-2023"
          ],
          "relevance_score": 0.89
        }
      ],
      "trend_analysis": {
        "coverage_frequency": "EU renewable stories increased 150% since 2019",
        "emerging_themes": ["policy incentives", "grid modernization", "battery storage"],
        "coverage_gaps": ["Small member states", "offshore wind permitting"]
      },
      "total_articles_found": 47,
      "search_confidence": 0.91
    }
  }
}<p>이 단계에서는 Elastic의 A2A 에이전트가 뉴스룸 워크플로우에 어떻게 통합되는지 보여드립니다. Archive 에이전트(뉴스룸 전용 에이전트)는 Elastic의 A2A 에이전트(타사 전문가)와 협력하여 Elasticsearch의 강력한 검색 및 분석 기능을 활용합니다. Elastic의 에이전트는 내부적으로 MCP를 사용하여 Elasticsearch 도구에 액세스하므로 에이전트 조정(A2A)과 도구 액세스(MCP)가 깔끔하게 분리되어 있습니다.</p><h3>5단계: 연구자가 MCP 서버 사용</h3><p><strong>연구원 에이전트는</strong> 여러 MCP 서버에 액세스하여 정보를 수집합니다:</p># Researcher Agent using MCP to access tools
async def gather_facts(self, queries):
    results = []
    
    # Use News API MCP Server
    news_data = await self.mcp_client.invoke_tool(
        server="news_api_mcp",
        tool="search_articles",
        parameters={
            "query": queries[0],
            "date_range": "2024-01-01 to 2024-09-30",
            "sources": ["reuters", "bloomberg", "euractiv"]
        }
    )
    
    # Use Statistics MCP Server
    stats_data = await self.mcp_client.invoke_tool(
        server="eurostat_mcp",
        tool="get_energy_statistics",
        parameters={
            "metrics": ["solar_capacity_mw", "wind_capacity_mw"],
            "countries": ["EU27"],
            "year": 2024
        }
    )
    
    # Use Fact-Checking MCP Server
    verified_facts = await self.mcp_client.invoke_tool(
        server="factcheck_mcp",
        tool="verify_claims",
        parameters={
            "claims": [
                "EU solar capacity increased 40% in 2024",
                "Germany leads in wind installations"
            ]
        }
    )
    
    return self._synthesize_research(news_data, stats_data, verified_facts)<h3>6단계: 연구원이 리포터에게 데이터를 반환합니다.</h3><p><strong>리서처 에이전트는</strong> A2A를 통해 종합적인 리서치를 다시 보냅니다:</p>{
  "message_type": "task_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "status": "completed",
    "research_data": {
      "key_facts": [
        {
          "claim": "EU solar capacity reached 260 GW in 2024",
          "source": "Eurostat Q3 2024 Report",
          "confidence": 0.95,
          "verified": true
        },
        {
          "claim": "Germany installed 12 GW of wind capacity in 2024",
          "source": "German Federal Network Agency",
          "confidence": 0.92,
          "verified": true
        }
      ],
      "statistics": {
        "solar_growth_rate": "35%",
        "wind_growth_rate": "28%"
      },
      "sources_count": 15
    }
  }
}<h3>7단계: 기자가 기사 작성</h3><p><strong>리포터 에이전트는</strong> 리서치 데이터와 자체 LLM 기능을 사용하여 기사를 작성합니다. 글을 작성하는 동안 리포터는 스타일과 템플릿을 위해 MCP 서버를 사용합니다:</p># Reporter Agent writing with MCP assistance
async def write_article(self, research_data, assignment):
    # Get style guidelines via MCP
    style_guide = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="style://editorial/ap_style_guide"
    )
    
    # Get article template via MCP
    template = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="template://articles/news_story"
    )
    
    # Generate article using LLM + research + style
    draft = await self.llm.generate(
        prompt=f"""
        Write a news article following these guidelines:
        {style_guide}
        
        Using this template:
        {template}
        
        Based on this research:
        {research_data}
        
        Assignment: {assignment}
        """
    )
    
    # Self-evaluate confidence in claims
    confidence_check = await self._evaluate_confidence(draft)
    
    return draft, confidence_check<h3>8단계: 낮은 신뢰도로 인한 재조사 트리거</h3><p><strong>리포터 에이전트가</strong> 초안을 평가한 결과 한 클레임의 신뢰도가 낮다는 것을 발견했습니다. <strong>연구원 에이전트에게</strong> 또 다른 요청을 보냅니다:</p>{
  "message_type": "collaboration_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "request_type": "fact_verification",
    "claims": [
      {
        "text": "France's nuclear phase-down contributed to 15% increase in renewable capacity",
        "context": "Discussing policy drivers for renewable growth",
        "current_confidence": 0.45,
        "required_confidence": 0.80
      }
    ],
    "urgency": "high"
  }
}<p><strong>연구원은</strong> 사실 확인 MCP 서버를 사용하여 클레임을 확인하고 업데이트된 정보를 반환합니다:</p>{
  "message_type": "collaboration_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "verified_claims": [
      {
        "original_claim": "France's nuclear phase-down contributed to 15% increase...",
        "verified_claim": "France's renewable capacity increased 18% in 2024, partially offsetting reduced nuclear output",
        "confidence": 0.88,
        "corrections": "Percentage was 18%, not 15%; nuclear phase-down is gradual, not primary driver",
        "sources": ["RTE France", "French Energy Ministry Report 2024"]
      }
    ]
  }
}<h3>9단계: 리포터가 수정하여 편집자에게 제출합니다.</h3><p><strong>리포터는</strong> 확인된 사실을 통합하고 A2A를 통해 완성된 초안을 <strong>편집 에이전트에게</strong> 보냅니다:</p>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "editor_agent",
  "payload": {
    "task_id": "edit_renewable_story",
    "parent_task_id": "story_renewable_energy_2024",
    "content": {
      "headline": "Europe's Renewable Revolution: Solar and Wind Surge 30% in 2024",
      "body": "[Full article text...]",
      "word_count": 1185,
      "sources": [/* array of sources */]
    },
    "editing_requirements": {
      "check_style": true,
      "check_facts": true,
      "check_seo": true
    }
  }
}<h3>10단계: MCP 도구를 사용한 편집자 리뷰</h3><p><strong>편집자 에이전트는</strong> 여러 MCP 서버를 사용하여 문서를 검토합니다:</p># Editor Agent using MCP for quality checks
async def review_article(self, content):
    # Grammar and style check
    grammar_issues = await self.mcp_client.invoke_tool(
        server="grammarly_mcp",
        tool="check_document",
        parameters={"text": content["body"]}
    )
    
    # SEO optimization check
    seo_analysis = await self.mcp_client.invoke_tool(
        server="seo_mcp",
        tool="analyze_content",
        parameters={
            "headline": content["headline"],
            "body": content["body"],
            "target_keywords": ["renewable energy", "Europe", "solar", "wind"]
        }
    )
    
    # Plagiarism check
    originality = await self.mcp_client.invoke_tool(
        server="plagiarism_mcp",
        tool="check_originality",
        parameters={"text": content["body"]}
    )
    
    # Generate editorial feedback
    feedback = await self._generate_feedback(
        grammar_issues, 
        seo_analysis, 
        originality
    )
    
    return feedback<p><strong>편집자가</strong> 글을 승인하고 앞으로 보냅니다:</p>{
  "message_type": "task_response",
  "sender": "editor_agent",
  "receiver": "reporter_agent",
  "payload": {
    "status": "approved",
    "quality_score": 9.2,
    "minor_edits": [
      "Changed 'surge' to 'increased' in paragraph 3 for AP style consistency",
      "Added Oxford comma in list of countries"
    ],
    "approved_content": "[Final edited article]"
  }
}<h3>11단계: CI/CD를 통해 게시자 게시</h3><p>마지막으로 <strong>프린터 에이전트는</strong> CMS 및 CI/CD 파이프라인을 위한 MCP 서버를 사용하여 승인된 문서를 게시합니다:</p># Publisher Agent publishing via MCP
async def publish_article(self, content, metadata):
    # Upload to CMS via MCP
    cms_result = await self.mcp_client.invoke_tool(
        server="wordpress_mcp",
        tool="create_post",
        parameters={
            "title": content["headline"],
            "body": content["body"],
            "status": "draft",
            "categories": metadata["categories"],
            "tags": metadata["tags"],
            "featured_image_url": metadata["image_url"]
        }
    )
    
    post_id = cms_result["post_id"]
    
    # Trigger CI/CD deployment via MCP
    deploy_result = await self.mcp_client.invoke_tool(
        server="cicd_mcp",
        tool="trigger_deployment",
        parameters={
            "pipeline": "publish_article",
            "environment": "production",
            "post_id": post_id,
            "schedule": "immediate"
        }
    )
    
    # Track analytics
    await self.mcp_client.invoke_tool(
        server="analytics_mcp",
        tool="register_publication",
        parameters={
            "post_id": post_id,
            "publish_time": datetime.now().isoformat(),
            "story_id": metadata["story_id"]
        }
    )
    
    return {
        "status": "published",
        "post_id": post_id,
        "url": f"https://newsroom.example.com/articles/{post_id}",
        "deployment_id": deploy_result["deployment_id"]
    }<p><strong>게시자는</strong> A2A를 통해 게시를 확인합니다:</p>{
  "message_type": "task_complete",
  "sender": "printer_agent",
  "receiver": "news_chief",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "status": "published",
    "publication": {
      "url": "https://newsroom.example.com/articles/renewable-europe-2024",
      "published_at": "2025-09-30T17:45:00Z",
      "post_id": "12345"
    },
    "workflow_metrics": {
      "total_time_minutes": 45,
      "agents_involved": ["reporter", "researcher", "archive", "editor", "printer"],
      "iterations": 2,
      "mcp_calls": 12
    }
  }
}<p>다음은 위에서 설명한 것과 동일한 에이전트를 사용하여 첨부된 리포지토리에 있는 A2A 워크플로우의 전체 시퀀스입니다.</p><p>#</p><p>에서</p><p>To</p><p>액션</p><p>프로토콜</p><p>설명</p><p>1</p><p>사용자</p><p>뉴스 수석</p><p>스토리 할당</p><p>HTTP POST</p><p>사용자가 스토리 주제와 각도를 제출합니다.</p><p>2</p><p>뉴스 수석</p><p>내부</p><p>스토리 만들기</p><p>-</p><p>고유 ID로 스토리 레코드 생성</p><p>3</p><p>뉴스 수석</p><p>리포터</p><p>위임자 할당</p><p>A2A</p><p>A2A 프로토콜을 통해 스토리 할당 전송</p><p>4</p><p>리포터</p><p>내부</p><p>할당 수락</p><p>-</p><p>내부적으로 스토어 할당</p><p>5</p><p>리포터</p><p>MCP 서버</p><p>개요 생성</p><p>MCP/HTTP</p><p>기사 개요 및 연구 질문 생성</p><p>6a</p><p>리포터</p><p>연구원</p><p>연구 요청</p><p>A2A</p><p>질문 보내기(6b와 병행)</p><p>6b</p><p>리포터</p><p>아카이브</p><p>아카이브 검색</p><p>A2A JSONRPC</p><p>과거 기사 검색(6a와 병행)</p><p>7</p><p>연구원</p><p>MCP 서버</p><p>연구 질문</p><p>MCP/HTTP</p><p>MCP를 통해 Anthropic을 사용하여 질문에 답하기</p><p>8</p><p>연구원</p><p>리포터</p><p>리서치 반환</p><p>A2A</p><p>연구 답변 반환</p><p>9</p><p>아카이브</p><p>Elasticsearch</p><p>검색 색인</p><p>ES REST API</p><p>뉴스_아카이브 인덱스 쿼리</p><p>10</p><p>아카이브</p><p>리포터</p><p>아카이브 반환</p><p>A2A JSONRPC</p><p>과거 검색 결과 반환</p><p>11</p><p>리포터</p><p>MCP 서버</p><p>문서 생성</p><p>MCP/HTTP</p><p>리서치/아카이브 컨텍스트가 포함된 문서 생성</p><p>12</p><p>리포터</p><p>내부</p><p>저장 초안</p><p>-</p><p>내부적으로 초안 저장</p><p>13</p><p>리포터</p><p>뉴스 수석</p><p>초안 제출</p><p>A2A</p><p>완성된 초안 제출</p><p>14</p><p>뉴스 수석</p><p>내부</p><p>업데이트 스토리</p><p>-</p><p>초안 저장, "draft_submitted로 상태 업데이트"</p><p>15</p><p>뉴스 수석</p><p>편집기</p><p>초안 검토</p><p>A2A</p><p>검토를 위해 편집기로 자동 라우팅</p><p>16</p><p>편집기</p><p>MCP 서버</p><p>기사 검토</p><p>MCP/HTTP</p><p>MCP를 통해 Anthropic을 사용하여 콘텐츠 분석</p><p>17</p><p>편집기</p><p>뉴스 수석</p><p>반품 검토</p><p>A2A</p><p>편집 피드백 및 제안을 보냅니다.</p><p>18</p><p>뉴스 수석</p><p>내부</p><p>스토어 리뷰</p><p>-</p><p>편집자 피드백 저장</p><p>19</p><p>뉴스 수석</p><p>리포터</p><p>편집 적용</p><p>A2A</p><p>리포터에게 리뷰 피드백 전달</p><p>20</p><p>리포터</p><p>MCP 서버</p><p>편집 적용</p><p>MCP/HTTP</p><p>피드백에 따라 문서 수정</p><p>21</p><p>리포터</p><p>내부</p><p>업데이트 초안</p><p>-</p><p>수정 사항이 있는 초안 업데이트</p><p>22</p><p>리포터</p><p>뉴스 수석</p><p>수정된 반환</p><p>A2A</p><p>수정된 문서 반환</p><p>23</p><p>뉴스 수석</p><p>내부</p><p>업데이트 스토리</p><p>-</p><p>스토어 수정 초안, 상태 "수정됨"</p><p>24</p><p>뉴스 수석</p><p>게시자</p><p>기사 게시</p><p>A2A</p><p>게시자에게 자동 라우팅</p><p>25</p><p>게시자</p><p>MCP 서버</p><p>태그 생성</p><p>MCP/HTTP</p><p>태그 및 카테고리 만들기</p><p>26</p><p>게시자</p><p>Elasticsearch</p><p>색인 문서</p><p>ES REST API</p><p>기사를 뉴스_아카이브 인덱스에 색인화</p><p>27</p><p>게시자</p><p>파일 시스템</p><p>마크다운 저장</p><p>파일 I/O</p><p>.md로 문서 저장 아티클의 파일</p><p>28</p><p>게시자</p><p>뉴스 수석</p><p>게시 확인</p><p>A2A</p><p>성공 상태 반환</p><p>29</p><p>뉴스 수석</p><p>내부</p><p>업데이트 스토리</p><p>-</p><p>스토리 상태를 "게시됨으로 업데이트"</p><h2>결론</h2><p>A2A와 MCP는 모두 최신 증강-LLM 인프라 패러다임에서 중요한 역할을 담당하고 있습니다. A2A는 복잡한 멀티 에이전트 시스템을 위한 유연성을 제공하지만 잠재적으로 휴대성이 떨어지고 운영 복잡성이 높아질 수 있습니다. MCP는 멀티에이전트 오케스트레이션을 처리하도록 설계되지는 않았지만 구현 및 유지 관리가 더 간편한 도구 통합을 위한 표준화된 접근 방식을 제공합니다.</p><p>선택은 이분법적이지 않습니다. 뉴스룸의 예시를 통해 알 수 있듯이, 가장 정교하고 효과적인 LLM 지원 시스템은 에이전트가 A2A 프로토콜을 통해 조정하고 전문화하면서 MCP 서버를 통해 도구와 리소스에 액세스하는 두 가지 접근 방식을 결합하는 경우가 많습니다. 이 하이브리드 아키텍처는 MCP의 표준화 및 에코시스템의 장점과 함께 멀티 에이전트 시스템의 조직적 이점을 제공합니다. 이는 선택의 여지가 전혀 없을 수도 있음을 시사합니다. 두 가지를 모두 표준 접근 방식으로 사용하기만 하면 됩니다.</p><p>특정 사용 사례에 적합한 결과를 만들기 위해 두 솔루션을 가장 잘 조합하여 테스트하고 결정하는 것은 개발자 또는 아키텍트의 몫입니다. 각 접근 방식의 강점, 한계, 적절한 적용 사례를 이해하면 보다 효과적이고 유지 관리가 용이하며 확장 가능한 AI 시스템을 구축할 수 있습니다.</p><p>디지털 뉴스룸, 고객 서비스 플랫폼, 리서치 어시스턴트 또는 기타 LLM 기반 애플리케이션을 구축하는 경우, 조정 요구 사항(A2A)과 도구 액세스 요구 사항(MCP)을 신중하게 고려하면 성공의 길로 들어설 수 있습니다.</p><h2>추가 리소스</h2><ul><li><p><strong>Elasticsearch 에이전트 빌더: </strong><a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">https://www.elastic.co/docs/solutions/search/elastic-agent-builder</a></p></li><li><p><strong>A2A 사양:</strong> <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></p></li><li><p><strong>A2A 및 MCP 통합:</strong> <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">https://a2a-protocol.org/latest/topics/a2a-and-mcp/</a></p></li><li><p><strong>모델 컨텍스트 프로토콜:</strong> <a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b1f22cdc2130333/6a17f161ec0f8917fa5a6712/f87330e5d4ca961593b3cfb861ca850a4cc34186-1519x1173.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[맥락을 위한 검색 - 3부: 맥락 엔지니어링에서 하이브리드 검색의 힘]]></title>
    <description><![CDATA[컨텍스트 엔지니어링과 하이브리드 검색을 사용하여 집계, RBAC 및 비콘텐츠 신호로 AI 출력 정확도를 개선하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>지금까지 하이브리드 검색<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">(1부)</a>과 컨텍스트 엔지니어링<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">(2부)</a>에 대해 살펴봤는데, 이제 이 두 가지가 어떻게 함께 작동하여 RAG 및 에이전트 AI 운영에 타겟팅된 컨텍스트를 제공하는 데 가장 큰 효과를 가져오는지 살펴보겠습니다.</p><h2>검색은 죽지 않았고, 단지 이동했을 뿐입니다.</h2><p>따라서 주로 텍스트 상자를 통해 문맥을 검색하고 반환된 정보(문맥)를 사용하여 직접 답변을 구성하는 방식에서 이제는 자연어를 사용하여 상담원에게 원하는 것을 말하면 자동으로 검색하여 답변을 작성하는 방식으로 전환했습니다. 기술 업계의 많은 사람들이 이러한 변화를 지적하며 "검색은 죽었다"고 선언하고 있지만(물론 SEO와 애드워즈 세계는 <a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">확실히 변화하고</a> 있습니다 <a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">.</a> 누구세요?) 검색은 여전히 에이전트 운영에 절대적으로 중요하며, 지금은 대부분 도구를 통해 보이지 않는 곳에서 수행될 뿐입니다.</p><p>이전에는 사용자가 주관적인 관련성의 주요 중재자였습니다. 사용자마다 검색을 실행하는 이유가 다르고, 개인적인 경험에 따라 결과의 상대적 정확도가 달라집니다. 에이전트가 우리와 동일한(또는 더 나은) 결론에 도달할 수 있다고 믿으려면 에이전트가 액세스할 수 있는 컨텍스트 정보가 우리의 주관적인 의도에 최대한 가깝도록 보장해야 합니다. 우리는 그 목표를 향해 LLM을 제공하는 맥락을 설계해야 합니다!</p><h2>하이브리드 검색 검색을 통한 컨텍스트 생성</h2><p>1부에서 다시 한 번 말씀드리지만, Elastic의 하이브리드 검색은 기존 키워드 기반 검색의 강점(구문 유연성, 키워드 정밀도, 관련성 점수)과 벡터 유사성 검색의 의미론적 이해를 결합하고 다양한 재순위 지정 기술을 제공합니다. 이 시너지 효과(이 단어의 진정한 용도는 찾아볼 수 없습니다!) 를 사용하면 콘텐츠를 타겟팅하는 방식에 훨씬 더 미묘한 차이가 있는 쿼리를 통해 연관성이 높은 결과를 얻을 수 있습니다. 검색 단계 <em>중 하나로</em> 주관적 연관성을 적용할 수 있다는 것뿐만 아니라, 실제로는 1단계 검색에 다른 모든 모드와 함께 연관성 점수를 한 번에 포함할 수 있다는 것입니다.</p><h3>뛰어난 정확도 &amp; 효율성</h3><p>분산 검색, 검색 및 순위 재지정을 제공할 수 있는 데이터 플랫폼을 기본 컨텍스트 검색 엔진으로 사용하는 것은 매우 합리적입니다. 고급 쿼리 구문을 사용하여 주관적 의도의 누락된 구성 요소를 추가하고, 반환된 문맥 정보의 가치를 흐리게 하거나 방해할 수 있는 콘텐츠를 필터링할 수 있습니다. 사용 가능한 개별 구문 옵션 중에서 선택하거나 각 유형의 데이터를 가장 잘 이해하는 방식으로 타겟팅하는 단일 검색으로 모달리티를 결합한 다음 순위를 재조정하여 결합/재배열할 수 있습니다. 원하는 필드/값만 포함하도록 응답을 필터링하여 불필요한 데이터를 차단할 수 있습니다. 상담원 서비스에서는 이러한 타겟팅 유연성을 통해 컨텍스트를 검색하는 방식이 매우 정확한 툴을 구축할 수 있습니다.</p><h3>컨텍스트 세분화(집계 및 비콘텐츠 신호)</h3><p>집계는 도구가 컨텍스트 창에 제공하는 콘텐츠를 구성하는 데 특히 유용할 수 있습니다. 집계는 자연스럽게 반환된 컨텍스트 데이터의 형태에 대한 수치 기반 사실을 제공하므로, LLM이 더 쉽고 정확하게 추론할 수 있습니다. 집계는 계층적으로 중첩될 수 있기 때문에 LLM에 다단계 세부 정보를 쉽게 추가하여 보다 미묘한 차이를 파악할 수 있습니다. 집계는 컨텍스트 창 크기를 관리하는 데도 도움이 됩니다. 10만 개의 문서에 대한 쿼리 결과를 수백 개의 집계된 인사이트 토큰으로 쉽게 줄일 수 있습니다.</p><p>비콘텐츠 신호는 인기도, 신선도, 지리적 위치, 카테고리, 호스트 다양성, 가격대 등 결과의 추가적인 특성을 나타내는 데이터의 내재적 지표로, 현재 보고 있는 내용에 대한 더 큰 그림을 알려줍니다. 이러한 정보는 상담원이 수신한 컨텍스트의 중요도를 평가하는 데 유용할 수 있습니다. 몇 가지 간단한 예시를 통해 이를 가장 잘 설명할 수 있습니다:</p><ul><li><p><strong>최근에 게시된 인기 콘텐츠 강화하기</strong> - 문서에 대한 지식창고가 있다고 가정해 보세요. 사용자의 검색어와 관련된 문서를 찾고 싶지만, 최근 문서이면서 다른 사용자가 도움이 되었다고 판단한 문서(예: "좋아요" 수가 많은 문서)도 부스팅하고 싶을 수 있습니다. 이 시나리오에서는 하이브리드 검색을 사용하여 관련성 있는 문서를 찾은 다음 게시 날짜와 인기도를 조합하여 순위를 재조정할 수 있습니다.</p></li><li><p><strong>판매 및 재고 조정 기능이 있는 전자상거래 검색</strong> - 전자상거래 환경에서는 고객에게 검색어와 일치하는 제품을 표시하는 동시에 잘 팔리고 재고가 있는 제품을 홍보하고 싶을 수 있습니다. 또한 재고가 적은 제품의 순위를 낮춰 고객의 불만을 피할 수도 있습니다.</p></li><li><p><strong>버그 트래커에서 심각도가 높은 이슈 우선 순위 지정하기</strong> - 소프트웨어 개발팀의 경우 이슈를 검색할 때 심각도가 높고 우선 순위가 높으며 최근에 업데이트된 이슈를 먼저 표시하는 것이 중요합니다. '중요도' 및 '가장 많이 논의된' 등의 비신호를 사용하여 다양한 요소를 독립적으로 평가하여 가장 중요하고 활발하게 논의된 이슈가 맨 위에 표시되도록 할 수 있습니다.</p></li></ul><p>이러한 예제 쿼리 등은 함께 제공되는 Elasticsearch Labs <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">콘텐츠 페이지에서</a> 확인할 수 있습니다.</p><h3>보안 시행</h3><p>컨텍스트 엔지니어링을 위해 Elastic과 같은 검색 기반 속도 계층을 활용할 때의 중요한 장점은 기본 제공 보안 프레임워크입니다. Elastic의 플랫폼은 세분화된 역할 기반 액세스 제어(RBAC)와 속성 기반 액세스 제어(ABAC)를 통해 에이전트 및 생성 AI 작업에 제공되는 컨텍스트가 민감한 개인 보유 정보를 존중하고 보호하도록 보장합니다. 즉, 쿼리가 효율적으로 처리될 뿐만 아니라 요청을 시작한 상담원이나 사용자의 특정 권한에 따라 결과가 필터링됩니다.</p><p>에이전트는 인증된 사용자로 실행되므로 플랫폼에 내장된 보안 기능을 통해 보안이 암시적으로 적용됩니다:</p><ul><li><p><strong>세분화된 권한:</strong> 문서, 필드 또는 용어 수준에서 액세스 권한을 정의하여 AI 에이전트가 볼 권한이 있는 데이터만 받도록 하세요.</p></li><li><p><strong>역할 기반 액세스 제어(RBAC):</strong> 에이전트 또는 사용자에게 역할을 할당하여 정의된 책임에 따라 특정 데이터 세트 또는 기능에 대한 액세스 권한을 부여합니다.</p></li><li><p><strong>속성 기반 액세스 제어(ABAC):</strong> 데이터, 사용자 또는 환경의 속성을 기반으로 동적 액세스 정책을 구현하여 고도로 적응력이 뛰어나고 상황에 맞는 보안을 구현할 수 있습니다.</p></li><li><p><strong>문서 수준 보안(DLS) 및 필드 수준 보안(FLS):</strong> 이러한 기능은 검색된 문서 내에서도 승인된 부분만 볼 수 있도록 하여 민감한 정보가 노출되는 것을 방지합니다.</p></li><li><p><strong>엔터프라이즈 보안과 통합:</strong> 기존 ID 관리 시스템(예: LDAP, SAML, OIDC)과 원활하게 통합하여 조직 전체에 일관된 보안 정책을 적용할 수 있습니다.</p></li></ul><p>이러한 보안 조치를 컨텍스트 검색 메커니즘에 직접 통합함으로써 Elastic은 보안 게이트키퍼 역할을 수행하여 AI 에이전트가 정의된 데이터 경계 내에서 작동하도록 보장하고 무단 데이터 노출을 방지하며 데이터 개인 정보 보호 규정을 준수하도록 유지합니다. 이는 기밀 또는 독점 정보를 처리하는 에이전트 AI 시스템에 대한 신뢰를 구축하는 데 가장 중요한 요소입니다.</p><p>추가로, 엔터프라이즈 데이터 소스에서 통합 데이터 속도 계층을 사용하면 에이전트 도구가 생성하는 리포지토리의 예기치 않은 임시 쿼리 부하를 완화할 수 있습니다. 한 곳에서 모든 것을 거의 실시간으로 검색하고 보안 및 거버넌스 제어를 적용할 수 있습니다.</p><h2>하이브리드 검색 기반 도구</h2><p>컨텍스트 엔지니어링의 추구를 가속화하는 Elastic 플랫폼의 몇 가지 핵심 기능( <a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">계속 추가될</a> 예정)이 있습니다. 여기서 가장 중요한 것은 플랫폼이 AI 생태계가 발전함에 따라 유연하게 적응, 변경, 확장할 수 있는 다양한 방법을 제공한다는 점입니다.</p><h3>에이전트 빌더 소개</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">에이전트 빌더는</a> Elastic에 이미 저장되어 있는 데이터와 채팅할 수 있도록 구축된 에이전트 AI 도구 영역에 처음으로 진출한 제품입니다. 에이전트 빌더는 사용자가 Kibana 내에서 자신만의 에이전트와 도구를 생성하고 관리할 수 있는 채팅 인터페이스를 제공합니다. 기본 제공 MCP 및 A2A 서버, 프로그래밍 방식의 API, Elasticsearch 인덱스를 쿼리 및 탐색하고 자연어로부터 ES|QL 쿼리를 생성하기 위한 사전 구축된 시스템 도구 세트가 함께 제공됩니다. 에이전트 빌더를 사용하면 표현식 <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> 쿼리 구문을 통해 에이전트에게 반환되는 컨텍스트 데이터를 타겟팅하고 조각하는 사용자 지정 도구를 만들 수 있습니다.</p><p>ES|QL은 하이브리드 검색을 어떻게 수행하나요? <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">핵심 기능은 semantic_text</a> 필드 유형과 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"></a><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FORK/FUSE</a> 명령의 조합을 통해 수행됩니다(FUSE는 기본적으로 각 포크의 결과를 병합하는 데 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF를 사용합니다).</a> 다음은 가상의 제품 검색에 대한 간단한 예제입니다:</p>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type<p>위의 예제에서 각 FORK 브랜치에 포함된 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a> 절은 반드시 필요한 것은 아니며, 특정 검색 결과가 어떤 검색 방식에서 반환되었는지 추적하는 방법을 보여주기 위해 포함되었을 뿐입니다.</p><h3>템플릿 검색</h3><p>자체 외부 에이전트 도구를 Elastic 배포로 가리키고 싶다고 가정해 보겠습니다. 또한 ES|QL 대신 다단계 검색기를 사용하거나 개발한 기존 DSL 구문을 재사용하고 쿼리가 허용하는 입력, 검색 실행에 사용되는 구문 및 출력에 반환되는 필드를 제어할 수 있기를 원합니다. <a href="https://www.elastic.co/docs/solutions/search/search-templates">검색 템플릿을</a> 사용하면 일반적인 검색 패턴에 대해 미리 정의된 구조를 정의하여 데이터 검색의 효율성과 일관성을 개선할 수 있습니다. 이는 상용구 코드를 표준화하고 검색 로직의 빠른 반복을 가능하게 하므로 검색 API와 상호 작용하는 에이전트 도구에 특히 유용합니다. 이러한 요소 중 하나를 조정해야 하는 경우 검색 템플릿을 업데이트하기만 하면 변경 사항이 바로 적용됩니다. 에이전트 도구에서 작동하는 검색 템플릿의 예를 찾고 계신다면, 외부 MCP 서버에서 도구 호출 뒤에 검색 템플릿을 활용하는 Elasticsearch Labs 블로그 '<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">지능형 검색을 위한 MCP</a>'를 살펴보시기 바랍니다.</p><h3>통합 워크플로(FTW!)</h3><p>새로운 에이전트 AI 세계에서 가장 어려운 점 중 하나는 반자율적이고 자기 주도적인 '추론' 에이전트의 비결정적 특성입니다. 컨텍스트 엔지니어링은 에이전트 AI의 중요한 분야로, 에이전트가 생성할 수 있는 결론의 범위를 우리가 알고 있는 사실에 근거하여 좁히는 데 도움이 되는 기술입니다. 매우 정확하고 관련성이 높은 컨텍스트 창이 있더라도 (수치적 사실의 영역을 벗어나면) 상담원의 응답이 완전히 반복 가능하고 신뢰할 수 있다는 확신을 줄 수 있는 부분이 여전히 부족합니다.</p><p>상담원에게 동일한 요청을 여러 번 실행하면 응답에 약간의 차이가 <em>있을 뿐</em> <em>본질적으로</em> 동일한 답변이 나올 수 있습니다. 이는 보통 눈에 띄지 않을 정도로 단순한 쿼리의 경우 괜찮으며 컨텍스트 엔지니어링 기법을 사용하여 결과물을 구체화할 수 있습니다. 하지만 상담원에게 요청하는 작업이 복잡해짐에 따라 하나 이상의 하위 작업으로 인해 최종 결과가 약간 달라질 수 있는 변수가 발생할 가능성이 커지고 있습니다. 상담원 간 커뮤니케이션에 더 많이 의존하기 시작하면 이러한 차이는 더욱 심해질 것이며, 이러한 차이는 누적될 것입니다. 이는 상담원이 상호작용하는 툴이 컨텍스트 데이터를 정확하게 타겟팅할 수 있도록 매우 유연하고 조정이 가능해야 하며, 예상 출력 형식으로 응답해야 한다는 점을 다시 한 번 강조합니다. 또한 많은 사용 사례에서 에이전트와 툴의 상호 작용을 지시해야 할 필요가 있음을 나타내며, 바로 여기에서 워크플로우가 등장합니다!</p><p>Elastic은 곧 플랫폼의 핵심에 완전히 사용자 정의 가능한 워크플로우를 내장할 예정입니다. 이러한 워크플로는 상담원 및 툴과 양방향으로 작동할 수 있으므로 워크플로는 상담원 및 툴을 호출할 수 있고, 상담원 및 툴은 워크플로를 호출할 수 있게 됩니다. 이러한 기능이 모든 데이터가 있는 동일한 검색 AI 플랫폼에 완전히 통합되어 워크플로우를 혁신적으로 변화시킬 수 있는 잠재력은 매우 흥미롭습니다! 곧 출시됩니다!</p><h3>통합 메모리 뱅크로서의 Elastic</h3><p>실시간에 가까운 검색을 위해 만들어진 분산 데이터 플랫폼이기 때문에, Elastic은 에이전트 AI 시스템을 위한 장기 메모리 기능을 자연스럽게 수행합니다. 기본 제공되는 상담원 빌더 채팅 환경을 통해 단기 기억 및 채팅 기록을 추적하고 관리할 수도 있습니다. 그리고 전체 플랫폼이 API 우선이기 때문에, 에이전트의 컨텍스트 창을 압도할 수 있는 도구의 컨텍스트 출력을 유지(그리고 나중에 참조할 수 있도록)하기 위한 플랫폼으로 Elastic을 매우 쉽게 활용할 수 있습니다. 이 기술은 컨텍스트 엔지니어링 업계에서 "<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">메모 작성</a>"이라고도 불립니다.</p><p>동일한 검색 플랫폼에서 단기 메모리와 장기 메모리를 모두 사용하면 많은 본질적인 이점을 얻을 수 있습니다. 채팅 기록과 지속적 문맥 반응을 향후 채팅 상호작용에 시맨틱 영향력의 일부로 사용하거나 위협 분석을 수행하거나 자주 반복되는 도구 호출에서 자동으로 생성되는 지속적 데이터 제품을 만들 수 있다고 상상해 보세요... 가능성은 무궁무진합니다!</p><h2>결론</h2><p>대규모 언어 모델의 등장으로 콘텐츠를 매칭하는 방식과 데이터를 조사하는 방법이 바뀌었습니다. 사람이 직접 조사하고, 맥락을 고려하고, 논리적 추론을 통해 질문에 답하는 현재의 세상에서 에이전트 AI를 통해 이러한 단계가 대부분 자동화되는 세상으로 빠르게 전환되고 있습니다. 생성된 답변을 신뢰할 수 있으려면 상담원이 답변을  생성할 때 <em>가장 관련성이 높은 모든</em> 정보(주관적 관련성 요소 포함)를 고려했다는 확신이 있어야 합니다. 에이전트 AI를 신뢰할 수 있게 만드는 기본 방법은 RAG 및 컨텍스트 엔지니어링 기술을 통해 추가 컨텍스트를 검색하는 도구를 기반으로 하는 것이지만, 이러한 도구가 <em>초기 검색을</em> 수행하는 방식은 응답의 정확성에 매우 중요할 수 있습니다.</p><p>Elastic Search AI 플랫폼은 정확성, 성능, 확장성 측면에서 에이전트 AI를 지원하는 여러 기본 제공 기능과 함께 하이브리드 검색의 유연성과 이점을 제공합니다. 즉, Elastic은 컨텍스트 엔지니어링의 여러 측면을 위한 환상적인 플랫폼입니다! 검색 플랫폼을 통해 컨텍스트 검색을 표준화함으로써 여러 측면에서 에이전트 도구 운영을 간소화하며, '느려야 빨리 간다'는 모순처럼 컨텍스트 생성 계층에서의 간소화는 더 빠르고 더 신뢰할 수 있는 에이전트 AI를 의미합니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a203a316f0e22e/6a170932b339d58ebc769f5f/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[컨텍스트에 대한 이해 - 2부: 에이전트 AI와 컨텍스트 엔지니어링의 필요성]]></title>
    <description><![CDATA[에이전트 AI를 향한 LLM의 진화로 인해 RAG 컨텍스트 제한과 메모리 관리를 해결하기 위한 컨텍스트 엔지니어링의 필요성이 어떻게 증가하고 있는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>LLM이 정보 검색의 기본 프로세스를 변화시킨 방식에 대한 (상당히 광범위한) <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">배경</a> 지식을 바탕으로, 데이터 쿼리 방식도 어떻게 변화시켰는지 살펴봅시다.</p><h2>데이터와 상호 작용하는 새로운 방법</h2><p>제너레이티브(genAI) 및 에이전트 AI는 기존 검색과는 다른 방식으로 작업을 수행합니다. 과거에는 검색("구글에 검색해 볼게요...")을 통해 정보 조사를 시작했지만, 세대별 AI와 상담원 모두 채팅 인터페이스에 자연어를 입력하는 것이 시작 작업의 대부분을 차지합니다. 채팅 인터페이스는 LLM과의 토론으로, 의미론적 이해를 바탕으로 우리의 질문을 모든 종류의 정보에 대한 폭넓은 지식을 가진 오라클이 제공하는 것처럼 보이는 요약된 답변으로 바꿔줍니다. LLM의 진정한 매력은 드러나는 지식의 조각들을 하나로 묶어 일관성 있고 사려 깊은 문장을 만들어내는 능력입니다. 부정확하거나 완전히 환각적인 내용일지라도 <a href="https://en.wikipedia.org/wiki/Truthiness">진실성이</a> 담겨 있습니다.</p><p>우리가 익숙한 검색창은 우리가 <em><strong>직접</strong></em> 추론 에이전트였을 때 사용했던 RAG 엔진이라고 생각할 수 있습니다. 이제 인터넷 검색 엔진도 기존의 '사냥과 쪼기' 방식의 어휘 검색 경험을 AI 기반 개요로 전환하여 검색어에 대한 답변과 결과 요약을 제공함으로써 사용자가 개별 결과를 직접 클릭하고 평가할 필요가 없도록 돕고 있습니다.</p><h2>제너레이티브 AI &amp; RAG</h2><p>생성형 AI는 세상에 대한 의미론적 이해를 바탕으로 채팅 요청에 명시된 주관적 의도를 분석한 다음 추론 능력을 사용하여 즉석에서 전문가 답변을 생성합니다. 생성형 AI 상호작용에는 사용자의 입력/질문으로 시작하여 채팅 세션의 이전 대화를 추가 컨텍스트로 사용할 수 있으며, LLM에 추론 방법과 응답을 구성할 때 따라야 할 절차를 알려주는 지시 프롬프트 등 여러 부분이 있습니다. 안내 메시지는 " "5살짜리 아이처럼 설명해 주세요"라는 단순한 안내에서 요청 처리 방법에 대한 완전한 분석으로 발전했습니다. 이러한 분류에는 종종 AI의 페르소나/역할, 생성 전 추론/내부 사고 과정, 객관적 기준, 제약 조건, 출력 형식, 대상에 대한 세부 사항을 설명하는 별도의 섹션과 예상 결과를 입증하는 데 도움이 되는 예시가 포함됩니다.</p><p>검색 증강 생성(RAG)은 사용자의 쿼리와 시스템 프롬프트 외에도 "컨텍스트 창"이라고 하는 추가 컨텍스트 정보를 제공합니다. RAG는 아키텍처에 중요한 추가 기능으로, LLM이 세계를 의미론적으로 이해하는 데 있어 누락된 부분을 알려주는 데 사용됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfa000ccfdd9d184/6a17ddb57b54f955f38b37da/5b9671d5d07d4caefde372bb3188000754a91eed-1470x746.png" alt="LLM이 사용자 쿼리를 처리하고 컨텍스트를 생성하는 방법" /><p>컨텍스트 창은 무엇을, 어디에, 얼마나 제공해야 하는지에 대해 다소 <a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html">까다로울</a> 수 있습니다. 물론 어떤 컨텍스트가 선택되는지도 매우 중요하지만, 제공된 컨텍스트의 신호 대 잡음비와 창 길이도 중요합니다.</p><h3>너무 적은 정보</h3><p>쿼리, 프롬프트 또는 컨텍스트 창에 너무 적은 정보를 제공하면 LLM이 응답을 생성할 올바른 의미적 컨텍스트를 정확하게 판단할 수 없기 때문에 착각이 발생할 수 있습니다. 문서 청크 크기의 벡터 유사성에도 문제가 있습니다. 짧고 간단한 질문이 벡터화된 지식창고에 있는 풍부하고 상세한 문서와 의미적으로 일치하지 않을 수 있습니다. <a href="https://medium.com/data-science/how-to-use-hyde-for-better-llm-rag-retrieval-a0aa5d0e23e8">가상의 문서 임베딩(HyDE)</a> 과 같은 쿼리 확장 기법이 개발되어 LLM을 사용하여 짧은 쿼리보다 더 풍부하고 표현력이 풍부한 가상의 답변을 생성할 수 있습니다. 물론 여기서 위험은 가상의 문서 자체가 올바른 맥락에서 더욱 멀어지게 하는 환각이라는 점입니다.</p><h3>너무 많은 정보</h3><p>우리 인간과 마찬가지로, 컨텍스트 창에 너무 많은 정보가 있으면 LLM이 중요한 부분이 무엇인지 압도당하고 혼란스러워할 수 있습니다. 컨텍스트 오버플로(또는 "<a href="https://research.trychroma.com/context-rot">컨텍스트 썩</a>음")는 생성 AI 작업의 품질과 성능에 영향을 미치며, LLM의 "주의 예산"(작업 메모리)에 큰 영향을 미치고 여러 경쟁 토큰 간의 관련성을 희석시킵니다. '컨텍스트 썩음'의 개념에는 LLM이 중간 섹션의 콘텐츠보다 컨텍스트 창의 시작 또는 끝에 있는 콘텐츠를 선호하는 <a href="https://alexandrabarr.beehiiv.com/p/context-windows">위치 편향이</a> 있다는 관찰 결과도 포함됩니다.</p><h3>산만하거나 상충되는 정보</h3><p>컨텍스트 창이 커질수록 불필요하거나 상충되는 정보가 포함될 가능성이 높아져 LLM이 올바른 컨텍스트를 선택하고 처리하는 데 방해가 될 수 있습니다. 어떤 면에서는 가비지 인/가비지 아웃의 문제가 됩니다. 문서 결과 집합을 컨텍스트 창에 덤핑하는 것만으로도 LLM이 처리해야 할 정보가 너무 많지만, 컨텍스트가 어떻게 선택되었는지에 따라 상충되거나 관련 없는 정보가 스며들 가능성이 더 커질 수 있기 때문입니다.</p><h2>에이전틱 AI</h2><p>다뤄야 할 내용이 많다고 말씀드렸지만, 드디어 에이전트 AI 주제에 대해 이야기하게 되었습니다! 에이전트 AI는 사용자가 제공한 지식과 문맥 정보를 바탕으로 답변을 합성하는 제너레이티브 AI의 (이미 '레거시'라고 불러도 될까요?) 기능을 확장한 LLM 채팅 인터페이스의 매우 흥미로운 새로운 사용법입니다. 제너레이티브 AI가 더욱 성숙해지면서 처음에는 사람이 쉽게 확인/검증할 수 있는 지루하고 위험도가 낮은 활동으로 한정했던 작업을 LLM이 수행할 수 있는 일정 수준의 작업과 자동화가 가능하다는 것을 깨달았습니다. 짧은 기간 동안 초기 범위가 확장되어 이제 LLM 채팅 창은 AI 에이전트가 자율적으로 계획을 수립하고 실행하며 지정된 목표를 달성하기 위해 계획을 반복적으로 평가하고 조정하도록 하는 촉매제가 될 수 있습니다. 상담원은 LLM의 추론, 채팅 기록 및 사고 기억(있는 그대로)에 액세스할 수 있으며, 이러한 목표를 위해 활용할 수 있는 특정 도구도 마련되어 있습니다. 또한 최상위 에이전트가 각각 고유한 로직 체인, 명령어 세트, 컨텍스트 및 도구를 갖춘 여러 <a href="https://www.philschmid.de/the-rise-of-subagents">하위 에이전트의</a> 오케스트레이터로 기능할 수 있는 아키텍처도 등장하고 있습니다.</p><p>상담원은 대부분 자동화된 워크플로우의 시작점으로, 사용자와 채팅한 다음 '로직'을 사용하여 사용자의 질문에 답할 수 있는 도구를 결정할 수 있다는 점에서 자기 주도적입니다. 도구는 일반적으로 에이전트에 비해 수동적인 것으로 간주되며 한 가지 유형의 작업을 수행하도록 만들어집니다. 툴이 수행할 수 있는 작업의 <em>유형은</em> 무궁무진하지만(정말 흥미롭습니다!) 툴이 수행하는 주요 작업은 상담원이 워크플로우를 실행할 때 고려할 수 있도록 컨텍스트 정보를 수집하는 것입니다.</p><p>에이전트 AI는 아직 초기 단계의 기술로서 주의력 결핍 장애에 해당하는 LLM에 취약하며, 요청받은 작업을 쉽게 잊어버리고 업무에 전혀 포함되지 않은 다른 일을 하러 도망가는 경우가 많습니다. 겉보기에는 마술처럼 보이지만, LLM의 '추론' 능력은 여전히 시퀀스에서 다음으로 가능성이 높은 토큰을 예측하는 것을 기반으로 합니다. 추론(또는 언젠가는 인공 일반 지능(AGI)이)이 신뢰할 수 있고 신뢰할 수 있으려면 정확한 최신 정보가 주어졌을 때 우리가 기대하는 방식으로 추론할 수 있는지(그리고 우리가 미처 생각하지 못했던 것을 조금 더 제공할 수도 있는지) 검증할 수 있어야 합니다. 이를 위해서는 에이전트 아키텍처가 명확하게 커뮤니케이션하고(프로토콜), 워크플로와 제약 조건을 준수하며(가드레일), 작업의 현재 위치를 기억하고(상태), 사용 가능한 메모리 공간을 관리하고, 응답이 정확하고 작업 기준을 충족하는지 검증할 수 있는 기능이 필요합니다.</p><h2>내가 이해할 수 있는 언어로 대화하기</h2><p>새로운 개발 영역에서 흔히 그렇듯이(특히 LLM의 세계에서는 더욱 그렇습니다) 처음에는 에이전트 간 커뮤니케이션을 위한 여러 가지 접근 방식이 있었지만, 사실상의 표준인 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">모델 컨텍스트 프로토콜(MCP)</a> 로 빠르게 수렴되었습니다. 모델 컨텍스트 프로토콜의 정의는 이름 그대로  <strong>모델이</strong> <strong>컨텍스트</strong> 정보를 요청하고 수신하는 데 사용하는 프로토콜입니다. MCP는 LLM 에이전트가 외부 도구 및 데이터 소스에 연결할 수 있는 범용 어댑터 역할을 하며, 서로 다른 LLM 프레임워크와 도구가 쉽게 상호 운용될 수 있도록 API를 단순화하고 표준화합니다. 따라서 MCP는 에이전트가 목표를 달성하기 위해 자율적으로 수행하도록 에이전트에 제공되는 오케스트레이션 로직 및 시스템 프롬프트와 보다 격리된 방식으로 수행하도록 툴로 전송되는 작업(적어도 시작 에이전트와 관련해서는 격리된) 사이의 일종의 구심점 역할을 합니다.</p><p>이 에코시스템은 모든 방향이 새로운 개척지처럼 느껴질 정도로 모든 것이 새롭습니다. 에이전트 간 상호작용을 위한 유사한 프로토콜<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">(에이전트2에이전트(A2A)</a> natch!)과 에이전트 추론 메모리 개선<a href="https://venturebeat.com/ai/new-memory-framework-builds-ai-agents-that-can-handle-the-real-worlds">(ReasoningBank</a>), 현재 작업에 가장 적합한 MCP 서버 선택<a href="https://arxiv.org/abs/2505.03275">(RAG-MCP</a>), 입력 및 출력의 제로 샷 분류 및 패턴 감지 등의 의미 분석을 <a href="https://openai.github.io/openai-guardrails-python/">가드레일로</a> 사용하여 에이전트의 작업 허용 대상을 제어하기 위한 다른 프로젝트도 있습니다.</p><p>이러한 각 프로젝트의 기본 의도가 에이전트/genAI 컨텍스트 창에 반환되는 정보의 품질과 제어를 개선하는 것임을 눈치채셨나요? 에이전트 AI 생태계가 컨텍스트 정보를 더 잘 처리(제어, 관리 및 운영)할 수 있는 기능을 계속 개발하는 동안에도 에이전트가 밀링할 수 있는 <em>가장 관련성</em> 높은 컨텍스트 정보를 검색해야 할 필요성은 항상 존재할 것입니다.</p><h2>컨텍스트 엔지니어링에 오신 것을 환영합니다!</h2><p>제너레이티브 AI 용어에 익숙하다면 '프롬프트 엔지니어링'에 대해 들어보셨을 텐데요, 지금은 거의 유사 과학에 가깝다고 할 수 있습니다. 프롬프트 엔지니어링은 LLM이 응답을 생성할 때 사용할 동작을 사전에 설명하는 가장 효율적인 최선의 방법을 찾는 데 사용됩니다. '<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">컨텍스트 엔지니어링</a>'은 '프롬프트 엔지니어링' 기술을 에이전트 측면을 넘어 MCP 프로토콜의 도구 측면에서 사용 가능한 컨텍스트 소스 및 시스템까지 포함하도록 확장하며, 컨텍스트 관리, 처리 및 생성에 대한 광범위한 주제를 다룹니다:</p><ul><li><p><strong>컨텍스트 관리 </strong>- 장기 실행 및/또는 복잡한 상담원 워크플로 전반에서 상태 및 컨텍스트 효율성을 유지하는 것과 관련이 있습니다. 상담원의 목표를 달성하기 위한 작업 및 도구 호출의 반복적인 계획, 추적 및 오케스트레이션. 상담원이 작업할 수 있는 '주의 예산'이 제한되어 있기 때문에 컨텍스트 관리는 주로 컨텍스트 창을 세분화하여 전체 범위와 가장 중요한 컨텍스트(정확도 대비 회상률!)를 모두 포착하는 데 도움이 되는 기술과 관련이 있습니다. 압축, 요약, 이전 단계 또는 도구 호출의 컨텍스트를 유지하여 작업 메모리에 후속 단계의 추가 컨텍스트를 위한 공간을 확보하는 기술 등이 있습니다.</p></li><li><p><strong>컨텍스트 처리 </strong>- 에이전트가 모든 컨텍스트를 다소 일관된 방식으로 추론할 수 있도록 서로 다른 소스에서 얻은 컨텍스트를 통합, 정규화 또는 정제하는 논리적이고 대부분 프로그램적인 단계입니다. 기본 작업은 에이전트가 최대한 효율적으로 소비할 수 있는 모든 소스(프롬프트, RAG, 메모리 등)에서 컨텍스트를 만드는 것입니다. </p></li><li><p>컨텍스트 <strong>생성 </strong>- 컨텍스트 처리가 검색된 컨텍스트를 상담원이 사용할 수 있도록 만드는 것이라면 컨텍스트 생성은 상담원이 마음대로 추가 컨텍스트 정보를 요청하고 수신할 수 있는 기능을 제공하지만 제약 조건도 함께 제공합니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e1e68c08fe050bc/6a17ddb7414c645035945073/4a8240e1eb078b2294b8d981b9caa8593589cac4-1600x900.png" alt="LLM의 컨텍스트 엔지니어링" /><p>LLM 채팅 애플리케이션의 다양한 형태는 컨텍스트 엔지니어링의 높은 수준의 기능에 직접적으로(때로는 중복되는 방식으로) 매핑됩니다:</p><ul><li><p><strong>지침/시스템 프롬프트</strong> - 프롬프트는 생성(또는 에이전트) AI 활동이 사용자의 목표를 달성하기 위해 어떻게 사고를 유도할지에 대한 발판이 됩니다. 프롬프트는 그 자체로 컨텍스트이며, 단순한 톤의 지시가 아니라 사용자의 요청을 완전히 충족하는지 확인하기 위해 응답하기 전에 '단계별로 생각하기', '심호흡하기' 등의 작업 실행 로직과 규칙이 포함되어 있는 경우가 많습니다. 최근 테스트에 따르면 마크업 언어는 프롬프트의 여러 부분을 구성하는 데 매우 효과적이지만 너무 모호한 것과 너무 구체적인 것 사이의 적절한 지점으로 지침을 조정하는 데에도 주의를 기울여야 합니다. 우리는 LLM이 올바른 맥락을 찾을 수 있도록 충분한 지침을 제공하되 너무 규범적이어서 예상치 못한 통찰력을 놓치지 않기를 원합니다.</p></li><li><p><strong>단기 메모리</strong> (상태/기록) - 단기 메모리는 본질적으로 사용자와 LLM 간의 채팅 세션 상호작용입니다. 이는 라이브 세션에서 컨텍스트를 구체화하는 데 유용하며, 나중에 검색하고 계속 사용할 수 있도록 저장할 수 있습니다. </p></li><li><p><strong>장기 기억</strong> - 장기 기억은 여러 세션에 걸쳐 유용한 정보로 구성되어야 합니다. 또한 RAG를 통해 액세스하는 도메인별 지식 기반뿐만 아니라, 최근 연구에서는 이전 에이전트/제너레이티브 AI 요청의 결과를 사용하여 현재 에이전트 상호 작용 내에서 학습하고 참조할 수 있습니다. 장기 기억 공간에서 가장 흥미로운 혁신 중 일부는 에이전트가 중단한 부분을 다시 시작할 수 있도록 상태를 <a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a">저장하고 연결하는</a> 방식을 조정하는 것과 관련이 있습니다. </p></li><li><p><strong>구조화된 출력</strong> - 인지에는 노력이 필요하므로 추론 능력이 있더라도 (인간과 마찬가지로) LLM도 생각할 때 노력을 덜 들이고 싶어하며, 정의된 API나 프로토콜이 없는 경우 도구 호출에서 반환된 데이터를 읽는 방법에 대한 맵(스키마)이 있으면 매우 유용하다는 것은 놀라운 일이 아닐 수 없습니다. 에이전트 프레임워크의 일부로 <a href="https://platform.openai.com/docs/guides/structured-outputs?lang=javascript">구조화된 출력을</a> 포함하면 이러한 기계 간 상호 작용을 더 빠르고 안정적으로 수행할 수 있으며, 사고에 기반한 구문 분석이 덜 필요하게 됩니다.</p></li><li><p><strong>사용 가능한 도구</strong> - 도구는 추가 정보 수집(예: 엔터프라이즈 데이터 리포지토리에 대한 RAG 쿼리 발행 또는 온라인 API를 통한)부터 상담원을 대신하여 자동화된 작업 수행(예: 상담원의 요청 기준에 따라 호텔 객실 예약)까지 모든 종류의 작업을 수행할 수 있습니다. 도구는 자체 에이전트 처리 체인을 가진 하위 에이전트가 될 수도 있습니다. </p></li><li><p><strong>검색 증강 생성(RAG)</strong> - "동적 지식 통합"이라는 RAG에 대한 설명이 정말 마음에 듭니다. 앞서 설명한 것처럼 RAG는 학습할 때 LLM이 접근하지 못했던 추가 정보를 제공하거나 정답을 얻기 위해 가장 중요하다고 생각되는 아이디어, 즉 주관적인 질문과 가장 관련성이 높은 아이디어를 반복하는 기법입니다.</p></li></ul><h2>경이로운 우주의 힘, 아주 작은 생활 공간!</h2><p>에이전트 AI에는 탐험할 수 있는 흥미롭고 새로운 영역이 정말 많습니다! 여전히 해결해야 할 오래된 전통적인 데이터 검색 및 처리 문제도 많지만, 새로운 LLM 시대를 맞아 이제야 빛을 보게 된 완전히 새로운 종류의 문제도 있습니다. 현재 우리가 당면한 많은 문제는 제한된 작업 메모리 공간에 부담을 주지 않으면서도 LLM에 필요한 추가 컨텍스트 정보를 제공하는 컨텍스트 엔지니어링과 관련이 있습니다.</p><p>다양한 도구(및 다른 에이전트)에 액세스할 수 있는 반자율 에이전트의 유연성으로 인해 AI 구현을 위한 새로운 아이디어가 너무 많이 생겨나서 어떤 방식으로 조합할 수 있을지 가늠하기조차 어렵습니다. 현재 대부분의 연구는 컨텍스트 엔지니어링 분야에 속하며 더 많은 양의 컨텍스트를 처리하고 추적할 수 있는 메모리 관리 구조를 구축하는 데 중점을 두고 있는데, 이는 LLM이 실제로 해결하기를 원하는 심층 사고 문제는 복잡성이 증가하고 기억이 매우 중요한 장기적이고 다단계적인 사고 단계가 존재하기 때문입니다.</p><p>현장에서 진행 중인 많은 실험은 에이전트에게 최적의 작업 관리 및 도구 구성을 제공하기 위해 노력하고 있습니다. 상담원의 추론 체인에서 각 툴 호출은 해당 툴의 기능을 수행하기 위한 컴퓨팅과 제한된 컨텍스트 창에 미치는 영향 측면에서 누적 비용을 발생시킵니다. LLM 에이전트의 컨텍스트를 관리하는 최신 기술 중 일부는 장기 실행 작업에 대해 누적된 컨텍스트를 압축/요약하면 손실이 너무 <em>커지는</em>' 컨텍스트 붕괴'와<a href="https://venturebeat.com/ai/ace-prevents-context-collapse-with-evolving-playbooks-for-self-improving-ai">같은 의도하지 않은 연쇄 효과를 발생시켰습니다.</a> 원하는 결과는 간결하고 정확한 컨텍스트를 반환하는 도구로, 불필요한 정보가 소중한 컨텍스트 창 메모리 공간으로 유출되지 않도록 하는 것입니다.</p><h3>너무 많은/너무 많은 가능성</h3><p>도구/구성 요소를 재사용할 수 있는 유연성을 갖춘 업무 분리를 원하므로 특정 데이터 소스에 연결하기 위한 전용 에이전트 도구를 만드는 것이 좋습니다. 각 도구는 한 유형의 리포지토리, 한 유형의 데이터 스트림 또는 하나의 사용 사례 쿼리에 특화될 수 있습니다. 하지만 주의하세요: 시간/비용을 절약하고 무언가 가능하다는 것을 증명하기 위해 LLM을 연합 도구로 사용하고 싶은 강한 유혹이 있을 것입니다... 그러지 마세요, 저희도 <a href="https://www.elastic.co/pdf/elastic-distributed-not-federated-search.pdf">그 길을</a> 가본 적이 있습니다! 연합 쿼리는 들어오는 쿼리를 원격 리포지토리가 이해하는 구문으로 변환한 다음 여러 소스의 결과를 어떻게든 일관된 응답으로 합리화해야 하는 '범용 번역기' 같은 역할을 합니다. 페더레이션은 소규모에서는  <em>잘</em> 작동하지만 대규모, 특히 데이터가 멀티모달인 경우 페더레이션은 너무 넓은 간격을 메우기 위해 시도합니다.</p><p>에이전트 세계에서는 에이전트가 페더레이터가 되고 도구(MCP를 통해)는 서로 다른 리소스에 수동으로 정의된 연결이 됩니다. 전용 도구를 사용하여 서로 연결되지 않은 데이터 원본에 접근하는 것은 쿼리별로 서로 다른 데이터 스트림을 동적으로 통합하는 강력하고 새로운 방법처럼 보일 수 있지만, 도구를 사용하여 여러 원본에 동일한 질문을 하는 것은 결국 해결되는 문제보다 더 많은 문제를 야기할 수 있습니다. 이러한 데이터 소스 각각은 그 아래에 서로 다른 유형의 리포지토리가 있을 가능성이 높으며, 그 안의 데이터를 검색, 순위 지정 및 보호하기 위한 고유한 기능을 갖추고 있습니다. 물론 리포지토리 간의 이러한 차이 또는 "임피던스 불일치"는 처리 부하를 증가시킵니다. 또한 상충되는 정보나 신호가 발생할 수 있는데, 점수 정렬 오류처럼 별것 아닌 것처럼 보이는 것이 반환된 컨텍스트의 중요성을 크게 떨어뜨리고 결국 생성된 응답의 관련성에 영향을 미칠 수 있습니다.</p><h3>컴퓨터에서도 컨텍스트 전환은 어렵습니다.</h3><p>에이전트를 임무에 파견할 때 가장 먼저 해야 할 일은 해당 에이전트가 액세스할 수 있는 모든 관련 데이터를 찾는 것입니다. 상담원이 연결한 각 데이터 소스가 서로 다르거나 분리된 응답으로 응답하는 경우 사람과 마찬가지로, 검색된 콘텐츠에서 중요한 맥락적 비트를 추출하는 것과 관련된 인지적 부하(정확히 같은 종류는 아니지만)가 발생할 수 있습니다. 여기에는 시간/계산이 필요하며, 에이전트 로직 체인에서 각각의 작은 부분이 합산됩니다. 따라서 <a href="https://blog.cloudflare.com/code-mode/">MCP에</a> 대해 논의되고 있는 것과 마찬가지로 대부분의 에이전트 툴은 알려진 입력 및 출력을 가진 격리된 함수, 즉 다양한 종류의 에이전트의 요구 사항을 지원하도록 조정된 API처럼 작동해야 한다는 결론에 도달하게 됩니다. 특히 자연어를 구조화된 구문으로 번역하는 작업과 같이 참조할 <a href="https://arxiv.org/html/2501.12372v5">스키마가</a> 있는 경우(실제로 RTFM!) 의미적 점들을 훨씬 더 잘 연결할 수 있다는 사실도 깨닫고 있습니다.</p><h2>7회 연장!</h2><p>지금까지 <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">LLM이 데이터 검색 및 쿼리에 미치는 영향과</a> 채팅 창이 상담원 AI 경험으로 어떻게 발전하고 있는지에 대해 살펴보았습니다. 이 두 가지 주제를 종합하여 컨텍스트 엔지니어링에서 새로운 검색 및 검색 기능을 사용하여 결과를 개선하는 방법을 살펴봅시다. <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy">3부: 컨텍스트 엔지니어링에서 하이브리드 검색의 힘</a>!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f98889141fba45b/6a17ddb80b0bed0822dd34a2/79c0378b68d74d9e018c35ee2c1fd17daeee9f2c-1080x608.webp" length="0" type="image/webp"/>
    <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기: 1부]]></title>
    <description><![CDATA[전문 LLM 에이전트가 협업하여 뉴스 기사를 조사, 작성, 편집, 게시하는 실제 뉴스룸 사례에서 A2A 프로토콜과 MCP의 개념을 살펴보세요.]]></description>
    <content:encoded><![CDATA[<h2>소개</h2><p>현재의 LLM 지원 시스템은 단일 모델 애플리케이션을 넘어 전문 에이전트가 함께 협력하여 이전에는 불가능하다고 생각했던 작업을 수행하는 복잡한 네트워크로 빠르게 진화하고 있습니다. 이러한 시스템이 복잡해짐에 따라 상담원 커뮤니케이션과 툴 액세스를 지원하는 인프라가 개발의 주요 초점이 되고 있습니다. 이러한 요구를 해결하기 위해 두 가지 상호 보완적인 접근 방식이 등장했습니다: 바로 멀티 에이전트 조정을 위한 <strong>A2A(에이전트2에이전트)</strong> 프로토콜과 표준화된 도구 및 리소스 액세스를 위한 <strong>MCP(모델 컨텍스트 프로토콜)</strong> 입니다.</p><p>각각의 기능을 서로 조화롭게 사용할 때와 사용하지 않을 때를 이해하면 애플리케이션의 확장성, 유지보수성 및 효율성에 큰 영향을 미칠 수 있습니다. 이 문서에서는 전문 LLM 에이전트가 협업하여 뉴스 기사를 조사, 작성, 편집 및 게시하는 디지털 뉴스룸의 실제 사례에서 <strong>A2A의</strong> 개념과 구현을 살펴봅니다.</p><p>관련 리포지토리는 <a href="https://github.com/justincastilla/elastic-newsroom/tree/main">여기에서</a> 확인할 수 있으며, A2A의 구체적인 활용 사례는 섹션 5의 마지막 부분에서 살펴볼 것입니다.</p><h3>필수 구성 요소</h3><p><a href="https://github.com/justincastilla/elastic-newsroom/tree/main">리포지토리는</a> A2A 에이전트의 Python 기반 구현으로 구성되어 있습니다. Flask에는 API 서버가 제공되며, 로깅 및 UI 업데이트를 위한 메시지를 라우팅하는 이벤트 허브라는 사용자 정의 Python 메시징 서비스도 있습니다. 마지막으로, 뉴스룸 기능을 독립적으로 사용할 수 있는 React UI가 제공됩니다. 모든 것이 도커 이미지에 포함되어 있어 쉽게 구현할 수 있습니다. 컴퓨터에서 직접 서비스를 실행하려면 이러한 기술이 설치되어 있는지 확인해야 합니다:</p><p>언어 및 런타임</p><ul><li><p>Python 13.12 - 핵심 백엔드 언어</p></li><li><p>Node.js 18+ - 선택적 React UI</p></li></ul><p>핵심 프레임워크 및 SDKS:</p><ul><li><p>A2A SDK 0.3.8 - 상담원 조정 및 커뮤니케이션</p></li><li><p>Anthropic SDK - AI 생성을 위한 클로드 통합</p></li><li><p>Uvicorn - 에이전트 실행을 위한 ASGI 서버</p></li><li><p>FastMCP 2.12.5+ - MCP 서버 구현</p></li><li><p>React 18.2 - 프론트엔드 UI 프레임워크</p></li></ul><p>데이터 &amp; 검색</p><ul><li><p>Elasticsearch 9.1.1+ - 문서 색인 및 검색</p></li></ul><p>Docker 배포(선택 사항이지만 권장)</p><ul><li><p>Docker 28.5.1+</p></li></ul><h2>섹션 1: 에이전트2에이전트(A2A)란 무엇인가요?</h2><h3>정의 및 핵심 개념</h3><p>에이전트2에이전트 (A2A)는 독립적인 LLM 에이전트 간의 상호 작용을 위한 표준화된 프로토콜입니다. A2A는 모든 작업을 처리하는 단일 모놀리식 시스템 대신 여러 전문 에이전트가 소통, 조정 및 협업하여 단일 에이전트가 효율적으로 처리하기 어렵거나 느리거나 아예 불가능한 복잡한 워크플로우를 수행할 수 있도록 지원합니다.</p><p><strong>공식 사양:</strong> <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></p><h3>기원과 진화</h3><p>에이전트 간 통신 또는 다중 에이전트 시스템의 개념은 <a href="https://en.wikipedia.org/wiki/Multi-agent_system">수십 년</a> 전의 분산 시스템, 마이크로 서비스 및 다중 에이전트 연구에 뿌리를 두고 있습니다. 분산형 인공 지능의 초기 연구는 협상, 조정, 협업이 가능한 에이전트의 토대를 마련했습니다. 이러한 초기 시스템은 대규모 <a href="https://www.jasss.org/5/1/7.html">소셜 시뮬레이션</a>, <a href="https://arxiv.org/html/2410.09403v1">학술 연구</a>, <a href="https://www.researchgate.net/publication/334765661_Generation_Expansion_Planning_Considering_Investment_Dynamic_of_Market_Participants_Using_Multi-agent_System">전력망 관리를</a> 위해 사용되었습니다.</p><p>LLM의 가용성이 시작되고 운영 비용이 절감되면서 멀티 에이전트 시스템은 Google과 광범위한 AI 연구 커뮤니티의 지원을 받아 '프로슈머' 시장에서 사용할 수 있게 되었습니다. 현재 에이전트2에이전트 시스템으로 알려진 A2A 프로토콜의 추가는 노력과 작업을 조정하는 여러 대규모 언어 모델 시대를 위해 특별히 설계된 최신 표준으로 발전했습니다.</p><p>A2A 프로토콜은 LLM이 연결되고 소통하는 상호작용 지점에 일관된 표준과 원칙을 적용하여 에이전트 간의 원활한 커뮤니케이션과 조정을 보장합니다. 이러한 표준화를 통해 서로 다른 기본 모델을 사용하는 서로 다른 개발자의 에이전트가 효과적으로 협업할 수 있습니다.</p><p>통신 프로토콜은 새로운 것이 아니며, 인터넷에서 이루어지는 거의 모든 디지털 거래에 널리 뿌리를 두고 있습니다. h <a href="https://www.elastic.co/search-labs">ttps://</a> www.elastic.co/search-labs 를 브라우저에 입력해 이 문서에 도달했다면 TCP/IP, HTTP 전송 및 DNS 조회 프로토콜이 모두 실행되어 일관된 브라우징 환경을 보장할 가능성이 높습니다.</p><h3>주요 특징</h3><p>A2A 시스템은 원활한 커뮤니케이션을 보장하기 위해 몇 가지 기본 원칙을 기반으로 구축되었습니다. 이러한 원칙을 기반으로 구축하면 서로 다른 LLM, 프레임워크 및 프로그래밍 언어를 기반으로 하는 다양한 에이전트가 모두 원활하게 상호 작용할 수 있습니다.</p><p>다음은 네 가지 주요 원칙입니다:</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></ul><h3>뉴스룸 실행 중인 예제</h3><p>저널리즘의 각기 다른 측면을 전문으로 하는 AI 에이전트로 운영되는 디지털 뉴스룸을 상상해 보세요:</p><ul><li><p><strong>뉴스 책임자</strong> (코디네이터/클라이언트): 스토리를 할당하고 워크플로우를 감독합니다.</p></li><li><p><strong>리포터 에이전트</strong>: 리서치 및 인터뷰에 기반한 기사 작성</p></li><li><p><strong>리서처 에이전트</strong>: 사실, 통계 및 배경 정보 수집</p></li><li><p><strong>아카이브 에이전트</strong>: Elasticsearch를 사용하여 과거 기사를 검색하고 동향을 파악합니다.</p></li><li><p><strong>에디터 에이전트</strong>: 품질, 스타일 및 SEO 최적화를 위해 기사를 검토합니다.</p></li><li><p><strong>게시자 에이전트</strong>: CI/CD를 통해 승인된 글을 블로그 플랫폼에 게시합니다.</p></li></ul><p>뉴스 책임자가 <em>재생 에너지 도입에</em> 관한 기사를 배정하면 기자는 통계를 수집하고, 편집자는 초안을 검토하고, 발행인은 최종 기사를 발행해야 합니다. 이러한 조정은 A2A 프로토콜을 통해 이루어집니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6c7215a96326481/6a17f2dd445de953024d0243/cc0760dbd74c49b92fa00dafbb8c2e8740eb70b6-963x693.png" alt="" /><h2>섹션 2: A2A 아키텍처 이해</h2><h3>클라이언트 에이전트 및 원격 에이전트 역할</h3><p>A2A 아키텍처에서 에이전트는 두 가지 주요 역할을 수행합니다. <strong>클라이언트 에이전트는</strong> 작업을 공식화하고 시스템의 다른 에이전트에게 전달할 책임이 있습니다. 이 정보를 사용하여 작업 위임에 대한 정보에 입각한 결정을 내릴 수 있도록 원격 상담원과 해당 상담원의 기능을 식별합니다. 클라이언트 에이전트는 전체 워크플로우를 조정하여 작업이 적절히 분배되고 시스템이 목표를 향해 진행되도록 합니다.</p><p>반면 <strong>원격 에이전트는</strong> 클라이언트로부터 위임받은 작업을 수행합니다. 요청에 대한 응답으로 정보를 제공하거나 특정 조치를 취하지만 독립적으로 작업을 시작하지는 않습니다. 원격 상담원은 자신에게 할당된 책임을 완수하기 위해 필요에 따라 다른 원격 상담원과 소통하여 전문화된 기능의 협업 네트워크를 만들 수도 있습니다.</p><p>뉴스룸에서는 뉴스 책임자가 클라이언트 에이전트 역할을 하고 기자, 연구원, 편집자, 발행인이 원격 에이전트로서 요청에 응답하고 서로 조율하는 역할을 합니다.</p><h3>A2A 핵심 기능</h3><p>A2A 프로토콜은 멀티 에이전트 협업을 가능하게 하는 몇 가지 기능을 정의합니다:</p><h4>1. 발견</h4><p>A2A 서버는 고객이 특정 작업에 언제, 어떻게 활용할 수 있는지 알 수 있도록 해당 기능을 알려야 합니다. 이는 상담원의 능력, 입력 및 출력을 설명하는 상담원 카드-JSON 문서를 통해 이루어집니다. 에이전트 카드는 일관되고 잘 알려진 엔드포인트(예: 권장되는 <code>/.well-known/agent-card.json</code> 엔드포인트)에서 제공되므로 클라이언트는 협업을 시작하기 전에 에이전트의 기능을 검색하고 조회할 수 있습니다.</p><p>다음은 Elastic의 사용자 정의 아카이브 에이전트에 대한 에이전트 카드 예시입니다 "Archie Archivist". Elastic과 같은 소프트웨어 공급자는 A2A 에이전트를 호스팅하고 액세스를 위한 URL을 제공한다는 점에 유의하세요:</p>{
  "name": "Archie Archivist",
  "description": "Helps find historical news documents in the Elasticsearch Index of archived news articles and content.",
  "url": "https://xxxxxxxxxxxxx-abc123.kb.us-central1.gcp.elastic.cloud/api/agent_builder/a2a/archive-agent",
  "provider": {
    "organization": "Elastic",
    "url": "https://elastic.co"
  },
  "version": "0.1.0",
  "protocolVersion": "0.3.0",
  "preferred_transport": "JSONRPC",
  "documentationURL": "https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": [
    {
      "id": "platform.core.search",
      "name": "platform.core.search",
      "description": "A powerful tool for searching and analyzing data within your Elasticsearch cluster.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    },
    {
      "id": "platform.core.index_explorer",
      "name": "platform.core.index_explorer",
      "description": "List relevant indices, aliases and datastreams based on a natural language query.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"]
}<p>이 에이전트 카드는 Elastic의 아카이브 에이전트에 대한 몇 가지 중요한 측면을 보여줍니다. 에이전트는 자신을 "Archie Archivist" 로 식별하고 그 목적을 Elasticsearch 인덱스에서 과거 뉴스 문서를 찾는 데 도움을 주는 것으로 명확하게 명시합니다. 이 카드는 공급자(Elastic)와 프로토콜 버전(0.3.0)을 지정하여 다른 A2A 호환 에이전트와의 호환성을 보장합니다. 가장 중요한 것은 <code>skills</code> 배열에 강력한 검색 기능 및 지능형 인덱스 탐색 등 이 에이전트가 제공하는 특정 기능이 열거되어 있다는 점입니다. 각 스킬은 지원하는 입력 및 출력 모드를 정의하여 클라이언트가 이 상담원과 통신하는 방법을 정확히 이해할 수 있도록 합니다. 이 에이전트는 데이터 저장소에서 단순히 검색하는 것이 아니라 데이터 저장소와 대화할 수 있도록 기본 LLM 지원 도구와 API 엔드포인트 제품군을 제공하는 Elastic의 에이전트 빌더 서비스에서 파생되었습니다. Elasticsearch의 A2A 에이전트에 대한 액세스는 <a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">여기에서</a> 확인할 수 있습니다.</p><h4>2. 협상</h4><p>고객과 상담원은 적절한 사용자 상호작용과 데이터 교환을 위해 텍스트, 양식, 아이프레임, 오디오/비디오 등 커뮤니케이션 방식에 대해 합의해야 합니다. 이 협상은 상담원 협업이 시작될 때 이루어지며 워크플로 전반에 걸쳐 상호 작용을 관리할 프로토콜을 설정합니다. 예를 들어 음성 기반 고객 서비스 상담원은 오디오 스트림을 통해 통신하도록 협상할 수 있고, 데이터 분석 상담원은 구조화된 JSON을 선호할 수 있습니다. 협상 프로세스를 통해 양 당사자는 각자의 역량과 당면한 업무의 요구 사항에 맞는 형식으로 정보를 효과적으로 교환할 수 있습니다.</p><p>위의 JSON 스니펫에 나열된 기능은 모두 입력 및 출력 스키마를 가지고 있으며, 이는 다른 에이전트에서 이 에이전트와 상호 작용하는 방법에 대한 기대치를 설정합니다.</p><h4>3. 작업 및 상태 관리</h4><p>클라이언트와 상담원은 작업 실행 전반에 걸쳐 작업 상태, 변경 사항 및 종속성을 전달할 수 있는 메커니즘이 필요합니다. 여기에는 작업 생성 및 할당부터 진행 상황 업데이트 및 상태 변경에 이르기까지 작업의 전체 수명 주기를 관리하는 것이 포함됩니다. 일반적인 상태에는 보류 중, 진행 중, 완료 또는 실패 상태가 포함됩니다. 또한 시스템은 작업 간의 종속성을 추적하여 종속 작업이 시작되기 전에 선행 작업이 완료되도록 해야 합니다. 오류 처리 및 재시도 로직도 필수 구성 요소로, 시스템이 장애로부터 원활하게 복구하고 주요 목표를 향해 계속 나아갈 수 있도록 합니다.</p><p>작업 메시지 예시:</p>{
  "message_id": "msg_789xyz",
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "researcher_agent",
  "timestamp": "2025-09-30T10:15:00Z",
  "payload": {
    "task_id": "task_456abc",
    "capability": "fact_gathering",
    "parameters": {
      "query": "renewable energy adoption rates in Europe 2024",
      "sources": ["eurostat", "iea", "ember"],
      "depth": "comprehensive"
    },
    "context": {
      "story_id": "story_123",
      "deadline": "2025-09-30T18:00:00Z",
      "priority": "high"
    }
  }
}<p>이 예제 작업 메시지는 A2A 커뮤니케이션의 몇 가지 주요 측면을 보여줍니다.</p><ul><li><p><strong>메시지</strong> 구조에는 고유 메시지 식별자, 전송되는 메시지 유형, 발신자 및 수신자 식별, 추적 및 디버깅을 위한 타임스탬프 등의 메타데이터가 포함됩니다.</p></li><li><p><strong>페이로드에는</strong> 원격 에이전트에서 호출되는 기능을 지정하고 해당 기능을 실행하는 데 필요한 매개 변수를 제공하는 실제 작업 정보가 포함되어 있습니다.</p></li><li><p><strong>컨텍스트</strong> 섹션에서는 상담원이 리소스를 할당하고 작업 일정을 잡는 방법을 알려주는 마감일 및 우선 순위 수준을 포함하여 수신 상담원이 더 넓은 워크플로우를 이해하는 데 도움이 되는 추가 정보를 제공합니다.</p></li></ul><h4>4. 협업</h4><p>고객과 상담원은 역동적이면서도 구조화된 상호작용을 지원하여 상담원이 고객, 다른 상담원 또는 사용자에게 설명, 정보 또는 하위 작업을 요청할 수 <strong>있어야</strong> 합니다. 이를 통해 상담원은 초기 지침이 모호할 때 후속 질문을 하고, 더 나은 결정을 내리기 위해 추가 컨텍스트를 요청하고, 더 적절한 전문 지식을 갖춘 다른 상담원에게 하위 작업을 위임하고, 전체 작업을 진행하기 전에 피드백을 위한 중간 결과를 제공할 수 있는 협업 환경을 조성할 수 있습니다. 이러한 다방향 커뮤니케이션은 상담원이 고립된 채로 일하는 것이 아니라 지속적인 대화에 참여하여 더 나은 결과를 이끌어낼 수 있도록 합니다.</p><h3>분산형 P2P 커뮤니케이션</h3><p>A2A를 사용하면 여러 조직에서 에이전트를 호스팅할 수 있으며, 일부 에이전트는 사내에서 관리하고 다른 에이전트는 타사 서비스에서 제공하는 분산 커뮤니케이션이 가능합니다. 이러한 에이전트는 여러 클라우드 제공업체 또는 온프레미스 데이터 센터에 걸쳐 다양한 인프라에서 실행될 수 있습니다. 일부 에이전트는 GPT 모델, 다른 에이전트는 Claude, 다른 에이전트는 오픈 소스 대안으로 구동되는 등 서로 다른 기본 LLM을 사용할 수 있습니다. 에이전트는 데이터 주권 요건을 준수하거나 지연 시간을 줄이기 위해 여러 지역에 걸쳐 운영될 수도 있습니다. 이러한 다양성에도 불구하고 모든 에이전트는 정보 교환을 위한 공통 커뮤니케이션 프로토콜에 동의하여 구현 세부 사항에 관계없이 상호 운용성을 보장합니다. 이러한 분산 아키텍처는 시스템 구축 및 배포 방식에 유연성을 제공하여 조직이 특정 요구 사항에 가장 적합한 에이전트와 인프라를 혼합하여 사용할 수 있도록 합니다.</p><p>이것이 뉴스룸 애플리케이션의 최종 아키텍처입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt74d59cd9267f54d8/6a17f2de505ac31129ad8c71/82e01a0d9746038eafd69d11177042b5390507ae-1600x838.png" alt="" /><h2>섹션 3: 모델 컨텍스트 프로토콜(MCP)</h2><h3>정의 및 목적</h3><p>모델 컨텍스트 프로토콜(MCP)은 사용자 정의 도구, 리소스, 프롬프트 등 코드베이스에 추가된 추가 기능으로 개별 LLM을 개선하고 권한을 부여하기 위해 Anthropic에서 개발한 표준화된 프로토콜입니다. MCP는 언어 모델과 작업을 효과적으로 완료하는 데 필요한 외부 리소스 간의 범용 인터페이스를 제공합니다. 이 <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">문서에서는</a> 사용 사례, 새로운 트렌드, Elastic의 자체 구현 사례를 통해 MCP의 현재 상태를 간략하게 설명합니다.</p><h3>핵심 MCP 개념</h3><p>MCP는 세 가지 주요 구성 요소로 구성된 클라이언트-서버 아키텍처에서 작동합니다:</p><ul><li><p><strong>클라이언트:</strong> MCP 서버에 연결하여 해당 기능에 액세스하는 애플리케이션(예: 클로드 데스크톱 또는 사용자 지정 AI 애플리케이션)입니다.</p></li><li><p><strong>서버</strong>: 리소스, 도구 및 프롬프트를 언어 모델에 노출하는 애플리케이션입니다. 각 서버는 특정 기능이나 데이터 소스에 대한 액세스를 제공하는 데 특화되어 있습니다.</p><ul><li><p><strong>도구</strong>: 데이터베이스 검색, 외부 API 호출 또는 데이터에 대한 실행 변환과 같은 작업을 수행하기 위해 모델이 호출할 수 있는 사용자 정의 함수입니다.</p></li><li><p><strong>리소스:</strong> 모델이 읽을 수 있는 데이터 소스, 동적 또는 정적 데이터로 제공되며 URI 패턴(REST 경로와 유사)을 통해 액세스됩니다.</p></li><li><p><strong>프롬프트: </strong>모델이 특정 작업을 수행하도록 안내하는 변수가 포함된 재사용 가능한 프롬프트 템플릿입니다.</p></li></ul></li></ul><h3>요청-응답 패턴</h3><p>MCP는 REST API와 유사한 친숙한 요청-응답 상호 작용 패턴을 따릅니다. 클라이언트(LLM)가 리소스를 요청하거나 도구를 호출하면 MCP 서버가 요청을 처리하고 결과를 반환하며, LLM은 이를 사용하여 작업을 계속 진행합니다. 주변 서버가 있는 이 중앙 집중식 모델은 피어 투 피어 에이전트 통신에 비해 더 간단한 통합 패턴을 제공합니다.</p><h3>뉴스룸의 MCP</h3><p>뉴스룸의 예에서 개별 에이전트는 MCP 서버를 사용하여 필요한 도구와 데이터에 액세스합니다:</p><ul><li><p><strong>연구원 에이전트</strong> 사용:</p><ul><li><p>뉴스 API MCP 서버(뉴스 데이터베이스에 액세스)</p></li><li><p>사실 확인 MCP 서버(신뢰할 수 있는 출처에 대한 클레임 확인)</p></li><li><p>학술 데이터베이스 MCP 서버(학술 논문 및 연구)</p></li></ul></li><li><p><strong>리포터 에이전트</strong> 사용:</p><ul><li><p>스타일 가이드 MCP 서버(뉴스룸 작성 표준)</p></li><li><p>템플릿 MCP 서버(문서 템플릿 및 형식)</p></li><li><p>이미지 라이브러리 MCP 서버(스톡 사진 및 그래픽)</p></li></ul></li><li><p><strong>에디터 에이전트</strong> 사용:</p><ul><li><p>문법 검사기 MCP 서버(언어 품질 도구)</p></li><li><p>표절 감지 MCP 서버(독창성 검증)</p></li><li><p>SEO 분석 MCP 서버(헤드라인 및 키워드 최적화)</p></li></ul></li><li><p><strong>게시자 에이전트</strong> 사용:</p><ul><li><p>CMS MCP 서버(콘텐츠 관리 시스템 API)</p></li><li><p>CI/CD MCP 서버(배포 파이프라인)</p></li><li><p>Analytics MCP 서버(추적 및 모니터링)</p></li></ul></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt195fe0bd36d36a48/6a17f2e0b1e113afe479f36c/b67311e3b58b27f9eb1b42a7b1dbad47ef3be4ad-808x535.png" alt="" /><h2>
섹션 4: 아키텍처 비교</h2><h3>A2A 사용 시기</h3><p>A2A 아키텍처는 <strong>진정한 멀티 에이전트 협업이 필요한 시나리오에서</strong> 탁월한 성능을 발휘합니다. 조율이 필요한 다단계 워크플로, 여러 순차적 또는 병렬 단계가 포함된 작업, 반복 및 개선이 필요한 워크플로, 체크포인트 및 검증이 필요한 프로세스의 경우 A2A의 이점을 크게 누릴 수 있습니다. 뉴스룸의 예에서 스토리 워크플로에서는 기자가 글을 작성해야 하지만 특정 사실에 대한 신뢰도가 낮으면 리서처에게 다시 전달한 다음 편집자에게 전달하고 마지막으로 게시자에게 전달해야 할 수도 있습니다.</p><p><strong>여러 영역에 걸친 도메인별 전문화는</strong> A2A의 또 다른 강력한 사용 사례입니다. 더 큰 작업을 수행하기 위해 다양한 분야의 여러 전문가가 필요하고 각 에이전트가 서로 다른 측면에 대한 심층적인 도메인 지식과 전문화된 추론 기능을 제공하는 경우 A2A는 이러한 연결을 만드는 데 필요한 조정 프레임워크를 제공합니다. 뉴스룸은 정보 수집을 전문으로 하는 연구원, 글쓰기를 전문으로 하는 기자, 품질 관리를 전문으로 하는 편집자 등 각기 다른 전문성을 가진 팀원들로 구성되어 있습니다.</p><p>에이전트의 자율적인 행동이 필요하기 때문에 A2A는 특히 유용합니다. <strong> 독립적인 의사 결정을 내리고, 변화하는 상황에 따라 능동적인 행동을 보이며, 워크플로 요구 사항에 동적으로 적응할</strong> 수 있는 상담원은 A2A 아키텍처에서 성공할 수 있습니다. 전문 기능의 수평적 확장은 또 다른 주요 이점입니다. 단일 마스터 오브 올 트레이드가 아닌 여러 전문 에이전트가 협력하여 작업하고 동일한 에이전트의 여러 인스턴스가 하위 작업을 비동기적으로 처리할 수 있습니다. 예를 들어, 뉴스룸에서 뉴스 속보를 작성할 때 여러 명의 리포터가 동시에 같은 기사를 다른 각도에서 작업할 수 있습니다.</p><p>마지막으로 진정한 멀티 에이전트 협업이 필요한 작업은 A2A에 이상적입니다. 여기에는 <a href="https://arxiv.org/abs/2404.18796">배심원 평가</a> 메커니즘, 합의 도출 및 투표 시스템, 최상의 결과에 도달하기 위해 <strong>다양한 관점이 필요한 협업적 문제 해결이</strong> 포함됩니다.</p><h3>MCP 사용 시기</h3><p>모델 컨텍스트 프로토콜은 단일 AI 모델의 기능을 확장할 때 이상적입니다. 단일 AI 모델이 여러 도구 및 데이터 소스에 액세스해야 하는 경우, MCP는 중앙 집중식 추론과 분산 도구 및 간단한 도구 통합을 결합한 완벽한 솔루션을 제공합니다. 뉴스룸 예시에서 리서처 에이전트(한 모델)는 뉴스 API, 사실 확인 서비스, 학술 데이터베이스 등 여러 데이터 소스에 액세스해야 하며, 모두 표준화된 MCP 서버를 통해 액세스할 수 있습니다.</p><p>도구 통합의 광범위한 공유와 재사용성이 중요한 경우 표준화된 도구 통합이 우선순위가 됩니다. MCP는 공통 통합을 위한 개발 시간을 크게 단축하는 사전 구축된 MCP 서버 에코시스템으로 빛을 발합니다. 단순성과 유지보수가 필요한 경우, MCP의 요청-응답 패턴은 개발자에게 친숙하고 분산 시스템보다 이해와 디버깅이 쉬우며 운영 복잡성이 낮습니다.</p><p>마지막으로, MCP는 소프트웨어 제공업체에서 시스템과의 원격 통신을 용이하게 하기 위해 제공하는 경우가 많습니다. 이러한 제공업체 제공 MCP 서버는 온보딩 및 개발 시간을 크게 단축하는 동시에 독점 시스템에 대한 표준화된 인터페이스를 제공하므로 맞춤형 API 개발보다 훨씬 간편하게 통합할 수 있습니다.</p><h3>둘 다 사용해야 하는 경우(A2A ❤️의 MCP)</h3><p>많은 정교한 시스템은 A2A와 <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">MCP 통합에 대한 A2A 문서에</a> 명시된 대로 A2A와 MCP를 결합하면 이점을 얻을 수 있습니다. 조정과 표준화가 모두 필요한 시스템은 하이브리드 접근 방식에 이상적인 후보입니다. A2A는 상담원 조정 및 워크플로 오케스트레이션을 처리하고 MCP는 개별 상담원에게 툴 액세스를 제공합니다. 뉴스룸의 예에서 에이전트는 A2A를 통해 조율하며, 워크플로는 리포터에서 리서처, 편집자, 퍼블리셔로 이동합니다. 하지만 각 에이전트는 전문화된 도구에 MCP 서버를 사용하므로 아키텍처가 깔끔하게 분리되어 있습니다.</p><p>각각 도구 액세스를 위해 MCP를 사용하는 여러 전문 에이전트는 A2A가 처리하는 에이전트 조정 계층과 MCP가 관리하는 도구 액세스 계층이 있는 일반적인 패턴을 나타냅니다. 이렇게 우려 사항을 명확하게 분리하면 시스템을 더 쉽게 이해하고 유지 관리할 수 있습니다.</p><p>두 가지 접근 방식을 결합하면 상당한 이점이 있습니다. 전문화, 자율성, 병렬 처리 등 멀티 에이전트 시스템의 조직적 이점을 누리는 동시에 툴 통합, 리소스 액세스 등 MCP의 표준화 및 에코시스템의 이점도 누릴 수 있습니다. 에이전트 조정(A2A)과 리소스 액세스(MCP)는 명확하게 분리되어 있으며, 중요한 점은 A2A는 API 액세스 같은 소규모 작업에는 필요하지 않으며, 멀티에이전트 오케스트레이션의 오버헤드 없이 MCP가 효율적으로 처리한다는 점입니다.</p><p><strong>FAQ: A2A와 MCP- 사용 사례</strong></p><p>기능</p><p>에이전트2에이전트(A2A)</p><p>모델 컨텍스트 프로토콜(MCP)</p><p>하이브리드(A2A + MCP)</p><p>주요 목표</p><p>다중 에이전트 조정: 전문 에이전트로 구성된 팀이 복잡한 다단계 워크플로우에서 함께 작업할 수 있습니다.</p><p>단일 에이전트 향상: 외부 도구, 리소스 및 데이터로 단일 LLM/에이전트의 기능을 확장합니다.</p><p>결합된 힘: A2A는 팀의 워크플로우를 관리하고, MCP는 각 팀원에게 도구를 제공합니다.</p><p>뉴스룸 팀 예시</p><p>워크플로 체인: 뉴스 책임자 → 기자 → 연구원 → 편집자 → 발행인. 이것이 바로 조정 레이어입니다.</p><p>개별 에이전트의 도구: 리포터 에이전트가 스타일 가이드 서버 및 템플릿 서버에 액세스하는 경우(MCP를 통해). 이것이 도구 액세스 레이어입니다.</p><p>전체 시스템: 리포터가 편집자(A2A)와 협력하고, 리포터는 이미지 라이브러리 MCP 서버를 사용하여 기사에 사용할 그래픽을 찾습니다.</p><p>사용 시기</p><p>진정한 협업, 반복 및 개선이 필요하거나 여러 상담원이 전문 지식을 공유해야 하는 경우.</p><p>단일 에이전트가 여러 도구 및 데이터 소스에 액세스해야 하거나 독점 시스템과의 표준화된 통합이 필요한 경우.</p><p>멀티 에이전트 시스템의 조직적 이점과 MCP의 표준화 및 에코시스템 이점이 필요한 경우.</p><p>핵심 이점</p><p>자율성과 확장성: 상담원은 독립적인 의사 결정을 내릴 수 있으며, 이 시스템을 통해 전문화된 기능을 수평적으로 확장할 수 있습니다.</p><p>단순성 및 표준화: 중앙 집중식 추론으로 인해 디버깅 및 유지 관리가 더 쉬우며, 리소스를 위한 범용 인터페이스를 제공합니다.</p><p>우려 사항을 명확하게 분리: 시스템을 더 쉽게 이해할 수 있습니다: A2A = 팀워크, MCP = 도구 액세스.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1735ea5de41e10fd/6a17f2e26864a4125cb688c4/ddf6a29b1107ac6a63e94ecef703abc561a29e1e-986x656.png" alt="" /><h2>결론</h2><p>이 글은 데이터 및 도구에 대한 지원과 외부 액세스를 제공하기 위해 MCP 서버로 강화된 A2A 기반 에이전트의 구현을 다루는 두 편의 글 중 첫 번째 섹션입니다. 다음 글에서는 온라인 뉴스룸의 활동을 모방하기 위해 함께 작동하는 실제 코드를 살펴볼 것입니다. 두 프레임워크 모두 그 자체로 매우 훌륭하고 유연하지만, 함께 작업할 때 서로를 얼마나 보완하는지 알게 될 것입니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2716d804698ec878/6a17f2e41480095fd7b48888/9f938d8e2f0fdf7509edf028816c48bdbc8b3fc7-1600x900.png" length="0" type="image/png"/>
    <pubDate>Thu, 13 Nov 2025 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>
  <item>
    <title><![CDATA[Elastic MCP 서버: 모든 AI 에이전트에 에이전트 빌더 도구 노출]]></title>
    <description><![CDATA[에이전트 빌더에 기본 제공되는 Elastic MCP 서버를 사용하여 개인 데이터 및 사용자 정의 도구에 대한 액세스를 통해 모든 AI 에이전트를 안전하게 확장하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic 에이전트 빌더는 Elasticsearch의 자체 데이터와 긴밀하게 통합되는 도구와 에이전트를 생성하기 위한 플랫폼입니다. 예를 들어, 내부 문서에 대해 시맨틱 검색을 수행하거나 통합 가시성 로그를 분석하거나 보안 경고를 쿼리하는 도구를 만들 수 있습니다.</p><p>하지만 대부분의 시간을 보내는 환경에 이러한 맞춤형 데이터 인식 도구를 도입할 때 진정한 마법이 일어납니다. 코드 편집기 에이전트가 조직의 비공개 지식창고에 안전하게 액세스할 수 있다면 어떨까요?</p><p>이것이 바로 <strong>모델 컨텍스트 프로토콜(MCP)</strong> 이 필요한 이유입니다. Elastic 에이전트 빌더는 플랫폼의 도구에 대한 액세스를 제공하는 기본 제공 MCP 서버와 함께 제공됩니다.</p><h2>Elastic 에이전트 빌더 MCP 서버를 사용하는 이유는 무엇인가요?</h2><p>AI 에이전트는 매우 강력하지만, 일반적으로 학습된 데이터와 공개 인터넷에서 적극적으로 검색할 수 있는 정보로 지식이 제한되어 있습니다. 그들은 회사의 내부 디자인 문서, 팀의 특정 배포 런북 또는 애플리케이션 로그의 고유한 구조에 대해 알지 못합니다.</p><p>문제는 AI 어시스턴트에게 필요한 전문 컨텍스트를 제공하는 것입니다. 이것이 바로 MCP가 해결하고자 하는 문제입니다. <strong>MCP는 AI 모델이나 에이전트가 외부 도구를 검색하고 사용할 수 있도록 하는 개방형 표준입니다.</strong></p><p>이를 가능하게 하기 위해 Elastic 에이전트 빌더는 기본적으로 기본 제공 MCP 서버를 통해 사용자 정의 도구를 노출합니다. 즉, Elastic 에이전트 빌더로 구축한 전문화된 데이터 인식 도구로 <strong>Cursor</strong>, <strong>VS Code</strong> 또는 <strong>Claude Desktop과</strong> 같은 모든 MCP 호환 클라이언트를 쉽게 연결할 수 있습니다.</p><h2>MCP를 사용해야 할 때(그리고 사용하지 말아야 할 때)</h2><p>Elastic 에이전트 빌더에는 다양한 통합 패턴을 지원하기 위한 여러 프로토콜이 포함되어 있습니다. 효과적인 AI 워크플로우를 구축하려면 올바른 것을 선택하는 것이 중요합니다.</p><ul><li><p><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server"><strong>MCP를 </strong></a><strong>사용하여</strong> 전문화된 도구로 AI 에이전트(예:<strong>커서</strong> 또는 <strong>VS 코드)를 보강할 수 있습니다.</strong> 개인 데이터에 대한 안전한 액세스를 통해 이미 사용 중인 어시스턴트를 개선하는 "가져오기(" ) 방식입니다. 도구만 MCP 서버를 통해 노출되며, Elastic의 에이전트는 이와는 별개입니다.</p></li><li><p><a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"><strong>A2A 프로토콜을 </strong></a><strong>사용하여</strong> 완전한 사용자 정의 Elastic 에이전트가 다른 자율 에이전트와 협업할 수 있도록 하세요(<a href="https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise"><strong>Google의 Gemini Enterprise에서처럼).</strong></a> 상담원 간 위임은 각 상담원이 동료로서 문제를 해결하는 데 사용됩니다.</p></li><li><p><strong>처음부터 </strong>사용자 지정 애플리케이션을 구축할 때<a href="https://www.elastic.co/docs/solutions/search/agent-builder/kibana-api"><strong>에이전트 빌더 API를 사용하여 완전한 프로그래밍 방식으로 제어할 수 있습니다.</strong></a></p></li></ul><p>IDE를 벗어나지 않고도 내부 문서에서 답을 얻고자 하는 개발자에게 MCP는 완벽한 솔루션입니다.</p><h2>예: 에이전트 빌더 MCP 서버를 사용하는 커서의 사용자 지정 도구</h2><p>제가 매일 사용하는 실제 사례를 살펴보겠습니다. 먼저, 내부 엔지니어링 문서를 크롤링하여 <code>elastic-dev-docs</code> 이라는 Elasticsearch 인덱스로 색인화했습니다. 상담원 빌더에서 제공되는 일반적인 기본 제공 툴을 사용할 수도 있지만, 이 특정 지식창고를 쿼리하기 위한 자체 사용자 지정 툴을 만들겠습니다.</p><p>사용자 지정 도구를 구축하는 이유는 간단합니다: <strong>제어와 정밀도입니다</strong>. 이 접근 방식을 사용하면 <code>elastic-dev-docs</code> 인덱스에 대해 직접 빠른 시맨틱 쿼리를 실행할 수 있습니다. 당사는 정확히 어떤 인덱스를 대상으로 하고 데이터를 검색하는 방법을 완전히 제어할 수 있습니다.</p><p>이제 커서와 같은 AI 기반 코드 편집기에서 이 사용자 지정 지식창고를 사용하는 방법은 다음과 같습니다.</p><h3>1단계: 상담원 빌더에서 사용자 지정 지식창고 툴 만들기</h3><p>먼저 상담원 빌더에서 새 도구를 만듭니다. 명확하고 구체적인 도구 설명은 내부 Elastic 에이전트든 MCP를 통해 연결되는 Cursor와 같은 외부 도구든 모든 AI 에이전트가 올바른 작업을 위해 도구를 검색하고 선택하는 방식이기 때문에 중요합니다.</p><p>강력한 설명은 명시적이어야 합니다. 예를 들어 "내부 엔지니어링 문서, 런북, 릴리즈 절차를 찾기 위해 elastic-dev-docs 인덱스에서 시맨틱 검색을 수행합니다."</p><p>이를 통해 특정 인덱스에 대해 시맨틱 검색을 수행하도록 도구를 구성했습니다. 저장하면 즉시 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt011118f0a9279185/6a17f367dbb4ffc4f3fb581a/1eea079908fdf7cc72dbe81abd07ff51601a43d4-1472x1600.png" alt="상담원 빌더에서 사용자 지정 지식창고 툴 만들기." /><p>외부에 연결하기 전에 UI에서 직접 테스트해 볼 수 있습니다. <strong>테스트</strong> 버튼을 클릭하여 매개변수를 수동으로 입력하여 LLM이 수행하는 작업을 에뮬레이션하고 결과를 검사하여 모든 것이 올바르게 작동하는지 확인하기만 하면 됩니다.</p><h3>2단계: 커서를 Elastic MCP 서버에 연결하기</h3><p>Elastic 에이전트 빌더는 안전한 MCP 엔드포인트를 통해 사용 가능한 모든 도구를 자동으로 노출합니다. Kibana 내의 도구 UI에서 고유한 서버 URL을 찾을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd0e62ae0f394c3d/6a17f368e317916ec32d5933/ba137be30f0eaa7f028b96bd8af4e2779c3f8a33-1600x589.png" alt="방법Kibana의 도구 UI에서 커서를 Elastic MCP 서버에 연결합니다." /><p>Cursor에 연결하려면 이 URL을 구성 파일에 인증용 Elastic API 키와 함께 추가하기만 하면<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">됩니다(ES API 키 생성 방법 알아보기)</a>. 모든 접근 제어 규칙을 준수하면서 사용자가 부여한 권한으로만 도구가 실행되도록 보장하기 때문에 권한 부여를 위해 API 키를 사용합니다.</p><p>Cursor의 <code>~/.cursor/mcp.json</code> 의 MCP 구성은 다음과 같습니다:</p>{
  "mcpServers": {
    "elastic-agent-builder": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-kibana.kb.company.io/api/agent_builder/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "ApiKey &lt;ELASTIC_API_KEY&gt;"
      }
    }
  }
}<p>구성이 저장되면, Cursor에 Elastic 에이전트 빌더 MCP 서버 도구가 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2837638263e628ed/6a17f36adbb4ffeb9cfb5820/d302c6d3609fbf14fd40e21b9e69e567bf12553f-1600x1002.png" alt="커서에서 사용할 수 있는 Elastic 에이전트 빌더 MCP 서버 도구의 이미지입니다." /><h3>3단계: 물어보세요!</h3><p>연결이 설정되면 이제 커서 상담원이 사용자 지정 도구를 호출하여 질문에 답하거나 코드 생성 프로세스를 안내할 수 있습니다.</p><p>구체적인 질문을 해보겠습니다:</p><p><em>"탄력적 검색 조직의 엔지니어링 내부 문서에서 크롤러 서비스를 릴리즈하기 위한 조회 단계"</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83fa357261b30e93/6a17f36c4b055d16d1432326/14f572730203c23615bb9dd38234bcb3b0f81155-1600x1468.png" alt="커서 에이전트는 사용자 지정 도구를 호출하여 질문에 답하고 코드 생성 프로세스를 안내합니다." /><p>무대 뒤에서는 마법이 일어납니다:</p><ol><li><p>커서 상담원은 질문에 가장 적합한 방식으로 답변하는 방법을 결정한 후 <code>engineering_documentation_internal_search</code></p></li><li><p>자연어 쿼리로 도구를 호출합니다.</p></li><li><p>이 도구는 <code>elastic-dev-docs</code> 인덱스에 대해 시맨틱 검색을 실행하여 가장 관련성이 높은 최신 절차를 반환합니다.</p></li></ol><p>코드 편집기를 벗어나지 않고도 내부 문서를 기반으로 정확하고 신뢰할 수 있는 답변을 얻을 수 있습니다. 원활하고 강력한 경험을 제공합니다.</p><h2>빌드할 차례</h2><p>이제 Elastic 에이전트 빌더의 기본 제공 MCP 서버를 사용하여 개인 데이터에 대한 안전한 액세스로 AI 어시스턴트를 확장하는 방법을 살펴보셨습니다. 모델을 진정으로 유용하게 활용하려면 자신의 정보에 근거를 두는 것이 중요합니다.</p><p>핵심 단계를 다시 한 번 정리해 보았습니다:</p><ul><li><p>필요에 맞는 프로토콜 선택하기(MCP).</p></li><li><p>사용자 지정 지식창고 툴 구축하기</p></li><li><p>해당 도구를 커서와 같은 IDE 보조 도구에 연결합니다.</p></li></ul><p>더 이상 상담원과 툴이 가장 중요한 컨텍스트에서 분리될 필요가 없습니다. 이 가이드가 보다 효과적이고 데이터를 인식하는 워크플로를 만드는 데 도움이 되길 바랍니다. 행복한 건물!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elastic-mcp-server-agent-builder-tools</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elastic-mcp-server-agent-builder-tools</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[AI 도구 ]]></category>
    <dc:creator><![CDATA[Jedr Blaszyk,Joe McElroy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5b61961b6269ab1/6a17f36ea29299d839d02db2/ef5153551a1d14833c7f512fede554d1dfb31553-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI 에이전트 평가: Elastic이 에이전트 프레임워크를 테스트하는 방법]]></title>
    <description><![CDATA[정확하고 검증 가능한 결과를 보장하기 위해 에이전트 시스템에 대한 변경 사항을 Elastic 사용자에게 릴리즈하기 전에 어떻게 평가하고 테스트하는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>소개</h2><p>Elastic Stack에는 곧 출시될<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> 에이전트 빌더의</a> Elastic AI 에이전트(현재 기술 프리뷰 중)와 <a href="https://www.elastic.co/docs/solutions/security/ai/attack-discovery">공격 탐색</a> (8.18 및 9.0+의<a href="https://www.elastic.co/blog/whats-new-elastic-security-9-0-0">GA)</a> 과 같은 많은 LLM 기반 에이전트 애플리케이션이 있으며, 더 많은 애플리케이션이 개발 중입니다. 개발 중은 물론 배포 후에도 이러한 질문에 답하는 것이 중요합니다:</p><ul><li><p>이러한 AI 애플리케이션의 응답 품질을 어떻게 평가할 수 있을까요?</p></li><li><p>변경을 하는 경우, 변경이 진정으로 개선된 것이며 사용자 경험의 저하를 초래하지 않는다고 어떻게 보장할 수 있을까요?</p></li><li><p>이러한 결과를 반복 가능한 방식으로 쉽게 테스트하려면 어떻게 해야 할까요?</p></li></ul><p>기존의 소프트웨어 테스트와 달리, 생성 AI 애플리케이션을 평가하려면 통계적 방법, 미묘한 정성적 검토, 사용자 목표에 대한 깊은 이해가 필요합니다.</p><p>이 문서에서는 Elastic 개발자 팀이 평가를 수행하고, 배포 전에 변경 사항의 품질을 보장하고, 시스템 성능을 모니터링하기 위해 사용하는 프로세스에 대해 자세히 설명합니다. 모든 변화가 증거에 의해 뒷받침되어 신뢰할 수 있고 검증 가능한 결과로 이어질 수 있도록 노력합니다. 이 프로세스의 일부는 오픈 소스 정신의 일부로서 투명성에 대한 우리의 약속을 반영하여 Kibana에 직접 통합되어 있습니다. 평가 데이터와 메트릭의 일부를 공개적으로 공유함으로써 커뮤니티의 신뢰를 증진하고 AI 에이전트를 개발하거나 제품을 활용하는 모든 사람에게 명확한 프레임워크를 제공하고자 합니다.</p><h2>제품 예시</h2><p>이 문서에서 사용된 방법은 공격 탐색 및 Elastic AI 에이전트와 같은 솔루션을 반복하고 개선하는 방법의 기초가 되었습니다. 두 가지에 대해 각각 간략하게 소개합니다:</p><h3>Elastic Security의 공격 탐색</h3><p>공격 탐색은 LLM을 사용해 Elastic에서 공격 시퀀스를 식별하고 요약합니다. 지정된 기간(기본 24시간)에 Elastic Security 경보가 주어지면, 공격 탐색의 에이전트 워크플로우가 자동으로 공격이 발생했는지 여부와 침해된 호스트 또는 사용자, 결론에 기여한 경보와 같은 중요한 정보를 찾아냅니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb70932abe8d4de75/6a17f04ea292990c52d02d61/20fabb47642dad7b588daaaa8c3a98de860ad01d-1251x758.png" alt="" /><p></p><p>목표는 LLM 기반 솔루션이 최소한 사람만큼 좋은 결과물을 만들어내는 것입니다.</p><h3>Elastic AI 에이전트</h3><p><strong>Elastic 에이전트 빌더는</strong> 모든 검색 기능을 활용하는 상황 인식 AI 에이전트를 구축하기 위한 새로운 플랫폼입니다. 사용자가 대화형 상호 작용을 통해 데이터를 이해하고 데이터로부터 답을 얻을 수 있도록 설계된 사전 구축된 범용 에이전트인 <strong>Elastic AI 에이전트가</strong> 함께 제공됩니다.</p><p>에이전트는 Elasticsearch 또는 연결된 지식 기반 내에서 관련 정보를 자동으로 식별하고 사전 구축된 도구 모음을 활용하여 이들과 상호 작용함으로써 이를 달성합니다. 이를 통해 Elastic AI 에이전트는 단일 문서에 대한 간단한 질문(&amp;A)부터 여러 인덱스에 걸친 집계와 단일 또는 다단계 검색이 필요한 복잡한 요청까지 다양한 범위의 사용자 쿼리에 응답할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b9dbede85a56bd6/6a17f050e8fbce88943a1a30/d29dee100bb8a17bb623acd745773a5164a1df4f-1600x1014.png" alt="" /><h2>실험을 통한 개선 사항 측정</h2><p>AI 에이전트의 맥락에서 실험이란 잘 정의된 차원(예: 유용성, 정확성, 지연 시간)에서 성능을 개선하기 위해 설계된 시스템에 대한 구조적이고 테스트 가능한 변경을 의미합니다. 목표는 확실한 답을 찾는 것입니다: "이 변경 사항을 병합하면 진정한 개선이 이루어지고 사용자 경험이 저하되지 않는다고 보장할 수 있는가?"라는 질문에 확실히 답하는 것입니다.</p><p>저희가 수행하는 대부분의 실험에는 일반적으로 다음이 포함됩니다:</p><ul><li><p><strong>가설:</strong> 구체적이고 검증 가능한 주장. <em>예시:</em> "공격 검색 도구에 대한 액세스 권한을 추가하면 보안 관련 쿼리의 정확도가 향상됩니다."</p></li><li><p><strong>성공 기준:</strong> '성공'의 의미를 정의하는 명확한 임계값을 설정하세요. <em>예시:</em> "보안 데이터 집합의 정확도 점수 +5% 개선, 다른 곳에서는 성능 저하 없음."</p></li><li><p><strong>평가 계획:</strong> 성공 측정 방법(지표, 데이터 세트, 비교 방법)</p></li></ul><p>성공적인 실험은 체계적인 탐구 과정입니다. 사소한 프롬프트 조정부터 대대적인 아키텍처 변경에 이르기까지 모든 변경은 이 7단계에 따라 의미 있고 실행 가능한 결과를 보장합니다:</p><ul><li><p>1단계: 문제 식별</p></li><li><p>2단계: 지표 정의</p></li><li><p>3단계: 명확한 가설 수립하기</p></li><li><p>4단계: 평가 데이터 세트 준비</p></li><li><p>5단계: 실험 실행</p></li><li><p>6단계: 결과 분석 + 반복하기</p></li><li><p>7단계: 의사 결정 및 문서화</p></li></ul><p>이러한 단계의 예는 <em>그림 1에</em> 나와 있습니다. 다음 하위 섹션에서는 각 단계에 대해 설명하며, 다음 문서에서 각 단계의 기술적 세부 사항에 대해 자세히 설명할 예정입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06bfe2f0e4205a18/6a17f052faa91358eb93c968/3a9f5a3e92dd4922a795a19104c6e4ad8c98958d-2400x1352.png" alt="" /><h2>실제 Elastic 예제를 통한 단계별 안내</h2><h3>1단계: 문제 식별</h3><p><em>이 변경으로 해결하고자 하는 문제는 정확히 무엇인가요?</em></p><p>공격 탐지 예시: 요약이 불완전하거나 양성 활동이 공격으로 잘못 플래그가 지정되는 경우(오탐): 간혹 요약이 불완전합니다.</p><p>Elastic AI 에이전트 예시: 특히 분석 쿼리에 대한 에이전트의 도구 선택이 최적이 아니며 일관성이 없어 종종 잘못된 도구가 선택되는 경우가 있습니다. 이는 결과적으로 토큰 비용과 지연 시간을 증가시킵니다.</p><h3>2단계: 지표 정의</h3><p><em>문제를 측정 가능하게 만들어 현재 상태와 변경 사항을 비교할 수 있도록 합니다.</em></p><p>일반적인 측정지표에는 <a href="https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall">정확도 및 회수율</a>, <a href="https://en.wikipedia.org/wiki/Semantic_similarity">의미적 유사성</a>, 사실성 등이 포함됩니다. 사용 사례에 따라 코드 검사를 사용하여 경고 ID 일치 또는 올바르게 검색된 URL과 같은 메트릭을 계산하거나 보다 자유로운 형식의 답변을 위해 LLM-as-judge와 같은 기법을 사용하여 메트릭을 계산합니다.</p><p>다음은 실험에 사용된 몇 가지 지표의 예시<em>(전부는 아님)</em>입니다:</p><p><strong>공격 탐지</strong></p><p>Metric</p><p>설명</p><p>정밀도 &amp; 리콜</p><p>실제 출력과 예상 출력 간의 알림 ID를 일치시켜 탐지 정확도를 측정합니다.</p><p>유사성</p><p>BERTScore를 사용하여 응답 텍스트의 의미적 유사성을 비교합니다.</p><p>사실성</p><p>주요 IOC(타협 지표)가 존재하나요? MITRE 전술(업계 공격 분류)이 올바르게 반영되어 있나요?</p><p>공격 체인 일관성</p><p>발견 횟수를 비교하여 공격의 과대 또는 과소 보고 여부를 확인합니다.</p><p><strong>Elastic AI 에이전트</strong></p><p>Metric</p><p>설명</p><p>정밀도 &amp; 리콜</p><p>상담원이 사용자 쿼리에 답변하기 위해 검색한 문서/정보와 쿼리에 답변하는 데 필요한 실제 정보 또는 문서를 대조하여 정보 검색 정확도를 측정합니다.</p><p>사실성</p><p>사용자 쿼리에 답변하는 데 필요한 핵심 사실이 존재하나요? 절차적 문의를 위한 사실관계가 올바른 순서로 정리되어 있나요?</p><p>응답 관련성</p><p>응답에 사용자 쿼리와 관련이 없거나 주변적인 정보가 포함되어 있나요?</p><p>응답 완전성</p><p>응답이 사용자 쿼리의 모든 부분에 대한 답변을 제공하나요? 응답에 근거 사실에 존재하는 모든 정보가 포함되어 있나요?</p><p>ES|QL 유효성 검사</p><p>생성된 ES|QL이 구문적으로 올바른가요? 기능적으로 기준 데이터 ES|QL과 동일한가요?</p><h3>3단계: 명확한 가설 수립하기</h3><p><em>위에서 정의한 문제와 지표를 사용하여 명확한 성공 기준을 설정하세요.</em></p><p>Elastic AI 에이전트 예시:</p><ol><li><p><strong>특정 기능과 사용 사례를 명확하게 정의하기 위해 relevance_search 및 nl_search 도구의 설명을 변경하여</strong> 구현합니다.</p></li><li><p> <strong>도구 호출 정확도가</strong> <strong>25 %</strong> 향상될 것으로 예상됩니다 %.</p></li><li><p>다른 지표에 부정적인 영향을 미치지 않는지 확인하여 이것이 순 긍정적인지 확인할 것입니다. <strong>사실성과 완전성</strong>.</p></li><li><p><strong>정확한 도구 설명은 상담원이 다양한 쿼리 유형에 가장 적합한 검색 도구를 보다 정확하게 선택하고 적용하는 데 도움이 되어 잘못된 적용을 줄이고 전반적인 검색 효율성을 개선할</strong> 수 있기 때문입니다.</p></li></ol><h3>4단계: 평가 데이터 세트 준비</h3><p><em>시스템의 성능을 측정하기 위해 실제 시나리오를 캡처한 데이터 세트를 사용합니다.</em></p><p>수행하는 평가 유형에 따라 LLM에 공급되는 원시 데이터 등 다양한 유형의 데이터 형식이 필요할 수 있습니다(예 공격 발견을 위한 공격 시나리오) 및 예상 결과물입니다. 애플리케이션이 챗봇인 경우 입력은 사용자 쿼리일 수 있고 출력은 올바른 챗봇 응답, 검색했어야 하는 올바른 링크 등이 될 수 있습니다.</p><p>공격 발견 예시:</p><p>10가지 새로운 공격 시나리오</p><p>오 마이 멀웨어 에피소드 8편(omymalware.com)</p><p>4가지 다중 공격 시나리오(처음 2개 카테고리의 공격을 결합하여 생성)</p><p>3가지 양성 시나리오</p><p>Elastic AI 에이전트 평가 데이터 세트 예제<a href="https://github.com/elastic/kibana/blob/main/x-pack/platform/packages/shared/onechat/kbn-evals-suite-onechat/evals/kb/kb.spec.ts">(Kibana 데이터 세트 링크)</a>:</p><p>14 오픈 소스 데이터 세트를 사용하여 여러 소스를 KB 단위로 시뮬레이션하는 인덱스.</p><p>5가지 쿼리 유형(분석, 텍스트 검색, 하이브리드...)</p><p>7 쿼리 의도 유형(절차적 , 사실적 - 분류, 조사; ...)</p><h3>5단계: 실험 실행</h3><p>평가 데이터 세트에 대해 기존 에이전트와 수정된 버전 모두에서 응답을 생성하여 실험을 실행합니다. 사실 여부와 같은 지표를 계산합니다(2단계 참조).</p><p>2단계에서 요구되는 메트릭을 기반으로 다양한 평가를 혼합합니다:</p><ul><li><p>규칙 기반 평가(예 파이썬/타입스크립트를 사용하여 .json이 유효한지 확인)</p></li><li><p>LLM-as-judge(응답이 원본 문서와 사실적으로 일치하는지 별도의 LLM에게 문의)</p></li><li><p>뉘앙스 품질 검사를 위한 휴먼 인 더 루프 검토</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17ec63af0850d8dd/6a17f054505ac3e508ad8c1e/8648e75818d3291f0ac66f069438a500d42b8225-1600x1099.png" alt="이것은 내부 프레임워크에서 생성된 평가 결과의 예입니다. 다양한 데이터 세트에서 수행한 실험의 다양한 지표를 제시합니다." /><h3>6단계: 결과 분석 + 반복하기</h3><p>이제 메트릭을 확보했으니 결과를 분석합니다. <u><em>결과가 3단계에서 정의한 성공 기준을 충족하더라도 변경 사항을 프로덕션에 병합하기 전에 인적 검토를</em></u> 거치고, 기준에 부합하지 않으면 문제를 반복하여 수정한 다음 새 변경 사항에 대한 평가를 실행합니다.</p><p>병합하기 전에 최적의 변경 사항을 찾으려면 몇 번의 반복이 필요할 것으로 예상됩니다. 커밋을 푸시하기 전에 로컬 소프트웨어 테스트를 실행하는 것과 마찬가지로, 오프라인 평가도 로컬 변경 사항 또는 여러 제안된 변경 사항으로 실행할 수 있습니다. 실험 결과, 종합 점수 및 시각화 저장을 자동화하여 분석을 간소화하는 데 유용합니다.</p><h3>7단계: 의사 결정 및 문서화</h3><p>의사 결정 프레임워크와 승인 기준에 따라 변경 사항 병합을 결정하고 실험을 문서화합니다. 의사 결정은 다면적이며 다른 데이터 세트에 대한 회귀 시나리오를 확인하거나 제안된 변경의 비용 편익을 평가하는 등 평가 데이터 세트 이외의 요소를 고려할 수 있습니다.</p><p>예시: 몇 번의 반복을 테스트하고 비교한 후 가장 높은 점수를 받은 변경 사항을 선택하여 제품 관리자 및 기타 관련 이해 관계자에게 보내 승인을 받습니다. 이전 단계의 결과를 첨부하여 결정을 내리는 데 도움이 되도록 합니다. 공격 탐색 측면에 대한 더 많은 예는 <a href="https://www.elastic.co/blog/elastic-security-generative-ai-features">Elastic Security의 생성형 AI 기능의 비하인드 스토</a>리를 참조하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62a466f3a0da114a/6a17f056faa91342c393c96c/74c80b8f34dce8ddd20873ecb2f553873587ed35-1600x618.png" alt="" /><h2>결론</h2><p>이 블로그에서는 실험 워크플로우의 엔드투엔드 프로세스를 살펴보면서 Elastic 사용자에게 변경 사항을 릴리즈하기 전에 에이전트 시스템의 변경 사항을 평가하고 테스트하는 방법을 설명합니다. 또한 Elastic에서 에이전트 기반 워크플로우를 개선하는 몇 가지 예도 제공했습니다. 다음 블로그 게시물에서는 좋은 데이터 집합을 만드는 방법, 신뢰할 수 있는 메트릭을 설계하는 방법, 여러 메트릭이 관련된 경우 의사 결정을 내리는 방법 등 다양한 단계의 세부 사항에 대해 자세히 설명합니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-evaluation-elastic</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-evaluation-elastic</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Susan Chang,Abhimanyu Anand]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte578b636637be6b1/6a17f057e8fbcebe9e3a1a36/ef3922076713872163e1aab47735361513b2c9ee-2400x1352.heif" length="0" type="image/*"/>
    <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[A2A 프로토콜을 통해 Elastic 에이전트를 Gemini Enterprise에 연결하기]]></title>
    <description><![CDATA[에이전트 빌더를 사용하여 사용자 정의 Elastic 에이전트를 A2A 프로토콜을 통해 Gemini Enterprise와 같은 외부 서비스에 노출하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><strong>Elastic 에이전트 빌더는</strong> Elasticsearch에서 직접 데이터 기반 AI 에이전트를 생성하기 위한 일련의 기능입니다. <a href="https://www.elastic.co/search-labs/blog/series/context-aware-ai-agentic-workflows-with-elastic">시리즈의</a> 이전 게시물에서는 사용자 지정 상담원에게 복잡한 작업을 수행할 수 있는 툴을 제공하고 이들의 행동을 안내하는 일련의 사용자 지정 지침을 제공하는 방법에 대해 설명했습니다.</p><p>하지만 이미 사용하고 있는 애플리케이션 및 생산성 도구와 함께 사용자 지정 상담원을 사용하려면 어떻게 해야 할까요?</p><p>이것이 바로 <strong>에이전트 간(A2A) 프로토콜이</strong> 필요한 이유입니다. A2A는 상호 운용성을 위한 <a href="https://github.com/a2aproject/A2A">개방형 표준으로</a>, 서로 다른 플랫폼의 상담원들이 소통하고 협업할 수 있도록 합니다. 그리고 이를 Elastic 에이전트 빌더에 직접 구축했습니다.</p><p>오늘은 구축한 사용자 지정 상담원을 다른 서비스, 특히 <strong>Gemini Enterprise </strong>(이전의 Agentspace)에 노출하는 방법을 보여드리겠습니다.</p><h2>개방형 표준의 힘: A2A가 중요한 이유</h2><p>블로그 게시물 <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">첫 번째 Elastic 에이전트에서</a> 시장 데이터에 안전하게 액세스할 수 있는 <em>금융 지원</em> 에이전트와 같은 사용자 정의 에이전트를 구축하는 방법을 보여드렸습니다. 그러나 작업을 재구축하지 않고 Gemini Enterprise와 같은 다른 환경에서 인사이트를 사용할 수 없다면 그 가치는 제한적입니다.</p><p>이러한 상호운용성 문제가 에이전트 AI의 발목을 잡고 있습니다. 에이전트는 여러 플랫폼에서 소통하기 위해 공통 언어가 필요하며, 이것이 바로 A2A 프로토콜의 역할입니다. 상담원과 직접 소통할 수 있는 표준 커뮤니케이션 계층을 제공할 뿐만 아니라 조직 전체의 전문 상담원이 협업하고 인사이트를 공유할 수 있는 미래를 열어줍니다.</p><p>이를 가능하게 하기 위해 Elastic 에이전트 빌더는 기본적으로 모든 에이전트에 대해 두 개의 표준 엔드포인트를 통해 A2A 프로토콜을 지원합니다:</p><ol><li><p>상담원 <strong>카드 엔드포인트(</strong><strong><code>GET {your-kibana-url}/api/agent_builder/a2a/{agentId}.json</code></strong><strong>) - </strong>사용자 지정 상담원의 명함 역할을 합니다. A2A 호환 서비스에 상담원에 대한 메타데이터(이름, 설명, 기능 등)를 제공합니다.</p></li><li><p><strong>A2A 프로토콜 엔드포인트 (</strong><strong><code>POST {your-kibana-url}/api/agent_builder/a2a/{agentId}</code></strong><strong>)</strong> - 통신 채널입니다. 다른 상담원이 여기로 요청을 보내면 상담원이 이를 처리하고 응답을 반환하며, 이 모든 과정은 <a href="https://a2a-protocol.org/latest/specification/">A2A 프로토콜 사양에</a> 따라 이루어집니다.</p></li></ol><h2>A2A 인스펙터로 에이전트 테스트하기</h2><p>에이전트를 프로덕션 시스템에 연결하기 전에 에이전트가 제대로 통신하고 있는지 확인하는 것이 좋습니다. 가장 쉬운 방법은 A2A 통합 테스트 및 디버깅을 위해 특별히 설계된 도구인 <strong>A2A 인스펙터를</strong> 사용하는 것입니다.</p><p>인스펙터를 실행하는 방법은 간단합니다. <a href="https://github.com/a2aproject/a2a-inspector">a2a-inspector</a> 리포지토리를 복제하고 README 지침에 따라 <a href="https://github.com/a2aproject/a2a-inspector?tab=readme-ov-file#3-run-the-application">애플리케이션을 실행할</a> 수 있습니다. 시작하면 기본적으로 <code>http://localhost:5001/</code> 에서 UI를 사용할 수 있습니다.</p><p>A2A 인스펙터를 상담원과 연결하려면 두 가지 주요 정보를 제공해야 합니다:</p><ul><li><p>상담원 카드 URL: 상담원을 설명하는 엔드포인트입니다. <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">이전 게시물의 재무 도우미 상담</a>원의 경우 이 URL은 <code>{your-kibana-url}/api/agent_builder/a2a/financial_assistant.json</code> 입니다.</p></li><li><p>인증 헤더: 인증에 표준 API 키를 사용합니다.</p></li></ul><p>인스펙터의 UI에 이러한 세부 정보를 입력하면 바로 상담원과 연결하여 채팅을 시작할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6381135e3fb297df/6a17ef4bec0f898b0c5a66ea/7231c72bf30bed2a854f58658c1eca2843f43bfc-1600x1296.png" alt="A2A 상담원 카드 및 상담원 인스펙터 설정" /><p>이 간단한 검증을 통해 에이전트가 올바르게 구성되었으며 다음 단계로 넘어갈 준비가 되었다는 확신을 가질 수 있습니다.</p><h2>라이브 시작! Gemini Enterprise의 사용자 지정 에이전트</h2><p>이제 흥미로운 부분인 맞춤형 재무 설계사 에이전트를 Gemini Enterprise(이전의 Agentspace) 내에서 구현하는 것입니다. 이 통합은 <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-ai-agent">Google Cloud Marketplace에서 사용할 수 있는 Elastic AI 에이전트에</a> 의해 구동됩니다.</p><p>연결되면 Gemini Enterprise는 A2A 프로토콜을 사용하여 상담원과 직접 통신합니다. 이제 사용자는 익숙한 환경을 벗어나지 않고도 사용자 정의 Elasticsearch 에이전트의 심층적인 데이터 기반 인사이트에 액세스할 수 있습니다. 상담원 목록에서 사용자 정의 Elastic 에이전트를 확인할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f54f0bb15216d8e/6a17ef4d6df73107d90a0fdb/37a39e92ebf3d72c6c8014397cd8e846336173a4-1600x834.png" alt="Google 상담원 공간 목록에서 사용자 지정 상담원 보기" /><p>Gemini Enterprise의 사용자가 다음과 같이 질문한다고 상상해 보세요:</p><p><em>"시장 심리가 걱정됩니다. 어떤 고객이 악재로 인해 가장 위험에 처해 있는지 알려주실 수 있나요?</em>"</p><p>뒤에서 Gemini Enterprise는 A2A 프로토콜을 통해 이 쿼리를 사용자 정의 Elastic 에이전트로 라우팅합니다. 그런 다음 상담원이 전문화된 도구를 사용하여 데이터를 쿼리하고 답변을 작성하여 다시 전송합니다. 최종 사용자에게는 매끄러운 경험이 제공됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte130c332ee0648a6/6a17ef4fe9ea874426a9c6bb/e5f126c1a27a51c6e69a767aa87c9f746b62e39c-1600x1044.png" alt="사용자가 에이전트스페이스에 쿼리를 요청하면 백그라운드에서 쿼리가 어떻게 되는지 알아보기" /><p>그리고 여기서 멈추지 않습니다! 이제 Elastic 에이전트로 검색된 답변을 다른 전문 에이전트를 트리거할 수 있는 다음 질문의 컨텍스트로 사용할 수 있습니다(예. 투자 플랫폼 에이전트를 통해 상장사에 대한 노출을 조정할 수 있습니다). 검색창을 떠나지 않고도 이 모든 것이 가능합니다.</p><p>A2A가 포함된 Gemini Enterprise에 배포된 Elastic 에이전트를 사용하면 사용자가 데이터 및 도구와 대화할 수 있는 단일 UI를 제공하여 액세스, 오케스트레이션, 워크플로우를 통합하여 AI, 검색, 엔터프라이즈 시스템 간의 마찰을 제거할 수 있으며, 모두 맥락에 맞게 사용할 수 있습니다. 사용자 입장에서는 도구 전환이 줄어들고 직관적이고 유능한 AI 비서가 더 많아진다는 뜻입니다. 조직에게는 일관된 거버넌스, 확장성 및 상호 운용성이 내장되어 있다는 의미입니다.</p><h2>구축할 차례</h2><p>이제 어디서나 Elastic 에이전트를 사용할 수 있는 도구가 생겼습니다. 개방형 A2A 프로토콜을 활용하면 데이터를 인식하는 맞춤형 상담원의 범위를 확장할 수 있습니다.</p><p>이 게시물에서는 주요 단계를 안내해 드렸습니다:</p><ul><li><p>A2A 에이전트 카드 및 프로토콜 엔드포인트를 통해 에이전트를 노출합니다.</p></li><li><p>A2A 인스펙터로 연결을 테스트합니다.</p></li><li><p>Google의 Gemini Enterprise와 같은 외부 서비스에 상담원을 실시간으로 통합합니다.</p></li></ul><p>상담원을 더 이상 격리할 필요가 없습니다. 여러분이 만든 강력하고 상호 연결된 시스템을 빨리 보고 싶어요. 행복한 건물!</p><p>가장 쉽게 시작할 수 있는 방법은 <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace에서</a>Elastic Cloud 무료 체험판을 사용하는 것입니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jedr Blaszyk,Valerio Arvizzigno,Joe McElroy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63d7675adc5bc211/6a17ef51ddf97d38e8910bdf/5be8a425fab55dca2f9717d2e50812b0450fa625-1440x840.png" length="0" type="image/png"/>
    <pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch용 에이전트 AI 도구 개선 실험]]></title>
    <description><![CDATA[확장 가능한 RAG 최적화를 위해 선형 검색기, 하이브리드 검색, semantic_text를 결합하여 반복적인 실험을 통해 Elasticsearch의 AI 에이전트 워크플로우를 개선한 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>요즘 다른 모든 사람들과 마찬가지로 Elastic도 Chat, 에이전트, RAG에 올인하고 있습니다. 검색 부서에서는 최근 에이전트 빌더와 도구 레지스트리를 개발 중이며, 모두 Elasticsearch에서 데이터와 '채팅'하는 것을 간단하게 만들기 위한 것입니다.</p><p>이러한 노력의 '큰 그림'에 <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">대해 자세히 알아보려면 Elasticsearch로 AI 에이전트 워크플로우 구축</a> <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">블로그 또는 첫 번째 Elastic 에이전트를 읽어보세요: 단일 쿼리에서 AI 기반 채팅까지에서</a> 보다 실용적인 입문서를 읽어보세요.</p><p>하지만 이 블로그에서는 채팅을 시작할 때 가장 먼저 일어나는 일 중 하나를 조금 더 자세히 살펴보고 최근 개선된 몇 가지 사항을 안내해드리려고 합니다.</p><h2>여기서 무슨 일이 일어나고 있나요?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>Elasticsearch 데이터와 채팅할 때 기본 AI 에이전트가 이 표준 플로우를 안내합니다:</p><ol><li><p>프롬프트를 확인합니다.</p></li><li><p>해당 프롬프트에 대한 답변이 포함되어 있을 가능성이 높은 인덱스를 식별합니다.</p></li><li><p>프롬프트에 따라 해당 인덱스에 대한 쿼리를 생성합니다.</p></li><li><p>해당 쿼리로 해당 인덱스를 검색합니다.</p></li><li><p>결과를 종합합니다.</p></li><li><p>결과가 프롬프트를 해결할 수 있나요? 그렇다면 응답하세요. 그렇지 않다면 반복하되 다른 것을 시도하세요.</p></li></ol><p>검색 증강 세대(RAG)에 불과하기 때문에 너무 새롭지 않을 것입니다. 예상대로 응답의 품질은 초기 검색 결과의 관련성에 따라 크게 달라집니다. 따라서 응답 품질을 개선하기 위해 노력하면서 3단계에서 생성하고 4단계에서 실행하는 쿼리에 매우 세심한 주의를 기울이고 있습니다. 그리고 흥미로운 패턴을 발견했습니다.</p><p>첫 번째 응답이 '나쁨'인 경우가 종종 있었는데, 이는 쿼리를 잘못 실행했기 때문이 아니었습니다. 쿼리할 <em>인덱스를 잘못 선택했기</em> 때문입니다. 3단계와 4단계는 보통 2단계가 문제가 되지 않았습니다.</p><h2>우리가 뭘 하고 있었나요?</h2><p>초기 구현은 간단했습니다. 저희는 <code>_cat/indices</code> 을 통해 사용 가능한 모든 인덱스를 나열한 다음, 이 인덱스 중 사용자의 메시지/질문/프롬프트에 가장 적합한 인덱스를 식별하도록 LLM에 요청하는 도구(index_explorer라고 함)를 구축했습니다. 이 <a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">원본 구현은 여기에서</a> 확인할 수 있습니다.</p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

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

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

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


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>이 도구의 초기 개발자는 이러한 문제를 예상하고 있었습니다. 인덱스의 매핑은 정보를 얻을 수 있는 금광이지만, 상당히 장황한 JSON 블록이기도 합니다. 그리고 수많은 인덱스(평가 데이터 세트는 20개를 정의함)를 비교하는 현실적인 시나리오에서는 이러한 JSON 블롭이 합쳐집니다. 따라서 모든 옵션에 대한 인덱스 이름뿐만 아니라 각 옵션의 전체 매핑이 아닌 더 많은 컨텍스트를 LLM에 제공하여 결정에 도움을 주고자 합니다.</p><h3>가설 2: 절충안으로 '플랫화' 매핑(필드 목록) 사용</h3><p>우리는 인덱스 작성자가 의미론적으로 의미 있는 인덱스 이름을 사용한다는 가정에서 시작했습니다. 이 가정을 필드 이름으로도 확장하면 어떨까요? 이전 실험은 실패했는데, 그 이유는 JSON 매핑에 복잡한 메타데이터와 상용구가 많이 포함되어 있었기 때문입니다.</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>예를 들어, 위의 블록은 236자이며 Elasticsearch 매핑에서 단 하나의 필드만 정의합니다. 반면 "description_text" 문자열은 16자에 불과합니다. 이는 문자 수가 거의 15배 증가한 것이지만, 해당 필드가 사용 가능한 데이터에 대해 의미하는 바를 설명하는 데 있어 의미 있는 의미 개선은 없습니다. 모든 인덱스에 대한 매핑을 가져오되, LLM으로 보내기 전에 필드 이름 목록으로만 '플랫화'하면 어떨까요?</p><p>저희도 사용해 보았습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>정말 멋지네요! 전반적으로 개선되었습니다. 하지만 더 잘할 수 있을까요?</p><h3>가설 3: 매핑 _meta의 설명</h3><p>추가 컨텍스트 없이 필드 이름만으로 그렇게 많은 점프가 발생했다면, 아마도 상당한 컨텍스트를 추가하는 것이 더 좋을 것입니다! 모든 인덱스에 반드시 설명을 첨부해야 하는 것은 아니지만, 매핑의 _meta 객체에 모든 종류의 인덱스 수준 메타데이터를 추가할 수 있습니다. 생성된 인덱스로 돌아가서 데이터 세트의 모든 인덱스에 대한 설명을 추가했습니다. 설명이 지나치게 길지 않다면 전체 매핑보다 적은 토큰을 사용하고 인덱스에 포함된 데이터에 대한 훨씬 더 나은 인사이트를 제공해야 합니다. 실험을 통해 이 가설을 검증했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>소폭의 개선으로 이제 &gt;90% 전반적으로 정확해졌습니다.</p><h3>가설 4: 합이 부분보다 큼</h3><p>필드 이름을 사용하면 결과가 향상되었습니다. 설명을 통해 결과가 향상되었습니다. 따라서 설명과 필드 이름을 <em>모두 </em>활용하면 더 나은 결과를 얻을 수 있겠죠?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>데이터는 "아니오"라고 응답했습니다(이전 실험에서 변경 사항 없음). 여기서 유력한 이론은 설명이 처음부터 인덱스 필드/매핑에서 생성되었기 때문에 이 두 컨텍스트 간에 서로 다른 정보가 충분하지 않아 결합할 때 '새로운' 것을 추가하는 데 도움이 되지 않는다는 것입니다. 또한 20개의 테스트 지수에 대해 전송하는 페이로드가 상당히 커지고 있습니다. 지금까지 우리가 따라온 사고방식은 확장할 수 없습니다. 사실, 지금까지의 실험 중 어떤 것도 수백, 수천 개의 인덱스가 있는 Elasticsearch 클러스터에서는 작동하지 않을 것이라고 믿을 만한 충분한 이유가 있습니다. 인덱스의 총 수가 증가함에 따라 LLM으로 전송되는 메시지 크기를 선형적으로 증가시키는 접근 방식은 일반화할 수 있는 전략이 아닐 수 있습니다.</p><p>우리에게 정말 필요한 것은 수많은 후보를 가장 관련성이 높은 옵션으로 좁히는 데 도움이 되는 접근 방식입니다....</p><p>여기에는 검색 문제가 있습니다.</p><h3>가설 5: 시맨틱 검색을 통한 선택</h3><p>인덱스 이름에 의미론적 의미가 있는 경우, 벡터로 저장하여 의미론적으로 검색할 수 있습니다.</p><p>인덱스의 필드 이름에 의미론적 의미가 있는 경우, 이를 벡터로 저장하고 의미론적으로 검색할 수 있습니다.</p><p>인덱스에 의미론적 의미가 있는 설명이 있는 경우, 이 역시 벡터로 저장하고 의미론적으로 검색할 수 있습니다.</p><p>오늘날 Elasticsearch 인덱스는 이러한 정보를 검색할 수 없지만(어쩌면 그렇게 해야 할지도 모릅니다!), 그 차이를 해결할 수 있는<a href="https://github.com/elastic/connectors/pull/3638"> 무언가를 함께 해킹하는</a> 것은 꽤나 사소한 일이었습니다. Elastic의 커넥터 프레임워크를 사용해 클러스터의 모든 인덱스에 대한 문서를 출력하는 커넥터를 구축했습니다. 출력 문서는 다음과 같은 형태가 됩니다:</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>저는 이 문서들을 수동으로 매핑을 정의한 새 인덱스로 보냈습니다:</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>이렇게 하면 의미론적 의미를 가진 다른 모든 필드가 청크업되어 색인되는 단일 semantic_content 필드가 생성됩니다. 이 인덱스를 검색하는 것은 사소한 일이 됩니다:</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>수정된 <code>index_explorer</code> 도구는 이제 LLM에 요청할 필요 없이 주어진 쿼리에 대해 단일 임베딩을 요청하고 효율적인 벡터 검색 작업을 수행할 수 있으므로 <em>훨씬</em> 더 빨라졌습니다. 상위 히트를 선택한 지표로 삼은 결과 다음과 같은 결과를 얻었습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>이 접근 방식은 확장 가능합니다. 이 접근 방식은 효율적입니다. 하지만 이 접근 방식은 기준선보다 겨우 나은 수준입니다. 하지만 이는 놀라운 일이 아닙니다. 검색 접근 방식이 매우 순진하기 때문입니다. 뉘앙스가 없습니다. 인덱스의 이름과 설명이 인덱스에 포함된 임의의 필드 이름보다 더 많은 가중치를 가져야 한다는 인식이 없습니다. 동의어 일치보다 정확한 어휘 일치에 가중치를 부여하는 어포던스는 없습니다. 그러나 고도로 미묘한 쿼리를 작성하려면 현재 데이터에 대해 많은 것을 가정해야 합니다. 지금까지 인덱스와 필드 이름에 의미론적 의미가 있다는 큰 가정을 해 보았지만, 한 걸음 더 나아가서 <em>얼마나 많은</em> 의미를 가지고 있으며 서로 어떻게 연관되어 있는지 가정해 볼 필요가 있습니다. 이렇게 하지 않으면 최상의 일치 항목을 최고의 결과로 확실하게 식별할 수는 없지만, 상위 N개의 결과 중 어딘가에 최상의 일치 항목이 있다고 말할 수 있습니다. 우리는 의미론적 정보가 존재하는 맥락에서 의미론적 정보를 소비하고, 의미론적으로 구별되는 방식으로 자신을 표현할 수 있는 다른 개체와 비교하여 그 둘을 판단할 수 있는 무언가가 필요합니다. LLM처럼요.</p><h3>가설 6: 후보 세트 감소</h3><p>이 외에도 여러 가지 실험이 있었지만, 핵심적인 돌파구는 시맨틱 검색만으로 최적의 일치 항목을 고르려는 욕구를 버리고 대신 시맨틱 검색을 필터로 활용하여 LLM의 고려 대상에서 관련 없는 인덱스를 걸러내는 것이었습니다. <a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">검색을</a> 위해 선형 검색기, 하이브리드 검색과 RRF, <code>semantic_text</code> 를 결합하여 상위 5개 일치하는 인덱스로 결과를 제한했습니다.</p><p>그런 다음 각 일치 항목에 대해 인덱스의 이름, 설명 및 필드 이름을 LLM용 메시지에 추가했습니다. 결과는 환상적이었습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>역대 실험 중 가장 높은 정확도! 또한 이 접근 방식은 총 인덱스 수에 비례하여 메시지 크기가 증가하지 않기 때문에 훨씬 더 확장성이 뛰어납니다.</p><h2>결과</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>첫 번째 분명한 결과는 우리의 기준선을 개선할 <em>수</em> 있다는 것이었습니다. 지금 생각하면 당연해 보이지만 실험을 시작하기 전에는 <code>index_explorer</code> 도구를 완전히 버리고 검색 공간을 제한하기 위해 사용자의 명시적 설정에 의존해야 하는지에 대해 진지한 논의가 있었습니다. 여전히 실행 가능하고 유효한 옵션이지만, 이 연구는 이러한 사용자 입력을 사용할 수 없는 경우 인덱스 선택을 자동화하는 방향으로 나아갈 수 있는 유망한 경로가 있음을 보여줍니다.</p><p>다음으로 분명한 결과는 문제에 더 많은 설명 문자를 던지는 것만으로는 수익이 줄어든다는 것이었습니다. 이 연구 이전에는 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">필드 수준 메타데이터를</a> 저장하기 위해 Elasticsearch의 기능을 확장하는 데 투자해야 할지 고민하고 있었습니다. 현재 <code>meta</code> 값은 50자로 제한되어 있으며, 필드의 의미적 이해를 도출하기 위해서는 이 값을 늘려야 한다는 가정이 있었습니다. 이는 분명히 사실이 아니며, LLM은 필드 이름만으로 상당히 잘 작동하는 것 같습니다. 나중에 더 조사할 수 있지만 더 이상 시급하다고 생각하지 않습니다.</p><p>반대로, 이는 '검색 가능한' 인덱스 메타데이터의 중요성에 대한 명확한 증거가 되었습니다. 이 실험을 위해 저희는 인덱스 오브 인덱스를 해킹했습니다. 그러나 이것은 Elasticsearch에 직접 구축하거나, 관리할 API를 구축하거나, 최소한 관련 규칙을 수립하는 것을 검토할 수 있는 부분입니다. 여러 옵션을 검토하고 내부적으로 논의 중이니 계속 지켜봐 주시기 바랍니다.</p><p>마지막으로, 이러한 노력을 통해 시간을 들여 실험하고 데이터 기반 의사 결정을 내리는 것이 얼마나 가치 있는 일인지 확인했습니다. 실제로 에이전트 빌더 제품에 강력한 제품 내 평가 기능이 필요하다는 것을 재확인하는 데 도움이 되었습니다. 인덱스를 선택하는 도구만을 위한 전체 테스트 하네스를 구축해야 한다면, 고객은 반복적으로 조정할 때 사용자 지정 도구를 정성적으로 평가할 수 있는 방법이 반드시 필요합니다.</p><p>앞으로 무엇을 만들게 될지 기대가 되며, 여러분도 기대가 되시길 바랍니다!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[첫 번째 Elastic 에이전트: 단일 쿼리에서 AI 기반 채팅까지]]></title>
    <description><![CDATA[Elastic의 AI 에이전트 빌더를 사용해 전문화된 AI 에이전트를 생성하는 방법을 알아보세요. 이 블로그에서는 금융 AI 에이전트를 구축하는 방법을 소개합니다.]]></description>
    <content:encoded><![CDATA[<p>Elastic의 새로운 <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">에이전트 빌더를</a> 사용하면 특정 비즈니스 도메인의 전문가 역할을 하는 전문화된 AI 에이전트를 생성할 수 있습니다. 이 기능은 단순한 대시보드와 검색창을 넘어 데이터를 수동적인 리소스에서 능동적인 대화 파트너로 탈바꿈시킵니다.</p><p>고객과의 미팅 전에 정보를 빠르게 파악해야 하는 재무 관리자가 있다고 상상해 보세요. 이제 수동으로 뉴스 피드를 검색하고 포트폴리오 대시보드를 상호 참조하는 대신 맞춤형 상담원에게 직접 질문할 수 있습니다. 이것이 바로 "채팅 우선" 접근 방식의 장점입니다. 관리자는 데이터에 직접 대화할 수 있는 라인을 통해 "ACME Corp의 최신 뉴스는 무엇이며 고객의 보유 자산에 어떤 영향을 미칩니까?" 같은 질문을 할 수 있습니다. 검색하면 몇 초 만에 종합적인 전문가 답변을 얻을 수 있습니다.</p><p>오늘날 금융 전문가를 구축하는 과정에서 데이터만큼이나 응용 분야도 다양합니다. 동일한 권한으로 위협을 찾아내는 사이버 보안 분석가, 장애를 진단하는 사이트 안정성 엔지니어, 캠페인을 최적화하는 마케팅 관리자를 만들 수 있습니다. 도메인에 관계없이 핵심 미션은 동일합니다. 데이터를 전문가와 대화할 수 있는 데이터로 전환하는 것입니다.</p><h2>0단계: 데이터 세트</h2><p>현재 저희 데이터 세트는 금융 계좌, 자산 현황, 뉴스 및 재무 보고서로 구성된 합성 금융 기반 데이터 세트입니다. 합성 데이터 세트이긴 하지만, 실제 금융 데이터 세트를 단순화한 버전을 재현한 것입니다.</p><p><code>financial_accounts</code>: 위험 프로필이 있는 고객 포트폴리오</p><p><code>financial_holdings</code>: 매수 내역이 있는 주식/ETF/채권 포지션</p><p><code>financial_asset_details</code>: 주식/ETF/채권에 대한 세부 정보</p><p><code>financial_news</code>: 감정 분석을 통해 AI가 생성한 시장 기사</p><p><code>financial_reports</code>: 기업 실적 및 애널리스트 노트</p><p>이 데이터 세트는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">여기에</a> 있는 첨부된 노트북을 따라 직접 로드할 수 있습니다.</p><h2>1단계: 기초 - ES|QL로서의 비즈니스 로직</h2><p>모든 AI 기술은 탄탄한 로직에서 시작됩니다. 재무 관리자 상담원에게 일반적인 질문에 답하는 방법을 가르쳐야 합니다: "시장 심리가 걱정됩니다. 어떤 고객이 나쁜 소식으로 가장 위험에 처해 있는지 보여 주시겠습니까?" 이 질문은 단순한 검색을 넘어서는 질문입니다. 이를 위해서는 시장 심리와 고객 포트폴리오의 상관관계를 파악해야 합니다.</p><p>부정적인 기사에 언급된 자산을 찾고, 해당 자산을 보유한 모든 고객을 식별하고, 해당 자산의 현재 시장 가치를 계산한 다음, 그 결과를 순위화하여 위험도가 가장 높은 자산의 우선순위를 정해야 합니다. 이러한 복잡한 다중 조인 분석은 고급 ES|QL 도구에 완벽한 작업입니다.</p><p>다음은 우리가 사용할 전체 쿼리입니다. 인상적으로 보이지만 개념은 간단합니다.</p><h2>분석: 조인 및 가드레일</h2><p>이 쿼리에는 상담원 빌더를 만드는 두 가지 중요한 개념이 작용하고 있습니다.</p><h3>1. 조회 조인</h3><p>수년 동안 Elasticsearch에서 가장 많이 요청된 기능 중 하나는 공통 키를 기반으로 서로 다른 인덱스의 데이터를 조인하는 기능이었습니다. 이제 ES|QL을 사용하면 <code>LOOKUP JOIN</code> 에서 가능합니다.</p><p>새 쿼리에서는 먼저 부정적인 뉴스를 자산 세부 정보에 연결한 다음, 해당 자산을 고객 보유 자산에 연결하고 마지막으로 고객의 계정 정보에 연결하는 세 개의 <code>LOOKUP JOIN</code> 연쇄를 수행합니다. 이렇게 하면 하나의 효율적인 쿼리에서 4개의 서로 다른 인덱스로부터 놀랍도록 풍부한 결과를 얻을 수 있습니다. 즉, 모든 데이터를 미리 하나의 거대한 인덱스로 비정규화할 필요 없이 서로 다른 데이터 집합을 결합하여 통찰력 있는 단일 답변을 만들 수 있습니다.</p><h3>2. LLM 가드레일로서의 매개변수</h3><p>쿼리가 <code>?time_duration</code> 을 사용하는 것을 알 수 있습니다. 이는 단순한 변수가 아니라 AI를 위한 보호 장치입니다. LLM(대규모 언어 모델)은 쿼리 생성에 탁월하지만, 데이터를 자유롭게 사용할 수 있도록 허용하면 비효율적이거나 심지어 잘못된 쿼리가 발생할 수도 있습니다.</p><p>매개변수화된 쿼리를 생성하여 전문가가 이미 정의한 테스트되고 효율적이며 올바른 비즈니스 로직 내에서 LLM이 작동하도록 강제합니다. 이는 개발자들이 수년 동안 검색 템플릿을 사용하여 애플리케이션에 쿼리 기능을 안전하게 노출해 온 방식과 유사합니다. 에이전트는 이번 주 "이번 주" 같은 사용자의 요청을 해석하여 <code>time_duration</code> 매개 변수를 채울 수 있지만, 반드시 쿼리 구조를 사용하여 답변을 얻어야 합니다. 이를 통해 유연성과 제어의 완벽한 균형을 이룰 수 있습니다.</p><p>궁극적으로 이 쿼리를 통해 데이터를 이해하는 전문가가 자신의 지식을 도구로 캡슐화할 수 있습니다. 그러면 다른 사람, 즉 AI 에이전트는 이 도구를 사용하여 근본적인 복잡성에 대해 아무것도 모른 채 단일 매개변수만 제공하면 상관관계가 있는 결과를 얻을 수 있습니다.</p><h2>2단계: 기술 - 쿼리를 재사용 가능한 도구로 전환하기</h2><p>ES|QL 쿼리는 <strong>도구로</strong> 등록하기 전까지는 텍스트에 불과합니다. 상담원 빌더에서 도구는 단순히 저장된 쿼리 그 이상의 의미로, AI 상담원이 이해하고 사용할 수 있는 "스킬(" )을 의미합니다. 마법 같은 것은 저희가 제공하는 <strong>자연어 설명에</strong> 있습니다. 이 설명은 사용자의 질문을 기본 쿼리 로직에 연결하는 다리 역할을 합니다. 방금 작성한 쿼리를 등록해 보겠습니다.</p><h3>UI 경로</h3><p>Kibana에서 도구를 만드는 것은 간단한 과정입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte73e11c1d87593fa/6a17f2134202294dae29f6f2/a29c53a73b99af5972273c51218ea9004a9b0abb-1600x812.png" alt="Kibana에서 도구를 만드는 방법." /><p>1. <strong>상담원으로</strong>이동합니다.</p><ul><li><p><strong> 도구 </strong>또는 <strong>도구 관리를</strong> 클릭하고 <strong>새 도구</strong> 버튼을 클릭합니다.</p></li></ul><p>2. 다음 세부 정보를 입력하여 양식을 작성합니다:</p><ul><li><p><strong>도구 ID:</strong> <code>find_client_exposure_to_negative_news</code></p></li></ul><p>             i. 도구의 고유 ID입니다.</p><ul><li><p><strong>설명:</strong> "부정적인 뉴스에 노출된 고객 포트폴리오를 찾습니다. 이 도구는 최근 뉴스와 보고서에서 부정적인 감정을 검색하고 관련 자산을 식별한 후 해당 자산을 보유한 모든 고객을 찾아냅니다. 포지션의 현재 시장가 기준으로 정렬된 목록을 반환하여 잠재적 위험이 가장 높은 포지션을 강조 표시합니다."</p></li></ul><p>             i. LLM은 이를 읽고 이 도구가 작업에 적합한지 여부를 결정합니다.</p><ul><li><p><strong>레이블</strong>: <code>retrieval</code> 및 <code>risk-analysis</code></p></li></ul><p>         레이블은 여러 도구를 그룹화하는 데 사용됩니다.</p><ul><li><p><strong>구성으로 이동합니다:</strong> 1단계의 전체 ES|QL 쿼리 붙여넣기</p></li></ul><p>            i. 상담원이 사용할 검색은 다음과 같습니다.</p><p>3. <strong>쿼리에서 매개변수 유추를</strong> 클릭합니다. UI에서 <code>?time_duration</code> 을 자동으로 찾을 수 있습니다. 상담원(및 다른 사용자)이 목적을 이해하는 데 도움이 되도록 각각에 대한 간단한 설명을 추가하세요.</p><ul><li><p><code>time_duration</code>: 부정적인 뉴스를 다시 검색할 수 있는 기간입니다. 형식은 "X 시간" 기본값은 8760시간입니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7afbb0589c1828ad/6a17f2146864a44e7cb688a9/deb422d97863f78dbe08bfa2e3c708d1f75166ff-1600x938.png" alt="ESQL 쿼리를 사용하여 로직 및 필요한 매개변수를 포함하여 도구를 구성합니다. " /><p>4. 테스트해 보세요!</p><ul><li><p>저장 &amp; 테스트를 클릭합니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd09afbef6e21a93/6a17f2162f4a5c73b1fa89fd/57e768b88327821e70bd616744822f98fa367362-732x136.png" alt="Kibana의 동일한 &amp; 테스트 버튼." /><ul><li><p>쿼리가 예상대로 작동하는지 테스트할 수 있는 새로운 플라이아웃이 표시됩니다.</p></li></ul><p>             i. <code>time_duration</code> 에서 원하는 범위를 입력합니다. 여기서는 "8760 시간"을 사용하고 있습니다.</p><ul><li><p>'제출'을 클릭하고 모든 것이 정상적으로 진행되면 JSON 응답이 표시됩니다. 예상대로 작동하는지 확인하려면 아래로 스크롤하여 <code>values</code> 개체를 확인합니다. 여기에서 실제 일치하는 문서가 반환됩니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bdc3f093363f2a/6a17f217be60861c9c00488a/7e0c5171a4f7ffdfc1830f1a05a9acb987870b75-1600x722.png" alt="제출을 클릭한 후 표시되는 JSON 응답입니다." /><p>5. 오른쪽 상단의 'X'를 클릭하여 테스트 플라이아웃을 닫습니다. 이제 새 도구가 목록에 표시되며 상담원에게 배정할 준비가 되었습니다.</p><h3>API 경로</h3><p>자동화를 선호하거나 프로그래밍 방식으로 도구를 관리해야 하는 개발자의 경우, 한 번의 API 호출로 동일한 결과를 얻을 수 있습니다. <code>POST</code> 요청을 <code>/api/agent_builder/tools</code> 엔드포인트에 도구의 정의와 함께 보내면 됩니다.</p>POST kbn://api/agent_builder/tools
{
  "id": "find_client_exposure_to_negative_news",
  "type": "esql",
  "description": "Finds client portfolio exposure to negative news. This tool scans recent news and reports for negative sentiment, identifies the associated asset, and finds all clients holding that asset. It returns a list sorted by the current market value of the position to highlight the highest potential risk.",
  "configuration": {
    "query": """
        FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) &gt;= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50
      """,
    "params": {
      "time_duration": {
        "type": "keyword",
        "description": """The timeframe to search back for negative news. Format is "X hours" DEFAULT TO 8760 hours """
      }
    }
  },
  "tags": [
    "retrieval",
    "risk-analysis"
  ]
}<h2>3단계: 두뇌 - 사용자 지정 상담원 만들기</h2><p>재사용 가능한 스킬(도구)을 만들었습니다. 이제 실제로 사용할 페르소나, 즉 <strong>에이전트를</strong> 만들어야 합니다. 에이전트는 LLM, 액세스 권한을 부여한 특정 도구 세트, 그리고 가장 중요한 것은 에이전트의 성격, 규칙 및 목적을 정의하는 구성 요소 역할을 하는 <strong>사용자 지정 지침</strong> 세트의 조합입니다.</p><h3>프롬프트의 기술</h3><p>신뢰할 수 있는 전문 상담원을 만드는 데 있어 가장 중요한 부분은 바로 프롬프트입니다. 잘 만들어진 지침 세트는 일반 챗봇과 집중력 있는 전문 비서의 차이점입니다. 여기에서 가드레일을 설정하고, 출력을 정의하고, 에이전트에게 임무를 부여할 수 있습니다.</p><p><code>Financial Manager</code> 에이전트의 경우 다음 프롬프트를 사용합니다.</p>You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**<p>이 프롬프트가 효과적인 이유를 자세히 알아보세요:</p><ul><li><p><strong>이는 정교한 페르소나를 정의합니다: </strong>첫 번째 줄은 상담원을 "전문 데이터 인텔리전스 어시스턴트(" )로 즉시 설정하여 전문적이고 유능한 분위기를 조성합니다.</p></li><li><p><strong>추론 프레임워크를 제공합니다: </strong>상담원에게 "이해, 계획, 실행 및 종합," 표준 운영 절차를 제공하도록 지시합니다. 이를 통해 복잡한 다단계 질문을 처리하는 능력이 향상됩니다.</p></li><li><p><strong>대화형 대화를 촉진합니다: </strong>" 명확한 질문을" 하라는 지시는 상담원을 더욱 강력하게 만듭니다. 모호한 요청에 대한 잘못된 가정을 최소화하여 보다 정확한 답변으로 이어질 수 있습니다.</p></li></ul><h3>UI 경로</h3><p>1. <strong>상담원으로 이동</strong>합니다.</p><ul><li><p><strong> 도구 </strong>또는 <strong>도구 관리를</strong> 클릭하고 <strong>새 도구</strong> 버튼을 클릭합니다.</p></li></ul><p>2. 기본 세부 정보를 입력합니다:</p><ul><li><p><strong>상담원 ID:</strong> <code>financial_assistant</code>.</p></li><li><p><strong>지침을 따르세요: </strong>위의 프롬프트를 복사합니다.</p></li><li><p><strong>레이블</strong>: <code>Finance</code>.</p></li><li><p><strong>표시 이름:</strong> <code>Financial Assistant</code>.</p></li><li><p><strong>디스플레이 설명: </strong><code>An assistant for analyzing and understanding your financial data</code>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ac12cbd2b689dee/6a17f219dbb4ff262bfb57ef/18ea73f1cae620129c0afa0e7ba9e2a3390224a7-1600x1189.png" alt="재무 도우미 만들기 - 상담원 ID 필드 작성하기." /><p>3. 3. 상단으로 돌아가서 <strong>도구를</strong> 클릭합니다.</p><ul><li><p><code>find_client_exposure_to_negative_news</code> 도구 옆의 확인란을 선택합니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcd23556e556a76c5/6a17f21baf47b63a9fcde0a0/0c1e4ecbbd51d0dd10c6e861dbe9a9ccddeb35f6-1600x149.png" alt="" /><p>4. <strong>저장을</strong> 클릭합니다.</p><h3>API 경로</h3><p><code>/api/agent_builder/agents</code> 엔드포인트에 <code>POST</code> 요청을 통해 똑같은 에이전트를 만들 수 있습니다. 요청 본문에는 ID, 이름, 설명, 전체 지침, 상담원이 사용할 수 있는 도구 목록 등 모든 동일한 정보가 포함됩니다.</p>POST kbn://api/agent_builder/agents
    {
      "id": "financial_assistant",
      "name": "Financial Assistant",
      "description": "An assistant for analyzing and understanding your financial data",
      "labels": [
        "Finance"
      ],
      "avatar_color": "#16C5C0",
      "avatar_symbol": "💰",
      "configuration": {
        "instructions": """You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**
""",
        "tools": [
          {
            "tool_ids": [
              "platform.core.search",
              "platform.core.list_indices",
              "platform.core.get_index_mapping",
              "platform.core.get_document_by_id",
              "find_client_exposure_to_negative_news"
            ]
          }
        ]
      }
    }<h2>4단계: 보상 - 대화 나누기</h2><p>저희는 비즈니스 로직을 도구에 캡슐화하고 에이전트에서 사용할 수 있는 "브레인" 을 준비했습니다. 이제 이 모든 것이 한데 어우러질 때입니다. 이제 전문 상담원을 통해 데이터와 채팅을 시작할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8826539b16e46f4/6a17f21d505ac35924ad8c5c/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" alt="재정 도우미를 생성한 후 Elastic 에이전트 빌더와 대화하기." /><h3>UI 경로</h3><ol><li><p>Kibana에서 <strong>에이전트로 </strong>이동합니다.</p></li><li><p>채팅 창의 오른쪽 하단에 있는 드롭다운을 사용하여 기본 <strong>Elastic AI 에이전트에서</strong> 새로 생성된 <strong>재무 지원 </strong>에이전트로 전환하세요.</p></li><li><p>상담원이 전문 도구를 사용할 수 있는 질문을 하세요:</p><ol><li><p><em>시장 심리가 걱정됩니다. 어떤 고객이 나쁜 소식으로 인해 가장 위험에 처해 있는지 보여주시겠어요?</em></p></li></ol></li></ol><p>잠시 후 상담원이 완벽한 형식의 완전한 답변을 반환합니다. LLM의 특성상 답변의 형식이 약간 다를 수 있지만 이번 실행에서는 상담원이 반환했습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1e163fd7c4416bd/6a17f21f6864a4e35bb688ad/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" alt="부정적인 뉴스로 인해 가장 위험에 처한 고객을 위해 Elastic 에이전트 빌더가 금융 도우미로서 만든 응답입니다." /><h3>방금 무슨 일이 있었나요? 에이전트의 추론</h3><p>상담원은 "" 답을 알고 있었습니다. 작업에 가장 적합한 도구를 선택하는 데 중점을 둔 다단계 계획을 실행했습니다. 그 사고 과정을 살펴보세요:</p><ul><li><p><strong>의도를 확인했습니다:</strong> " 위험" 및 "부정적인 뉴스," 같은 질문의 키워드와 <code>find_client_exposure_to_negative_news</code> 도구의 설명이 일치했습니다.</p></li><li><p><strong>계획을 실행했습니다:</strong> 요청에서 기간을 추출하여 해당 전문 도구로 <strong>한 번만 호출합니다</strong>.</p></li><li><p><strong>작업 위임:</strong> 그런 다음 도구가 연쇄 조인, 값 계산 및 정렬과 같은 무거운 작업을 모두 수행했습니다.</p></li><li><p><strong>결과 종합:</strong> 마지막으로 에이전트는 프롬프트의 규칙에 따라 도구의 원시 데이터를 명확하고 사람이 읽을 수 있는 요약으로 포맷했습니다.</p></li></ul><p>생각을 확장하여 더 자세히 살펴보면 추측만 할 필요는 없습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93f6075be8495418/6a17f221af47b65eadcde0a4/6a4da9262d3f88c60bfd8f8bf9b67c3b84e961ba-1600x607.png" alt="재무 도우미가 부정적인 뉴스에 가장 많이 노출된 고객과 함께 찾은 50가지 문서입니다." /><h3>API 경로</h3><p>동일한 대화를 프로그래밍 방식으로 시작할 수 있습니다. 입력 질문을 <code>converse</code> API 엔드포인트로 보내면 되며, <code>financial_manager</code> 의 <code>agent_id</code> 을 지정해야 합니다.</p>POST kbn://api/agent_builder/converse
{
  "input": "Show me our largest positions affected by negative news",
  "agent_id": "financial_assistant"
}<h2>개발자용 API와 통합하기</h2><p>Kibana UI는 에이전트 구축과 관리를 위한 환상적이고 직관적인 환경을 제공하지만, 오늘 보신 모든 것을 프로그래밍 방식으로도 수행할 수 있습니다. 에이전트 빌더는 일련의 API를 기반으로 구축되었으므로 이 기능을 자체 애플리케이션, CI/CD 파이프라인 또는 자동화 스크립트에 직접 통합할 수 있습니다.</p><p>작업하게 될 세 가지 핵심 엔드포인트는 다음과 같습니다:</p><ul><li><p><strong><code>/api/agent_builder/tools</code></strong>: 상담원이 사용할 수 있는 재사용 가능한 스킬을 만들고, 나열하고, 관리하기 위한 엔드포인트입니다.</p></li><li><p><strong><code>/api/agent_builder/agents</code></strong>: 중요한 지침 및 도구 할당을 포함하여 상담원 페르소나를 정의하기 위한 엔드포인트입니다.</p></li><li><p><strong><code>/api/agent_builder/converse</code></strong>: 상담원과 상호작용하고, 대화를 시작하고, 답변을 얻기 위한 엔드포인트입니다.</p></li></ul><p>이 튜토리얼의 모든 단계를 수행하기 위해 이러한 API를 사용하는 방법에 대한 완전한 실습 과정을 보려면 여기 GitHub <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb"></a> 리포지토리에서 함께 <strong>제공되는 Jupyter Notebook을 확인하세요.</strong></p><h2>결론: 빌드할 차례</h2><p>먼저 ES|QL 쿼리를 가져와 재사용 가능한 스킬로 변환하는 것으로 시작했습니다. 그런 다음 전문화된 AI 에이전트를 구축하여 명확한 미션과 규칙을 부여하고 해당 기술을 강화했습니다. 그 결과 복잡한 질문을 이해하고 다단계 분석을 실행하여 정확한 데이터 기반 답변을 제공할 수 있는 정교한 어시스턴트가 탄생했습니다.</p><p>이 워크플로는 Elastic의 새로운 <strong>에이전트 빌더의</strong> 핵심입니다. 기술 전문가가 아닌 사용자도 UI를 통해 에이전트를 만들 수 있을 만큼 간단하면서도 개발자가 API를 기반으로 맞춤형 AI 기반 애플리케이션을 구축할 수 있을 만큼 미묘한 차이가 있도록 설계되었습니다. 가장 중요한 것은 사용자가 정의한 전문 로직에 따라 LLM을 자신의 데이터에 안전하게 연결하고 데이터와 채팅할 수 있다는 점입니다.</p><h2>에이전트를 사용하여 데이터와 채팅할 준비가 되셨나요?</h2><p>배운 내용을 확고히 하는 가장 좋은 방법은 직접 손을 더럽히는 것입니다. <a href="https://www.elastic.co/training/elastic-ai-agents-mcp"><strong>무료 대화형 실습 워크숍에서</strong></a> 오늘 논의한 모든 내용을 직접 체험해 보세요. 전용 샌드박스 환경에서 이 전체 흐름과 그 이상을 체험할 수 있습니다.</p><p>향후 블로그에서는 <code>Financial Assistant</code> 에이전트와 상호 작용하는 독립형 애플리케이션을 사용하는 방법과 이 모든 것을 가능하게 하는 <strong>모델 컨텍스트 프로토콜(MCP)</strong> 에 대해 자세히 살펴보겠습니다. 그리고 별도의 블로그에서 에이전트 빌더의 에이전트2에이전트 또는 A2A 프로토콜 개발에 대한 지원에 대해 설명할 예정입니다.</p><p>앞으로도 계속 지켜봐 주시고, 행복한 구축이 되시길 바랍니다!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe5e78eeb775d715/6a17f2230b0bed719ddd369a/ca853555eaa213f10f1db8c0ab0a2bbacee97b88-1456x816.png" length="0" type="image/png"/>
    <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch로 AI 에이전트 워크플로우 구축하기]]></title>
    <description><![CDATA[하이브리드 검색을 사용해 에이전트가 추론하고 행동하는 데 필요한 컨텍스트를 제공하는 AI 에이전트 워크플로우를 구축하기 위한 프레임워크를 제공하는 Elasticsearch의 새로운 AI 계층인 에이전트 빌더에 대해 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic에서는 AI 어시스턴트, 고급 RAG, 벡터 데이터베이스 개선을 통해 LLM과 대화형 인터페이스에 컨텍스트를 제공해 왔습니다. 최근 AI 에이전트의 등장으로 관련 컨텍스트에 대한 필요성이 커지고 있으며, 영향력이 큰<strong> AI 에이전트에는 뛰어난 검색 기능이 필요하다는</strong> 사실을 알게 되었습니다. 그래서 Elasticsearch에서 데이터를 활용하는 AI 에이전트를 개발하는 데 도움이 되도록 설계된 새로운 기본 기능을 Elastic Stack에 구축했습니다. 이 여정의 진행 상황과 앞으로의 계획을 공유하고자 합니다.</p><h2>에이전트 빌더: 데이터 기반 AI 에이전트 구축을 위한 토대</h2><p>AI 에이전트의 약속은 간단합니다. 목표를 부여하면 작업을 완료한다는 것입니다. 하지만 개발자에게 현실은 복잡한 도전의 연속입니다. 첫째, 상담원은 환경에 대한 인식과 사용자 목표를 달성하기 위해 주어진 도구에 대한 인식이 뛰어나야 합니다. 그렇다면 다양한 기업 데이터에서 올바른 컨텍스트를 제공하는 것은 엄청난 과제입니다. 마지막으로, 이 모든 것은 계획, 실행, 학습할 수 있는 신뢰할 수 있는 추론 루프를 통해 조율되어야 합니다.</p><p>이를 해결하기 위해 개발자는 복잡하고 깨지기 쉬운 스택을 처음부터 새로 구축해야 합니다. 오늘날의 에이전트 아키텍처는 LLM, 벡터 데이터베이스, 메타데이터 저장소, 로깅 및 추적을 위한 별도의 시스템, 그리고 이 모든 것이 제대로 작동하는지 평가하는 방법 등 여러 가지 이질적인 조각들을 하나로 연결해야 합니다. 이는 복잡할 뿐만 아니라 비용이 많이 들고 오류가 발생하기 쉬우며 사용자가 요구하는 고품질의 신뢰할 수 있는 AI 시스템을 구축하기 어렵게 만듭니다.</p><p>그래서 저희는 더 간단하게 만들고자 합니다. 이를 위해, 저희의 접근 방식은 효과적인 컨텍스트 기반 에이전트의 필수 요소를 가져와 <strong>Elastic AI 에이전트 빌더라는</strong> 새로운 기능 세트를 통해 Elasticsearch의 핵심에 직접 통합하는 것입니다. 이 새로운 계층은 개방형 기본 요소 세트, 표준 기반 프로토콜, 데이터에 대한 안전한 액세스 등 Elasticsearch 기반 AI 에이전트를 생성하기 위한 모든 필수 구성 요소를 갖춘 프레임워크를 제공하므로 실제 데이터와 요구 사항에 맞는 에이전트 시스템을 구축할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2779dae5df010328/6a17e15eabe0f24f18dfe931/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" alt="" /><p><strong>AI 경험 제공</strong>: 이것이 궁극적인 목표입니다. 검색 AI 플랫폼과 데이터를 기반으로 사용자 지정 채팅 인터페이스부터 LangChain과 같은 에이전트 프레임워크 또는 Salesforce와 같은 비즈니스 애플리케이션과의 통합에 이르기까지 모든 유형의 생성형 AI 애플리케이션을 구축할 수 있습니다.</p><p><strong>에이전트 제공 &amp; 도구</strong>: 플랫폼 위에 깔끔하고 단순한 추상화 계층을 노출합니다. 특정 요구 사항에 맞게 사용자 지정할 수 있는 상담원 및 도구와 직접 상호 작용합니다. 또한 강력한 API와 MCP 및 A2A와 같은 개방형 표준을 통해 플랫폼의 기능에 액세스할 수도 있습니다.</p><p><strong>검색 AI 플랫폼에서 사용 가능</strong>: 이 플랫폼은 구성 요소를 통합한 핵심 엔진입니다. 고급 벡터 데이터베이스, 에이전트 로직, 쿼리 구성, 보안 기능, 평가를 위한 추적 등 모든 것이 여기에 있으며, Elastic에서 관리하고 최적화합니다.</p><p><strong>데이터의 힘 활용하기</strong>: 훌륭한 상담원의 기본은 훌륭한 데이터입니다. Atlassian 플랫폼은 모든 엔터프라이즈 데이터에 대한 수집 또는 연합 액세스 기능으로 시작됩니다.</p><h2>플랫폼 내 에이전트 구축</h2><p>검색 AI 플랫폼에 통합된 에이전트 빌더는 에이전트 개발을 위한 완벽한 프레임워크를 제공합니다. 프로덕션급 AI 시스템 구축 및 배포의 중요한 측면을 해결하도록 설계된 5가지 핵심 요소를 기반으로 구축되었습니다. 에이전트가 목표를 정의하고, 도구가 기능을 제공하며, 개방형 표준이 상호 운용성을 보장하고, 평가가 투명성을 제공하고, 보안이 신뢰를 제공하는 방식을 세분화해 보겠습니다.</p><h3>상담원</h3><p>에이전트는 이 새로운 Elasticsearch 계층에서 가장 높은 수준의 빌딩 블록입니다. 에이전트는 달성할 목표, 실행에 사용할 수 있는 도구 세트 및 작동할 수 있는 데이터 소스를 정의합니다. 상담원은 대화형 상호작용에만 국한되지 않고 전체 워크플로, 작업 자동화 또는 사용자 대면 경험을 강화할 수 있습니다.</p><p>쿼리가 상담원에게 전달되면 구조화된 주기를 따릅니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt774ffd7df65bd01d/6a17e15f25daabd5cc08a17f/627ad1744b629bbe27359325702f40d97e40d1f4-704x852.png" alt="" /><ol><li><p>사용자의 입력과 목표 해석</p></li><li><p>실행에 적합한 도구와 인수를 선택합니다.</p></li><li><p>도구의 응답에 대한 이유</p></li><li><p>결과를 반환할지 아니면 추가 도구 호출을 계속할지 결정하세요.</p></li></ol><p>Elastic은 이 주기의 오케스트레이션, 컨텍스트 및 실행을 처리합니다. 개발자는 목표, 도구, 데이터 등 에이전트가 수행해야 할 <em>작업을</em> 정의하는 데 집중하고, 시스템은 추론과 워크플로우가 수행되는 <em>방식을</em> 관리합니다.</p><p><em>기본 에이전트</em></p><p>이 플랫폼을 기반으로 구축된 첫 번째 에이전트는 Kibana의 기본 대화형 에이전트로, 데이터와 즉시 상호 작용할 수 있는 기능을 제공합니다. 바로 사용할 수 있는 환경을 제공하는 동시에 완벽하게 확장 가능하며, 추가 구성 없이도 데이터와 즉시 상호 작용할 수 있습니다.</p><p>새로운 채팅 사용자 환경을 통해 또는 API를 통해 Kibana에서 직접 이 환경과 상호 작용할 수 있습니다.</p><p>API를 통해 기본 상담원을 쿼리하려면 한 번만 호출하면 됩니다:</p>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}<p>대화가 상태 저장되므로 conversation_id 를 사용하여 상담원과 계속 대화하거나 전체 대화 기록을 검색할 수 있습니다:</p>POST kbn://api/agent_builder/converse
{
    "input": "What about the second top?",
    "conversation_id": "ec757c6c-c3ed-4a83-8e2c-756238f008bb"
}

## get the full conversation
GET kbn://api/agent_builder/conversations/ec757c6c-c3ed-4a83-8e2c-756238f008bb<p><em>맞춤형 상담원</em></p><p>개발자는 간단한 API를 통해 자신만의 사용자 지정 에이전트를 만들 수도 있습니다. 에이전트는 지침, 도구 및 데이터 액세스를 캡슐화하여 맞춤형 추론 엔진을 생성합니다.</p><p>사용자 지정 상담원을 만드는 것은 API 호출 한 번으로 간단합니다. 아래 샘플에서는 '구성' 필드에 지침이나 사용 가능한 도구 등 모든 주요 세부 정보가 들어 있는 예시를 보여 줍니다:</p>POST kbn://api/agent_builder/agents
{
  "id": "custom_agent",
  "name": "My Custom Agent",
  "description": "Description of the custom agent",
  "configuration": {
      "instructions": "You are a log expert specialising in ...",
      "tools": 
...
   }
}<p>에이전트가 생성되면 바로 쿼리할 수 있습니다:</p>POST kbn://api/agent_builder/converse
{
    "input": "What news about DIA?",
    "agent_id": "custom_agent"
}<p>이 접근 방식은 에이전트를 처음부터 구축해야 하는 복잡한 시스템에서 단순하고 선언적인 비즈니스 로직 단위로 전환하여 지능형 자동화를 더 빠르게 제공할 수 있도록 합니다.</p><p>전문 에이전트를 처음부터 구축하는 방법에 대해 자세히 알아보려면 자세한 단계별 가이드를 참조하세요: <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">첫 번째 Elastic 에이전트: 단일 쿼리에서 AI 기반 채팅까지</a>.</p><h3>도구</h3><p>에이전트가 <em>무엇을</em> 달성할지 정의한다면 도구는 <em>어떻게</em> 달성할지 정의합니다.</p><p>도구는 에이전트가 정보를 실행 및 검색하거나 작업을 수행할 수 있도록 특정 Elastic 핵심 기능을 노출합니다. 도구에는 인덱스 가져오기 또는 매핑 가져오기와 같은 핵심 기능이나 자연어에서 ES|QL로의 고급 기능과 같은 고급 기능이 포함될 수 있습니다.</p><p>Elasticsearch는 일반적인 요구 사항에 최적화된 기본 도구 세트와 함께 제공됩니다. 하지만 진정한 유연성은 나만의 유연성을 만드는 데서 비롯됩니다. 도구를 정의함으로써 어떤 쿼리, 인덱스 및 필드를 ES|QL을 통해 에이전트에 노출할지 정확히 결정하여 속도, 정확성 및 보안을 정밀하게 제어할 수 있습니다.</p><p>새 도구를 등록하는 것도 API 호출 한 번으로 간단하게 할 수 있습니다. 특정 금융 자산에 대한 뉴스를 찾기 위해 <a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">Elasticsearch 쿼리 언어(ES|QL)</a> 를 활용하는 도구를 만들 수 있습니다:</p>POST kbn://api/agent_builder/tools
{
  "id": "news_on_asset",
  "type": "esql",
  "description": "Find news and reports about a particular asset where ...",
  "configuration": {
    "query": "FROM financial_news, financial_reports | where MATCH(company_symbol, ?symbol) OR MATCH(entities, ?symbol) | limit 5",
    "params": {
      "symbol": {
        "type": "keyword",
        "description": "The asset symbol"
      }
    }
  ...
  }
...
}<p>등록한 후에는 새 도구를 사용자 지정 상담원에게 할당하여 선별된 기능을 추론하고 필요할 때마다 호출할 수 있도록 할 수 있습니다.</p><p>저희는 고객의 고유한 데이터 및 비즈니스 도메인에 기반하여 에이전트를 범용 에이전트에서 도메인별 전문가로 전환하는 ES|QL과 같이 고객의 특정 요구에 맞는 맞춤형 도구를 만들 수 있는 플랫폼을 제공합니다.</p><h3>개방형 표준 및 상호 운용성</h3><p>Elasticsearch 에이전트와 도구는 개방형 표준 API를 통해 노출되므로 에이전트 프레임워크의 광범위한 에코시스템 내에서 기본 블록으로 쉽게 통합할 수 있습니다. 우리의 접근 방식은 간단합니다: 블랙박스를 사용하지 않습니다. Elastic의 핵심 강점인 검색을 보완적인 기능 및 기타 에이전트 시스템과 결합하여 사용할 수 있기를 바랍니다.</p><p>이를 가능하게 하기 위해 저희는 API, 새로운 프로토콜, 개방형 표준을 통해 역량을 노출하고 있습니다.</p><p><em>모델 컨텍스트 프로토콜(MCP)</em></p><p><a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">MCP(모델 컨텍스트 프로토콜)</a> 는 시스템 간 도구 연결을 위한 개방형 표준으로 빠르게 자리 잡고 있습니다. MCP를 지원함으로써 Elasticsearch는 대화형 AI를 데이터베이스, 인덱스 및 외부 API에 연결할 수 있습니다. Elastic Stack에 내장된 원격 MCP 서버를 통해 모든 MCP 호환 클라이언트는 Elastic의 도구에 액세스하고 이를 대규모 에이전트 워크플로우의 빌딩 블록으로 사용할 수 있습니다.</p><p>이것은 일방통행이 아닙니다. 또한 외부 MCP 서버에서 도구를 가져와서 Elasticsearch 내에서 사용할 수 있게 할 수도 있습니다. 곧 MCP 서버는 거의 모든 용도로 사용할 수 있게 될 것이며, 우리가 직접 만드는 것보다 훨씬 더 포괄적인 서버가 될 것입니다. Elastic은 대규모 검색 및 검색 기능을 제공하며, 이를 다른 플랫폼의 전문 기능과 결합하여 효과적인 에이전트를 구축할 수 있습니다.</p><p><em>에이전트 간(A2A)</em></p><p>또한 에이전트 간(A2A) 지원도 준비 중입니다. MCP가 툴을 연결하는 것이라면 A2A는 에이전트를 연결하는 것이 핵심입니다. A2A 서버를 사용하면 구축한 Elastic 에이전트가 다른 시스템의 에이전트와 직접 대화하여 컨텍스트를 공유하고, 작업을 위임하고, 워크플로우를 조정할 수 있습니다.</p><p>추론 계층에서의 상호 운용성이라고 생각하면 됩니다. Elastic 에이전트가 검색 및 검색을 처리한 다음 전문 지원팀이나 IT 에이전트에게 작업을 넘겨주고 결과를 원활하게 돌려받을 수 있습니다. 그 결과 각자가 가장 잘하는 일을 하는 협력 에이전트로 구성된 생태계가 탄생했습니다.</p><p>궁극적으로 MCP와 A2A를 채택함으로써, 더 광범위한 에이전트 에코시스템 전반에 걸쳐 개방형 통합을 보장하는 일류 시민으로서 Elasticsearch의 역할에 대한 우리의 약속을 강화할 수 있게 되었습니다.</p><h3>추적 및 평가</h3><p>검색이 상담원과 통합됨에 따라 효과적인 평가라는 과제가 중요해졌습니다. 실제 기업 환경에서 자신 있게 에이전트를 배포하려면 정확할 뿐만 아니라 효율적이고 신뢰할 수 있다는 확신이 있어야 합니다. 성능을 측정하고, 잘못된 응답을 진단하거나, 기준선을 개선하려면 어떻게 해야 하나요? 모든 것은 가시성에서 시작됩니다.</p><p>이것이 바로 처음부터 투명성을 위해 상담원 API를 설계한 이유입니다. 이 간단한 상담원 상호 작용을 생각해 보세요:</p>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}<p>응답에는 최종 답변뿐만 아니라 상담원이 선택한 도구, 사용한 매개변수 및 각 단계의 결과를 자세히 설명하는 전체 실행 추적이 포함됩니다.</p>{
  "conversation_id": "db5c0c8b-12bf-4928-a57e-d99129ad2fea",
  "steps": [
    {
      "type": "tool_call",
      "tool_call_id": "tooluse_Nfqr3mwtR92HTRIsTcGXZQ",
      "tool_id": ".index_explorer",
      "params": {
        "query": "indices containing portfolio data"
      },
      "results": [...]
    }
    // ... more steps ...
  ],
  "response": {
    "message": "Based on the information I've gathered...."
  }
}<p>포괄적인 추적과 로깅은 지속적인 개선 루프에 필수적이며, 곧 이러한 에이전트 추적을 Elasticsearch에 직접 저장하고 볼 수 있게 됩니다. 더 좋은 점은 이러한 추적이 OpenTelemetry 프로토콜을 기반으로 구축되어 표준화되고 원하는 통합 가시성 플랫폼과 통합할 수 있도록 이식성이 보장된다는 것입니다.</p><p>이러한 수준의 세부 사항은 진정한 지속적인 개선 루프의 토대입니다. 포괄적인 테스트 제품군을 구축하고, 실패를 디버그하고, 실패 모드를 식별하여 회귀를 방지하고, 성공 패턴을 캡처하여 성능을 미세 조정할 수 있습니다. 궁극적으로 이러한 데이터 중심 접근 방식은 유망한 프로토타입을 생산 등급의 신뢰할 수 있는 AI 시스템으로 전환하는 데 핵심적인 역할을 합니다.</p><h3>보안</h3><p>에이전트와 툴의 기능이 향상됨에 따라 보안은 선택 사항이 아니라 기본이 되었습니다. API를 노출하고, 작업을 자동화하고, 워크플로우를 자동화하려면 엔터프라이즈 시스템을 신뢰할 수 있어야 합니다. 특히 상담원이 더 많은 워크플로를 자동화하기 시작하면서 이러한 워크플로를 보호하고 기업의 요구 사항을 충족할 수 있는 기능이 필수적입니다.</p><p>무엇보다도 이 기능은 API 호출을 위한 <a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">역할 기반 액세스 제어(RBAC)</a> 와 API 키 관리를 포함해 현재 Elastic에서 이미 사용 가능한 제어 기능을 그대로 계승합니다. 또한 MCP와 같은 새로운 프로토콜에도 동일한 제어 기능을 확장하고 있습니다. 즉, OAuth와 같은 표준을 지원할 뿐만 아니라 사용자 지정 인증 메커니즘을 연결할 수 있습니다.</p><p>저희의 목표는 조직이 요구하는 보안, 규정 준수 및 거버넌스 수준을 유지하면서 에이전트와 도구를 유연하게 실험할 수 있도록 하는 것입니다.</p><h2>다음 단계</h2><p>단순히 기능만 추가하는 것이 아니라 에이전트 컨텍스트 엔지니어링을 위해 Elasticsearch를 확장하고 있습니다. 앞으로도 이러한 원칙에 따라 발전해 나갈 계획입니다:</p><p>1. 오픈 소스 &amp; 표준에 대한 약속</p><p>오픈 소스 및 개방형 표준에 대한 당사의 노력은 이러한 기능이 외부 에이전트 프레임워크와 상호 운용성을 유지하도록 보장합니다. 데이터와 워크플로우를 항상 제어하면서 에코시스템 전반에서 에이전트를 연결, 확장 및 구성할 수 있습니다.</p><p>2. 컨텍스트의 가치</p><p>AI 에이전트의 가장 큰 자산은 컨텍스트입니다. 상담원이 검색 및 워크플로 작업을 수행할 때 컨텍스트를 관리하는 것은 어려운 작업일 수 있습니다. 저희는 Elastic의 핵심 강점을 활용하여 컨텍스트 엔지니어링을 해결함으로써 상담원이 항상 가장 관련성 높은 정보를 사용할 수 있도록 보장하고 있습니다.</p><p>3. 에이전트 데이터 스트림에 집중</p><p>앞으로 상담원은 상담원의 출력물(생성된 문서, 보고서, 시각화)과 상담원의 실행 추적(사고, 도구 호출, 메모리/컨텍스트)을 포함하여 점점 더 큰 데이터 소스가 될 것입니다. Elastic은 이러한 유형의 데이터를 처리하는 데 매우 적합하며, 이러한 데이터를 사용하여 분석, 평가 및 자동화된 개선 작업을 수행하는 것과 관련된 연구를 진행하고 있습니다.</p><p>4. 설계를 통한 보안 및 안전</p><p>AI 에이전트는 완전히 새로운 보안 및 안전 문제를 야기합니다. Elastic은 항상 보안 솔루션의 리더로서 엔터프라이즈급 가드레일, 액세스 제어, "제로 트러스트" 원칙을 지속적으로 구축해 왔습니다.</p><p>5. 플랫폼에 내장</p><p>AI 에이전트를 구축하기 위한 기능은 Elasticsearch 플랫폼에 내장되어 있습니다. 즉, 추적, 평가, 시각화 및 분석과 같은 플랫폼 수준의 기능을 모두 상담원에게 적용할 수 있습니다. 에이전트 실행을 기반으로 대시보드를 개발하려는 경우 - 이 기능이 기본으로 제공됩니다. 감정 분석을 사용하여 AI 상담원의 성과를 평가하고 싶다면 이 플랫폼을 통해 가능합니다. 이를 통해 AI 경험을 중심으로 완전한 라이프사이클을 구축할 수 있습니다.</p><p>Elastic의 목표는 완전히 통합되고 확장 가능하며 데이터에 기반한 대화형 AI와 자동화된 워크플로우를 구축할 수 있는 인터페이스를 제공하는 것입니다. 자세한 기술적 세부 사항과 진행 상황은 곧 공유될 예정입니다.</p><p>상담원 빌더는 현재 비공개 미리 보기로 제공됩니다. 액세스 권한을 요청하려면 <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">당사에 문의</a> 하세요. 질문이나 피드백이 있으신가요? <a href="https://elasticstack.slack.com/archives/C09GRHEQ4AG"><strong>Slack 워크스페이스</strong></a> 또는 <a href="https://discuss.elastic.co/c/search/84"><strong>토론 포럼에서</strong></a> 개발자 커뮤니티와 소통하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <dc:creator><![CDATA[Anish Mathur,Dana Juratoni]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16a3d8736bf086e0/6a17e1616864a45410b686c7/71876470119e02a45bcbfcbf27a3e110328bbd14-1020x654.png" length="0" type="image/png"/>
    <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[JavaScript, Mastra, Elasticsearch로 에이전트 RAG 어시스턴트 구축하기]]></title>
    <description><![CDATA[JavaScript 에코시스템에서 AI 에이전트를 구축하는 방법 알아보기]]></description>
    <content:encoded><![CDATA[<p>이 아이디어는 열띤 경쟁이 벌어지는 판타지 농구 리그에 참여하던 중 떠올랐습니다. 궁금했습니다: <em>주간 매치업에서 우위를 점하는 데 도움이 되는 AI 에이전트를 만들 수 있을까요? 물론 가능합니다!</em></p><p>이 글에서는 <a href="https://mastra.ai/en/docs">Mastra와</a> 경량 JavaScript 웹 애플리케이션을 사용하여 에이전트 RAG 어시스턴트를 구축하여 상호 작용하는 방법을 살펴봅니다. 이 에이전트를 Elasticsearch에 연결하면 구조화된 플레이어 데이터에 액세스하고 실시간 통계 집계를 실행할 수 있는 기능을 제공하여 플레이어 통계에 기반한 추천을 제공할 수 있습니다. GitHub <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">리포지토리로</a> 이동하여 애플리케이션을 직접 복제하고 실행하는 방법에 대한 지침을 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/README.md">읽어보세요</a>. </p><p>모든 것이 합쳐지면 어떤 모습일까요?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63ea3e7a09306fbf/6a17f1d97f6f150e22c09c50/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" alt="" /><p>참고: 이 블로그 게시물은 "<a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">AI SDK와 Elastic으로 AI 에이전트 구축하기</a>"를 기반으로 합니다. AI 에이전트를 처음 접하고 어떤 용도로 사용할 수 있는지 궁금하다면 여기서부터 시작하세요.
</p><h2><strong>아키텍처 개요</strong></h2><p>시스템의 핵심에는 에이전트의 추론 엔진(두뇌) 역할을 하는 대규모 언어 모델(LLM)이 있습니다. 사용자 입력을 해석하고, 호출할 도구를 결정하며, 관련 응답을 생성하는 데 필요한 단계를 오케스트레이션합니다.</p><p>에이전트 자체는 자바스크립트 에코시스템의 에이전트 프레임워크인 Mastra에 의해 스캐폴드됩니다. Mastra는 LLM을 백엔드 인프라로 래핑하여 API 엔드포인트로 노출하고 도구, 시스템 프롬프트 및 에이전트 동작을 정의하기 위한 인터페이스를 제공합니다.</p><p>프론트엔드에서는 <a href="https://vite.dev/guide/">Vite를</a> 사용하여 에이전트에게 쿼리를 보내고 응답을 받기 위한 채팅 인터페이스를 제공하는 React 웹 애플리케이션을 빠르게 스캐폴딩합니다.</p><p>마지막으로 에이전트가 쿼리하고 집계할 수 있는 플레이어 통계 및 매치업 데이터를 저장하는 Elasticsearch가 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte13f09493f217047/6a17f1db1d1b83178d93e546/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" alt="" /><h2><strong>배경</strong></h2><p>몇 가지 기본 개념을 살펴보겠습니다:</p><h3><strong>에이전트 RAG란 무엇인가요?</strong></h3><p>AI 에이전트는 다른 시스템과 상호 작용하고, 독립적으로 작동하며, 정의된 매개변수에 따라 작업을 수행할 수 있습니다. 에이전틱 RAG는 AI 에이전트의 자율성과 검색 증강 생성의 원칙을 결합하여 LLM이 응답을 생성하기 위해 호출할 도구와 컨텍스트로 사용할 데이터를 선택할 수 있도록 합니다. <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">여기에서</a> RAG에 대해 자세히 알아보세요.</p><h3><strong>프레임워크 선택, 왜 AI-SDK를 넘어야 할까요?</strong></h3><p>사용 가능한 AI 에이전트 프레임워크는 여러 가지가 있으며, <a href="https://www.elastic.co/search-labs/blog/using-crewai-with-elasticsearch">CrewAI</a>, <a href="https://www.elastic.co/search-labs/blog/using-autogen-with-elasticsearch">AutoGen</a>, <a href="https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch">LangGraph와</a> 같은 인기 있는 프레임워크에 대해 들어보셨을 것입니다. 이러한 프레임워크는 대부분 다양한 모델 지원, 도구 사용, 메모리 관리 등 공통된 기능 세트를 공유합니다.</p><p>다음은 해리슨 체이스(LangChain의 CEO)가 작성한 프레임워크 <a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">비교표입니다</a>.</p><p>Mastra에 관심을 갖게 된 이유는 풀스택 개발자가 에이전트를 에코시스템에 쉽게 통합할 수 있도록 구축된 자바스크립트 우선 프레임워크라는 점입니다. Vercel의 AI-SDK도 이 작업의 대부분을 수행하지만, 프로젝트에 더 복잡한 에이전트 워크플로가 포함될 때 Mastra가 빛을 발합니다. Mastra는 AI-SDK에서 설정한 기본 패턴을 개선하며, 이 프로젝트에서는 이 두 가지를 함께 사용할 예정입니다.</p><h3><strong>프레임워크 및 모델 선택 고려 사항</strong></h3><p>이러한 프레임워크는 AI 에이전트를 빠르게 구축하는 데 도움이 될 수 있지만 고려해야 할 몇 가지 단점이 있습니다. 예를 들어, AI 에이전트나 일반적인 추상화 계층이 아닌 다른 프레임워크를 사용하면 제어권을 약간 잃게 됩니다. LLM이 도구를 올바르게 사용하지 않거나 원치 않는 작업을 수행하는 경우 추상화로 인해 디버깅이 더 어려워집니다. 하지만 제 생각에는 이러한 프레임워크가 모멘텀을 얻고 있고 지속적으로 반복되고 있기 때문에 구축할 때 얻을 수 있는 쉽고 빠른 속도를 고려하면 이러한 절충안은 가치가 있습니다.</p><p>다시 말하지만, 이러한 프레임워크는 모델에 구애받지 않으므로 다양한 모델을 플러그 앤 플레이할 수 있으며, 학습된 데이터 세트에 따라 모델이 달라지고 그에 따라 응답도 달라진다는 점을 기억하세요. 일부 모델은 도구 호출도 지원하지 않습니다. 따라서 여러 모델을 전환하고 테스트하여 어떤 모델이 가장 좋은 응답을 제공하는지 확인할 수 있지만 각 모델에 대해 시스템 프롬프트를 다시 작성해야 할 가능성이 높다는 점에 유의하세요. 예를 들어, Llama3.3 사용 보다 훨씬 더 많은 프롬프트와 구체적인 지침을 통해 원하는 응답을 얻을 수 있습니다.</p><h3><strong>NBA 판타지 농구</strong></h3><p>판타지 농구는 친구 그룹과 함께 리그를 시작하는 것으로, 보통 약간의 돈을 걸고 진행합니다(그룹의 경쟁 정도에 따라 우정의 상태에 영향을 미칠 수 있습니다). 그런 다음 각자 10명의 플레이어로 팀을 구성하여 매주 번갈아 가며 다른 친구의 10명의 플레이어와 경쟁합니다. 전체 점수에 기여하는 포인트는 주어진 주에 각 플레이어가 상대 플레이어를 상대로 어떤 성적을 거두었는지에 따라 결정됩니다.</p><p>팀에 소속된 선수가 부상, 출장 정지 등을 당한 경우 팀에 추가할 수 있는 자유 계약 선수 목록이 있습니다. 판타지 스포츠에서는 픽업 횟수가 제한되어 있고 모두가 최고의 선수를 뽑기 위해 끊임없이 노력하기 때문에 이 부분에서 많은 고민이 일어납니다.</p><p>특히 어떤 선수를 뽑을지 빠르게 결정해야 하는 상황에서 NBA AI 어시스턴트가 빛을 발하는 부분입니다. 어시스턴트는 플레이어가 특정 상대와의 경기력을 수동으로 조회할 필요 없이 해당 데이터를 빠르게 찾아 평균을 비교하여 정보에 입각한 추천을 제공합니다.</p><p>이제 에이전트 RAG와 NBA 판타지 농구에 대한 기본 사항을 알아봤으니 실제로 확인해 보겠습니다.</p><h2><strong>프로젝트 구축</strong></h2><p>어느 지점에서 막히거나 처음부터 빌드하고 싶지 않다면 <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">리포지토리를</a> 참조하세요.</p><h3><strong>다룰 내용</strong></h3><ol><li><p><strong>프로젝트 발판 마련하기:</strong></p><ol><li><p><strong>백엔드(마스트라):</strong> 백엔드를 스캐폴드하고 에이전트 로직을 정의하려면 npx create mastra@latest를 사용하세요.</p></li><li><p><strong>프런트엔드(Vite + React):</strong> 상담원과 상호작용할 수 있는 프론트엔드 채팅 인터페이스를 구축하려면 npm create vite@latest를 사용하세요.</p></li></ol></li><li><p><strong>환경 변수 설정</strong></p><ol><li><p>환경 변수를 관리하려면 dotenv를 설치하세요.</p></li><li><p>.env 파일 만들기 파일을 열고 필요한 변수를 입력합니다.</p></li></ol></li><li><p><strong>Elasticsearch 설정</strong></p><ol><li><p>(로컬 또는 클라우드에서) Elasticsearch 클러스터를 스핀업합니다.</p></li><li><p>공식 Elasticsearch 클라이언트를 설치합니다.</p></li><li><p>환경 변수에 액세스할 수 있는지 확인합니다.</p></li><li><p>클라이언트에 연결을 설정합니다.</p></li></ol></li><li><p><strong>NBA 데이터를 Elasticsearch로 대량 수집하기</strong></p><ol><li><p>적절한 매핑으로 인덱스를 생성하여 집계를 활성화합니다.</p></li><li><p>CSV 파일에서 플레이어 게임 통계를 Elasticsearch 인덱스로 일괄 수집합니다.</p></li></ol></li><li><p><strong>Elasticsearch 집계 정의</strong></p><ol><li><p>특정 상대에 대한 과거 평균을 계산하기 위한 쿼리입니다.</p></li><li><p>특정 상대에 대한 시즌 평균을 계산하기 위한 쿼리입니다.</p></li></ol></li><li><p><strong>플레이어 비교 유틸리티 파일</strong></p><ol><li><p>도우미 기능과 Elasticsearch 집계를 통합합니다.</p></li></ol></li><li><p><strong>에이전트 구축하기</strong></p><ol><li><p>상담원 정의 및 시스템 프롬프트를 추가합니다.</p></li><li><p>zod를 설치하고 도구를 정의합니다.</p></li><li><p>CORS를 처리하기 위한 미들웨어 설정을 추가합니다.</p></li></ol></li><li><p><strong>프론트엔드 통합</strong></p><ol><li><p>AI-SDK의 useChat을 사용하여 상담원과 상호 작용합니다.</p></li><li><p>적절한 형식의 대화를 담을 수 있는 UI를 만듭니다.</p></li></ol></li><li><p><strong>애플리케이션 실행</strong></p><ol><li><p>백엔드(Mastra 서버)와 프론트엔드(React 앱)를 모두 시작하세요.</p></li><li><p>샘플 쿼리 및 사용법.</p></li></ol></li><li><p><strong>다음 단계 에이전트의 지능 향상</strong></p><ol><li><p>시맨틱 검색 기능을 추가하여 더욱 통찰력 있는 추천을 지원합니다.</p></li><li><p>검색 로직을 Elasticsearch MCP(모델 컨텍스트 프로토콜) 서버로 이동하여 동적 쿼리를 활성화합니다.</p></li></ol></li></ol><h3><strong>필수 구성 요소</strong></h3><ul><li><p><strong>Node.js 및 npm</strong>: 백엔드와 프론트엔드 모두 Node에서 실행됩니다. Node 18+ 및 npm v9+가 설치되어 있는지 확인합니다(Node 18+에 번들로 제공됨).</p></li><li><p><strong>Elasticsearch 클러스터:</strong> 로컬 또는 클라우드에 있는 활성 Elasticsearch 클러스터입니다.</p></li><li><p><strong>OpenAI API 키</strong>: <a href="https://platform.openai.com/api-keys">OpenAI 개발자 포털의</a> API 키 페이지에서 생성하세요.</p></li></ul><p></p><h3><strong>프로젝트 구조</strong></h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt749baa120552e4ab/6a17f1dd1d1b83bfe993e54a/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" alt="" /><h4><strong>1단계: 프로젝트 발판 마련</strong></h4><ol><li><p>먼저 nba-ai-assistant-js 디렉토리를 생성하고 다음을 사용하여 내부로 이동합니다: </p></li></ol>mkdir nba-ai-assistant-js &amp;&amp; cd nba-ai-assistant-js<p><strong>백엔드:</strong></p><ol><li><p>다음 명령과 함께 Mastra 생성 도구를 사용합니다: </p></li></ol>npx create-mastra@latest<p>2. 터미널에 몇 가지 메시지가 표시되는데, 첫 번째 메시지는 프로젝트 백엔드의 이름을 지정하는 것입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65abf68fe588e968/6a17f1de63baff2814741d5b/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" alt="" /><p>3. 다음으로, Mastra 파일을 저장하는 기본 구조를 유지하므로 <code>src/</code> 을 입력합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bd829fcf0ae6b9/6a17f1e04b055dd30e432302/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" alt="" /><p>4. 그런 다음 기본 LLM 공급자로 OpenAI를 선택합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd167cc77a40b9a8/6a17f1e11480099e29b48863/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" alt="" /><p>5. 마지막으로 OpenAI API 키를 입력하라는 메시지가 표시됩니다. 지금은 건너뛰는 옵션을 선택하고 나중에<code> .env</code> 파일로 제공하겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12654151ed495370/6a17f1e22f4a5c0f84fa89f9/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" alt="" /><p><strong>프론트엔드:</strong></p><ol><li><p>다시 루트 디렉토리로 이동하여 다음 명령을 사용하여 <a href="https://vite.dev/guide/">Vite 생성 도구를</a> 실행합니다: <code>npm create vite@latest frontend -- --template react</code></p></li></ol><p>이렇게 하면 React용 특정 템플릿이 포함된 <code>frontend</code> 이라는 이름의 경량 React 앱이 생성됩니다.</p><p>모든 것이 정상적으로 진행되었다면 프로젝트 디렉토리 내부에 Mastra 코드가 있는 백엔드 디렉토리와 React 앱이 있는 <code>frontend</code> 디렉토리가 있을 것입니다.</p><p></p><h4><strong>2단계: 환경 변수 설정</strong></h4><ol><li><p>민감한 키를 관리하기 위해 <code>dotenv</code> 패키지를 사용하여 .env에서 환경 변수를 로드합니다. 파일을 만듭니다. 백엔드 디렉토리로 이동하여 <code>dotenv</code> 을 설치합니다:</p></li></ol>cd backend
npm install dotenv --save<p>2. 백엔드 디렉토리에 적절한 변수를 입력할 수 있는 example.env 파일이 제공됩니다. 직접 생성하는 경우 다음 변수를 반드시 포함하세요:</p># OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here

# Elasticsearch Configuration
ELASTIC_ENDPOINT=your_elasticsearch_endpoint_here
ELASTIC_API_KEY=your_elasticsearch_api_key_here
<p></p><p>참고: <code>.gitignore</code> 에 <code>.env</code> 을 추가하여 이 파일이 버전 관리에서 제외되었는지 확인하세요.</p><h4><strong>3단계: Elasticsearch 설정하기</strong></h4><p>먼저 활성 Elasticsearch 클러스터가 필요합니다. 두 가지 옵션이 있습니다:</p><ul><li><p><strong>옵션 A: Elasticsearch Cloud 사용</strong></p><ul><li><p><a href="https://cloud.elastic.co/registration">Elastic Cloud에</a>가입하기</p></li><li><p>새 배포 만들기</p></li><li><p>엔드포인트 URL 및 API 키(인코딩된) 가져오기</p></li></ul></li><li><p><strong>옵션 B: 로컬에서 Elasticsearch 실행하기</strong></p><ul><li><p>로컬에 Elasticsearch 설치 및 실행</p></li><li><p>엔드포인트로 http://localhost:9200 사용</p></li><li><p>API 키 생성</p></li></ul></li></ul><p></p><p><strong>백엔드에 Elasticsearch 클라이언트 설치하기:</strong></p><ol><li><p>먼저, 백엔드 디렉터리에 공식 Elasticsearch 클라이언트를 설치합니다:</p></li></ol>npm install @elastic/elasticsearch<p>2. 그런 다음 재사용 가능한 함수를 저장할 디렉터리 라이브러리를 만들고 그 안으로 이동합니다:</p>mkdir lib &amp;&amp; cd lib<p>3. 내부에 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticClient.js">elasticClient.js라는</a> 새 파일을 생성합니다. 이 파일은 Elasticsearch 클라이언트를 초기화하고 프로젝트 전체에서 사용할 수 있도록 노출합니다.</p><p>4. ECMAScript 모듈(ESM)을 사용하고 있으므로 __dirname and __파일명을 사용할 수 없습니다. .env 파일에서 환경 변수가 올바르게 로드되었는지 확인하려면 파일에서 이 설정을 파일 맨 위에 추가합니다:</p>import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { Client } from '@elastic/elasticsearch';

// Grab current directory and load .env from backend folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '../.env');

// Load environment variables from the correct path
config({ path: envPath });<p>5. 이제 환경 변수를 사용하여 Elasticsearch 클라이언트를 초기화하고 연결을 확인합니다:</p>//Elastic client Initialization, make sure environment variables are being loaded in correctly
const config= {
    node: `${process.env.ELASTIC_ENDPOINT}`,
    auth: {
        apiKey: `${process.env.ELASTIC_API_KEY}`,
    },
};

export const elasticClient = new Client(config);

//Check if the client is connected
async function checkConnection() { 
    try {
        const info = await elasticClient.info();
        console.log('Elasticsearch is connected:', info);
    } catch (error) {
        console.error('Elasticsearch connection error:', error);
    }
}

checkConnection();
<p>이제 이 클라이언트 인스턴스를 Elasticsearch 클러스터와 상호 작용해야 하는 모든 파일로 가져올 수 있습니다.</p><p></p><h4><strong>4단계: NBA 데이터를 Elasticsearch로 대량 수집하기</strong></h4><p><strong>데이터 세트:</strong></p><p>이 프로젝트에서는 리포지토리의 <a href="https://github.com/jdarmada/nba-ai-assistant-js/tree/main/backend">백엔드/데이터</a> 디렉터리에서 사용 가능한 데이터 세트를 참조하겠습니다. NBA 어시스턴트는 이 데이터를 지식 베이스로 사용하여 통계 비교를 실행하고 추천을 생성합니다.</p><ul><li><p><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/sample_nba_data.csv">sample_player_game_stats.csv</a> - 선수의 전체 NBA 커리어에 대한 경기당 득점, 리바운드, 스틸 등 선수별 게임 통계 샘플입니다. 이 데이터 집합을 사용하여 집계를 수행합니다. (참고: 이 데이터는 데모 목적으로 미리 생성된 모의 데이터이며 공식 NBA 소스에서 가져온 것이 아닙니다.)</p></li><li><p><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - 에이전트가 플레이어 및 팀 이름을 ID와 일치시킬 수 있도록 일반적으로 API 호출을 통해 제공되는 플레이어 및 팀 메타데이터를 대체합니다. 샘플 데이터를 사용하기 때문에 외부 API에서 가져오는 데 따른 오버헤드를 피하기 위해 에이전트가 참조할 수 있는 몇 가지 값을 하드코딩했습니다.</p></li></ul><p></p><p><strong>구현:</strong></p><ol><li><p><code>backend/lib</code> 디렉터리에서 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">플레이어데이터인입.js라는</a> 파일을 생성합니다.</p></li><li><p>가져오기를 설정하고, CSV 파일 경로를 확인하고, 구문 분석을 설정합니다. 다시 말하지만, ESM을 사용하고 있으므로 <code>__dirname</code> 을 재구성하여 샘플 CSV의 경로를 확인해야 합니다. 또한 <a href="http://node.js/">Node.js의</a> 기본 제공 모듈인 <code>fs</code> 및 <code>readline</code> 을 사용하여 주어진 CSV 파일을 한 줄씩 구문 분석합니다.</p></li></ol>import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { fileURLToPath } from 'url';
import { elasticClient } from './elasticClient.js';

const indexName = 'sample-nba-player-data'; //Replace with your preferred index name

//Since we are using ES modules __dirname and __filename don't exist, so this is a workaround that allows us to use the absolute file path for our sample data.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, '../data/sample_nba_data.csv');<p>이렇게 하면 대량 수집 단계에 도달했을 때 CSV를 효율적으로 읽고 구문 분석할 수 있습니다.</p><p>3. 적절한 매핑으로 인덱스를 생성합니다. Elasticsearch는 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic">동적 매핑을</a> 통해 필드 유형을 자동으로 추론할 수 있지만, 여기서는 각 통계가 숫자 필드로 처리되도록 명시적으로 처리하고자 합니다. 이 필드는 나중에 집계에 사용할 것이므로 중요합니다. 또한 득점, 리바운드 등과 같은 통계에는 <code>float </code>유형을 사용하여 소수점 값을 포함하도록 합니다. 마지막으로, 매핑 속성 <code>dynamic: 'strict'</code> 을 추가하여 Elasticsearch가 인식할 수 없는 필드를 동적으로 매핑하지 않도록 하려고 합니다. 
</p>// Function to create an index with mappings
async function createIndex() {
    try {
        // Check if the index already exists
        const exists = await elasticClient.indices.exists({ index: indexName });

        if (exists) {
            console.log(`Index "${indexName}" already exists, deleting it now.`);
            await elasticClient.indices.delete({ index: indexName });
            console.log(`Deleted index "${indexName}".`);
        }
        // Create the index with mappings
        const response = await elasticClient.indices.create({
            index: indexName,
            body: {
                mappings: {
                    dynamic: 'strict', // Prevent dynamic mapping
                    properties: {
                        game_id: { type: 'integer' },
                        game_date: { type: 'date' },
                        player_id: { type: 'integer' },
                        player_full_name: { type: 'text' },
                        player_team_id: { type: 'integer' },
                        player_team_name: { type: 'text' },
                        home_team: { type: 'boolean' },
                        opponent_team_id: { type: 'integer' },
                        opponent_team_name: { type: 'text' },
                        points: { type: 'float' },
                        rebounds: { type: 'float' },
                        assists: { type: 'float' },
                        steals: { type: 'float' },
                        blocks: { type: 'float' },
                        fg_percentage: { type: 'float' },
                        minutes_played: { type: 'float' },
                    },
                },
            },
        });

        console.log('Index created:', response);
        return true;
    } catch (error) {
        console.error('Error creating index:', error);
        return false;
    }
}
<p>4. CSV 데이터를 Elasticsearch 인덱스에 대량 수집하는 기능을 추가합니다. 코드 블록 내부에서는 헤더 줄을 건너뜁니다. 그런 다음 각 줄 항목을 쉼표로 분할하고 문서 개체에 밀어 넣습니다. 이 단계는 또한 이들을 정리하고 올바른 유형인지 확인합니다. 다음으로, 문서를 인덱스 정보와 함께 bulkBody 배열에 밀어넣고, 이 배열은 Elasticsearch로의 대량 수집을 위한 페이로드 역할을 하게 됩니다.</p>async function bulkIngestCsv(filePath) {
    const readStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: readStream,
        crlfDelay: Infinity,
    });

    const bulkBody = [];
    let lineNum = 0;

    //Skip the header line
    let headerLine = true;
    for await (const line of rl) {
        if (headerLine) {
            headerLine = false;
            continue;
        }
        lineNum++;

        // Split the line by comma and remove whitespace
        const [
            game_id,
            game_date,
            player_id,
            player_full_name,
            player_team_id,
            player_team_name,
            home_team,
            opponent_team_id,
            opponent_team_name,
            points,
            rebounds,
            assists,
            steals,
            blocks,
            fg_percentage,
            minutes_played,
        ] = line.split(',');

        // Create a document object
        const document = {
            game_id: parseInt(game_id),
            game_date: game_date.trim(),
            player_id: parseInt(player_id),
            player_full_name: player_full_name.trim(),
            player_team_id: parseInt(player_team_id),
            player_team_name: player_team_name.trim(),
            home_team: home_team.trim() === 'True', // Converts True/False into a boolean
            opponent_team_id: parseInt(opponent_team_id),
            opponent_team_name: opponent_team_name.trim(),
            points: parseFloat(points),
            rebounds: parseFloat(rebounds),
            assists: parseFloat(assists),
            steals: parseFloat(steals),
            blocks: parseFloat(blocks),
            fg_percentage: parseFloat(fg_percentage),
            minutes_played: parseFloat(minutes_played),
        };

        // Prepare the bulk operation format
        bulkBody.push({ index: { _index: indexName } });
        bulkBody.push(document);
    }

    console.log(`Parsed ${lineNum} lines from CSV`);
<p>5. 그런 다음 <code>elasticClient.bulk()</code> 와 함께 Elasticsearch의 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">대량 API를</a> 사용하여 단일 요청으로 여러 문서를 수집할 수 있습니다. 아래의 오류 처리는 수집에 실패한 문서 수와 수집에 성공한 문서 수를 알려주는 구조로 되어 있습니다.</p>try {
        // Perform the bulk request
        const response = await elasticClient.bulk({ body: bulkBody });

        if (response.errors) {
            console.log('Bulk Ingestion had some hiccups:');

            // Count successful vs failed operations
            let successCount = 0;
            let errorCount = 0;
            const errorDetails = [];

            response.items.forEach((item, index) =&gt; {
                const operation = item.index || item.create || item.update || item.delete;
                if (operation.error) {
                    errorCount++;
                    errorDetails.push({
                        document: index + 1,
                        error: operation.error,
                    });
                } else {
                    successCount++;
                }
            });

            console.log(`Successfully indexed: ${successCount} documents`);
            console.log(`Failed to index: ${errorCount} documents, here are the details`, errorDetails);

        } else {
            console.log(`Bulk Ingestion fully successful!`);
        }

    } catch (error) {
        console.error('Error performing bulk ingestion:', error);
    }
}
<p>6. 아래 <code>main()</code> 함수를 실행하여 <code>createIndex()</code> 및 <code>bulkIngestCsv()</code> 함수를 순차적으로 실행합니다.</p>// Run this function
async function main() {
    const result = await createIndex();
    if (!result) {
        console.error('Index setup failed. Aborting.');
        return;
    }

    await bulkIngestCsv(filePath);
    console.log('Bulk ingestion completed!');
}

main();
<p>대량 수집이 성공했다는 콘솔 로그가 표시되면, Elasticsearch 인덱스에서 빠른 확인을 수행하여 문서가 실제로 성공적으로 수집되었는지 확인합니다.</p><h4><strong>5단계: Elasticsearch 집계 정의 및 통합하기</strong></h4><p>이는 플레이어의 통계를 서로 비교하기 위해 AI 에이전트의 도구를 정의할 때 사용되는 주요 기능이 될 것입니다.</p><p>1. <code>backend/lib</code> 디렉터리로 이동하여 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticAggs.js">elasticAggs.js라는</a> 파일을 생성합니다.</p><p>2. 아래 쿼리를 추가하여 특정 상대에 대한 플레이어의 기록 평균을 계산합니다. 이 쿼리는 <code>player_id</code> 과 일치하는 조건과 <code>opponent_team_id</code> 의 두 가지 조건이 포함된 <code>bool</code> <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">필터를</a> 사용하여 관련 게임만 검색합니다. 우리는 문서를 반환할 필요가 없고 집계만 중요하므로 <code>size:0</code> 로 설정합니다. <code>aggs</code> 블록에서는 <code>points, rebounds, assists, steals, blocks</code> 및 <code>fg_percentage</code> 같은 필드에서 여러 메트릭 <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">집계를</a> 병렬로 실행하여 평균값을 계산합니다. LLM은 계산에 적중하거나 실패할 수 있으며, 이렇게 하면 해당 프로세스가 Elasticsearch로 오프로드되어 NBA AI 어시스턴트가 정확한 데이터에 액세스할 수 있습니다.</p>export async function getHistoricalAveragesAgainstOpponent(player_id, opponent_team_id) {
    try {
        //Query for Historical Averages
        const historicalQuery = await elasticClient.search({
            index: 'sample-nba-player-data', 
            size: 0,
            query: {
                bool: {
                    must: [
                        {
                            term: {
                                player_id: {
                                    value: player_id,
                                },
                            },
                        },
                        {
                            term: {
                                opponent_team_id: {
                                    value: opponent_team_id,
                                },
                            },
                        },
                    ],
                },
            },
            aggs: {
                avg_points: { avg: { field: 'points' } },
                avg_rebounds: { avg: { field: 'rebounds' } },
                avg_assists: { avg: { field: 'assists' } },
                avg_steals: { avg: { field: 'steals' } },
                avg_blocks: { avg: { field: 'blocks' } },
             avg_fg_percentage: { avg: { field: 'fg_percentage' } },
            },
        });

        return {
            points: historicalQuery.aggregations.avg_points.value || 0,
            rebounds: historicalQuery.aggregations.avg_rebounds.value || 0,
            assists: historicalQuery.aggregations.avg_assists.value || 0,
            steals: historicalQuery.aggregations.avg_steals.value || 0,
            blocks: historicalQuery.aggregations.avg_blocks.value || 0,
            fgPercentage: historicalQuery.aggregations.avg_fg_percentage.value || 0,
        };
    } catch (error) {
        console.error('Query error from getHistoricalAveragesAgainstOpponent function:', error);
        return { error: 'Queries failed in getting historical averages against opponent.' };
    }
}
<p>3. 특정 상대를 상대로 한 플레이어의 시즌 평균을 계산하기 위해 과거 기록과 거의 동일한 쿼리를 사용합니다. 이 쿼리의 유일한 차이점은 <code>bool</code> 필터에 <code>game_date</code> 에 대한 추가 조건이 있다는 것입니다. <code>game_date</code> 필드는 현재 NBA 시즌 범위 내에 있어야 합니다. 이 경우 범위는 <code>2024-10-01</code> 에서 <code>2025-06-30</code> 사이입니다. 아래의 추가 조건은 다음 집계에서 이번 시즌의 게임만 격리되도록 합니다.
</p>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
<h4><strong>6단계: 플레이어 비교 유틸리티</strong></h4><p>코드를 모듈식으로 유지 관리하기 위해 메타데이터 도우미 함수와 Elasticsearch 집계를 통합하는 유틸리티 파일을 만들겠습니다. 이렇게 하면 상담원이 사용하는 기본 도구에 전원이 공급됩니다. 이에 대해서는 나중에 자세히 설명합니다:</p><p>1. <code>backend/lib</code> 디렉토리에 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/comparePlayers.js">비교플레이어.js</a> 파일을 새로 만듭니다.</p><p>2. 아래 함수를 추가하여 메타데이터 헬퍼와 Elasticsearch 집계 로직을 에이전트가 사용하는 기본 도구를 강화하는 단일 함수로 통합하세요.
</p>import { playersByName } from '../data/playerAndTeamInfo.js';
import { teamsByName } from '../data/playerAndTeamInfo.js';
import { upcomingMatchups } from '../data/playerAndTeamInfo.js';
import { getHistoricalAveragesAgainstOpponent } from './elasticAggs.js';
import { getSeasonAveragesAgainstOpponent } from './elasticAggs.js';

//Simple helper functions to simulate API calls for player and team metadata. These reference the hardcoded values from playerAndTeamInfo.js in the data directory
export function getPlayerInfo(playerFullName) {
    return playersByName[playerFullName];
}

export function getTeamID(teamFullName) {
    return teamsByName[teamFullName];
}

export function getUpcomingMatchups(teamId) {
    return upcomingMatchups[teamId];
}

//Main function used by the 'playerComparisonTool' agent tool
export async function comparePlayersForNextMatchup(player1Name, player2Name) {
    //Get Player Info
    const player1Info = getPlayerInfo(player1Name);
    const player2Info = getPlayerInfo(player2Name);

    //Get upcoming matchups
    const player1NextGame = getUpcomingMatchups(player1Info.team_id)[0];
    const player2NextGame = getUpcomingMatchups(player2Info.team_id)[0];

    //Get season and historical averages against next opponent for player 1
    const player1SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );
    const player1HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );

    //Get season and historical averages against next opponent for player 2
    const player2SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );
    const player2HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );

    const player1 = {
        name: player1Name,
        playerId: player1Info.player_id,
        teamId: player1Info.team_id,
        nextOpponent: {
            teamId: player1NextGame.opponent_team_id,
            teamName: player1NextGame.opponent_team_name,
            home: player1NextGame.home,
        },
        stats: {
            seasonAverages: player1SeasonAverages,
            historicalAverages: player1HistoricalAverages,
        },
    };

    const player2 = {
        name: player2Name,
        playerId: player2Info.player_id,
        teamId: player2Info.team_id,
        nextOpponent: {
            teamId: player2NextGame.opponent_team_id,
            teamName: player2NextGame.opponent_team_name,
            home: player2NextGame.home,
        },
        stats: {
            seasonAverages: player2SeasonAverages,
            historicalAverages: player2HistoricalAverages,
        },
    };

    return [player1, player2];
}
<h4><strong>7단계: 에이전트 구축하기</strong></h4><p>이제 프론트엔드 및 백엔드 스캐폴딩을 생성하고, NBA 경기 데이터를 수집하고, Elasticsearch에 대한 연결을 설정했으므로 에이전트 구축을 위해 모든 조각을 조합하기 시작할 수 있습니다.</p><p><strong>상담원 정의하기</strong></p><p>1. <code>backend/src/mastra/agents</code> 디렉터리 내의 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/agents/index.ts">index.ts</a> 파일로 이동하여 상담원 정의를 추가합니다. 다음과 같은 필드를 지정할 수 있습니다:</p><ul><li><p><strong>이름:</strong> 이름: 프런트엔드에서 상담원을 호출할 때 참조할 수 있는 이름을 지정합니다.</p></li><li><p><strong>지침/시스템 프롬프트: </strong>시스템 프롬프트는 상호 작용 중에 따라야 할 초기 컨텍스트와 규칙을 LLM에 제공합니다. 사용자가 채팅창을 통해 보내는 메시지와 비슷하지만, 이 메시지는 사용자가 입력하기 전에 먼저 표시됩니다. 이 역시 선택한 모델에 따라 달라집니다.</p></li><li><p><strong>모델:</strong> 사용할 LLM(Mastra는 OpenAI, Anthropic, 로컬 모델 등을 지원합니다).</p></li><li><p><strong>도구:</strong> 도구: 상담원이 호출할 수 있는 도구 기능의 목록입니다.</p></li><li><p><strong>메모리:</strong> (선택 사항) 상담원이 대화 기록 등을 기억하도록 하려는 경우 선택합니다. 간단하게 하기 위해 영구 메모리 없이 시작할 수 있지만, Mastra는 이를 지원합니다.</p></li></ul><p></p>import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { playerComparisonTool } from '../tools';

export const basketballAgent = new Agent({
    name: 'Basketball Agent',
    instructions: `
      You are a NBA Basketball expert.
      Your primary function is to compare two NBA players and recommend which one is the better fantasy pickup.

      Only compare players from the following list:
      - LeBron James
      - Stephen Curry
      - Jayson Tatum
      - Jaylen Brown
      - Nikola Jokic
      - Luka Doncic
      - Kyrie Irving
      - Anthony Davis
      - Kawhi Leonard
      - Russell Westbrook

      Input Handling Rules:
      - If the user asks about a player that is not on this list, respond with the list of available players for comparison.
      - If the user only inputs one player, ask the user to add another player from the list provided.
      - If the user inputs a player with the wrong spelling or capitalizations, infer from the list of available players provided.
      - IMPORTANT: If the user asks a question or asks you to generate a response about anything outside of basketball or the scope of this project, DO NOT answer and affirm you can only talk about basketball.

      Tool Usage:
      - Extract and standardize player names to match the list exactly.
      - Use the playerComparisonTool, passing both names as strings.
      - The tool will return an object with game information, stats, and analysis.

      Format your response using Markdown syntax. Use:

        Example output format:

       
        #### Next Game Info
        - ***LeBron James** vs Warriors, May 24 (Home)  
        - ***Stephen Curry** vs Lakers, May 24 (Away)


        #### Stats Comparison  
        \`\`\`  
        Stat                  LeBron James (vs Warriors)    Stephen Curry (vs Lakers)  
        --------------------  -----------------------------  ----------------------------  
        Historical Points     28.3                          30.3  
        Historical Assists    6.7                           8.7  
        Season Points         28.8                          23.3  
        Season Assists        6.2                           4.7  
        \`\`\`

        #### Fantasy Recommendation  
        Explain which player is the better fantasy pickup and why.
      
    `,
    model: openai('gpt-4o'),
    tools: { playerComparisonTool },
});
<p><strong>
도구 정의</strong></p><ol><li><p><code>backend/src/mastra/tools</code> 디렉터리 내의 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/tools/index.ts">index.ts</a> 파일로 이동합니다.</p></li><li><p>명령을 사용하여 Zod를 설치합니다:</p></li></ol>npm install zod<p>3. 도구 정의를 추가합니다. <code>comparePlayers.js</code> 파일 내의 함수를 에이전트가 이 도구를 호출할 때 사용할 기본 함수로 가져옵니다. Mastra의 <code>createTool()</code> 기능을 사용하여 <code>playerComparisonTool</code> 을 등록합니다. 필드에는 다음이 포함됩니다:</p><ul><li><p><code>id</code>: 상담원이 도구의 기능을 이해하는 데 도움이 되는 자연어 설명입니다.</p></li><li><p><code>input schema</code>: 도구의 입력 형태를 정의하기 위해 Mastra는 타입스크립트 스키마 유효성 검사 라이브러리인 <a href="https://zod.dev/">Zod</a> 스키마를 사용합니다. Zod는 에이전트가 올바른 구조의 입력을 입력하는지 확인하고 입력 구조가 일치하지 않으면 도구가 실행되지 않도록 방지하여 도움을 줍니다.</p></li><li><p><code>description</code>: 상담원이 언제 전화를 걸어 도구를 사용해야 하는지 이해하는 데 도움이 되는 자연어 설명입니다.</p></li><li><p><code>execute</code>: 도구가 호출될 때 실행되는 로직입니다. 저희의 경우 가져온 헬퍼 함수를 사용하여 성능 통계를 반환하고 있습니다.</p></li></ul>import { comparePlayersForNextMatchup } from '../../../lib/comparePlayers.js'
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const playerComparisonTool = createTool({
    id: "Compare two NBA players",
    inputSchema: z.object({
        player1:z.string(),
        player2:z.string()
    }),
    description: "Use this tool to compare two players given in the user prompt.",
    execute: async ({ context: { player1, player2 } }) =&gt; {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})<p><strong>CORS 처리를 위한 미들웨어 추가</strong></p><p>Mastra 서버에 미들웨어를 추가하여 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS를</a> 처리합니다. 인생에서 피할 수 없는 세 가지가 있다면 죽음, 세금, 그리고 웹 개발자에게는 CORS라고 합니다. 간단히 말해, 교차 출처 리소스 공유는 프론트엔드에서 다른 도메인이나 포트에서 실행되는 백엔드에 요청을 하지 못하도록 차단하는 브라우저 보안 기능입니다. 로컬호스트에서 백엔드와 프론트엔드를 모두 실행하지만 서로 다른 포트를 사용하므로 CORS 정책이 트리거됩니다. 백엔드에서 프론트엔드의 요청을 허용할 수 있도록 <a href="https://mastra.ai/en/docs/server-db/middleware">Mastra 문서에</a> 지정된 미들웨어를 추가해야 합니다.</p><p>1. <code>backend/src/mastra</code> 디렉터리 내의 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/index.ts">index.ts</a> 파일로 이동하여 CORS에 대한 구성을 추가합니다:</p><ul><li><p><code>origin: ['http://localhost:5173']</code></p><ul><li><p>이 주소(Vite 기본 주소)에서만 요청을 허용합니다.</p></li></ul></li><li><p><code>allowMethods: ["GET", "POST"]</code></p><ul><li><p>허용되는 HTTP 메서드입니다. 대부분의 경우 POST를 사용합니다.</p></li></ul></li><li><p><code>allowHeaders: ["Content-Type", "Authorization", "x-mastra-client-type, "x-highlight-request", "traceparent"],</code></p><ul><li><p>요청에 사용할 수 있는 사용자 정의 헤더를 결정합니다.</p></li></ul></li></ul><p></p>import { Mastra } from '@mastra/core/mastra';
import { basketballAgent } from './agents';

console.log('Starting Mastra server...');

export const mastra = new Mastra({
  agents: { basketballAgent },
  server:{
    timeout: 10 * 60 * 1000, // 10 minutes
    cors: {
      origin: ['http://localhost:5173'],
      allowMethods: ["GET", "POST"],
      allowHeaders: [
        "Content-Type",
        "Authorization",
        "x-mastra-client-type",
        "x-highlight-request",
        "traceparent",
      ],
      exposeHeaders: ["Content-Length", "X-Requested-With"],
      credentials: false,
    },
  },

});

console.log('Mastra server configured.'); // Log after server configuration
<h4><strong>8단계: 프론트엔드 통합</strong></h4><p>이 React 컴포넌트는 <code>@ai-sdk/react</code> 에서 <a href="https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk#using-the-usechat-hook">useChat()</a> 훅을 사용하여 Mastra AI 에이전트에 연결되는 간단한 채팅 인터페이스를 제공합니다. 또한 이 훅을 사용하여 토큰 사용량, 도구 호출을 표시하고 대화를 렌더링할 것입니다. 위의 시스템 프롬프트에서 상담원에게 마크다운으로 응답을 출력하도록 요청하므로 <code>react-markdown</code> 을 사용하여 응답 형식을 올바르게 지정합니다.</p><p></p><p>1. 프론트엔드 디렉터리에서 @ai-sdk/react 패키지를 설치하여 useChat() 훅을 사용합니다.</p>npm install @ai-sdk/react<p>2. 같은 디렉토리에 있는 동안 에이전트가 생성하는 응답의 형식을 올바르게 지정할 수 있도록 React Markdown을 설치합니다.</p>npm install react-markdown<p>3. <code>useChat()</code> 을 구현합니다. 이 훅은 프론트엔드와 AI 에이전트 백엔드 간의 상호작용을 관리합니다. 메시지 상태, 사용자 입력, 상태를 처리하고 통합 가시성을 위한 라이프사이클 훅을 제공합니다. 전달되는 옵션에는 다음이 포함됩니다:</p><ul><li><p><code>api:</code> 이는 Mastra AI 에이전트의 엔드포인트를 정의합니다. 기본값은 포트 4111이며 스트리밍 응답을 지원하는 경로도 추가하려고 합니다.</p></li><li><p><code>onToolCall</code>: 상담원이 도구를 호출할 때마다 실행되며, 상담원이 어떤 도구를 호출하는지 추적하는 데 사용합니다.</p></li><li><p><code>onFinish</code>: 상담원이 전체 응답을 완료한 후에 실행됩니다. 스트리밍을 활성화하더라도 <code>onFinish</code> 은 각 청크가 아닌 전체 메시지가 수신된 후에 실행됩니다. 여기서는 토큰 사용량을 추적하는 데 사용하고 있습니다. 이는 LLM 비용을 모니터링하고 최적화할 때 유용할 수 있습니다.</p></li></ul><p>4. 마지막으로 <code>frontend/components</code> 디렉토리에 있는 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/frontend/components/ChatUI.jsx">ChatUI.jsx</a> 컴포넌트로 이동하여 대화를 담을 UI를 만듭니다. 그런 다음 응답을 <code>ReactMarkdown</code> 컴포넌트로 래핑하여 상담원의 응답 형식을 올바르게 지정합니다.</p>import React, { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import ReactMarkdown from 'react-markdown';

export default function ChatUI() {
    const [totalTokenUsage, setTotalTokenUsage] = useState(0);
    const [promptTokenUsage, setPromptTokenUsage] = useState(0);
    const [completionTokenUsage, setCompletionTokenUsage] = useState(0);
    const [toolsCalled, setToolsCalled] = useState([]);

    const { messages, input, handleInputChange, handleSubmit, status } = useChat({
        api: 'http://localhost:4111/api/agents/basketballAgent/stream', //Replace with your own endpoint for your agent
        id: 'my-chat-session',

        //Optional parameter to check agent tool calls
        onToolCall: ({ toolCall }) =&gt; {
            setToolsCalled((prev) =&gt; [...prev, toolCall.toolName]);
        },

        //Optional parameter to check token usages
        onFinish: (message, { usage }) =&gt; {
            setTotalTokenUsage((prev) =&gt; prev + usage.totalTokens);
            setPromptTokenUsage((prev) =&gt; prev + usage.promptTokens);
            setCompletionTokenUsage((prev) =&gt; prev + usage.completionTokens);
        },

        //Optional parameter for error handling
        onError: (error) =&gt; {
            console.error('Agent error:', error);
        },
    });

    return (
        &lt;div&gt;
            &lt;div className="agent-info"&gt;
                &lt;h4 className="stats-title"&gt;What's My Agent Doing?&lt;/h4&gt;

                &lt;div className="stats-box"&gt;
                    &lt;strong className="stats-sub-title"&gt;Tools Called:&lt;/strong&gt;
                    &lt;ul className="tool-list"&gt;
                        {toolsCalled.map((tool, idx) =&gt; (
                            &lt;li key={idx}&gt;{tool}&lt;/li&gt;
                        ))}
                        {toolsCalled.length === 0 &amp;&amp; &lt;li&gt;No tools called yet.&lt;/li&gt;}
                    &lt;/ul&gt;

                    &lt;div className="usage-stats"&gt;
                        &lt;p&gt;Prompt Token Usage: {promptTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Completion Token Usage: {completionTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Total Token Usage: {totalTokenUsage}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;strong&gt;Conversation:&lt;/strong&gt;
            &lt;div className="convo-box"&gt;
                {messages.map((msg) =&gt; (
                    &lt;div key={msg.id} className="message-item"&gt;
                        &lt;strong className="message-role"&gt;{msg.role === 'assistant' ? 'Basketbot' : 'You'}:&lt;/strong&gt;
                        &lt;ReactMarkdown&gt;{msg.content}&lt;/ReactMarkdown&gt;
                    &lt;/div&gt;
                ))}
            &lt;/div&gt;

            &lt;form onSubmit={handleSubmit}&gt;
                &lt;input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                /&gt;
                &lt;button type="submit" disabled={status === 'streaming'}&gt;
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    );
}<h4><strong>9단계: 애플리케이션 실행</strong></h4><p>축하합니다! 이제 애플리케이션을 실행할 준비가 되었습니다. 백엔드와 프론트엔드를 모두 시작하려면 다음 단계를 따르세요.</p><ol><li><p>터미널 창에서 루트 디렉토리에서 시작하여 백엔드 디렉토리로 이동하여 Mastra 서버를 시작합니다:</p></li></ol>cd backend

npm run dev<p>2. 다른 터미널 창에서 루트 디렉토리에서 시작하여 프론트엔드 디렉토리로 이동하고 React 앱을 시작합니다:</p><p></p>cd frontend

npm run dev<p></p><p>3. 3. 브라우저로 이동하여 다음으로 이동합니다:</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>채팅 인터페이스가 보일 것입니다. 이 샘플 프롬프트를 사용해 보세요:</p><ul><li><p>"르브론 제임스와 스테픈 커리 비교"</p></li><li><p>"제이슨 테이텀과 루카 돈치치 중 누구를 선택해야 하나요?"</p></li></ul><p></p><h3><strong>다음 단계 에이전트의 지능 향상</strong></h3><p>어시스턴트의 에이전트 기능과 추천 기능을 더욱 강화하기 위해 다음 버전에서는 몇 가지 주요 업그레이드 기능을 추가할 예정입니다.</p><p></p><p><strong>NBA 뉴스 시맨틱 검색</strong></p><p>플레이어 성과에 영향을 줄 수 있는 요소는 무수히 많으며, 그 중 상당수는 원시 통계에 나타나지 않습니다. 부상 보고서, 라인업 변경 또는 경기 후 분석과 같은 내용은 뉴스 기사에서만 볼 수 있습니다. 이러한 추가적인 맥락을 파악하기 위해 시맨틱 검색 기능을 추가하여 에이전트가 관련 NBA 기사를 검색하고 해당 내러티브를 추천에 반영할 수 있도록 할 것입니다.</p><p></p><p><strong>Elasticsearch MCP 서버를 사용한 동적 검색</strong></p><p>MCP(모델 컨텍스트 프로토콜)는 상담원이 데이터 소스에 연결하는 방법의 표준으로 빠르게 자리 잡고 있습니다. 에이전트가 우리가 제공하는 사전 정의된 검색 기능에 의존하지 않고 동적으로 쿼리를 작성할 수 있도록 검색 로직을 Elasticsearch MCP 서버로 마이그레이션할 것입니다. 이를 통해 보다 자연스러운 언어 워크플로우를 사용할 수 있고 모든 검색 쿼리를 수동으로 작성할 필요성을 줄일 수 있습니다. <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">여기에서</a> Elasticsearch MCP 서버와 에코시스템의 현재 상태에 대해 자세히 알아보세요.</p><p></p><p>이러한 변경 사항은 이미 진행 중이니 계속 지켜봐 주세요!</p><h3><strong>결론</strong></h3><p></p><p>이 블로그에서는 JavaScript, Mastra 및 Elasticsearch를 사용하여 판타지 농구 팀에 맞춤형 추천을 제공하는 에이전트 RAG 어시스턴트를 구축했습니다. 저희가 다뤘습니다:</p><ul><li><p><strong>에이전트 RAG의 기본</strong> 사항과 AI 에이전트의 자율성과 RAG를 효과적으로 사용할 수 있는 도구를 결합하여 보다 미묘하고 역동적인 에이전트를 만드는 방법에 대해 알아보세요.</p></li><li><p>데이터 저장 기능과 강력한 기본 집계 기능을 갖춘 <strong>Elasticsearch가 </strong>어떻게 LLM의 지식 베이스로서 훌륭한 파트너가 되는지 알아보세요.</p></li><li><p>자바스크립트 에코시스템에서 개발자를 위해 이러한 에이전트 구축을 간소화하는 <strong>Mastra </strong>프레임워크와 그 방법을 소개합니다.</p></li></ul><p>농구광이든, AI 에이전트를 구축하는 방법을 모색 중이든, 저처럼 두 가지 모두에 관심이 있는 분이라면 이 블로그가 시작을 위한 몇 가지 기본 요소를 제공했으면 합니다. 전체 리포지토리는 <a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub에서</a> 사용할 수 있으며, 자유롭게 복제하고 수정할 수 있습니다. 이제 판타지 리그에서 우승하세요!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agentic-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-rag</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[Javascript]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ffd561836a4cb20/6a17f1e47b54f978588b39e4/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[모델 컨텍스트 프로토콜을 사용하여 에이전트를 Elasticsearch에 연결하기]]></title>
    <description><![CDATA[모델 컨텍스트 프로토콜 서버를 사용해 Elasticsearch에서 데이터와 채팅해 보겠습니다.]]></description>
    <content:encoded><![CDATA[<p>데이터와 상호 작용하는 것이 동료와 채팅하는 것처럼 쉬우면 어떨까요? "지난달의 500달러 이상 모든 주문 표시" 또는 "어떤 제품이 가장 많은 별점 리뷰를 받았나요?" 라고 질문하고 쿼리할 필요 없이 즉각적이고 정확한 답변을 받는다고 상상해 보세요.</p><p>모델 컨텍스트 프로토콜(MCP)이 이를 가능하게 합니다. 대화형 AI를 데이터베이스 및 외부 API와 원활하게 연결하여 복잡한 요청을 자연스러운 대화로 변환합니다. 최신 LLM은 언어 이해 능력이 뛰어나지만, 실제 시스템과 통합될 때 진정한 잠재력을 발휘합니다. MCP는 이러한 간극을 메워 데이터 상호 작용을 보다 직관적이고 효율적으로 만들어 줍니다.</p><p>이 게시물에서는 이에 대해 알아보겠습니다:</p><ul><li><p>MCP 아키텍처 - 내부 작동 방식</p></li><li><p>Elasticsearch에 연결된 MCP 서버의 이점</p></li><li><p><a href="https://github.com/elastic/mcp-server-elasticsearch">Elasticsearch 기반 MCP 서버</a>구축하기</p></li></ul><p>앞으로의 흥미로운 시간! MCP와 Elastic 스택의 통합은 정보와의 상호 작용 방식을 혁신하여 복잡한 쿼리를 일상적인 대화처럼 직관적으로 만듭니다.</p><h2>모델 컨텍스트 프로토콜</h2><p>Anthropic에서 개발한 MCP(<a href="https://modelcontextprotocol.io/introduction">모델 컨텍스트 프로토콜</a> )는 안전한 양방향 채널을 통해 AI 모델을 외부 데이터 소스에 연결하는 개방형 표준입니다. 대화 컨텍스트를 유지하면서 외부 시스템에 대한 실시간 액세스라는 AI의 주요 한계를 해결합니다.</p><h3>MCP 아키텍처</h3><p>모델 컨텍스트 프로토콜 아키텍처는 두 가지 주요 구성 요소로 이루어져 있습니다:</p><ul><li><p><strong>MCP 클라이언트</strong> - 사용자를 대신하여 정보를 요청하거나 작업을 실행하는 AI 어시스턴트 및 챗봇입니다.</p></li><li><p><strong>MCP 서버</strong> - 관련 정보를 검색하거나 요청된 작업(예: 외부 API 호출)을 수행하는 데이터 저장소, 검색 엔진 및 API입니다.</p></li></ul><p>MCP 서버는 네 가지 주요 기능을 클라이언트에 노출합니다:</p><ul><li><p><strong>리소스</strong> - LLM 상호 작용을 위한 컨텍스트로 검색 및 사용할 수 있는 구조화된 데이터, 문서 및 콘텐츠입니다. 이를 통해 AI 어시스턴트는 데이터베이스, 검색 인덱스 또는 기타 소스에서 관련 정보에 액세스할 수 있습니다.</p></li><li><p><strong>도구</strong> - LLM이 외부 시스템과 상호 작용하거나 계산을 수행하거나 실제 작업을 수행할 수 있도록 하는 실행 가능한 함수입니다. 이러한 도구는 텍스트 생성을 넘어 AI 기능을 확장하여 어시스턴트가 워크플로우를 트리거하거나 API를 호출하거나 데이터를 동적으로 조작할 수 있도록 지원합니다.</p></li><li><p><strong>프롬프트</strong> - 재사용 가능한 프롬프트 템플릿 및 워크플로를 통해 일반적인 LLM 상호 작용을 표준화하고 공유할 수 있습니다.</p></li><li><p><strong>샘플링</strong> - 클라이언트를 통해 LLM 완료를 요청하여 보안과 개인정보 보호를 유지하면서 정교한 에이전트 동작을 가능하게 합니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfe82754551bb187a/6a17f7ec6864a43e71b6895d/bef5178133391e96e3d66ae634e41a85712a33a9-2345x1620.png" alt="모델 컨텍스트 프로토콜(MCP) 아키텍처" /><h2>MCP 서버 + Elasticsearch</h2><p></p><p>기존의 검색 증강 생성(RAG) 시스템은 사용자 쿼리를 기반으로 문서를 검색하지만, MCP는 여기서 한 걸음 더 나아가 AI 에이전트가 실시간으로 동적으로 작업을 구성하고 실행할 수 있도록 지원합니다. 이를 통해 사용자는 다음과 같은 자연어 질문을 할 수 있습니다:</p><p></p><ul><li><p>"지난달의 500달러 이상 주문을 모두 표시합니다."</p></li><li><p>"어떤 제품이 가장 많은 별점 리뷰를 받았나요?"</p></li></ul><p></p><p>쿼리를 작성하지 않고도 즉각적이고 정확한 답변을 얻을 수 있습니다.</p><p></p><p>MCP는 이를 통해 이를 달성합니다:</p><ul><li><p>동적 도구 선택 - 에이전트는 사용자 의도에 따라 MCP 서버를 통해 노출되는 적합한 도구를 지능적으로 선택합니다. "더 똑똑한" LLM은 일반적으로 상황에 따라 적절한 인수를 사용하여 올바른 도구를 선택하는 데 더 능숙합니다.</p></li><li><p>양방향 커뮤니케이션 - 에이전트와 데이터 소스가 유동적으로 정보를 교환하여 필요에 따라 쿼리를 구체화합니다(예. 조회 인덱스 매핑을 먼저 수행한 다음 ES 쿼리를 구성합니다.)</p></li><li><p>멀티 툴 오케스트레이션 - 워크플로에서 여러 MCP 서버의 툴을 동시에 활용할 수 있습니다.</p></li><li><p>지속적인 컨텍스트 - 상담원은 이전 상호 작용을 기억하여 대화 전반의 연속성을 유지합니다.</p></li></ul><p>Elasticsearch에 연결된 MCP 서버는 강력한 실시간 검색 아키텍처를 제공합니다. AI 에이전트는 필요에 따라 Elasticsearch 데이터를 탐색, 쿼리, 분석할 수 있습니다. 간단한 채팅 인터페이스를 통해 데이터를 검색할 수 있습니다.</p><p>MCP는 단순한 데이터 검색을 넘어 조치를 가능하게 합니다. 다른 도구와 통합하여 워크플로를 트리거하고, 프로세스를 자동화하고, 분석 시스템에 인사이트를 제공할 수 있습니다. MCP는 검색과 실행을 분리함으로써 AI 기반 애플리케이션을 유연하고 최신 상태로 유지하며 에이전트 워크플로에 원활하게 통합합니다.</p><h2>실습하기: Elasticsearch 데이터와 채팅하기 위한 MCP 서버</h2><p>MCP 서버를 통해 Elasticsearch와 상호 작용하려면 최소한 다음과 같은 기능이 필요합니다:</p><ul><li><p>인덱스 검색</p></li><li><p>매핑 가져오기</p></li><li><p>Elasticsearch의 쿼리 DSL을 사용해 검색 수행하기</p></li></ul><p>저희 서버는 타입스크립트로 작성되었으며, 공식 <a href="https://github.com/modelcontextprotocol/typescript-sdk">MCP 타입스크립트 SDK를</a> 사용할 예정입니다. 설정을 위해서는 MCP 클라이언트가 내장되어 있는 Claude 데스크톱 앱(무료 버전으로도 충분)을 설치하는 것을 권장합니다. 당사의 MCP 서버는 기본적으로 MCP 도구를 통해 공식 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/client/javascript-api/current/index.html">JavaScript Elasticsearch 클라이언트를</a> 노출합니다.</p><p>먼저 Elasticsearch 클라이언트와 MCP 서버를 정의해 보겠습니다:</p> const esClient = new Client({
    node: url,
    auth: {
      apiKey: apiKey,
    },
  });

  const server = new McpServer({
    name: "elasticsearch-mcp-server",
    version: "0.1.0",
  });<p>Elasticsearch와 상호 작용할 수 있는 다음 MCP 서버 도구를 사용할 것입니다:</p><ul><li><p><strong>인덱스 목록</strong> <a href="https://github.com/elastic/mcp-server-elasticsearch/blob/main/index.ts#L46">(list_indices</a>): 이 도구는 사용 가능한 모든 Elasticsearch 인덱스를 검색하여 인덱스 이름, 상태, 문서 수와 같은 세부 정보를 제공합니다.</p></li><li><p><strong>매핑</strong> 가져오기<a href="https://github.com/elastic/mcp-server-elasticsearch/blob/main/index.ts#L94">(get_mappings</a>): 이 도구는 사용자가 저장된 문서의 구조와 데이터 유형을 이해할 수 있도록 지정된 Elasticsearch 인덱스에 대한 필드 매핑을 가져옵니다.</p></li><li><p> <a href="https://github.com/elastic/mcp-server-elasticsearch/blob/main/index.ts#L147"></a>검색(검색): 이 도구는 제공된 쿼리 DSL을 사용하여 Elasticsearch 검색을 실행합니다. 텍스트 필드에 자동으로 하이라이트를 활성화하여 관련 검색 결과를 더 쉽게 식별할 수 있습니다.</p></li></ul><p>전체 Elasticsearch MCP 서버 구현은 <a href="https://github.com/elastic/mcp-server-elasticsearch">elastic/mcp-server-elasticsearch</a> 리포지토리에서 확인할 수 있습니다.</p><h4>색인과 채팅하기</h4><p>"지난달의 500달러 이상 모든 주문 찾기와 같이 데이터에 대해 자연어 질문을 할 수 있도록 Elasticsearch MCP 서버를 설정하는 방법을 살펴보겠습니다."</p><p><strong>Claude 데스크톱 앱 구성</strong></p><ul><li><p>Claude 데스크톱 앱 열기</p></li><li><p>설정으로 이동 &gt; 개발자 &gt; MCP 서버</p></li><li><p>"편집 구성" 을 클릭하고 <code>claude_desktop_config.json</code> 에 이 구성을 추가합니다:</p></li></ul>{
  "mcpServers": {
    "Elasticsearch MCP Server": {
      "command": "npx",
      "args": [
        "-y",
        "@elastic/mcp-server-elasticsearch"
      ],
      "env": {
        "ES_URL": "",
        "ES_API_KEY": ""
      }
    }
  }
}<p>참고: 이 설정은 Elastic에서 게시한 <a href="https://www.npmjs.com/package/@elastic/mcp-server-elasticsearch">@elastic/mcp-server-elasticsearch</a> npm 패키지를 활용합니다. 로컬에서 개발하려는 경우, <a href="https://github.com/elastic/mcp-server-elasticsearch/blob/main/README.md">여기에서</a> Elasticsearch MCP 서버를 가동하는 방법에 대한 자세한 내용을 확인할 수 있습니다.</p><p><strong>Elasticseach 인덱스 채우기</strong></p><ul><li><p><a href="https://gist.github.com/jedrazb/60e9400cbe40addfd9e4337749c28431">예제 데이터를</a> 사용하여 이 데모의 "orders" 인덱스를 채울 수 있습니다.</p></li><li><p>"지난달의 $500 이상 모든 주문 찾기와 같은 쿼리를 시도해 볼 수 있습니다."</p></li></ul><p><strong>사용 시작</strong></p><ul><li><p>Claude 데스크톱 앱에서 새 대화 열기</p></li><li><p>MCP 서버가 자동으로 연결됩니다.</p></li><li><p>Elasticsearch 데이터에 대한 질문을 시작하세요!</p></li></ul><p>이 데모를 통해 자연어를 사용해 Elasticsearch 데이터를 쿼리하는 것이 얼마나 쉬운지 확인해 보세요:</p><h4>어떻게 작동하나요?</h4><p>'지난달의 500달러 이상 모든 주문 찾기'라는 요청을 받으면 LLM은 지정된 제약 조건으로 Elasticsearch 인덱스를 검색하려는 의도를 인식합니다. 효과적인 검색을 수행하기 위해 상담원은 다음과 같이 파악합니다:</p><ul><li><p>인덱스 이름을 알아냅니다: <code>orders</code></p></li><li><p><code>orders</code> 인덱스의 매핑 이해</p></li><li><p>인덱스 매핑과 호환되는 쿼리 DSL을 빌드하고 마지막으로 검색 요청을 실행합니다.</p></li></ul><p>이 상호 작용은 다음과 같이 표현할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt152f41bc8c3e9752/6a17f7ee6df73152df0a10cc/8875bc75745124be87deac0be666509446887de2-2345x1620.png" alt="MCP 서버 + Elasticsearch의 작동 방식" /><h2>결론</h2><p>모델 컨텍스트 프로토콜은 복잡한 쿼리 대신 자연어 대화를 가능하게 하여 Elasticsearch 데이터와 상호 작용하는 방식을 개선합니다. MCP는 AI 기능과 데이터를 연결하여 상호 작용 전반에 걸쳐 컨텍스트를 유지하는 보다 직관적이고 효율적인 워크플로우를 만듭니다.</p><p>Elasticsearch MCP 서버는 공개 npm 패키지<a href="https://www.npmjs.com/package/@elastic/mcp-server-elasticsearch">(@elastic/mcp-server-elasticsearch)</a>로 제공되므로 개발자가 쉽게 통합할 수 있습니다. 최소한의 설정으로 팀은 간단한 대화를 통해 데이터를 탐색하고, 워크플로를 트리거하고, 인사이트를 얻을 수 있습니다.</p><p>직접 체험해 볼 준비가 되셨나요? 지금 바로 <a href="https://github.com/elastic/mcp-server-elasticsearch">Elasticsearch MCP 서버를</a> 사용해 보고 데이터와 채팅을 시작하세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch</guid>
    <category><![CDATA[에이전틱 AI]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jedr Blaszyk,Joe McElroy]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce68a95c633809ae/6a17f7f0148009fa28b48915/65b378f644bd13e3edf2f108d48186f1889f546c-1200x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 서버리스 프로젝트를 관리하기 위한 AI 에이전트]]></title>
    <description><![CDATA[자연어 기반 AI 에이전트로, Elasticsearch 서버리스 프로젝트를 손쉽게 관리하여 프로젝트 생성, 삭제, 상태 확인을 지원합니다.]]></description>
    <content:encoded><![CDATA[<h2>AI 에이전트를 사용하여 서버리스 Elasticsearch 프로젝트를 관리하는 방법</h2><ol><li><p><strong>리포지토리를 복제합니다:</strong> <code>git clone https://github.com/elastic/elasticsearch-labs/supporting-blog-content/serverless-ai-agent</code> <code>a</code>을 사용하여 GitHub에서 도구의 코드를 다운로드하고 <code>cd serverless-ai-agent</code> 을 사용하여 디렉토리로 이동합니다.</p></li><li><p><strong>환경을 설정합니다: </strong> <code>python -m venv venv</code> 으로 가상 환경(선택 사항)을 만들고 활성화합니다(Windows의 경우<code>source venv/bin/activate</code> 또는 <code>venv\Scripts\activate</code> ). 그런 다음 <code>pip install -r requirements.txt</code> 를 사용하여 필요한 Python 패키지를 설치합니다.</p></li><li><p><strong>자격 증명을 구성합니다: </strong>프로젝트 루트에 <code>.env</code> 파일을 생성하고 Elasticsearch API URL(<code>ES_URL</code>), API 키(<code>API_KEY</code>), 지역(<code>REGION</code>), OpenAI API 키(<code>OPENAI_API_KEY</code>)로 채웁니다.</p></li><li><p><strong>도구를 실행합니다: </strong>터미널에서 <code>python main.py</code> 을 실행하여 도구를 실행합니다. 그러면 AI 에이전트가 시작되고 명령에 대한 프롬프트가 표시됩니다.</p></li><li><p><strong>자연어로 프로젝트를 관리하세요:</strong> " 내\_프로젝트라는 서버리스 프로젝트 만들기", "내\_프로젝트라는 서버리스 프로젝트의 상태 보기", 또는 "내\_프로젝트라는 서버리스 프로젝트 삭제" 와 같은 일반 영어 명령을 사용하여 도구와 상호 작용합니다. AI가 사용자의 명령을 해석하고 해당 기능을 실행합니다.</p></li></ol><h2>배경</h2><p>이 작은 명령줄 도구를 사용하면 <a href="https://www.elastic.co/guide/en/serverless/current/intro.html">서버리스 Elasticsearch 프로젝트를</a> 일반 영어로 관리할 수 있습니다. AI(이 경우 OpenAI)와 대화하여 사용자가 의미하는 바를 파악하고 LlamaIndex를 사용하여 올바른 함수를 호출합니다!</p><h3>Elasticsearch 서버리스 AI 에이전트가 수행할 수 있는 작업</h3><ul><li><p><strong>프로젝트를 생성합니다</strong>: 새 서버리스 Elasticsearch 프로젝트를 생성합니다.</p></li><li><p><strong>프로젝트를 삭제합니다</strong>: 기존 프로젝트를 삭제합니다(예, 자동으로 정리됩니다).</p></li><li><p><strong>프로젝트 상태를 확인하세요</strong>: 프로젝트 진행 상황을 확인하세요.</p></li><li><p><strong>프로젝트 세부 정보 가져오기</strong>: 프로젝트에 대한 모든 중요한 세부 정보를 가져옵니다.</p></li></ul><p><a href="https://github.com/elastic/elasticsearch-labs/tree/a65f7bc1e4a041765d1c0a45ac44b9cd9fc1589f/supporting-blog-content/serverless-ai-agent">GitHub에서</a>코드를 확인하세요.</p><h3>Elasticsearch 서버리스 AI 에이전트의 작동 방식</h3><p>다음과 같은 내용을 입력하면</p><p><em>"내_프로젝트라는 이름의 서버리스 프로젝트를 만듭니다."</em></p><p>...무대 뒤에서 일어나는 일들을 소개합니다:</p><ul><li><p><strong>사용자 입력 &amp; 컨텍스트:</strong> 자연어 명령이 AI 상담원에게 전송됩니다.</p></li><li><p><strong>함수 설명:</strong> AI 에이전트는 이미 자세한 설명을 제공했기 때문에 create_ess_project, delete_ess_project, get_ess_project_status, get_ess_project_details와 같은 몇 가지 함수에 대해 알고 있습니다. 이러한 설명은 각 기능이 수행하는 작업과 필요한 매개변수를 AI에 알려줍니다.</p></li><li><p><strong>LLM 처리:</strong> 쿼리와 함수 정보가 LLM으로 전송됩니다. 즉, AI가 본다는 뜻입니다:</p><ul><li><p><strong>사용자 쿼리입니다</strong>: 일반 영어 명령어.</p></li><li><p><strong>사용 가능한 기능 &amp; 설명</strong>: 올바른 도구를 선택할 수 있도록 각 도구의 기능에 대한 세부 정보입니다.</p></li><li><p><strong>컨텍스트/기록 채팅 정보</strong>: 대화이기 때문에 이전에 대화한 내용을 기억합니다.</p></li></ul></li><li><p><strong>함수 호출 &amp; 응답:</strong> AI가 어떤 함수를 호출할지 파악하고 프로젝트 이름과 같은 올바른 매개변수를 전달한 다음 함수가 실행됩니다. 응답은 친숙한 형식으로 다시 전송됩니다.</p></li></ul><p>즉, 자연어 쿼리와 자세한 도구 설명 목록을 모두 LLM에 전송하여 요청에 대한 올바른 조치를 '이해'하고 선택할 수 있도록 합니다.</p><h3>AI 에이전트 설정</h3><h4>전제 조건:</h4><p>AI 에이전트를 실행하기 전에 다음 사항을 설정했는지 확인하세요:</p><ol><li><p><strong>Python(v3.7 이상)이</strong> 설치되어 있어야 합니다.</p></li><li><p>Elastic Cloud에 <strong>서버리스 계정</strong> 설정.</p></li><li><p>언어 모델과 상호 작용할 수 있는 <strong>OpenAI 계정입니다</strong>.</p></li></ol><h4>단계:</h4><p><strong>1. 리포지토리를 복제합니다:</strong></p>git clone https://github.com/elastic/elasticsearch-labs/supporting-blog-content/serverless-ai-agent
cd serverless-ai-agent<p><strong>2. 가상 환경 만들기(선택 사항이지만 권장):</strong> 환경 관련 문제가 발생하면 가상 환경을 설정하여 격리할 수 있습니다:</p>python -m venv venv
source venv/bin/activate  # On Windows, use venv\Scripts\activate<p><strong>3. 종속성을 설치합니다:</strong> 실행하여 필요한 모든 종속성이 설치되었는지 확인합니다:</p>pip install -r requirements.txt<p><strong>4. 환경을 구성합니다:</strong>.env 파일을 프로젝트 루트에 다음 변수와 함께 추가합니다. 다음은 도움이 되는 <code>.env.example</code> 파일 예제입니다:</p>ES_URL=your_elasticsearch_api_url  # The base URL for your Elasticsearch service (e.g., https://your-cluster-id.es.region.aws.elastic-cloud.com)
API_KEY=your_elasticsearch_api_key  # Your API key for Elasticsearch
REGION=your_region  # Example: aws-eu-west-1
OPENAI_API_KEY=your_openai_api_key  # Your OpenAI API key<p><code>ES_URL</code>, <code>API_KEY</code>, <code>OPENAI_API_KEY</code> 에 올바른 값을 입력했는지 확인합니다. API 키는 각 서비스 대시보드에서 찾을 수 있습니다.</p><p><strong>5. 프로젝트 파일:</strong> 이 도구는 <code>projects.json</code> 파일을 사용하여 프로젝트 매핑(프로젝트 이름과 세부 정보)을 저장합니다. 이 파일이 아직 존재하지 않으면 자동으로 생성됩니다.</p><h3>AI 에이전트 실행</h3>python main.py<p>다음과 같은 메시지가 표시됩니다:</p>Welcome to the Serverless Project AI Agent Tool!
You can ask things like:
 - 'Create a serverless project named my_project'
 - 'Delete the serverless project named my_project'
 - 'Get the status of the serverless project named my_project'
 - 'Get the details of the serverless project named my_project'<p>명령을 입력하면 AI 에이전트가 마법을 부립니다! 완료되면 <code>exit</code> 또는 <code>quit</code> 을 입력하여 종료합니다.</p><h3>몇 가지 추가 정보</h3><ul><li><p><strong>LLM 통합</strong>: LLM에는 쿼리와 사용 가능한 각 기능에 대한 자세한 설명이 모두 제공됩니다. 이를 통해 컨텍스트를 이해하고 예를 들어 <code>create_ess_project</code> 또는 <code>delete_ess_project</code> 으로 전화할지 여부를 결정할 수 있습니다.</p></li><li><p><strong>도구 설명</strong>: 각 함수 도구(FunctionTool.from_defaults를 사용하여 생성됨) 에는 친절한 설명이 있습니다. 이 설명은 LLM에 전송되는 프롬프트에 포함되어 있어 사용 가능한 작업과 각 작업이 무엇을 기대하는지 "알 수 있도록" 합니다.</p></li><li><p><strong>지속성</strong>: 프로젝트와 프로젝트의 세부 정보는 projects.json에 저장됩니다, 매번 정보를 다시 입력할 필요가 없습니다.</p></li><li><p><strong>자세한 로깅</strong>: 에이전트가 자세한 정보 모드로 설정되어 있어 디버깅을 하고 명령어가 함수 호출로 변환되는 과정을 확인하는 데 유용합니다.</p></li></ul><h3>Elasticsearch AI 에이전트 활용 예시</h3>python main.py 

Welcome to the Serverless Project AI Agent Tool!

You can ask things like:
 - 'Create a serverless project named my_project'
 - 'Delete the serverless project named my_project'
 - 'Get the status of the serverless project named my_project'
 - 'Get the details of the serverless project named my_project'

User: Create a serverless project named my-february-test
Added user message to memory: Create a serverless project named my-february-test

=== Calling Function ===
Calling function: create_ess_project with args: {"project_name":"my-february-test"}
========================


Agent: The serverless Elasticsearch project named "my-february-test" has been created successfully. Here are the details:

- **Project ID:** &lt;&gt;&gt;
- **Elasticsearch Endpoint:** [https://my-february-test-ccc399.es.eu-west-1.aws.qa.elastic.cloud](https://my-february-test-ccc399.es.eu-west-1.aws.qa.elastic.cloud)
- **Kibana Endpoint:** [https://my-february-test-ccc399.kb.eu-west-1.aws.qa.elastic.cloud](https://my-february-test-ccc399.kb.eu-west-1.aws.qa.elastic.cloud)

User: get my-february-test status
Added user message to memory: get my-february-test status

=== Calling Function ===
Calling function: get_ess_project_status with args: {"project_name":"my-february-test"}
========================

Agent: The status of the "my-february-test" project is currently **initializing**.

User: get my-february-test info
Added user message to memory: get my-february-test info

=== Calling Function ===
Calling function: get_ess_project_details with args: {"project_name":"my-february-test"}
========================

Agent: Here are the details for the "my-february-test" project:

- **Project Name:** my-february-test
- **Alias:** my-february-test-ccc399
- **Cloud ID:** ""
- **Project ID:** ""
- **Region ID:** aws-eu-west-1
- **Created At:** 2025-02-12T15:42:00.530177619Z
- **Created By:** 571213384
- **Organization ID:** ""
- **Type:** Elasticsearch
- **Optimized For:** General Purpose
- **Search Lake:**
  - **Boost Window:** 7
  - **Search Power:** 100
- **Endpoints:**
  - **Elasticsearch:** https://my-february-test-ccc399.es.eu-west-1.aws.qa.elastic.cloud
  - **Kibana:** https://my-february-test-ccc399.kb.eu-west-1.aws.qa.elastic.cloud
- **Credentials:**
  - **Username:** ""
  - **Password:** ""

Please ensure to keep the credentials secure.

User: please delete the my-february-test project
Added user message to memory: please delete the my-february-test project

=== Calling Function ===
Calling function: delete_ess_project with args: {"project_name":"my-february-test"}
========================

Agent: The "my-february-test" project has been deleted successfully.<p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/serverless-elasticsearch-ai-agent</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/serverless-elasticsearch-ai-agent</guid>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[에이전틱 AI]]></category>
    <dc:creator><![CDATA[Fram Souza]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88526af16bafdb7c/6a17d7807f6f15825dc0998d/d11e1ba058784ec92b8953fb8db62e1bad21c210-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>