<?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/cn/search-labs/blog/category/agentic-ai</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/agentic-ai</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/agentic-ai.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 08:54:02 GMT</lastBuildDate>
  <item>
    <title><![CDATA[137,000 人，零人工决策：依托 Elasticsearch 实现智能体灾害应急响应]]></title>
    <description><![CDATA[了解当飓风来袭时，Kibana 检测规则、工作流和 AI 智能体如何自动安排转移七处设施中的 137,000 名军事人员，全程无需人工调度。]]></description>
    <content:encoded><![CDATA[<p>Elastic 刚刚协调完成了七处设施中 137,000 名军事人员的自动化疏散，全程无需人工干预。当 4 级飓风袭击汉普顿锚地海岸线时，Elasticsearch 的地理空间数据富化功能在索引数据入库时便已识别出受影响区域内的每一处设施。一条 Kibana 检测规则被触发。工作流启动了 AI 智能体对话。智能体根据容量、距离和分支兼容性进行了推理，然后一次性发送了 16 条疏散和接收通知。从接收原始 GDACS 事件到生成协同动作，全程自动完成。</p><p>每年，自然灾害都迫使应急管理人员、军事指挥官和公共安全官员在紧迫的时间内做出关乎重大利益的高风险决策。在传统模式下，这些决策依赖于电话联络网、电子表格以及散落分布在数十人脑海中的机构知识。单单是多头协调带来的沟通成本就会白白消耗掉极其宝贵的黄金救援时间。</p><p>本文旨在展示 Elastic 如何驱动一套具备敏捷响应能力的智能体灾害应急协调系统，该系统能够检测威胁、推理后勤逻辑并自动采取行动。为了具体说明，我们构建了一个模拟：一场虚构的 4 级飓风威胁着汉普顿锚地海岸线，触发了七个军事设施中超过 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 推理服务 (EIS) 的本地 Elastic 集群。</p><h2>Elasticsearch 智能体灾害响应管道如何运作</h2><p>该管道包含七个端到端协同工作层：</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>全球灾害警报与协调系统 (GDACS) 灾害事件发送至 Elasticsearch</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>警报会触发一个 Kibana 工作流，从而启动 AI 智能体对话。</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 数据。我们使用自定义采集管道将此数据源采集到数据流 (logs-gdacs.events-*)，并将原始 GeoJSON 规范化为 ECS 字段。</p><p>GDACS 采集管道有几个值得注意的功能：</p><p><strong>几何提取：</strong>质心作为 geo_point 存储以用于地图显示，而影响范围多边形则作为 geo_shape 存储在 gdacs.affected_area 中，该字段后续将用于相交查询。</p><p><strong>严重性归一化：</strong>每种灾害类型都有不同的严重性等级。例如热带气旋按风速 (km/h) 衡量；地震则按里氏震级衡量。管道将它们全部映射到一个归一化的 0–100 分数：</p>// Painless snippet from the ingest pipeline
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 标签（低、中、高、极高），该标签用于检测规则中的警报严重性映射。</p><p><strong>ECS 对齐：</strong>event.kind 设为 alert，event.category 设为 threat，时间戳映射到 event.start/event.end，并使用基于指纹的稳定 _id 实现去重。</p><h2>第 3 步：地理空间信息富化：在索引时查找受影响的设施</h2><p>Elasticsearch 的 geo_match 富化策略会在索引阶段将灾害影响区域多边形与各个设施边界进行匹配，无需在查询时执行连接操作。我们无需在搜索时进行查询，而是在采集管道中利用<strong>富化处理器</strong>，<em>在文档被索引时</em>将灾害影响区域多边形与各个设施的边界进行匹配。</p><p>该富化策略属于一种 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: "New Natural Disaster Alert: {{ 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 级风暴（最大风速 213 公里/小时），预计将在弗吉尼亚州的汉普顿锚地区域登陆。当系统接收到 GDACS 事件数据时，受影响区域的多边形与该地区的七个主要军事设施发生重叠。检测规则随之触发。工作流随即启动了智能体对话。</p><p>在单次智能体循环中，智能体执行了以下操作：</p><ul><li><p>识别出受影响区域内的七处设施，共涉及 137,372 名人员。</p></li><li><p>调用 mitra.nearest_facility 查找风暴路径之外的接收设施。</p></li><li><p>根据可用安置容量和距离，将人员分配到九个接收设施。</p></li><li><p>生成并向所有七个受影响的设施发送疏散命令。</p></li><li><p>生成并向所有九个接收设施发送接收通知。</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>弗吉尼亚州国民警卫队彭德尔顿营基地</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>97 公里</p><p>~40,000</p><p>匡提科海军陆战队基地</p><p>148 公里</p><p>~30,000</p><p>印第安黑德海军支援设施</p><p>151 公里</p><p>~30,000</p><p>安德鲁斯联合基地</p><p>180 公里</p><p>~30,000</p><p>帕图森特河海军航空站</p><p>141 公里</p><p>~10,000</p><p>国民警卫队巴特纳军营训练中心</p><p>174 公里</p><p>~5,000</p><p>国民警卫队贝瑟尼海滩训练基地</p><p>209 公里</p><p>~4,707</p><p>里瓦娜基地</p><p>140 公里</p><p>~7,500</p><p>国防通用物资供应中心</p><p>22 公里</p><p>~6,000</p><p>转移的资产包括运输车辆、直升机、巡逻艇、医疗单元、工程车辆、发电机、拖挂式水罐车、避难所套件以及通信系统。</p><h3>自动化电子邮件通知</h3><p>智能体确定分配方案后，便调用 mitra.send_email 并一次性发送了 16 封电子邮件；即向所有七个受影响设施发送疏散指令，向所有九个接收设施发送接收通知。每封邮件均包含目标设施、迁入人员数量、待转移资产以及协调联系人。原本需要耗费数小时通过电话逐级通知才能完成的工作，在智能体完成推理的那一刻便已自动处理完毕。</p><h3>借助 RAG 和策略锚定增强智能体灾害应急响应</h3><p>此演示纯粹基于结构化数据构建，例如容量数值、距离和运行状态。Elastic 的语义搜索和检索增强生成 (RAG) 功能可以通过两项新增功能使智能体变得更加智能：</p><p><strong>历史响应检索：</strong>将以往的行动后报告、联邦紧急事务管理局 (FEMA) 事件摘要以及灾害响应记录作为向量嵌入编入索引。当新事件触发时，智能体能够从语义层面检索类似事件的处置方式，从而利用积累的机构经验（而非仅仅依赖容量计算）来指导分配决策。</p><p><strong>策略与条令依据：</strong>索引 DoD 应急管理指令、设施业务连续性计划以及指挥官指导。智能体可以检索并引用指导响应的实际策略，确保每项决策都基于条令而非推断。</p><p>这两项功能都遵循相同的 Elastic 原生方法：推理管道会在索引时生成嵌入向量，并向智能体提供语义搜索工具。协调管道保持不变，智能体则变得更智能。</p><h2>为什么 Elasticsearch 是公共部门智能体响应的理想平台</h2><p>Elasticsearch 不是聊天机器人，也不是仪表板。它是具备响应能力的智能体工作流系统：它能够自主检测威胁、分析复杂的物流难题，并协调 137,000 人的转移工作，全程无需人工干预。之所以能实现这样的结果，是因为它所依赖的每项功能都集中在一个统一的平台中。</p><p>Elasticsearch 的地理空间支持功能（geo_point、geo_shape、富化策略以及基于距离的排序）能够处理空间推理，使大规模的相交检测和设施查询成为可能。语义搜索和向量嵌入技术确保了智能体的决策基于事实，即 AI 的推理建立在实际数据之上，而非凭空产生的“幻觉”假设。Kibana 的检测引擎、工作流、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>消防与紧急医疗服务</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>我们最近通过添加对 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 实现检索增强生成 (RAG) 架构的智能体的创建过程。我们将展示一个演示项目，其中采用智能体方法来与存储在 Elasticsearch 中的科幻电影数据语料库进行交互。该项目可在 <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>允许其与“外部世界”交互，例如与 Web 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> 存储库，其中包含演示项目的源代码。存储库中报告的示例演示了如何在 Mastra 中创建一个实现 RAG 架构、用于从 Elasticsearch 检索文档的智能体。</p><p>我们为演示提供了一个关于科幻电影的数据集。我们从 <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>。如果您想更改它，可以使用环境变量 <code>ELASTICSEARCH_INDEX_NAME</code>。</p><p>我们使用 OpenAI 作为嵌入服务，这意味着您需要在 <code>OPENAI_API_KEY</code> 环境变量中提供 OpenAI 的 API 密钥。</p><p>示例中使用的嵌入模型是 <a href="https://developers.openai.com/api/docs/models/text-embedding-3-small">openai/text-embedding-3-small</a>，嵌入维度为 1536。</p><p>为了生成最终答案，我们使用了 <a href="https://developers.openai.com/api/docs/models/gpt-5-nano">openai/gpt-5-nano</a> 模型来降低成本。</p><p>RAG 架构允许您使用性能较低（且通常成本较低）的 LLM 模型，因为答案落地的主要工作是由检索组件（此处为 Elasticsearch）承担。</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>scifi-movies 索引的映射包含以下字段：</p><ul><li><p><strong>embedding</strong>：dense_vector，1536 维，cosine 相似度。</p></li><li><p><strong>description</strong>，包含电影描述的文本。</p></li><li><p><strong>director</strong>，包含导演姓名的文本。</p></li><li><p><strong>title</strong>，包含电影标题的文本。</p></li></ul><p>我们使用 title + description 生成嵌入向量。由于 title 和 description 是两个独立的字段，将两者拼接可以确保生成的嵌入向量同时捕获电影的具体唯一标识 (title) 和丰富的描述性上下文 (description)，从而实现更准确、更全面的语义搜索结果。这种组合输入为嵌入模型提供了更好的文档内容单一表示，便于相似性匹配。</p><h2>运行演示</h2><p>您可以使用以下命令运行演示：</p>npm run dev<p>该命令将在 <strong>localhost:4111</strong> 启动一个 Web 应用，以访问 Mastra Studio（图3）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a539df677a33b36/6a17041a47d49c36842d88a0/1567e309df21a12bcf1dfef4429f82342549956c-1705x1079.png" alt="Mastra Studio 的屏幕截图，其中包含 Elasticsearch Agent 示例。" /><p><a href="https://mastra.ai/docs/getting-started/studio">Mastra Studio</a>提供了一个交互式 UI 用于构建和测试您的智能体，以及一个将 Mastra 应用程序作为本地服务公开的 REST API。这让您可以立即开始构建，无需担心集成问题。</p><p>我们提供了一个 <strong>Elasticsearch Agent</strong>，它使用 Mastra 的 <a href="https://mastra.ai/reference/tools/vector-query-tool">createVectorQueryTool</a> 作为工具，利用 Elasticsearch 执行语义搜索。该智能体采用 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>，则可以使用聊天系统测试该智能体。例如，您可以提出如下关于科幻电影的问题：</p><p><em>查找五部关于 UFO 的电影或电视剧</em>。</p><p>您会注意到智能体将执行 vectorQueryTool。您可以点击调用的工具来查看输入和输出。执行结束时，LLM 将根据来自 Elasticsearch 的 scifi-movies 索引的上下文回答您的问题（图 4）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda92a9b6e56528d3/6a17041c2b835f9724f4b0d6/d9998d4f687984de98845dae52d1288166abf448-1344x1071.png" alt=" 使用 Elasticsearch 智能体的 LLM 响应。" /><p>Mastra 在内部执行以下步骤：</p><ol><li><p><strong>向量转换：</strong>用户的问题 “<em>查找五部关于 UFO 的电影或电视剧</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 处理这些信息并生成最终答案，确保满足用户请求的五个结果。</p></li></ol><h2>Elasticsearch 智能体</h2><p>下面我们展示了 Elasticsearch 智能体的源代码。</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 示例中检索部分的工具。它使用了 <a href="https://mastra.ai/reference/vectors/elasticsearch">Elastic 为 Mastra 贡献的 ElasticSearchVector</a> 实现。</p><p>该智能体是 agent 类的一个对象，它使用了 vectorQueryTool、提示和内存组件。可以看出，将 Elasticsearch 连接到智能体所需的代码量非常少。</p><h2>结论</h2><p>本文展示了将 Elasticsearch 与 Mastra 框架集成以构建复杂的智能体 AI 应用程序的简便性和强大功能。具体来说，我们逐步实现了一个 RAG 智能体，能够对 Elasticsearch 中索引的科幻电影数据语料库执行语义搜索。</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 服务器的优势，并展示如何使用 TypeScript 创建该服务器，以将 Elasticsearch 连接到 LLM 驱动的应用程序。</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">Elastic Agent Builder MCP 服务器，适用于 Elasticsearch 9.2 及以上版本</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>通过阅读本文，您将学会使用 TypeScript 创建 MCP 服务器，该服务器可搜索存储在 Elasticsearch 索引中的信息，对其进行总结并提供引用。我们将使用 Elasticsearch 进行检索，使用 OpenAI 的 <code>gpt-4o-mini</code> 模型提炼摘要并生成引用，并使用 Claude Desktop 作为 MCP 客户端和 UI 来接收用户查询并提供回复。最终我们将得到一个内部知识助手，帮助工程师在整个组织的技术文档中发现并综合最佳实践。</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> 创建的开放标准，提供大型语言模型与外部系统（如 Elasticsearch）之间的安全双向连接。您可以在<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">这篇文章</a>中了解更多关于 MCP 现状的信息。</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 客户端。它将作为聊天界面，用户可以用自然语言提问，它还将自动调用我们的 MCP 服务器提供的工具来搜索文档和生成摘要。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06fd7a02042094e1/6a170c1b14b2700024e3c651/66eb0b11473347b6cf2d85718251eeac38d6249d-1999x1491.png" alt="Claude 4.5 十四行诗页面，附有“到喝咖啡和用 Claude 时间了？今天我能为您做什么？”" /><h2>创建 Elasticsearch MCP 服务器</h2><p>通过使用 <a href="https://github.com/modelcontextprotocol/typescript-sdk">TypeScript 软件开发工具包</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>结构化内容： </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>我们将 fuzziness : “AUTO” 配置</em><em>为根据被分析的词元的长度具有可变的拼写错误容忍度。我们还设置了</em> <em><code>title^2</code></em> <em>来提高标题字段匹配的文档的分数。</em></p><h4>摘要和引用工具</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>请按照<a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers">本指南</a>配置 MCP 服务器和 Claude Desktop。在 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 十四行诗页面，附有“下午好，Jeff。今天我能为您做什么？”" /><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="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 聊天，附有“Jeff 回来了”，外加一个新的用户问题：“在我们的系统中改进身份验证和访问控制的主要建议是什么？附上参考文献。”" /><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 桌面聊天页面，包含滚动文本，其中包括每个工具对问题“在我们的系统中改进身份验证和访问控制的主要建议是什么？附上参考文献。”的响应。" /><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 的 &quot;始终允许&quot; 和 &quot;允许一次&quot; 选项，供用户选择。" /><h2>结论</h2><p>MCP 服务器代表了本地和远程应用中 LLM 工具标准化的重要一步。虽然完全兼容仍在开发中，但我们正朝这个方向快速推进。</p><p>在本文中，我们学习了如何用 TypeScript 构建一个自定义 MCP 服务器，将 Elasticsearch 连接到基于 LLM 的应用。我们的服务器公开了两个工具：<code>search_docs</code> 用于使用查询 DSL 检索相关文档；<code>summarize_and_cite</code> 用于通过 OpenAI 模型和 Claude Desktop 作为客户端 UI 生成带引用的摘要。</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[shell 工具并非上下文工程的灵丹妙药]]></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>shell 工具和文件系统是否就是智能体进行上下文工程所需的一切？</em>不幸的是，讨论很快偏离了焦点，转向了文件系统与数据库之争。</p><p>本文重新聚焦于这个问题：<em>智能体构建自身上下文需要哪些正确的搜索接口？</em>它首先讨论了 shell 工具与专用数据库工具之间的取舍。在此基础上，它提供了一个实用的框架，用于为您的智能体需求找到正确的接口。</p><h2>对智能体而言，“构建上下文”到底意味着什么？</h2><p>在早期的 <a href="https://www.elastic.co/what-is/retrieval-augmented-generation">Retrieval-Augmented Generation (RAG) 管道</a>中，开发人员设计了一个固定的检索管道，而大型语言模型（LLM）只是上下文的被动接收者。这是一个根本性的限制：无论是否需要，每次查询都要检索上下文，而且不检查上下文是否真的有帮助。</p><p>随着向智能体式 RAG 的转变，智能体现在可以访问一组搜索工具来构建自己的上下文。例如，Claude Code [1] 和 Cursor [2] 都允许智能体根据任务的实际需求，在不同的搜索工具之间进行选择，甚至将它们组合起来用于链式查询。</p><h2>有哪些用于上下文工程的搜索接口？</h2><p>上下文可以存在于不同的位置，例如网络上、本地文件系统中或数据库中。智能体可以通过不同的工具与这些脱离上下文的每个数据源进行交互：</p><ul><li><p><strong>shell 工具</strong> 可以执行 shell 命令并访问本地文件系统。一些内置 shell 工具的例子包括 <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>可以搜索和读取本地（或上传）文件（无需完整的 shell 访问权限）。一些内置文件搜索工具的例子是 <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>如图所示，shell 工具功能强大，可用于从不同数据源检索上下文，包括：</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 或运行脚本，这在与 Agent Skills 结合使用时特别有用。Agent Skills 是注入到智能体上下文中用于指导正确工具使用的可复用、带示例的文档（例如 <a href="https://github.com/elastic/agent-skills">Elastic Agent Skills for Elasticsearch</a>）。</p></li><li><p><strong>网络：</strong>智能体可以通过搜索提供商的 API，使用 curl 命令执行网络搜索。</p></li></ul><p>然而，shell 工具提供直接的系统访问权限，因此需要采取安全措施，例如在隔离的沙盒环境中运行，并记录所有执行的命令。</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>Shell 工具 + 文件系统</h3><p>对于文件系统搜索，shell 工具是自然的起点。当前，编码智能体正在推动该领域取得巨大进展。由于它们处理本地文件中的代码，因此自然是以文件为主的用例。因此，LLM 在后训练阶段会针对编码任务进行微调。这就是为什么许多 LLM 不仅擅长编写代码，还擅长使用 shell 命令和操作文件系统的原因。</p><p>使用带有内置 CLI（如 <code>ls</code> 和 <code>grep</code>）的 shell 工具来查找文件是有效的。使用 grep，像“Find all files that import <code>matplotlib</code>”这样的查询既快速、精确又廉价。但是，当智能体需要处理概念性查询（例如“How does our app handle failed authentication?”）时，使用 grep 进行模式匹配很快就会触及天花板。为了填补这一空白，出现了一些将语义搜索能力带到命令行的替代方案，包括 <a href="https://github.com/jina-ai/jina-grep-cli"><code>jina-grep</code></a>。</p><p>然而，grep 及其许多语义搜索替代方案在语料库上的运行速度为 O(n)。对于代码库相关的使用场景，这可能没问题。但是，如果数据量增加，延迟就会变得很明显。在这种情况下，为了保持性能，需要使用索引数据存储。</p><h3>shell 工具 + 数据库</h3><p>另一种为数据添加更多搜索能力（例如语义搜索或混合搜索）的方法是将数据存储在数据库中，就像 Cursor 所做的那样。此外，当数据需要复杂的关系连接或聚合时，数据库接口是不可或缺的。</p><p>数据存储在数据库中而非文件系统上时，shell 工具可以在某些用例中充当轻量级的数据库接口。如果您的查询足够简单，只需 CLI 或 curl 调用即可完成，那么专用的数据库工具可能会带来不必要的复杂性。</p><p>这种方法也适用于早期的探索阶段，此时您还不知道智能体最终会发展出什么样的查询模式。在这种情况下，Agent Skills 可以为智能体提供足够的结构来正确执行查询，而无需投入构建专用工具。但是，当智能体需要大量迭代才能找出针对重复任务查询数据库的正确方式时，使用 shell 工具作为接口所带来的词元开销，就不再能抵消避免使用额外工具的简单性优势了。</p><h3>专用数据库工具</h3><p>特别是当重复的查询模式是结构化的或分析性的时，专用的数据库工具就变得必要了。<a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Vercel 和 Braintrust 的一篇博客文章</a>比较了拥有不同搜索工具集的智能体，在半结构化数据（如客户支持工单和销售通话记录）上执行真实世界的检索任务（例如，“How many open issues mention 'security'?”或“Find issues where someone reported a bug and later someone submitted a PR claiming to fix it?”）[4]。</p><p>拥有专用数据库工具的智能体，与仅拥有 shell 工具和文件系统的智能体相比，使用的词元更少，速度更快，犯的错误也更少。经验表明，当查询需要对半结构化数据进行分析推理时，直接使用数据库工具才是正确的选择。</p><h3>组合使用搜索接口</h3><p>没有哪一个搜索接口能完美处理所有查询。例如，Cursor 将 shell 工具（用于通过 grep 搜索）和语义搜索工具组合起来，让智能体根据用户提示选择正确的工具。智能体会选择 grep 来匹配特定的符号或字符串，选择语义搜索来处理概念性或行为性问题，并在探索性任务中同时使用两者。</p><p>Vercel 的实验报告了相同的结果：其混合型智能体可同时访问 shell 工具和专用数据库工具，通过首先使用专用数据库工具，然后通过文档系统 grepping 验证结果，在所有测试的智能体中取得了最佳性能。这种方法在工具选择和验证的推理上消耗了更多的词元和时间。</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>，用于决定哪些工具值得被纳入。高上限工具不会限制智能体的潜力。例如，一个通用的 shell 工具允许智能体编写完整的数据库查询（包括模糊查询），但代价是推理开销、更高的延迟和更低的可靠性。</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> 工具或 shell 工具）开始。然后，复用您出于安全目的已经保留的命令日志，来跟踪智能体的实际行为，包括工具调用、重试次数以及每个用户查询的调用次数。并且，当您看到某个查询模式重复出现或执行失败时，这就是为该模式构建专用工具的信号。</p><h2>总结</h2><p>文件系统与数据库的争论分散了工程师们真正需要关注的问题：<em>智能体构建自身上下文需要哪些正确的搜索接口？</em>答案很可能是：<em>不是单一一个接口</em>。</p><p>shell 工具是一种用于与不同上下文外数据源交互的通用工具，因此是一个很好的起点。但在结构化分析查询的用例中，它的效率和准确度不如专用数据库工具。</p><p>目标是找到能够良好处理智能体实际查询模式的最小搜索工具集。从 shell 工具开始，记录智能体的实际行为。当您发现某个查询模式重复出现且执行失败时，就该为该模式设计专用工具了。</p><h2>参考资料</h2><p>1. Thariq (Anthropic). <a href="https://x.com/trq212/status/2027463795355095314">Lessons from Building Claude Code: Seeing like an Agent</a> (2026).</p><p>2. Cursor: Documentation. <a href="https://cursor.com/docs/agent/tools/search">Semantic &amp; agentic search</a> (2026).</p><p>3. Harrison Chase (LangChain). <a href="https://x.com/hwchase17/status/2011814697889316930">How we built Agent Builder’s memory system</a> (2026).</p><p>4. Ankur Goyal (Braintrust) and Andrew Qu (Vercel). <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Testing if "bash is all you need"</a> (2026).</p><p>5. Anthropic. <a href="https://www.anthropic.com/engineering/advanced-tool-use">Introducing advanced tool use on the Claude Developer Platform</a> (2025).</p><p>6. Cursor. <a href="https://cursor.com/blog/dynamic-context-discovery">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><a href="https://www.python.org/">在此处</a>下载 Python。</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 推荐生成系统的文章网站用户界面可能的外观。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20e69b9a06fecd65/6a170d6e839dfa6f97dcff58/8d3b86b212f28ff279f2da67a33e6134039f0e4e-1999x949.png" alt="具备 AI 推荐生成系统的文章网站的用户界面，列出了三个示例，文本为英语，标题为英语或西班牙语。" /><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”的模型目录，其中显示了一个名为“smollm3‑3b”的模型，具有文本生成、vLLM、GPU 1× NVIDIA L4，标价 0.8 美元，并附有一条建议将搜索范围扩展至所有 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 模型终端的接口，显示模型名称、“已由 Hugging Face 验证”注释、终端名称字段、每个运行副本每小时 0.80 美元的成本、cURL 选项和“创建终端”按钮。" /><p>从此处获取 Hugging Face 终端 URL：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25714021711ed6ff/6a170d72c1e8a54853f88336/025094ddb2cfbd1f0f216a5ec4e119b0f4fa2c42-646x328.png" alt="名为“smollm3‑3b‑pnz”的 Hugging Face 推理终端的仪表板视图，显示绿色运行状态、一个活跃副本、过去一小时内的零请求、导航选项卡和显示的终端 URL。" /><p>正如在 Elasticsearch <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">Hugging Face 推理终端文档</a>中提到的，文本生成需要一个与 OpenAI API 兼容的模型。因此，我们需要将 <code>/v1/chat/completions</code> 子路径附加到 Hugging Face 终端 URL。最终结果将如下所示：</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="标题为“推荐文章”的部分显示了五篇方框式文章摘要，包括身份验证系统漏洞、迁移风险、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[Elastic 推出了适用于谷歌 Gemini CLI 的扩展，用于在开发人员和智能体工作流中搜索、检索和分析 Elasticsearch 数据。
]]></description>
    <content:encoded><![CDATA[<p>我们很高兴地宣布， Elastic 发布了适用于 Google 的 Gemini CLI 扩展，将 <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a> 和 <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a> 的全部功能直接引入您的 AI 开发工作流。此扩展还提供几种最近开发的智能体技能，用于与 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> 是一个开源的 AI 智能体，它可将 Google 的 Gemini 模型直接引入命令行。它允许开发人员从终端与 AI 进行交互，以执行诸如生成代码、编辑文件、运行 shell 命令和从网上检索信息等任务。</p><p>与典型的聊天界面不同，Gemini CLI 可与您的本地开发环境集成，这意味着它可以直接在终端内理解项目上下文、修改文件、运行构建或测试，以及自动化工作流。这对于想要在不离开命令行工作流的情况下进行 AI 辅助编码和自动化的开发人员、网站可靠性工程师 (SREs) 和工程师来说非常有用。</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>您可以使用以下命令为 Gemini CLI 安装 Elasticsearch 扩展：</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 密钥。如果您使用 <code>start-local</code> 运行 Elasticsearch，那么您已经在 <code>start-local</code> .env 文件中拥有了 <code>ES_LOCAL_API_KEY</code>。否则，您可以使用 Kibana 界面创建 API 密钥，详见<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">此处</a>：</p><ul><li><p>在 Kibana 中：Stack Management &gt; Security &gt; API 密钥 &gt; 创建 API 密钥。</p></li><li><p>我们建议仅设置 API 密钥的读取权限，并启用 <code>feature_agentBuilder.read</code> 权限，详见<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions#grant-access-with-roles">此处</a>。</p></li><li><p>复制已编码的 API 密钥值。</p></li></ul><p>在您的 shell 中设置所需的环境变量：</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>电子商务订单</strong>数据集。它包含一个名为 <strong><code>kibana_sample_data_ecommerce</code></strong> 的单个索引，其中包含来自一家电子商务网站的 4675 个订单的信息。对于每笔订单，我们都有以下信息：</p><ul><li><p>客户信息（姓名、ID 号码、出生日期、电子邮件等）。</p></li><li><p>订单日期。</p></li><li><p>订单编号。</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>示例数据</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>您应该会看到 <strong><code>elastic-agent-builder</code></strong> 已启用，如图 2 所示：</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>该扩展还附带了 ES|QL 的<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> 是一种开放格式，为 AI 编码智能体（如 Gemini CLI）提供特定任务的自定义指令。它们使用一种称为<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> esql </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>构建一个 ES|QL 查询，比如：<code>FROM kibana_sample_data_ecommerce | STATS total_revenue = SUM(taxful_total_price) BY category.keyword | SORT total_revenue DESC | LIMIT 5</code>。</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 模型如何通过显示其遵循的所有步骤来生成最终响应。在这里，您可以清楚地看到技能对模型推理过程的影响。当模型第一次意识到需要使用某项技能或执行 shell 命令时，它会采用人机协同的方式请求权限。</p><p>通过处理模式发现、查询生成和执行的繁重工作，<code>esql</code> 技能可以让您完全专注于答案，而不是获得答案的机制。您将获得所需的数据，格式正确且直接存储在终端中，无需写一行语法或切换到其他应用。</p><h2>结论</h2><p>在本文中，我们介绍了我们最近发布的适用于 Gemini CLI 的 Elasticsearch 扩展。此扩展让您可以使用 Gemini 和 Elastic Agent Builder 提供的 Elasticsearch MCP 服务器（从 9.3.0 版本开始提供）以及 <code>/elastic</code> 命令与您的 Elasticsearch 实例进行交互。</p><p>此外，该扩展还包含一项 <code>esql</code> 技能，可以将用户的自然语言请求转换为 ES|QL 查询。这种技能在无法使用 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[Elastic Agent Skills：将您的 AI 智能体变成 Elastic 专家。]]></title>
    <description><![CDATA[让您的 AI 编码智能体通过 Elastic Agent Skills 获得知识，以实现查询、可视化、安全和自动化。]]></description>
    <content:encoded><![CDATA[<p>每一位尝试在专业平台上使用 AI 编码智能体的开发人员、站点可靠性工程师 (SRE) 或分析师都会遇到同样的问题。您要求智能体编写查询、配置警报或调查某件事，结果接近但不完全正确。Elastic 在这方面具有优势：十多年来积累的文档、博客文章和社区解答意味着 AI 智能体比大多数数据平台更了解 Elastic。但这种深度也伴随着噪音。已弃用的 API 与当前的 API 并存。过时的模式与最佳实践的排名一样高。该智能体自信地复现了三个版本前行之有效的方法，因为在其训练数据中，这种方法确实奏效了。结果是产生了纠错税：用户手动将文档输入上下文，修复虚构的语法，并绕过智能体，而不是与智能体合作。更糟糕的是，高级功能完全未被使用，不是因为用户不需要它们，而是因为智能体不知道它们的存在。</p><p>这就是为什么我们要开源 <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>智能体在专业平台上为何面临重重困难</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>代理技能：Platform 知识，专为代理人员量身定制</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-basic-query</p><p>编写了 ES|QL 管道语法以替代查询 DSL</p><p>esql-error-handling</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，刷新间隔为 “-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、筛选复杂数据以及评估服务健康状况的操作难度。将 Elastic 原生专业知识直接嵌入 AI 智能体中，可以让团队通过简单的自然语言执行复杂的可观测工作流。这使 SRE 和运营团队能够更快地解决事件，并更轻松地维护可靠的系统。阅读<a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">这篇博文</a>了解详情。</p><h2>开源、开放规范、社区驱动</h2><p>我们根据 Apache 2.0 许可协议发布 Agent Skills，因为我们认为智能体知识应该是开放的。技能所遵循的 <a href="https://agentskills.io">agentskills.io</a> 规范是一项开放标准，不是 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>对于部署在其他地方的智能体，我们正投入努力，建设开放的生态系统：</p><ul><li><p><strong>模型上下文协议 (MCP) 服务器扩展：</strong>扩展 Agent Builder 中的 <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">MCP 终端</a>，在当前搜索、ES|QL 和索引操作之外提供更多工具。</p></li><li><p><strong>身份验证改进：</strong>使代理能够更轻松地安全连接，目标是消除手动复制粘贴 API 密钥的操作。</p></li><li><p><strong>LLM 可读文档：</strong>发布 <code>llms.txt</code> 和 <code>AGENTS.md</code> 文件，让智能体能够自行发现和理解 Elastic API。</p></li><li><p><strong>用于智能体工作流的命令行接口 (CLI)：</strong>命令行工具，使连接管理和常见操作更适合智能体使用。</p></li></ul><p>技能是您今天能使用的层面。其余的功能即将到来。</p><h2>开始使用</h2><p><strong>开始之前：</strong>AI 编码智能体使用真实凭证、真实 shell 访问权限进行操作，通常还拥有运行它们的用户的全部权限。当这些智能体用于安全工作流时，风险会更高：您相当于是将检测逻辑、响应操作和敏感遥测数据的访问权限交给了一个自动化系统。每个组织的风险状况都是不同的。在启用 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>。您只需一分钟就能获得一个完全配置的环境。</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 Agent 集成中的数据收集]]></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 Web 界面安装和管理的包。</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 与其他编程语言的区别、我们针对 Filebeat 的 CEL 输入所进行的扩展，以及由此带来的数据采集逻辑表达灵活性与能力提升。</p><h2>CEL 及其在输入中的工作方式</h2><p>CEL 是一种表达式语言，它没有“语句”这一概念。在编写 CEL 时，您不是通过编写语句来指示它执行操作，而是通过编写表达式来定义要生成的值。每个 CEL 表达式都会计算出一个值，较小的表达式可以组合成更大的表达式，按照更复杂的规则生成结果。稍后，我们将了解如何通过表达式来实现其他语言中需要用语句来完成的逻辑。</p><p>CEL 有意设计为一种非图灵完备的语言，因此不支持无界循环。稍后，我们将介绍如何使用宏来处理列表和映射；正是通过禁止无界循环，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 Stack 环境中通过 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 获取 issue 数据</h2><p>以下示例是一个完整的 CEL 程序，用于从 <a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues">GitHub API</a> 获取 issue 数据。它的初始输入状态包含 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>在这个例子中，从 state 读取的计数器值需要转换为 <code>int</code>，因为状态中的所有数字都按照 JSON 和 JavaScript <code>Number</code> 类型的惯例序列化为浮点数。另外需要注意的是，Mito 会响应 <code>"want_more": true</code>，但在 CEL 输入中运行时，只有当输出中同时包含事件时，才会重复执行。</p><p>由 CEL 输入运行的 CEL 程序必须在输出映射中包含一个 <code>"events"</code> 键。它的值可以是事件映射的列表、空列表，或单个事件映射。单个事件映射通常用于表示错误。该事件会被输入发布，其值也会被记录到日志；如果设置了 <code>error.message</code>，该值还会用于更新集成在 Fleet 中的健康状态。如果程序生成的是单个非错误事件，最好将其包装在列表中。</p><p>回顾一下我们之前的 GitHub issues 程序的输出：</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 issues 示例程序应该已经比较容易阅读了。不妨用 Mito 运行一下，并尝试做些修改。</p><h2>回顾与资源</h2><p>本文介绍了 CEL 语言，以及它如何在 Mito 库中得到扩展以用于 CEL 输入。通过一个从 GitHub API 获取 issue 数据的示例程序，我们展示了 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 输入构建集成，最有价值的资源或许是现有 Elastic 集成中的 CEL 代码，这些代码可以在 GitHub 上找到：</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 集成存储库中的 cel.yml.hbs 文件 － 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，超越聊天框：介绍增强型基础架构]]></title>
    <description><![CDATA[了解具有增强型基础架构的 Elastic Agent Builder，这是一个 AI 智能体，能够实现增强型运维、增强型开发和增强型合成。]]></description>
    <content:encoded><![CDATA[<p><strong>这不是空谈。我们正在付诸实践。</strong></p><p>我们都见证了 AI 智能体的兴起。它们在总结文本、编写代码片段以及基于文档回答问题方面表现出色。但对于我们从事 DevOps 和网站可靠性工程 (SRE) 的人来说，一直存在一个令人沮丧的限制。大多数智能体都困于呼叫中心模式，这意味着它们可以阅读、思考和聊天，但无法触及它们本该管理的基础架构。</p><p>在最新的黑客马拉松项目中，我们决定打破这一限制。</p><p>我们构建了<strong>增强型基础架构</strong>：这是一个基础架构协同助手，它不仅能为您提供建议，还能创建、部署、监测和修复您的实时环境。</p><h2><strong>问题：复制、重新格式化、粘贴</strong></h2><p>标准智能体在孤立状态下运行。如果您的应用宕机，给公司造成 500 万美元的损失，标准智能体可以为您朗读如何修复的应急预案手册。但<em>您</em>仍然需要亲自动手。您只能复制代码，根据环境重新格式化，然后粘贴到终端中。</p><p>我们需要一个能理解<em>谈论</em> Kubernetes 和<em>配置</em> Kubernetes 之间区别的智能体。</p><h2><strong>引擎：什么是 Elastic Agent Builder？</strong></h2><p>我们并不是从零开始构建的。我们是基于 <a href="https://www.elastic.co/cn/elasticsearch/agent-builder"><strong>Elastic Agent Builder</strong></a> 进行构建。对于不熟悉的人来说，Elastic Agent Builder 是一个旨在快速开发智能体的框架，它充当大型语言模型 (LLM)（在我们的演示中，我们使用了 Google Gemini）与存储在 Elasticsearch 中的私有数据之间的桥梁。</p><p>Agent Builder 可以通过将 AI 与内部数据（如文档或日志）相结合，用于对话式 AI。但它最强大的功能是能够分配<strong>工具</strong>。这些工具允许 LLM 跳出聊天接口，执行特定任务。我们意识到，如果将此功能发挥到极致，我们可以将 Agent Builder 转变为一个自动化引擎。</p><h2><strong>使其运行：构建第一个版本</strong></h2><p>在项目启动之初，我们就知道要让智能体能够改变外部世界。我们当时有个想法：如果我们开发一些“运行器”软件（在主机上运行智能体能想到的任何命令）会怎样？然后：如果运行器、Elastic Agent Builder 和用户进行三方通话会怎样？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec9d20da8c41a898/6a170704dc55debd4ce00d43/8dc8317c1301b8eb7b89438529e8d8d17411c95a-1024x559.png" alt="Agent Builder with Augmented Infrastructure architecture" /><p>我们首先构建了一个 Python 项目“增强型基础架构运行器”，其本质是一个 while(true) 循环，每秒查询 Elastic Agent Builder 对话 API，并检查我们创建的特殊语法：</p>{
	"tool_name": "my_tool",
       "tool_arguments": "\{stringified json arguments\}"
}<p>然后我们更新了提示，以教会它我们新的工具调用语法。Bill 是 FastMCP 的维护者，<a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> 是在 Python 中构建模型上下文协议 (MCP) 服务器的最常用框架。他开始尝试使用 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>除了 Elasticsearch 查询语言 (ES|QL) 和索引搜索工具调用之外，Agent Builder 智能体还可以调用 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: "unhandled"

  - name: output_result
    type: console
    with:
      message: "Called tool, with execution 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>增强型基础架构运行器：</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 集群中的盲点导致 500 万美元宕机而惊慌失措的场景开始。</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>创建拉取请求并在前端服务上实现验证码。</p></li><li><p><strong>增强型运维：</strong>在宕机期间自动重新配置 DNS 解析器。</p></li></ul><h2><strong>亲自试用</strong></h2><p>我们认为，AI 的未来不仅仅是聊天支持，而是<strong>增强型基础架构</strong>。它关乎拥有一个可以与您并肩部署、修复、观测和保护的合作伙伴。</p><p>立即查看代码，并通过分布式运行器 (<a href="https://github.com/strawgate/augmented-infrastructure">GitHub</a>) 加上 <a href="https://cloud.elastic.co/">Elastic Cloud Serverless</a> 上的 Elastic Agent Builder 亲自尝试吧！</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>Alex、Bill、Gil、Graham 和 Norrie</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>我们非常高兴地宣布，Agent Builder 在 Elastic Cloud Serverless 和即将发布的 9.3 版本中正式推出。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>通过与第三方解决方案（如用于复杂文档处理的 LlamaIndex 或用于安全、结构化工具访问的 Arcade.dev）集成，扩展 Agent Builder 的功能。</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 与工具和数据连接起来非常复杂。Elastic Agent Builder 与 Arcade.dev 结合，为开发者提供了一种结构化且安全的方式来处理代理如何检索上下文、进行推理和采取行动，从而将代理从演示级别提升至生产级别。”</em></p><p>Agent Builder 还利用了 Elasticsearch 的可扩展性来处理复杂数据。正如 <strong>LlamaIndex 首席执行官 Jerry Liu</strong> 所描述的那样，</p><p><em>“从非结构化数据源中解锁企业上下文是建立有效代理的关键。Elastic Agent Builder 与 LlamaIndex 复杂文档处理相结合，强化了关键的上下文层，帮助团队检索、处理和准备数据，从而使代理能够更准确地推理并提供更好的结果。”</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>参加 <a href="https://elasticsearch.devpost.com/">Agent Builder 黑客松活动</a>，该活动将于 2026 年 1 月 22 日至 2 月 27 日举行。与社区合作，构建基于上下文的多步骤 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 和自主管理的企业层中使用。</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>语音代理领域主要有两种范式：第一种使用语音到语音模型，第二种使用由语音转文本、LLM 和文本转语音组成的语音处理流水线。语音到语音模型有其自身优势，但语音处理流水线在所用技术、上下文管理方式以及代理行为的控制方面提供了更多自定义空间。下文将重点介绍语音处理流水线模型。</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 生成。目前有多家第三方提供商提供低延迟转写服务。在选择提供商时，需要考虑延迟和转写准确性，并确保其支持流式转写。</p><p></p><p>第三方 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>轮次检测是流水线中的一个组件，用于检测说话者何时讲完，从而确定何时开始生成回复。一种常见的方法是使用语音活动检测（VAD）模型，例如 <a href="https://github.com/snakers4/silero-vad">Silero VAD</a>。VAD 利用音频能量水平来检测音频中何时包含语音以及语音何时结束。但是，单独使用 VAD 无法区分暂时停顿与真正结束发言。因此，通常会将它与句末检测模型结合使用，该模型基于临时转写结果或原始音频来判断说话者是否已经说完。</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 时，主要需要考虑两个指标：推理能力基准和首个 Token 时间（TTFT）。</p><p>推理基准反映 LLM 生成正确响应的能力水平。可以重点关注衡量多轮对话一致性和整体智能水平的基准，比如 MT-Bench 和 Humanity's Last Exam 等数据集。</p><p>TTFT 基准用于评估模型产出第一个输出 Token 的速度。还有其他类型的延迟基准，但 TTFT 对语音代理尤为重要，因为在收到第一个 Token 后就可以开始音频合成，从而降低轮次之间的延迟，让对话更自然。</p><p>通常需要在这两个指标之间做权衡，因为速度更快的模型在推理基准测试中的表现往往较差。</p><p><a href="https://huggingface.co/openai/gpt-oss-20b">示例（Hugging Face）：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>Agent Builder 作为 MCP：语音转文本 → LLM（通过 MCP 访问 Agent Builder）→ 文本转语音</p></li><li><p>Agent Builder 作为核心：语音转文本 → Agent Builder → 文本转语音</p></li></ol><p>在本项目中，我选择采用“Agent Builder 作为核心”的方案。采用这种方案，可以充分利用 Agent Builder 及其工作流的全部功能。该项目使用 LiveKit 来编排语音转文本、轮次检测和文本转语音环节，并实现了一个自定义的大型语言模型节点，直接与 Agent Builder 集成。</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 发送短信。该工作流会作为工具提供给自定义代理，使其在通话过程中即可向来电者发送短信，从而带来顺畅的使用体验。例如，这样一来，来电者就可以询问：“你能通过短信发送更多关于 <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 Builder 为 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 执行语义搜索并检索到匹配的产品。混合搜索查询会在这两个字段上执行语义匹配，并通过 boost 略微提高名称字段匹配结果的权重。</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>垃圾进，垃圾出</em>”说的就是这个道理。</p><p>本文将介绍短期记忆和长期记忆对 AI 智能体的意义，具体包括：</p><ul><li><p>短期记忆和长期记忆的区别。</p></li><li><p>它们与使用向量数据库（如 Elasticsearch）的检索增强生成 (RAG) 技术有何关联，以及为什么需要细致的记忆管理。</p></li><li><p>忽略记忆（包括上下文溢出和上下文污染）有何风险。</p></li><li><p>最佳实践，如上下文修剪、总结和仅检索相关内容，以保持代理的记忆既有用又安全。</p></li><li><p>最后，我们将探讨如何使用 Elasticsearch 在多智能体系统中共享和传播记忆，使智能体能够协作而不会产生混乱。</p></li></ul><h2>AI 智能体中的短期记忆与长期记忆</h2><p><em><strong>AI 智能体的短期记忆</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>检索增强生成 (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>将 RAG 与 Elasticsearch 或任何向量存储结合使用可带来诸多好处：</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 支持智能体处理的知识库远远大于提示所能容纳的任何内容。RAG 不是将整个文档（例如长篇研究论文或政策文件）推送到上下文窗口，从而导致信息过载或无关信息<a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-poisoning">上下文污染</a>，而是依赖于<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">《Longer context ≠ better: Why RAG still matters》</a>。这避免了“垃圾进，垃圾出”的问题：LLM 始终专注于少数重要内容，而不是应付噪音。</p><p>也就是说，将 Elasticsearch 或任何向量存储集成到 AI 智能体架构中可以提供<strong>长期记忆</strong>。智能体将知识存储在外部，并在需要时将其作为记忆上下文提取出来。这可以作为一种<em>架构</em>来实现，在每次用户查询后，智能体都会在 Elasticsearch 上搜索相关信息，然后在调用 LLM 之前将排序靠前的结果附加到提示中。如果响应包含有用的新信息，它也会保存回长期存储中（从而形成学习的反馈循环）。通过使用这种基于检索的记忆，智能体可以随时了解最新信息，无需将所有知识塞入每个提示，尽管上下文窗口支持<em>一百万个词元</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 上执行语义搜索的方法，以便在将检查点汇总并索引到 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>既然我们已经探讨了如何利用 LangGraph 的检查点在 Elasticsearch 中索引和提取短期记忆和长期记忆，接下来让我们花点时间了解为什么索引和转储完整对话可能存在风险。</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>（TL;DR，请点击<a href="https://www.dbreunig.com/2025/06/17/an-agentic-case-study-playing-pok%C3%A9mon-with-gemini.html">此处</a>查看）中观察到，一个长期运行的 <em>Pokémon</em> 游戏智能体出现了这种情况：如果智能体产生一个错误的游戏状态幻觉，并且该状态被记录到它的<em>上下文</em>（它对目标的记忆）中，那么智能体就会围绕一个不可能完成的目标形成<strong>毫无意义的策略</strong>，从而陷入困境。换句话说，受污染的记忆会让智能体无限期地走上错误的道路。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd56e9e0681f32239/6a170f3b4a531bd79536aa21/3f2facf5aad67613ad557422e09ec23a66adc0ed-1600x1388.png" alt="上下文污染" /><p>上下文污染可能是无意的（误操作），也可能是恶意的，例如通过提示注入攻击，用户或第三方偷偷输入隐藏指令或错误事实，智能体随后记住并遵循这些指令或事实。</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>人机协同：安排人员审查重要的输出或将其与已知的可信来源进行比较。</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 智能体为例，它支持百万词元级别的窗口，但当其上下文增长超过一定程度时（在实验中约为 100000 个词元），它会开始<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">《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/">OWASP 定义的LLM 应用十大风险</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/">LLM 08: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 经常被用来减少幻觉的原因：检索权威来源，模型就不必编造事实。但如果您的检索拉取了错误的文档（比如包含错误信息的文档），或者早期幻觉没有被去除，系统可能会在所有操作中传播这些错误信息。 </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>优先使用感知相关性的检索策略：top-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>，LLM 也是如此。当工具数量超过 30 个左右时，描述会开始重叠，模型会变得混乱。当工具数量超过约 100 个时，失败几乎是必然的。这不是一个上下文窗口问题，而是上下文混淆的问题。</p><p><a href="https://arxiv.org/abs/2505.03275"><strong>RAG-MCP</strong></a> 是一个简单有效的解决办法。它不是将每个工具都放入提示中，而是将工具描述存储在向量数据库中，每个请求只检索最相关的工具。在实际操作中，这样可以使装载保持小型化和专注化，大幅缩短提示，并且可以将工具选择的准确性至多提高 3 倍。</p><p>小模型甚至会更快出现这样的问题。研究表明，8B 模型在装载数十种工具时会失败，但在精简装载后会变得成功。动态选择工具（有时先由 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>强调的那样，使用 Provence 模型 (`<a href="https://huggingface.co/naver/provence-reranker-debertav3-v1">naver/provence-reranker-debertav3-v1</a>`) 可能会有所帮助。Provence 模型是一个轻量级 (1.75 GB)、高效且准确的问答上下文修剪器。它可以将大型文档裁剪为仅与给定查询最相关的文本。您可以按特定时间间隔调用它。</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 重排序模型 (`naver/provence-reranker-debertav3-v1`) 来对句子的相关性进行评分。基于阈值的过滤功能可将句子保留在相关性阈值之上。此外，我们引入了一种回退机制，如果修剪失败，我们将返回原始上下文。最后，统计日志在详细模式下跟踪减少百分比。</p><h3>上下文总结：将旧信息浓缩而非完全放弃</h3><p><em>总结</em>是对修剪的补充。当历史记录或知识库变得过于庞大时，您可以使用 LLM 生成重要内容的简短总结，并在未来使用该总结代替完整内容，就像我们在上面的代码中所做的那样。</p><p>例如，如果 AI 助手进行了 50 轮对话，系统不是在第 51 轮将全部 50 轮对话发送给模型（很可能容纳不下），而是选择第 1 至 40 轮，让模型用一段话对其总结，然后在下一个提示中只提供该总结和最后的 10 轮对话。这样一来，模型无需每个细节也能知道讨论的内容。早期的聊天机器人用户是手动总结的，他们问聊天机器人：“你能总结一下我们到目前为止谈过的内容吗？”然后带着总结继续进行新的会话。现在总结可以自动进行。总结不仅可以节省上下文窗口的空间，还可以通过去除多余的细节并只保留重要的事实来减少<strong>上下文混淆/干扰</strong>。</p><p>以下展示了我们如何使用 OpenAI 模型（您可以使用任何大型语言模型）来压缩上下文，同时保留所有相关信息，并消除冗余和重复。
</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[在 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>我们最近为 <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>安装完成后，您可以使用启动本地脚本（存储在 .env 文件中）生成的用户名 <em>elastic</em> 和密码登录 Kibana。</p><p>您可以安装 Kibana 提供的<strong>电子商务订单</strong>数据集。它包含一个名为 <strong>kibana_sample_data_ecommerce</strong> 的单个索引，其中包含来自一家电子商务网站的 4,675 个订单的信息。对于每笔订单，我们都有以下信息：</p><ul><li><p>客户信息（姓名、ID 号码、出生日期、电子邮件等）</p></li><li><p>订单日期</p></li><li><p>订单编号</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>”页面（在顶部搜索栏中搜索“集成”），然后安装“示例数据”。有关详细信息，请参阅此处的文档：<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 工具箱</h2><p>Google MCP Toolbox 是一款开源 MCP 服务器，旨在使应用程序和 AI 代理能够轻松、安全、高效地与数据库进行交互。该项目以前称为“GenAI Toolbox for Databases”，在与<a href="https://www.anthropic.com/news/model-context-protocol">模型上下文协议</a> (MCP) 完全兼容后重新命名。其目的是通过在幕后处理连接池、身份验证、可观察性和其他操作问题，消除传统上需要将代理连接到数据库的繁重工作。</p><p>Toolbox 的核心功能是允许开发人员定义可重用的高级工具，封装数据库交互操作。然后，任何兼容 MCP 的客户端（如 AI 代理）都可以调用这些工具，而无需客户端执行低级 SQL 查询或管理数据库连接。这种方法大大减少了构建数据库感知代理所需的模板代码量，只需几行应用程序逻辑就能集成高级数据操作。一旦定义工具，就可以在多个代理、框架或语言之间共享（图 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>配置适用于 Elasticsearch 的 Toolbox</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>您需要使用有效的 Elasticsearch API 密钥替换 <strong>&lt;insert-here-api-key&gt;</strong> 值。如果您使用 start-local 在本地运行 Elasticsearch，则可以在.env 文件中找到由 start-local 生成的 API 密钥，位于 <strong>ES_LOCAL_API_KEY</strong> 变量下。如果您正在使用 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，它是由 Elastic 开发的一种类似于 SQL 的查询语言，可用于在一个或多个索引中进行搜索。您可以在<a href="https://www.elastic.co/docs/reference/query-languages/esql">此处</a>的正式文档中阅读有关 ES|QL 的更多信息。</p><p>上述查询使用 <strong>?name</strong> 参数（问号表示参数）搜索存储在 <strong>kibana_sample_data_ecommerce</strong> 索引中所有包含指定客户姓名的订单。</p><p>在之前的 YAML 配置中，客户名称使用字符串类型并附带描述“客户名称”来定义。</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">使用 Elasticsearch 进行 OpenAI 函数调用</a>》。</p><h2>运行 Toolbox 服务器</h2><p>您可以使用之前的 tools.yaml 文件，通过以下命令运行 MCP 工具箱：</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> 上运行 Web 应用程序（图 2）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0fb3953fae603572/6a16fa2aa6c2b92763e794fb/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" alt="" /><p>您可以选择<strong>工具</strong> &gt; <strong>客户订单</strong>，并在参数<strong>名称</strong>（例如，Gwen Sanders）中插入客户名称。然后点击<strong>“运行工具”</strong>按钮。您应该会看到如图 3 所示的 JSON 响应。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca02df8c78cb39ff/6a16fa2c961e6909b5c4cd22/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" alt="" /><p>设置已完成，MCP Toolbox 可以执行<strong>客户订单</strong>工具与 Elasticsearch 进行通信，运行 ES|QL 查询。</p><h2>将 MCP Toolbox 与 Gemini CLI 结合使用</h2><p>我们可以使用任何 MCP 客户端与 MCP Toolbox for Databases 进行通信。例如，我们可以使用命令行工具 <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a> 来使用 Gemini。您可以按照<a href="https://geminicli.com/docs/get-started/installation/">此处</a>提供的说明安装 Gemini CLI。</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>客户订单</strong>工具的 <strong>mcp_toolbox</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 服务器请求执行客户订单工具的权限（参见图 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 工具箱 SDK</h2><p>Google MCP Toolbox 还提供一个 SDK，可用于访问用 Go、Python 和 Javascript 编写的程序中的所有功能。</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 工具箱。我们需要安装以下软件包：</p>pip install toolbox-core
pip install google-adk<p>然后使用以下命令创建一个新的代理项目：</p>adk create my_agent<p>这会创建一个名为 <strong>my_agent</strong> 的新目录，其中包含文件 <strong>agent.py</strong>。</p><p>使用以下内容更新 <strong>my_agent/agent.py</strong>，以连接到 Toolbox：</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>创建一个 <strong>.env</strong>文件，其中包含您的 Google API 密钥：</p>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' &gt; my_agent/.env<p>最后，我们可以运行代理并观察结果。要执行代理，您可以运行以下命令：</p>adk run my_agent<p>或者，您也可以通过 Web 接口提供服务：</p>adk web --port 8000<p>在这两种情况下，您都可以使用问答接口与 MCP Toolbox 进行交互。例如，您可以提出前一个问题：<em>给我客户 Gwen Sanders 的订单</em>。</p><p>有关不同 SDK 的更多信息，可以参考<a href="https://googleapis.github.io/genai-toolbox/sdks/">此文档页面</a>。</p><h2>结论</h2><p>在本文中，我们演示了 Elasticsearch 与 Google MCP Toolbox for Databases 的集成。使用简单的 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 工具箱都为使用自然语言与 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">《使用 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> 构建一个面向金融场景的 AI 搜索解决方案。</p><h2>什么是 LangGraph</h2><p><a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a> 是一个用于构建 AI 代理，并将其编排进工作流，从而打造 AI 辅助应用的框架。LangGraph 采用节点式架构，我们可以声明代表不同任务的函数，并将这些函数指定为工作流中的节点。多个节点相互作用后形成的便是一个图结构。LangGraph 是更广泛的 <a href="https://js.langchain.com/docs/introduction/">LangChain</a> 生态系统的一部分，该生态为构建模块化、可组合的 AI 系统提供了丰富的工具。</p><p>为了更直观地理解 LangGraph 有何用处，我们不妨用它来解决一个真实的业务难题。</p><h2>解决方案概述</h2><p>在一家风险投资公司中，投资人可以访问一个带有大量筛选条件的大型数据库，但一旦需要组合多重条件，查询就会变得既繁琐又缓慢。这可能会导致一些本应纳入投资视野的优质初创公司被漏掉。结果就是，团队要耗费大量时间去筛选最佳标的，甚至因此错失投资机会。</p><p>借助 LangGraph 和 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>“查找已完成 A 轮或 B 轮融资、融资额在 800 万至 2,500 万美元之间且月收入超过 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 API密钥</p></li><li><p>OpenAPI API密钥</p></li><li><p>Node 18 或更高版本</p></li></ul><h2>分步操作指南</h2><p>在本节中，我们先来看一下这个应用的外观。为此，我们将使用 <a href="https://www.typescriptlang.org/">TypeScript</a>，这是 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>该流程包括一个<a href="https://langchain-ai.github.io/langgraphjs/how-tos/branching/?h=conditional#how-to-create-branches-for-parallel-node-execution">条件分支</a>，相当于一条“if”语句，可根据用户输入决定使用投资还是市场搜索路径。这种由 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>该节点从状态中获取生成的搜索参数，首先将其发送到 Elasticsearch，使用<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-render-search-template">_render API</a>来可视化查询以便调试，然后发送请求以检索结果。</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>最后，此节点显示 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> 方法中整合起来，在名为 workflow 的变量中声明这个包含所有元素的图结构：</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>系统会从这句自然语言“查找已完成 A 轮或 B 轮、融资额在 800 万至 2,500 万美元之间且月收入高于 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>使用搜索模板比让大语言模型编写 Elasticsearch 查询能使结果更加一致，而且速度也更快</p></li><li><p>条件边是一种强大的机制，用于构建具有多个变体和分支路径的应用程序。</p></li><li><p>结构化输出在使用大型语言模型生成信息时非常有用，因为它能强制执行可预测且类型安全的响应。这不仅提高了整体可靠性，还减少了对提示词的误解。</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、Dropbox 等平台上的数据。还可以使用 MCP 服务器创建自定义连接器。</p><p>定制连接器使您能够将现有的 ChatGPT 连接器与其他数据源（如 Elasticsearch）结合，以获得全面的答案。</p><p>在本文中，我们将构建一个 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 服务器，将 ChatGPT 连接到包含内部 GitHub 问题和拉取请求信息的 Elasticsearch 索引。这样就可以使用 Elasticsearch 数据回答自然语言查询。</p><p>我们将在 Google Colab 上使用 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> 和 ngrok 部署 MCP 服务器，以获取 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> - 问题或拉取请求标题</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> - 问题或拉取请求标题</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><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">在多个字段中进行搜索</a>，并使用增强功能（标题^3、文本^2、分配人员^2）。插入符号 (^) 会乘以相关性分数，优先考虑标题中的匹配项而非内容中的匹配项。</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">适用于 ChatGPT 的 Elasticsearch MCP</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 Collab 添加敏感信息" /><h3>步骤 3：运行 Notebook</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 Collab 中运行笔记本的输出结果" /><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 迁移相关的问题&quot;并确认调用工具的操作。" /><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 识别到您需要有关特定问题的详细信息，并调用 fetch 工具，在对该工具采取任何行动前与用户确认。" /><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>在本文中，我们构建了一个自定义 MCP 服务器，使用专用的<strong>搜索</strong>和<strong>获取</strong> MCP 工具将 ChatGPT 连接到 Elasticsearch，从而实现对私有数据的自然语言查询。</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 知识，从而提高了 LLM 响应的准确性和上下文。另一方面，人工智能代理可自主运行，为实现指定目标做出决策并采取行动。</p><p>代理 RAG 是一个将检索增强生成和代理推理的优势结合在一起的框架。它将 RAG 集成到代理的决策过程中，使系统能够动态地选择数据源，改进查询以获得更好的上下文检索，生成更准确的响应，并应用反馈循环来不断提高输出质量。</p><h2>代理 RAG 的主要特点</h2><p>代理 RAG 框架标志着传统 RAG 系统的重大进步。它不再遵循固定的检索流程，而是利用能够实时规划、执行和优化结果的动态代理。</p><p>让我们来看看代理 RAG 管道的一些主要特点：</p><ul><li><p><strong>动态决策</strong>：Agentic RAG 使用推理机制来理解用户的意图，并将每个查询路由到最相关的数据源，从而生成准确且能感知上下文的响应。</p></li><li><p><strong>全面的查询分析：</strong>Agentic RAG 深入分析用户查询，包括子问题及其总体意图。它能评估查询的复杂性，并动态选择最相关的数据源来检索信息，确保准确和完整的响应。</p></li><li><p><strong>多阶段协作</strong>：该框架通过专业代理网络实现多阶段协作。每个代理处理更大目标中的特定部分，依次或同时工作，以实现协调一致的结果。</p></li><li><p><strong>自我评估机制</strong>：代理式 RAG 管道利用自我反思来评估检索到的文档和生成的回复。它可以检查检索到的信息是否完全符合查询要求，然后审查输出信息的准确性、完整性和事实一致性。</p></li><li><p><strong>与外部工具集成</strong>：该工作流程可与外部应用程序接口、数据库和实时信息源交互，纳入最新信息并动态适应不断变化的数据。</p></li></ul><h2>代理 RAG 的工作流程模式</h2><p>工作流模式定义了代理人工智能如何以可靠、高效的方式构建、管理和协调基于 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>：顺序工作流将复杂的任务划分为简单、有序的步骤。每一步都会改进下一步的输入，从而取得更好的结果。例如，在创建客户档案时，一名代理可能会从客户关系管理中获取基本信息，另一名代理可能会从交易数据库中检索购买历史记录，最后一名代理可能会将这些信息结合起来，生成一份完整的客户档案，用于推荐或报告。</p></li><li><p><strong>路由检索链</strong>：在这种工作流程模式中，路由器代理分析输入，并将其导向最合适的流程或数据源。当存在多个不同的数据源且重叠程度极低时，这种方法尤为有效。例如，在客户服务系统中，路由器代理会对收到的请求（如技术问题、退款或投诉）进行分类，并将其路由到相应的部门进行有效处理。</p></li><li><p><strong>并行检索链</strong>：在这种工作流程模式中，多个独立的子任务同时执行，然后将其输出汇总，生成最终响应。这种方法大大缩短了处理时间，提高了工作流程效率。例如，在客户服务并行工作流程中，一名代理检索过去的类似请求，另一名则查阅相关的知识库文章。然后，聚合器将这些输出合并起来，生成一份综合决议。</p></li><li><p><strong>Orchestrator 工作链</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">Jupyter 笔记本</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>从<code>langchain_elasticsearch</code> 开始使用<a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">ElasticsearchStore 模块</a>作为我们的向量存储。在检索方面，我们采用 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 chains 部分找到所有代理的说明。</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 的概念，并强调了它如何通过结合代理人工智能的自主能力来增强传统的 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[利用弹性代理生成器和 GPT-OSS 构建人力资源人工智能代理]]></title>
    <description><![CDATA[了解如何使用 Elastic Agent Builder 和 GPT-OSS 构建一个人工智能代理，回答有关员工人力资源数据的自然语言查询。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>本文将向您展示如何使用<a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a>和 Elastic Agent Builder 为人力资源部门构建人工智能代理。代理可以回答你的问题，而无需向 OpenAI、Anthropic 或任何外部服务发送数据。</p><p>我们将使用 LM Studio 在本地为 GPT-OSS 提供服务，并将其连接到 Elastic Agent Builder。</p><p>本文结束时，您将拥有一个定制的人工智能代理，可以回答有关员工数据的自然语言问题，同时保持对信息和模型的完全控制。</p><h2>准备工作</h2><p>这篇文章需要</p><ul><li><p><a href="https://www.elastic.co/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 内存的机器（GPT-OSS 20B 最低 16GB 内存）</p></li><li><p>已安装<a href="https://lmstudio.ai/">LM 工作室</a></p></li><li><p>已安装<a href="https://www.docker.com/products/docker-desktop/">Docker 桌面</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 Agent Builder 利用这种连接创建可查询员工数据的自定义人工智能代理。</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>为人力资源部门建立人工智能代理：步骤</h2><p>我们将把实施分为 5 个步骤：</p><ol><li><p>使用本地模型配置 LM 工作室</p></li><li><p>使用 Docker 部署本地弹性</p></li><li><p>在 Elastic 中创建 OpenAI 连接器</p></li><li><p>将员工数据上传到 Elasticsearch</p></li><li><p>构建并测试人工智能代理</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 部署本地弹性</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> Agent Builder </strong>并激活 Agent Builder。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a934bd99fa6a0ce/6a170d046234e019c3db1a5a/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" alt="" /><h2>第 3 步：在 Elastic 中创建 OpenAI 连接器</h2><p>现在，我们将配置 Elastic 以使用本地 LLM。</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，因此与 OpenAI 兼容。</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">人力资源员工数据集</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 的 Dev Tools 中并执行：</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 步：构建并测试人工智能代理</h2><p>一切配置完成后，就可以使用 Elastic Agent Builder 创建自定义人工智能代理了。有关详细信息，请参阅<a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Elastic 文档</a>。</p><h3>添加连接器</h3><p>在创建新代理之前，我们必须将代理生成器设置为使用名为<code>LM Studio - GPT-OSS 20B</code> 的自定义连接器，因为默认连接器是<a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>。为此，我们需要进入 "<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>代理编号</strong>是：

Agent ID： <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.生成适当的 Elasticsearch 查询（使用内置工具或自定义<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL）</a></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 的代理生成器（Agent Builder）构建了一个自定义人工智能代理，以连接到本地运行的 OpenAI GPT-OSS 模型。通过在本地机器上部署 Elastic 和 LLM，这种架构可以让您利用生成式人工智能功能，同时保持对数据的完全控制，而无需向外部服务发送信息。</p><p>我们使用 GPT-OSS 20B 作为实验，但<a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">此处</a>参考了官方推荐的 Elastic Agent Builder 模型。如果您需要更高级的推理能力，还可以选择<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[来自 Cal Hacks 12.0 的顶级弹性代理生成器项目和学习成果]]></title>
    <description><![CDATA[探索 Cal Hacks 12.0 中的顶级 Elastic Agent Builder 项目，深入了解我们在无服务器、ES|QL 和代理架构方面的技术要点。]]></description>
    <content:encoded><![CDATA[<p>几周前，我们有幸赞助了<a href="https://cal-hacks-12-0.devpost.com/">Cal Hacks 12.0</a>，这是规模最大的个人黑客马拉松之一，有来自世界各地的 2000 多名参赛者。我们为在 Serverless 上最佳使用 Elastic Agent Builder 设立了专门的奖项，反响非常好。在短短 36 小时内，我们就收到了 29 份以创造性方式使用 Agent Builder 的提交，其中包括构建野火情报工具和 StackOverflow 验证器。</p><p>除了令人印象深刻的项目之外，Cal Hacks 12.0 还为我们带来了同样宝贵的经验：首次接触我们 Stack 的开发人员提供了快速、未经过滤的反馈。黑客马拉松是一种独特的压力测试，时间紧迫，事先完全不熟悉，还有不可预知的障碍（比如臭名昭著的 WiFi 中断）。它们准确地揭示了开发人员体验的闪光点和仍需改进的地方。随着开发人员越来越多地通过 LLM 驱动的工作流，以新的方式与 Elastic Stack 进行交互，这一点现在变得更加重要。在这篇博文中，我们将深入探讨参与者使用 Agent Builder 构建的内容，以及我们在此过程中学到的东西。</p><h2>获奖项目</h2><h3>第一名AgentOverflow</h3><p>为 LLM 和代理时代重建的 Stack Overflow。</p><p><a href="https://devpost.com/software/agentoverflow">点击此处</a>了解有关 AgentOverflow 的更多信息。</p><p>AgentOverflow 解决了大多数人工智能开发人员遇到的问题：LLM 会产生幻觉，聊天记录会消失，开发人员会浪费时间重新解决同样的问题。</p><p>AgentOverflow 可以捕捉、验证和重新浮现真实的问题-解决方案对，因此开发人员可以打破幻觉漩涡，更快地完成开发。</p><h4>如何使用</h4><p><strong>1.共享 JSON--"解决方案模式"。</strong></p><p>从克劳德共享中点击一下，就能刮取、提取并组装一个共享解决方案 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="用户通过网络前端提供额外的上下文，然后在 Elasticsearch 中对 JSON 进行索引" /><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>AgentOverflow 使用 Agent Builder 和 Elasticsearch 作为结构化内存层，将相关上下文注入 LLM。这就使它们从被动的聊天机器人转变为能感知上下文的问题解决者。</p><h3>亚军MarketMind</h3><p>由六个弹性代理提供支持的可实时解释的市场能量视图。</p><p><a href="https://devpost.com/software/marketmind-b6cy2q">点击此处</a>了解有关 MarketMind 的更多信息。</p><p>MarketMind 通过为新手交易者提供一个平台，将零散的市场数据转换成清晰的实时信号，赢得了自己的一席之地。MarketMind 将所有这些信息整合到一个平台中，帮助交易者获得可操作的洞察力，而不是在不同的工具中纠缠价格走势、基本面、情绪和波动性。该项目在构建代理时还使用了一些复杂的 ES|QL 查询。</p><h4>如何使用</h4><p><strong>1.收集实时市场数据</strong></p><p>MarketMind 从雅虎财经中提取价格-行动、基本面、情绪、波动性和风险指标。这些数据被摄取并组织到多个 Elasticsearch 索引中。</p><p><strong>2.六家专业代理商分析市场</strong></p><p>使用 Agent Builder 创建的每个代理都专注于不同的市场层。它们从 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 专门代理商的统一 &quot;市场能量 &quot;模式" /><p><strong>4.可视化洞察力</strong></p><p>前端采用 React 和<a href="https://github.com/vercel/next.js"> Next.js</a> ，使用 TypeScript、SVG 物理视觉效果和<a href="https://github.com/chartjs"> Chart. 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>法律硕士擅长 </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> 。错误信息让他们追逐 Serverless 上不存在的命令。我们将这一情况反映给了产品团队，他们立即启动了一个针对无服务器的可执行消息的修复程序。从长远来看，我们的目标是完全隐藏重新索引的复杂性<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 个小时内，我们看到各个团队开始使用 Agent Builder，将数据导入 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 代理新闻室：第二部分]]></title>
    <description><![CDATA[了解如何使用 A2A 协议（用于代理协作）和 MCP（用于 Elasticsearch 中的工具访问）建立专门的混合 LLM 代理新闻室。]]></description>
    <content:encoded><![CDATA[<h2>A2A 和 MCP：行动守则</h2><p>本文是 "在 Elasticsearch 中使用 A2A 协议和 MCP 创建 LLM 代理新闻室！"一文的配套文章，该文章介绍了在同一个代理中同时实施 A2A 和 MCP 架构的好处，以真正获得这两种框架的独特优势。如果您希望自行运行演示，我们还提供了一个<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 的弹性 A2A 代理</h3><p><strong>存档代理</strong>使用 Elastic 的 A2A 代理，而 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 Agent 如何集成到新闻编辑室的工作流程中。Archive Agent（新闻编辑室专用代理）与 Elastic 的 A2A Agent（第三方专家）协调，以利用 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>至</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 使用人类学来回答问题</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>查询 news_archive 索引</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>220</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>将文章索引到 news_archive 索引</p><p>27</p><p>出版商</p><p>文件系统</p><p>保存标记</p><p>文件输入/输出</p><p>将文章保存为 .md文件在 /articles</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>作为开发人员或架构师，您需要测试并确定这两种解决方案的最佳组合，从而为您的特定用例创造正确的结果。了解每种方法的优势、局限性和适当应用，将使您能够构建更有效、可维护和可扩展的人工智能系统。</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[你懂的，语境--第三部分：混合搜索在语境工程中的威力]]></title>
    <description><![CDATA[了解如何利用上下文工程和混合搜索，通过聚合、RBAC 和非内容信号来提高人工智能输出的准确性。]]></description>
    <content:encoded><![CDATA[<p>我们已经讨论了混合搜索<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">（第一部分</a>）和上下文工程<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">（第二部分</a>）；现在，让我们深入探讨它们如何协同工作，为 RAG 和代理人工智能操作提供有针对性的上下文，以达到最佳效果。</p><h2>搜索并未消亡，只是转移了位置</h2><p>因此，我们已经从主要通过文本框搜索上下文，然后使用返回的信息（上下文）自己构建答案，转变为现在使用自然语言告诉代理我们想要什么，然后让它自动研究并为我们编译答案。科技界的许多人都指出了这一转变，并宣称 "搜索已死"（搜索引擎优化和广告词的世界<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/"> GEO</a> 谁知道？</p><p>以前，人类是主观相关性的主要仲裁者：每个用户都有自己进行搜索的理由，他们的个人经验会影响搜索结果的相对准确性。如果我们要相信代理能得出与我们相同（或更好）的结论，我们就必须确保代理能获得的上下文信息尽可能接近我们的主观意图。为了实现这一目标，我们必须设计为法律硕士提供的环境！</p><h2>利用混合搜索检索生成上下文</h2><p>在此提醒大家，Elastic 的混合搜索结合了传统基于关键字搜索的优势（语法灵活性、关键字精确度和相关性评分）和向量相似性搜索的语义理解，并提供了多种重排技术。这种协同作用（这个词从未有过如此真实的用法）这样就能获得高度相关的结果，查询内容的针对性也会更加细致。这不仅仅是说你可以将主观相关性作为检索阶段<em>之一</em>，而是说第一阶段检索可以同时包括相关性评分和所有其他模式。</p><h3>卓越的精度&amp; 效率</h3><p>使用可提供分布式搜索、检索和重新排序的数据平台作为主要的上下文检索引擎非常有意义。您可以使用高级查询语法来添加主观意图的缺失部分，并过滤掉可能干扰或混淆所返回的上下文信息价值的内容。您可以从任何可用的单独语法选项中进行选择，也可以将各种模式组合成一个单一的搜索，以其最能理解的方式针对每种类型的数据进行搜索，然后通过重新排序对其进行组合/重新排序。您可以对响应进行过滤，使其只包含您想要的字段/值，从而避免无关数据。在为代理提供服务时，这种目标定位的灵活性可让您构建的工具在检索上下文时极为准确。</p><h3>语境细化（聚合和非内容信号）</h3><p>聚合在塑造工具向上下文窗口提供的内容方面特别有用。聚合自然会提供有关返回的上下文数据形状的基于数字的事实，这使得 LLM 的推理更容易、更准确。由于聚合可以分层嵌套，因此很容易为 LLM 增加多层次的细节，从而产生更细致入微的理解。聚合还有助于管理上下文窗口的大小--您可以轻松地将 10 万个文档的查询结果减少到几百个聚合洞察的标记。</p><p>非内容信号是数据中的固有指标，它们能告诉你所查看内容的全貌；它们是结果的附加特征，如受欢迎程度、新鲜度、地理位置、类别、主机多样性或价格带。这些信息可以为代理如何权衡所接收到的上下文的重要性提供有用信息。一些简单的例子也许最能说明这一点：</p><ul><li><p><strong>提升近期发布的热门内容</strong>--想象一下，您有一个文章知识库。您希望找到与用户查询相关的文章，但同时也希望推广那些最近发表的、对其他用户有帮助的文章（例如，具有较高"likes" 数量的文章）。在这种情况下，我们可以使用混合搜索来查找相关文章，然后根据文章的发表日期和受欢迎程度对其进行排序。</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），确保向代理和生成式人工智能操作提供的上下文尊重并保护敏感的私人信息。这意味着不仅能高效处理查询，还能根据代理或发起请求的用户的特定权限对结果进行过滤。</p><p>代理以认证用户的身份运行，因此通过平台内置的安全功能隐式地应用了安全功能：</p><ul><li><p><strong>细粒度权限：</strong>在文档、字段甚至术语级别定义访问权限，确保人工智能代理只接收他们有权查看的数据。</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>与现有身份管理系统（如 LDAP、SAML、OIDC）无缝集成，在整个组织内执行一致的安全策略。</p></li></ul><p>通过将这些安全措施直接集成到上下文检索机制中，Elastic 成为了一个安全的看门人，确保人工智能代理在定义的数据边界内运行，防止未经授权的数据暴露，并维护数据隐私法规的合规性。这对于在处理机密或专有信息的人工智能代理系统中建立信任至关重要。</p><p>此外，通过在企业数据源上使用统一的数据速度层，还可以减轻代理工具在这些资源库上产生的意外临时查询负载。您只需在一个地方就能近乎实时地搜索所有内容，并在一个地方应用安全和治理控制。</p><h2>基于搜索的混合工具</h2><p>Elastic 平台的一些核心功能（<a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">更多</a>功能将陆续推出）能极大地促进情境工程的发展。这里最主要的是，该平台提供了多种实现方法，随着人工智能生态系统的发展，可以灵活地调整、改变和扩展方法。</p><h3>代理生成器介绍</h3><p>Elastic<a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder</a>是我们在代理式人工智能工具领域的首次尝试，该工具可与您已存储在 Elastic 中的数据聊天。Agent Builder 提供了一个聊天界面，使用户能够在 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 分支都包含了 EVAL 子句，但<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"> EVAL 子句并不是绝对必要的；包含 EVAL 子句只是为了演示如何跟踪给定结果是从哪种搜索模式返回的。</a></p><h3>搜索模板</h3><p>假设您想将自己的外部代理工具指向 Elastic 部署。您希望使用多级检索器或重新使用已开发的现有 DSL 语法，而不是 ES|QL，还希望能够控制查询接受的输入、执行搜索时使用的语法以及输出中返回的字段。<a href="https://www.elastic.co/docs/solutions/search/search-templates">搜索模板</a>允许用户为常用搜索模式定义预定义结构，从而提高检索数据的效率和一致性。这对与搜索应用程序接口交互的代理工具尤其有利，因为它们有助于规范模板代码，加快搜索逻辑的迭代速度。如果您需要调整其中任何一个因素，只需更新搜索模板，就可以实现更改。如果您正在寻找搜索模板与代理工具配合使用的示例，可以看看 Elasticsearch 实验室的博客 "<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent</a>search"，它在来自外部 MCP 服务器的工具调用背后使用了搜索模板。</p><h3>集成工作流程（FTW!）</h3><p>在我们新的代理人工智能世界中，最难驾驭的事情之一就是半自主、自导自演的 "推理 "代理的非确定性。情境工程是代理人工智能的一门关键学科：这些技术有助于将我们的代理可能得出的结论缩小到我们所知道的基本事实。即使有了高度准确和相关的上下文窗口（当我们跳出数字事实的范畴时），我们仍然缺少一点保证，即代理的反应是完全可重复和可靠的。</p><p>当您多次向代理运行同一个请求时，得到的答案可能<em>基本相同</em>，<em>只是</em>在响应上有那么一点点差别。对于简单的查询来说，这通常没什么问题，也许几乎不会引起注意，我们可以尝试使用上下文工程技术来塑造输出。但是，随着我们要求代理完成的任务变得越来越复杂，一个或多个子任务就更有可能带来差异，从而稍微改变最终结果。随着我们开始更多地依赖代理与代理之间的通信，这种情况可能会变得更糟，而这些差异也会累积起来。这再次说明，与我们的代理互动的工具需要非常灵活，并可进行调整，以精确瞄准上下文数据，而且它们应该以预期的输出格式做出响应。这也表明，在许多使用案例中，我们需要指导代理和工具之间的交互--这就是工作流的作用所在！</p><p>Elastic 将很快在平台核心中内置完全可定制的工作流程。这些工作流程将能以双向方式与代理和工具一起运行，因此工作流程将能呼叫代理和工具，代理和工具也能呼叫工作流程。将这些功能完全集成到同一搜索人工智能平台中，您的所有数据都将在该平台中存活，这将是一场变革，工作流程的潜力令人无比振奋！很快，很快就会到来！</p><h3>作为统一记忆库的弹性</h3><p>Elastic 是一个分布式数据平台，专为近乎实时的搜索而设计，因此能自然而然地为代理型人工智能系统提供长期记忆功能。通过内置的 Agent Builder 聊天体验，我们还可以跟踪和管理短期记忆和聊天记录。由于整个平台以 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>大型语言模型的出现改变了我们匹配内容的方式，也改变了我们查询数据的方法。在我们的世界里，人类正在迅速地从研究、背景考虑和逻辑推理来回答自己的问题，转变为这些步骤在很大程度上通过代理人工智能实现自动化。为了让我们相信所收到的生成答案，我们需要确保代理在生成答案时考虑了<em>所有</em> <em>最相关的</em>信息（包括主观相关性因素）。我们使代理人工智能值得信赖的主要方法是，通过 RAG 和上下文工程技术将检索额外上下文的工具落地，但这些工具如何进行<em>初始检索</em>对响应的准确性至关重要。</p><p>Elastic Search 人工智能平台提供了混合搜索的灵活性和优势，同时还提供了多项内置功能，有助于代理式人工智能的准确性、性能和可扩展性；换句话说，Elastic 是语境工程多个方面的绝佳平台！通过搜索平台将上下文检索标准化，我们在多个方面简化了代理工具的操作--与 "放慢速度才能更快 "的矛盾论类似，上下文生成层的简化意味着代理人工智能更快、更可信。</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[你知道的，为了情境--第二部分：代理人工智能和情境工程的必要性]]></title>
    <description><![CDATA[了解 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）和代理式人工智能的工作方式与传统搜索不同。过去，我们开始研究信息的方式是搜索（"让我谷歌一下......"），而基因人工智能和代理的发起行动通常是通过在聊天界面输入自然语言。聊天界面是与 LLM 的讨论，LLM 利用其语义理解能力将我们的问题转化为经过提炼的答案，这种经过总结的回答似乎来自一个对各种信息都有广泛了解的神谕。真正的卖点在于，法学硕士能够产生连贯、深思熟虑的句子，将浮现的知识点串联起来--即使不准确或完全是幻觉，也有其<a href="https://en.wikipedia.org/wiki/Truthiness">真实性</a>。</p><p>我们习惯于使用的老式搜索栏，可以看作是我们<em><strong>自己</strong></em>作为推理代理时使用的 RAG 引擎。现在，即使是互联网搜索引擎也正在将我们习以为常的 "猎取和啄食 "词条搜索体验转变为人工智能驱动的概述，通过对结果的总结来回答查询，帮助用户避免自己点击和评估单个结果。</p><h2>生成式人工智能&amp; RAG</h2><p>生成式人工智能试图利用其对世界的语义理解来解析聊天请求中表达的主观意图，然后利用其推理能力即时创建专家答案。生成式人工智能交互由几个部分组成：首先是用户的输入/询问，聊天会话中之前的对话可用作额外的上下文，然后是指导性提示，告诉 LLM 如何推理以及在构建回复时应遵循哪些程序。提示已从简单的""像五岁小孩一样解释给我听 "类型的指导发展到如何处理请求的完整细分。这些细目通常包括不同的部分，详细描述人工智能的角色/作用、生成前的推理/内部思维过程、客观标准、限制条件、输出格式、受众，以及有助于展示预期结果的示例。</p><p>除了用户查询和系统提示外，检索增强生成（RAG）还在所谓的 "上下文窗口 "中提供额外的上下文信息。RAG 是该架构的重要补充；我们用它来告知 LLM 在其对世界的语义理解中缺失的部分。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfa000ccfdd9d184/6a17ddb57b54f955f38b37da/5b9671d5d07d4caefde372bb3188000754a91eed-1470x746.png" alt="本地语言管理器如何处理用户查询和创建上下文" /><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>就像我们人类一样，上下文窗口中过多的信息会让法律硕士不知所措，不知道哪些是重要部分。上下文溢出（或 "<a href="https://research.trychroma.com/context-rot">上下文腐烂</a>"）会影响生成式人工智能操作的质量和性能；它会极大地影响 LLM 的 "注意力预算"（其工作记忆），并稀释许多竞争标记的相关性。语境轮换 "的概念还包括这样一个观察结果，即语言学习者往往有一种<a href="https://alexandrabarr.beehiiv.com/p/context-windows">位置偏差</a>--他们更喜欢语境窗口开头或结尾的内容，而不是中间部分的内容。</p><h3>分散注意力或相互冲突的信息</h3><p>上下文窗口越大，就越有可能包含多余或相互冲突的信息，从而分散 LLM 的注意力，使其无法选择和处理正确的上下文。在某种程度上，这就成了一个 "垃圾进/垃圾出 "的问题：只需将一组文档结果倒入上下文窗口，就能为 LLM 提供大量信息供其咀嚼（可能太多），但根据上下文的选择方式，更有可能渗入相互冲突或无关的信息。</p><h2>智能体 AI</h2><p>我告诉过你有很多内容要讲，但我们做到了--我们终于开始讨论代理人工智能话题了！代理式人工智能（Agentic AI）是 LLM 聊天界面的一种非常令人兴奋的新用法，它扩展了生成式人工智能（我们可以称之为 "传统 "人工智能吗？）的能力，即根据自身知识和您提供的上下文信息合成回复。随着生成式人工智能变得越来越成熟，我们意识到可以让 LLM 执行一定程度的任务和自动化操作，这些操作最初被归类为乏味的低风险活动，可以很容易地由人工进行检查/验证。在很短的时间内，最初的范围就扩大了：一个 LLM 聊天窗口现在可以成为一个火花，让一个人工智能代理去自主规划、执行、迭代评估和调整其计划，以实现指定的目标。代理可以访问其 LLM 自身的推理、聊天历史和思维记忆（比如说），他们还可以利用特定的工具来实现这一目标。我们现在看到的架构还允许一个顶级代理作为多个<a href="https://www.philschmid.de/the-rise-of-subagents"> 子代理</a> 的协调者，每个 子代理 都有自己的逻辑链、指令集、上下文和工具。</p><p>代理是大部分自动化工作流程的切入点：它们是自主的，能够与用户聊天，然后使用 "逻辑 "来决定有哪些工具可以帮助回答用户的问题。与代理相比，工具通常被认为是被动的，是为完成一种任务而构建的。工具可以执行的任务<em>类型</em>是无限的（这确实令人兴奋！），但工具执行的一项主要任务是收集上下文信息，供代理在执行工作流程时考虑。</p><p>作为一项技术，代理人工智能仍处于起步阶段，很容易患上法学硕士的注意力缺陷症--很容易忘记要求它做的事情，经常跑去做其他根本不在任务范围内的事情。在表面神奇的背后，LLM 的 "推理 "能力仍然是基于预测序列中下一个最有可能的标记。要使推理（或有朝一日的人工通用智能（AGI））变得可靠和值得信赖，我们需要能够验证，在获得正确、最新的信息时，它们会按照我们所期望的方式进行推理（也许还会给我们提供我们自己可能没有想到的更多信息）。要做到这一点，代理架构需要具备清晰的通信能力（协议），遵守我们赋予它们的工作流程和约束条件（护栏），记住它们在任务中的位置（状态），管理可用的内存空间，以及验证它们的响应是否准确并符合任务标准。</p><h2>用我能听懂的语言跟我说话</h2><p>在新的开发领域（尤其是在 LLM 领域），代理与工具之间的通信最初有很多方法，但很快就趋同于<a href="https://modelcontextprotocol.io/docs/getting-started/intro">模型上下文协议（MCP）</a>，将其作为事实上的标准。模型上下文协议的定义其实就在名字里--它是<strong> 模型</strong> 用来请求和接收 <strong>上下文</strong> 信息的<strong> 协议 。</strong>MCP 是 LLM 代理连接外部工具和数据源的通用适配器；它简化了应用程序接口并使之标准化，这样不同的 LLM 框架和工具就能轻松互操作。这就使得 MCP 成为一种支点，它介于协调逻辑和系统提示与发送给工具的操作之间，前者要求代理为实现其目标而自主执行，而后者则要求代理以更孤立的方式执行（至少与启动代理隔离）。</p><p>这个生态系统是如此之新，以至于每个扩展方向都像是一个新领域。我们有类似的协议用于代理与代理之间的交互 （Agent2Agent<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/"> (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/"> Guardrails</a> 。</p><p>您可能已经注意到，这些项目的根本目的都是为了提高返回到代理/人工智能上下文窗口的信息的质量和控制？虽然人工智能代理生态系统将继续发展更好地处理上下文信息（对其进行控制、管理和操作）的能力，但始终需要检索<em>最相关的</em>上下文信息，作为代理的研磨材料。</p><h2>欢迎使用情境工程！</h2><p>如果你熟悉生成式人工智能术语，你可能听说过 "提示工程"--在这一点上，它几乎是一门伪科学。提示工程用于找到最佳和最有效的方法，主动描述您希望 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="法律硕士的语境工程" /><p>LLM 聊天应用程序的各种历时直接（有时以重叠的方式）映射到上下文工程的这些高级功能：</p><ul><li><p><strong>指令/系统提示</strong>--提示是生成式（或代理式）人工智能活动如何引导其思维实现用户目标的支架。提示本身就是一种语境；它们不仅仅是音调指令，还经常包含任务执行逻辑和规则，如 "逐步思考 "或 "深呼吸"，然后再做出回应，以验证答案是否完全满足用户的要求。最近的测试表明，标记语言在框定提示的不同部分时非常有效，但也要注意在过于模糊和过于具体之间调整指示；我们希望提供足够的指示，让 LLM 找到正确的上下文，但又不能过于规范，以至于错过意想不到的见解。</p></li><li><p><strong>短期记忆</strong>（状态/历史）--短期记忆主要是用户与 LLM 之间的聊天会话互动。这些信息有助于在现场会议中完善上下文，并可保存起来供今后检索和继续使用。 </p></li><li><p><strong>长时记忆</strong>--长时记忆应包含在多个时段都有用的信息。通过 RAG 访问的不仅仅是特定领域的知识库，最近的研究还利用以前的代理/生成式人工智能请求的结果，在当前的代理互动中进行学习和参考。在长期记忆领域，一些最有趣的创新与调整状态的<a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a">存储和链接</a>方式有关，这样，代理就能从他们离开的地方继续前进。 </p></li><li><p><strong>结构化输出</strong>--认知需要花费精力，因此，即使拥有推理能力，LLM（就像人类一样）也希望在思考时花费更少的精力，这一点不足为奇。在没有定义好的应用程序接口或协议的情况下，有一个如何读取工具调用返回数据的地图（模式）是非常有用的。将 "<a href="https://platform.openai.com/docs/guides/structured-outputs?lang=javascript">结构化输出 "</a>作为代理框架的一部分，有助于使这些机器与机器之间的交互更快、更可靠，同时减少思维驱动的解析。</p></li><li><p><strong>可用工具</strong>- 工具可以做各种各样的事情，从收集额外信息（如向企业数据存储库或通过在线 API 发出 RAG 查询）到代表代理执行自动操作（如根据代理请求的标准预订酒店房间）。工具也可以是子代理，有自己的代理处理链。 </p></li><li><p><strong>检索增强生成（RAG）</strong>--我非常喜欢将 RAG 描述为 "动态知识集成"。如前所述，RAG 是一种提供 LLM 在接受训练时无法获得的额外信息的技术，或者说是重申我们认为对获得正确答案最重要的想法--与我们的主观疑问最相关的想法。</p></li></ul><h2>惊人的宇宙力量，微不足道的生活空间！</h2><p>代理人工智能有许多迷人而令人兴奋的新领域有待探索！我们仍有许多传统的数据检索和处理问题需要解决，但同时也面临着全新的挑战，这些挑战现在才在新的 LLM 时代暴露出来。我们今天要解决的许多紧迫问题都与情境工程有关，即如何在不占用有限工作记忆空间的前提下，为 LLM 提供所需的额外情境信息。</p><p>半自主代理可以使用一系列工具（和其他代理），其灵活性为人工智能的实施带来了许多新思路，我们很难想象会有什么不同的方法可以将这些碎片组合在一起。目前的大部分研究都属于上下文工程学领域，主要集中在构建能够处理和跟踪大量上下文的内存管理结构上，这是因为我们真正希望 LLM 能够解决的深度思考问题具有更高的复杂性和更长的多阶段思考步骤，在这些问题中，记忆极为重要。</p><p>该领域正在进行的许多实验都是为了找到最佳的任务管理和工具配置，以满足代理的需求。代理推理链中的每次工具调用都会产生累积成本，既包括执行工具功能所需的计算量，也包括对有限上下文窗口的影响。为 LLM 代理管理上下文的一些最新技术造成了意想不到的连锁效应，如 "<a href="https://venturebeat.com/ai/ace-prevents-context-collapse-with-evolving-playbooks-for-self-improving-ai">上下文崩溃</a>"，在这种情况下，压缩/汇总长期运行任务的累积上下文会造成<em>过多</em>损失。理想的结果是工具能够返回简洁准确的上下文，而不会让无关信息渗入宝贵的上下文窗口内存空间。</p><h3>太多/太多种可能性</h3><p>我们希望职责分离，并能灵活地重复使用工具/组件，因此创建专用的代理工具来连接特定的数据源是完全合理的--每种工具都可以专门查询一种类型的存储库、一种类型的数据流，甚至一种使用案例。但要注意：为了节省时间/金钱/证明某些事情是可行的，我们会受到强烈的诱惑，把 LLM 用作联盟工具......尽量不要这样做，我们以前<a href="https://www.elastic.co/pdf/elastic-distributed-not-federated-search.pdf">走过这条路</a>！联合查询就像一个 "通用翻译器"，它将输入的查询转换成远程存储库能理解的语法，然后以某种方式将多个来源的结果合理化为一个连贯的响应。联盟作为一种技术，在小范围内<em>效果</em> <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>，以及聊天窗口如何逐渐成为人工智能代理体验。让我们把这两个主题放在一起，看看如何利用新式搜索和检索功能来改进上下文工程的结果。进入<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy">第三部分：混合搜索在情境工程中的威力</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 Agent 新闻室：第一部分]]></title>
    <description><![CDATA[在一个实际的新闻编辑室案例中探索 A2A 协议和 MCP 的概念，在这个案例中，专门的 LLM 代理合作研究、撰写、编辑和发布新闻文章。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>当前由 LLM 支持的系统正在迅速发展，超越了单一模型应用，成为复杂的网络，其中专门的代理共同完成现代计算前所未有的任务。随着这些系统的复杂性不断增加，使代理通信和工具访问成为可能的基础设施成为开发的重点。为满足这些需求，出现了两种互补的方法：用于多代理协调的<strong>代理2代理（A2A）</strong>协议，以及用于标准化工具和资源访问的<strong>模型上下文协议（MCP）</strong>。</p><p>了解在什么情况下可以同时使用和不使用这两种方法，会对应用程序的可扩展性、可维护性和有效性产生重大影响。本文以数字新闻编辑室为例，探讨了<strong>A2A</strong>的概念和实现方法，在数字新闻编辑室中，专门的 LLM 代理合作研究、撰写、编辑和发布新闻文章。</p><p>我们将<a href="https://github.com/justincastilla/elastic-newsroom/tree/main">在</a>文章最后的第 5 部分探讨 A2A 的具体应用实例。</p><h3>准备工作</h3><p><a href="https://github.com/justincastilla/elastic-newsroom/tree/main">资源库</a>由 A2A 代理的 Python 实现组成。Flask 提供了一个 API 服务器，以及一个名为 Event Hub 的自定义 Python 消息传递服务，用于路由日志和 UI 更新消息。最后，还提供了一个 React UI，用于独立使用新闻编辑室的功能。所有内容都包含在一个 Docker 镜像中，以便于实施。如果您想直接在机器上运行服务，则需要确保安装了这些技术：</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 - Agent 协调与通信</p></li><li><p>Anthropic SDK--克劳德集成人工智能生成器</p></li><li><p>Uvicorn - 用于运行代理的 ASGI 服务器</p></li><li><p>FastMCP 2.12.5+ - MCP 服务器实施</p></li><li><p>React 18.2 - 前端用户界面框架</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 部分：什么是 Agent2Agent（A2A）？</h2><h3>定义和核心概念</h3><p>Agent2Agent(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>Agent2Agent 通信或多代理系统的概念源于<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 的出现和运行成本的降低，多代理系统开始进入 "专业消费者 "市场。现在，A2A 协议被称为 Agent2Agent 系统，它已发展成为一个现代标准，专为多个大型语言模型协调工作和任务的时代而设计。</p><p>A2A 协议将一致的标准和原则应用于 LLM 连接和通信的交互点，从而确保代理之间的无缝通信和协调。这种标准化使来自不同开发商、使用不同底层模型的代理能够有效地协同工作。</p><p>通信协议并非新生事物，在互联网上进行的几乎所有数字交易中都有广泛的应用。如果您键入<a href="https://www.elastic.co/search-labs">https://www.elastic.co/search-labs</a>在浏览器中访问这篇文章时，很有可能 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>试想一个由人工智能代理驱动的数字新闻编辑室，每个代理都擅长新闻业的不同方面：</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>：对文章的质量、风格和搜索引擎优化进行审核</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> 数组列举了该代理提供的具体功能，包括强大的搜索功能和智能索引探索。每种技能都定义了它所支持的输入和输出模式，使客户能够准确了解如何与该代理进行通信。该代理源于 Elastic 的代理生成器服务，它提供了一套本地 LLM 支持的工具和 API 端点，用于与数据存储对话，而不仅仅是从存储中检索。可<a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">在此处</a>访问 Elasticsearch 中的 A2A 代理。</p><h4>2.谈判</h4><p>客户和代理需要就交流方式达成一致--无论互动是通过文本、表单、iframe 还是音频/视频进行，以确保适当的用户互动和数据交换。这种协商发生在代理合作的开始阶段，并确立了整个工作流程中的交互协议。例如，语音客户服务代理可能会协商通过音频流进行通信，而数据分析代理可能更喜欢结构化的 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>分布式点对点通信</h3><p>A2A 实现了分布式通信，其中代理可能由不同的组织托管，一些代理由内部维护，另一些则由第三方服务提供。这些代理可以在不同的基础设施上运行，可能跨越多个云提供商或内部数据中心。它们可能使用不同的底层 LLM，一些代理采用 GPT 模型，另一些采用 Claude 模型，还有一些采用开源替代模型。代理甚至可以跨越不同的地理区域运行，以符合数据主权要求或减少延迟。尽管存在这种多样性，但所有代理都同意使用共同的通信协议来交换信息，从而确保了互操作性，而不管实施细节如何。这种分布式架构为系统的构建和部署提供了灵活性，使企业能够根据自身的具体需求，混合和匹配最佳的代理和基础设施。</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）是 Anthropic 开发的一种标准化协议，旨在通过用户定义的工具、资源和提示，以及其他补充代码库增添的内容，来增强单个 LLM 的功能和能力。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 服务器以访问其功能的应用程序（如 Claude Desktop 或自定义 AI 应用程序）。</p></li><li><p><strong>服务器</strong>：向语言模型提供资源、工具和提示的应用程序。每个服务器都专门提供对特定功能或数据源的访问。</p><ul><li><p><strong>工具</strong>：用户定义的函数，模型可调用这些函数进行操作，如搜索数据库、调用外部应用程序接口或对数据执行转换等。</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>搜索引擎优化分析 MCP 服务器（标题和关键词优化）</p></li></ul></li><li><p><strong>出版商代理</strong>使用：</p><ul><li><p>内容管理系统 MCP 服务器（内容管理系统 API）</p></li><li><p>CI/CD MCP 服务器（部署管道）</p></li><li><p>分析 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 尤其有价值。在 A2A 架构中，能够<strong> 根据不断变化的条件做出独立决策、表现出积极主动行为并能动态适应工作流程要求的</strong>代理可茁壮成长。专业化功能的横向扩展是另一个关键优势--多个专业化代理协同工作，而不是只有一个万能代理，同一代理的多个实例可以异步处理子任务。例如，在我们的新闻编辑室报道突发新闻时，多名记者代理可能会同时从不同角度报道同一新闻。</p><p>最后，需要真正多代理协作的任务是 A2A 的理想选择。这包括<a href="https://arxiv.org/abs/2404.18796">法律硕士即评审团的评估</a>机制、建立共识和投票系统，以及<strong>需要多角度</strong>达成最佳结果的协作式问题解决方法。</p><h3>何时使用 MCP</h3><p>模型上下文协议是扩展单一人工智能模型功能的理想选择。当单个人工智能模型需要访问多个工具和数据源时，MCP 提供了完美的解决方案，集中式推理与分布式工具和直接的工具集成相结合。在我们的新闻编辑室示例中，研究员代理（一种模式）需要访问多个数据源，包括新闻 API、事实核查服务和学术数据库--所有这些都通过标准化的 MCP 服务器访问。</p><p>当工具集成的广泛共享和可重用性变得非常重要时，标准化工具集成就成了优先事项。MCP 凭借其预构建的 MCP 服务器生态系统大放异彩，大大缩短了常见集成的开发时间。当需要简单性和可维护性时，MCP 的请求-响应模式是开发人员所熟悉的，比分布式系统更容易理解和调试，操作复杂性也更低。</p><p>最后，软件供应商通常会提供 MCP，以方便与其系统进行远程通信。这些由供应商提供的 MCP 服务器大大缩短了入网和开发时间，同时为专有系统提供了标准化接口，使集成比定制 API 开发更加简单。</p><h3>何时同时使用两种方法（A2A ❤️ 的 MCP）</h3><p><a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">正如 A2A 有关 MCP 集成的文档</a> 所指出的，许多复杂的系统都能从 A2A 和 MCP 的 结合中受益。既需要协调又需要标准化的系统是混合方法的理想选择。A2A 处理代理协调和工作流程协调，而 MCP 则为单个代理提供工具访问。在我们的新闻编辑室示例中，代理通过 A2A 进行协调；工作流程从记者到研究员，再到编辑，最后到出版商。不过，每个代理都使用 MCP 服务器来管理其专用工具，从而实现了干净利落的架构分离。</p><p>多个专门的代理，每个都使用 MCP 进行工具访问，这代表了一种常见的模式，即代理协调层由 A2A 处理，工具访问层由 MCP 管理。这种明确的分工使系统更容易理解和维护。</p><p>将这两种方法结合起来的好处是巨大的。您可以获得多代理系统的组织优势，包括专业化、自主性和并行处理，同时还可以享受 MCP 的标准化和生态系统优势，如工具集成和资源访问。代理协调（A2A）和资源访问（MCP）之间有明确的分离，而且重要的是，A2A 不需要单独用于 API 访问等较小的任务，MCP 可以高效地处理这些任务，而不需要多代理协调的开销。</p><p><strong>常见问题：A2A 与 MCP--使用案例</strong></p><p>功能</p><p>Agent2Agent (A2A)</p><p>模型上下文协议（MCP）</p><p>混合型（A2A + MCP）</p><p>首要目标</p><p>多代理协调：使专业代理团队能够在复杂的多步骤工作流程中协同工作。</p><p>单一代理增强：利用外部工具、资源和数据扩展单一 LLM/Agent 的能力。</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>这是两篇文章的第一部分，内容涉及基于 A2A 的代理的实施，并通过 MCP 服务器提供支持和外部数据及工具访问。下一篇文章将探讨实际代码，以演示它们如何共同模拟在线新闻编辑室的活动。虽然这两种框架本身都具有极强的能力和灵活性，但当它们协同工作时，你就会发现它们之间的互补性有多大。</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>正变得越来越重要。随着模型越来越完善，其有效性和可靠性已不再依赖于训练有素的数据，而更多地取决于模型在正确环境中的立足程度。能够在正确的时间检索和应用最相关信息的代理更有可能产生准确和可信的输出结果。</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>什么是 Mastra？</h2><p>Mastra 是一个开源的 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 支持其他人工智能 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="告诉 mastra 保存提示文件的位置" /><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> - 核心人工智能 SDK 软件包，提供用于在 JavaScript/TypeScript 中管理人工智能模型、提示和工作流程的工具。Mastra 是在 Vercel 的<a href="https://ai-sdk.dev/">人工智能 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>、用于连接到弹性云或本地集群，以进行索引、搜索和矢量操作。</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 首席技术官）分享了名为<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 Serverless。这一点很重要，因为无服务器部署不允许手动配置分片。</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>缓存结果，避免重复调用应用程序接口</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> 参数是每个嵌入向量的长度，这取决于你使用的嵌入模型。在我们的例子中，我们将使用 OpenAI 的<code>text-embedding-3-small</code> 模型生成嵌入，该模型输出大小为<code>1536</code> 的向量。我们将以此作为默认值。</p></li><li><p>下面的映射中使用的<code>similarity</code> 变量是由辅助函数 c<code>onst similarity = this.mapMetricToSimilarity(metric)</code> 定义的，该函数接收<code>metric</code> 参数的值，并将其转换为与 Elasticsearch 兼容的关键字，用于所选的距离度量。</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 Serverless 会自动管理这些设置。</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-近邻）查询。</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 Serverless，可选择指定<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 使用<a href="https://github.com/pinojs/pino">Pino</a>，这是一个轻量级结构化 JSON 日志记录器。它可捕捉代理启动和停止、工具调用和结果、错误以及 LLM 响应时间等事件。</p></li><li><p><code>observability</code>:控制人工智能跟踪和代理执行的可见性。它可以跟踪</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 提供了一个内置的聊天用户界面，这样我们就不必自己创建了。</p><p>要启动 Mastra 开发服务器，请打开终端并运行以下命令：</p>npm run dev<p>在初始捆绑和启动服务器后，它应该会为你提供一个 Playground 的地址。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="Playground 服务器地址" /><p>将此地址粘贴到浏览器中，您将看到 Mastra Studio。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="粘贴 Playground 地址以访问 Mastra Studio" /><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>早期的整合只是一个起点。同样的模式可以让支持代理记住过去的票单，让内部机器人检索相关文档，或者让人工智能助理在对话中回忆起客户的详细信息。我们还在努力实现与 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[弹性 MCP 服务器：将代理生成器工具暴露给任何人工智能代理]]></title>
    <description><![CDATA[了解如何使用代理生成器中的内置弹性 MCP 服务器安全地扩展任何人工智能代理，以访问您的私人数据和自定义工具。]]></description>
    <content:encoded><![CDATA[<p>Elastic Agent Builder 是一个平台，用于创建与 Elasticsearch 中自己的数据深度集成的工具和代理。例如，您可以创建对内部文档进行语义搜索、分析可观察性日志或查询安全警报的工具。</p><p>但是，当你能将这些定制的、数据感知工具带入你花费时间最多的环境中时，真正的奇迹就发生了。如果您的代码编辑器代理可以安全地访问组织的私人知识库，那会怎样？</p><p>这就是<strong>模型上下文协议（MCP）</strong>的作用所在。Elastic Agent Builder 内置 MCP 服务器，可访问平台中的工具。</p><h2>为什么要使用 Elastic Agent Builder MCP 服务器？</h2><p>人工智能代理的功能非常强大，但它们的知识通常仅限于它们接受过训练的数据以及它们可以在公共互联网上主动搜索的信息。他们不了解贵公司的内部设计文档、团队的特定部署运行手册或应用程序日志的独特结构。</p><p>我们面临的挑战是如何为人工智能助手提供其所需的专业背景。这正是 MCP 所要解决的问题。<strong>MCP 是一种开放标准，允许人工智能模型或代理发现和使用外部工具。</strong></p><p>为了实现这一点，Elastic Agent Builder 通过内置的 MCP 服务器本机公开了您的自定义工具。这意味着您可以轻松地将任何与 MCP 兼容的客户端（如<strong>Cursor</strong>、<strong>VS Code</strong> 或<strong>Claude Desktop</strong>）与您使用 Elastic Agent Builder 创建的专门的数据感知工具连接起来。</p><h2>何时使用 MCP（何时不使用）</h2><p>Elastic Agent Builder 包含多种协议，可支持不同的集成模式。选择正确的人工智能工作流是建立有效人工智能工作流的关键。</p><ul><li><p><strong>使用 </strong><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server"><strong>MCP</strong></a>通过专业工具来增强人工智能代理（如在<strong>Cursor</strong>或<strong>VS Code</strong> 中）。这是"自带工具" 方法，通过安全访问您的私人数据来增强您已经使用的助手。只有工具是通过 MCP 服务器公开的，Elastic 的代理是独立于 MCP 服务器的。</p></li><li><p><strong>使用 </strong><a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"><strong>A2A 协议</strong></a>，让您的完整自定义弹性代理与其他自主代理协作（如<a href="https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise"><strong>谷歌的双子座企业版</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>对于希望在不离开集成开发环境的情况下从内部文档中获得答案的开发人员来说，MCP 是最合适的选择。</p><h2>示例：在 Cursor 中使用代理生成器 MCP 服务器的自定义工具</h2><p>让我们来看一个我每天都在使用的实际例子。首先，我将我们的内部工程文档抓取并编入一个名为<code>elastic-dev-docs</code> 的 Elasticsearch 索引。虽然我们可以使用 Agent Builder 中的通用内置工具，但我们将创建自己的自定义工具来查询这个特定的知识库。</p><p>定制工具的原因很简单：<strong>控制和精度</strong>。这种方法使我们能够直接针对<code>elastic-dev-docs</code> 索引运行快速语义查询。我们可以完全控制具体针对哪个索引以及如何检索数据。</p><p>现在，我们来看看如何在 Cursor 等人工智能驱动的代码编辑器中使用自定义知识库。</p><h3>第 1 步：在 Agent Builder 中创建自定义知识库工具</h3><p>首先，在 Agent Builder 中创建一个新工具。清晰而具体的工具描述非常重要，因为这是任何人工智能代理（无论是内部的弹性代理还是通过 MCP 连接的外部工具，如 Cursor）发现并为正确的任务选择工具的方式。</p><p>有力的描述应该是明确的。例如"在 elastic-dev-docs 索引上执行语义搜索，以查找内部工程文档、运行手册和发布程序"。</p><p>有了这些，就可以对工具进行配置，以便针对我们的特定索引执行语义搜索。一旦保存，就可以立即食用。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt011118f0a9279185/6a17f367dbb4ffc4f3fb581a/1eea079908fdf7cc72dbe81abd07ff51601a43d4-1472x1600.png" alt="在 Agent Builder 中创建自定义知识库工具。" /><p>在连接到外部世界之前，您可以直接在用户界面中进行测试。只需单击 "<strong>测试</strong>"按钮，手动填写参数，模拟 LLM 的工作，然后检查结果，确认一切工作正常。</p><h3>第 2 步：将光标连接到弹性 MCP 服务器</h3><p>Elastic Agent Builder 可通过安全的 MCP 端点自动公开所有可用工具。您可以在 Kibana 的工具用户界面中找到唯一的服务器 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's<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>保存配置后，你应该能在光标中看到 Elastic Agent Builder MCP 服务器工具。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2837638263e628ed/6a17f36adbb4ffeb9cfb5820/d302c6d3609fbf14fd40e21b9e69e567bf12553f-1600x1002.png" alt="Cursor 中提供的 Elastic Agent Builder MCP 服务器工具的图像。" /><h3>第三步：提问！</h3><p>建立连接后，Cursor 代理现在可以调用您的自定义工具来回答您的问题或指导代码生成过程。</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 Agent Builder 中的内置 MCP 服务器来扩展人工智能助手，使其能够安全地访问您的私人数据。将模型建立在自己的信息基础上是使其真正有用的关键。</p><p>概括地说，我们介绍了核心步骤：</p><ul><li><p>根据需要选择合适的协议（MCP）。</p></li><li><p>构建自定义知识库工具</p></li><li><p>将该工具与 Cursor 等集成开发环境助手连接起来。</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[人工智能代理评估：Elastic 如何测试代理框架]]></title>
    <description><![CDATA[了解我们如何在向 Elastic 用户发布代理系统变更之前对其进行评估和测试，以确保结果的准确性和可验证性。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>在 Elastic Stack 中，有许多由 LLM 驱动的代理应用程序，例如<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> Agent Builder</a> 中即将推出的 Elastic AI Agent（目前处于技术预览阶段）和<a href="https://www.elastic.co/docs/solutions/security/ai/attack-discovery"> Attack Discovery</a> （ 8.18 和 9.0+ 中的 GA<a href="https://www.elastic.co/blog/whats-new-elastic-security-9-0-0"> ），还有更多正在开发中。</a>在开发过程中，甚至在部署之后，回答这些问题都非常重要：</p><ul><li><p>我们如何估算这些人工智能应用的响应质量？</p></li><li><p>如果我们做出改变，如何保证这种改变是真正的改进，而不会导致用户体验下降？</p></li><li><p>如何以可重复的方式轻松测试这些结果？</p></li></ul><p>与传统的软件测试不同，评估生成式人工智能应用涉及统计方法、细致的定性审查以及对用户目标的深刻理解。</p><p>本文详细介绍了 Elastic 开发人员团队进行评估、确保部署前变更的质量以及监控系统性能的流程。我们的目标是确保每一项变革都有据可依，从而取得可信和可验证的成果。这一过程的一部分直接集成到了 Kibana 中，体现了我们对透明度的承诺，这也是我们开源精神的一部分。通过公开分享我们的部分评估数据和指标，我们力求促进社区信任，并为开发人工智能代理或使用我们产品的任何人提供一个清晰的框架。</p><h2>产品示例</h2><p>本文档中使用的方法是我们迭代和改进 "攻击发现 "和 "弹性人工智能代理 "等解决方案的基础。分别对两者进行简要介绍：</p><h3>弹性安全的攻击发现</h3><p>攻击发现使用 LLM 来识别和总结 Elastic 中的攻击序列。在给定的时间范围（默认 24 小时）内收到 Elastic Security 警报后，Attack Discovery 的代理工作流程会自动查找是否发生了攻击，以及重要信息，如哪台主机或用户受到了攻击，哪些警报促成了这一结论。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb70932abe8d4de75/6a17f04ea292990c52d02d61/20fabb47642dad7b588daaaa8c3a98de860ad01d-1251x758.png" alt="" /><p></p><p>我们的目标是，基于 LLM 的解决方案所产生的输出结果至少与人类的输出结果一样好。</p><h3>弹性人工智能代理</h3><p><strong>Elastic Agent Builder</strong>是我们的新平台，用于构建可利用我们所有搜索功能的上下文感知人工智能代理。它配备了<strong>Elastic AI Agent</strong>，这是一个预构建的通用代理，旨在通过对话式交互帮助用户理解数据并从中获得答案。</p><p>该代理通过自动识别 Elasticsearch 或连接的知识库中的相关信息，并利用一套预建工具与之交互，来实现这一目标。这使得 Elastic AI Agent 能够响应各种用户查询，从单个文档的简单 Q&amp;A 到需要在多个索引中进行聚合和单步或多步搜索的复杂请求。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b9dbede85a56bd6/6a17f050e8fbce88943a1a30/d29dee100bb8a17bb623acd745773a5164a1df4f-1600x1014.png" alt="" /><h2>通过实验衡量改进</h2><p>就人工智能代理而言，实验是对系统进行的结构化、可测试的更改，旨在提高系统在明确定义的维度（如有用性、正确性、延迟）上的性能。我们的目标是明确回答"如果我们合并这一改动，能否保证它是真正的改进，不会降低用户体验？</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>成功的实验是一个系统的探究过程。从细微的提示调整到重大的架构转变，每一项改变都要遵循这七个步骤，以确保结果是有意义和可操作的：</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>弹性人工智能代理示例：代理的工具选择，尤其是分析查询工具的选择，不够理想且不一致，经常导致选择错误的工具。这反过来又增加了令牌成本和延迟。</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>Attack Discovery</strong></p><p>公制</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>弹性人工智能代理</strong></p><p>公制</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>弹性人工智能代理示例：</p><ol><li><p><strong>对 relevance_search 和 nl_search 工具的说明进行修改，以明确定义其具体功能和用例</strong>。</p></li><li><p>我们预测，我们的<strong> 工具调用准确率</strong> 将<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 集 Oh My Malware (ohmymalware.com)</p><p>4 种多重攻击情景（通过组合前两类攻击而创建）</p><p>3 种良性情景</p><p>弹性人工智能代理评估数据集示例<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>基于规则的评估（如使用 Python/TypeScript 检查 .json 是否有效）。</p></li><li><p>法学硕士即法官（询问另一位法学硕士某项答复是否与源文件的事实相符）</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 的生成式人工智能功能幕后</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 协议将弹性代理连接到 Gemini Enterprise]]></title>
    <description><![CDATA[了解如何使用 Agent Builder 通过 A2A 协议将定制的 Elastic Agent 暴露给 Gemini Enterprise 等外部服务。]]></description>
    <content:encoded><![CDATA[<p><strong>Elastic Agent Builder</strong>是一套直接在 Elasticsearch 中创建数据驱动的人工智能代理的功能。在本<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>，允许来自不同平台的代理进行通信和协作。我们已将其直接内置到弹性代理生成器中。</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">你的第一个弹性代理</a>"中，我们展示了如何构建自定义代理，例如可安全访问市场数据的<em>财务助理</em>代理。但是，如果你不能在其他环境（如双子座企业版）中使用其洞察力，而又不重建你的工作，那么它的价值就会受到限制。</p><p>这种互操作性的挑战正是阻碍人工智能发展的原因。代理需要一种跨平台交流的通用语言，这正是 A2A 协议的作用所在。它提供了一个标准通信层，不仅可以让您与代理直接互动，还能开启未来，让整个组织的专业代理都能协作并分享见解。</p><p>为了实现这一点，Elastic Agent Builder 通过两个标准端点为所有代理提供 A2A 协议本机支持：</p><ol><li><p><strong>Agent Card 端点 (</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>在将我们的代理连接到生产系统之前，最好检查一下它的通信是否正确。最简单的方法是使用<strong>A2A 检查器</strong>，这是一款专门用于测试和调试 A2A 集成的工具。</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> 上可用。</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>在检查员用户界面输入这些详细信息后，您就可以立即连接并开始与您的代理聊天。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6381135e3fb297df/6a17ef4bec0f898b0c5a66ea/7231c72bf30bed2a854f58658c1eca2843f43bfc-1600x1296.png" alt="A2A 代理卡和代理检查员设置" /><p>这一简单的验证让我们确信，我们的代理已正确配置并准备好进行下一步操作。</p><h2>开始直播您在双子座企业中的定制代理</h2><p>现在是激动人心的部分：在 Gemini Enterprise（前身为 Agentspace）中启用我们的定制财务顾问代理。该集成由<a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-ai-agent"> Elastic AI Agent 提供支持 ，它可在谷歌云市场上购买</a> 。</p><p>连接后，Gemini Enterprise 使用 A2A 协议与您的代理直接通信。这就是互操作性的真正威力所在：用户现在可以访问来自自定义 Elasticsearch 代理的深度数据驱动洞察，而无需离开他们熟悉的环境。你可以在代理列表中看到你的自定义弹性代理：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f54f0bb15216d8e/6a17ef4d6df73107d90a0fdb/37a39e92ebf3d72c6c8014397cd8e846336173a4-1600x834.png" alt="在 Google 代理空间列表中查看自定义代理" /><p>想象一下，双子座企业的用户会问</p><p><em>"我担心市场情绪。您能告诉我哪些客户最容易受到坏消息的影响吗？</em>"</p><p>在幕后，Gemini Enterprise 通过 A2A 协议将此查询路由到您的自定义弹性代理。然后，您的代理会使用其专业工具查询您的数据、制定答案并将其发送回来。对于最终用户来说，这种体验是无缝的。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte130c332ee0648a6/6a17ef4fe9ea874426a9c6bb/e5f126c1a27a51c6e69a767aa87c9f746b62e39c-1600x1044.png" alt="用户向 Agentspace 提出查询，以及查询在幕后发生的情况" /><p>而且还不止于此！使用弹性代理获取的答案现在可以用作下一个问题的上下文，这些问题可能会触发不同的专门代理（例如您的投资平台代理，以调整对上市公司的投资）。无需离开搜索栏。</p><p>通过在具有 A2A 功能的 Gemini Enterprise 上部署弹性代理，您可以统一访问、协调和工作流，通过提供用户与其数据和工具对话的单一用户界面，消除人工智能、搜索和企业系统之间的摩擦--所有这些都在上下文中进行。对用户来说，这意味着更少的工具切换和更直观、更有能力的人工智能助手。对组织而言，这意味着协调一致的管理、可扩展性和内置的互操作性。</p><h2>轮到您建造</h2><p>您现在拥有了让您的弹性代理随时随地可用的工具。通过利用开放式 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">谷歌云市场</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 改进代理人工智能工具的实验]]></title>
    <description><![CDATA[了解我们如何通过迭代实验，结合线性检索器、混合搜索和语义文本进行可扩展的 RAG 优化，从而改进 Elasticsearch 的人工智能代理工作流。]]></description>
    <content:encoded><![CDATA[<p>如今，在 Elastic，我们也像其他人一样，全力投入到聊天、代理和 RAG 中。在搜索部门，我们最近一直在开发代理生成器和工具注册表，目的都是为了简化在 Elasticsearch 中与数据 "聊天 "的过程。</p><p>请阅读<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> " 利用 Elasticsearch 构建人工智能代理工作流 "博客</a> ，了解更多有关这项工作的 "全貌"，或阅读<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch"> " 你的第一个弹性代理" 博客 ，了解更多有关这项工作的实用入门知识</a> ：从单个查询到人工智能驱动的聊天 》，了解更多实用入门知识。</p><p>不过，在本博客中，我们将放大一些，看看当您开始聊天时最先发生的事情之一，并向您介绍我们最近做出的一些改进。</p><h2>这里发生了什么？</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>当您与 Elasticsearch 数据聊天时，我们默认的人工智能代理会执行此标准流程：</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>我们最初的实施很简单。我们建立了一个工具（名为 index_explorer），它可以有效地进行<code>_cat/indices</code> ，列出我们可用的所有索引，然后要求 LLM 识别这些索引中哪个最符合用户的信息/问题/提示。您可以 在这里 看到<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，由 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 blob 会不断增加。因此，我们希望为 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> 工具现在速度<em>更快</em>，因为它不需要向 LLM 提出请求，而是可以为给定的查询请求单个嵌入，并执行高效的向量搜索操作。以最高点击率为选定索引，我们得到的结果是</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>这种方法具有可扩展性。这种方法效率很高。但这种方法比我们的基准线好不了多少。但这并不奇怪，因为这里的搜索方法太天真了。没有任何细微差别。不承认索引的名称和描述应比索引包含的任意字段名称更有分量。没有加权精确词性匹配而非同义匹配的功能。不过，要建立一个高度细致的查询，需要对手头的数据进行大量假设。到目前为止，我们已经对索引和字段名称的语义做了一些大的假设，但我们还需要更进一步，开始假设它们有<em>多大</em>的意义以及它们之间的关系。如果不这样做，我们可能无法可靠地将最佳匹配结果确定为我们的首要结果，但更有可能说最佳匹配结果就在前 N 个结果中的某个地方。我们需要的是一种能够在语义信息存在的语境中消费语义信息的东西，它可以与另一个可能以不同语义方式表示自己的实体进行比较，并在两者之间做出判断。比如法学硕士。</p><h3>假设 6：候选集减少</h3><p>还有很多实验我就不一一列举了，但关键的突破是放弃了纯粹从语义搜索中挑选最佳匹配项的愿望，转而利用语义搜索作为过滤器，从 LLM 的考虑范围中剔除不相关的索引。我们将线性检索、混合检索与 RRF 以及<code>semantic_text</code> 结合起来进行<a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">检索</a>，将结果限制在匹配指数的前 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>下一个明确的结果是，一味地增加描述性文字的数量，其回报率会越来越低。在这项研究之前，我们一直在讨论是否应该投资扩展 Elasticsearch 存储<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">字段级元数据</a>的能力。如今，这些<code>meta</code> 值的上限是 50 个字符，而且有一种假设认为，我们需要增加这个值，以便能够从语义上理解我们的字段。但情况显然不是这样，法律硕士似乎只需填写字段名称就可以了。我们以后可能会进一步调查这个问题，但现在已经没有紧迫感了。</p><p>相反，这也清楚地证明了 "可搜索 "索引元数据的重要性。在这些实验中，我们破解了指数的索引。但是，我们可以研究将其直接构建到 Elasticsearch 中，构建应用程序接口来进行管理，或者至少围绕其建立一个惯例。我们将权衡各种选择并进行内部讨论，敬请期待。</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[您的第一个弹性代理：从单一查询到人工智能驱动的聊天]]></title>
    <description><![CDATA[了解如何使用 Elastic 的人工智能代理生成器创建专门的人工智能代理。在本博客中，我们将构建一个金融人工智能代理。]]></description>
    <content:encoded><![CDATA[<p>借助 Elastic 的全新<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">代理生成器</a>，您可以创建专门的人工智能代理，使其成为特定业务领域的专家。该功能使您不再局限于简单的仪表盘和搜索栏，而是将数据从被动的资源转变为主动的对话伙伴。</p><p>想象一下，一位财务经理需要在与客户会面之前加快速度。现在，他们只需向定制的代理直接提问，而无需手动挖掘新闻源和交叉参考投资组合仪表板。这就是"聊天优先" 方法的好处。经理与他们的数据直接对话，询问诸如"ACME 公司的最新消息是什么，它对我客户的持股有何影响？"并在几秒钟内得到综合的专家答复。</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>:人工智能生成的带有情感分析的市场文章</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>每一项人工智能技能都以坚实的逻辑为起点。对于我们的财务经理代理，我们需要教它如何回答一个常见问题："我担心市场情绪。你能告诉我哪些客户最容易受到坏消息的影响吗？这个问题超出了简单的搜索范围。这要求我们将市场情绪与客户投资组合联系起来。</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>'s：首先将负面新闻与资产详细信息连接起来，然后将这些资产与客户持有的资产连接起来，最后再与客户的账户信息连接起来。这样，在一次高效的查询中，就能从四个不同的索引中获得极其丰富的结果。这意味着我们可以将不同的数据集结合起来，创建一个具有洞察力的单一答案，而无需事先将所有数据反规范化为一个巨大的索引。</p><h3>2.作为 LLM 护栏的参数</h3><p>您会发现查询使用了<code>?time_duration</code> 。这不仅是一个变量，还是人工智能的护栏。虽然大型语言模型 (LLM) 是生成查询的好帮手，但让它们自由支配数据可能会导致查询效率低下甚至错误。</p><p>通过创建参数化查询，我们迫使 LLM 按照人类专家已经定义的经过测试、高效且正确的业务逻辑工作。这与多年来开发人员使用搜索模板安全地向应用程序公开查询功能的方式类似。代理可以解释用户的请求，如"this week" 来填充<code>time_duration</code> 参数，但它必须使用我们的查询结构来获取答案。这使我们在灵活性和控制性之间取得了完美的平衡。</p><p>最终，这种查询可以让了解数据的专家将其知识封装到一个工具中。其他人和人工智能代理只需提供一个参数，就能使用该工具获得相关结果，而无需了解底层的复杂性。</p><h2>步骤 2：技能--将查询转化为可重复使用的工具</h2><p>在我们将 ES|QL 查询注册为<strong>工具</strong>之前，它只是一个文本。在代理生成器中，工具不仅仅是一个已保存的查询；它还是一个"技能" ，人工智能代理可以理解并选择使用。神奇之处在于我们提供的<strong>自然语言描述</strong>。该描述是连接用户问题和底层查询逻辑的桥梁。让我们注册一下刚刚创建的查询。</p><h3>用户界面路径</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.法律硕士就是通过阅读这些内容来判断这个工具是否适合这项工作。</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>。用户界面会自动查找<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="&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>应用程序接口路径</h3><p>对于喜欢自动化或需要以编程方式管理工具的开发人员来说，只需调用一个 API 就能实现同样的效果。只需向带有工具定义的<code>/api/agent_builder/tools</code> 端点发送<code>POST</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>通过告诉代理"Understand（理解）、Plan（计划）、Execute（执行）和 Synthesize（综合），" ，我们给了它一个标准的操作程序。这提高了它处理复杂、多步骤问题的能力。</p></li><li><p><strong>它促进了互动对话： </strong> "提出澄清性问题的指令" 使代理更加稳健。这将最大限度地减少对模棱两可的请求做出不正确的假设，从而获得更准确的答复。</p></li></ul><h3>用户界面路径</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>代理编号：</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.回到顶部，点击 "<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>应用程序接口路径</h3><p>您可以通过<code>POST</code> 请求<code>/api/agent_builder/agents</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>我们已将业务逻辑封装在一个工具和一个"大脑" 中，准备在我们的 Agent 中使用它。是时候见证这一切了。现在，我们可以使用专门的代理与数据聊天了。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8826539b16e46f4/6a17f21d505ac35924ad8c5c/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" alt="创建财务助理后与弹性代理生成器对话。" /><h3>用户界面路径</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>片刻之后，代理将返回一个格式完美、内容完整的答案。由于法律硕士的性质，您的答案格式可能会略有不同，但这次运行中，代理返回的答案是一样的：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1e163fd7c4416bd/6a17f21f6864a4e35bb688ad/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" alt="由 Elastic Agent Builder 创建的回复，为：最易受负面新闻影响的客户提供财务助理。" /><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>应用程序接口路径</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>致开发人员：与应用程序接口集成</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>有关使用这些应用程序接口执行本教程中每一步的完整实践演示，请查看我们 GitHub 软件仓库中的配套<strong>Jupyter Notebook</strong> <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>总结：轮到你来建设</h2><p>我们首先使用 ES|QL 查询，并将其转换为可重复使用的技能。然后，我们建立了一个专门的人工智能代理，赋予它明确的任务和规则，并赋予它这种技能。它是一个复杂的助手，能够理解复杂的问题，并执行多步骤分析，提供精确的数据驱动型答案。</p><p>这一工作流程是 Elastic 中新的<strong>代理生成器</strong>的核心。它的设计足够简单，非技术用户可以通过用户界面创建代理，但又足够细致，开发人员可以在我们的应用程序接口基础上构建定制的人工智能驱动应用程序。最重要的是，它可以让您安全可靠地将 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>。在另一篇博客中，我们将讨论 Agent Builder 对开发中的 Agent2Agent（或 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 构建人工智能代理工作流]]></title>
    <description><![CDATA[了解代理生成器（Agent Builder），它是 Elasticsearch 中的一个新人工智能层，为构建人工智能代理工作流提供了一个框架，使用混合搜索为代理提供推理和行动所需的上下文。]]></description>
    <content:encoded><![CDATA[<p>在 Elastic，我们通过人工智能助手、高级 RAG 和矢量数据库的改进，为 LLM 和对话界面带来了语境。最近，随着人工智能代理的兴起，我们发现对相关上下文的需求日益增长，并了解到高效的<strong> 人工智能代理需要出色的搜索</strong>。因此，我们在 Elastic Stack 中构建了新的本地功能，旨在帮助开发可利用 Elasticsearch 中数据的人工智能代理。我们希望与大家分享我们在这一历程中取得的进展，以及我们对下一步发展的展望。</p><h2>代理生成器：构建数据驱动型人工智能代理的基础</h2><p>人工智能代理的承诺很简单：给它一个目标，它就能完成工作。但对于开发商来说，现实却是一系列复杂的挑战。首先，代理的能力取决于其对环境的感知以及为实现用户目标而提供的工具。那么，如何从纷繁复杂的企业数据中提供正确的上下文是一项巨大的挑战。最后，所有这一切都必须由一个可靠的推理循环来协调，该循环可以进行规划、执行和学习。</p><p>为了解决这个问题，开发人员需要从头开始构建一个复杂而脆弱的堆栈。如今的代理架构需要将多个不同的部分拼接在一起：一个 LLM、一个向量数据库、一个元数据存储、用于日志记录和跟踪的独立系统，以及一些评估它们是否都能正常工作的方法。这不仅复杂，而且成本高昂、容易出错，并且难以建立用户所需的高质量、值得信赖的人工智能系统。</p><p>因此，我们想让它变得更简单。为此，我们的方法是将有效的上下文驱动型代理的重要部分直接集成到 Elasticsearch 的核心中，并提供一套名为<strong>Elastic AI Agent Builder</strong> 的新功能。这一新层提供了一个框架，其中包含创建由 Elasticsearch 支持的人工智能代理所需的所有基本构件：一套开放的基元、基于标准的协议和对数据的安全访问--因此您可以根据真实世界的数据和要求构建代理系统：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2779dae5df010328/6a17e15eabe0f24f18dfe931/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" alt="" /><p><strong>提供人工智能体验</strong>：这是终极目标。以我们的搜索人工智能平台和您的数据为基础，您可以构建任何类型的生成式人工智能应用程序：从定制聊天界面到与 LangChain 等代理框架或 Salesforce 等业务应用程序的集成。</p><p><strong>由 Agents&amp; 工具提供支持</strong>：在平台之上，我们提供了一个简洁的抽象层。您可以直接与代理和工具互动，并根据具体需求进行定制。您还可以通过强大的应用程序接口和开放标准（如 MCP 和 A2A）访问平台的功能。</p><p><strong>由搜索人工智能平台支持</strong>：这是我们集成了各种组件的核心引擎。先进的矢量数据库、代理逻辑、查询结构、安全功能、评估跟踪都在这里，由 Elastic 管理和优化。</p><p><strong>释放数据的力量</strong>：任何优秀代理商的基础都是优秀的数据。我们的平台首先能够摄取或联合访问您的所有企业数据</p><h2>平台中的代理建设</h2><p>Agent Builder 集成到搜索人工智能平台中，为代理开发提供了一个完整的框架。它建立在五个关键支柱之上，每个支柱都旨在解决构建和部署生产级人工智能系统的一个关键方面。让我们来分析一下，代理如何定义目标，工具如何提供功能，开放标准如何确保互操作性，评估如何提供透明度，安全如何提供信任。</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>您可以直接在 Kibana 中通过新的聊天用户体验或通过 API 与此体验进行交互。</p><p>通过 API 查询默认代理只需一次调用：</p>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}<p>由于会话是有状态的，因此您可以使用会话 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>开发人员还可以通过简单的应用程序接口创建自己的定制代理。代理封装了指令、工具和数据访问，创建了量身定制的推理引擎。</p><p>创建自定义代理只需调用一次应用程序接口。下面的示例显示了一个例子，"配置 "字段包含所有关键细节，如说明或可用工具：</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">您的第一个弹性代理：从单一查询到人工智能驱动的聊天</a>。</p><h3>工具</h3><p>如果说代理确定了要完成的<em>任务</em>，那么工具则确定了<em>如何</em>完成。</p><p>工具为代理执行和检索信息或执行操作暴露了特定的弹性核心功能。工具可以包括获取索引或获取映射等核心功能，也可以包括从自然语言到 ES|QL 等更高级的功能。</p><p>Elasticsearch 随附一套针对常见需求进行了优化的默认工具。但真正的灵活性来自于自己的创造。通过定义工具，您可以决定将哪些查询、索引和字段通过 ES|QL 暴露给代理，从而对速度、准确性和安全性进行精确控制。</p><p>注册新工具也很简单，只需调用一次应用程序接口。您可以创建一个工具，利用我们的<a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">ES|QL（Elasticsearch 查询语言）</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>为了实现这一点，我们正在通过应用程序接口、新兴协议和开放标准公开我们的能力。</p><p><em>模型上下文协议（MCP）</em></p><p><a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">模型上下文协议（MCP）</a>正迅速成为跨系统连接工具的开放标准。通过支持 MCP，Elasticsearch 可以将对话式人工智能与您的数据库、索引和外部 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>将其视为推理层的互操作性。您的弹性代理可以处理搜索和检索，然后将任务交给专门的支持或 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>这种详细程度是真正持续改进循环的基础。它使您能够建立一套全面的测试、调试故障、识别失败模式以防止回归，并捕捉成功模式以微调性能。归根结底，这种数据驱动的方法是将有前途的原型转化为生产级、值得信赖的人工智能系统的关键。</p><h3>安全性</h3><p>随着代理和工具的功能越来越强大，安全性不再是可有可无的，而是基础性的。要公开应用程序接口、自动执行任务和工作流程，就必须信任企业系统。特别是当代理开始自动执行更多的工作流程时，确保这些流程安全并满足企业要求的能力就显得尤为重要。</p><p>上述功能都继承了 Elastic 目前已有的控制功能，包括针对 API 调用和 API 密钥管理的<a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">基于角色的访问控制 (RBAC)</a>。我们还将同样的控制扩展到 MCP 等新协议。这意味着支持 OAuth 等标准，以及插入自定义身份验证机制的能力。</p><p>我们的目标是让您灵活地尝试使用代理和工具，同时保持组织所需的安全性、合规性和管理水平。</p><h2>下一步行动</h2><p>我们不仅要增加功能，还要扩展 Elasticsearch 的代理上下文工程。我们计划在这些原则的基础上继续发展：</p><p>1.致力于开放源码&amp; 标准</p><p>我们致力于开放源代码和开放标准，确保这些功能与外部代理框架保持互操作性。您始终能够在生态系统中连接、扩展和组成代理，同时将数据和工作流程置于您的控制之下。</p><p>2.背景的价值</p><p>人工智能代理的背景是其最大的资产。在代理执行搜索和工作流操作时管理上下文是一项极具挑战性的任务。我们正在利用 Elastic 的核心优势来解决上下文工程问题，确保您的代理始终可以获得最相关的信息。</p><p>3.关注代理数据流</p><p>展望未来，代理将成为越来越大的数据源，包括代理的输出（生成的文档、报告、可视化）和代理的执行轨迹（其思维、工具调用、内存/上下文）。Elastic 非常适合处理此类数据，我们正在研究如何利用这些数据进行分析、评估和自动改进。</p><p>4.设计的安保和安全</p><p>人工智能代理带来了全新的安全保障挑战。Elastic 一直是安全解决方案的领导者，我们将继续构建企业级防护、访问控制和"零信任" 原则。</p><p>5.嵌入平台</p><p>构建人工智能代理的功能已嵌入 Elasticsearch 平台。这意味着平台级功能，如跟踪、评估、可视化和分析，都适用于代理。希望根据代理执行情况开发仪表板--这是内置功能。希望通过情感分析来评估人工智能代理的性能--该平台可以实现这一点。这样就能围绕人工智能体验构建一个完整的生命周期。</p><p>Elastic 的目标是为您提供建立对话式人工智能和自动化工作流程的接口，这些接口完全集成、可扩展并以您的数据为基础。更多技术细节和进展情况将很快与大家分享。</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 生态系统中构建人工智能代理]]></description>
    <content:encoded><![CDATA[<p>我是在激烈的高风险梦幻篮球联赛中萌生这个想法的。我想知道<em>我能否建立一个人工智能代理，帮助我在每周的对阵中占据优势？当然可以！</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">README</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>"为基础。如果您是第一次接触人工智能代理及其用途，请从这里开始。
</p><h2><strong>结构概述</strong></h2><p>该系统的核心是一个大型语言模型（LLM），它充当了代理的推理引擎（大脑）。它能解释用户输入，决定调用哪些工具，并协调生成相关响应所需的步骤。</p><p>代理本身由 JavaScript 生态系统中的代理框架 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>人工智能代理可以与其他系统互动，独立运行，并根据其定义的参数执行操作。代理式 RAG 将人工智能代理的自主性与检索增强生成的原则相结合，使 LLM 能够选择调用哪些工具和使用哪些数据作为上下文来生成响应。<a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">点击此处</a>了解有关 RAG 的更多信息。</p><h3><strong>选择框架，为什么要超越 AI-SDK？</strong></h3><p>目前有许多人工智能代理框架，你可能听说过<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 首席执行官）的框架<a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">比较表</a>。</p><p>让我对 Mastra 产生兴趣的是，它是一个 JavaScript 优先框架，专为全栈开发人员设计，可以轻松地将代理集成到他们的生态系统中。Vercel 的 AI-SDK 也能实现大部分功能，但 Mastra 的优势在于当项目包含更复杂的代理工作流程时。Mastra 增强了 AI-SDK 设置的基本模式，在本项目中，我们将同时使用它们。</p><h3><strong>框架和模型选择考虑因素</strong></h3><p>虽然这些框架可以帮助您快速构建人工智能代理，但也有一些缺点需要考虑。例如，在使用人工智能代理或任何抽象层之外的其他框架时，你会失去一些控制权。如果 LLM 没有正确使用工具，或者做了一些你不希望它做的事情，抽象化就会增加调试难度。不过，在我看来，这种折衷还是值得的，尤其是因为这些框架的发展势头越来越好，而且还在不断迭代。</p><p>同样，这些框架与模型无关，这意味着您可以即插即用不同的模型，但请记住，模型在不同的数据集上训练出来的结果是不同的，反过来，它们给出的响应也是不同的。有些型号甚至不支持工具调用。因此，可以切换和测试不同的型号，看看哪种型号能给您带来最好的响应，但请记住，您很可能需要为每种型号重写系统提示。例如，使用 Llama3.3与 GPT-4o 相比，它需要更多的提示和具体指令才能得到您想要的回应。</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>后端（Mastra）：</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>创建用户界面，以保存格式正确的对话。</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.然后，我们将选择 OpenAI 作为默认的 LLM 提供商。</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>这将创建一个名为<code>frontend</code> 的轻量级 React 应用程序，并为 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 云</strong></p><ul><li><p>注册<a href="https://cloud.elastic.co/registration">弹性云</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.然后创建一个 lib 目录来存放可重复使用的函数，并导航进入该目录：</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 匹配。由于我们使用的是样本数据，我们不希望从外部应用程序接口获取数据造成开销，因此我们硬编码了一些代理可以引用的值。</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">playerDataIngestion.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>这些将是我们为人工智能代理定义工具时使用的主要功能，以便对球员的统计数据进行比较。</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>bool</code> <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">过滤器</a>，其中包含两个条件：一个匹配<code>player_id</code> ，另一个匹配<code>opponent_team_id</code> ，以便只检索相关游戏。我们不需要返回任何文档，我们只关心聚合，因此我们设置<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">comparePlayers.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>模式，这是一个 TypeScript 模式验证库。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。简而言之，跨源资源共享是一种浏览器安全功能，可阻止前台向运行在不同域或端口的后台发出请求。尽管我们在 localhost 上运行后端和前端，但它们使用不同的端口，从而触发了 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 代理。我们还将使用此钩子来显示标记的使用情况、工具调用情况并渲染对话。在上面的系统提示中，我们还要求代理以 markdown 格式输出响应，因此我们将使用<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> 。此钩子将管理前台与人工智能代理后台之间的交互。它可以处理消息状态、用户输入和状态，并为您提供生命周期钩子，以实现可观察性。我们提供的选项包括</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>组件，创建用户界面来进行对话。接下来，用<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.打开浏览器，导航到</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>，以及如何将人工智能代理的自主性与有效使用 RAG 的工具相结合，从而产生更细致入微、更具活力的代理。</p></li><li><p><strong>Elasticsearch </strong>及其数据存储能力和强大的本地聚合功能如何使其成为法律硕士知识库的最佳合作伙伴。</p></li><li><p><strong>Mastra </strong>框架及其如何为 javaScript 生态系统中的开发人员简化这些代理的构建。</p></li></ul><p>无论你是篮球迷，还是在探索如何构建人工智能代理，或者像我一样两者兼而有之，我都希望这篇博客能为你提供一些入门的基础知识。完整的软件源可在<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) 使之成为可能。它能将对话式人工智能与数据库和外部应用程序接口无缝连接，将复杂的请求转化为自然的对话。虽然现代 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><a href="https://modelcontextprotocol.io/introduction">模型上下文协议</a>（MCP）由 Anthropic 开发，是一种开放标准，可通过安全的双向渠道将人工智能模型与外部数据源连接起来。它解决了人工智能的一个主要限制：实时访问外部系统，同时保留对话语境。</p><h3>MCP 架构</h3><p>模型上下文协议架构由两个关键部分组成：</p><ul><li><p><strong>MCP 客户端</strong>--代表用户请求信息或执行任务的人工智能助理和聊天机器人。</p></li><li><p><strong>MCP 服务器</strong>- 数据存储库、搜索引擎和 API，用于检索相关信息或执行请求的操作（如调用外部 API）。</p></li></ul><p>MCP 服务器向客户端提供四种主要功能：</p><ul><li><p><strong>资源</strong>- 结构化数据、文件和内容，可检索并用作 LLM 交互的上下文。这样，人工智能助理就能从数据库、搜索索引或其他来源获取相关信息。</p></li><li><p><strong>工具</strong>- 可执行的功能，使 LLM 能够与外部系统交互、执行计算或采取实际行动。这些工具将人工智能的功能扩展到文本生成之外，使助理能够触发工作流、调用应用程序接口或动态处理数据。</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 则更进一步：它使人工智能代理能够实时动态地构建和执行任务。这样，用户就可以提出自然语言问题，如</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 服务器智能选择合适的工具。"更聪明的 "法律硕士通常更善于根据上下文选择合适的工具，并提出适当的论据。</p></li><li><p>双向通信--代理和数据源可流畅地交换信息，并根据需要改进查询（如先查找索引映射，然后才构建 ES 查询）。</p></li><li><p>多工具协调--工作流程可同时利用多个 MCP 服务器的工具。</p></li><li><p>持续的上下文--代理可记住以前的互动，保持对话的连续性。</p></li></ul><p>连接到 Elasticsearch 的 MCP 服务器可释放强大的实时检索架构。人工智能代理可按需探索、查询和分析 Elasticsearch 数据。您的数据可以通过一个简单的聊天界面进行搜索。</p><p>除了检索数据外，MCP 还能采取行动。它可与其他工具集成，以触发工作流、实现流程自动化，并将见解反馈到分析系统中。通过将搜索与执行分离，MCP 可使人工智能驱动的应用程序保持灵活、与时俱进，并无缝集成到代理工作流中。</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>我们的服务器是用 TypeScript 编写的，我们将使用官方的<a href="https://github.com/modelcontextprotocol/typescript-sdk">MCP TypeScript SDK</a>。安装时，我们建议安装 Claude Desktop App（免费版即可），因为它内置了 MCP 客户端。我们的 MCP 服务器本质上是通过 MCP 工具公开官方<a href="https://www.elastic.co/cn/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><strong>搜索</strong><a href="https://github.com/elastic/mcp-server-elasticsearch/blob/main/index.ts#L147">（search</a>）：该工具使用提供的查询 DSL 执行 Elasticsearch 搜索。它可自动启用文本字段的高亮显示，从而更容易识别相关搜索结果。</p></li></ul><p>完整的 Elasticsearch MCP 服务器实现可在<a href="https://github.com/elastic/mcp-server-elasticsearch">elastic/mcp-server-elasticsearch</a>repo 中找到。</p><h4>与您的索引聊天</h4><p>让我们来探讨一下如何设置 Elasticsearch MCP 服务器，以便就数据提出自然语言问题，例如"查找上个月所有超过 500 美元的订单。"</p><p><strong>配置您的克劳德桌面应用程序</strong></p><ul><li><p>打开克劳德桌面应用程序</p></li><li><p>导航至设置&gt; 开发人员&gt; MCP 服务器</p></li><li><p>单击"Edit Config" ，将此配置添加到<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>来填充"订单" 索引，用于此演示</p></li><li><p>这样您就可以尝试查询，如"查找上个月所有超过 500 美元的订单"</p></li></ul><p><strong>开始使用</strong></p><ul><li><p>在克劳德桌面应用程序中打开新对话</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 可创建一个更直观、更高效的工作流程，在整个互动过程中保持上下文关联。</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 Serverless 项目的人工智能代理]]></title>
    <description><![CDATA[一个由自然语言驱动的人工智能代理，可轻松管理 Elasticsearch Serverless 项目--实现项目创建、删除和状态检查。]]></description>
    <content:encoded><![CDATA[<h2>如何使用人工智能代理管理无服务器 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> ，执行该工具。这将启动人工智能代理，并提示您执行命令。</p></li><li><p><strong>用自然语言管理项目：</strong>使用纯英文命令与工具交互，如"Create a serverless project named my\_project","Get status of the serverless project named my\_project", 或"Delete the serverless project named my\_project" 。人工智能将解读您的命令并执行相应的功能。</p></li></ol><h2>背景</h2><p>这个小命令行工具能让你用简单的英语管理你的<a href="https://www.elastic.co/guide/en/serverless/current/intro.html">无服务器 Elasticsearch 项目</a>。它会与人工智能（本例中为 OpenAI）对话，以了解您的意思，并使用 LlamaIndex 调用正确的函数！</p><h3>Elasticsearch Serverless 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 Serverless AI 代理如何工作</h3><p>当您输入以下内容时</p><p><em>"创建一个名为 my_project 的无服务器项目"</em></p><p>......下面是幕后花絮：</p><ul><li><p><strong>用户输入&amp; 上下文：</strong>您的自然语言命令将发送给人工智能代理。</p></li><li><p><strong>功能描述：</strong>人工智能代理已经知道一些函数，如创建项目、删除项目、获取项目状态和获取项目细节，因为我们给了它详细的说明。这些说明会告诉人工智能每个函数的作用以及需要的参数。</p></li><li><p><strong>LLM 处理：</strong>将您的查询和功能信息发送给 LLM。这意味着人工智能可以看到</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>人工智能会找出要调用的函数，传递正确的参数（如项目名称），然后执行函数。回复会以友好的格式发回给您。</p></li></ul><p>简而言之，我们将您的自然语言查询和详细的工具描述列表同时发送给 LLM，这样它就能 "理解 "并为您的请求选择正确的操作。</p><h3>设置人工智能代理</h3><h4>先决条件</h4><p>运行人工智能代理之前，请确保已设置好以下内容：</p><ol><li><p>已安装<strong>Python（v3.7 或更高版本）</strong>。</p></li><li><p>在 Elastic Cloud 上设置<strong>Elasticsearch 无服务器账户</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>运行人工智能代理</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>输入指令，人工智能代理就会施展魔法！完成后，请输入<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 的提示中，以便 LLM "知道 "有哪些可用的操作，以及每个操作的期望值。</p></li><li><p><strong>持久性</strong>：项目及其详细信息保存在 projects.json 中、这样您就不必每次都重新输入信息了。</p></li><li><p><strong>详细记录</strong>：代理被设置为 "详细 "模式，非常适合调试和查看指令如何被转换为函数调用。</p></li></ul><h3>Elasticsearch 人工智能代理利用示例</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>