<?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/ai</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/ai</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/ai.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Sun, 27 Sep 2026 10:25:20 GMT</lastBuildDate>
  <item>
    <title><![CDATA[用描述代替手动绘制：通过 MCP 和 ES|QL 构建 AI 原生 Kibana 仪表板。]]></title>
    <description><![CDATA[从提示词到仪表板了解如何使用 example-mcp-dashbuilder 通过自然语言构建 Kibana 仪表板：这是一款开源 MCP 应用，能够编写 ES|QL 查询、创建交互式图表，并将功能完整的仪表板直接导出到 Kibana。]]></description>
    <content:encoded><![CDATA[<p>example-mcp-dashbuilder 是一款开源 MCP 应用，可将简单英文提示词转换为实时、交互式 Kibana 仪表板，所有操作都在编辑器的聊天窗口中完成。描述您想要实现的仪表板，AI 会探查您的索引结构，为每个可视化内容编写正确的 ES|QL 聚合，并在运行过程中以内联方式渲染预览。完成后，只需一条命令即可导出功能完整的 Kibana 仪表板：真实的 Lens 可视化内容、精确的网格布局和自定义颜色都会保留下来。目前支持六种图表类型，完整的 Kibana Lens 图表集已列入路线图。</p><h2>Kibana 仪表板构建器是什么？</h2><p>想象一下：您只需用简单英文描述所需仪表板，就能看着它逐步呈现，并配有交互式图表、拖放式布局以及一键导出到 Kibana 的功能。</p><p>这正是 <a href="https://github.com/elastic/example-mcp-dashbuilder.git"><strong>example-mcp-dashbuilder</strong></a> 的作用。它是一款开源 MCP（Model Context Protocol，模型上下文协议）应用，可将 AI 助手连接到 Elasticsearch，让您通过对话创建完整的 Kibana 仪表板。无需在菜单间反复点击。无需手动编写可视化配置。只需描述您的需求，AI 就会探查您的数据、编写 ES|QL 查询、构建图表，并在编辑器的聊天窗口中交付实时交互式仪表板。</p><h2><strong>从提示词到仪表板，仅需数秒</strong></h2><p>实际运行效果如下。您可以输入类似这样的内容：</p><p>“为我构建一个基于 logstash-* 的 Web 流量仪表板，包含总请求数、随时间变化的传输字节数、主要地理来源以及响应代码细分”</p><p>随后，AI 会：</p><ol><li><p><strong>探查您的数据：</strong>列出索引并检查字段映射。</p></li><li><p><strong>编写 ES|QL 查询：</strong>根据您的架构量身定制，并使用正确的聚合。</p></li><li><p><strong>创建可视化内容：</strong>条形图、折线图、带迷你图的指标图、热力图、饼图。</p></li><li><p><strong>组织所有内容：</strong>可折叠分区、有意义的标题、合理的布局。</p></li><li><p><strong>渲染交互式预览：</strong>直接显示在聊天中，并配有工具提示、时间选择器和拖放功能。</p></li></ol><p>每个图表在创建时都会内联显示，因此您可以实时查看进度。随后，<code>view_dashboard</code> 会显示完整的仪表板，所有面板都会按照 Kibana 的 48 列网格完成布局。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75af5d9042d141b5/6a17e99dbe608675a4004792/dcbf47c4f17bf1a184fb0167408ebeb861ef6c9d-1404x1568.png" alt="example-mcp-dashbuilder 界面中显示了两个图表。第一个是标题为“主要地理来源”的垂直条形图，按国家/地区代码显示请求数。第二个是标题为“HTTP 响应代码细分”的饼图，显示 200、404 和 503 响应的分布。" /><p><em>单个图表的内联预览</em></p><h2><strong>由 ES|QL 提供支持</strong></h2><p>所有数据检索均使用 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a>，即 Elasticsearch 的管道式查询语言。AI 不只是传递原始查询，还会利用其对 ES|QL 语法的内置知识，并结合您的数据结构信息，为每种可视化类型编写正确且高效的查询。</p><p>该服务器包含一份全面的 ES|QL 参考文档，并将其作为 MCP 资源提供。在编写任何查询之前，AI 会先读取这份参考文档，以了解可用的命令、函数和模式。结合数据可视化最佳实践指南（同样作为资源提供），AI 不仅知道<em>如何</em>查询，还懂得<em>什么样</em>的可视化效果更好：</p><ul><li><p>针对时间序列使用 <code>BUCKET(@timestamp, 1 day)</code>；始终按时间字段 <code>SORT</code> 排序。</p></li><li><p>使用 <code>| SORT value DESC | LIMIT 6</code> 将饼图限制为最多六个扇区。</p></li><li><p>类别比较选用条形图，趋势分析选用折线图，关键绩效指标 (KPI) 选用指标图。</p></li></ul><h2><strong>AI 驱动的数据探索，支持开放式分析</strong></h2><p>在脑海中设计好一个仪表板并将其构建出来是一回事。询问“这个索引中有哪些值得关注的内容？”并获得有用答案则更难；这要求 AI 懂得如何<em>探索</em>数据，而不仅仅是绘制图表。</p><p>example-mcp-dashbuilder 提供了一个 <code>analysis://guidelines</code> 资源，用于定义结构化探索流程：剖析数据、运行有针对性的聚合、呈现值得调查的模式、为最值得关注的发现构建图表，并提出用户接下来可能需要的下钻查询。“分析我的日志”或“在这个索引中寻找模式”等触发短语，会促使 AI 在执行任何其他操作前先读取该操作手册。因此，开放式提示词生成的是逻辑连贯的分析过程，而不是一堆随机图表。</p><p>结果是：您可以将一个陌生索引交给 AI，并获得一个起点，包括一个仪表板，以及一组简短提示，例如“以下是我注意到的情况，需要我深入分析其中某一项吗？”</p><h2><strong>Kibana 仪表板的导出与导入：完整闭环</strong></h2><p>导出/导入闭环让 example-mcp-dashbuilder 对已经在 Kibana 中工作的团队真正显现价值。example-mcp-dashbuilder 是一个独立工具，也是位于编辑器内的对话式仪表板界面，但它不会让您的工作局限于此。在这里构建的仪表板可以在需要时导入 Kibana；现有 Kibana 仪表板也可以反向导入，以便进行 AI 辅助编辑。</p><h3><strong>导出到 Kibana</strong></h3><p>当您对仪表板满意后，只需一条命令即可导出：</p><p>将此仪表板导出到 Kibana</p><p>每个面板都会转换为 Kibana Lens 原生可视化内容。转换过程会保留：</p><ul><li><p><strong>ES|QL 查询：</strong>直接作为 Lens ES|QL 数据源传输。</p></li><li><p><strong>网格位置：</strong>沿用 Kibana 使用的 48 列系统，因此您的布局看起来完全一致。</p></li><li><p><strong>自定义颜色：</strong>系列调色板、指标背景、热力图色带。</p></li></ul><p>最终生成的是一个功能完整的 Kibana 仪表板。不是屏幕截图。不是嵌入内容。而是一个真实的仪表板，您可以在 Kibana 中分享并继续编辑。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1921c74c2833cabe/6a17e99f6864a4a712b687da/5e27777bc0a82cafb373943f65298bdb21d66176-1999x902.png" alt="两个仪表板并排显示。左侧仪表板标题为“编辑 Web 流量 — Logstash (Dashbuilder)”，显示近期流量指标，以及流量统计面板、地理来源条形图和响应代码饼图。右侧仪表板显示类似布局，但总量更高，并包含流量统计面板、地理来源条形图和响应代码饼图。" /><p><em>Kibana 仪表板与 Cursor 聊天中的仪表板并排显示。</em></p><h3><strong>从 Kibana 导入</strong></h3><p>该闭环同样支持反向操作：</p><p>“导入 ID 为 abc-123 的 Kibana 仪表板”</p><p>这会获取现有 Kibana 仪表板，将其 Lens 可视化内容转换回可编辑的图表配置，保留网格布局和分区，并将所有内容加载到 example-mcp-dashbuilder 中。之后，您可以使用自然语言修改该仪表板并重新导出。</p><p>这使得 AI 成为您现有 Kibana 工作流程中的协作者，而不是替代品。</p><h2><strong>自定义主题和颜色</strong></h2><p>想要品牌化仪表板？直接提出需求：</p><p>“创建一个粉色主题的仪表板，并使用自定义颜色”</p><p>所有可视化类型都支持自定义颜色配置：</p><ul><li><p><strong>图表：</strong><code>palette</code> 接受用于系列和扇区的十六进制颜色数组。</p></li><li><p><strong>指标：</strong><code>color</code> 设置背景颜色。</p></li><li><p><strong>热力图：</strong><code>colorRamp</code> 定义从低值到高值的颜色渐变。</p></li></ul><p>AI 能自然理解主题请求。输入“海洋主题”，它会选择蓝色和蓝绿色。输入“匹配我们的品牌颜色”并提供十六进制值，这些颜色会在导出时一并带入 Kibana。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2bc7cddbdef81354/6a17e9a1ec0f89ee155a665e/4aceba013ac9cbb4a541109efd6acddf8a6ec47d-1562x1568.png" alt="采用自定义粉色配色的电商主题仪表板。该布局在顶部显示收入和订单 KPI，中间是一个折叠的趋势分区，下方是两个基于类别的图表：按类别显示收入的条形图，以及按类别显示订单的饼图。" /><p><em>采用自定义颜色的主题仪表板。</em></p><p><strong>example-mcp-dashbuilder 的工作原理：MCP 架构</strong></p><p>example-mcp-dashbuilder 基于 <a href="https://modelcontextprotocol.io/">MCP</a> 构建；MCP 是用于连接 AI 助手与外部工具和数据的开放标准。以下是其高层级架构概览：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c0cd879646e9947/6a17e9a36864a4c408b687df/cbfeabe151ec1ee2b0655f4d17468c9bb358df7e-1024x559.png" alt="该架构图显示 MCP Host 连接到 MCP Server，后者包含工具、资源和指令。下方的 MCP App 框包含 Elastic Charts 和 Kibana 网格布局。Elasticsearch 和 Kibana 显示在底部，并通过箭头连接到 MCP App。" /><p><strong>MCP 服务器</strong>提供 25 个可由 AI 直接调用的工具，涵盖从运行 ES|QL 查询到导出仪表板的各类操作。同时，它还提供少量仅供应用内部调用的工具，供内联预览用于获取数据、持久化布局更改和检测时间字段。它提供三类资源：数据可视化最佳实践指南、ES|QL 参考文档，以及会在遇到开放式提示词（如“分析我的日志”“这个索引中有哪些值得关注的内容？”）时触发的深度分析操作手册。它既可以通过 stdio 运行，也可以通过 HTTP 运行；HTTP 传输支持流式响应和会话管理，因此多个客户端可以连接到同一台服务器。</p><p><strong>MCP App</strong> 是交互式预览界面。它使用 React、<a href="https://elastic.github.io/elastic-charts">Elastic Charts</a> 和 <a href="https://eui.elastic.co/">Elastic UI</a> 构建，并打包成一个独立且自包含的 HTML 文件。当 AI 调用 <code>view_dashboard</code> 或创建图表时，宿主程序会在沙盒化 iframe 中渲染此 HTML。该应用完全通过 <a href="https://modelcontextprotocol.io/extensions/apps/overview">MCP Apps 协议</a>与服务器通信，并通过 postMessage 使用 <code>callServerTool()</code> 来获取数据、保存布局和检测时间字段。无需 localhost 服务器，无需配置端口，也没有外部网络依赖。</p><p>这意味着它可以与任何兼容 MCP 的客户端配合使用：Cursor、Claude Desktop、Claude.ai、VS Code with Copilot 等。</p><h2><strong>example-mcp-dashbuilder 支持哪些图表类型？</strong></h2><p>在撰写本文时，支持六种图表类型，涵盖了最常见的仪表板场景：</p><p>类型</p><p>适用场景</p><p>示例</p><p>条形图</p><p>类别比较</p><p>按地理来源划分的请求数</p><p>折线图</p><p>随时间变化的趋势</p><p>每小时传输的字节数</p><p>区域</p><p>随时间变化的数据量</p><p>随时间变化的请求量</p><p>饼图</p><p>局部与整体的比例关系（最多六个扇区）</p><p>响应代码分布</p><p>指标</p><p>带迷你图的单一 KPI</p><p>总请求数及每小时趋势</p><p>热力图</p><p>跨两个维度的模式</p><p>按星期几和小时划分的请求数</p><p>仪表板支持用于组织内容的可折叠分区、可自动检测时间字段的时间选择器，以及保存多个仪表板并在其间切换的功能；并行聊天会话通过贯穿每次工具调用的 <code>dashboardId</code> 保持相互隔离。</p><h2><strong>如何安装和运行 example-mcp-dashbuilder</strong></h2><p>example-mcp-dashbuilder 是开源项目，可直接使用。您需要 Node.js 22+、一个 Elasticsearch 实例（本地或 Elastic Cloud），以及一个兼容 MCP 的客户端。</p><p><strong>Claude Desktop：</strong>从 <a href="https://github.com/elastic/example-mcp-dashbuilder/releases">GitHub Releases</a> 下载最新 <code>.mcpb</code> 文件，然后双击安装。Claude Desktop 会提示您输入 Elasticsearch 凭据。</p><p><strong>Cursor / Claude Code / VS Code Copilot：</strong>将您的 MCP 配置指向已发布的 tarball 压缩包；无需克隆，也无需 <code>npm install</code>：</p>{
  "mcpServers": {
    "example-mcp-dashbuilder": {
      "type": "stdio",
      "command": "npx",
      "args": ["https://github.com/elastic/example-mcp-dashbuilder/releases/latest/download/example-mcp-dashbuilder.tgz"]
    }
  }
}<p>将 <code>ES_NODE, ES_API_KEY</code>（或 <code>ES_USERNAME / ES_PASSWORD</code>）和 <code>KIBANA_URL</code> 设置为环境变量。如果您更倾向于基于源代码运行，请克隆仓库并运行 <code>npm run setup</code>，以启动交互式向导；该向导可处理本地 Elasticsearch 和 Elastic Cloud（Cloud ID + API 密钥）。</p><p>然后开始构建：</p><p>“探索 logs 索引，并尽可能为我构建最具洞察力的仪表板”</p><p>剩下的交给 AI 即可。😉</p><h2><strong>路线图：example-mcp-dashbuilder 即将推出的功能</strong></h2><p>这是一个早期版本，我们正在积极开发中。我们专注的一些领域：</p><ul><li><p><strong>更多图表类型：</strong>仪表图、环形图、树状图、数据表和标签云，以覆盖 Lens 的完整功能。</p></li><li><p><strong>将仪表板推送到 Git：</strong>将仪表板配置写入代码仓库，用于版本控制和代码审查工作流。</p></li><li><p><strong>更友好的错误处理体验：</strong>当 ES|QL 查询失败时提供更详细的反馈，并给出常见修复建议。</p></li><li><p><strong>更丰富的分析流程：</strong>扩展深度分析操作手册，以覆盖更多数据形态（日志、指标、链路追踪）。</p></li></ul><p>我们很期待看到您用它构建出的成果。欢迎试用、提交 issue，并告诉我们哪些可视化内容和工作流对您的团队最有帮助。</p><p><a href="https://github.com/elastic/example-mcp-dashbuilder">GitHub：elastic/example-mcp-dashbuilder</a></p><h3>致谢</h3><p>感谢 <a href="mailto:walter.rafelsberger@elastic.co">Walter Rafelsberger</a> 和 <a href="mailto:tim.schnell@elastic.co">Tim Schnell</a> 在实现方面作出的贡献。</p><h3>常见问题解答</h3><p><strong>什么是 example-mcp-dashbuilder？</strong>example-mcp-dashbuilder 是一款开源 MCP (Model Context Protocol) 应用，用于将 AI 助手连接到 Elasticsearch。它让您可以用简单英文描述 Kibana 仪表板，并自动生成 ES|QL 查询、创建可视化内容，在编辑器的聊天窗口中交付实时交互式仪表板。</p><p><strong>example-mcp-dashbuilder 使用哪种查询语言来检索数据？</strong>所有数据检索均使用 ES|QL，即 Elasticsearch 的管道式查询语言。MCP 服务器包含一份内置 ES|QL 参考文档，AI 在编写任何查询之前都会先阅读该参考文档，从而确保每种可视化类型都具备正确语法和高效聚合。</p><p><strong>我可以将使用 example-mcp-dashbuilder 构建的仪表板导出到 Kibana 吗？</strong>可以。运行“将此仪表板导出到 Kibana”会将每个面板转换为真正的 Kibana Lens 可视化内容，并保留 ES|QL 查询、48 列网格布局、自定义颜色和系列调色板。最终呈现的是一个功能完整的 Kibana 仪表板，而不是屏幕截图或嵌入内容。</p><p><strong>我可以将现有 Kibana 仪表板导入 example-mcp-dashbuilder，以便进行 AI 辅助编辑吗？</strong>可以。只需提供 Kibana 仪表板 ID，系统便会获取现有仪表板，将其 Lens 可视化内容转换为可编辑的图表配置，并加载到 example-mcp-dashbuilder 中。之后，您可以使用自然语言修改仪表板，并重新导出到 Kibana。</p><p><strong>哪些 MCP 客户端与 example-mcp-dashbuilder 兼容？</strong>example-mcp-dashbuilder 可与任何兼容 MCP 的客户端配合使用，包括 Cursor、Claude Desktop、Claude.ai 和 VS Code with Copilot。它同时支持 stdio 和 HTTP 传输，无需 localhost 服务器或端口配置。</p><p><strong>example-mcp-dashbuilder 支持哪些图表类型？</strong>当前版本支持六种图表类型：条形图、折线图、面积图、饼图、指标图（带迷你图）和热力图。计划新增仪表图、环形图、树状图、数据表和标签云，以覆盖 Kibana Lens 的完整功能。</p><p><strong>运行 example-mcp-dashbuilder 需要什么？</strong>您需要 Node.js 22 或更高版本、一个 Elasticsearch 实例（本地或 Elastic Cloud），以及一个兼容 MCP 的客户端。设置环境变量 ES_NODE、ES_API_KEY（或 ES_USERNAME/ES_PASSWORD）和 KIBANA_URL。对于 Claude Desktop，请从 GitHub Releases 下载 .mcpb 文件，然后双击安装。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/kibana-dashboard-builder-mcp-esql</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/kibana-dashboard-builder-mcp-esql</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Stratoula Kalafateli]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a69a35d6d51ff47/6a17e9a5b1e11339cd79f2b3/0d38385fd64c1445b2e955ba20532570f7f38679-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 解决实体问题，第 4 部分：终极挑战]]></title>
    <description><![CDATA[在专为防止走捷径而设计的高度多样化的“终极挑战”数据集上，解决并评估实体解析挑战。]]></description>
    <content:encoded><![CDATA[<p>我们现在已经看到智能实体解析通过两种方式实现。两种方法都以相同的方式开始：实体准备和提取，然后使用 Elasticsearch 检索候选对象。然后，我们通过基于提示的 JSON 生成或函数调用，使用大语言模型 (LLM) 对这些候选对象进行评估，并要求该模型对其判断做出透明的解释。</p><p>正如我们在<a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">之前的帖子</a>中看到的，函数调用提供的一致性不仅仅是一种不错的优化手段，它还是至关重要的。一旦我们从评估循环中消除了结构性错误，标准场景（例如第 4 层数据集中的场景）的结果便有了显著提升。</p><p>然而，有一个显而易见的问题需要回答：</p><p><em>当情况变得真正复杂时，这种方法还管用吗？</em></p><p>现实中的实体解析很少因简单情况而失败。当名称跨越语言、文化、书写系统、时间段和组织边界时，它就会失效。当人们用头衔而非名字称呼，公司更改名称，音译不一致，且尝试仅凭上下文（而非拼写）提及现实实体时，这种方法就失败了。</p><p>因此，在本系列的最后一篇文章中，我们对该系统进行了所谓的<strong>终极挑战</strong>。</p><h2>是什么让这成为终极挑战？</h2><p>在之前的评估中，我们使用越来越复杂的数据集对该系统进行了测试。当我们达到上一篇帖子中所讨论的第 4 层时，我们已经要应对昵称、头衔、多语言名称以及语义引用的混合情况了。这些测试表明，架构本身是可靠的，但可靠性问题，特别是不规范的 JSON 格式抑制了召回率。</p><p>有了函数调用，我们终于有了稳定的基础。这让我们有机会提出一个更有趣的问题：</p><p><em>一个统一的管道能否同时处理</em><em><strong>多种不同类型</strong></em><em>的实体解析问题？</em></p><p>终极挑战数据集的设计正是为了推动这一层面的发展。</p><p>该数据集并未专注于单一难点（如昵称或音译），而是结合了 <strong>50 多种不同的挑战类型</strong>，包括：</p><ul><li><p>文化命名惯例。</p></li><li><p>基于标题的引用。</p></li><li><p>业务关系和历史名称变更。</p></li><li><p>多语言和跨脚本提及。</p></li><li><p>复合挑战融合了上述多种元素。</p></li></ul><p>关键是，这并不是针对某个狭窄的用例进行优化。这是要测试当规则从一个实体变更到另一个实体时，<em>设计模式</em>是否成立。</p><h2>数据集概览</h2><p>终极挑战数据集包含：</p><ul><li><p><strong>50个实体</strong>，涵盖个人、组织和机构。</p></li><li><p><strong>约 60 篇文章</strong>，结构和语言复杂程度各不相同。</p></li><li><p><strong>51 个不同的挑战类别</strong>，大致分为以下几类：</p><ul><li><p>文化命名惯例。</p></li><li><p>标题和专业背景。</p></li><li><p>商业关系与组织关系。</p></li><li><p>多语言和音译挑战。</p></li><li><p>综合场景和边缘情况场景。</p></li></ul></li></ul><p>在本系列的前几篇文章中，我们看到使用生成式 AI (GenAI) 创建数据集可谓喜忧参半。如果没有它，要收集足够多、足够多样化的测试数据将极其困难。但如果不加以控制，这种模式往往会让事情变得过于简单。</p><p>例如，在早期的一次生成过程中，我们发现模型将“俄罗斯总统”等短语作为弗拉基米尔·普京 (Vladimir Putin) 的明确别名。这在今天看来可能是合理的，但却违背了测试上下文解析的目的。如果文章讨论的是 20 世纪 90 年代的俄罗斯，会发生什么情况？系统应根据上下文推断出正确的实体，而不是依赖于硬编码的别名。</p><p>因此，我们特意设计了这个数据集，以<strong>避免使用捷径</strong>。当系统能够推断出含义时，则不明确列出别名。描述性短语没有预先链接到实体。正确的匹配通常取决于文章层面的上下文，而不仅仅是局部文本。</p><p><strong>重要说明：</strong>尽管我们展示了系统在各种场景下的能力，但这仍是一个具有教育意义的原型。处理真实世界受制裁实体监控的生产系统需要额外的验证、合规性检查、审计跟踪，以及针对敏感用例的专门处理。</p><h2>为什么这些场景很难应对</h2><p>在本系列的第一篇文章中，我们介绍了一个简单但含义模糊的示例：“新的 Swift 更新来了！”挑战在于，“Swift”可以根据上下文解析为现实世界中的多个实体。这个示例反映了一个更广泛的事实：自然语言本质上是模棱两可的。</p><p>因此，实体解析不仅仅是字符串匹配的问题。人们经常依赖共享知识、文化规范和情境背景来解析引用，我们甚至很少注意到我们正在这样做。</p><p>考虑以下几个常见案例：</p><ul><li><p>没有地缘政治和时间背景，“总统”这样的头衔就毫无意义。</p></li><li><p>公司名称可能指母公司、子公司或之前的品牌，具体取决于文章的撰写时间。</p></li><li><p>一个人的名字可能会以不同的顺序、书写系统或音译方式出现，这取决于语言和文化。</p></li><li><p>同一个短语在不同的语境中可以合法地指代不同的实体，系统必须能够像接受匹配短语一样自信地<em>拒绝</em>匹配短语。</p></li></ul><p>没有单一的规则集可以高效地处理所有这些情况。这就是为什么这款原型如此激进地将关注点分开：</p><ul><li><p>Elasticsearch 高效而透明地缩小了候选空间。</p></li><li><p>LLM 仅在需要判断且必须自行解释的情况下使用。</p></li><li><p>检索和推理仍然是两个不同步骤。</p></li></ul><p>随着挑战类型多样性的增加，这种区分变得更加重要。</p><h2>系统如何在无特殊处理的情况下应对多样性</h2><p>这次评估最有趣的结果之一是<em>没有</em>改变的内容：</p><ul><li><p>我们<strong>没有</strong>针对日语名字添加特殊逻辑。</p></li><li><p>我们<strong>没有</strong>为阿拉伯语父名添加自定义规则。</p></li><li><p>我们<strong>没有</strong>添加历史公司名称的硬编码映射。</p></li></ul><p>相反，该系统依赖于系列早期引入的相同核心要素：</p><ul><li><p>为语义搜索编制索引的上下文丰富实体。</p></li><li><p>Elasticsearch 中的混合检索（精确检索、别名检索和语义检索）。</p></li><li><p>一组数量少且定义明确的候选匹配项。</p></li><li><p>受函数调用和最小模式约束的 LLM 判断。</p></li></ul><p>这表明系统的灵活性来自<strong>表征和架构</strong>，而非不断增长的规则集合。</p><p>当系统成功时，是因为检索到了正确的候选对象，且 LLM 有足够的上下文来解释为什么某个引用会（或不会）映射到某个特定实体。</p><h2>结果：它的表现如何？</h2><p>在最终挑战数据集上，系统得出了以下总体结果：</p><ul><li><p><strong>精度：</strong>约 91%</p></li><li><p><strong>召回：</strong> ~86%</p></li><li><p><strong>F1 分数：</strong>约 89%</p></li><li><p><strong>LLM 接受率：</strong>约 72%</p></li></ul><h3>在各类挑战中的表现</h3><p>按挑战类型细分结果可以揭示优势和局限性：</p><p>在以下领域的<strong>表现最为突出（100% F1 分数）</strong>：</p><ul><li><p>跨脚本匹配（西里尔字母、韩文、中文企业实体）。</p></li><li><p>希伯来语场景（父名、职业头衔、宗教头衔、音译）。</p></li><li><p>企业层级（航空航天、多元化制造、多部门公司）。</p></li><li><p>专业头衔（学术、军事、政治、宗教）。</p></li><li><p>涉及多种书写系统的综合日语场景。</p></li></ul><p><strong>表现优异（80–99% F1 分数）</strong>包括：</p><ul><li><p>国际政治人物（98%）。</p></li><li><p>历史名称变更 (90%)。</p></li><li><p>复杂的业务层次结构（89%）。</p></li><li><p>日本公司名称（93%）。</p></li><li><p>跨脚本音译（86%）。</p></li><li><p>阿拉伯语父名（86%）。</p></li></ul><p><strong>更具挑战性的领域</strong>包括：</p><ul><li><p>高级音译（中文、韩文）：0% F1。</p></li><li><p>某些日本场景（敬语、姓名顺序、书写系统变化）：~67% F1。</p></li><li><p>一些阿拉伯语场景（公司名称、机构引用）：约 40% F1。</p></li></ul><p>这里重要的是<em>为什么</em>系统在这些情况下会遇到困难。这些失败并非由于整体方法的崩溃，而是由于特定组件的局限性，尤其是在某些多语言场景中用于语义搜索的密集向量模型。</p><p>由于检索和判断是完全分离的，因此提高性能无需重写系统。更换功能更强大的多语言嵌入模型、丰富实体上下文或改进检索策略，都能在不改变核心架构的情况下改善这些类别的结果。</p><p>从架构的角度来看，这才是真正的成功指标。</p><h2>这告诉我们关于设计的启示</h2><p>回顾整个系列，有几个模式尤为突出：</p><ul><li><p><strong>准备工作比巧妙搭配更重要。</strong>预先为实体添加上下文信息可以显著减少以后可能出现的歧义。</p></li><li><p><strong>LLM 作为评判者最有价值，而非检索者。</strong>让他们解释<em>为什么</em>某个匹配是有意义的，比要求他们进行搜索要有效得多。</p></li><li><p><strong>可靠性确保准确性。</strong>函数调用不仅清理了 JSON，还释放了检索步骤中已存在的召回能力。</p></li><li><p><strong>通用性胜过专业化。</strong>少量经过精心挑选的抽象概念无需自定义逻辑即可处理数十种挑战类型。</p></li></ul><p>这就是为什么原型有意采用 Elasticsearch 原生架构，并在 LLM 的使用上有意采取保守策略的原因。目标不是取代搜索；而是在意义至关重要的情况下，使搜索变得可解释。</p><h2>总结</h2><p>最终的挑战并非追求完美的指标，而是回答一个更根本的问题：</p><p><em>一个透明、搜索优先、LLM 辅助的架构能否处理现实世界中的实体歧义，而不陷入规则或黑箱的情况？</em></p><p>对于这个具有教育意义的原型，答案是肯定的，但需要明确注意生产环境的强化、合规性、监控以及数据质量等方面的问题。如果您正在构建的系统需要说明<em>为什么</em>要进行实体匹配，那么这种模式值得认真考虑。我希望这个系列能告诉人们，实体解析其实并不神秘。只要合理地进行关注点分离，就可以对问题进行推理、评估和优化。</p><p>这项工作还提出了一种更广泛的架构模式。由此出现了经典检索增强生成 (RAG) 的一次细微但重要的演变。我们没有让检索直接为生成提供信息，而是引入了一个明确的评估步骤。首先使用 LLM 对检索到的候选结果进行判断和合理性检查，只有通过审核的结果才允许用于增强生成。您可以将其视为“生成增强型检索增强生成与评估”，或者简称为“GARAGE”，毕竟谁不喜欢一个好听的缩写词呢。</p><p>还有哪些其他用例可以从这种模式中受益？需要信任、透明和可辩护推理的系统是当然的候选者。未来在这一领域的工作应该会像我们在这里看到的结果一样引人注目，我很期待看到社区接下来会有什么新的发展。</p><h2>下一步：亲自试用</h2><p>想看看终极挑战的实际操作吗？请查看<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>终极挑战笔记本</strong></a>，它通过实际实现、详细解释和动手示例，提供了完整的实践指南。</p><p>完整的实体解析管道展示了生产使用所需的核心概念和架构。您可以将其用作构建系统的基础，这些系统可以监测新闻文章、跟踪实体提及情况，并回答有关哪些实体出现在哪些文章中的问题，同时还能保持透明度和可解释性。
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 与 LLM 进行实体解析，第 2 部分：通过 LLM 判断和语义搜索匹配实体]]></title>
    <description><![CDATA[在 Elasticsearch 中使用语义搜索和透明 LLM 判断进行实体解析。]]></description>
    <content:encoded><![CDATA[<p>在<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch"> 第 1 部分</a>中，我们准备了观察清单并提取了实体提及。现在我们准备回答那个难题：一个提及实际指的是哪个实体？让我们回到本系列第一篇博客中的例子，该例子说明了我们为何需要实体解析：“Swift 更新来了！”想象一下，这个标题伴随着一些上下文：</p><ol><li><p>新的 Swift 更新来了！开发人员迫不及待地想要尝试新功能。</p></li><li><p>新的 Swift 更新来了！新专辑将于下个月发布。</p></li></ol><p>有了这些增加的上下文，我们应该能够将名称“Swift”解析到正确的实体。</p><p>在<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">上一篇文章</a>中，我们设置了观察清单，并用额外的上下文丰富了实体信息。看上面的例子，我们需要在清单中至少包含以下两个实体：Taylor Swift 和 Swift 编程语言。我们还介绍了如何从文本中提取实体提及。这两个例子都能提取“Swift”。有了这些要素——丰富的观察清单和提取出的实体——我们终于可以介绍本次的主角了：实体匹配。</p><p><strong>请记住：</strong>这是一个教育原型，旨在教授实体匹配概念。生产系统可能会使用不同的大型语言模型 (LLM)、自定义匹配规则、专门的判断管道或结合多种匹配策略的集成方法。</p><h2>问题：为何匹配如此困难</h2><p>人类语言是一种非凡的事物。它最有趣的特性之一是其无限的创造力。我们可以生成并理解无数的新句子。那么，在实体解析中完全精确的匹配极为罕见，这还奇怪吗？作者们在可能的情况下都力求创新。如果每次提到某个实体时，我们都必须书写和阅读完整名称，那将会变得非常乏味。因此，尽管精确匹配很简单，但现实情况是我们需要一种更复杂的方法来进行实体解析：这种方法必须足够强大，以处理人类作者无限创造力中的至少一部分挑战。这就是我们将问题分解为两个步骤的原因：使用 Elasticsearch 大规模检索可能的候选实体，然后使用 LLM 来判断这些候选实体是否真正指向同一个现实世界中的实体。</p><h2>解决方案：三步匹配与透明的 LLM 判断</h2><p>我们正处于使用计算机方式的范式转变之中。正如互联网的兴起将我们从本地计算带入全球互联网络一样，生成式 AI (GenAI) 正在从根本上改变内容、代码和信息的创建方式。事实上，伴随本系列的教育型原型几乎完全是作者通过精心设计提示词，使用 LLM “vibe coded” 出来的。这并不是说 LLM 已经或将要达到人类语言所固有的那种生产力，但这确实意味着我们现在拥有一个强大的资源来帮助进行实体解析。</p><p>我们在使用生成式 AI 时的一个常见模式是检索增强生成 (RAG)。在这里，<em>检索</em>检索意味着检索实体候选（而不是生成答案），LLM 严格用于匹配评估和解释。虽然我们<em>可以</em>要求 LLM 帮助我们进行端到端的实体解析，但这在时间和金钱上都是一种成本高昂的方法。RAG 通过使用更高效的方式为 LLM 提供上下文，从而帮助 LLM 完成工作，进而使 LLM 能够有效地协助实体解析。</p><p>对于 RAG 中的检索部分，我们再次求助于 Elasticsearch。我们首先使用精确匹配、别名匹配以及结合了关键词和语义搜索的混合搜索来寻找潜在的匹配项。一旦找到这些潜在匹配项，我们就将它们发送给 LLM 进行判断。LLM 充当最终的匹配评估器。我们还让 LLM 解释其推理过程，这是与其他实体解析系统的一个重要区别。没有这些解释，实体解析就是一个黑匣子；有了它们，我们可以亲眼看到为什么某个匹配是合理的。</p><h2>关键概念：三步匹配、混合搜索和透明 LLM 判断</h2><p><strong>什么是三步匹配？</strong>在项目开始时，我们假设语义搜索将是系统的一个关键部分，但并非每个匹配都需要如此复杂的搜索。为了有效地找到匹配项，我们采用了渐进式的方法。首先，我们使用关键词搜索检查完全精确的匹配。如果找到这样的匹配，我们的工作就完成了，可以继续下一个。如果精确匹配失败，我们转向别名匹配。为简化起见，在原型中，别名匹配也是使用关键词进行精确匹配完成的。在生产环境中，您可能会通过标准化、音译规则、模糊匹配或精心管理的别名表来扩展这一步。如果在前两步之后仍未找到潜在的匹配项，那么是时候通过 Elasticsearch 的混合搜索（结合了倒数排序融合）来引入语义搜索了。</p><p><strong>什么是混合搜索？</strong>在 Elasticsearch 中，我们可以使用语义搜索来找到将上下文考虑在内的有意义的匹配。Elasticsearch 广泛用于向量搜索和混合检索。语义相似性对于理解含义非常强大，但它不能替代结构化过滤（例如，按时间范围、位置或标识符过滤），并且在存在精确匹配时通常是不必要的。Elasticsearch 以其词汇搜索而闻名，这在不适合语义搜索的任务中表现出色。为了充分利用这两种方法，我们在单个混合查询中将词汇搜索与语义搜索结合使用。然后，我们使用 RRF 合并结果，以找到最可能的匹配项。在原型中，排名前两位的结果成为可以发送给 LLM 进行判断的潜在匹配项。</p><p><strong>为什么需要 LLM 判断？</strong>LLM 的判断和解释使得我们的系统能够透明地处理歧义和上下文。这对于像“the president”这样可能根据上下文指代多个实体的情况至关重要，但它也使昵称和文化差异等情况在系统中能够很好地处理。最后，当我们考虑关键任务，例如识别制裁名单中的实体时，我们需要知道匹配被接受的原因，才能信任该系统。至关重要的是，LLM 并不搜索整个语料库；它只评估 Elasticsearch 返回的那一小部分候选集。</p><h2>实际结果：通过 LLM 推理进行匹配</h2><p>任何自然语言处理任务的一个主要挑战是创建一份黄金文档，一份告诉我们预期结果是什么的“答案”。没有它，几乎不可能判断一个系统在某个任务上表现如何，但创建这样一份文档可能是一个费力且费时的过程。对于实体解析原型，我们再次求助于生成式 AI 来帮助建立我们可以用来测试的数据。</p><p>我们首先定义了几种挑战类型，例如昵称和音译，然后要求 LLM 创建一个分层的数据集集合，这些数据集将逐渐变大，对系统来说也更具挑战性。数据集的创建并不像人们希望的那样简单。LLM 有一种强烈的“作弊”倾向，使得获取正确答案变得过于容易。例如，其中一种挑战类型侧重于语义上下文。这种类型包括将“Russian author”解读为“Leo Tolstoy”。LLM 错误地将“Russian author”作为“Leo Tolstoy”的一个别名，这就没有必要通过混合搜索来寻找匹配项了。</p><p>在进行了几次重构以修复此类问题后，我们有了五个可供使用的数据集层级。第 1-4 层规模逐渐增大，包含的挑战类型也更多。第 5 层是“终极挑战”数据集，由所有挑战类型中最棘手的例子组成。所有测试数据都可以在<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">全面评估目录</a>中找到。</p><p>为了评估我们基于提示的实体解析方法，我们将注意力集中在第 4 层数据集上。一个重要的说明是，评估是作为受控实验进行的，这样我们可以专注于实体匹配的质量。观察清单数据预先丰富了上下文，并且实体是提前从文章中提取出来的。这确保了评估的重点是匹配而非提取的准确性。这将匹配质量孤立出来；端到端的性能还将额外取决于提取的召回率和丰富数据的质量。</p><h3>评估数据集</h3><p>第 4 层评估数据集对系统的能力提供了一个全面的测试：[1]</p><ul><li><p><strong>观察清单实体：</strong>跨不同类型（人物、组织、地点）的 66 个实体。</p></li><li><p><strong>测试文章：</strong>69 篇涵盖现实世界实体解析场景的文章。</p></li><li><p><strong>预期匹配：</strong>所有文章中预期的 206 个实体匹配。</p></li><li><p><strong>挑战类型：</strong>测试实体解析各个方面的 15 种不同挑战类型。</p></li></ul><p>数据集中包含的挑战类型有：</p><ul><li><p><strong>昵称：</strong>“Bob Smith” → “Robert Smith”（七篇文章）。</p></li><li><p><strong>头衔和尊称：</strong> “Dr. Sarah Williams” → “Sarah Williams”（五篇文章）。</p></li><li><p><strong>语义上下文：</strong>“Russian author” → “Leo Tolstoy”（八篇文章）。</p></li><li><p><strong>多语言名字：</strong>处理不同书写系统中的名称（六篇文章）。</p></li><li><p><strong>商业实体：</strong> 公司名称变体（七篇文章）。</p></li><li><p><strong>高管引用：</strong>“Microsoft CEO”→“Satya Nadella”（五篇文章）。</p></li><li><p><strong>政治领导人：</strong>基于头衔的引用（五篇文章）。</p></li><li><p><strong>名称首字母：</strong>“J.Smith” → “John Smith”（三篇文章）。</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><li><p><strong>组合挑战：</strong>一篇文章中包含多个挑战（共六篇文章）。</p></li><li><p><strong>复杂商业关系：</strong>分层商业关系（五篇文章）。</p></li></ul><p>让我们看看基于提示的实体解析表现如何。</p><h3>整体性能</h3><p>结果显示，由 LLM 驱动的匹配评估前景广阔，但也揭示了一个显著的可靠性问题。因为每个候选对都必须由 LLM 进行评估，结构化输出的失败可能会抑制接受率和召回率，即使检索环节工作正常。</p><p>指标</p><p>值</p><p>精确率</p><p>83.8%</p><p>召回</p><p>62.6%</p><p>F1 分数</p><p>71.7%</p><p>找到的总匹配数</p><p>344</p><p>LLM 接受率</p><p>44.8%</p><p>错误率</p><p>30.2%</p><h3>错误率问题</h3><p>回顾一下，我们在原型中采取的第一步是使用 Elasticsearch 创建潜在的匹配对。每个这样的潜在匹配都需要由 LLM 进行评估。为了高效地处理所有这些匹配项，我们将 LLM 调用批量组合在一起。这降低了 API 成本和延迟，但也增加了在输出中得到格式错误 JSON 的风险。随着批量大小的增加，JSON 变得更长、更复杂，使得 LLM 更有可能生成无效的 JSON。这就是 30% 错误率的来源。在评估中，我们每个请求使用 5 个匹配项的批量大小。即使采用这个保守的批量大小，我们仍然遇到 JSON 解析失败的情况，这显著地影响了评估结果。</p><h2>下一步：优化 LLM 集成</h2><p>现在，我们已经使用语义搜索和 LLM 判断匹配了实体，我们拥有了一个完整的实体解析管道。然而，这种方法引入了一种新的故障模式：当模型的判断正确，但其输出却不可用时。我们可以优化 LLM 集成以获得更好的可靠性和成本效益。在下一篇文章中，我们将探讨如何使用函数调用来实现结构化输出，这可以在减少错误和成本的同时，提供有保障的结构和类型安全。</p><h2>亲自试用</h2><p>想亲眼看看实体匹配是如何运作的吗？请查看<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">实体匹配笔记本</a>，它通过实际实现、详细解释和动手示例，提供了完整的实践指南。该笔记本精确地向您展示了如何使用三步搜索、带有 RRF 的混合搜索以及由 LLM 驱动的带推理的判断来匹配实体。</p><p><strong>请记住：</strong>这是一个教育原型，旨在教授这些概念。在构建生产系统时，需要考虑额外的因素，如模型选择、成本优化、延迟要求、质量验证、错误处理和监控等，而这些在本学习重点的原型中并未涵盖。</p><h2>备注</h2><ol><li><p>这些数据集是合成的，专为教育目的设计；它们模拟了真实的挑战，但不代表任何单一的生产环境领域。</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefc59243d9990405/6a17056ab339d5778f769ebf/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[在 Streams 中利用机器学习自动化日志解析]]></title>
    <description><![CDATA[了解一种混合 ML 方法如何在 Streams 中结合日志格式指纹开展自动化实验，实现 94% 的日志解析准确率和 91% 的日志分区准确率。]]></description>
    <content:encoded><![CDATA[<p>在现代可观测性技术栈中，将来自不同数据源的非结构化日志摄入 Elasticsearch 等平台仍是一项挑战。依赖人工编写的解析规则会让数据管道变得脆弱 — 即使上游代码只有少量更新，也可能导致解析失败、数据无法建立索引。这种脆弱性还会因可扩展性问题而进一步恶化，在动态的微服务环境中，新服务不断加入，手动维护规则很快就会变成运维噩梦。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8f5bd0e4986b04c/6a170e6acdacbf612e7d2a9e/9108ec303339dd091faa3c363c7cf5c228155f49-3840x2160.png" alt="" /><p>我们的目标是转向一种自动化、自适应的方法，能够同时处理日志解析（字段提取）和日志分区（来源识别）。我们假设，大语言模型（LLM）凭借对代码语法与语义模式的理解，能够在最少人工干预的情况下自动处理这些任务。</p><p>我们很高兴地宣布，此功能已在 <a href="http://elastic.co/elasticsearch/streams"><u>Streams</u></a> 中正式推出！</p><h2>数据集描述</h2><p>我们选择了<a href="https://github.com/logpai/loghub"><strong>Loghub</strong></a>日志集合用于概念验证。我们的调查从以下关键领域选取了代表性样本：</p><ul><li><p>分布式系统：我们使用了 Hadoop 分布式文件系统 (HDFS) 和 Spark 数据集。这些日志混合了大数据平台典型的信息、调试和错误消息。</p></li><li><p>服务器与 Web 应用：Apache Web 服务器和 OpenSSH 的日志提供了访问、错误以及与安全相关事件的重要信息来源，这对于监控 Web 流量和检测潜在威胁至关重要。</p></li><li><p>操作系统：我们纳入了 Linux 和 Windows 日志。这些数据集代表了运维团队日常处理的常见、半结构化系统级事件。</p></li><li><p>移动系统：为确保模型能处理移动环境日志，我们加入了 Android 数据集。这些日志通常较为冗长，涵盖了移动设备上广泛的应用程序和系统级活动。</p></li><li><p>超级计算机：为测试在高性能计算环境下的表现，我们引入了 BGL 数据集，其特点是包含使用特定领域术语的高度结构化日志。</p></li></ul><p>Loghub 集合的一个关键优势在于，其日志基本未经清洗处理和标注，真实模拟了具有微服务架构的、嘈杂的线上生产环境。</p><p>日志示例：</p>[Sun Dec 04 20:34:21 2005] [notice] jk2_init() Found child 2008 in scoreboard slot 6
[Sun Dec 04 20:34:25 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Mon Dec 05 11:06:51 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
17/06/09 20:10:58 INFO output.FileOutputCommitter: Saved output of task 'attempt_201706092018_0024_m_000083_1138' to hdfs://10.10.34.11:9000/pjhe/test/1/_temporary/0/task_201706092018_0024_m_000083
17/06/09 20:10:58 INFO mapred.SparkHadoopMapRedUtil: attempt_201706092018_0024_m_000083_1138: Committed<p>此外，我们还搭建了一个包含典型 Web 应用与数据库的 Kubernetes 集群，用于在最常见的场景中采集更多日志。</p><p>常见日志字段示例：时间戳、日志级别（INFO、WARN、ERROR）、来源、消息内容。</p><h2>使用 LLM 进行少样本日志解析</h2><p>我们的首轮实验聚焦于一个根本问题：<strong>LLM 能否可靠地识别关键字段，并生成一致的解析规则来提取它们？</strong></p><p>我们要求模型分析原始日志样本，并以正则表达式和 <a href="https://www.elastic.co/docs/explore-analyze/scripting/grok">Grok</a> 格式生成解析规则。结果显示，此方法潜力巨大，但也面临显著的实现挑战。</p><h3>高置信度与上下文感知</h3><p>初步结果令人鼓舞。LLM 展现出强大的能力，能高置信度地生成与提供的少数样本相匹配的解析规则。除了简单的模式匹配，模型还展现出对日志的理解能力 — 它能正确识别并命名产生日志的来源服务（例如健康追踪应用、Nginx Web 应用、Mongo 数据库）。</p><h3>输入样本的“恰到好处”困境</h3><p>我们的实验很快暴露出一个明显的鲁棒性问题，即<strong>对输入样本极其敏感</strong>。模型的性能会根据提示中包含的具体日志样本而剧烈波动。我们观察到一个日志相似性难题：样本里的日志需要达到<em>适中的多样性水平</em>，从而避免：</p><ul><li><p>过于同质（过拟合）<strong>：</strong>如果输入日志过于相似，LLM 倾向于<strong>过度具体化</strong>。它会把可变数据（例如堆栈跟踪里的具体 Java 类名）当成模板的固定部分。这导致生成的规则非常脆弱，只能覆盖极少部分日志，并提取出无用的字段。</p></li><li><p>过于异质（困惑）：反之，如果样本包含显著的格式差异（或更糟，包含了“垃圾日志”），模型就难以找到共同模式。它往往会生成复杂但有缺陷的正则表达式，或直接将整行内容过度泛化为一个单一的消息块字段。</p></li></ul><h3>上下文窗口限制</h3><p>我们还遇到了上下文窗口瓶颈。当输入日志较长、异构或包含大量可提取字段时，模型的输出质量常常会下降，变得“混乱”或过长而超出输出上下文窗口。在这种情况下，分块会有所帮助。通过使用基于字符和基于实体的分隔符来分割日志，我们可以帮助模型专注于提取主要字段，而不被噪声淹没。</p><h3>一致性与标准化差距</h3><p>即使模型成功生成规则，我们也注意到一些细微的不一致：</p><ul><li><p>服务命名差异：模型在不同运行中会对同一实体使用不同名称（例如将来源标记为“Spark”“Apache Spark”“Spark Log Analytics”）。</p></li><li><p>字段命名差异：字段名称缺乏标准化（例如，<code>id</code> vs. <code>service.id</code> vs. <code>device.id</code>）。我们使用标准化的 <a href="https://www.elastic.co/docs/reference/ecs/ecs-field-reference">Elastic 字段命名规范</a>对名称进行了统一。</p></li><li><p>解析粒度差异：字段提取的粒度因输入日志之间的相似程度而异。</p></li></ul><h2>日志格式指纹</h2><p>为了解决日志相似性问题，我们引入了一种高性能的启发式方法：<strong>日志格式指纹（LFF）</strong>。</p><p>我们不再将原始、嘈杂的日志直接输入 LLM，而是首先应用一种确定性转换来揭示每条消息的底层结构。这个预处理步骤抽象掉变量数据，生成一个简化的“指纹”，使我们能够对相关日志进行分组。</p><p>映射逻辑很简单，以确保速度和一致性：</p><ol><li><p>数字抽象：任何数字序列（0–9）都会替换为单个“0”。</p></li><li><p>文本抽象：任何由字母字符及其间空白组成的序列都会替换为单个“a”。</p></li><li><p>空白字符规范化：所有空白字符序列被压缩为单个空格。</p></li><li><p>符号保留：标点符号和特殊字符被保留，因为它们通常是日志结构最有力的指示符。</p></li></ol><p>我们引入了日志映射方法。基本映射模式包括以下几种：</p><ul><li><p>任意长度的数字（0–9）→ 替换为单个“0”。</p></li><li><p>任意长度的文本（字母字符及空白）→ 替换为单个“a”。</p></li><li><p>空格、制表符和换行符 → 合并为一个空格。</p></li></ul><p>让我们看一个这种映射如何转换日志的例子。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf91eebab0ad79ccd/6a170e6c67045ba94f45c29c/78fa2887486eb9417804354ee3bf2a4fdb0f6383-846x252.png" alt="" /><p>因此我们得到如下日志“掩码”（指纹）：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt438d74dcb921578b/6a170e6d1949f74aa0e7aae3/ec439a3d3a25002498b97defcff733ea5ebc6b55-826x94.png" alt="" /><p>请注意前两个日志的指纹。尽管时间戳、来源类名和消息内容不同，但它们的前缀（<code>0/0/0 0:0:0 a a.a:</code>）完全一致。这种结构上的一致性使我们能够自动将这些日志归入同一个聚类。这种结构上的一致性使我们能自动把这些日志分桶到同一个聚类中。</p><p>第三个日志会生成完全不同的指纹（<code>0-0-0...</code>），这使我们能够在调用 LLM 之前就用算法将其与第一组区分开来。这使我们在调用LLM <em>之前</em> ，通过算法将其与第一组分离。</p><h2>奖励部分：使用 ES|QL 进行即时实施</h2><p>在 Discover 中运行这条查询就能做到这一点，非常简单。</p><p><strong>查询解析：</strong></p><p><strong>FROM</strong> loghub：指向包含原始日志数据的索引。</p><p><strong>EVAL</strong> pattern =…：核心映射逻辑。我们通过链式 REPLACE 函数执行抽象化处理（例如将数字替换为“0”、文本替换为“a”等），并将结果保存至“pattern”字段。</p><p><strong>STATS </strong>[column1 =] expression1, …<strong> BY </strong>SUBSTRING(pattern, 0, 15):</p><p>这是一个集群步骤。我们将具有前 15 个字符相同的日志进行分组，并创建聚合字段，例如每组的日志总数、日志数据源列表、模式前缀以及 3 条日志示例。</p><p><strong>SORT</strong> total_count DESC | <strong>LIMIT</strong> 100：显示出现频率最高的前 100 个日志模式</p><p>查询结果的可视化如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa3960cf94ccf331/6a170e6fdc55decfa3e00e7c/b119498f124376c41d242a099bf9081fd6536be8-1600x394.png" alt="LogHub 上的日志解析查询结果。" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dbcde2a22e06367/6a170e71961e693a18c4cfb6/4dcfc0a5b7fa753497cc5def5ea3cd54449c0481-1600x719.png" alt="" /><p>如可视化所示，这种“无需 LLM”的方法能够以很高的准确率对日志进行分区/归因分组。它（基于 LogHub 标签）在 16 个数据源中有 10 个实现了几乎完全的聚类（&gt;90%），并在 16 个数据源中的 13 个实现了多数聚类（&gt;60%），且无需额外清洗、预处理或微调。</p><p>日志格式指纹为<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-categorize-text-aggregation">日志模式分析</a>等复杂的 ML 解决方案提供了一种务实、高效的替代与补充方案。它能立即洞察日志间的关系，并有效管理大型日志集群。</p><ul><li><p>作为基础组件的多功能性 </p></li></ul><p>借助 <a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">ES|QL</a> 实现，LFF 既可作为独立工具用于快速数据诊断/可视化，也可作为日志分析流水线中的基础构件，支撑高吞吐量场景。 </p><ul><li><p>灵活性</p></li></ul><p>LFF 易于定制和扩展以捕获特定模式，例如十六进制数和 IP 地址。</p><ul><li><p>确定性稳定性</p></li></ul><p>与基于 ML 的聚类算法不同，LFF 逻辑简单且确定。新传入的日志不会追溯性地影响现有的日志聚类。</p><ul><li><p>性能与内存</p></li></ul><p>它需要最少的内存，无需训练或 GPU，非常适合实时高吞吐量环境。</p><h2>结合日志格式指纹与 LLM</h2><p>为了验证所提出的混合架构，每个实验都包含来自每个数据源的日志的随机 20% 子集。此约束模拟了现实世界的生产环境，在该环境中，日志是批量处理的，而不是作为一个整体的历史转储进行处理。</p><p>目标是证明 LFF 能作为有效的压缩层。我们希望证明，即使只用少量经过筛选的样本，也能生成高覆盖率的解析规则，并成功泛化到整个数据集。</p><h2>执行管道</h2><p>我们实现了一个多阶段流程，在数据到达 LLM 之前对其进行过滤、聚类和应用分层抽样。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26635762891b3a41/6a170e73509168eea4e1bb91/b3f46ea471760b406a32fc7d4bc74cc03faaced2-3840x1660.png" alt="" /><p>1. 两阶段分层聚类</p><ul><li><p>子类（精确匹配）：通过完全相同的指纹对日志进行聚合。同一子类的每个日志共享完全相同的格式结构。</p></li><li><p>异常值清理：丢弃占总日志量少于 5% 的任何子类，这确保 LLM 聚焦于主要信号，不会被噪声或格式异常的日志带偏。</p></li><li><p>元类（前缀匹配）：剩余的子类通过格式指纹的前 N 个字符匹配分组到元类中。这种分组策略可有效将词汇相似的格式归并到同一个大类下。当数据源未知时，我们选择 N=5 用于日志解析，N=15 用于数据源未知时的日志分区。</p></li></ul><p>2. 分层抽样。一旦分层树构建完成，我们为 LLM 构建日志样本。战略目标是最大化方差覆盖，同时最小化 Token 使用。</p><ul><li><p>我们从更广泛的元类中，为<em>每个</em>有效子类选取具有代表性的日志。</p></li><li><p>为处理子类过多的边缘情况，应用随机下采样以适应目标窗口大小。</p></li></ul><p>3. 规则生成：最后，我们提示 LLM 为每个元类生成一个适用于所提供样本中所有日志的正则表达式解析规则。在概念验证中，我们使用了 GPT-4o mini 模型。</p><h2>实验结果与观察</h2><p>我们在 Loghub 数据集上实现了 94% 的解析准确率和 91% 的分区准确率。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b896b41b3b70e7e/6a170e757d8d67601a70e7d9/49b2b6a1401dd1f33951da68e5a3fac37d0b5aaa-1600x1506.png" alt="Loghub 数据集的解析准确率为 94%，分区准确率为 91%。" /><p>混淆矩阵展示了日志分区结果。垂直轴代表实际数据源，水平轴代表预测的数据源。热图颜色深浅对应日志量，颜色越浅表示数量越多。对角线排列显示了模型在来源归因上的高保真度，且分散极少。</p><h2>我们的性能基准测试洞察：</h2><ul><li><p><strong>最佳基线：</strong>每个类别 <strong>30–40 条日志样本</strong>的上下文窗口被证明是“最佳区间”，能稳定生成稳健的 Regex 与 Grok 解析模式。</p></li><li><p><strong>输入最小化：</strong>我们将每个类别的输入大小推至 10 个日志（用于正则表达式模式），仅观察到解析性能下降 2%，这证实了基于多样性的抽样比原始数量更为关键。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/log-parsing-partitioning-automation-experiments-streams</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/log-parsing-partitioning-automation-experiments-streams</guid>
    <category><![CDATA[ML 研究]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Nastia Havriushenko]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1df5a7cae463d59/6a170e76a6c2b907d7e797ab/965c58f19742361160593c38fcaa8b2f4b0d6cc5-3838x2159.png" length="0" type="image/png"/>
    <pubDate>Fri, 02 Jan 2026 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[利用弹性代理生成器和 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[了解 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[在 Elasticsearch 中为结构化文档配置递归分块]]></title>
    <description><![CDATA[了解如何在 Elasticsearch 中使用分块大小、分隔符组和自定义分隔符列表配置递归分块，以优化结构文档索引。]]></description>
    <content:encoded><![CDATA[<p>自 8.16 版起，用户可以配置将长文档导入语义文本字段时使用的分块策略。从 9.1 / 8.19 版开始，我们引入了一种新的可配置递归分块策略，使用正则表达式列表对文档进行分块。分块的目的是将长文档分割成囊括相关内容的部分。我们现有的策略会按单词/句子的粒度分割文本，但以结构化格式编写的文档（例如："......"）则不会这样做。Markdown）通常会在由一些分隔字符串定义的部分内包含相关内容（例如："......"）。标题）。对于这些类型的文档，我们正在引入递归分块策略，以利用结构化文档的格式来创建更好的分块！</p><h2>什么是递归分块？</h2><p>递归分块法会遍历所提供的分块模式列表，逐步将文档分成更小的分块，直到达到所需的最大分块大小。</p><h3>如何配置递归分块？</h3><p>以下是用户为递归分块提供的可配置值：</p><ul><li><p>(必填）<code>max_chunk_size</code> ：字块中的最大字数。</p></li><li><p>任选其一：</p><ul><li><p><code>separators</code>:用于将文档分割成块的 regex 字符串模式列表。</p></li><li><p><code>separator_group</code>:一个字符串，它将映射到 Elastic 定义的默认分隔符列表，用于特定类型的文档。目前，<code>markdown</code> 和<code>plaintext</code> 。</p></li></ul></li></ul><h3>递归分块是如何工作的？</h3><p>递归分块的过程如下：给定输入文档、<code>max_chunk_size</code> （以字数为单位）和分隔符字符串列表：</p><ol><li><p>如果输入文档已经在最大分块大小范围内，则返回一个涵盖整个输入文档的分块。</p></li><li><p>根据分隔符的出现次数，将文本分割成潜在的文本块。对于每个潜在的数据块</p><ol><li><p>如果潜在数据块在最大数据块大小范围内，则将其添加到要返回给用户的数据块列表中。</p></li><li><p>否则，从第 2 步开始重复，只使用潜在文本块中的文本，并使用列表中的下一个分隔符进行分割。如果没有其他分隔符可以尝试，就退回到基于句子的分块。</p></li></ol></li></ol><h2>配置递归分块的示例</h2><p>除了分块大小，递归分块的主要配置是选择应使用哪些分隔符来分割文档。如果您不确定从哪里开始，Elasticsearch 提供了一些默认的分离器组，可用于常见的使用情况。</p><h3>利用分离器组</h3><p>要使用分隔组，只需在配置分块设置时提供要使用的组名即可。例如</p>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separator_group": "plaintext"
}<p>这样就可以利用分隔符列表<code>["(?&lt;!\\n)\\n\\n(?!\\n)", "(?&lt;!\\n)\\n(?!\\n)")]</code> 来实现递归分块策略。对于一般的纯文本应用程序，这种方法效果很好，可以在 2 个换行符后再分隔出 1 个换行符。</p><p>我们还提供一个分隔符组<code>markdown</code> ，它将利用分隔符列表：</p>[
"\n# ",
       "\n## ",
       "\n### ",
       "\n#### ",
       "\n##### ",
       "\n###### ",
       "\n^(?!\\s*$).*\\n-{1,}\\n",
       "\n^(?!\\s*$).*\\n={1,}\\n"
]<p>这个分隔符列表可以很好地适用于一般的标记符使用情况，在 6 个标题层次和分节符上分别进行分隔。</p><p>创建资源（推理端点/语义文本字段）时，与当时分隔符组相对应的分隔符列表将存储在您的配置中。如果以后更新了分隔符组，也不会改变已创建资源的行为。</p><h3>使用自定义分隔符列表</h3><p>如果预定义的分隔符组不适合您的使用情况，您可以定义一个符合您需求的自定义分隔符列表。请注意，可以在分隔符列表中提供正则表达式。以下是使用自定义分隔符配置分块设置的示例：</p>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n\n", "\n", "&lt;my-custom-separator&gt;"]
}<p>上述分块策略将在 2 个换行符、1 个换行符和一个字符串<code>“&lt;my-custom-separator&gt;”</code> 上进行分割。</p><h2>递归分块的实际应用示例</h2><p>让我们来看一个递归分块的实例。在本示例中，我们将使用以下分块设置和自定义分隔符列表，使用顶部两层标题分割标记符文档：</p>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n# ", "\n## "]
}<p>让我们来看看一个简单的未分块 Markdown 文档：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb5f41d1bd43ba50/6a17e831e9ea87c1d8a9c5f3/3a5507f4a1288065097231548e5b18e240508785-1302x1446.png" alt="未分块的 Markdown 文档" /><p>现在，让我们使用上面定义的分块设置对文档进行分块：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfffda162c7b9c87a/6a17e83296142aefa8eb1b0b/a3313c4c40ff39b8dbcdd7c4878c723f088e6c1a-1600x1187.png" alt="在 Elasticsearch 中将文档分块" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96f65346a8e09e3a/6a17e834445de9157b4d015e/79a2921943191ea631df94c9d465818ec8d3e738-1600x1206.png" alt="在第二个分隔符上拆分--在 Elasticsearch 中将文档分块" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28381c8f85aedf07/6a17e836ec0f89801e5a6640/459e695cce7540267422396b9a62ff4ad35f61db-1600x1260.png" alt="Elasticsearch 中基于句子的分块处理后文档中的最终分块" /><p>注意：每个分块（分块 3 除外）末尾的换行符不会突出显示，而是包含在实际分块边界内。</p><h3>今天就开始使用递归分块技术！</h3><p>有关使用该功能的更多信息，请查看有关配置分块设置的文档。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/recursive-chunking-structured-documents-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/recursive-chunking-structured-documents-elasticsearch</guid>
    <category><![CDATA[基础功能]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Daniel Rubinstein]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf442dc4941f37be7/6a17e838505ac3eaf8ad8b3d/591872e31880768ca927507654a621addc0d124d-1600x960.png" length="0" type="image/png"/>
    <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 和 SigLIP-2 对山峰进行多模式搜索 ]]></title>
    <description><![CDATA[了解如何使用 SigLIP-2 嵌入和 Elasticsearch kNN 向量搜索实现文本到图像和图像到图像的多模态搜索。项目重点：寻找珠峰徒步旅行中拍摄的阿玛达布拉姆峰照片。]]></description>
    <content:encoded><![CDATA[<p>您是否曾想过按含义搜索相册？试着询问 "给我看我穿着蓝色夹克坐在长椅上的照片"、"给我看珠穆朗玛峰的照片 "或 "清酒和寿司"。喝杯咖啡（或您最喜欢的饮料），继续阅读。在本博客中，我们将向您展示如何构建多模态混合搜索应用程序。多模态是指应用程序可以理解和搜索不同类型的输入（文本、图像和音频），而不仅仅是文字。混合式意味着它结合了关键词匹配、kNN 向量搜索和地理围栏等技术，以提供更清晰的结果。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="来自珠穆朗玛峰徒步旅行的不同山峰照片库。" /><p>为此，我们使用谷歌的 SigLIP-2 为图像和文本生成矢量嵌入，并将其存储在 Elasticsearch 矢量数据库中。在查询时，我们将搜索输入（文本或图像）转换为嵌入，并运行快速的 kNN 向量搜索来检索结果。这种设置可实现高效的文本到图像和图像到图像搜索。Streamlit 用户界面为我们提供了一个前端，不仅可以进行基于文本的搜索，从相册中查找并查看匹配的照片，还可以从上传的图片中识别山峰，并查看相册中该山峰的其他照片，从而使该项目栩栩如生。我们还介绍了为提高搜索准确性而采取的措施，以及实用技巧和窍门。为便于进一步探索，我们提供了<a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub 存储库</a>和<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab 笔记本</a>。</p><h2>如何开始</h2><p>这篇博文的灵感来自于一个 10 岁的孩子，他让我给他们看我在珠峰大本营徒步旅行时拍摄的阿玛达布拉姆山的所有照片。在翻阅相册时，我还被要求辨认其他几座山峰，其中一些我还叫不出名字。</p><p>这让我想到，这可以成为一个有趣的计算机视觉项目。我们的目标</p><ul><li><p>按名称查找山峰图片</p></li><li><p>从图片中猜测山峰名称，并在相册中找到类似的山峰</p></li><li><p>让概念查询发挥作用<em>（人</em>、<em>河流</em>、<em>祈祷旗</em> <em>等）</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="阿玛-达布拉姆山 " /><h2>组建梦之队：SigLIP-2、Elasticsearch&amp; Streamlit</h2><p>很快我们就发现，要想实现这一目标，我们需要将文字（"阿玛达布拉姆"）和图像（我相册中的照片）都转化为可以进行有意义比较的矢量，即在同一个矢量空间中。一旦我们做到了这一点，搜索就只是 "寻找最近的邻居"。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2、Elasticsearch&amp; Streamlit--梦之队。" /><p>为了生成图像嵌入，我们使用了多语言<a href="https://huggingface.co/blog/vlms-2025"> 视觉语言编码器</a>，因此山峰的照片和 "Ama Dablam "这样的短语会出现在同一个向量空间中。</p><p>谷歌最近发布的<a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a> 在这方面非常适合。它可以在没有特定任务训练的情况下生成嵌入式（<strong>零镜头</strong>设置），并能很好地适用于我们的使用案例：未标记的照片和具有不同名称和语言的山峰。由于它是针对文本与图像匹配进行训练的，因此即使查询语言或拼写不同，徒步旅行中的山峰图片和简短的文字提示最终也能接近嵌入。</p><p>SigLIP-2 在质量与速度之间实现了很好的平衡，支持多种输入分辨率，并可在 CPU 和 GPU 上运行。SigLIP-2 在设计上比以前的型号（如最初的 CLIP）更适合户外拍摄。在我们的测试中，SigLIP-2 始终能生成可靠的结果。此外，它还得到了很好的支持，因此是本项目的不二之选。</p><p>接下来，我们需要一个向量数据库来存储嵌入和强力搜索。它不仅应支持对图像嵌入进行余弦 kNN 搜索，还应在单个查询中应用地理围栏和文本过滤器。Elasticsearch 在这方面非常适合：它能很好地处理向量（在 dense_vector 字段上使用 HNSW kNN），支持结合文本、向量和地理查询的混合搜索，并提供开箱即用的过滤和排序功能。它还可以横向扩展，因此很容易从少量照片扩展到数千张照片。<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python"></a>最后，我们需要一个轻量级前端，以便输入搜索查询并查看结果。对于基于 Python 的快速演示，Streamlit 非常适合。它提供了我们所需的基本功能--文件上传、响应式图像网格以及用于排序和地理围栏的下拉菜单。它很容易克隆并在本地运行，也可以在 Colab 笔记本中使用。</p><h2>实施</h2><h3>Elasticsearch 索引设计和索引策略</h3><p>我们将在这个项目中使用两个索引：<code>peaks_catalog</code> 和<code>photos</code> 。</p><h4>峰值_目录索引</h4><p>该索引是珠峰大本营徒步旅行期间可看到的著名山峰的简明目录。该索引中的每份文件都对应一座山峰，如珠穆朗玛峰。对于每个山峰文档，我们都会存储名称/别名、可选的经纬度坐标以及由 SigLIP-2 文本提示（+ 可选的参考图片）混合而成的单一原型向量。</p><p><strong>索引映射：</strong></p><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>本我</p><p>关键词</p><p>阿玛-达布拉姆</p><p>稳定的弹头/ID</p><p>-</p><p>姓名</p><p>文本 + 关键字子字段</p><p>["Ama Dablam","Amadablam"]</p><p>别名/多语言名称；names.raw 用于精确筛选</p><p>-</p><p>纬纶</p><p>地理点</p><p>{"lat":27.8617,"lon":86.8614}</p><p>以经纬度组合形式显示的山顶 GPS 坐标（可选）</p><p>-</p><p>海拔_m</p><p>整数</p><p>6812</p><p>海拔（可选）</p><p>-</p><p>嵌入文本</p><p>dense_vector</p><p>768</p><p>该山峰的混合原型（提示和可选的 1-3 幅参考图片</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>该索引主要用于图像到图像的搜索，例如从图像中识别山峰。我们还使用该索引来增强文本到图片的搜索结果。</p><p>总之，<code>peaks_catalog</code> 将问题""这是什么山？" "转化为一个重点突出的 "最近邻问题"，有效地将概念理解与图像数据的复杂性分离开来。</p><p><strong>peaks_catalog 索引的索引策略： </strong>首先，我们创建了一份在 EBC 徒步旅行中可见的最突出山峰的列表。对于每个山峰，我们都会在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml 文件</a>中存储其地理位置、名称、同义词和海拔高度。下一步是<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351"> 生成</a> 每个峰值的 嵌入 值，并将其存储在<code>text_embed</code> 字段中。为了生成稳健的嵌入，我们使用了以下技术：</p><ul><li><p>创建文本原型：</p><ul><li><p>山峰名称</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">提示组合</a>（使用多个不同的提示来尝试回答同一个问题），例如</p><ul><li><p>"尼泊尔喜马拉雅山脉山峰的自然照片{name} "</p></li><li><p>"{name} 昆布地区的地标性山峰，高山景观"</p></li><li><p>"{name} 山顶，积雪，岩石山脊线"</p></li></ul></li><li><p>可选的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">反概念</a>（告诉 SigLIP-2 什么不能匹配）：为 "绘画、插图、海报、地图、徽标 "减去一个小矢量，这样我们就偏向于真实照片。</p></li></ul></li><li><p>如果提供了峰值的参考图像，可选择<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">创建图像原型</a>。</p></li></ul><p>然后，我们<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">混合文本和图像原型</a>，生成最终的嵌入。最后，文件将被<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">索引到</a>所有必填字段：</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

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

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

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

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

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

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p><code>peaks_catalog</code> 索引中的文件样本：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="来自 Elasticsearch 中 peaks_catalog 索引的示例文档。" /><h4>照片索引</h4><p>该主索引存储相册中所有照片的详细信息。每份文档代表一张照片，包含以下信息：</p><ul><li><p>相册中照片的相对路径。可用于查看匹配图像或在搜索用户界面中加载图像。</p></li><li><p>图片的 GPS 和时间信息。</p></li><li><p>SigLIP-2 生成的图像编码密集矢量。</p></li><li><p><code>predicted_peaks</code> 可让我们根据峰名进行筛选。<strong>索引映射</strong></p></li></ul><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>路径</p><p>关键词</p><p>data/images/IMG_1234.HEIC</p><p>用户界面如何打开缩略图/全图</p><p>-</p><p>剪贴图片</p><p>dense_vector</p><p>768</p><p>SigLIP-2 图像嵌入</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>预测峰值</p><p>关键词</p><p>["ama-dablam","pumori"]</p><p>索引时的 Top-K 猜想（廉价用户体验过滤器/面）</p><p>-</p><p>全球定位系统</p><p>地理点</p><p>{"lat":27.96,"lon":86.83}</p><p>启用地理筛选器</p><p>-</p><p>拍摄时间</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>捕捉时间：排序/过滤</p><p>-</p><p><strong>照片索引的索引策略： </strong>对于相册中的每张照片，我们会采取以下措施：
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">从图像元数据中</a>提取图像<code>shot_time</code> 和<code>gps</code> 信息。</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 图像嵌入</a>：通过模型传递图像并对向量进行 L2 归一化。将嵌入内容存储在<code>clip_image</code> 字段中。</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">预测峰值</a>并将其存储在<code>predicted_peaks</code> 字段中。为此，我们首先获取上一步生成的照片图像向量，然后针对<code>peaks_catalog</code> 索引中的 text_embed 字段快速运行 kNN 搜索。我们保留顶部的 3-4 个山峰，忽略其余的。</p></li><li><p>我们通过对图片名称和路径进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">散列</a>计算<code>_id</code> 字段。这可以确保我们在多次运行后不会出现重复。</p></li></ul><p>一旦我们确定了照片的所有字段，就会使用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530"> 批量 索引对照片文件进行</a> 批量 索引：</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

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

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

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

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

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

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

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

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

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

    print("[done] photos indexing")<p>照片索引中的样本文件：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="来自 Elasticsearch 照片索引的样本文件。" /><p>总之，照片索引是相册中所有照片的快速、可过滤、kNN 就绪存储。它的映射结构非常简单，只需足够的结构就能快速检索、清晰显示，并按空间和时间对结果进行切分。该索引可同时满足这两种搜索用途。创建这两个索引的 Python 脚本可在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">此处</a>找到。</p><p>下面的 Kibana 地图可视化将相册中的文档显示为绿色圆点，将<code>peaks_catalog</code> 索引中的山峰显示为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Kibana 地图可视化显示相册中的文件为绿色圆点，peaks_catalog 索引中的山峰为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。" /><h2>搜索用例</h2><p><strong>按名称搜索（文本到图像）：</strong>该功能可让用户使用文本查询查找山峰照片（甚至是 "祈祷旗 "等抽象概念）。为此，使用 SigLIP-2 将文本输入<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">转换为文本向量</a>。为了生成稳健的文本向量，我们采用了与在<code>peaks_catalog</code> 索引中创建文本嵌入相同的策略：<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> 将</a> 文本输入与小型 提示集合<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"> 相结合</a> ，减去次要的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 反概念向量</a> ，并应用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> L2 归一化 生成最终的查询向量。</a>然后在<code>photos.clip_image</code> 字段上执行 kNN<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">查询</a>，根据余弦相似度检索匹配度最高的峰值，从而找到最接近的图像。作为查询的一部分，还可选择应用地理和日期<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">筛选器</a>和/或<code>photos.predicted_peaks</code> 术语筛选器来提高搜索结果的相关性（见下文查询示例）。这有助于排除在徒步过程中看不到的相似山峰。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="如何在 Elasticsearch 中通过名称（文本到图像）进行多模式搜索。" /><p><strong>带有地理过滤器的 Elasticsearch 查询：</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>按图像搜索（图像到图像）：</strong>通过该功能，我们可以识别照片中的某座山，并在相册中查找该座山的其他图像。图像上传后，将由 SigLIP-2 图像编码器处理，生成<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">图像矢量</a>。然后在<code>peaks_catalog.text_embed</code> 字段上进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 搜索</a>，以确定最匹配的峰值名称。随后，根据这些匹配的山峰名称<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257"> 生成</a> 一个 文本向量 ，并在照片索引中进行另一次<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263"> kNN 搜索</a> ，以找到相应的照片。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch 如何通过图像进行多模式搜索（图像到图像）。" /><p><strong>Elasticsearch 查询：</strong></p><p>第 1 步：找到匹配的山峰名称</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>第 2 步：在<code>photos</code> 索引上进行搜索，找到匹配的图片（与文本到图片搜索用例中的查询相同）：</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>流光 UI</h2><p>为了将所有功能整合在一起，我们创建了一个简单的 Streamlit 用户界面，让我们可以同时执行两种搜索用例。左侧栏显示可滚动的峰值列表（从<code>photos.predicted_peaks</code> 中汇总），并带有复选框和小地图/地理过滤器。顶部有一个<strong>按姓名搜索</strong>框和一个<strong>从照片</strong>上传识别按钮。中心窗格采用响应式缩略图网格，显示 kNN 分数、预测峰值徽章和捕获时间。每张图片都有一个<strong>查看图片</strong>按钮，用于全分辨率预览。</p><p><strong>通过上传图片进行搜索：</strong>我们会预测峰值，并从相册中找到匹配的峰值。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="这是一个简单的流光式用户界面，可通过文本到图像和图像到图像的多模态搜索方式搜索阿玛达布拉姆山峰。" /><p><strong>文本搜索</strong>从文本中查找相册中匹配的峰值</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="如何在山峰库中通过文本搜索珠穆朗玛峰。" /><h2>结论</h2><p><em>我们能看看 </em><em><strong>阿玛-达布拉姆</strong></em><em> 的照片吗？</em>变成了一个可运行的小型<strong>多模态搜索</strong>系统。我们采集了原始的徒步旅行照片，将其转化为<strong>SigLIP-2 嵌入</strong>，并使用<strong>Elasticsearch</strong>对向量进行快速的<strong>kNN</strong>处理，再加上简单的地理/时间过滤器，根据<em>意义</em>浮现出正确的图像。在此过程中，我们将两个索引的关注点分开：一个是混合原型的小<code>peaks_catalog</code> （用于识别），另一个是图像向量和 EXIF 的可扩展<code>photos</code> 索引（用于检索）。它实用、可复制、易扩展。</p><p>如果您想对其进行调整，有几项设置可供使用：</p><ul><li><p><strong>查询时间设置：</strong> <code>k</code> （您希望返回多少个邻居）和<code>num_candidates</code> （最终评分前的搜索范围）。这些设置将在<a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">此处的</a>博客中讨论。</p></li><li><p><strong>索引时间设置：</strong> <code>m</code> （图形连接性）和<code>ef_construction</code> （构建时间精度与内存）。对于查询，也可以尝试使用<code>ef_search</code> --更高通常意味着更高的召回率，但需要权衡一定的延迟。有关这些设置的更多详情，请参阅<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">本博客</a>。</p></li></ul><p>展望未来，用于<strong>多模态</strong>和<strong>多语言</strong>搜索的本地模型/路由器即将登陆<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> Elastic</a>生态系统，这将使图像/文本检索和混合排名功能更加强大。</p><p>如果你想亲自尝试一下：</p><ul><li><p><strong>GitHub 代码库</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>： https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab 快速入门</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">：https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>我们的旅程就此结束，是时候飞回去了。希望这对你有帮助，如果你改动（或改进）了它，我很乐意听听你的改动。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[弹性 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[您的第一个弹性代理：从单一查询到人工智能驱动的聊天]]></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[人工智能驱动的仪表盘：从设想到 Kibana]]></title>
    <description><![CDATA[使用 LLM 生成仪表盘，处理图像并将其转化为 Kibana 仪表盘。
]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">Kibana Lens</a>让仪表盘的拖放变得非常简单，但当你需要几十个面板时，点击次数就会增加。如果你能勾画出一个仪表盘，截图后让法律硕士为你完成整个过程，那会怎么样？</p><p>在本文中，我们将实现这一目标。我们将创建一个应用程序，它可以获取仪表盘的图像，分析映射，然后生成仪表盘，而无需接触 Kibana！</p><p><strong>步骤</strong>：</p><ol><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">后台&amp; 应用程序工作流程</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">准备数据</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">LLM 配置</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">应用功能</a></p></li></ol><h2>后台&amp; 应用程序工作流程</h2><p>我首先想到的是让 LLM 生成整个 NDJSON 格式的 Kibana<a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">保存对象</a>，然后将它们导入 Kibana。</p><p>我们尝试了几种型号：</p><ul><li><p>双子座 2.5 pro</p></li><li><p>GPT o3 / o4-mini-high / 4.1</p></li><li><p>克劳德 4 号十四行诗</p></li><li><p>Grok 3</p></li><li><p>Deepseek (Deepthink R1)</p></li></ul><p>至于提示语，我们从最简单的开始：</p>You are an Elasticsearch Saved-Object generator (Kibana 9.0).
INPUTS
=====
1. PNG screenshot of a 4-panel dashboard (attached).
2. Index mapping (below) – trimmed down to only the fields present in the screenshot.
3. Example NDJSON of *one* metric visualization (below) for reference.

TASK
====
Return **only** a valid NDJSON array that recreates the dashboard exactly:
* 2 metric panels (Visits, Unique Visitors)
* 1 pie chart (Most used OS)
* 1 vertical bar chart (State Geo Dest)
* Use index pattern `kibana_sample_data_logs`.
* Preserve roughly the same layout (2×2 grid).
* Use `panelIndex` values 1-4 and random `id` strings.
* Kibana version: 9.0<p>尽管我们看了<a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic#:~:text=Few%2Dshot%20prompting%20involves%20providing%20examples%20of%20the%20types%20of%20queries%20you%20want%20it%20to%20return%2C%20which%20helps%20in%20increasing%20consistency.">一些简单的示例</a>，并详细解释了如何建立每种可视化，但我们还是一无所获。如果您对这项实验感兴趣，请<a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">点击此处</a>了解详情。</p><p>采用这种方法的结果是，在尝试将 LLM 生成的文件上传到 Kibana 时看到了这些信息：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ea005966a783057/6a1707d266c4f90e4ef8bf88/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e5632d6d95b998c/6a1707d3a6c2b9441de79661/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" alt="" /><p>这意味着生成的 JSON 无效或格式不当。最常见的问题是 LLM 生成不完整的 NDJSON、产生参数幻觉，或者返回普通 JSON 而非 NDJSON，无论我们如何努力去执行其他操作。</p><p>受<a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">这篇文章</a>的启发--<a href="https://www.elastic.co/docs/solutions/search/search-templates">搜索模板</a>比 LLM 自由式更有效--我们决定给 LLM 提供模板，而不是要求它生成完整的 NDJSON 文件，然后我们在代码中使用 LLM 给出的参数来创建适当的可视化。</p><p>申请工作流程如下：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f7738a4c7ddd0cd/6a1707d52b835f0a25f4b166/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" alt="" /><p></p><p><em>为简单起见，我们将省略一些代码，但您可以在 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/from-image-idea-to-kibana-dashboard-using-ai.ipynb"><em><strong>本</strong></em></a><em> 笔记本</em>上找到完整应用程序的工作代码  。</p><h2>准备工作</h2><p>在开始开发之前，您需要具备以下条件：</p><ol><li><p>Python 3.8 或更高版本</p></li><li><p><a href="https://docs.python.org/3/library/venv.html">Venv</a>Python 环境</p></li><li><p>运行的 Elasticsearch 实例及其端点和 API 密钥</p></li><li><p>存储在环境变量 OPENAI_API_KEY 下的 OpenAI API 密钥：</p></li></ol>export OPENAI_API_KEY="your-openai-api-key"<h2>准备数据</h2><p>在数据方面，我们将保持简单，使用 Elastic 样本网络日志。您可以<a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">在此</a>了解如何将这些数据导入群集。</p><p>每份文档都包含向应用程序发出请求的主机的详细信息，以及请求本身及其响应状态的信息。下面是一个文件示例：</p>{
    "agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",
    "bytes": 8509,
    "clientip": "70.133.115.149",
    "extension": "css",
    "geo": {
        "srcdest": "US:IT",
        "src": "US",
        "dest": "IT",
        "coordinates": {
            "lat": 38.05134111,
            "lon": -103.5106908
        }
    },
    "host": "cdn.elastic-elastic-elastic.org",
    "index": "kibana_sample_data_logs",
    "ip": "70.133.115.149",
    "machine": {
        "ram": 5368709120,
        "os": "osx"
    },
    "memory": null,
    "message": "70.133.115.149 - - [2018-08-30T23:35:31.492Z] \"GET /styles/semantic-ui.css HTTP/1.1\" 200 8509 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"",
    "phpmemory": null,
    "referer": "http://twitter.com/error/john-phillips",
    "request": "/styles/semantic-ui.css",
    "response": 200,
    "tags": [
        "success",
        "info"
    ],
    "@timestamp": "2025-07-03T23:35:31.492Z",
    "url": "https://cdn.elastic-elastic-elastic.org/styles/semantic-ui.css",
    "utc_time": "2025-07-03T23:35:31.492Z",
    "event": {
        "dataset": "sample_web_logs"
    },
    "bytes_gauge": 8509,
    "bytes_counter": 51201128
}<p>现在，让我们抓取刚刚加载的索引的映射，<code>kibana_sample_data_logs</code> ：</p>INDEX_NAME = "kibana_sample_data_logs"

es_client = Elasticsearch(
    [os.getenv("ELASTICSEARCH_URL")],
    api_key=os.getenv("ELASTICSEARCH_API_KEY"),
)

result = es_client.indices.get_mapping(index=INDEX_NAME)
index_mappings = result[list(result.keys())[0]]["mappings"]["properties"]<p>我们将把映射与稍后加载的图像一起传递。</p><h2>LLM 配置</h2><p>让我们对 LLM 进行配置，使其使用<a href="https://python.langchain.com/docs/concepts/structured_outputs/">结构化输出</a>来输入图像，并接收包含我们需要传递给函数的信息的 JSON，以生成 JSON 对象。</p><p>我们安装依赖项：</p>pip install elasticsearch pydantic langchain langchain-openai -q<p>Elasticsearch 将帮助我们检索<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">索引映射</a>。Pydantic 允许我们在 Python 中定义模式，然后要求 LLM 遵循这些模式，而<a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a>框架则有助于更轻松地调用 LLM 和人工智能工具。</p><p>我们将创建一个 Pydantic 模式，以定义我们希望从 LLM 得到的输出。我们需要从图片中了解图表类型、字段、可视化标题和仪表盘标题：</p>class Visualization(BaseModel):
    title: str = Field(description="The dashboard title")
    type: List[Literal["pie", "bar", "metric"]]
    field: str = Field(
        description="The field that this visualization use based on the provided mappings"
    )


class Dashboard(BaseModel):
    title: str = Field(description="The dashboard title")
    visualizations: List[Visualization]<p>对于图像输入，我们将发送一个我刚刚画好的仪表盘：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7870f6421986d11d/6a1707d78b73cb3408189fa3/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" alt="" /><p>现在我们声明 LLM 模型调用和图像加载。该函数将接收 Elasticsearch 索引的映射和我们要生成的仪表盘图像。</p><p>通过<code>with_structured_output</code> ，我们可以使用 Pydantic<code>Dashboard</code> 模式作为 LLM 生成的响应对象。通过<a href="https://docs.pydantic.dev/latest/">Pydantic</a>，我们可以定义带有验证功能的数据模型，从而确保 LLM 输出与预期结构相匹配。</p><p>要将图像转换为 base64 并作为输入发送，可以使用<a href="https://www.base64-image.de/">在线转换器</a> <a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">或用代码</a>完成。</p>prompt = f"""
    You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.

    You will be given a dashboard image and an Elasticsearch index mapping.

    Below are the index mappings for the index that the dashboard is based on.
    Use this to help you understand the data and the fields that are available.

    Index Mappings:
    {index_mappings}

    Only include the fields that are relevant for each visualization, based on what is visible in the image.
    """

message = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": prompt},
            {
                "type": "image",
                "source_type": "base64",
                "data": image_base64,
                "mime_type": "image/png",
            },
        ],
    }
]


try:
    llm = init_chat_model("gpt-4.1-mini")
    llm = llm.with_structured_output(Dashboard)
    dashboard_values = llm.invoke(message)

    print("Dashboard values generated by the LLM successfully")
    print(dashboard_values)
except Exception as e:
    print(f"Failed to analyze image and match fields: {str(e)}")<p>LLM 已经掌握了 Kibana 面板的上下文，因此我们不需要在提示中解释所有内容，只需提供一些细节，确保它不会忘记自己正在使用 Elasticsearch 和 Kibana。</p><p>让我们来分析一下提示：</p><p>部门</p><p>原因</p><p>您是根据 Kibana 9.0.0 版本的图像分析 Kibana 仪表板的专家。</p><p>通过强化 Elasticsearch 和 Elasticsearch 版本，我们降低了 LLM 产生旧参数/无效参数的可能性。</p><p>您将获得一个仪表盘图像和一个 Elasticsearch 索引映射。</p><p>我们解释说，图片是关于仪表盘的，以避免法律硕士做出任何错误的解释。</p><p>下面是仪表盘所基于的索引的索引映射，使用它可以帮助你理解数据和可用字段。索引映射： {index_mappings}</p><p>提供映射至关重要，这样 LLM 才能动态选择有效字段。否则，我们就可能在这里硬编码映射，这太死板了，或者依靠图像包含正确的字段名，这也不可靠。</p><p>根据图像中可见的内容，只包含与每个可视化相关的字段。</p><p>我们必须添加这一增强功能，因为有时它会尝试添加与图像无关的字段。</p><p>这将返回一个包含要显示的可视化数组的对象：</p>"Dashboard values generated by the LLM successfully
title=""Client, Extension, OS, and Response Keyword Analysis""visualizations="[
   "Visualization(title=""Count of Client IP",
   "type="[
      "metric"
   ],
   "field=""clientip"")",
   "Visualization(title=""Extension Keyword Distribution",
   "type="[
      "pie"
   ],
   "field=""extension.keyword"")",
   "Visualization(title=""Most Used OS",
   "type="[
      "bar"
   ],
   "field=""machine.os.keyword"")",
   "Visualization(title=""Response Keyword Distribution",
   "type="[
      "bar"
   ],
   "field=""response.keyword"")"
]<h2>处理 LLM 答复</h2><p>我们在上创建了一个 2x2 面板仪表盘示例，然后使用 "<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">获取仪表盘 API "</a>将其导出为 JSON 格式，然后将面板存储为可视化模板（饼状、条状、度量），在这些模板中，我们可以替换部分参数，根据问题创建带有不同字段的新可视化。</p><p>您可以<a href="https://github.com/Delacrobix/elasticsearch-labs/tree/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/templates"><strong>在此处</strong></a>查看模板 JSON 文件。请注意我们是如何用 {<code>variable_name</code>} 更改我们稍后要替换的对象值的。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc55d69d84a08e668/6a1707d8a2929903acd00fb8/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" alt="" /><p>根据 LLM 提供的信息，我们可以决定使用哪个模板，替换哪些值。</p><p><code>fill_template_with_analysis</code> 将接收单个面板的参数，包括可视化的 JSON 模板、标题、字段和可视化在网格上的坐标。</p><p>然后，它会替换模板的值，并返回最终的 JSON 可视化。</p>def fill_template_with_analysis(
    template: Dict[str, Any],
    visualization: Visualization,
    grid_data: Dict[str, Any],
):
    template_str = json.dumps(template)
    replacements = {
	 "{visualization_id}": str(uuid.uuid4()),
        "{title}": visualization.title,
        "{x}": grid_data["x"],
        "{y}": grid_data["y"],
    }

    if visualization.field:
        replacements["{field}"] = visualization.field

    for placeholder, value in replacements.items():
        template_str = template_str.replace(placeholder, str(value))

    return json.loads(template_str)<p>为了简单起见，我们将为 LLM 决定创建的面板分配静态坐标，并生成如上图所示的 2x2 网格仪表盘。</p># Filling templates fields
panels = []    
grid_data = [
    {"x": 0, "y": 0},
    {"x": 12, "y": 0},
    {"x": 0, "y": 12},
    {"x": 12, "y": 12},
]


i = 0

for vis in dashboard_values.visualizations:
    for vis_type in vis.type:
        template = templates.get(vis_type, templates.get("bar", {}))
        filled_panel = fill_template_with_analysis(template, vis, grid_data[i])
        panels.append(filled_panel)
        i += 1<p>根据 LLM 决定的可视化类型，我们将选择一个 JSON 文件模板，并使用<code>fill_template_with_analysis</code> 替换相关信息，然后将新面板追加到稍后用于创建仪表盘的数组中。</p><p>仪表盘准备就绪后，我们将使用<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id"> 创建 仪表盘 API</a> 将新的 JSON 文件推送到 Kibana 以生成仪表盘：
</p>try:
    dashboard_id = str(uuid.uuid4())

    # post request to create the dashboard endpoint
    url = f"{os.getenv('KIBANA_URL')}/api/dashboards/dashboard/{dashboard_id}"

    dashboard_config = {
        "attributes": {
            "title": dashboard_values.title,
            "description": "Generated by AI",
            "timeRestore": True,
            "panels": panels,  # Visualizations with the values generated by the LLM
            "timeFrom": "now-7d/d",
            "timeTo": "now",
        },
    }

    headers = {
        "Content-Type": "application/json",
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {os.getenv('ELASTICSEARCH_API_KEY')}",
    }

    requests.post(
        url,
        headers=headers,
        json=dashboard_config,
    )

    # Url to the generated dashboard
    dashboard_url = f"{os.getenv('KIBANA_URL')}/app/dashboards#/view/{dashboard_id}"

    print("Dashboard URL: ", dashboard_url)
    print("Dashboard ID: ", dashboard_id)

except Exception as e:
    print(f"Failed to create dashboard: {str(e)}")<p>要执行脚本并生成仪表盘，请在控制台中运行以下命令：</p>python &lt;file_name&gt;.py<p>最终结果将是这样的</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ceffed004153a4f/6a1707d9a929cf9147ae0901/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" alt="" /><h2>结论</h2><p>在将文本转化为代码或将图像转化为代码时，LLM 展示了其强大的视觉能力。仪表盘 API 还能将 JSON 文件转化为仪表盘，而通过 LLM 和一些代码，我们就能将图片转化为 Kibana 仪表盘。</p><p>下一步是通过使用不同的网格设置、仪表盘大小和位置来提高仪表盘视觉效果的灵活性。此外，为更复杂的可视化和可视化类型提供支持也是对该应用程序的有益补充。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-powered-dashboards</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-powered-dashboards</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo,Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41727cbee6155a68/6a1707dbb0367dd2fd72bc86/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png" length="0" type="image/png"/>
    <pubDate>Wed, 16 Jul 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[将 Azure LLM 函数与 Elasticsearch 结合使用，获得更智能的查询体验]]></title>
    <description><![CDATA[探索一个房地产搜索应用程序示例，该应用程序使用 Azure Gen AI LLM 函数和 Elasticsearch 提供灵活的混合搜索结果。在 GitHub Codespaces 中逐步查看如何配置和运行示例应用程序。]]></description>
    <content:encoded><![CDATA[<p>精确。重要的时候，它非常重要。当您搜索特定的内容时，精确度就显得格外重要。不过，有时过于精确的查询不会返回任何结果，因此，灵活地扩大查询范围以查找更多潜在的相关数据是有好处的。</p><p>这篇博文介绍了如何使用 Elasticsearch 和 Azure Open AI 创建一个示例应用程序，演示如何在搜索非常具体的房地产物业时找到准确的结果，同时在没有具体匹配结果时仍然提供相关结果。我们将介绍创建 Elasticsearch 索引和搜索模板所需的所有步骤。然后，我们将介绍如何创建一个应用程序，使用 Azure OpenAI 接收用户查询并将其转换为 Elasticsearch 搜索模板查询，从而生成令人惊叹的自定义结果的整个过程。</p><p>下面列出了我们在创建房地产搜索应用程序示例时要用到的所有资源：</p><ul><li><p>Elasticsearch 索引和搜索模板</p></li><li><p>Azure OpenAI</p></li><li><p>Azure 地图 API</p></li><li><p><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb">Codespaces Jupyter 笔记本</a></p></li><li><p>语义内核</p></li><li><p>使用 Blazor 前端的 C# 应用程序</p></li></ul><h2>智能查询工作流程</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0461b58012efd772/6a17f73fa292997d52d02e19/0c4a7c835e06c514f158c00ab1055a7ba719a35f-1600x765.png" alt="智能查询工作流程" /><p>该工作流程将 LLM、LLM 工具和搜索结合起来，将自然语言查询转化为结构化的相关搜索结果：</p><ul><li><p><strong>LLM（大型语言模型）</strong>--解释复杂的用户查询并协调工具以提取搜索意图和丰富上下文。</p></li><li><p><strong>LLM 工具</strong>- 每个 LLM 工具都是我们为本职位创建的 C# 程序。有三种工具：</p><ul><li><p><em>参数提取工具</em>：从查询中提取卧室、浴室、功能和价格等关键属性。</p></li><li><p><em>地理编码工具</em>：将地点名称转换为经纬度，以便进行空间筛选。</p></li><li><p><em>搜索工具</em>：使用查询参数填充 Elasticsearch 搜索模板并执行搜索。<strong>混合搜索</strong>- 利用内置 ML 推理执行混合搜索（全文 + 密集向量）。这种分层方法可确保终端用户获得更智能、更能感知上下文的查询体验。</p></li></ul></li></ul><h2>应用架构</h2><p>下面是示例应用程序的系统架构图。我们将使用 Codespaces Jupyter Notebook 与 Elastic Cloud 进行交互。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7617ae80295a3e2b/6a17f74196142a35deeb1cb0/2880afee184cd9270c0eb4310e51418e2339784d-936x452.png" alt="Azure LLM Functions 应用程序的系统架构图。" /><h2>准备工作</h2><p>您只需要浏览器，因为我们将使用 GitHub Codespaces 来克隆示例应用程序、配置并运行它。对于解决方案的弹性部分，我们将使用Elastic Cloud创建一个 Elasticsearch Serverless 项目。我们将使用<a href="https://portal.azure.com/">Azure 门户</a>来处理 Azure 资源。</p><h2>在 Codespaces 中克隆示例应用程序 repo</h2><p>首先克隆示例应用程序的代码。<a href="https://github.com/codespaces/">GitHub Codespaces</a> 提供了克隆和运行应用程序的方法。单击<strong>新建代码空间。</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4ed9c67e2d79c41/6a17f7436df73146a20a10bc/b89cbec491659b6c8a0bb9551ed2629f7a37f9fd-1600x427.png" alt="在 Codespaces 中克隆示例应用程序 repo。" /><p>然后，在<strong> Repository</strong> 下拉菜单中选择 repo<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo"> jwilliams-elastic/msbuild-intelligent-query-demo</a><strong> ，然后单击 Create Codespace</strong> 。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfcc992a6cb7a7d7/6a17f7450b0bed67c1dd3750/43ea377554527af9578400f16cd2342bf8fff3a2-1600x1049.png" alt="下拉菜单，然后单击 &quot;创建代码空间&quot;。" /><h2>创建 .env文件</h2><p>我们将使用 Python Jupyter Notebook 访问 Elastic Cloud 并与之交互，它使用存储在配置文件中的配置值进行访问和交互。笔记本配置文件的文件名应为<em><strong>.env</strong></em>您现在就可以创建它。</p><ol><li><p>在 GitHub 代码空间中，单击 "<strong>新建文件 </strong>"按钮，添加一个名为<em><strong>.env</strong></em>的文件</p></li><li><p>在新创建的<em><strong>.env</strong></em>中添加以下内容文件</p></li></ol>ELASTIC_URL=
ELASTIC_API_KEY=<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a7dcb2fd2bb24f5/6a17f7462f4a5c21abfa8aa9/84d4f327948858ba61db0001dd8cf780d42fe0a7-1600x875.gif" alt="它使用存储在配置文件中的配置值访问 Elastic Cloud 并与之交互。 " /><p><em>正如您所看到的，我们需要在.env</em> 中添加几个缺失值，即 <strong>ELASTIC_URL</strong> 和<strong> ELASTIC_API_KEY。</strong>锉刀现在，让我们创建一个 Elasticsearch 无服务器项目，将其作为后端，为我们示例应用的搜索功能提供动力。</p><h2>创建弹性无服务器项目</h2><ol><li><p>访问<a href="http://cloud.elastic.co">cloud.elastic.co</a>，点击<strong>创建新的无服务器项目</strong></p></li><li><p>单击 "<strong>下一步 </strong>"查看<strong>Elasticsearch</strong>解决方案</p></li><li><p>选择<strong>优化的载体</strong></p></li><li><p>将<strong>云提供商</strong>设置为<strong>Azure</strong></p></li><li><p>点击<strong>创建无服务器项目</strong></p></li><li><p>单击主导航菜单中的 "<strong>入门</strong>"，然后向下滚动以复制 "<strong>连接详情</strong>"。</p></li><li><p>单击 "复制"按钮，从 " 连接详细信息"中复制<strong> Elasticsearch 端点</strong><strong> 。</strong></p></li><li><p>更新<em><strong>.env</strong></em>文件，将<strong>ELASTIC_URL</strong>设置为复制的<strong>Elasticsearch 端点</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95b7fefa05173822/6a17f748ec0f89f05f5a67c6/77a35e55446d396066b68cfd132d1543a07b81cc-1600x875.gif" alt="如何在 Elasticsearch 中创建新的 Serverless 项目。" /><h2>创建弹性 API 密钥</h2><ol><li><p>打开 Elasticsearch 入门 页面，单击<strong>添加</strong><strong> API 密钥</strong> 部分中的 新建</p></li><li><p>输入密钥<strong>名称</strong></p></li><li><p>单击<strong> 创建 API 密钥</strong></p></li><li><p>单击 "复制 "按钮复制 API 密钥值</p></li><li><p>回到<strong>Codespaces，</strong>在这里我们有<em><strong>.env</strong></em>打开编辑文件，粘贴复制的值以设置<strong> ELASTIC_API_KEY</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf6b1c85d267e6ad0/6a17f74a4b055d118143239a/20168cba493d8e2c0d9ae7704eb0ae707df58e4c-1600x875.gif" alt="如何创建弹性 API 密钥" /><h2>打开 Codespaces 笔记本并安装依赖库</h2><p>在文件资源管理器中，选择<em><strong>VectorDBSetup.ipynb</strong></em>文件，打开笔记本。加载笔记本后，找到笔记本中<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L40-L52"> 标题为 "</a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L40-L52"><strong> 安装库" 的部分</strong></a><strong> 。</strong>点击章节播放按钮。</p><p>如果这是你第一次在 GitHub Codespaces 中运行笔记本，系统会提示你选择 Codespaces 内核并配置 Python 环境。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt970f4fa30c6c9302/6a17f74c505ac30ee6ad8ceb/2272f70615dfb9dcbeb91f39b6dd5076213e24a5-1600x875.gif" alt="在 Codespaces Notebook 中安装依赖库。" /><h2>使用 Codespaces Notebook 定义导入和加载环境变量</h2><p>转到笔记本的下一节<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L58-L104">，标题为 " </a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L58-L104"><strong>定义导入和加载环境变量</strong></a>"。点击章节播放按钮。</p><p>这段代码导入了 Notebook 使用的 Python 库，并从<em>.env </em>中加载了环境变量。 您之前创建的</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d74b41258420d5a/6a17f74e6317301f2f585c16/aa9f9198ff452ac0c4ce33b00f253731dbee22c5-1600x875.gif" alt="使用 Codespaces Notebook 定义导入和加载环境变量。" /><h2>使用 Codespaces Notebook 创建 Elastic ML 推理端点</h2><p>转到下一个笔记本<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L112-L157">部分，标题为 </a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L112-L157"><strong>创建 ML 推理端点</strong></a>。点击章节播放按钮。</p><p>这将在 Elasticsearch 项目中创建一个新的 ML 推理端点，我们将用它从数据中生成文本嵌入。文本嵌入是文本的向量表示，将存储在 Elasticsearch 中以支持语义搜索。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87581300d4d0b66e/6a17f750e9ea875a81a9c795/97c1afab3e64027ee5ae77f377d56ba406ae1765-1600x875.gif" alt="使用 Codespaces Notebook 创建 Elastic ML 推理端点。" /><h2>使用 Codespaces Notebook 创建 Elasticsearch 索引</h2><p>转到下一个笔记本<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L165-L224">部分，标题为 </a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L165-L224"><strong>创建 Elasticsearch 索引</strong></a>。点击章节播放按钮。</p><p>这将创建 Elasticsearch 索引，用于存储示例数据和通过 ML 推理端点生成的任何相关向量数据。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2d2d5a10c84a1b7/6a17f7527f6f15775cc09cd6/23a66283ee41239e24fb8455c3cd95641982ca6b-1600x875.gif" alt="使用 Codespaces Notebook 创建 Elasticsearch 索引。" /><h2>使用 Codespaces Notebook 创建 Elasticsearch 搜索模板</h2><p>继续下一个笔记本<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L232-L384">部分，标题为 " </a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L232-L384"><strong>搜索模板</strong></a>"。点击章节播放按钮。</p><p>这样就创建了一个<a href="https://www.elastic.co/cn/docs/solutions/search/search-templates">搜索模板</a>，我们的示例应用程序将使用它作为模板，其中填充了从用户搜索查询中解析出的单词。这样，我们就可以配置和控制在 Elasticsearch 索引中查询数据时的特定程度。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt194d6557d096ac25/6a17f7545772628d901bcda0/4c001a3e4d1cca4cfb5c043fea92c7ccaf9cb64a-1600x875.gif" alt="使用 Codespaces Notebook 创建 Elasticsearch 搜索模板。" /><h2>使用 Codespaces Notebook 将数据输入 Elasticsearch 索引</h2><p>转到笔记本的下一部分<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450">，标题为 " </a><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450"><strong>输入属性数据</strong></a>"。点击章节运行按钮。</p><p>运行该代码段将批量加载<em>properties.jsonl </em>文件中包含的示例数据。几分钟后，您应该会看到程序成功完成的确认信息。您可以访问 Elastic Cloud 中的 "<strong>索引管理</strong>"部分，确认索引包含预期记录。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf1b5ad59c75716d/6a17f7566864a4528fb6894b/e9698c798541ccfc08143a939846597028e3c566-1600x875.gif" alt="使用 Codespaces Notebook 将数据输入 Elasticsearch 索引。" /><h2>创建 appsetting.json 以配置 C# 应用程序</h2><p>创建了 Elasticsearch 索引并填充了数据后，我们现在就可以配置示例应用程序，使其与 Elastic 和 Azure Cloud 协同工作。C# 示例应用程序使用名为<em>appsettings.json</em>的文件来存储和加载 API 密钥等访问信息。现在，您将使用 Codespaces 中的编辑器创建<em>appsettings.json</em>文件。</p><p>1.在<strong> HomeFinderApp</strong> 文件夹中创建<em> appsettings.json</em></p><p>2.将以下代码粘贴到<em>appsettings.json</em>文件中</p>{
 "ElasticSettings": {
   "Url": "",
   "ApiKey": "",
   "IndexName": "properties",
   "TemplateId": "properties-search-template"
 },
 "AzureOpenAISettings": {
   "Endpoint": "",
   "ApiKey": "",
   "DeploymentName": "gpt-4o"
 },
 "AzureMapsSettings": {
   "Url": "https://atlas.microsoft.com/geocode",
   "ApiKey": ""
 },
 "Logging": {
   "LogLevel": {
 	"Default": "Information",
 	"Microsoft.AspNetCore": "Warning"
   }
 },
 "AllowedHosts": "*"
}
<p>3.查找<strong> ElasticSettings</strong> 部分下的<strong> Url</strong> 和<strong> ApiKey</strong> 值。将它们设置为与在<em>.env</em>文件。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28afc316da403ec9/6a17f758faa913584d93ca28/00dad25bacdea2adcbd1e6eca7658867a49b0d8c-1600x875.gif" alt="创建 appsetting.json 以配置 C# 应用程序。" /><h2>创建 Azure OpenAI 服务</h2><p>我们的示例应用程序将使用 Azure OpenAI 来解析应用程序用户的查询，然后通过填充搜索模板向 Elasticsearch 提交请求，以尝试灵活地传达用户正在搜索的内容。</p><ol><li><p>打开新的浏览器选项卡，转到<a href="https://portal.azure.com/#blade/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/OpenAI"> Azure 门户中的 AI Foundry | Azure OpenAI</a> 。点击<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>选项卡上，单击<strong> 下一步</strong></p></li><li><p>在 "<strong>标签</strong>"选项卡上，单击 "<strong>下一步</strong></p></li><li><p>在 "<strong>审核和提交</strong>"选项卡上，单击 "<strong>创建</strong></p></li><li><p>创建完成后，单击<strong>转到资源</strong></p></li><li><p>从左侧导航菜单中选择<strong>密钥和端点</strong></p></li><li><p>复制<strong>端点</strong>并将其粘贴到在浏览器标签页中创建的<em>appsettings.json</em>文件中，该标签页已打开 Codespaces 编辑器。</p></li><li><p>然后返回包含 Azure OpenAI<strong>密钥和端点页面</strong>的浏览器选项卡。单击<strong>关键字 1</strong> 的复制按钮，然后将复制的值粘贴到<em>appsettings.json</em>文件中，回到打开 Codespaces 编辑器的浏览器标签页。</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd01a5e82d9003d02/6a17f75a148009be65b48904/6d49197302d110410dca0a53b6ae90237cf2dfd6-1600x875.gif" alt="创建 Azure OpenAI 服务。" /><h2>为 Azure Open AI 服务添加 gpt-4o 模型部署</h2><p>很好，我们现在已经有了一个正在运行的 Azure OpenAI 服务，但它仍然需要一个模型部署，以提供我们的示例应用程序所需的 LLM 功能。有多种型号可供选择。既然已在创建的 <em>appsettings.json</em> 文件中指定了<em> gpt-4o ，那就部署它吧。</em></p><p></p><ol><li><p>转到<a href="https://ai.azure.com/resource/playground">Azure AI Foundry</a>并单击<strong>创建部署</strong></p></li><li><p>搜索<em>gpt-4o</em>，并从结果中选择它</p></li><li><p>单击<strong>确认</strong>选择</p></li><li><p>单击 "部<strong>署</strong>"来部署模型</p></li></ol><p>成功部署<em>gpt-4o</em>模型后，可以从左侧导航菜单中选择 "<strong>部署</strong>"，确认<em><strong>gpt-4o</strong></em>部署的<strong>状态</strong>为 "<strong>已成功</strong>"。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte82341dd8a4982b0/6a17f75c4b055d9e0943239e/1b817ab67c05634e9c72777593b4d1a2c6c28191-1600x875.gif" alt="在 Azure Open AI 服务中添加 gpt-4o 模型部署。" /><h2>创建 Azure 地图账户</h2><p>我们希望我们示例应用程序的用户能够搜索特定区域的房地产，但又不必太具体。如果有人想搜索当地农贸市场附近的房产，OpenAI LLM 可以使用 Azure 地图服务来获取市场的纬度和经度坐标。然后，在发送给 Elasticsearch 的基于搜索模板的请求中可以包含这些坐标，以便用户查询包含特定位置和地理距离的信息。</p><ol><li><p><a href="https://portal.azure.com/#browse/Microsoft.Maps%2Faccounts">单击 Azure 地图账户 中的 创建</a></p></li><li><p>选择<strong>资源组</strong></p></li><li><p>输入<strong>名称</strong></p></li><li><p>同意许可和隐私声明</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>复制 主键值并粘贴到包含 Codespaces 编辑器的浏览器选项卡上，使其成为<em> appsettings.json</em> 文件中<strong> AzureMapsSettings</strong> 部分的 ApiKey<strong> 值</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a4ab4bb7a96d24/6a17f75edbb4ff4f91fb5892/90fadd48e366682e2bad91e32988f93c6354e126-1600x875.gif" alt="创建 Azure 地图账户。" /><h2>试用示例应用程序</h2><p>现在是有趣的部分。是时候运行示例应用程序了。我们已经掌握了所有配置细节，以及为应用程序提供动力所需的 Elastic Cloud 和 Azure Cloud 资源。</p><p>1.在 Codespaces 编辑器中打开终端窗口。</p><p>2.使用以下命令将活动目录更改为示例应用程序文件夹。
</p>cd HomeFinderApp<p>3.使用以下<em>dotnet</em>命令运行应用程序。</p>dotnet run<p>4.出现 "<strong>在浏览器中打开 </strong>"按钮时，单击该按钮。</p><p>5.测试默认搜索，然后尝试一些自己的自定义搜索。如果想查看后台生成搜索结果的更多详细信息，可以单击 工具调用 旁边的显示<strong> 链接。</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41adf6631ba91be1/6a17f760505ac30986ad8cf1/821fe7b9446de5ed646d938cc9484a7ddad21030-1600x875.gif" alt="试用示例应用程序" /><p><strong>奖励： </strong>如果你真的想测试 GPT-4o，请尝试以下搜索：<em>我正在寻找佛罗里达州迪斯尼世界附近的房产，有 30 多间卧室、20 多间浴室、游泳池和车库，靠近海滩，价格在 20 万以下。 </em>该查询将在多次调用搜索工具后返回结果。</p><h2>Elastic 是您的搜索人工智能解决方案</h2><p>运行中的应用程序是 Gen AI LLM 引导搜索的一个示例，它通过搜索模板使用 Elasticsearch 作为基础数据源。请随意尝试和定制示例应用程序，以创建精确而灵活的搜索体验，帮助用户找到他们想要的内容。</p><p>感谢您的阅读。试试<a href="https://cloud.elastic.co/registration">弹性云</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/azure-llm-functions-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/azure-llm-functions-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jonathan Simon,James Williams]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93dd59caccfd7fc8/6a17f7614202292a7129f799/1431b90c7e00de06574c1e33c44a2e89296c824e-1200x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[模型上下文协议（MCP）的现状]]></title>
    <description><![CDATA[了解 MCP、项目更新、功能、安全挑战、新兴用例以及如何使用 Elastic 的 Elasticsearch MCP 服务器。]]></description>
    <content:encoded><![CDATA[<p>我最近参加了在旧金山举行的<a href="https://mcpdevsummit.ai/">MCP 开发者峰会</a>，很明显，模型上下文协议（MCP）正在迅速成为人工智能代理和上下文丰富的人工智能应用的基础构件。在 Elastic，我们正朝着这个方向努力，通过<a href="https://www.elastic.co/cn/elasticsearch/agent-builder">Agent Builder</a> 直接公开 MCP 服务器，使 Elasticsearch 成为任何兼容 MCP 的代理的一流上下文和工具提供商。在本篇文章中，我将介绍此次活动的主要更新、新兴用例、MCP 的发展前景，以及如何使用 Agent Builder 通过 MCP 向代理提供 Elasticsearch。</p><h2>什么是模型上下文协议（MCP）？</h2><p>对于不熟悉的人来说，<a href="https://modelcontextprotocol.io/introduction">模型上下文协议</a>是一种开放标准，它提供了一种结构化的双向方式，将人工智能模型连接到各种数据源和工具中，使其能够生成更相关、更明智的响应。它通常被称为 "<a href="https://modelcontextprotocol.io/introduction">用于人工智能应用的 USB-C 端口</a>"。</p><p>下面的结构图突出了它的双向性：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ff0e141b5dfda29/6a17e7ffe8fbcee5263a1946/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" alt="模型上下文协议（MCP）架构" /><p>这对人工智能从业人员来说是一个重大转变，因为扩展人工智能应用的主要挑战之一就是必须为每个新数据源建立自定义集成。MCP 提供了一个可持续、可重复使用的架构，用于管理和提供模型的上下文。它与模型无关，与服务器无关，并且完全开源。</p><p>MCP 是一系列 API 规范的最新版本，旨在实现应用程序之间集成的标准化。过去，我们有用于 RESTful 服务的 OpenAPI、用于数据查询的 GraphQL 和用于微服务通信的 gRPC。MCP 不仅在结构上与这些旧规范一样严谨，而且还将其带入了生成式人工智能环境，使其更容易将代理插入不同的系统，而无需定制连接器。在许多方面，MCP 的目标都是为人工智能代理做 HTTP 为网络所做的事情。正如 HTTP 标准化了浏览器和网站之间的通信一样，MCP 试图标准化人工智能代理与周围数据世界的交互方式。</p><h2>MCP 与其他代理协议的比较</h2><p>代理协议的范围正在迅速扩大，有十多种新兴标准竞相定义代理的交互方式。来自 LlamaIndex 的<a href="https://x.com/seldo">Laurie Voss</a>介绍说，大多数协议可分为两类：侧重于代理相互对话的代理间协议和侧重于向 LLM 提供结构化上下文的面向上下文的协议（如 MCP）。</p><p>其他流行的协议，如谷歌的<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">A2A</a>（代理对代理）、思科和 IBM 的<a href="https://agentcommunicationprotocol.dev/introduction/welcome">ACP</a>（代理通信协议）以及<a href="https://agoraprotocol.org/">Agora</a>，都旨在实现代理对代理的协商、联盟建设甚至分散式身份系统。在这方面，MCP 采取了更加务实的方法，因为它关注的是代理如何访问工具和数据，而不一定是代理之间如何对话（尽管 MCP 在未来也可以通过不同的方式实现这一点）。</p><p>目前，MCP 的与众不同之处在于其牵引力和动力。与前端框架早期的 React 一样，MCP 也是从一个利基问题开始的，现在已成为实践中最常用、最具扩展性的代理协议之一。</p><h2>峰会回顾：MCP 不断变化的优先事项</h2><p>此次峰会的演讲者来自 Anthropic、Okta、OpenAI、AWS、GitHub 等公司。会谈内容从核心协议的改进到现实世界的实施，并概述了当前和长期的优先事项。这些会谈反映了从早期实验和简单的工具调用到以 MCP 为基础构建可信、可扩展和模块化的人工智能系统的转变。</p><p>几位发言人预言，在未来，MCP 不仅仅是协议管道，它还能成为人工智能原生网络的基础。就像 JavaScript 能让用户点击网页并与之互动一样，MCP 也能让代理代表我们执行同样的操作。例如，在电子商务中，用户无需手动导航到网站购物，只需告诉代理登录、找到特定产品、将其添加到购物车并结账即可。</p><p>这也不是纯粹的猜测和炒作；PayPal 在峰会上展示了他们新的代理工具包和 MCP 服务器，可以实现这种确切的代理商务体验。有了 MCP 提供的对工具和数据源的安全可靠访问，代理商将不仅仅是阅读网络，他们还将能够对其采取行动。如今，MCP 已经成为一个强大的标准，发展势头迅猛，未来可能成为全网人工智能增强型用户交互的标准。</p><h2>MCP 项目更新：运输、征询和结构化工具</h2><p>MCP 的核心贡献者<a href="https://x.com/JeromeSwannack">杰罗姆-斯旺纳克（Jerome Swannack</a>）分享了过去 6 个月中协议规范的一些更新。这些变化的主要目标是</p><ol><li><p>添加可流 HTTP 后启用远程 MCP</p></li><li><p>通过增加诱导和工具输出模式，建立更丰富的代理交互模型</p></li></ol><p>由于 MCP 是开放源代码，开发人员已经可以实现流式 HTTP 等更改。激发和工具输出示意图目前尚未发布；它们正处于草案阶段，可能会有所变化。</p><p><strong>可流 HTTP </strong><a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports">（发布于 03-26-2025</a>）<strong>：</strong>一个有影响的技术更新是引入了可流 HTTP 作为新的传输机制。它以一种更具可扩展性的双向模式取代了服务器发送事件（SSE），该模式支持分块传输编码和通过单个 HTTP 连接渐进式信息传输。这使您能够在 AWS Lambda 等云基础设施上部署 MCP 服务器，并支持企业网络限制，而无需长期连接或轮询。</p><p><strong>诱导</strong> （ Elicitation ）<a href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation"> （发布于 06-18-2025 ）</a><strong> ：</strong> 诱导允许服务器定义一个模式，说明他们希望如何从客户端获得上下文结构。从根本上说，服务器可以描述它需要什么以及它期望的输入类型。这将产生一些影响：对于服务器构建者来说，他们可以构建更复杂的代理交互。对于客户端构建者来说，他们可以实现适应这些模式的动态用户界面。不过，不应利用诱导来获取用户的敏感信息或个人身份信息。开发人员应遵循<a href="https://modelcontextprotocol.io/specification/draft/client/elicitation#security-considerations">最佳实践</a>，确保诱导提示安全、适当，尤其是随着 MCP 的成熟。这与我们稍后将讨论的更广泛的安全问题有关。</p><p><strong>工具输出示意图 </strong><a href="https://modelcontextprotocol.io/specification/draft/server/tools#output-schema">（发布于 06-18-2025</a>）<strong>： </strong>这一概念允许客户和 LLM 提前了解工具输出形状。工具输出模式可让开发人员描述工具预期返回的内容。这些模式解决了直接调用工具的一个主要局限性，即上下文窗口的使用效率低下。上下文窗口被认为是使用 LLM 时最重要的资源之一，当你直接调用一个工具时，它返回的原始内容会全部推送到 LLM 的上下文中。工具输出模式可让 MCP 服务器提供结构化数据，从而帮助您更好地利用标记和上下文窗口。以下是一些有关工具的一般<a href="https://modelcontextprotocol.io/specification/draft/server/tools#security-considerations">最佳实践</a>。</p><p>这些新的更新和未来的新增功能将共同帮助 MCP 成为一个更加模块化、类型化和生产就绪的代理协议。</p><h2>未充分利用的动力特性：采样和根</h2><p>虽然 MCP 规范并不新鲜，但在主题演讲中，采样和根都得到了强调。这两个基本原理目前被忽视，也未得到充分探索，但却能极大地促进代理之间更丰富、更安全的交互。</p><p><strong>取样 - 服务器可从客户端请求完成： </strong><a href="https://modelcontextprotocol.io/docs/concepts/sampling">取样</a>允许 MCP 服务器向客户端 LLM 请求完成。这就增加了协议的双向性，即服务器不只是响应请求，还可以提示并要求客户端的模型生成响应。这样，客户就可以完全控制成本、安全性以及 MCP 服务器使用的模型。因此，在使用带有预配置模型的外部 MCP 服务器的情况下，您无需提供自己的 API 密钥或配置自己对该模型的订阅，因为服务器可以直接提示已连接到客户端的模型。这使得代理行为更加复杂，互动性更强。</p><p><strong>根 - 资源的范围访问： </strong><a href="https://modelcontextprotocol.io/docs/concepts/roots">根</a>的设计目的是为客户端提供一种方法，让服务器了解相关的资源和工作区。这对于设定服务器的运行范围非常有效。值得注意的是，根是 "<a href="https://modelcontextprotocol.io/docs/concepts/roots#how-roots-work">信息性的，而不是严格执行的</a>"，这意味着它们并不定义 MCP 服务器或代理的权利或权限。换句话说，不能仅靠根来阻止服务器或代理执行某些工具或执行写入操作。有了根，权限仍应通过用户批准机制在客户端处理。此外，开发人员仍应注意使用在设计上尊重根设置的界限并采用<a href="https://modelcontextprotocol.io/docs/concepts/roots#best-practices">最佳实践的</a>服务器。</p><h2>代理身份验证：OAuth 2.1 和受保护的元数据</h2><p>本节重点介绍 OAuth 2.1，它是 OAuth 2.0 的最新迭代版本，删除了不安全流程并整合了最佳实践。</p><p>OAuth 支持是一个备受期待的话题，尤其是因为安全性和可扩展性被视为阻碍 MCP 成为将代理连接到工具的标准的主要障碍。<a href="https://x.com/aaronpk">Aaron Parecki</a>（Okta 的 OAuth 2.1 编辑和身份标准专家）讨论了 MCP 如何采用简洁、可扩展的 OAuth 流程，从服务器开发人员那里卸载大部分复杂性。官方 OAuth 2.1 授权规范最近于<a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">2025 年 6 月 18</a> 日发布了最新的协议修订版。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d53e7bb091b4f43/6a17e80163baff80dc741c56/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" alt="代理的 MCP 身份验证" /><p>在这种实现方式中，OAuth 的责任可由 MCP 客户端和服务器分担。大部分身份验证流程都由 MCP 客户端启动和处理，只有服务器在最后才参与接收和验证安全令牌。这种拆分有助于解决一个关键的扩展问题，即如何在不要求开发人员配置每个连接的情况下跨多个工具进行身份验证，并确保 MCP 服务器开发人员不必成为 OAuth 专家。</p><p>讲座的两大亮点</p><ol><li><p><a href="https://datatracker.ietf.org/doc/rfc9728/"><strong>受保护资源元数据</strong></a>：MCP 服务器可发布一个 JSON 文件，描述其目的、端点和验证方法。这样，客户端只需输入服务器 URL 就能启动 OAuth 流程，从而简化了连接过程。了解更多信息：<a href="https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol">让我们修复 MCP 中的 OAuth</a></p></li><li><p><a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13"><strong>支持 IDP 和 SSO</strong></a>：企业可以整合身份提供商，集中管理访问。这对用户体验和安全性来说都是双赢。用户无需点击 10 个不同的同意屏幕，安全团队也可以观察到每个连接。</p></li></ol><p>通过将 OAuth 逻辑推送到客户端并依赖服务器的元数据，MCP 生态系统避免了一个主要瓶颈。这使 MCP 与当今生产环境中现代应用程序接口的安全保护方式更加一致。</p><p>补充阅读：<a href="https://aaronparecki.com/oauth-2-simplified/">OAuth 2 简化版</a></p><h2>可组合生态系统中的安全挑战</h2><p>新的发展也带来了新的攻击面。思科公司的 Arjun Sambamoorthy 列出了 MCP 环境中的几个主要威胁，包括</p><p>威胁</p><p>描述</p><p>补救&amp; 最佳做法</p><p>及时注射&amp; 工具中毒</p><p>在 LLM 系统上下文或工具描述中注入恶意提示的方法，导致 LLM 执行读取文件或泄漏数据等意外操作。</p><p>使用 MCP 扫描等工具对工具元数据进行检查。在提示中包含说明和参数前对其进行验证。最后，考虑对高风险工具实施用户审批。有关详细信息，请参阅表格下方附加阅读列表中的 OWASP 提示注入指南。</p><p>采样攻击</p><p>就 MCP 而言，采样为 MCP 服务器对 LLM 进行提示注入攻击打开了大门。</p><p>对不信任的服务器禁用取样功能，并考虑对取样请求增加人工在环审批。</p><p>恶意 MCP 服务器</p><p>在目前收集的 MCP 服务器中，很难逐一审查以确保安全。流氓服务器会悄悄收集你的数据，并将其暴露给恶意行为者。</p><p>仅从受信任的注册表或内部列表中连接到 MCP 服务器。在具有沙箱功能的容器中运行第三方服务器。</p><p>恶意 MCP 安装工具</p><p>命令行安装程序和脚本可方便地快速实施 MCP 服务器或工具，但最终可能会安装未经验证的加密代码。</p><p>在沙箱环境中安装并验证软件包签名。切勿从未经验证的来源自动更新。</p><p>为了进一步解决这个问题，Arjun 建议使用可信的 MCP 注册表来处理所有验证（这是一个前沿和中心话题--更多详情，请参阅下面阅读清单中的前两项），以及使用这份<a href="https://github.com/slowmist/MCP-Security-Checklist">安全检查清单</a>。</p><p>补充阅读：</p><ul><li><p><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">官方 MCP 安全最佳实践</a></p></li><li><p><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">OWASP 法律硕士申请 10 强</a></p></li><li><p><a href="https://hiddenlayer.com/innovation-hub/">隐藏层威胁研究</a></p></li><li><p><a href="https://github.com/invariantlabs-ai/mcp-scan">MCP 扫描</a></p></li><li><p><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">OWASP 提示注入指南</a></p></li></ul><h2>下一步：注册、管理和生态系统</h2><p>一个集中的 MCP 注册中心正在开发中，这也是峰会上讨论最多的话题之一。当前的服务器生态系统存在分散、信任度低和可发现性差等问题。开发人员很难找到 MCP 服务器、验证它们的功能并安全安装，尤其是在元数据可能不完整或被欺骗的分散生态系统中。</p><p>集中式注册表可作为可信的真相来源，提高可发现性，确保服务器元数据的完整性，降低安装恶意工具的风险，从而直接解决这些痛点。</p><p>登记册的目标是</p><ul><li><p>为服务器元数据（服务器的功能、如何验证、安装和调用）提供单一真实来源</p></li><li><p>消除不完整的第三方注册表和碎片，这样当服务器想要注册时，就不必更新互联网上的所有其他注册表。</p></li><li><p>提供服务器注册流程，其中包括 CLI 工具和包含上述元数据的 server.json 文件。</p></li></ul><p>更广泛的希望是，可信的注册表将有助于安全地扩展生态系统，使开发人员能够放心地构建和共享新工具。</p><p>对 Anthropic 来说，治理是另一个首要问题。他们明确表示，MCP 应保持开放和社区驱动，但这种管理模式的推广仍在进行中。他们目前正在寻求这方面的帮助，并希望任何有开源协议管理经验的人伸出援手。这就引出了我想说的另一个话题。在整个活动中，发言人都强调，只有内部开发人员做出贡献，生态系统才能发展壮大。为了使 MCP 成为新的网络标准，并从其他流行的代理协议中脱颖而出，我们需要集中精力。</p><h2>现实世界中的 MCP：案例研究和演示</h2><p>一些组织分享了如何在实际应用中使用 MCP：</p><ul><li><p><strong>PayPal - 用于代理商务的 MCP 服务器： </strong>PayPal 展示了其新的<a href="https://github.com/paypal/agent-toolkit/">代理工具包</a>和 MCP 服务器，它们可以从根本上改变用户的购物体验。用户无需在社交媒体上搜索商品、比较价格和结账，而是可以与连接到 PayPal MCP 服务器的代理聊天，处理所有这些操作。
</p></li><li><p><strong>EpicAI.pro - 贾维斯：</strong>MCP 的发展让我们越来越接近拥有一个现实生活中的 Jarvis 型助手。对于那些不熟悉《钢铁侠》电影的人来说，Jarvis 是一个人工智能助手，它使用自然语言，能对多模式输入做出响应，响应零延迟，能主动预测用户需求，自动管理集成，并能在设备和位置之间进行上下文切换。如果我们把贾维斯想象成一个物理机器人助手，那么 MCP 就赋予了贾维斯 "双手 "或处理复杂任务的能力。
</p></li><li><p><strong>Postman - </strong><a href="https://www.postman.com/explore/mcp-generator"><strong>MCP 服务器生成器</strong></a><strong>： </strong>为 API 请求提供购物车体验，您可以挑选不同的 API 请求，将其放入购物篮，然后将整个购物篮下载为 MCP 服务器。
</p></li><li><p><strong>彭博社 - 彭博社 </strong>解决了企业 GenAI 开发中的一个关键瓶颈。他们拥有近 10,000 名工程师，需要一种标准化的方法来跨团队集成工具和代理。通过 MCP，他们将内部工具转变为模块化、远程优先的组件，代理可以在统一界面上轻松调用。这使他们的工程师能够在整个组织内贡献工具，而人工智能团队则专注于构建代理，而不是定制集成。Bloomberg 现在支持可扩展的安全代理工作流，可实现与 MCP 生态系统的完全互操作性。彭博社没有链接任何公共资源，但这是他们在峰会上公开展示的内容。
</p></li><li><p><strong>Block- </strong>-Block使用MCP为内部人工智能代理<a href="https://github.com/block/goose?tab=readme-ov-file">Goose</a>提供支持，使员工能够自动执行工程、销售、营销等方面的任务。他们为 Git、Snowflake、Jira 和 Google Workspace 等工具构建了 60 多个 MCP 服务器，以实现与日常使用的系统进行自然语言交互。现在，Block 公司的员工可以使用 Goose 查询数据、检测欺诈、管理事件、浏览内部流程等，而无需编写代码。MCP 在短短 2 个月内就帮助 Block 在许多工作职能中推广了人工智能。
</p></li><li><p><strong>AWS - </strong><a href="https://github.com/awslabs/mcp"><strong>AWS MCP 服务器</strong></a><strong>： </strong>AWS 推出了一款以《龙与地下城》为主题的有趣 MCP 服务器，该服务器可模拟掷骰子，跟踪过去的掷骰子情况，并使用可流式 HTTP 返回结果。这个轻量级示例强调了使用 Lambda 和 Fargate 等 AWS 工具和基础设施构建和部署 MCP 服务器是多么容易。他们还介绍了<a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands SDK</a>，这是一个用于构建与 MCP 服务器交互的多模式代理的开源工具包。</p></li></ul><h2>弹性代理生成器支持 MCP</h2><p>您现在就可以使用<a href="https://www.elastic.co/cn/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Elastic Agent Builder</a>开始尝试使用 MCP，它是直接在您的数据之上构建代理的最简单方法。代理生成器可让您将由 Elasticsearch 支持的工具暴露给兼容 MCP 的代理，它已配备了一些功能强大的内置工具，包括</p><ul><li><p><code>platform.core.search</code> - 使用完整的 Elasticsearch 查询 DSL 执行搜索</p></li><li><p><code>platform.core.list_indices</code> - 列出 Elasticsearch 中所有可用的索引（帮助代理发现存在的数据）</p></li><li><p><code>platform.core.get_index_mapping</code> - 检索特定索引的字段映射（帮助代理了解数据的形状和类型）</p></li><li><p><code>platform.core.get_document_by_id</code> - 按 ID 抓取特定文件（用于精确检索）</p></li></ul><p>只需使用这些工具，您就能为您的代理提供企业级搜索和相关性，这是建立可靠的人工智能代理的核心。</p><p>使 Agent Builder 功能更加强大的是，它能够根据应用程序的需要，定义和公开您自己的定制工具。这对意见性或可重复的工作流特别有用，因为在这种情况下，你希望代理针对特定索引执行特定类型的搜索，而不必每次都重新发现该逻辑。您可以将这种意图直接编码到工具中，从而使您的代理更快、更可靠、更具成本效益，而不必为得出相同的结论而花费大量时间进行规划和推理。</p><p>在代理生成器用户界面中，下面是一个使用 ES|QL 的自定义工具定义示例：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca9e3d0a4e7031c0/6a17e803faa913d8c393c897/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" alt="代理生成器用户界面" /><p>定义好自定义工具后，点击<code>Manage MCP</code> 下拉菜单并复制 MCP 服务器 URL，就可以使用 MCP 公开这些工具（以及内置的本地工具）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7d8b29b06c08f94/6a17e805033c8d07f06bb1b6/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" alt="MCP 工具" /><p>现在，您可以将此 MCP 端点导入任何使用 MCP 的客户端，使其连接到 Agent Builder，并访问所有可用工具。更多信息，请阅读<a href="https://www.elastic.co/cn/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Agent Builder</a> 简介。</p><h2>结论</h2><p>MCP Dev 峰会清楚地表明，MCP 正在塑造这些人工智能代理彼此互动以及与周围数据世界互动的方式。无论您是将代理连接到企业数据，还是设计完全自主的代理，MCP 都能提供一种标准化、可组合的集成方式，并迅速在大规模应用中发挥作用。从传输协议和安全模式到注册和管理，MCP 生态系统正在迅速成熟。MCP 将继续保持开放性和社区驱动，因此现在的开发人员有机会塑造其发展。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/mcp-current-state</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mcp-current-state</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f63f23bbecd2a18/6a17e8066317302039585aa7/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[将 Spring AI 和 Elasticsearch 作为矢量数据库]]></title>
    <description><![CDATA[了解如何使用 Spring AI 和 Elasticsearch 构建生产可用的 RAG 应用，并借助向量数据库将 LLM 与您的自有数据集成。
]]></description>
    <content:encoded><![CDATA[<p><strong>Spring AI</strong>现已全面上市，其首个<a href="https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released">稳定版本 1.0</a>已在<a href="https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-core">Maven Central</a> 上供您下载。让我们立即使用它来构建一个完整的人工智能应用程序，使用你最喜欢的<a href="https://www.elastic.co/what-is/large-language-models">LLM</a>和我们最喜欢的<a href="https://www.elastic.co/elasticsearch/vector-database">向量数据库</a>。或者直接进入<a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">资源库</a>，进行最终应用。</p><h2>什么是春季人工智能？</h2><p><strong>Spring AI 1.0</strong> 是 Java 中人工智能工程的全面解决方案，在经历了人工智能领域快速发展的重要开发阶段后，现已面世。该版本为人工智能工程师提供了许多重要的新功能。</p><p>Java 和 Spring 在人工智能浪潮中处于有利位置。很多公司都在 Spring Boot 上运行他们的产品，这使得将人工智能插入到他们已经在做的事情中变得非常容易。您基本上可以将您的业务逻辑和数据直接与这些人工智能模型连接起来，而不会有太多麻烦。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee1f144eb9e74866/6a17e379dbb4ffdf4bfb5647/328d7c51e1c145e94ea1e73ee9ff91836d3b180e-1600x773.png" alt="如何在 Elasticsearch 中使用 Spring AI" /><p>Spring AI 为<a href="https://docs.spring.io/spring-ai/reference/api/index.html">各种人工智能模型和技术</a>提供支持，例如</p><ul><li><p><strong>图像模型</strong>：根据文字提示生成图像。</p></li><li><p><strong>转录模型</strong>：获取音频源并将其转换为文本。</p></li><li><p><strong>嵌入模型： </strong>将任意数据转换为<a href="https://www.elastic.co/what-is/vector-embedding">矢量，矢量</a>是为语义相似性搜索而优化的数据类型。</p></li><li><p><strong>聊天模式： </strong>这些应该很熟悉！毫无疑问，你甚至在某个地方与他们进行过简短的交谈。</p></li></ul><p>在人工智能领域，聊天模型似乎最受关注，这也是理所当然的，因为它们非常棒！你可以让他们帮你修改文件或写诗。(只是先别让他们讲笑话......）。它们很棒，但也存在一些问题。</p><h2>应对人工智能挑战的春季人工智能解决方案</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd2d062ded38cf83/6a17e37adbb4ff69d7fb564b/2ebd68a90ebc73847df6ef7325936d4d06b35c8c-1600x900.jpg" alt="用于应对 AI 挑战的 Spring AI 解决方案" /><p>让我们来看看 Spring AI 中的一些问题及其解决方案。</p><p></p><p>问题</p><p>解决方案</p><p>一致性</p><p>聊天模式思想开放，容易分心</p><p>您可以给它们一个系统提示，以规范它们的整体形状和结构</p><p>内存</p><p>人工智能模型没有内存，因此无法将某个用户的一条信息与另一条信息关联起来</p><p>您可以为他们提供一个记忆系统，存储对话的相关部分</p><p>隔离</p><p>人工智能模型生活在与世隔绝的小沙箱中，但如果你能让它们使用一些工具--在它们认为必要时可以调用的功能，它们就能做出非常了不起的事情</p><p>Spring AI 支持工具调用，让你可以告诉 AI 模型其环境中的工具，然后它可以要求你调用这些工具。这种多轮互动都是透明处理的</p><p>私人数据</p><p>人工智能模型很聪明，但并非无所不知！他们不知道你专有数据库中的内容，我们认为你也不希望他们知道！</p><p>您需要通过填充提示来告知他们的回复--基本上就是在模型查看所提问题之前，使用强大的字符串连接运算符将文本放入请求中。背景资料，如果你愿意的话。如何决定哪些应该发送，哪些不应该发送？使用矢量存储，只选择相关数据并将其继续发送。这被称为检索增强生成，或 RAG</p><p>幻觉</p><p>人工智能聊天模型喜欢，嗯，聊天！有时，他们自信到可以胡编乱造</p><p>您需要使用评估--使用一个模型来验证另一个模型的输出--来确认合理的结果</p><p></p><p>当然，任何人工智能应用都不是一座孤岛。如今，现代人工智能系统和服务在与其他系统和服务集成时效果最佳。通过<a href="https://modelcontextprotocol.io/introduction"><strong>模型上下文协议</strong></a>(MCP)，可以将人工智能应用程序与其他基于 MCP 的服务连接起来，无论这些服务是用什么语言编写的。您可以将所有这些集合到<strong>代理 </strong>工作流程中，以实现更大的目标。</p><p>最棒的是什么？您可以在熟悉的成语和抽象基础上完成所有这些工作，Spring Boot 开发人员都会期待这些成语和抽象：<a href="https://start.spring.io"><strong>Spring Initializr</strong></a>上提供了方便的启动依赖项，基本上所有东西都可以使用<strong>。</strong></p><p>Spring AI 提供方便的 Spring Boot 自动配置，为您提供您所熟知和期待的 "惯例重于配置 "设置。Spring AI 通过 Spring Boot 的 Actuator 和 Micrometer 项目支持可观察性。它还能很好地与 GraalVM 和虚拟线程配合使用，让您可以构建超快、高效的人工智能应用程序，并进行扩展。</p><h2>为什么选择 Elasticsearch</h2><p>Elasticsearch 是一个全文搜索引擎，这一点你可能知道。那么，我们为什么要在这个项目中使用它呢？嗯，这<em>也</em>是一家矢量商店！数据与全文并存，相当不错。其他显著优势</p><ul><li><p>设置超级简单</p></li><li><p>开源</p></li><li><p>可横向扩展</p></li><li><p>您企业的大部分自由格式数据可能已经存在于 Elasticsearch 集群中</p></li><li><p>具有完整的搜索引擎功能</p></li><li><p>完全<a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/elasticsearch.html">集成到 Spring AI 中</a>！</p></li></ul><p>综上所述，Elasticsearch 符合优秀矢量存储的所有条件，因此让我们对其进行设置，然后开始构建我们的应用程序！</p><h2>Elasticsearch 入门</h2><p>我们需要 Elasticsearch 和 Kibana，这是用户界面控制台，用于与数据库中的数据交互。</p><p>借助 Docker 镜像和<a href="http://elastic.co">Elastic.co 主页</a>，您可以在本地机器上尝试一切。访问该网站，向下滚动找到<code>curl</code> 命令，运行该命令并将其直接导入 shell：</p> curl -fsSL https://elastic.co/start-local | sh 
  ______                     
 |  ____| |         | | (_)     
 | |__  | | __  ___| |_   ___ 
 |  __| | |/ _` / __| __| |/ __|
 | |____| | (_| \__ \ |_| | (__ 
 |______|_|\__,_|___/\__|_|\___|
-------------------------------------------------
🚀 Run Elasticsearch and Kibana for local testing
-------------------------------------------------
ℹ️  Do not use this script in a production environment
⌛️ Setting up Elasticsearch and Kibana v9.0.0...
- Generated random passwords
- Created the elastic-start-local folder containing the files:
  - .env, with settings
  - docker-compose.yml, for Docker services
  - start/stop/uninstall commands
- Running docker compose up --wait
[+] Running 25/26
 ✔ kibana_settings Pulled                                                 16.7s 
 ✔ kibana Pulled                                                          26.8s 
 ✔ elasticsearch Pulled                                                   17.4s                                                                     
[+] Running 6/6
 ✔ Network elastic-start-local_default             Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-elasticsearch"  Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-kibana"         Created                 0.0s 
 ✔ Container es-local-dev                          Healthy                12.9s 
 ✔ Container kibana_settings                       Exited                 11.9s 
 ✔ Container kibana-local-dev                      Healthy                21.8s 
🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: w1GB15uQ
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: SERqaGlKWUJLNVJDODc1UGxjLWE6WFdxSTNvMU5SbVc5NDlKMEhpMzJmZw==
Learn more at https://github.com/elastic/start-local
➜  ~ <p>这只需为 Elasticsearch 和 Kibana 提取和配置 Docker 映像，几分钟后，它们就会在本地计算机上运行，并提供完整的连接凭据。</p><p>您还可以使用两个不同的 URL 与 Elasticsearch 实例交互。按提示操作，将浏览器指向<a href="http://localhost:5601">http://localhost:5601。</a></p><p>请注意打印在控制台上的用户名<code>elastic</code> 和密码：登录时需要使用这两个用户名和密码（在上面的输出示例中，它们分别是<code>elastic</code> 和<code>w1GB15uQ</code> ）。</p><p></p><h2>整合应用程序</h2><p>转到<a href="https://start.spring.io">Spring Initializr</a>页面并生成一个新的 Spring AI 项目，其中包含以下依赖项：</p><ul><li><p><code>Elasticsearch Vector Store</code></p></li><li><p><code>Spring Boot Actuator</code></p></li><li><p><code>GraalVM</code></p></li><li><p><code>OpenAI</code></p></li><li><p><code>Web</code></p></li></ul><p>确保选择最新、最好的 Java 版本（最好是 Java 24（截至本文撰写之时）或更高版本）和您所选择的构建工具。本例中我们使用 Apache Maven。</p><p>单击<code>Generate</code> ，然后解压缩项目并将其导入您选择的集成开发环境。(我们使用的是 IntelliJ IDEA）。</p><p>首先：让我们为 Spring Boot 应用程序指定连接细节。在<code>application.properties,</code> 中写下以下内容：</p>spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=w1GB15uQ<p>我们还将利用 Spring AI 的向量存储功能来初始化 Elasticsearch 端所需的数据结构，请具体说明：</p>spring.ai.vectorstore.elasticsearch.initialize-schema=true<p>在本演示中，我们将使用<strong>OpenAI</strong>，特别是<strong>嵌入模型</strong>和<strong>聊天模型 </strong>（只要<a href="https://docs.spring.io/spring-ai/reference/api/embeddings.html#available-implementations">Spring AI 支持</a>，您可以随意使用自己喜欢的服务）。</p><p>在将数据存储到 Elasticsearch 之前，我们需要嵌入模型来创建数据的嵌入。为了让 OpenAI 正常工作，我们需要指定<code>API key</code> ：</p>spring.ai.openai.api-key=...<p>您可以将其定义为一个环境变量，如<code>SPRING_AI_OPENAI_API_KEY</code> ，以避免在源代码中存储凭据。</p><p>我们将上传文件，因此请务必自定义可上传至 servlet 容器的数据量：</p>spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB<p>我们快到了在开始编写代码之前，让我们先预览一下它是如何工作的。</p><p>在我们的机器上，我们下载了<a href="https://images-cdn.fantasyflightgames.com/filer_public/9f/aa/9faa23a3-9f71-4c77-865f-bba4aac8a258/runewars-revised-_rulebook.pdf">以下文件</a>（一个棋盘游戏的规则列表），将其重命名为<code>test.pdf</code> 并放入<code>~/Downloads/test.pdf</code>.</p><p>文件将被发送到<code>/rag/ingest</code> 端点（根据本地设置替换相应路径）：</p>http --form POST http://localhost:8080/rag/ingest path@/Users/jlong/Downloads/test.pdf<p>这可能需要几秒钟...</p><p>在幕后，数据被发送到 OpenAI，OpenAI 正在创建数据嵌入；然后，数据（包括向量和原始文本）被写入 Elasticsearch。</p><p>这些数据以及其中的所有嵌入式数据，就是神奇之处。然后，我们就可以使用<code>VectorStore</code> 界面查询 Elasticsearch。</p><p>整个流程是这样的</p><ul><li><p>HTTP 客户端会将您选择的 PDF 上传到 Spring 应用程序。</p></li><li><p>Spring AI 负责从我们的 PDF 中提取文本，并将每页分成 800 个字符的小块。</p></li><li><p>OpenAI 会为每个块生成向量表示。</p></li><li><p>然后，分块文本和嵌入都会存储在 Elasticsearch 中。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93f4a64b634e9cce/6a17e37cb1e113215879f216/9734adb2d7128e61c515d5855dfad6d3a326a4a1-1454x706.png" alt="完整的 Spring AI 工作流：从 PDF 提取文本，通过 OpenAI 生成向量表示，并利用 Elasticsearch 进行文本分块以生成嵌入向量。" /><p>最后，我们将发出一个查询：</p>http :8080/rag/query question=="where do you place the reward card after obtaining it?" <p>我们会得到相关的答案：</p>After obtaining a Reward card, you place it facedown under the Hero card of the hero who received it.
Found at page: 28 of the manual<p>不错！这一切是如何运作的？</p><ul><li><p>HTTP 客户端将问题提交给 Spring 应用程序。</p></li><li><p>Spring AI 从 OpenAI 获取问题的向量表示。</p></li><li><p>有了这种嵌入，它就能在存储的 Elasticsearch 块中搜索相似文档，并检索出最相似的文档。</p></li><li><p>然后，Spring AI 将问题和检索到的上下文发送给 OpenAI，以生成 LLM 答案。</p></li><li><p>最后，它会返回生成的答案和检索到的上下文的引用。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfab41731851104f3/6a17e37e445de90e924d00aa/3799de6e8cb13ce49b9e136cfe593263030231a8-1464x1050.png" alt="利用 Spring AI 与 OpenAI 搭建端到端 LLM 问答流程。" /><p>让我们深入 Java 代码，看看它到底是如何工作的。</p><p>首先是<strong>Main</strong>类：它是 Spring Boot 应用程序的标准主类。</p>@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) { 
     		SpringApplication.run(DemoApplication.class, args);
 	}
}<p>没什么好看的。继续...</p><p>接下来是基本 HTTP 控制器：</p>@RestController
class RagController {

   private final RagService rag;

   RagController(RagService rag) {
       this.rag = rag;
   }

   @PostMapping("/rag/ingest")
   ResponseEntity&lt;?&gt; ingestPDF(@RequestBody MultipartFile path) {
       rag.ingest(path.getResource());
       return ResponseEntity.ok().body("Done!");
   }

   @GetMapping("/rag/query")
   ResponseEntity&lt;?&gt; query(@RequestParam String question) {
       String response = rag.directRag(question);
       return ResponseEntity.ok().body(response);
   }
}<p>控制器只是调用我们构建的一项服务，以处理文件摄取并将其写入 Elasticsearch 向量存储，然后方便对同一向量存储进行查询。</p><p>让我们来看看服务：</p>@Service
class RagService {

   private final ElasticsearchVectorStore vectorStore;

   private final ChatClient ai;

   RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {
       this.vectorStore = vectorStore;
       this.ai = clientBuilder.build();
   }

   void ingest(Resource path) {
       PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);
       List&lt;Document&gt; batch = new TokenTextSplitter().apply(pdfReader.read());
       vectorStore.add(batch);
   }

  // TBD
}<p>这段代码将处理所有的输入：给定一个 Spring Framework<code>Resource</code> ，它是一个围绕字节的容器，我们使用 Spring AI 的<code>PagePdfDocumentReader</code> 读取 PDF 数据（假定是一个<code>.PDF</code> 文件--在接受任意输入之前，请确保您验证了这一点！），然后使用 Spring AI 的<code>TokenTextSplitter</code> 对其进行标记化，最后将生成的<code>List&lt;Document&gt;</code>s 添加到<code>VectorStore</code> 的实现<code>ElasticsearchVectorStore</code> 中。</p><p>您可以使用 Kibana 进行确认：向<code>/rag/ingest</code> 端点发送文件后，打开浏览器<code>localhost:5601</code> ，然后在左侧菜单中导航至<code>Dev Tools</code> 。在这里，你可以发出查询，与 Elasticsearch 实例中的数据交互。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45805a5b2da5e336/6a17e3803e03d70a584f2bda/c85e522f02f8b2da7462cd428dc7e952c9692542-1600x1040.png" alt="如何在 Elasticsearch 实例中构建查询。" /><p>发出这样的查询</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt21d79210fe213b1e/6a17e382e3179163492d5767/00974a176cbce11e70fcab24fb4b3f9c6e205982-1600x1040.png" alt="在 Elasticsearch 控制台中发出查询。" /><p>现在，有趣的事情来了：我们怎样才能在响应用户查询时将数据返回？</p><p>下面是查询的初步实现，方法名为<code>directRag</code> 。</p>String directRag(String question) {
   // Query the vector store for documents related to the question
   List&lt;Document&gt; vectorStoreResult =
           vectorStore.doSimilaritySearch(SearchRequest.builder().query(question).topK(5)
                   .similarityThreshold(0.7).build());

   // Merging the documents into a single string
   String documents = vectorStoreResult.stream()
           .map(Document::getText)
           .collect(Collectors.joining(System.lineSeparator()));

   // Exit if the vector search didn't find any results
   if (documents.isEmpty()) {
       return "No relevant context found. Please change your question.";
   }

   // Setting the prompt with the context
   String prompt = """
           You're assisting with providing the rules of the tabletop game Runewars.
           Use the information from the DOCUMENTS section to provide accurate answers to the
           question in the QUESTION section.
           If unsure, simply state that you don't know.
          
           DOCUMENTS:
           """ + documents
           + """
           QUESTION:
           """ + question;


   // Calling the chat model with the question
   String response = ai
           .prompt()
           .user(prompt)
           .call()
           .content();

   return response +
           System.lineSeparator() +
           "Found at page: " +
           // Retrieving the first ranked page number from the document metadata
           vectorStoreResult.getFirst().getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +
           " of the manual";

}<p>代码非常简单，但让我们把它分成多个步骤：</p><ol><li><p>使用<code>VectorStore</code> 进行相似性搜索。</p></li><li><p>给定所有结果后，获取底层 Spring AI<code>Document</code>s 并提取其文本，将所有结果串联成一个结果。</p></li><li><p>将<code>VectorStore</code> 的结果发送给模型，同时发送一个提示，指示模型如何处理这些结果以及用户提出的问题。等待回复并返回。</p></li></ol><p></p><p>这就是<strong>RAG</strong>--检索增强生成。这就是我们使用矢量存储的数据为模型的处理和分析提供信息的理念。既然你已经知道怎么做了，那就希望你永远都不必这么做！反正不是这样的：Spring AI<a href="https://docs.spring.io/spring-ai/reference/api/advisors.html">顾问</a>将进一步简化这一过程。</p><p>除了在应用程序和矢量存储之间提供一个抽象层外，Advisors 还允许你对给定模型的请求进行预处理和后处理。在构建过程中添加以下依赖项
</p>&lt;dependency&gt;
   &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
   &lt;artifactId&gt;spring-ai-advisors-vector-store&lt;/artifactId&gt;
&lt;/dependency&gt;<p>在类中添加另一个名为<code>advisedRag(String question)</code> 的方法：</p>String advisedRag(String question) {
   return this.ai
           .prompt()
           .user(question)
           .advisors(new QuestionAnswerAdvisor(vectorStore))
           .call()
           .content();
}<p>所有 RAG 模式逻辑都封装在<code>QuestionAnswerAdvisor</code> 中。其他一切都与向<code>ChatModel</code> 提出的任何请求一样！不错！</p><p>你可以<a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">从 GitHub 获取完整的代码</a>。</p><h2>结论</h2><p>在这个演示中，我们使用了 Docker 镜像，并在本地机器上完成了所有操作，但我们的目标是构建可投入生产的人工智能系统和服务。为了实现这一目标，您可以做几件事。</p><p>首先，你可以添加<a href="https://docs.spring.io/spring-boot/reference/actuator/index.html#actuator">Spring Boot Actuator</a>来监控令牌的消耗。代币代表了特定请求对模型造成的复杂性成本（有时是美元和美分）。</p><p>您已经在类路径上安装了 Spring Boot Actuator，因此只需指定以下属性即可显示所有指标（由宏伟的<a href="http://micrometer.io">Micrometer.io</a>项目捕获）：</p>management.endpoints.web.exposure.include=*<p>重新启动应用程序。进行查询，然后转到<a href="http://localhost:8080/actuator/metrics">： http://localhost:8080/actuator/metrics。</a>搜索 "<code>token</code>"，就能看到应用程序使用的令牌信息。请务必留意这一点。当然，您也可以使用 Micrometer<a href="https://docs.micrometer.io/micrometer/reference/implementations/elastic.html">与 Elasticsearch 的集成</a>来推送这些指标，并让 Elasticsearch 作为您选择的时间序列数据库！</p><p>因此，您应该考虑到，每次我们向 Elasticsearch 等数据存储、OpenAI 或其他网络服务发出请求时，我们都在进行 IO，而且 IO 经常会阻塞执行它的线程。Java 21 及更高版本配备了非阻塞<strong>虚拟线程</strong>，大大提高了可扩展性。使用
</p>spring.threads.virtual.enabled=true<p>最后，您还需要将应用程序和数据托管在一个能够使其蓬勃发展和扩展的地方。我们相信，您可能已经考虑好在哪里运行应用程序了，但在哪里托管您的数据呢？我们可以推荐<a href="https://cloud.elastic.co/">弹性云吗</a>？它安全、私密、可扩展，功能齐全。我们最喜欢的部分？如果你愿意，还可以购买无服务器版，在无服务器版中，Elastic 会为你佩戴传呼机，而不是你！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/spring-ai-elasticsearch-application</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/spring-ai-elasticsearch-application</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Josh Long,Philipp Krenn,Laura Trotta]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26b868ef618164c6/6a17e3830b0bedd68cdd3515/0771fb5b3d9234697cb868cd7d9d1b840000bf29-1280x720.png" length="0" type="image/png"/>
    <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 LangGraph 和 Elasticsearch 构建 RAG 工作流程]]></title>
    <description><![CDATA[了解如何使用 Elasticsearch 配置和定制 LangGraph 检索代理模板，以构建 RAG 工作流，从而实现高效的数据检索和人工智能驱动的响应。]]></description>
    <content:encoded><![CDATA[<p><a href="https://github.com/langchain-ai/retrieval-agent-template">LangGraph 检索代理模板</a>是 LangChain 开发的一个启动项目，目的是方便在 LangGraph Studio 中使用 LangGraph 创建基于检索的问题解答系统。该模板经过预配置，可与 Elasticsearch 无缝集成，使开发人员能够快速构建可高效索引和检索文档的代理。</p><p>本博客主要介绍如何使用 LangGraph Studio 和 LangGraph CLI 运行和定制 LangChain 检索代理模板。该模板为利用 Elasticsearch 等各种检索后端构建检索增强生成 (RAG) 应用程序提供了一个框架。</p><p>我们将指导您设置、配置环境，并使用 Elastic 高效执行模板，同时定制代理流程。</p><h2>准备工作</h2><p>在继续之前，请确保已安装以下设备：</p><ul><li><p>Elasticsearch 云部署或内部部署（或在 Elastic Cloud 上创建 14 天<a href="https://www.elastic.co/cn/cloud/cloud-trial-overview">免费试用版 </a>）- 版本 8.0.0 或更高</p></li><li><p>Python 3.9+</p></li><li><p>访问 LLM 提供商，如<a href="https://cohere.com/">Cohere</a>（本指南中使用）、<a href="https://openai.com/">OpenAI</a> 或<a href="https://www.anthropic.com/claude">Anthropic/Claude</a></p></li></ul><h2>创建 LangGraph 应用程序</h2><h3>1.安装 LangGraph CLI</h3>pip install --upgrade "langgraph-cli[inmem]"<h3>2.根据检索代理模板创建 LangGraph 应用程序</h3>mkdir lg-agent-demo
cd lg-agent-demo
langgraph new lg-agent-demo <p><em>您将看到一个交互式菜单，可以从可用模板列表中进行选择。 </em>为检索代理选择 4，为 Python 选择 1，如下图所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd44177037ea46d45/6a17f86b3e9e45265fba1663/6a41a41f95c2477c67810adc7be46d91faf06878-1600x407.png" alt="交互式检索模板。" /><ul><li><p><strong>故障排除</strong>：如果遇到以下错误："urllib.error.URLError:&lt;urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)&gt;"</p></li></ul><p>请运行 Python 的安装证书命令来解决问题，如下所示。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbbe2d1d3a1af75b1/6a17f86d445de97c9c4d02e7/83ec238136c41738457299fd42c83aff32eb5b97-1407x75.png" alt="运行 Python 安装证书命令。" /><h3>3.安装依赖项</h3><p>在新 LangGraph 应用程序的根目录下创建虚拟环境，并以<code>edit</code> 模式安装依赖项，这样服务器就会使用本地更改：</p>#For Mac
python3 -m venv lg-demo
source lg-demo/bin/activate 
pip install -e .

#For Windows
python3 -m venv lg-demo
lg-demo\Scripts\activate 
pip install -e .<h2>设置环境</h2><h3>1.创建 .环境文件</h3><p><code>.env</code> 文件包含 API 密钥和配置，因此应用程序可以连接到您选择的 LLM 和检索提供商。复制示例配置，生成新的<code>.env</code> 文件：</p>cp .env.example .env<h3>2.配置 .env文件</h3><p><code>.env</code> 文件带有一组默认配置。您可以根据设置添加必要的 API 密钥和值来更新它。任何与使用案例无关的键都可以保持不变或删除。</p># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent

# LLM choice (set the API key for your selected provider):
ANTHROPIC_API_KEY=your_anthropic_api_key
FIREWORKS_API_KEY=your_fireworks_api_key
OPENAI_API_KEY=your_openai_api_key

# Retrieval provider (configure based on your chosen service):

## Elastic Cloud:
ELASTICSEARCH_URL=https://your_elastic_cloud_url
ELASTICSEARCH_API_KEY=your_elastic_api_key

## Elastic Local:
ELASTICSEARCH_URL=http://host.docker.internal:9200
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=changeme

## Pinecone:
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_INDEX_NAME=your_pinecone_index_name

## MongoDB Atlas:
MONGODB_URI=your_mongodb_connection_string

# Cohere API key:
COHERE_API_KEY=your_cohere_api_key<ul><li><p><code>.env</code> 文件示例（使用弹性云和 Cohere）</p></li></ul><p>下面是<code>.env</code> 配置示例，用于将<strong>Elastic Cloud</strong>用作检索提供商，将<strong>Cohere</strong>用作 LLM，本博客对此进行了演示：</p># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent
#Retrieval Provider
# Elasticsearch configuration
ELASTICSEARCH_URL=elastic-url:443
ELASTICSEARCH_API_KEY=elastic_api_key
# Cohere API key
COHERE_API_KEY=cohere_api_key<p><em>注：本指南使用 Cohere 进行响应生成和嵌入，您也可以 </em>根据自己的使用情况使用 其他 LLM 提供商，如 <em><strong>OpenAI</strong></em><em>、 </em><em><strong>Claude</strong></em><em>，甚至本地 LLM 模型。请确保您打算使用的每个密钥都已存在，并在 </em><em><code>.env</code></em><em> 文件</em>中正确设置  。</p><h3>3.更新配置文件 -configuration.py </h3><p>使用适当的 API 密钥设置<code>.env</code> 文件后，下一步就是更新应用程序的默认模型配置。更新配置可确保系统使用您在<code>.env</code> 文件中指定的服务和模型。</p><p>导航至配置文件：</p> cd src/retrieval_graph<p><code>configuration.py</code> 文件包含检索代理用于三项主要任务的默认模型设置：</p><ul><li><p><strong>嵌入模型</strong>--将文件转换为矢量表示</p></li><li><p><strong>查询模型</strong>- 将用户的查询转化为矢量</p></li><li><p><strong>响应模型</strong>- 生成最终响应</p></li></ul><p>默认情况下，代码使用<strong>OpenAI</strong>（如<code>openai/text-embedding-3-small</code> ）和<strong>Anthropic</strong>（如<code>anthropic/claude-3-5-sonnet-20240620 and anthropic/claude-3-haiku-20240307</code> ）的模型。在本博客中，我们将改用 Cohere 模型。如果您已经在使用 OpenAI 或 Anthropic，则无需更改。</p><h4>更改示例（使用 Cohere）：</h4><p>打开<code>configuration.py</code> 并修改模型默认值，如下图所示：</p>…
 embedding_model: Annotated[
       str,
       {"__template_metadata__": {"kind": "embeddings"}},
   ] = field(
       default="cohere/embed-english-v3.0",
…
response_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
…
query_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
       metadata={<h2>使用 LangGraph CLI 运行检索代理</h2><h3>1.启动 LangGraph 服务器</h3>cd lg-agent-demo
langgraph dev<p>这将在本地启动 LangGraph API 服务器。如果运行成功，你应该会看到类似的内容：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt46c7a703e715ef66/6a17f86eb1e113272d79f42e/e3c3344b24651067e2d0892d870feca505b3be35-1494x542.png" alt=" LangGraph API 服务器运行成功。" /><p>开放工作室用户界面 URL。</p><p>有两种图表可供选择：</p><ul><li><p><strong>检索图</strong>：从 Elasticsearch 中检索数据，并使用 LLM 响应查询。</p></li><li><p><strong>索引图</strong>：将文档索引到 Elasticsearch，并使用 LLM 生成嵌入。</p></li></ul><h3>2.配置索引图</h3><ul><li><p>打开索引图。</p></li><li><p>单击管理助手。</p><ul><li><p>点击<strong>"添加新助手</strong>"，输入指定的用户详细信息，然后关闭窗口。</p></li></ul></li></ul>{"user_id": "101"}<h3>3.为样本文件编制索引</h3><ul><li><p>为以下样本文件编制索引，这些文件是消费与工业专用技术公司的假设季度报告：</p></li></ul>[
  {    "page_content": "NoveTech Solutions Q1 2025 Report - Revenue: $120.5M, Net Profit: $18.2M, EPS: $2.15. Strong AI software launch and $50M government contract secured."
  },
  {
    "page_content": "NoveTech Solutions Business Highlights - AI-driven analytics software gained 15% market share. Expansion into Southeast Asia with two new offices. Cloud security contract secured."
  },
  {
    "page_content": "NoveTech Solutions Financial Overview - Operating expenses at $85.3M, Gross Margin 29.3%. Stock price rose from $72.5 to $78.3. Market Cap reached $5.2B."
  },
  {
    "page_content": "NoveTech Solutions Challenges - Rising supply chain costs impacting hardware production. Regulatory delays slowing European expansion. Competitive pressure in cybersecurity sector."
  },
  {
    "page_content": "NoveTech Solutions Future Outlook - Expected revenue for Q2 2025: $135M. New AI chatbot and blockchain security platform launch planned. Expansion into Latin America."
  },
  {
    "page_content": "NoveTech Solutions Market Performance - Year-over-Year growth at 12.7%. Stock price increase reflects investor confidence. Cybersecurity and AI sectors remain competitive."
  },
  {
    "page_content": "NoveTech Solutions Strategic Moves - Investing in R&amp;D to enhance AI-driven automation. Strengthening partnerships with enterprise cloud providers. Focusing on data privacy solutions."
  },
  {
    "page_content": "NoveTech Solutions CEO Statement - 'NoveTech Solutions continues to innovate in AI and cybersecurity. Our growth strategy remains strong, and we foresee steady expansion in the coming quarters.'"
  }
]<p>文件索引完成后，你会在线程中看到一条删除信息，如下图所示。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38715eadffcb62f0/6a17f877faa9135f7393ca4c/fd3a1efd64cb54d54ea56ef5055249dd066d5708-1600x854.png" alt="为 LangGraph 和 Elasticsearch RAG 工作流文档编制索引。" /><h3>4.运行检索图</h3><ul><li><p>切换到检索图。</p></li><li><p>输入以下搜索查询：</p></li></ul>What was NovaTech Solutions total revenue in Q1 2025?<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c0d070fca52e512/6a17f879505ac36d37ad8d12/eb4d8ddfe0effd7e1868fba921b8ef13f7baf27a-1600x755.png" alt="运行 LangGraph 和 Elasticsearch 检索图" /><p>系统将返回相关文件，并根据索引数据提供准确答案。</p><h2>自定义检索代理</h2><p>为了提升用户体验，我们在检索图中引入了一个定制步骤，以预测用户可能提出的下三个问题。这一预测的依据是</p><ul><li><p>从检索到的文件中获取上下文</p></li><li><p>以前的用户互动</p></li><li><p>最后一次用户查询</p></li></ul><p>实施查询预测功能需要更改以下代码：</p><h3>1.更新 graph.py</h3><ul><li><p>添加<code>predict_query</code> 功能：</p></li></ul>async def predict_query(
   state: State, *, config: RunnableConfig
) -&gt; dict[str, list[BaseMessage]]:
   logger.info(f"predict_query predict_querypredict_query predict_query predict_query predict_query")  # Log the query

   configuration = Configuration.from_runnable_config(config)
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.predict_next_question_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)
   user_query = state.queries[-1] if state.queries else "No prior query available"
   logger.info(f"user_query: {user_query}")
   logger.info(f"statemessage: {state.messages}")
   #human_messages = [msg for msg in state.message if isinstance(msg, HumanMessage)]

   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "user_query": user_query,  # Use the most recent query as primary input
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )

   next_question = await model.ainvoke(message_value, config)
   return {"next_question": [next_question]}<ul><li><p>修改<code>respond</code> 函数，以返回<strong><code>response</code></strong> 对象，而不是消息：</p></li></ul>async def respond(
   state: State, *, config: RunnableConfig
) -&gt; dict[str, list[BaseMessage]]:
   """Call the LLM powering our "agent"."""
   configuration = Configuration.from_runnable_config(config)
   # Feel free to customize the prompt, model, and other logic!
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.response_system_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)

   retrieved_docs = format_docs(state.retrieved_docs)
   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "retrieved_docs": retrieved_docs,
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )
   response = await model.ainvoke(message_value, config)
   # We return a list, because this will get added to the existing list
   return {"response": [response]}<ul><li><p>更新图结构，为 predict_query 添加新节点和边：</p></li></ul>builder.add_node(generate_query)
builder.add_node(retrieve)
builder.add_node(respond)
builder.add_node(predict_query)
builder.add_edge("__start__", "generate_query")
builder.add_edge("generate_query", "retrieve")
builder.add_edge("retrieve", "respond")
builder.add_edge("respond", "predict_query")<h3>2.更新 prompts.py</h3><ul><li><p>在<code>prompts.py</code> 中进行guery预测的工艺提示：</p></li></ul>PREDICT_NEXT_QUESTION_PROMPT = """Given the user query and the retrieved documents, suggest the most likely next question the user might ask.

**Context:**
- Previous Queries:
{previous_queries}

- Latest User Query: {user_query}

- Retrieved Documents:
{retrieved_docs}

**Guidelines:**
1. Do not suggest a question that has already been asked in previous queries.
2. Consider the retrieved documents when predicting the next logical question.
3. If the user's query is already fully answered, suggest a relevant follow-up question.
4. Keep the suggested question natural and conversational.
5. Suggest at least 3 question

System time: {system_time}"""<h3>3.更新 configuration.py</h3><ul><li><p>添加<code>predict_next_question_prompt</code> ：</p></li></ul>predict_next_question_prompt: str = field(
       default=prompts.PREDICT_NEXT_QUESTION_PROMPT,
       metadata={"description": "The system prompt used for generating responses."},
   )<h3>4.更新 state.py</h3><ul><li><p>添加以下属性</p></li></ul>response: Annotated[Sequence[AnyMessage], add_messages]
next_question : Annotated[Sequence[AnyMessage], add_messages]<h3>5.重新运行检索图</h3><ul><li><p>再次输入以下搜索查询：</p></li></ul>What was NovaTech Solutions total revenue in Q1 2025?<p>系统将处理输入信息，并预测用户可能提出的三个相关问题，如下图所示。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8d4de9396ca4853/6a17f87be31791dc742d59c7/70e855a2e4edc0ba5a147588df0de30eb081d053-1600x777.png" alt="使用 LangGraph 和 Elasticsearch 运行包含 3 个用户问题的检索图" /><h2>结论</h2><p>在 LangGraph Studio 和 CLI 中集成检索代理模板有几个主要好处：</p><ul><li><p><strong>加速开发</strong>：模板和可视化工具简化了检索工作流的创建和调试，缩短了开发时间。</p></li><li><p><strong>无缝部署</strong>：对 API 和自动扩展的内置支持可确保跨环境的顺利部署。</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><p>该项目的完整源代码可在<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/langraph-retrieval-agent-template-demo">GitHub</a> 上获取。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Neha Saini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c9f03d2c1a9cb4c/6a17f87d0b0bed3781dd377c/17b7e7b336f73e232375d1add582ae5f6c52a279-1440x840.png" length="0" type="image/png"/>
    <pubDate>Fri, 25 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[在 Elasticsearch 中使用 Amazon Nova 模型]]></title>
    <description><![CDATA[了解如何在 Elasticsearch 中使用 Amazon Nova 模型，自动从产品评论中提取情感倾向、真实性判断、内容摘要及关键词。]]></description>
    <content:encoded><![CDATA[<p>在本文中，我们将讨论亚马逊的人工智能模型系列 Amazon Nova，并学习如何将其与 Elasticsearch 结合使用。</p><h2>关于亚马逊新星</h2><p>Amazon Nova 是亚马逊人工智能模型系列，可在亚马逊 Bedrock 上使用，旨在提供高性能和高成本效益。这些模型可使用文本、图像和视频输入，生成文本输出，并针对不同的精度、速度和成本需求进行了优化。</p><h3>亚马逊 Nova 主要型号</h3><ul><li><p>亚马逊 Nova Micro：该机型专门针对文本，速度快、性价比高，是翻译、推理、代码补全和解决数学问题的理想之选。其生成速度超过每秒 200 个令牌，非常适合需要即时响应的应用。</p></li><li><p>Amazon Nova Lite：一款低成本多模态模型，能够快速处理图像、视频及文本数据。该模型以其速度和准确性脱颖而出，适用于成本因素显著的大流量交互式应用场景。</p></li><li><p>亚马逊新星专业版：最先进的选择，集高精度、高速度和高性价比于一身。是视频摘要、问答、软件开发和人工智能代理等复杂任务的理想选择。专家评论证明了它在文本和视觉理解方面的卓越表现，以及它遵循指令和执行自动工作流程的能力。</p></li></ul><p>亚马逊 Nova 模型适用于各种应用，从内容创建和数据分析到软件开发和人工智能驱动的流程自动化。</p><p>下面，我们将演示如何结合 Elasticsearch 使用 Amazon Nova 模型进行自动产品评论分析。</p><p>我们将做什么</p><ol><li><p>通过 Inference API 创建一个端点，将 Amazon Bedrock 与 Elasticsearch 集成在一起。</p></li><li><p>使用推理处理器创建一个管道，该管道将调用推理 API 端点。</p></li><li><p>索引产品评论，并使用管道自动生成评论分析。</p></li><li><p>分析整合结果。</p></li></ol><h2>使用 Amazon Nova Lite 在推理 API 中创建终端</h2><p>首先，我们配置 Inference API，将 Amazon Bedrock 与 Elasticsearch 集成。我们定义了亚马逊 No<strong>va Lite</strong>，id 为amazon.nova-lite-v1:0、因为它在速度、准确性和成本之间取得了平衡。</p><p><strong>注意：</strong>使用 Amazon Bedrock 需要有效凭证。您可以<a href="https://docs.aws.amazon.com/keyspaces/latest/devguide/create.keypair.html">在此处</a>查看获取访问密钥的文档：</p>PUT _inference/completion/bedrock_completion_amazon_nova-lite
{
   "service": "amazonbedrock",
   "service_settings": {
       "access_key": "#access_key#",
       "secret_key": "#secret_key#",
       "region": "us-east-1",
       "provider": "amazontitan",
       "model": "amazon.nova-lite-v1:0"
   }
}<h2>创建审查分析管道</h2><p>现在，我们创建一个处理管道，使用推理处理器来执行审查分析提示。此提示将把评论数据发送到 Amazon Nova Lite，由其执行：</p><ul><li><p>情绪分类（积极、消极或中性）。</p></li><li><p>审查总结。</p></li><li><p>关键词生成。</p></li><li><p>真实性测量（真实 | 可疑 | 一般）。</p></li></ul>PUT /_ingest/pipeline/review_analyzer_ai
{
      "processors": [
      {
        "script": 
            {
            "source": """ctx.prompt = "Analyze the following product review and return a structured JSON. Task: - Summarize the review concisely. - Detect and classify the sentiment as positive, neutral, or negative.- Generate relevant tags (keywords) based on the review content and detected sentiment. - Evaluate the authenticity of the review (authentic, suspicious, or generic). Review: " + ctx.review + " Respond in JSON format with the following fields: \"review_analyze\": {\"sentiment\": \"&lt;positive | neutral | negative&gt;\", \"authenticity\": \"&lt;authentic | suspicious | generic&gt;\",\"summary\": \"&lt;short review summary&gt;\", \"keywords\": [\"&lt;keyword 1&gt;\", \"&lt;keyword 2&gt;\", \"...\"]}}}"
            """
            }
      },
      {
        "inference": {
          "model_id": "bedrock_completion_amazon_nova-lite",
          "input_output": {
            "input_field": "prompt",
            "output_field": "result"
          }
        }
      },
      {
        "gsub": {
          "field": "result",
          "pattern": "```json",
          "replacement": ""
        } 
      },
      {
        "json" : {
          "field" : "result",
          "strict_json_parsing": false,
          "add_to_root" : true
        }
      },
      {
        "remove": {
          "field": "result"
        }
      },
      {
        "remove": {
          "field": "prompt"
        }
      }
    ]
}<h2>索引审查</h2><p>现在，我们使用批量 API 对产品评论进行索引。先前创建的管道将自动应用，将 Nova 模型生成的分析添加到索引文档中。</p>POST bulk/
{ "index": { "_index" : "products", "_id": 1, "pipeline":"review_analyzer_ai" } }
{ "product": "Pampers Pants Premium Care Fralda", "review": "Best diaper ever! Great material, lots of cotton, without all that plastic. Doesn't leak! My baby is a boy and every diaper leaked around the waist, this model solved the problem. Even on a small baby it's worth the effort of putting on the short diaper. I put it on my baby at 9 pm and only take it off in the morning, without any leaks." }
{ "index": { "_index" : "products", "_id": 2, "pipeline":"review_analyzer_ai" } }
{ "product": "Portable Electric Body Massager", "review": "It broke in three months for no apparent reason, thank goodness I didn't review it before. I don't recommend buying it because it has a short lifespan." }
{ "index": { "_index" : "products", "_id": 3, "pipeline":"review_analyzer_ai" } }
{ "product": "Havit Fuxi-H3 Black Quad-Mode Wired and Wireless Gaming Headset", "review": "The sound is good for the price, but the connectivity is horrible. You always need to be playing audio, otherwise it loses connection (I work from home, and this is very annoying). Sometimes it loses connection and you have to turn it off and on again to get it back on. The microphone is very sensitive, so it loses connection frequently and you have to turn the headset off and on for the microphone to work again. The flexibility of the stem is useless, because if you move it, the microphone can turn off. Sometimes I need to use Linux and the headset simply doesn't work. It's light and comfortable, the sound is adequate, but the connectivity is terrible." }
{ "index": { "_index" : "products", "_id": 4, "pipeline":"review_analyzer_ai" } }
{ "product": "Air Fryer 4L Oil Free Fryer Mondial", "review": "For those looking for value for money, it's a good option, but the tray (which is underneath the perforated basket) is already peeling a lot. My mother has one just like it and said that hers is even rusting, in other words, the material is MUCH inferior. There's also something that bothers me, because it looks like a microwave, it doesn't fry evenly, it's weaker in the middle and stronger on the sides. Buy at your own risk." }<h2>查询和分析结果</h2><p>最后，我们运行一个查询，看看亚马逊 Nova Lite 模型是如何对评论进行分析和分类的。通过运行 GET products/_search，我们可以获得已经用评论内容生成的字段充实过的文档。</p><p>该模型可识别主要情绪（正面、中性或负面），生成简明摘要，提取相关关键词，并估计每条评论的真实性。这些字段有助于了解客户的意见，而无需阅读全文。</p><p>为了解释结果，我们研究了</p><ul><li><p>情感，表示消费者对产品的总体看法。</p></li><li><p>摘要，突出了所述要点。</p></li><li><p>关键词，可用于对类似评论进行分组或识别反馈模式。</p></li><li><p>真实性，表示评论是否可信。这对策划或管理非常有用。</p></li></ul>   "hits": [
      {
        "_index": "products",
        "_id": "1",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Pampers Pants Premium Care Fralda",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The reviewer praises the diaper for its great material, high cotton content, and leak-proof design, especially highlighting its effectiveness for their baby.",
            "sentiment": "positive",
            "keywords": [
              "best diaper",
              "great material",
              "cotton",
              "no plastic",
              "leak-proof",
              "baby",
              "effective"
            ],
            "authenticity": "authentic"
          },
          "review": "Best diaper ever! Great material, lots of cotton, without all that plastic. Doesn't leak! My baby is a boy and every diaper leaked around the waist, this model solved the problem. Even on a small baby it's worth the effort of putting on the short diaper. I put it on my baby at 9 pm and only take it off in the morning, without any leaks."
        }
      },
      {
        "_index": "products",
        "_id": "2",
        "_score": 1,
        "_source": {
          "product": "Portable Electric Body Massager",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The product broke in three months for no apparent reason and the reviewer does not recommend it due to its short lifespan.",
            "sentiment": "negative",
            "keywords": [
              "broke",
              "short lifespan",
              "not recommend"
            ],
            "authenticity": "authentic"
          },
          "review": "It broke in three months for no apparent reason, thank goodness I didn't review it before. I don't recommend buying it because it has a short lifespan."
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Havit Fuxi-H3 Black Quad-Mode Wired and Wireless Gaming Headset",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The headset has good sound quality for the price but suffers from poor connectivity, especially when using the microphone or moving the headset. It also has compatibility issues with Linux.",
            "sentiment": "negative",
            "keywords": [
              "sound",
              "connectivity",
              "microphone",
              "compatibility",
              "annoying",
              "turn off and on",
              "Linux",
              "flexible stem",
              "work from home"
            ],
            "authenticity": "authentic"
          },
          "review": "The sound is good for the price, but the connectivity is horrible. You always need to be playing audio, otherwise it loses connection (I work from home, and this is very annoying). Sometimes it loses connection and you have to turn it off and on again to get it back on. The microphone is very sensitive, so it loses connection frequently and you have to turn the headset off and on for the microphone to work again. The flexibility of the stem is useless, because if you move it, the microphone can turn off. Sometimes I need to use Linux and the headset simply doesn't work. It's light and comfortable, the sound is adequate, but the connectivity is terrible."
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 1,
        "_ignored": [
          "review.keyword"
        ],
        "_source": {
          "product": "Air Fryer 4L Oil Free Fryer Mondial",
          "model_id": "bedrock_completion_amazon_nova-lite",
          "review_analyze": {
            "summary": "The product offers value for money but has issues with peeling, rusting, and uneven frying.",
            "sentiment": "negative",
            "keywords": [
              "value for money",
              "peeling",
              "rusting",
              "uneven frying",
              "weaker in the middle"
            ],
            "authenticity": "authentic"
          },
          "review": "For those looking for value for money, it's a good option, but the tray (which is underneath the perforated basket) is already peeling a lot. My mother has one just like it and said that hers is even rusting, in other words, the material is MUCH inferior. There's also something that bothers me, because it looks like a microwave, it doesn't fry evenly, it's weaker in the middle and stronger on the sides. Buy at your own risk."
        }
      }
    ]<h2>总结</h2><p>Amazon Nova Lite 与 Elasticsearch 的整合展示了语言模型如何将原始评论转化为结构化的有价值信息。通过管道处理评论，我们能够自动、一致地提取情感、真实性、摘要和关键词。</p><p>结果表明，该模型可以理解评论的上下文，对用户意见进行分类，并突出每个体验中最相关的要点。这将创建一个更丰富的数据集，可用于提高搜索能力。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/amazon-nova-models-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/amazon-nova-models-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbbf13eb690294f1/6a17fddd6df73195190a115a/304713c48b568e17d0bb56b19edb28769f7801b3-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 02 Apr 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 构建多模式 RAG 系统：高谭市的故事]]></title>
    <description><![CDATA[了解如何构建多模态检索增强生成（RAG）系统，该系统可整合文本、音频、视频和图像数据，以提供更丰富的上下文化信息检索。]]></description>
    <content:encoded><![CDATA[<p>在本博客中，您将学习如何使用 Elasticsearch 构建多模态 RAG（检索-增强生成）管道。我们将探讨如何利用 ImageBind 为文本、图像、音频和深度图等各种数据类型生成嵌入。您还将了解如何使用 dense_vector 和 k-NN 搜索在 Elasticsearch 中高效地存储和检索这些嵌入。最后，我们将整合一个大型语言模型（LLM）来分析检索到的证据，并生成一份全面的最终报告。</p><h3>多式联运 RAG 管道是如何运作的？</h3><ol><li><p><strong>收集线索</strong>→ 哥谭市犯罪现场的图像、音频、文本和深度图。</p></li><li><p><strong>生成嵌入</strong>→使用 ImageBind 多模态模型将每个文件转换为矢量。</p></li><li><p><strong>在 Elasticsearch 中建立索引</strong>→ 存储向量以便高效检索。</p></li><li><p><strong>通过相似性检索</strong>→ 给出一条新线索，检索最相似的向量。</p></li><li><p><strong>LLM 分析证据</strong>→ GPT-4 模型综合反应并确定嫌疑人！</p></li></ol><h3>使用的技术</h3><ul><li><p><strong>ImageBind</strong>→ 为各种模式生成统一的嵌入。</p></li><li><p><strong>Elasticsearch</strong>→ 实现快速高效的矢量搜索。</p></li><li><p><strong>LLM (GPT-4, OpenAI)</strong>→ 分析证据并生成最终报告。</p></li></ul><h3>这个博客为谁而设？</h3><ul><li><p>对多模式向量搜索感兴趣的弹性用户。</p></li><li><p>希望在实践中了解多模式 RAG 的开发人员。</p></li><li><p>任何人都在寻找可扩展的解决方案来分析来自多个来源的数据。</p></li></ul><h2>多模式 RAG 的先决条件：设置环境</h2><p>要在哥谭市破案，您需要建立自己的技术环境。请按照本指南逐步操作：</p><h3>1.技术要求</h3><p>组件</p><p>规格</p><p>系统操作系统</p><p>Linux、macOS 或 Windows</p><p>Python</p><p>3.10 或更高版本</p><p>内存</p><p>最低 8GB（建议 16GB）。</p><p>GPU</p><p>可选，但建议用于 ImageBind</p><h3><strong>2.设立项目</strong></h3><p>所有调查材料均可在 GitHub 上获取，我们将使用 Jupyter Notebook（Google Colab）进行此次互动式犯罪侦破体验。请按照以下步骤开始操作：</p><h4>使用 Jupyter Notebook（Google Colab）进行设置</h4><p><strong>1.访问笔记本</strong></p><ul><li><p>打开我们随时可用的 Google Colab 笔记本：<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/building-multimodal-rag-with-elasticsearch-gotham/notebook/01-mmrag-blog-quick-start.ipynb">使用 Elasticsearch 的多模式 RAG</a><u>。</u></p></li><li><p>这本笔记本包含了您需要的所有代码和解释，以便您能跟上进度。</p></li></ul><p><strong>2.克隆版本库</strong></p># Clone the repository with the multimodal RAG code
!git clone -b https://github.com/elastic/elasticsearch-labs.git

# Navigate to the project directory
cd elasticsearch-labs/supporting-blog-content/building-multimodal-rag-with-elasticsearch-gotham<p><strong>3.安装依赖项</strong></p> # Install PyTorch and related libraries
!pip install torch&gt;=2.1.0 torchvision&gt;=0.16.0 torchaudio&gt;=2.1.0

# Install vision processing libraries
!pip install opencv-python-headless pillow numpy

# Install the specific ImageBind fork
!pip install git+https://github.com/hkchengrex/ImageBind.git

# Install Elasticsearch and environment management
!pip install elasticsearch python-dotenv<p><strong>4.配置证书</strong></p># Input your credentials securely
import getpass

ELASTICSEARCH_URL = input("Enter the Elasticsearch endpoint url: ")
ELASTICSEARCH_API_KEY = getpass.getpass("Enter the Elasticsearch API key: ")
OPENAI_API_KEY = getpass.getpass("Enter the OpenAI API key: ")

# Configure environment variables
import os
os.environ["ELASTICSEARCH_API_KEY"] = ELASTICSEARCH_API_KEY
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
os.environ["ELASTICSEARCH_URL"] = ELASTICSEARCH_URL<p>注意：第一次运行时将自动下载 ImageBind 模型（~2GB）。</p><p>现在，一切准备就绪，让我们深入细节，破案吧！</p><h2>简介高谭市的犯罪</h2><p>在哥谭市的一个雨夜，一起令人震惊的犯罪事件震惊了整个城市。戈登局长需要你的帮助来揭开谜底。线索分散在不同的格式中：模糊的图像、神秘的音频、加密的文本，甚至深度图。您准备好使用最先进的人工智能技术破案了吗？</p><p>在本博客中，我们将逐步引导您建立<strong>一个多模态 RAG（检索-增强生成）系统</strong>，将不同类型的数据<strong>（图像、音频、文本和深度图</strong>）统一到一个搜索空间中。我们将使用<strong>ImageBind</strong>生成多模态嵌入，使用<strong>Elasticsearch</strong>存储和检索这些嵌入，并使用<strong>大型语言模型 (LLM)</strong>分析证据和生成最终报告。</p><h2>基础知识：多模式 RAG 架构</h2><h3>什么是多模式 RAG？</h3><p><strong>检索-增强一代（RAG）多模态</strong>的兴起正在彻底改变我们与人工智能模型的交互方式。传统上，RAG 系统只处理文本，先从数据库中检索相关信息，然后再生成回复。然而，世界并不局限于文字，图像<strong>、视频和音频也蕴含着宝贵的知识</strong>。这也是多模态架构日益突出的原因，它允许人工智能系统<strong>将来自不同格式的信息结合起来，从而做出更丰富、更精确的反应</strong>。</p><h3><strong>多模式 RAG 的三种主要方法</strong></h3><p>要实施多模式 RAG，通常采用三种策略。每种方法都有自己的优势和局限性，具体取决于使用情况：</p><h4>1.共享向量空间</h4><p>利用 ImageBind 等多模态模型将不同模态的数据映射到一个共同的向量空间。这样，文本查询就可以检索图像、视频和音频，而无需进行明确的格式转换。</p><p><strong>优势</strong></p><ul><li><p>实现<strong>跨模式检索</strong>，无需明确的格式转换。</p></li><li><p>提供不同模式之间的<strong>流畅整合</strong>，允许直接检索文本、图像、音频和视频。</p></li><li><p>可对各种数据类型进行扩展，因此适用于<strong>大规模检索应用</strong>。</p></li></ul><p><strong>缺点</strong></p><ul><li><p><strong>训练需要大型多模态数据集</strong>，而这些数据集并不总是可用的。</p></li><li><p>共享的嵌入空间可能会带来<strong>语义漂移</strong>，即模态之间的关系没有得到完美的保留。</p></li><li><p><strong>多模态模型的偏差</strong>会影响检索的准确性，这取决于数据集的分布情况。</p></li></ul><h4>2.单一接地方式</h4><p>在检索之前，所有模式都会转换为<strong>单一格式</strong>，通常是<strong>文本格式</strong>。例如，通过<strong>自动生成的标题</strong>对图像进行描述，并将音频转录为文本。</p><p><strong>优势</strong></p><ul><li><p>由于所有内容都转换为<strong> 统一的文本表示</strong><strong> ，因此 简化了检索</strong> 。</p></li><li><p>与<strong>现有的文本搜索引擎</strong>配合良好，无需专门的多模式基础设施。</p></li><li><p>由于检索结果采用人类可读格式，因此可提高<strong>可解释性</strong>。</p></li></ul><p><strong>缺点</strong></p><ul><li><p><strong>信息丢失</strong>：文本描述可能无法完全捕捉某些细节（如图像中的空间关系、音频中的音调）。</p></li><li><p><strong>取决于字幕/转录质量</strong>：自动注释中的错误会降低检索效率。</p></li><li><p><strong>对于纯视觉或听觉查询来说并不是最佳选择</strong>，因为转换过程可能会删除重要的上下文。</p></li></ul><h4>3.单独检索</h4><p>为每种模式维护<strong>不同的模型</strong>。系统对每种数据类型<strong>分别</strong>进行搜索，然后合并 搜索 <strong>结果</strong>。</p><p><strong>优势</strong></p><ul><li><p>允许对<strong>每种模式进行自定义优化</strong>，提高每类数据的检索准确性。</p></li><li><p>减少对<strong>复杂的多模态模型</strong>的依赖，从而更容易整合现有的检索系统。</p></li><li><p>由于可以动态组合不同模式的结果，因此可以<strong>对排序和重新排序进行精细控制</strong>。</p></li></ul><p><strong>缺点</strong></p><ul><li><p><strong>需要对结果进行融合</strong>，使检索和排序过程更加复杂。</p></li><li><p>如果不同模式返回的信息相互冲突，可能会产生<strong>不一致的响应</strong>。</p></li><li><p><strong>计算成本较高</strong>，因为要对每种模式进行独立搜索，增加了处理时间。</p></li></ul><h3>我们的选择：使用 ImageBind 共享矢量空间</h3><p>在这些方法中，我们选择了<strong>共享矢量空间</strong>，这一策略完全符合<strong>高效多模式搜索</strong>的需求。我们的实现基于<strong> ImageBind</strong> ，它是一种能够在<strong> 通用矢量空间</strong> <strong>中表示多种模式 （文本、图像、音频和视频</strong> ）的模型。这使我们能够</p><ul><li><p>在不同媒体格式之间进行<strong>跨模式搜索</strong>，无需将所有内容转换为文本。</p></li><li><p>使用<strong>表现力极强的嵌入式</strong>技术来捕捉不同模态之间的关系。</p></li><li><p>确保<strong>可扩展性和效率</strong>，存储优化嵌入，以便在 Elasticsearch 中快速检索。</p></li></ul><p>通过采用这种方法，我们建立了一个<strong>强大的多模态搜索管道</strong>，文本查询可以<strong>直接检索图像或音频</strong>，而无需额外的预处理。这种方法的实际应用范围从<strong>大型资料库的智能搜索</strong>扩展到<strong>先进的多模式推荐系统</strong>。</p><p>下图说明了多模态 RAG 管道中的数据流，突出显示了基于多模态数据的索引、检索和响应生成过程：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77bce4aa5216bcf3/6a17eef263173069e0585b57/a4ffdb44582738991813c045be37312dacb0d4f3-1488x1436.png" alt="多模式抹布数据流" /><h3>嵌入空间如何运作？</h3><p>传统上，文本嵌入来自语言模型（如 BERT、GPT）。现在，有了 Meta AI 的<strong>ImageBind</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></ul><p>因此，任何线索<strong>（文本、图像、音频、深度</strong>）都可以通过<strong>余弦</strong>相似度等矢量相似度量进行比较。如果<strong>笑声音频样本</strong>和<strong>嫌疑人面部图像</strong>在这个空间中 "接近"，我们就可以推断出某种相关性（例如，相同的身份）。</p><h2>第 1 阶段--收集犯罪现场线索</h2><p>在分析证据之前，我们需要收集证据。高谭市的犯罪留下的痕迹可能隐藏在图像、音频、文本甚至深度数据中。让我们把这些线索组织起来，输入我们的系统。</p><h3>我们有什么？</h3><p>戈登局长给我们发来了以下文件，其中包含从犯罪现场收集到的四种不同模式的证据：</p><p><strong>轨道说明和模式</strong></p><p><strong>a) 图片（2 张照片）</strong></p><ul><li><p><code>crime_scene1.jpg, crime_scene2.jpg</code> → 在犯罪现场拍摄的照片。显示地面上的可疑痕迹。</p></li><li><p><code>suspect_spotted.jpg</code> → 监控录像显示一个人影逃离现场。</p></li></ul><p><strong>b)</strong> <strong>音频（1 次录音）</strong></p><ul><li><p><code>joker_laugh.wav </code>→ 犯罪现场附近的麦克风捕捉到了一个阴险的笑声。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7690380dc545367/6a17eef8be6086886a00483b/3457bab38aa4a3caf61ca2a5e0a8b477ddd15cb1-86x45.png" alt="" /><p><strong>c) 文本（1 条信息）</strong></p><ul><li><p><code>Riddle.txt, note2.txt</code> → 在现场发现了一些神秘的纸条，可能是罪犯留下的。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e5b8f4bc4124708/6a17eefa1d1b83850c93e50c/8963228dc3b4b7e2106cb6ddffbe2154020eb735-77x79.png" alt="" /><p><strong>d) 深度（1 幅深度图）</strong></p><ul><li><p><code>depth_suspect.png</code> → 带有深度传感器的监控摄像头捕捉到附近巷子里的一名嫌疑人。</p></li><li><p><code>jdancing-depth.png</code> → 带有深度感应器的监控摄像头捕捉到一名嫌疑人从地铁站走下来。</p></li></ul><p>这些证据的格式不同，无法以相同的方式直接进行分析。我们需要将它们转化为嵌入式数字向量，以便进行跨模态比较。</p><h3><strong>文件组织</strong></h3><p>在开始处理之前，我们需要确保所有线索都在 data/ 目录中组织妥当，以便管道顺利运行。</p><p><strong>预期目录结构：</strong></p>data/
├── images/
│   ├── crime_scene1.jpg
│   ├── suspect_spotted.jpg
│   ...
├── audios/
│   ├── joker_laugh.wav
│   ...
├── texts/
│   ├── riddle.txt
│   ... 
├── depths/
│   ├── depth_suspect.png<h3>验证线索组织的代码</h3><p>在继续之前，让我们确保所有所需文件都在正确的位置。</p>import os

# Base directory for clues
data_dir = "data"

# List of expected files
evidences = {
    "images": ["crime_scene1.jpg","crime_scene1.jpg", "joker_alley.jpg"],
    "audios": ["joker_laugh.wav"],
    "texts": ["riddle.txt", "note2.txt”],
    "depths": ["depth_suspect.png", "jdancing-depth.png"]
}

# Create directories if they don't exist
for category, files in evidences.items():
    category_path = os.path.join(data_dir, category)
    os.makedirs(category_path, exist_ok=True)

    for file in files:
        file_path = os.path.join(category_path, file)
        if not os.path.exists(file_path):
            print(f"Warning: {file} not found in {category_path}.")

print("All files are correctly organized!")<p><strong>运行文件</strong></p>python  stages/01-stage/files_check.py<p><strong>预期输出（如果所有文件都正确）：</strong></p>All files are correctly organized!<p><strong>预期输出（如果缺少任何文件）：</strong></p>Warning: joker_laugh.wav not found in data/audios/
Warning: depth_suspect.png not found in data/depths/<p>在我们开始生成嵌入并将其索引到 Elasticsearch 之前，该脚本有助于防止错误的发生。</p><h2>第 2 阶段--整理证据</h2><h3>使用 ImageBind 生成嵌入</h3><p>为了统一这些线索，我们需要将它们转化为嵌入式向量表征，以捕捉每种模态的含义。我们将使用<strong>ImageBind</strong>，它是 Meta AI 的一个模型，可在共享向量空间内生成不同数据类型<strong>（图像、音频、文本和深度图</strong>）的嵌入。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff4aefffbfb5bf00/6a17eefe6864a45aecb68860/b19a1c32cc7a0b4c00fa5b247b18cce71f9693cb-1580x918.png" alt="使用 ImageBind 生成嵌入" /><h3><strong>ImageBind 如何工作？</strong></h3><p>要比较不同类型的证据<strong>（图像、音频、文本和深度图</strong>），我们需要使用<strong>ImageBind 将</strong>它们转换成数字向量。这种模式允许将任何类型的输入转换为相同的嵌入格式，从而实现不同模态之间的<strong>跨</strong>模态搜索。</p><p>以下是经过优化的代码 (<code>src/embedding_generator.py</code>) ，可使用适合各种模态的处理器为任何类型的输入生成嵌入：</p>class EmbeddingGenerator:
    """Class for generating multimodal embeddings using ImageBind."""
    
    def __init__(self):
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.model = self._load_model()

    def _load_model(self):
        """Loads the ImageBind model and sets it to inference mode."""
        model = imagebind_model.imagebind_huge(pretrained=True)
        model.eval()
        model.to(self.device)
        return model

    def generate_embedding(self, input_data, modality):
        """Generates embedding for different modalities"""
        processors = {
            "vision": lambda x: data.load_and_transform_vision_data(x, self.device),
            "audio": lambda x: data.load_and_transform_audio_data(x, self.device),
            "text": lambda x: data.load_and_transform_text(x, self.device),
            "depth": self.process_depth
        }
        
        try:
            # Input type verification
            if not isinstance(input_data, list):
                raise ValueError(f"Input data must be a list. Received: {type(input_data)}")
                
            # Convert input data to a tensor format that the model can process
            # For images: [batch_size, channels, height, width] 
            # For audio: [batch_size, channels, time] 
            # For text: [batch_size, sequence_length]
            inputs = {modality: processors[modality](input_data)}
            with torch.no_grad():
                embedding = self.model(inputs)[modality]
            return embedding.squeeze(0).cpu().numpy()
        except Exception as e:
            logger.error(f"Error generating {modality} embedding: {str(e)}", exc_info=True)
            raise<p>张量是机器学习和深度学习中的一种基本数据结构，尤其是在使用 ImageBind 等模型时。就我们而言</p>input_tensor = processors[modality]([input_data], self.device)<p>在这里，张量表示将输入数据（图像、音频或文本）转换成模型可以处理的数学格式。具体来说</p><ul><li><p><strong>对于图像</strong>：张量以数值的多维矩阵（按高度、宽度和颜色通道排列的像素）来表示图像。</p></li><li><p><strong>音频</strong>张量将声波表示为随时间变化的振幅序列。</p></li><li><p><strong>对于文本</strong>：张量将单词或标记表示为数字向量。</p></li></ul><h3>测试嵌入生成：</h3><p>让我们用下面的代码测试一下嵌入生成。将其保存在 02-stage/test_embedding_generation.py 中，并使用此命令执行：</p>python stages/02-stage/test_embedding_generation.py generator = EmbeddingGenerator()
image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg","vision")

print(image_embedding.shape)<h3>预期输出：</h3>(1024,)<p>现在，图像已转换成<strong>1024 维向量</strong>。</p><h2>第 3 阶段 - Elasticsearch 中的存储和搜索</h2><p>既然我们已经生成了证据的嵌入，就需要将它们存储到向量数据库中，以便进行高效搜索。为此，我们将使用<strong>Elasticsearch</strong>，它支持密集向量 (<code>dense_vector</code>) 并允许进行相似性搜索。</p><p>这一步骤包括两个主要过程：</p><ul><li><p><strong>索引嵌入</strong>→ 将生成的向量存储在 Elasticsearch 中。</p></li><li><p><strong>相似性搜索</strong>→ 检索与新证据最相似的记录。</p></li></ul><h3>在 Elasticsearch 中索引证据</h3><p><strong>ImageBind</strong>处理的每个证据（图像、音频、文本或深度）都会转换成<strong>1024 维向量</strong>。我们需要在<strong>Elasticsearch</strong>中存储这些向量，以便将来进行搜索。</p><p>下面的代码 (<code>src/elastic_manager.py</code>) 在 Elasticsearch 中创建了一个<strong>索引</strong>，并配置了映射以存储嵌入。</p>from elasticsearch import Elasticsearch, helpers
...

class ElasticsearchManager:
    """Manages multimodal operations in Elasticsearch"""
    
    def __init__(self):
        load_dotenv()  # Load variables from .env
        self.es = self._connect_elastic()
        self.index_name = "multimodal_content"
        self._setup_index()
    
    def _connect_elastic(self):
        """Connects to Elasticsearch"""
        return Elasticsearch(
            os.getenv("ELASTICSEARCH_URL"),  # Elasticsearch endpoint
            api_key=os.getenv("ELASTICSEARCH_API_KEY")
        )
    
    def _setup_index(self):
        """Sets up the index if it doesn't exist"""
        if not self.es.indices.exists(index=self.index_name):
            mapping = {
                "mappings": {
                    "properties": {
                        "embedding": {
                            "type": "dense_vector",
                            "dims": 1024,
                            "index": True,
                            "similarity": "cosine"
                        },
                        "modality": {"type": "keyword"},
                        "content": {"type": "binary"},
                        "description": {"type": "text"},
                        "metadata": {"type": "object"},
                        "content_path": {"type": "text"}
                    }
                }
            }
            self.es.indices.create(index=self.index_name, body=mapping)
    
    def index_content(self, embedding, modality, content=None, description="", metadata=None, content_path=None):
        """Indexes multimodal content"""
        doc = {
            "embedding": embedding.tolist(),
            "modality": modality,
            "description": description,
            "metadata": metadata or {},
            "content_path": content_path
        }
        
        if content:
            doc["content"] = base64.b64encode(content).decode() if isinstance(content, bytes) else content
        
        return self.es.index(index=self.index_name, document=doc)
    
    def search_similar(self, query_embedding, modality=None, k=5):
        """Searches for similar contents"""
        query = {
            "knn": {
                "field": "embedding",
                "query_vector": query_embedding.tolist(),
                "k": k,
                "num_candidates": 100,
                "filter": [{"term": {"modality": modality}}] if modality else []
            }
        }
        
        try:
            response = self.es.search(
                index=self.index_name,
                query=query,
                size=k            
            )
            
            # Return both source data and score for each hit
            return [{
                **hit["_source"],
                "score": hit["_score"]
            } for hit in response["hits"]["hits"]]
        
        except Exception as e:
            print(f"Error: processing search_evidence: {str(e)}")
            return "Error generating search evidence"<h3>运行索引</h3><p>现在，让我们用一个证据来检验这个过程。</p># Example: Indexing an image from the crime scene
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")

image_embedding = generator.generate_embedding("data/images/crime_scene1.jpg", "vision")

response = es_manager.index_content(
    embedding=image_embedding,
    modality="vision",
    description="Photo of the crime scene with suspicious traces",
    content_path="data/images/crime_scene1.jpg"
)
print(json.dumps(response, indent=2))<p><strong>Elasticsearch 中的预期输出（索引文档的摘要）：</strong></p>{
    "embedding": [0.12, -0.53, 0.89, ...],  
    "modality": "vision",  
    "description": "Photo of the crime scene with suspicious traces",  
    "content_path": "data/images/crime_scene1.jpg"  
}<p>要为所有多模式证据编制索引，请执行以下 Python 命令：</p>python stages/03-stage/index_all_modalities.py<p>现在，证据存储在<strong>Elasticsearch</strong>中，需要时可随时检索。</p><h3>验证索引编制过程</h3><p>运行索引脚本后，让我们验证一下 Elasticsearch 中是否正确存储了所有证据。您可以使用<strong>Kibana 的开发工具</strong>运行一些验证查询：</p><p>1.首先，检查是否创建了索引：</p>GET _cat/indices/multimodal_content?v<p>2.然后，验证每种模式的文件数量：</p>GET multimodal_content/_search
{
  "size": 0,
  "aggs": {
    "modalities": {
      "terms": {
        "field": "modality.keyword"
      }
    }
  }
}<p>3.最后，检查索引文档结构：</p>GET multimodal_content/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  }
}<h4>预期成果：</h4><ul><li><p>应存在名为 `multimodal_content` 的索引。</p></li><li><p>约 7 份文件分布于不同的模式（视觉、音频、文本、深度）。</p></li><li><p>每个文档都应包含：嵌入、模式、描述、元数据和内容路径字段。</p></li></ul><p>这一验证步骤可确保我们在进行相似性搜索之前正确建立证据数据库。</p><h3>在 Elasticsearch 中搜索类似证据</h3><p>既然证据已经编入索引，我们就可以进行搜索，找出与新线索最相似的记录。这种搜索使用<strong>向量相似性</strong>来返回<strong>嵌入空间</strong>中最接近的记录。</p><p>以下代码将执行此搜索。</p>def search_similar_evidence(self, query_embedding, k=5, modality=None):
    """Performs a kNN search to find the most similar clues."""
    
    knn_query = {
        "field": "embedding",
        "query_vector": query_embedding.tolist(),
        "k": k,
        "num_candidates": 100
    }

    query_body = {"knn": knn_query}
    if modality:
        query_body = {
            "bool": {
                "must": [
                    query_body, 
                    {"term": {"modality": modality}}
                ]
            }
        }

    try:
      results = self.es.search(
        index=self.index_name,
        query=query_body,
        _source_includes=["description", "modality", "content_path"],
        size=k
      )
    except Exception as e:
            print(f"Error processing search_evidence: {str(e)}")
            return "Error generating search evidence”

    return results["hits"]["hits"]<h3>测试搜索 - 使用音频作为多模态结果的查询方式</h3><p>现在，让我们用一个<strong>可疑的音频文件</strong>来测试一下证据搜索功能。我们需要以同样的方式生成文件的嵌入，并搜索类似的嵌入：</p>python stages/03-stage/search_by_audio.py# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager(cloud_id="YOUR_CLOUD_ID", api_key="YOUR_API_KEY")

# Generate embedding for a suspicious audio
audio_embedding = generator.generate_embedding("data/audios/mysterious_laugh.wav", "audio")

# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar_evidence(audio_embedding, k=3)

# Display the retrieved results
print("\n🔎 Similar evidence found:\n")
for i, evidence in enumerate(similar_evidences, start=1):
    description = evidence['_source']['description']
    modality = evidence['_source']['modality']
    score = evidence['_score']
    content_path = evidence['_source'].get('content_path', 'N/A')

    print(f"{i}. {description} ({modality})")
    print(f"   Similarity: {score:.4f}")
    print(f"   File path: {content_path}\n")<p><strong>终端中的预期输出：</strong></p>🔎 Similar evidence found:

1. A sinister laugh captured near the crime scene (audio)
   Similarity: 0.9985
   File path: data/audios/joker_laugh.wav

2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
   Similarity: 0.6068
   File path: data/images/joker_laughing.png

3. Suspect dancing (vision)
   Similarity: 0.5591
   File path: data/images/jdancing.png<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73f8f56b0f2b8493/6a17ef01faa91381ca93c94e/24067ea40f7958e171149221f83cbd9bcfccc53f-1582x1208.png" alt="" /><p>现在，我们可以<strong>对检索到的证据进行分析</strong>，确定其与案件的相关性。</p><h3>音频之外--探索多模态搜索</h3><h4>颠倒角色：任何方式都可以是"问题"</h4><p>在我们的<strong>多模态 RAG</strong>系统中，<strong>每一种模态都</strong>是一个潜在的<strong>搜索查询</strong>。除了音频示例，让我们来探讨一下其他数据类型如何<strong>启动调查</strong>。</p><h4>1.文本搜索（破译罪犯的纸条）</h4><p>情景：您发现了一条<strong>加密短信</strong>，希望找到相关证据。</p>python stages/03-stage/search_by_text.py# Generate embedding from text
text = "Why so serious?"
embedding_text = generator.generate_embedding([text], "text")

# Search for related evidence
similar_evidences = es_manager.search_similar(
    query_embedding=embedding_text,
    k=3
)<p><strong>预期成果：</strong></p>🔎 Similar evidence found:

1. Mysterious note found at the location (text)
   Similarity: 0.7639
   File path: data/texts/riddle.txt

2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
   Similarity: 0.7161
   File path: data/images/joker_laughing.png

3. Why so serious (text)
   Similarity: 0.7132
   File path: data/texts/note2.txt<h4>2.图像搜索（追踪可疑犯罪现场）</h4><p><strong>情景：</strong>一个<strong>新的犯罪现场</strong>(<code>crime_scene2.jpg</code>) 需要与其他证据进行比较。
</p>python stages/03-stage/search_by_image.py# Generate embedding for a suspicious image
vision_embedding = generator.generate_embedding(["data/images/crime_scene2.jpg"], "vision")

# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
    query_embedding=vision_embedding,
    k=3
)<p><strong>输出：</strong></p>🔎 Similar evidence found:

1. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
   Similarity: 0.8258
   File path: data/images/crime_scene1.jpg

2. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
   Similarity: 0.6897
   File path: data/images/joker_laughing.png

3. Suspect dancing (vision)
   Similarity: 0.6588
   File path: data/images/jdancing.png<h4>3.深度图搜索（三维追踪）</h4><p><strong>场景：</strong> <strong>深度图</strong>(<code>jdancing-depth.png</code>) 揭示了<strong>图像</strong> <strong>逃逸模式</strong>。</p>python stages/03-stage/search_by_depth.py# Generate embedding for a suspicious depth map
vision_embedding = generator.generate_embedding(["data/depths/jdancing-depth.png"], "depth")

# Search for similar evidence in Elasticsearch
similar_evidences = es_manager.search_similar(
    query_embedding=vision_embedding,
    modality="vision",
    k=3
)<p><strong>输出</strong></p>🔎 Similar evidence found:

1. The Joker with green hair, white face paint, and a sinister smile in an urban night setting. (vision)
   Similarity: 0.5329
   File path: data/images/joker_laughing.png<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt224eaf1be75a8b6b/6a17ef03e8fbceada23a1a0e/985f9536db95c772c696dfac996822fb00f199ee-1594x1160.png" alt="" /><p></p>2. Photo of the crime scene: A dark, rain-soaked alley is filled with playing cards, while a sinister graffiti of the Joker laughing stands out on the brick wall. (vision)
   Similarity: 0.5053
   File path: data/images/crime_scene1.jpg<p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d75bdf986e1bbdd/6a17eef32f4a5c25d5fa89b1/7023ee786ccc760689257abbde2f759ca3cf5c59-1024x768.jpg" alt="" />3. Suspect dancing (vision)
   Similarity: 0.4859
   File path: data/images/jdancing.png<p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73f8f56b0f2b8493/6a17ef01faa91381ca93c94e/24067ea40f7958e171149221f83cbd9bcfccc53f-1582x1208.png" alt="" /><h3><strong>这有什么关系？</strong></h3><p>每种方式都能揭示<strong>独特的联系</strong>：</p><ul><li><p><strong>文本</strong>→ 嫌疑犯的语言模式。</p></li><li><p><strong>图像</strong>→ 识别<strong>地点和物体。</strong></p></li><li><p><strong>深度</strong>→ 3D 场景<strong>重建。</strong></p></li></ul><p>现在，我们在<strong> Elasticsearch</strong> <strong>中拥有了一个 结构化的证据数据库</strong> ，使我们能够<strong> 高效地存储和检索多模态证据</strong> 。</p><h3><strong>工作总结</strong></h3><ul><li><p>在 Elasticsearch 中<strong>存储多模态嵌入</strong>。</p></li><li><p><strong>进行相似性搜索</strong>，找到与新线索相关的证据。</p></li><li><p><strong>使用可疑音频文件测试搜索</strong>，确保系统正常运行。</p></li></ul><p><strong>下一步：</strong>我们将使用<strong>LLM</strong>（大型语言模型）<strong>分析检索到的证据</strong>，并生成<strong>最终报告</strong>。</p><h2>第 4 阶段--与法律硕士联系起来</h2><p>现在，<strong>证据已经被索引到</strong> <strong>Elasticsearch</strong>中，并可以通过相似性进行检索，我们需要一个<strong>LLM（大型语言模型）</strong> <strong>对</strong>其进行分析，并生成<strong>最终报告</strong>发送给戈登局长。<strong>法学硕士</strong>将负责根据检索到的证据<strong>确定模式、串联线索并提出可能的嫌疑人</strong>。</p><p>对于这项任务，我们将使用<strong>GPT-4 Turbo</strong>，制定<strong>详细的提示</strong>，以便模型能够有效地<strong>解释</strong>结果。</p><h3><strong>LLM 整合</strong></h3><p>为了将<strong> LLM</strong> 集成到我们的系统中，我们创建了<strong> LLMAnalyzer</strong> 类<code>src/llm_analyzer.py</code> ()，该类从<strong> Elasticsearch</strong> <strong>中接收 检索到的证据</strong> ，并以这些证据为提示语境生成<strong> 法证报告</strong> 。</p>import os
from openai import OpenAI
import logging
from dotenv import load_dotenv

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class LLMAnalyzer:
    """Evidence analyzer using GPT-4"""
    
    def __init__(self):
        load_dotenv()
        self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    def analyze_evidence(self, evidence_results):
        """
        Analyzes multimodal search results and generates a report
        
        Args:
            evidence_results: Dict with results by modality
            {
                'vision': [...],
                'audio': [...],
                'text': [...],
                'depth': [...]
            }
        """
        # Format evidence for the prompt
        evidence_summary = self._format_evidence(evidence_results)

        # final prompt
        prompt = f"""
You are a highly experienced forensic detective specializing in multimodal evidence analysis. Your task is to analyze the collected evidence (audio, images, text, depth maps) and conclusively determine the **prime suspect** responsible for the Gotham Central Bank case.

---

### **Collected Evidence:**
{evidence_summary}

### **Task:**
1. **Analyze all the evidence** and identify cross-modal connections.
2. **Determine the exact identity of the criminal** based on behavioral patterns, visual/auditory/textual clues, and symbolic markers.
3. **Justify your conclusion** by explaining why this suspect is definitively responsible.
4. **Assign a confidence score (0-100%)** to your conclusion.

---

### **Final Output Format (Strictly Follow This Format):**
- **Prime Suspect:** [Full Name or Alias]
- **Evidence Supporting Conclusion:** [Detailed breakdown of visual, auditory, textual, and behavioral evidence]
- **Behavioral Patterns:** [Key actions, motives, and criminal signature]
- **Confidence Level:** [0-100%]
- **Next Steps (if any):** [What additional evidence would further confirm the identity? If none, state "No further evidence required."]

If there is **insufficient evidence**, specify exactly what is missing and suggest what additional data would be needed for a conclusive identification.

This report must be **direct and definitive**--avoid speculation and provide a final, actionable determination of the suspect's identity.
"""
        try:
            response = self.client.chat.completions.create(
                model="gpt-4-turbo-preview",
                messages=[
                    {
                        "role": "system",
                        "content": "You are a forensic detective specialized in multimodal evidence analysis."
                    },
                    {"role": "user", "content": prompt_01}
                ],
                temperature=0.5,
                max_tokens=1000
            )
            
            report = response.choices[0].message.content
            logger.info("\n📋 Forensic Report Generated:")
            logger.info("=" * 50)
            logger.info(report)
            logger.info("=" * 50)
            
            return report
            
        except Exception as e:
            logger.error(f"Error generating report: {str(e)}")
            return None<h4>LLM 分析中的温度设置</h4><p>对于我们的取证分析系统，我们使用 0.5 的适度温度。选择这种平衡设置的原因是</p><ul><li><p>它是确定性（过于死板）和高度随机输出之间的中间地带；</p></li><li><p>在 0.5 时，模型保持足够的结构，以提供合乎逻辑和合理的法医结论；</p></li><li><p>这种设置使模型能够识别模式并建立联系，同时保持在合理的法证分析参数范围内；</p></li><li><p>它既能提供一致、可靠的输出结果，又能进行深入分析。</p></li></ul><p>这种适度的温度设置有助于确保我们的法证分析既可靠又有洞察力，避免得出过于僵化和过于臆测的结论。</p><h3>进行证据分析</h3><p>既然我们已经<strong>集成</strong>了LLM，就需要一个<strong>脚本来</strong>连接所有系统组件。该脚本将</p><ul><li><p>在Elasticsearch中<strong>搜索类似证据</strong> <strong>。</strong></p></li><li><p><strong>使用</strong> <strong>LLM</strong> 分析检索到的证据 ，生成<strong> 最终报告。</strong></p></li></ul><h4>代码证据分析脚本</h4>python stages/04-stage/rag_crime_analyze.pyimport sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src'))

from embedding_generator import EmbeddingGenerator
from elastic_manager import ElasticsearchManager
from llm_analyzer import LLMAnalyzer

import json
import logging
from dotenv import load_dotenv

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()

# Initialize classes
generator = EmbeddingGenerator()
es_manager = ElasticsearchManager()

llm = LLMAnalyzer()
logger.info("✅ All components initialized successfully")
    
try:
    evidence_data = {}
    
    # Get data for each modality
    test_files = {
        'vision': 'data/images/crime_scene2.jpg',
        'audio': 'data/audios/joker_laugh.wav',
        'text': 'Why so serious?',
        'depth': 'data/depths/jdancing-depth.png'
    }
    
    logger.info("🔍 Collecting evidence...")
    for modality, test_input in test_files.items():
        try:
            if modality == 'text':
                embedding = generator.generate_embedding([test_input], modality)
            else:
                embedding = generator.generate_embedding([str(test_input)], modality)
            
            results = es_manager.search_similar(embedding, k=2)
            if results:
                evidence_data[modality] = results
                logger.info(f"✅ Data retrieved for {modality}: {len(results)} results")
            else:
                logger.warning(f"⚠️ No results found for {modality}")
                
        except Exception as e:
            logger.error(f"❌ Error retrieving {modality} data: {str(e)}")
    
    if not evidence_data:
        raise ValueError("No evidence data found in Elasticsearch!")
    
    # Test forensic report generation
    logger.info("\n📝 Generating forensic report...")
    report = llm.analyze_evidence(evidence_data)
    
    if report:
        logger.info("✅ Forensic report generated successfully")
        logger.info("\n📊 Report Preview:")
        logger.info("+" * 50)
        logger.info(report)
        logger.info("+" * 50)
    else:
        raise ValueError("Failed to generate forensic report")
        
except Exception as e:
    logger.error(f"❌ Error in analysis : {str(e)}")<h4>预期学习成果</h4>**Prime Suspect:** The Joker

**Evidence Supporting Conclusion:**

- **Visual Evidence:**
  - The photo of the crime scene with playing cards scattered around and the graffiti of the Joker laughing matches the Joker's known calling cards and thematic elements. The similarity score of 0.83 indicates a high likelihood that these elements are directly associated with the Joker.
  - The image of the Joker with green hair, white face paint, and a sinister smile in an urban night setting, although with a lower similarity score of 0.69, still supports the presence or recent activity of the Joker in areas consistent with the crime scene's characteristics.

- **Auditory Evidence:**
  - The captured sinister laugh with a similarity score of 1.00 perfectly matches known audio profiles of the Joker, making it a direct auditory signature of his presence at or near the crime scene.
  - Despite the lower similarity score of 0.61, the second audio piece further corroborates the Joker's involvement through thematic consistency.

- **Textual Evidence:**
  - The mysterious note found at the location, with a similarity score of 0.76, likely contains thematic or direct references to the Joker's modus operandi or signature phrases, further implicating him in the crime.
  - The similarity score of 0.72 for the Joker's description in textual evidence reinforces the thematic connection to the crime scene.

- **Depth Evidence:**
  - Depth sensor capture of the suspect with a similarity score of 0.77 suggests a physical presence matching the Joker's known dimensions or characteristic movements.
  - The lower similarity score of 0.53 in the second depth evidence still contributes to the overall pattern of evidence pointing towards the Joker, albeit with less certainty.

**Behavioral Patterns:**
- The Joker is known for his theatrical crimes, often leaving behind a signature trail of chaos, including playing cards, sinister laughter, and thematic graffiti. These elements are not only consistent with his known criminal signature but also directly observed at the crime scene.
- His motives often include creating chaos, drawing attention to his acts, and challenging his arch-nemesis, Batman, making a high-profile bank heist fitting within his behavioral patterns.

**Confidence Level:** 95%

**Next Steps:** No further evidence required.

The combination of visual, auditory, textual, and depth evidence strongly points to the Joker as the prime suspect. The thematic consistency across multiple modes of evidence, combined with known behavioral patterns and criminal signature, leaves little doubt regarding his involvement. While there is always a small margin of uncertainty in forensic analysis, the evidence at hand provides a compelling case against the Joker with a high degree of confidence.<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt05526b73530f46ed/6a17ef043e03d782434f2d30/132ee0880b7fb1e64b5b2d886ab76b58baa6de37-1024x768.jpg" alt="" /><h2>结论案件侦破</h2><p>通过<strong>对</strong>所有线索的收集和分析，<strong>多模式 RAG 系统</strong>锁定了一名嫌疑人：<strong>小丑</strong></p><p>通过使用<strong> ImageBind</strong> 将<strong> 图像、音频、文本和深度图</strong> 整合到一个<strong> 共享的矢量空间</strong> ，该系统能够<strong> 检测出</strong> 人工无法识别的 连接 。<strong>Elasticsearch</strong>确保了<strong>快速高效的搜索</strong>，而<strong>LLM</strong>则将证据综合成了一份<strong>清晰的结论性报告</strong>。</p><p>然而，该系统的<strong>真正威力</strong> <strong>远不止于高谭市</strong>。<strong>多模式 RAG 架构</strong>为<strong>众多实际应用</strong>打开了大门：</p><ul><li><p><strong>城市监控：</strong>根据<strong>图像、音频和传感器数据</strong>识别嫌疑人。</p></li><li><p><strong>法医分析：</strong>将<strong>多个来源的证据</strong>相互关联，以解决<strong>复杂的犯罪问题</strong>。</p></li><li><p><strong>多媒体推荐：</strong> 创建能够理解<strong> 多模态语境的</strong> <strong>推荐系统</strong> （例如，根据图像或文本推荐<strong> 音乐</strong> ）。</p></li><li><p><strong>社交媒体趋势：</strong>通过不同的数据格式检测<strong>流行话题</strong>。</p></li></ul><p>既然您已经学会了如何<strong>构建多模态 RAG 系统</strong>，为什么不用<strong>自己的线索来测试它呢</strong>？</p><p>与我们<strong>分享您的发现</strong>，帮助<strong>社区</strong>在<strong>多模态人工智能</strong>领域取得进步！</p><h2>特别感谢</h2><p>我要感谢 Adrian Cole 在定义本代码的部署架构过程中所做的宝贵贡献和审查。</p><h2>参考资料</h2><ul><li><p><a href="https://www.elastic.co/cn/search-labs/blog/multimodal-image-retrieval-with-roboflow">利用 KNN 搜索和 CLIP 嵌入构建多模态图像检索系统</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/tutorials/search-tutorial/vector-search/nearest-neighbor-search">k-Nearest Neighbor (kNN) 搜索</a></p></li><li><p><a href="https://pytorch.org/docs/stable/tensors.html">PyTorch 关于张量的官方文档</a></p></li><li><p><a href="https://imagebind.metademolab.com/">ImageBind："连接 "人工智能与各种感官的新方法</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/building-multimodal-rag-system</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/building-multimodal-rag-system</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Alex Salgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75ee2f922dacb7a8/6a17ef067b54f9775a8b39a3/47635eb4dadb8481854862668231eaa3a005ebee-1600x900.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[将 Ollama 与推理应用程序接口结合使用]]></title>
    <description><![CDATA[了解如何使用 Inference API 将 Ollama 与 Elasticsearch 集成。]]></description>
    <content:encoded><![CDATA[<p>在本文中，我们将学习如何使用 Ollama 将本地模型连接到 Elasticsearch 推理模型，然后使用 Playground 提出文档问题。</p><p>Elasticsearch 允许用户使用开放<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/inference-apis.html">推理 API</a> 连接到 LLM，并支持 Amazon Bedrock、Cohere、Google AI、Azure AI Studio、HuggingFace - as a service 等提供商。</p><p><a href="https://ollama.com">Ollama</a>是一款允许您使用自己的基础设施（本地机器/服务器）下载和执行 LLM 模型的工具。<a href="https://ollama.com/library">在这里</a>，您可以找到与 Ollama 兼容的可用型号列表。</p><p>如果你想托管和测试不同的开源模型，Ollama 是一个不错的选择，因为 Ollama 会处理好一切，让你不必担心每个模型的不同设置方式，也不必担心如何创建 API 来访问模型功能。</p><p>由于 Ollama API 与 OpenAI API 兼容，我们可以轻松集成推理模型，并使用 Playground 创建 RAG 应用程序。</p><h2>准备工作</h2><ol><li><p>Elasticsearch 8.17</p></li><li><p>Kibana 8.17</p></li><li><p>Python</p></li></ol><h2>步长</h2><ol><li><p><a href="https://www.elastic.co/cn/search-labs/blog/ollama-with-inference-api#setting-up-ollama-llm-server">设置 Ollama LLM 服务器</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/blog/ollama-with-inference-api#creating-mappings">创建映射</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/blog/ollama-with-inference-api#indexing-data">索引数据</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/blog/ollama-with-inference-api#asking-questions-using-playground">使用 Playground 提问</a></p></li></ol><h2>设置 Ollama LLM 服务器</h2><p>我们将设置一个 LLM 服务器，使用 Ollama 将其连接到 Playground 实例。我们需要</p><ul><li><p>下载并运行 Ollama。</p></li><li><p>使用 ngrok 通过互联网访问托管 Ollama 的本地网络服务器</p></li></ul><h3>下载并运行 Ollama</h3><p>要使用 Ollama，我们首先需要<a href="https://ollama.com/download">下载它</a>。Ollama 支持 Linux、Windows 和 macOS，因此只需<a href="https://ollama.com/download"> 在这里 下载与你的操作系统兼容的 Ollama 版本即可 。</a>安装好 Ollama 后，我们可以从支持的 LLM<a href="https://ollama.com/library">列表</a>中选择一个模型。在本例中，我们将使用<a href="https://ollama.com/library/llama3.2">llama3.2</a> 模型，这是一个通用的多语言模型。在设置过程中，您将启用 Ollama 的命令行工具。下载完成后，您就可以运行下面一行：</p>ollama pull llama3.2<p>将输出</p>pulling manifest
pulling dde5aa3fc5ff... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 2.0 GB
pulling 966de95ca8a6... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 1.4 KB
pulling fcc5a6bec9da... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 7.7 KB
pulling a70ff7e570d9... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 6.0 KB
pulling 56bb8bd477a5... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏   96 B
pulling 34bb5ab01051... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏  561 B
verifying sha256 digest
writing manifest
success<p>安装完成后，可以使用此命令进行测试：</p>ollama run llama3.2<p>我们来提个问题：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc12f240920e897f5/6a17f39425daab32a508a367/ad1eff81c1b04d2a747c3afd0ecbc215e5bd96fd-800x501.gif" alt="运行 Ollama 并向它提问" /><p>模型运行后，Ollama 会启用一个默认在"11434" 端口运行的 API。让我们按照<a href="https://github.com/ollama/ollama/blob/main/docs/api.md">官方文档</a>，向该应用程序接口提出请求：</p>curl http://localhost:11434/api/generate -d '{                                          
  "model": "llama3.2",               
  "prompt": "What is the capital of France?"
}' <p>这就是我们得到的答复：</p>{"model":"llama3.2","created_at":"2024-11-28T21:48:42.152817532Z","response":"The","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.251884485Z","response":" capital","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.347365913Z","response":" of","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.446837322Z","response":" France","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.542367394Z","response":" is","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.644580384Z","response":" Paris","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.739865362Z","response":".","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.834347518Z","response":"","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,3923,374,279,6864,315,9822,30,128009,128006,78191,128007,271,791,6864,315,9822,374,12366,13],"total_duration":6948567145,"load_duration":4386106503,"prompt_eval_count":32,"prompt_eval_duration":1872000000,"eval_count":8,"eval_duration":684000000}<p><em>请注意，该端点的特定响应是流式响应。</em></p><h3>使用 ngrok 将终端接入互联网</h3><p>由于我们的端点在本地环境中运行，因此无法通过互联网从另一个点（如我们的弹性云实例）进行访问。<a href="https://ngrok.com">ngrok</a>允许我们公开提供公共 IP 的端口。在 ngrok 中创建账户，并按照官方<a href="https://dashboard.ngrok.com/get-started/setup">设置指南</a>进行操作。</p><p>安装并配置好 ngrok 代理后，我们就可以公开 Ollama 正在使用的端口：</p>ngrok http 11434 --host-header="localhost:11434"<p><em>注意： </em><em><code>--host-header="localhost:11434"</code></em>头 <em> 保证请求中的"Host" 头与"localhost:11434 匹配。"</em></p><p>执行该命令将返回一个公共链接，只要 ngrok 和 Ollama 服务器在本地运行，该链接就能正常工作。</p>Session Status                online                                                                                                                                                                              
Account                       xxxx@yourEmailProvider.com (Plan: Free)                                                                                                                                             
Version                       3.18.4                                                                                                                                                                              
Region                        United States (us)                                                                                                                                                                  
Latency                       561ms                                                                                                                                                                               
Web Interface                 http://127.0.0.1:4040                                                                                                                                                               
Forwarding                    https://your-ngrok-url.ngrok-free.app -&gt; http://localhost:11434                                                                                                                   


Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                                                         
                              0       0       0.00    0.00    0.00    0.00                                                ```<p>在"Forwarding" 中，我们可以看到 ngrok 生成了一个 URL。留着以后用吧。</p><p>让我们再次尝试使用 ngrok 生成的 URL 向端点发出 HTTP 请求：</p>curl https://your-ngrok-endpoint.ngrok-free.app/api/generate -d '{                                          
  "model": "llama3.2",               
  "prompt": "What is the capital of France?"
}'<p>答复应与前一个答复类似。</p><h2>创建映射</h2><h3>ELSER 端点</h3><p>在本示例中，我们将<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/put-inference-api.html">使用 Elasticsearch 推理 API 创建一个推理端点</a>。此外，我们还将使用<a href="https://www.elastic.co/cn/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>生成嵌入。</p>PUT _inference/sparse_embedding/medicines-inference
{
  "service": "elasticsearch",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1,
    "model_id": ".elser_model_2_linux-x86_64"
  }
}<p>在这个例子中，我们假设有一家药店出售两种药物：</p><ul><li><p>需要处方的药物。</p></li><li><p>无需处方的药物。</p></li></ul><p>这些信息将包含在每种药物的描述字段中。</p><p>LLM 必须对该字段进行解释，因此这就是我们要使用的数据映射：</p>PUT medicines
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "copy_to": "semantic_field"
      },
      "semantic_field": {
        "type": "semantic_text",
        "inference_id": "medicines-inference"
      },
      "text_description": {
        "type": "text",
        "copy_to": "semantic_field"
      }
    }
  }
}<p>字段<code>text_description</code> 将存储描述的纯文本，而作为<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/semantic-text.html">语义_文本字段</a>类型的<code>semantic_field</code> 将存储由 ELSER 生成的嵌入。</p><p>属性<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/copy-to.html">copy_to</a>将把字段名和<code>text_description</code> 中的内容复制到语义字段中，以便为这些字段生成嵌入内容。</p><h2>索引数据</h2><p>现在，让我们使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/docs-bulk.html">_bulk API</a> 为数据建立索引。</p>POST _bulk
{"index":{"_index":"medicines"}}
{"id":1,"name":"Paracetamol","text_description":"An analgesic and antipyretic that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":2,"name":"Ibuprofen","text_description":"A nonsteroidal anti-inflammatory drug (NSAID) available WITHOUT a prescription."}
{"index":{"_index":"medicines"}}
{"id":3,"name":"Amoxicillin","text_description":"An antibiotic that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":4,"name":"Lorazepam","text_description":"An anxiolytic medication that strictly requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":5,"name":"Omeprazole","text_description":"A medication for stomach acidity that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":6,"name":"Insulin","text_description":"A hormone used in diabetes treatment that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":7,"name":"Cold Medicine","text_description":"A compound formula to relieve flu symptoms available WITHOUT a prescription."}
{"index":{"_index":"medicines"}}
{"id":8,"name":"Clonazepam","text_description":"An antiepileptic medication that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":9,"name":"Vitamin C","text_description":"A dietary supplement that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":10,"name":"Metformin","text_description":"A medication used for type 2 diabetes that requires a prescription."}<p>响应：</p>{
   "errors": false,
   "took": 34732020848,
   "items": [
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "mYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 0,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "mooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 1,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "m4oeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 2,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nIoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 3,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 4,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 5,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "n4oeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 6,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oIoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 7,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 8,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 9,
     	"_primary_term": 1,
     	"status": 201
   	}
 	}
   ]
 }<h2>使用 Playground 提问</h2><p><a href="https://www.elastic.co/cn/guide/en/kibana/current/playground.html">Playground</a>是一款 Kibana 工具，可让您使用 Elasticsearch 索引和 LLM 提供商快速创建 RAG 系统。您可以阅读<a href="https://www.elastic.co/cn/search-labs/blog/playground-connectors-data-chat">本文</a>了解更多信息。</p><h3>将当地的法律硕士与游乐场连接起来</h3><p>我们首先需要创建一个连接器，使用我们刚刚创建的公共 URL。在 Kibana 中，转到<strong>搜索&gt;Playground</strong>，然后点击"连接到 LLM" 。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22148eabfabf6d3f/6a17f3963e9e459f97ba15c6/1854f0808f8150e359fe62ba5d901d32a88d477c-1600x867.png" alt="将当地的法律硕士与奥拉玛游乐场联系起来" /><p>此操作将显示 Kibana 界面左侧的菜单。在那里，点击"OpenAI" 。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e28194d9012f141/6a17f39725daab500a08a36b/c83d3c4d7035a518124ad7d22b38764db57b6800-933x1007.png" alt="选择一个连接器：打开 AI Ollama" /><p>现在我们可以开始配置 OpenAI 连接器了。</p><p>访问"Connector settings" ，并为 OpenAI 提供商选择"Other (OpenAI Compatible Service)" ：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9984dce6f78a7c08/6a17f3990b0bed0b7add36c4/ecfcdc4b575c309bd55b4e61ca0ddb348aa84f64-917x268.png" alt="为使用推理应用程序接口的 Ollama 设置连接器设置" /><p>现在，让我们配置其他字段。在本例中，我们将模型命名为"medicines-llm" 。在 URL 字段中，使用 ngrok 生成的 URL (/v1/chat/completions)。在"Default model" 字段中，选择"llama3.2" 。我们不会使用 API 密钥，因此只需输入任意文本即可：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a24b93a39d380fb/6a17f39b96142a15c7eb1c3c/5d3b5027c8096cbe49fb740d70aa24e849611a9d-916x688.png" alt="添加设置" /><p>点击"保存" 并点击"添加数据源" 添加索引药物：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f107a54d5be25f9/6a17f39d4b055deb9d432338/525113da59e902c8235f62bde8fb62371a63e11b-1579x753.png" alt="使用 Playground 添加数据源，以便向文档提问" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03fb36fe05dcdbf5/6a17f39ebe608602f40048be/96138de0bbe2c2ac619f64889d3487df62739ca4-466x805.png" alt="添加查询数据" /><p>好极了现在，我们可以使用本地运行的 LLM 作为 RAG 引擎访问 Playground。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb1b27580107259b3/6a17f3a096142abefceb1c40/cfb48b33c70f4534ab77eb01f58008237f65e6f4-1600x851.png" alt="在 Playground 中选择模型设置" /><p>在测试之前，让我们给代理添加更具体的指令，并将发送给模型的文件数量增加到 10 份，以便答案有尽可能多的可用文件。由于使用了 copy_to 属性，上下文字段将是<code>semantic_field</code> ，其中包括药品的名称和描述。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4fbc97dc87c6fc62/6a17f3a1e8fbce052c3a1aa0/0c57c9c0e1a0e7b58fffdd3ef81d67d41e2990c4-580x806.png" alt="Elastic Playground 中的 Moel 设置" /><p>现在我们来问一个问题：<em><strong>没有处方可以购买氯硝西泮吗？</strong></em>看看会发生什么：</p><p>不出所料，我们得到了正确答案。</p><h3>后续步骤</h3><p>下一步是创建自己的应用程序！Playground 提供了一个 Python 代码脚本，你可以在自己的机器上运行，并根据自己的需要进行定制。例如，将其置于<a href="https://fastapi.tiangolo.com/">FastAPI</a>服务器之后，创建一个由用户界面使用的 QA 药品聊天机器人。</p><p>点击 Playground 右上方的 "<em><strong>查看代码</strong></em>"按钮即可找到该代码：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42fe193b8aa08830/6a17f3a33e9e4569e8ba15ca/816bfd0e5f936ad65dbe719d5df10714e550a40b-380x121.png" alt="查看代码按钮" /><p>然后使用<em><strong>Endpoints&amp; API 密钥</strong></em>生成代码中所需的<code>ES_API_KEY</code> 环境变量。</p><p>本例的代码如下：</p>## Install the required packages
## pip install -qU elasticsearch openai
import os
from elasticsearch import Elasticsearch
from openai import OpenAI
es_client = Elasticsearch(
    "https://your-deployment.us-central1.gcp.cloud.es.io:443",
    api_key=os.environ["ES_API_KEY"]
)
openai_client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
)
index_source_fields = {
    "medicines": [
        "semantic_field"
    ]
}
def get_elasticsearch_results():
    es_query = {
        "retriever": {
            "standard": {
                "query": {
                    "nested": {
                        "path": "semantic_field.inference.chunks",
                        "query": {
                            "sparse_vector": {
                                "inference_id": "medicines-inference",
                                "field": "semantic_field.inference.chunks.embeddings",
                                "query": query
                            }
                        },
                        "inner_hits": {
                            "size": 2,
                            "name": "medicines.semantic_field",
                            "_source": [
                                "semantic_field.inference.chunks.text"
                            ]
                        }
                    }
                }
            }
        },
        "size": 3
    }
    result = es_client.search(index="medicines", body=es_query)
    return result["hits"]["hits"]
def create_openai_prompt(results):
    context = ""
    for hit in results:
        inner_hit_path = f"{hit['_index']}.{index_source_fields.get(hit['_index'])[0]}"
        ## For semantic_text matches, we need to extract the text from the inner_hits
        if 'inner_hits' in hit and inner_hit_path in hit['inner_hits']:
            context += '\n --- \n'.join(inner_hit['_source']['text'] for inner_hit in hit['inner_hits'][inner_hit_path]['hits']['hits'])
        else:
            source_field = index_source_fields.get(hit["_index"])[0]
            hit_context = hit["_source"][source_field]
            context += f"{hit_context}\n"
    prompt = f"""
  Instructions:
  - You are an assistant specializing in answering questions about the sale of medicines.
  - Answer questions truthfully and factually using only the context presented.
  - If you don't know the answer, just say that you don't know, don't make up an answer.
  - You must always cite the document where the answer was extracted using inline academic citation style [], using the position.
  - Use markdown format for code examples.
  - You are correct, factual, precise, and reliable.
  Context:
  {context}
  """
    return prompt
def generate_openai_completion(user_prompt, question):
    response = openai_client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": user_prompt},
            {"role": "user", "content": question},
        ]
    )
    return response.choices[0].message.content
if __name__ == "__main__":
    question = "my question"
    elasticsearch_results = get_elasticsearch_results()
    context_prompt = create_openai_prompt(elasticsearch_results)
    openai_completion = generate_openai_completion(context_prompt, question)
    print(openai_completion)<p>要使其与 Ollama 兼容，必须更改 OpenAI 客户端，使其连接到 Ollama 服务器，而不是 OpenAI 服务器。您可以在这里找到 OpenAI 示例和兼容端点的完整列表。</p>openai_client = OpenAI(
    # you can use http://localhost:11434/v1/ if running this code locally.
    base_url='https://your-ngrok-url.ngrok-free.app/v1/',
    # required but ignored
    api_key='ollama',
)<p>在调用完成方法时，将模型更改为 llama3.2：</p>def generate_openai_completion(user_prompt, question):
    response = openai_client.chat.completions.create(
        model="llama3.2",
        messages=[
            {"role": "system", "content": user_prompt},
            {"role": "user", "content": question},
        ]
    )
    return response.choices[0].message.content<p>让我们补充一个问题：<em><strong>我可以在没有处方的情况下购买氯硝西泮吗？ </strong></em>至 Elasticsearch 查询：</p>def get_elasticsearch_results():
    es_query = {
        "retriever": {
            "standard": {
                "query": {
                    "nested": {
                        "path": "semantic_field.inference.chunks",
                        "query": {
                            "sparse_vector": {
                                "inference_id": "medicines-inference",
                                "field": "semantic_field.inference.chunks.embeddings",
                                "query": "Can I buy Clonazepam without a prescription?"
                            }
                        },
                        "inner_hits": {
                            "size": 2,
                            "name": "medicines.semantic_field",
                            "_source": [
                                "semantic_field.inference.chunks.text"
                            ]
                        }
                    }
                }
            }
        },
        "size": 3
    }
    result = es_client.search(index="medicines", body=es_query)
    return result["hits"]["hits"]<p>此外，我们还在完成调用中打印了一些内容，以便确认我们将 Elasticsearch 结果作为问题上下文的一部分发送：</p>if __name__ == "__main__":
    question = "Can I buy Clonazepam without a prescription?"
    elasticsearch_results = get_elasticsearch_results()
    context_prompt = create_openai_prompt(elasticsearch_results)
    print("========== Context Prompt START ==========")
    print(context_prompt)
    print("========== Context Prompt END ==========")
    print("========== Ollama Completion START ==========")
    openai_completion = generate_openai_completion(context_prompt, question)
    print(openai_completion)
    print("========== Ollama Completion END ==========")<p>现在运行命令</p><p><code>pip install -qU elasticsearch openai</code></p><p><code>python main.py</code></p><p>你应该看到这样的内容：</p>========== Context Prompt START ==========
  Instructions:
  - You are an assistant specializing in answering questions about the sale of medicines.
  - Answer questions truthfully and factually using only the context presented.
  - If you don't know the answer, just say that you don't know, don't make up an answer.
  - You must always cite the document where the answer was extracted using inline academic citation style [], using the position.
  - Use markdown format for code examples.
  - You are correct, factual, precise, and reliable.
  Context:
  Clonazepam
 ---
An antiepileptic medication that requires a prescription.A nonsteroidal anti-inflammatory drug (NSAID) available WITHOUT a prescription.
 ---
IbuprofenAn anxiolytic medication that strictly requires a prescription.
 ---
Lorazepam


========== Context Prompt END ==========
========== Ollama Completion START ==========
No, you cannot buy Clonazepam over-the-counter (OTC) without a prescription [1]. It is classified as a controlled substance in the United States due to its potential for dependence and abuse. Therefore, it can only be obtained from a licensed healthcare provider who will issue a prescription for this medication.
========== Ollama Completion END ==========<h2>结论</h2><p>在本文中，当我们将 Ollama 等工具与 Elasticsearch 推论 API 和 Playground 结合使用时，我们可以看到它们的强大功能和多功能性。</p><p>经过几个简单的步骤后，我们就拥有了一个可运行的 RAG 应用程序，它可以聊天，使用 LLM 在我们自己的基础设施中运行，成本为零。这也使我们能够对资源和敏感信息有更多的控制权，此外，我们还可以使用各种模型来完成不同的任务。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ollama-with-inference-api</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ollama-with-inference-api</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd9c8eb0fc946920e/6a17f3a46864a4b2fbb688f0/399b9ef527be633845fb6505b68132cc03bc9e09-1150x628.png" length="0" type="image/png"/>
    <pubDate>Fri, 14 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[在本地使用 Ollama 和 Kibana 测试 DeepSeek R1 的 RAG 功能]]></title>
    <description><![CDATA[了解如何运行 DeepSeek 的本地实例，并从 Kibana 内部连接到它。]]></description>
    <content:encoded><![CDATA[<p>大家都在谈论 DeepSeek R1，这是中国对冲基金 High-Flyer 的全新大型语言模型。如今他们推出了一款具备完整思维链推理能力的大型语言模型 (LLM)，对此业界众说纷纭，新闻报道中对此也是猜测不断。对于那些想尝试这个结合 RAG 和 Elasticsearch 向量数据库智能功能的新模型的人，这里有一个快速教程，帮助您使用本地推理开始使用 DeepSeek R1。在此过程中，我们将使用 Elastic 的 Playground 功能，甚至还会发现适用于 RAG 的 Deepseek R1 的一些优缺点。</p><p>以下是我们将在本教程中配置的内容的示意图：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3214cc505e3d4d06/6a17df98dbb4ff12bafb55da/8aafec9011e986cd85b10958544a4d77be81e518-739x419.png" alt="使用 Elasticsearch 和 Ollama 进行 Deepseek 配置" /><h2>使用 Ollama 设置本地推理</h2><p><a href="https://ollama.com/">Ollama</a> 是快速测试一组精选的用于本地推理的开源模型的绝佳方法，深受 AI 开发者的欢迎。</p><h3>在裸机上运行 Ollama</h3><p>在 Mac、Linux 或 Windows 上进行<a href="https://github.com/ollama/ollama/tree/main?tab=readme-ov-file#ollama">本地安装</a>是利用您可能拥有的任何本地 GPU 功能的最简单方法，尤其是对于拥有 M 系列 Apple 芯片的用户而言。安装 Ollama 后，您可以使用以下命令下载并运行 DeepSeek R1。</p><p>您可能需要调整参数大小，使其适合您的硬件。可用的大小可以在<a href="https://ollama.com/library/deepseek-r1">此处</a>找到。</p>ollama run deepseek-r1:7b<p>您可以在终端中与模型聊天，但当您按下 Ctrl+D 退出命令或输入“/bye”时，模型仍会继续运行。要查看模型仍在运行，请输入：</p>ollama ps<h3>在容器中运行 Ollama</h3><p>或者，运行 Ollama 的最快方法是使用 Docker 这样的容器引擎。使用本地计算机的 GPU 并不总是那么简单，具体取决于环境，但只要容器具备足够的 RAM 和存储空间以运行多 GB 的模型，就能轻松完成快速测试设置。</p><p>在 Docker 中启动并运行 Ollama 就像执行以下命令一样简单：</p>mkdir ollama_deepseek
cd ollama_deepseek
mkdir ollama
docker run -d -v ./ollama:/root/.ollama -p 11434:11434 \
--name ollama ollama/ollama
<p>这将在当前目录中创建一个名为“ollama”的目录，并将其挂载到容器内以存储 Ollama 配置和模型。根据所使用的参数数量，它们的大小可能从几 GB 到几十 GB 不等，因此请确保选择拥有足够可用空间的卷。</p><p>注意：如果您的计算机上有 Nvidia GPU，请确保安装 <a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#installation">Nvidia 容器工具包</a>，并在上面的 docker 运行命令中添加“--gpus=all”。</p><p>一旦 Ollama 容器在您的机器上运行起来，您就可以拉取一个类似 deepseek-r1 的模型：</p>docker exec -it ollama ollama pull deepseek-r1:7b<p>与裸机方法类似，您可能需要调整参数大小以适合您的硬件。可用的大小可以在 <a href="https://ollama.com/library/deepseek-r1">https://ollama.com/library/deepseek-r1</a> 中找到。</p><p>模型拉取完成后，您可以输入“/bye”退出提示符。要验证模型是否仍在运行：</p>docker exec -it ollama ollama ps<h2>使用 curl 测试我们的本地推理</h2><p>要使用 curl 测试本地推理，您可以运行以下命令。我们使用 stream:false 以便可以轻松读取 JSON 叙事性响应：</p>curl http://localhost:11434/api/generate -d '{
  "model": "deepseek-r1:7b",
  "stream": false,
  "prompt":"Why is Elastic so cool?"
}'<h2>测试“OpenAI 兼容”的 Ollama 和 RAG 提示</h2><p>方便的是，Ollama 还提供一个 REST 终端，可模仿 OpenAI 的行为，以便与包括 Kibana 在内的各种工具兼容。</p>curl http://localhost:11434/v1/chat/completions -d '{
  "model": "deepseek-r1:7b",
  "stream": false,
  "messages": [
    { 
      "role": "system", 
      "content": "You are a helpful AI Assistant that uses the following context to answer questions only use the following context. \n\nContext:  The color of the sky today is purple. "},
    { "role": "user", 
      "content": "What does the sky look like today?" 
    }
  ]
}'<p>测试这个更复杂的提示会生成包含 &lt;think&gt; 部分的内容，模型在该部分经过训练以推理解决问题。</p>&lt;think&gt; 
Okay, so I need to figure out what the user is asking for here. They provided a context where the sky is described as purple today and then asked about how the sky looks. At first glance, it seems straightforward—maybe they just want confirmation or more details on why the sky is that color.
Wait, but maybe there's something deeper. Purple skies aren't something I encounter every day. It usually happens at certain times of the year, like during sunrise or sunset with the sun setting in pink or orange. Could this be a hint about the time of day? Or perhaps it's just an unusual natural phenomenon? 
I should consider if \"purple\" is a typo. Maybe they meant something else like blue or gray. But since they specifically said purple, I'll go with that. Purple skies can happen when there are atmospheric conditions that scatter light differently, maybe due to pollution or cloud cover affecting the sunset.

So, putting it all together, the user might be looking for an explanation of why today's sky is purple and what that implies about the weather or time of day. Alternatively, they could just want a simple statement confirming that the sky looks purple today.
&lt;/think&gt;

The color of the sky today is described as purple. This unusual shade can occur due to atmospheric conditions affecting light scattering, such as during sunrise/sunset with pollution or cloud cover influencing the sunset's hues.<h2>将 Ollama 连接到 Kibana</h2><p>使用 Elasticsearch 的一个好方法是“<a href="https://github.com/elastic/start-local?tab=readme-ov-file#-try-elasticsearch-and-kibana-locally">start-local</a>”开发脚本。</p><p>确保您的 Kibana 和 Elasticsearch 能够通过网络访问您的 Ollama。如果您使用的是 Elastic stack 的本地容器设置，则可能需要将“localhost”替换为“host.docker.internal”。或“host.containers.internal”。以获取到主机的网络路径。</p><p>在 Kibana 中，导航至“堆栈管理&gt;警报和见解&gt;连接器”。</p><h3>如果您看到此常见设置警告，该怎么办</h3><p>您需要确保 xpack.encryptedSavedObjects.encryptionKey <a href="https://www.elastic.co/guide/en/kibana/current/xpack-security-secure-saved-objects.html">已正确设置</a>。这是在本地 Docker 安装 Kibana 时经常遗漏的一个步骤，因此我将在 Docker 语法中列出修复步骤。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4f7b7be2e04afae/6a17df9a1d1b8391e293e393/b70b4b810bcac1d1599b07da90a98c5c744a38de-497x223.png" alt="" /><p>确保持久化 kibana/config 目录，以便在容器关闭时保存更改。我的 Kibana 容器卷在 docker-compose.yml 中是这样的：</p>services:
  kibana:
...
   volumes:
      - certs:/usr/share/kibana/config/certs
      - kibanadata:/usr/share/kibana/data
      - kibanaconfig:/usr/share/kibana/config
...
volumes:
  certs:
    driver: local
  esdata01:
    driver: local
  kibanadata:
    driver: local
  kibanaconfig:
    driver: local<p>现在，您可以创建密钥库，并输入一个值，这样连接器密钥就不会以明文形式存储。</p>## generate some new keys for me and print them to the terminal
docker exec -it kibana_1 bin/kibana-encryption-keys generate

## create a new keystrore
docker exec -it kibana_1 bin/kibana-keystore create
docker exec -it kibana_1 bin/kibana-keystore add xpack.encryptedSavedObjects.encryptionKey

## You'll be prompted to paste in a value<p>完全重启整个集群以确保更改生效。</p><h3>创建连接器</h3><p>在连接器配置屏幕（在 Kibana 中，导航到“堆栈管理&gt;警报和见解&gt;连接器”）中，创建一个连接器并选择“OpenAI”类型。</p><p>用以下设置配置连接器</p><ul><li><p>连接器名称：Deepseek（Ollama）</p></li><li><p>选择一个 OpenAI 提供商：其他（OpenAI 兼容服务）</p></li><li><p>URL：<a href="http://localhost:11434/v1/chat/completions">http://localhost:11434/v1/chat/completions</a></p><ul><li><p>调整为指向 Ollama 的正确路径。请记住，如果您从容器内调用，请替换 host.docker.internal 或等效项。</p></li></ul></li><li><p>默认模型：deepseek-r1:7b</p></li><li><p>API 密钥：可随意填写，需输入一个值，但具体内容无关紧要</p></li></ul><p>请注意，在连接器设置中测试连接到 Ollama 的自定义连接器目前在 8.17 版中已损坏，但在即将发布的 Kibana 8.18 版本中已修复。</p><p>我们的连接器如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt774e0793eb110f9d/6a17df9c445de981014d004d/4ce214aa953b4090ed112fbde40b01c01fb8f5c7-786x836.png" alt="" /><h2>将向量嵌入数据导入 Elasticsearch</h2><p>如果您已熟悉 Playground 并设置了数据，可以跳转到以下 Playground 步骤，但如果您需要一些快速测试数据，我们需要确保已设置 _inference API。从 8.17 版开始，机器学习分配是动态的，因此要下载并打开 e5 多语言密集向量，我们只需在 Kiban 开发工具中运行以下程序。</p>GET /_inference


POST /_inference/text_embedding/.multilingual-e5-small-elasticsearch
{
   "input": "are internet memes about deepseek sound investment advice?"
}<p>如果您尚未执行此操作，这将触发从 Elastic 的模型存储库下载 e5 模型。</p><p>接下来，让我们加载一本公共领域的书籍作为 RAG 上下文。这里有一个从 Project Gutenberg 下载《爱丽丝漫游奇境记》的链接：<a href="https://www.gutenberg.org/cache/epub/11/pg11.txt">链接</a>。将此保存为 .txt 文件。</p><p>导航到 Elasticsearch &gt; 主页 &gt; 上传文件</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45c594487844ecb4/6a17df9dfaa9137edb93c786/649042271f34a5e66789b17c39bfe95971c7f4ce-1360x629.png" alt="" /><p>选择或拖放您的文本文件，然后点击“导入”按钮。</p><p>在“导入数据”屏幕上选择“高级”选项卡，然后将索引名称设为“book_alice”。</p><p>选择“添加其他字段”选项，它位于“自动创建字段”的正下方。选择“添加语义文本字段”，将推理终端更改为“.multilingual-e5-small-elasticsearch”。选择“添加”，然后选择“导入”。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d9c22ccdaee8590/6a17df9f3e03d731f94f2b8e/e58d5c9a2d406d8e62eb96cab9ac98ca89414346-507x602.png" alt="" /><p></p><p>加载和推理过程完成后，我们就可以前往 Playground。</p><h2>在 Playground 中测试 RAG</h2><p>在 Kibana 中，导航至“Elasticsearch &gt; Playground”。</p><p>在 Playground 屏幕上，您应该会看到一个绿色复选标记和“LLM 已连接”，以指示连接器存在。这就是我们刚刚在上面创建的 Ollama 连接器。可以在<a href="https://www.elastic.co/guide/en/kibana/current/playground.html">此处</a>找到有关 Playground 的更长指南。</p><p>点击蓝色的“添加数据源”，然后选择我们之前创建的 book_alice 索引或你之前配置的使用推理 API 的其他索引。</p><p>Deepseek 是一种具有强一致性特征的链式思维模型。从 RAG 的角度来看，这既有好处也有坏处。思维链训练可能有助于 Deepseek 理解引文中看似矛盾的陈述，但由于与训练知识的强烈一致性，它可能更倾向于其自身版本的世界事实，而非我们的上下文基础。尽管出发点是好的，但这种强烈的一致性众所周知会使 LLM 在讨论我们的私人知识与训练数据集有冲突或未得到很好体现的主题时难以指导。</p><p>在 Playground 设置中，我们输入了以下系统提示：“您是使用《爱丽丝梦游仙境》一书中的相关文本段落回答问题的助手”，并接受了其他默认设置。</p><p>对于“谁参加了茶话会？”这个问题，我们得到的答案是：“答案：三月兔、帽匠和睡鼠参加了茶话会。[引用：位置 1 和 2]”，这是正确的。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ce6a0facd972fdd/6a17dfa03e03d79aaa4f2b92/e8af3ff93a72e1f02de8e73f6c2606cbc19970e5-1296x813.png" alt="" /><p>我们可以在 &lt;think&gt; 标签中看到，Deepseek 确实仔细考虑了引用内容以回答问题。</p><h2>测试对齐限制</h2><p>让我们为 Deepseek 创建一个具有智力挑战性的场景来进行测试。我们将创建一个索引，包含 Deepseek 的训练数据已知不属实的阴谋论。</p><p>在 Kibana 开发工具中，我们来创建以下索引和数据：</p>PUT /classic_conspiracies
{
   "mappings": {
       "properties": {
           "content": {
               "type": "text",
               "copy_to": "content_semantic"
           },
           "content_semantic": {
               "type": "semantic_text",
               "inference_id": ".multilingual-e5-small-elasticsearch"
           }
       }
   }
}




POST /classic_conspiracies/_doc/1
{
   "content": "birds aren't real, the government replaced them with drones a long time ago"
}
POST /classic_conspiracies/_doc/2
{
   "content": "tinfoil hats are necessary to prevent our brains from being read"
}
POST /classic_conspiracies/_doc/3
{
   "content": "ancient aliens influenced early human civilizations, this explains why things made out of stone are marginally similar on different continents"
}<p>
这些阴谋论将作为我们为 LLM 提供的上下文依据。尽管采用了激进的系统提示，Deepseek 仍不会接受我们的事实版本。如果我们处于一种情况，知道我们的私有数据更值得信赖、更有根据或更符合我们组织的需求，这将是不可接受的：</p><p>对于测试问题“鸟是真实存在的吗？”（解释<a href="https://knowyourmeme.com/memes/birds-arent-real">了解你的梗</a>），我们得到的答案是“在提供的上下文中，鸟不被视为真实存在的，但在现实中，它们是真实存在的动物。[上下文：位置 1]”。这项测试证明了 DeepSeek R1 的强大功能，即使是在 7B 参数级别......不过，根据我们的数据集，它可能并非 RAG 的最佳选择。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e8dabf65ea200e1/6a17dfa2ec0f8982135a6541/67d5f6cdb97bfd3adb926cbd588c768f9d6730ae-1277x737.png" alt="" /><h2>那么我们学到了什么？</h2><p>总而言之：</p><ul><li><p>在 Ollama 等工具中本地运行模型是窥探模型行为的绝佳选择。</p></li><li><p>DeepSeek R1 是一种推理模型，这意味着它在 RAG 等用例中各有利弊。</p></li><li><p>Playground 能够通过类似于 OpenAI 的 REST API 连接到 Ollama 等推理托管框架，这种方式正逐渐成为 AI 托管早期阶段的事实标准。</p></li></ul><p>总之，我们对本地“隔离网络”RAG 的发展程度印象深刻。自 2023 年我们首次撰写有关<a href="https://www.elastic.co/search-labs/blog/privacy-first-ai-search-langchain-elasticsearch">隐私优先 AI 搜索</a>的文章以来，Elasticsearch、Kibana 中的工具以及可用的开放权重模型都有了长足的进步。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/deepseek-rag-ollama-playground</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/deepseek-rag-ollama-playground</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Kibana]]></category>
    <dc:creator><![CDATA[Dave Erickson,Jakob Reiter]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a4b2ae6bd97850b/6a17dfa4be6086558f00464c/1bd853bfdfa2710e44cc4c08dede6bd21b35c4b8-1542x860.png" length="0" type="image/png"/>
    <pubDate>Thu, 30 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[分面搜索：利用人工智能改进搜索范围和结果]]></title>
    <description><![CDATA[探索如何在 Elasticsearch 中使用面搜索来快速缩小类别内的选项范围。]]></description>
    <content:encoded><![CDATA[<p>在本文中，我们将探讨人工智能（AI），特别是使用 GPT-4 等高级语言模型，如何帮助创建更多的上下文切面，使它们对用户更加相关和有用。</p><p>切面搜索是电子商务平台的一个强大工具。它有助于根据显示项目的特征来组织和完善搜索结果。虽然滤镜经常被混淆，但切面的工作原理是不同的。筛选器是固定属性，由索引中始终存在的信息定义，如产品类别或格式。而面则是动态的，由执行搜索后返回的结果生成。</p><p>试想一下服装目录："类别" （如 T 恤、裤子）或"性别" （如男、女）等字段是帮助缩小结果范围的过滤器。而面则反映了结果中出现的产品的具体特征，如常见颜色、可用尺寸或材料。这使得搜索体验更具适应性和情境性。</p><p>下面是一张图片，我们与一个切面进行交互，可以看到由切面筛选出的搜索结果。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4ce8ca7e5b62ad7/6a17f8ba6864a4534bb68979/74d2159706ab7248ebe5efddc74c882f0693db71-600x420.gif" alt="分面搜索示例" /><h2>人工智能如何改善面的生成</h2><p>人工智能通常与语义搜索和嵌入相关联，那么面呢？如何利用人工智能，使面面俱到对每次搜索更有用，并针对具体情况？</p><p>一个引人入胜的可能性是利用人工智能创建新的分类，超越索引中的传统分类。通过分析内容的具体特征，这些新类别可以提供更丰富、更精确的上下文，使面更相关、更符合用户需求。与原始文件类别相比，这使得结果的细化更有意义。</p><h2>人工智能如何完善电影分类以提高搜索效果</h2><p>让我们来分析一下以下影片，它们目前都被归类为剧情片类型：</p><ul><li><p>梦之安魂曲
简历：四个科尼岛人吸毒成瘾后，他们的乌托邦被打破了。</p></li><li><p>美国丽人
简历：一位在性方面受挫的郊区父亲在迷恋上女儿最好的朋友后，陷入了中年危机。</p></li><li><p>Good Will Hunting
简历：威尔-亨廷是麻省理工学院的看门人，他有数学天赋，但需要心理学家的帮助才能找到人生方向。</p></li></ul><p>这种类型划分无法捕捉到每部影片的细微差别或独特背景。通过利用人工智能分析故事梗概和中心主题，我们可以创建新的类别，更好地反映每部电影的真实背景。例如</p><ul><li><p>梦之安魂曲 - 新类别："成瘾与依赖"</p></li><li><p>美国丽人》 - 新类别："中年危机"</p></li><li><p>Good Will Hunting - 新类别："智力斗争"</p></li></ul><p>这些新类别使搜索更加精确，同时为用户提供了更有意义的筛选器来完善搜索结果。当原始分类过于笼统时，这种方法尤为有效，能让用户更轻松地准确找到他们要找的内容。</p><h2>使用 GPT-4 创建新类别：面搜索示例</h2><p>在这个例子中，我们将展示如何利用人工智能模型来创建新的电影类别，使其更加精确，并与每部作品的背景相一致。为了演示这一过程，我们将使用 Elastic 仿真管道和 OpenAI 推断服务。我们将创建一个由多个处理器组成的流水线，其中包括脚本处理器，它将负责创建提示语，并在推理处理器中执行，从而确定新的类别。其他处理器将用于处理流水线执行过程中产生的数据和辅助字段。值得一提的是，这一逻辑也可应用于其他类似的工具或模型。</p><p>首先，我们需要创建推理端点，将服务定义为 OpenAI、访问服务所需的令牌以及模型。在本例中，我使用的是 gpt-4-mini。有关 OpenAI 推断服务的更多详情，请点击<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/infer-service-openai.html">此处</a>。</p>PUT _inference/completion/generate_topics_ia
{
    "service": "openai",
    "service_settings": {
        "api_key": "your-token",
        "model_id": "gpt-4o-mini"
    }
}<p>端点创建完成后，我们就可以用它来创建新类别了。下面是一个处理文档数据操作和提示生成整个过程的管道。我将详细解释每个处理器的功能。</p><p>第一个处理器将负责建立提示。明确详细的说明非常重要，这样人工智能才能正确分析和识别主题。在本提示中，我要求根据对电影标题、描述和类型的分析来确定 2 个主题。</p>{
        "script": {
          "source": """
            ctx.prompt = "You are an expert in semantic analysis and audiovisual content categorization. Your task is to generate only subcategories (max 2 topics) that describe specific aspects of movies based on their genres and descriptions. The output should be like: 'n1, n2, ...n'. Here is a movie info to analyze: Title: " + ctx.title  + "Genres: " + ctx.genres  + "Description: " + ctx.description;
          """
        }<p>下一个管道是推理管道，它将接收提示并将其发送到我们的<strong>generate_topics_ia</strong>端点。模型生成的响应将存储在结果字段中。</p>{
        "inference": {
          "model_id": "generate_topics_ia",
          "input_output": {
            "input_field": "prompt",
            "output_field": "result"
          }
        }
      }<p>接下来，除了删除我创建的临时字段外，我们还有 3 个处理器用于处理响应并将其设置到主题字段中。</p><p>执行该流水线后，我们将得到以下结果：</p>{
  "docs": [
    {
      "doc": {
        "_index": "index",
        "_version": "-3",
        "_id": "1",
        "_source": {
          "description": "While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard.",
          "model_id": "generate_topics_ia",
          "title": "The Lord of the Rings: The Fellowship of the Ring",
          "genres": [
            "Action",
            "Adventure",
            "Drama"
          ],
          "topics": [
            "Fantasy",
            "Quest"
          ]
        },
        "_ingest": {
          "timestamp": "2024-11-22T17:51:51.340010257Z"
        }
      }
    },
    {
      "doc": {
        "_index": "index",
        "_version": "-3",
        "_id": "2",
        "_source": {
          "description": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
          "model_id": "generate_topics_ia",
          "title": "Interstellar",
          "genres": [
            "Adventure",
            "Drama",
            "Sci-Fi"
          ],
          "topics": [
            "space exploration",
            "human survival"
          ]
        },
        "_ingest": {
          "timestamp": "2024-11-22T17:51:51.340413173Z"
        }
      }
    },
    {
      "doc": {
        "_index": "index",
        "_version": "-3",
        "_id": "3",
        "_source": {
          "description": "An astronaut becomes stranded on Mars after his team assume him dead, and must rely on his ingenuity to find a way to signal to Earth that he is alive.",
          "model_id": "generate_topics_ia",
          "title": "The Martian",
          "genres": [
            "Adventure",
            "Drama",
            "Sci-Fi"
          ],
          "topics": [
            "survival",
            "ingenuity"
          ]
        },
        "_ingest": {
          "timestamp": "2024-11-22T17:51:51.340427965Z"
        }
      }
    }
  ]
}<p>请注意，尽管有些影片最初属于同一类型，但我们有了与影片背景更加相关的新类别。</p><p>现在，我们可以使用这些新类别，并将它们与文档一起编入索引。这样，在生成切面时，除了主类别外，我们还可以根据影片的背景情况，生成更具体的子类别。</p><p>此外，还可以将这些新类别矢量化，并将其用于矢量搜索。这意味着，新的类别不仅可以用作过滤器，还可以用来计算与搜索词的语义相似性，从而进一步提高搜索结果的相关性。</p><p>完整的管道：</p>POST /_ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
        "script": {
          "source": """
            ctx.prompt = "You are an expert in semantic analysis and audiovisual content categorization. Your task is to generate only subcategories (max 2 topics) that describe specific aspects of movies based on their genres and descriptions. The output should be like string: 'n1, n2m ...n'. Here is a movies info to analyze: Title: " + ctx.title  + "Genres: " + ctx.genres  + "Description: " + ctx.description;
          """
        }
      },
      {
        "inference": {
          "model_id": "generate_topics_ia",
          "input_output": {
            "input_field": "prompt",
            "output_field": "result"
          }
        }
      },
      {
        "split": {
          "field": "result",
          "target_field": "topics",
          "separator": ", "
        }
      },
      {
        "remove": {
          "field": "result"
        }
      },
      {
        "remove": {
          "field": "prompt"
        }
      }
    ]
  },
  "docs": [
    {
      "_index": "index",
      "_id": "1",
      "_source": {
        "title": "The Lord of the Rings: The Fellowship of the Ring",
        "description": "While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard.",
        "genres": [
          "Action",
          "Adventure",
          "Drama"
        ]
      }
    },
    {
      "_index": "index",
      "_id": "2",
      "_source": {
        "title": "Interstellar",
        "description": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
        "genres": [
          "Adventure", "Drama", "Sci-Fi"
        ]
      }
    },
    {
      "_index": "index",
      "_id": "3",
      "_source": {
        "title": "The Martian",
        "description": "An astronaut becomes stranded on Mars after his team assume him dead, and must rely on his ingenuity to find a way to signal to Earth that he is alive.",
        "genres": [
          "Adventure", "Drama", "Sci-Fi"
        ]
      }
    }
  ]
}<h2>结论</h2><p>利用人工智能改进面，可以使搜索结果更具体、更符合上下文，从而改变搜索体验。固定类别通常比较宽泛，而人工智能生成的类别则不同，它能更好地反映背景情况。例如，在对电影进行重新分类时，我们可以捕捉到主要类别所忽略的背景，从而提供更相关的分组。</p><p>将这些新的类别添加到索引中，不仅可以改进分面，还可以实现矢量搜索。其结果是搜索体验更加高效，筛选器更加符合上下文。</p><h2>参考资料</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/infer-service-openai.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/infer-service-openai.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/simulate-pipeline-api.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/script-processor.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/script-processor.html</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/faceted-search-examples-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/faceted-search-examples-ai</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2838185214162c8/6a17f8bedbb4ff04affb58a5/25c9f9baa2326b5189ce0b1cc6240475781c755d-721x421.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何使用用于微软语义内核（Microsoft Semantic Kernel）的Elasticsearch矢量存储连接器进行人工智能代理开发]]></title>
    <description><![CDATA[微软语义内核（Microsoft Semantic Kernel）是一款轻量级开源开发工具包，可让您轻松构建人工智能代理，并将最新的人工智能模型集成到您的 C#、Python 或 Java 代码库中。随着Semantic Kernel Elasticsearch向量存储连接器（Elasticsearch Vector Store Connector）的发布，使用Semantic Kernel构建人工智能代理的开发人员现在可以将Elasticsearch作为可扩展的企业级向量存储插件，同时继续使用Semantic Kernel抽象。]]></description>
    <content:encoded><![CDATA[<p>我们与<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> 微软语义内核</a> （ Microsoft<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> Semantic Kernel ）团队合作，宣布面向 微软语义内核</a> （.NET）用户推出<a href="https://github.com/elastic/semantic-kernel-net/"> Semantic Kernel Elasticsearch矢量存储连接器（Vector Store Connector ）。</a>语义内核（Semantic Kernel）简化了企业级人工智能代理的构建过程，包括利用来自矢量存储库（Vector Store）的更多相关数据驱动响应来增强大型语言模型（LLM）的能力。语义内核（Semantic Kernel）为与Elasticsearch等矢量存储进行交互提供了一个无缝的抽象层，可提供创建、列出和删除记录集合以及上传、检索和删除单条记录等基本功能。</p><p><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">开箱即用的Semantic Kernel Elasticsearch向量存储连接器（Vector Store Connector</a>）支持Semantic Kernel<a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">向量存储抽象</a>，这使得开发人员在构建人工智能代理时能够非常容易地将Elasticsearch作为向量存储插件。</p><p>Elasticsearch 在开源社区拥有坚实的基础，最近采用了<a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL 许可证</a>。这些工具与开源的微软语义内核（Microsoft Semantic Kernel）相结合，可提供强大的企业级解决方案。您可以通过运行此命令<code>curl -fsSL https://elastic.co/start-local | sh </code> ，在几分钟内启动 Elasticsearch（详情请参考<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a>），然后在生产人工智能代理的同时，迁移到<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">云托管</a>或<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">自托管</a>版本。</p><p>在本篇博客中，我们将探讨在使用Semantic Kernel（语义内核）时，如何使用<a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch向量存储连接器</a>。该连接器的 Python 版本将在未来推出。</p><h2>高级应用场景：利用 Semantic Kernel&amp; Elasticsearch 构建 RAG 应用程序</h2><p>下面我们将举例说明。在高层次上，我们正在构建一个 RAG（检索增强生成）应用程序，它将用户的问题作为输入，并返回一个答案。我们将使用 Azure OpenAI （ 也可使用<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/"> 本地 LLM</a> ）作为 LLM，Elasticsearch 作为向量存储，Semantic Kernel (.net) 作为将所有组件连接在一起的框架。</p><p>如果您不熟悉 RAG 架构，可以通过以下文章快速了解<a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">： https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag。</a></p><p>答案由 LLM 生成，LLM 从 Elasticsearch 向量存储中获取与问题相关的上下文。答复还包括法律硕士用作背景的资料来源。</p><h3>RAG 示例</h3><p>在这个具体例子中，我们创建了一个应用程序，允许用户就内部酒店数据库中存储的酒店提出问题。例如，用户可以根据不同标准搜索特定酒店，或要求提供酒店列表。</p><p>在示例数据库中，我们生成了一个包含 100 个条目的<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">酒店列表</a>。为了让您尽可能轻松地试用连接器演示，我们特意设置了较小的样本量。在实际应用中，Elasticsearch 连接器将显示出其优于其他选项（如 "InMemory "向量存储实现）的优势，尤其是在处理超大数据量时。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储<a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">库中</a>找到。</p><p>让我们先将所需的 NuGet 软件包和指令添加到项目中：</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>现在，我们可以创建我们的数据模型，并为其提供语义内核（Semantic Kernel）的特定属性，以定义存储模型模式和文本搜索的一些提示：</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>存储模型模式属性（`VectorStore*`）与 Elasticsearch 向量存储连接器的实际使用最为相关，即</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> 来标记记录类上的一个属性，作为记录存储在向量存储中的键。</p></li><li><p><code>VectorStoreRecordData</code> 将记录类的一个属性标记为 "数据"。</p></li><li><p><code>VectorStoreRecordVector</code> 将记录类的一个属性标记为矢量。</p></li></ul><p>所有这些属性都接受各种可选参数，可用于进一步定制存储模型。以<code>VectorStoreRecordKey </code> 为例，可以指定不同的距离函数或不同的索引类型。</p><p>文本搜索属性 (<code>TextSearch*</code>) 在本示例的最后一步中非常重要。我们稍后再谈。</p><p>下一步，我们将初始化语义内核引擎，并获取核心服务的引用。在实际应用中，应使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">依赖注入</a>而不是直接访问服务集合。同样的道理也适用于硬编码的配置和秘密，它们应该使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">配置提供程序</a>来读取：</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>现在可以使用<code>vectorStoreCollection</code> 服务创建数据集，并摄取一些<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">演示记录</a>：</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>由此可见，语义内核（Semantic Kernel）是如何将向量存储的使用及其复杂性简化为几个简单的方法调用的。</p><p>在 Elasticsearch 中创建一个新索引，并创建所有必要的属性映射。然后，我们的数据集会完全透明地映射到存储模型中，并最终存储到索引中。下面是映射在 Elasticsearch 中的显示方式。</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p><code>embeddings.GenerateEmbeddingsAsync()</code> 会透明地调用已配置的 Azure AI 嵌入生成服务。</p><p>在这个演示的最后一个步骤中，我们还可以看到更多的神奇之处。</p><p>当用户就数据提问时，只需调用<code>InvokePromptAsync</code> ，就能执行以下所有操作：</p><p>1.为用户的问题生成嵌入代码</p><p>2.在矢量存储器中搜索相关条目</p><p>3.将查询结果插入提示模板</p><p>4.最终提示形式的实际查询将发送到人工智能聊天完成服务</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>还记得我们之前在数据模型上定义的<code>TextSearch*</code> 属性吗？有了这些属性，我们就能在提示模板中使用相应的占位符，这些占位符会根据向量存储中的条目信息自动填充。</p><p>对于我们的问题"，请告诉我所有拥有屋顶酒吧的酒店。" ，最终答复如下：</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>正确答案是指 hotels.csv 中的以下条目</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>这个例子很好地说明了微软语义内核的使用是如何通过其深思熟虑的抽象功能大大降低复杂性，并实现高度灵活性的。例如，只需修改一行代码，就可以更换向量存储或所使用的人工智能服务，而无需重构代码的任何其他部分。</p><p>同时，该框架还提供了大量高级功能，如 "InvokePrompt "函数或模板或搜索插件系统。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储库中找到。</p><h2>Elasticsearch 还能做什么</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch 新语义文本映射：简化语义搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">利用检索器在 Elasticsearch 中进行语义重排</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">高级 RAG 技术第 1 部分：数据处理</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">高级 RAG 技术第 2 部分：查询和测试</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">使用 Llama 3 开放源代码和 Elastic 构建 RAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">使用 LangGraph、LLaMA3 和 Elasticsearch 向量存储从零开始构建本地代理的教程</a></p></li></ul><h2>Elasticsearch&amp; Semantic Kernel（语义内核）：下一步是什么？</h2><ul><li><p>我们展示了在.NET中构建GenAI应用时，如何将Elasticsearch向量存储轻松插入Semantic Kernel。敬请期待下一步的 Python 集成。</p></li><li><p>由于Semantic Kernel（语义内核）为<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">混合</a>搜索等高级搜索功能建立了抽象，Elasticsearch连接将使.NET开发人员能够在使用Semantic Kernel（语义内核）的同时轻松实现这些功能。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangChain4j 使用 Elasticsearch 作为嵌入存储]]></title>
    <description><![CDATA[LangChain4j（Java 版 LangChain）将 Elasticsearch 作为嵌入式存储。了解如何使用它在普通 Java 中构建 RAG 应用程序。]]></description>
    <content:encoded><![CDATA[<p>
在<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">上一篇文章</a>中，我们了解了什么是 LangChain4j 以及如何使用：</p><ul><li><p>与法律硕士进行讨论，实施<code>ChatLanguageModel</code> 和 <code>ChatMemory</code></p></li><li><p>在记忆中保留聊天记录，以便回忆起之前与一位法律硕士讨论的背景情况</p></li></ul><p>本博文将介绍如何</p><ul><li><p>根据文本示例创建向量嵌入</p></li><li><p>将向量嵌入存储在 Elasticsearch 嵌入存储中 </p></li><li><p>搜索类似载体</p></li></ul><h2>创建嵌入</h2><p>要创建嵌入式，我们需要定义一个<code>EmbeddingModel</code> 。例如，我们可以使用<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">上一篇文章</a>中使用过的相同的 mistral 模型。它和奥拉马一起跑：</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>模型能够从文本中生成向量。在这里，我们可以检查模型生成的维数：</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>要从文本中生成向量，我们可以使用</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>或者，如果我们还想提供元数据，以便对文本、价格、发布日期等内容进行筛选，我们可以使用<code>Metadata.from()</code>.NET。例如，我们在这里添加游戏名称作为元数据字段：</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a>类。</p><h2>添加 Elasticsearch 来存储向量</h2><p>LangChain4j 提供内存嵌入存储。这对运行简单测试非常有用：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>但是，这显然不能用于更大的数据集，因为该数据存储将所有内容都存储在内存中，而我们的服务器上没有无限的内存。因此，我们可以将嵌入式数据存储到 Elasticsearch 中，根据定义，Elasticsearch 是"elastic" ，可以随着数据的扩展而扩展。为此，让我们在项目中添加 Elasticsearch：</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>正如你所注意到的，我们还在项目中添加了 Elasticsearch TestContainers 模块，这样我们就可以从测试中启动 Elasticsearch 实例：</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>要将 Elasticsearch 用作嵌入式存储，"，" ，就必须从 LangChain4j 内存数据存储切换到 Elasticsearch 数据存储：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>这将在 Elasticsearch 中以<code>default</code> 索引的形式存储向量。您还可以将索引名称改为更有意义的名称：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>如果您想运行此代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a>类。</p><h2>搜索类似载体</h2><p>要搜索相似向量，我们首先需要使用之前使用过的相同模型，将问题转换为向量表示。我们已经做到了，所以再做一次并不难。请注意，在这种情况下我们不需要元数据：</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>我们可以用问题的这种表示法建立一个搜索请求，并要求嵌入式存储空间找出最前面的向量：</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>现在，我们可以遍历结果并打印一些信息，如来自元数据的游戏名称和得分：</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>正如我们所预料的那样，"Out Run" 作为第一击：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="跑出" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a>类。 </p><h2>幕后花絮</h2><p>Elasticsearch 嵌入存储的默认配置是在后台使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">近似 kNN 查询</a>。</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>但这可以通过向嵌入存储区提供默认配置 (<code>ElasticsearchConfigurationKnn</code>) 以外的另一种配置 (<code>ElasticsearchConfigurationScript</code>) 来改变：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p><code>ElasticsearchConfigurationScript</code><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"></a>执行程序使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"> 函数 在后台运行</a> 查询 。</p><p>基本上，打电话时</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>现在呼叫</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>在这种情况下，结果并不会因为"order" 而发生变化，只是分数会有所调整，因为<code>cosineSimilarity</code> 调用并不使用任何近似值，而是计算每个匹配向量的余弦值：</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a>类。</p><h2>结论</h2><p>我们已经介绍了如何从文本中轻松生成嵌入，以及如何使用两种不同的方法在 Elasticsearch 中存储和搜索近邻：</p><ul><li><p>使用<code>ElasticsearchConfigurationKnn</code> 默认选项进行近似和快速<code>knn</code> 查询</p></li><li><p>使用<code>ElasticsearchConfigurationScript</code> 选项进行精确但较慢的<code>script_score</code> 查询</p></li></ul><p>下一步将根据我们在这里学到的知识，构建一个完整的 RAG 应用程序。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[引入 LangChain4j 以简化 LLM 与 Java 应用程序的集成]]></title>
    <description><![CDATA[LangChain4j（LangChain for Java）是一个功能强大的工具集，可用于在纯 Java 中构建 RAG 应用程序。]]></description>
    <content:encoded><![CDATA[<p><a href="https://docs.langchain4j.dev/">LangChain4j 框架</a>正是<a href="https://github.com/langchain4j/langchain4j/blob/main/README.md#introduction">以此为目标</a>于 2023 年创建的：</p>LangChain4j 的目标是简化将 LLM 集成到 Java 应用程序中的过程。<p>LangChain4j 提供了一种标准的方法：</p><ul><li><p>根据给定内容（例如文本）创建嵌入向量</p></li><li><p>将嵌入式存储在嵌入式存储库中</p></li><li><p>在嵌入存储中搜索相似向量</p></li><li><p>与法律硕士讨论</p></li><li><p>使用聊天记忆功能，记住与法律硕士讨论的来龙去脉</p></li></ul><p>此列表并不详尽，LangChain4j 社区一直在实施新功能。</p><p>本帖将介绍该框架的第一个主要部分。</p><h2>将 LangChain4j OpenAI 添加到我们的项目中</h2><p>与所有 Java 项目一样，这只是一个依赖关系问题。这里我们将使用 Maven，但任何其他依赖管理器也可以实现同样的功能。</p><p>作为构建项目的第一步，我们将使用 OpenAI，因此只需添加<code>langchain4j-open-ai</code> 工具：</p>&lt;properties&gt;
  &lt;langchain4j.version&gt;0.34.0&lt;/langchain4j.version&gt;
&lt;/properties&gt;

&lt;dependencies&gt;
  &lt;dependency&gt;
    &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
    &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
    &lt;version&gt;${langchain4j.version}&lt;/version&gt;
  &lt;/dependency&gt;
&lt;/dependencies&gt;
<p>在其余代码中，我们将使用自己的 API 密钥（可通过注册<a href="https://platform.openai.com/signup/">OpenAI</a> 账户获得），或者 LangChain4j 项目提供的 API 密钥（仅供演示使用）：</p>static String getOpenAiApiKey() {
  String apiKey = System.getenv(API_KEY_ENV_NAME);
  if (apiKey == null || apiKey.isEmpty()) {
    Logger.warn("Please provide your own key instead using [{}] env variable", API_KEY_ENV_NAME);
    return "demo";
  }
  return apiKey;
}
<p>现在我们可以创建 ChatLanguageModel 的实例：</p>ChatLanguageModel model = OpenAiChatModel.withApiKey(getOpenAiApiKey());
<p>最后，我们可以问一个简单的问题，并得到答案：</p>String answer = model.generate("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>给出的答案可能是这样的</p>Thomas Pesquet is a French aerospace engineer, pilot, and European Space Agency astronaut.
He was selected as a member of the European Astronaut Corps in 2009 and has since completed 
two space missions to the International Space Station, including serving as a flight engineer 
for Expedition 50/51 in 2016-2017. Pesquet is known for his contributions to scientific 
research and outreach activities during his time in space.
<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step1AiChatTest.java">Step1AiChatTest.java</a>类。</p><h2>使用 langchain4j 提供更多语境</h2><p>让我们添加<code>langchain4j</code> 手工艺品：</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>它提供了一个工具集，可以帮助我们建立更高级的 LLM 集成，以构建我们的助手。在这里，我们只需创建一个<code>Assistant</code> 接口，该接口提供的<code>chat</code> 方法将自动调用我们之前定义的<code>ChatLanguageModel</code> ：</p>interface Assistant {
  String chat(String userMessage);
}
<p>我们只需请求 LangChain4j<code>AiServices</code> 类为我们构建一个实例：</p>Assistant assistant = AiServices.create(Assistant.class, model);
<p>然后调用<code>chat(String)</code> 方法：</p>String answer = assistant.chat("Who is Thomas Pesquet?");
Logger.info("Answer is: {}", answer);
<p>这与之前的行为相同。那么，我们为什么要修改代码呢？首先，它更优雅，但更重要的是，你现在可以使用简单的注释向 LLM 发出一些指示：</p>interface Assistant {
  @SystemMessage("Please answer in a funny way.")
  String chat(String userMessage);
}
<p>这就是现在的奉献：</p>Ah, Thomas Pesquet is actually a super secret spy disguised as an astronaut! 
He's out there in space fighting aliens and saving the world one spacewalk at a time. 
Or maybe he's just a really cool French astronaut who has been to the International 
Space Station. But my spy theory is much more exciting, don't you think?
<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step2AssistantTest.java">Step2AssistantTest.java</a>类。</p><h2>转到另一个 LLM：langchain4j-ollama</h2><p>我们可以利用伟大的<a href="https://ollama.com/">奥拉马项目</a>。在本地计算机上运行 LLM 会有帮助。</p><p>让我们添加<code>langchain4j-ollama</code> 手工艺品：</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-ollama&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;
<p>由于我们要使用测试来运行示例代码，因此让我们在项目中添加<a href="https://java.testcontainers.org/">Testcontainers</a>：</p>&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;ollama&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
<p>现在我们可以启动/停止 Docker 容器了：</p>static String MODEL_NAME = "mistral";
static String DOCKER_IMAGE_NAME = "langchain4j/ollama-" + MODEL_NAME + ":latest";

static OllamaContainer ollama = new OllamaContainer(
  DockerImageName.parse(DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("ollama/ollama"));

@BeforeAll
public static void setup() {
  ollama.start();
}

@AfterAll
public static void teardown() {
  ollama.stop();
}
<p>我们只需"，" ，将<code>model</code> 对象改为<code>OllamaChatModel</code> ，而不是之前使用的<code>OpenAiChatModel</code> ：</p>OllamaChatModel model = OllamaChatModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();
<p>请注意，提取图像及其模型可能需要一些时间，但一段时间后，您就可以得到答案了：</p>Oh, Thomas Pesquet, the man who single-handedly keeps the French space program running 
while sipping on his crisp rosé and munching on a baguette! He's our beloved astronaut 
with an irresistible accent that makes us all want to learn French just so we can 
understand him better. When he's not floating in space, he's probably practicing his 
best "je ne sais quoi" face for the next family photo. Vive le Thomas Pesquet! 
🚀🌍🇫🇷 #FrenchSpaceHero
<h2>记忆力更好</h2><p>如果我们提出多个问题，系统默认情况下不会记住之前的问题和答案。因此，如果我们在第一个问题之后提问"他是什么时候出生的？" 、我们的应用程序会回答：</p>Oh, you're asking about this legendary figure from history, huh? Well, let me tell 
you a hilarious tale! He was actually born on Leap Year's Day, but only every 400 
years! So, do the math... if we count backwards from 2020 (which is also a leap year), 
then he was born in... *drumroll please* ...1600! Isn't that a hoot? But remember 
folks, this is just a joke, and historical records may vary.
<p>这是无稽之谈。相反，我们应该使用<a href="https://docs.langchain4j.dev/tutorials/chat-memory">聊天记忆</a>：</p>ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
  .chatLanguageModel(model)
  .chatMemory(chatMemory)
  .build();
<p>现在运行同样的问题就能得到有意义的答案：</p>Oh, Thomas Pesquet, the man who was probably born before sliced bread but after dinosaurs! 
You know, around the time when people started putting wheels on suitcases and calling it 
a revolution. So, roughly speaking, he came into this world somewhere in the late 70s or 
early 80s, give or take a year or two - just enough time for him to grow up, become an 
astronaut, and make us all laugh with his space-aged antics! Isn't that a hoot? 
*laughs maniacally*
<h2>结论</h2><p>在<a href="https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store">下一篇文章</a>中，我们将了解如何使用 Elasticsearch 作为嵌入存储，向我们的私有数据集提问。这将为我们提供一种方法，使我们的应用程序搜索工作更上一层楼。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0435ed6d14579089/6a17e79ae8fbce7e433a192f/cf129b8b25fbe7204e2adca8fca5fec04207f096-720x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[高级 RAG 技术第 2 部分：查询和测试]]></title>
    <description><![CDATA[讨论并实施可提高 RAG 性能的技术。第 2 部分（共 2 部分），重点是查询和测试高级 RAG 管道。]]></description>
    <content:encoded><![CDATA[<p><em>所有代码都可以 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>在 Searchlabs 软件仓库的 advanced-rag-techniques 分支中</em></a>找到 <em>。</em></p><p>欢迎阅读我们关于高级 RAG 技术文章的第二部分！在<a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">本系列的第 1 部分</a>中，我们建立、讨论并实施了高级 RAG 管道的数据处理组件：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="高级 RAG 管道" /><p>在这一部分，我们将继续查询和测试我们的实现。让我们直奔主题！</p><h3>目录</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">搜索和检索，生成答案</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">用同义词丰富查询</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE（假设文档嵌入）</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">混合搜索</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">实验</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">结果摘要</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">测试 1：谁审核了 Elastic？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">测试 2：2023 年总收入</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">测试 3：增长主要依赖于什么产品？多少钱？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">测试 4：说明员工福利计划</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">测试 5：Elastic 收购了哪些公司？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">结论</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">附录</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">提示</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">RAG 问题解答提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">弹性查询生成器提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">潜在问题生成器提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">HyDE 生成器提示</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">混合搜索查询示例</a></p></li></ul></li></ul><h2>搜索和检索，生成答案</h2><p>让我们提出第一个问题，最好是主要在年度报告中找到的一些信息。怎么样？</p>Who audits Elastic?"
<p>现在，让我们运用一些技术来增强查询。</p><h3>用同义词丰富查询</h3><p>首先，让我们增强查询措辞的多样性，并将其转化为可轻松处理成 Elasticsearch 查询的形式。我们将借助 GPT-4o 将查询转换为 OR 子句列表。让我们来写下这个提示：</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>当应用到我们的查询时，GPT-4o 会生成基本查询和相关词汇的同义词。</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p>在<code>ESQueryMaker</code> 类中，我定义了一个分割查询的函数：</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>它的作用是将这串 OR 子句拆分成一个术语列表，使我们能够对关键文档字段进行多重匹配：</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>最后得出了这个疑问：</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>这比原始查询涵盖的范围更广，有望降低因忘记同义词而错过搜索结果的风险。但我们可以做得更多。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h3>HyDE（假设文档嵌入）</h3><p>让我们再次利用 GPT-4o 来实现<a href="https://arxiv.org/abs/2212.10496">HyDE</a>。</p><p>HyDE 的基本前提是生成一个假设文档--一种可能包含原始查询答案的文档。文件的真实性或准确性并不重要。有鉴于此，让我们写下下面的提示：</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>由于矢量搜索通常是通过余弦矢量相似性进行操作的，因此 HyDE 的前提是，我们可以通过文档与文档的匹配，而不是查询与文档的匹配，来获得更好的结果。</p><p>我们关心的是结构、流程和术语。事实性不强。GPT-4o 可以输出这样的 HyDE 文档：</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>它看起来非常可信，是我们希望索引的文档类型的理想候选者。我们将把它嵌入并用于混合搜索。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h3>混合搜索</h3><p>这是我们搜索逻辑的核心。我们的词法搜索组件将是生成的 OR 子句字符串。我们的密集矢量组件将是嵌入式 HyDE 文档（又称搜索矢量）。我们使用 KNN 来有效识别与搜索向量最接近的几个候选文档。我们将词法搜索组件默认称为<em>TF-IDF 和 BM25 评分</em>。最后，将采用<a href="https://arxiv.org/abs/2407.01219">Wang 等</a>人推荐的 30/70 比例合并词性和密集向量得分。</p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>最后，我们可以拼凑出一个 RAG 函数。我们的 RAG（从询问到答复）将遵循这一流程：</p><ol><li><p>将查询转换为 OR 子句。</p></li><li><p>生成 HyDE 文档并嵌入。</p></li><li><p>将二者作为混合搜索的输入。</p></li><li><p>检索前 N 个结果，将它们倒转，使最相关的得分是 LLM 上下文内存中"最近的" （反向打包） 反向打包示例：查询："Elasticsearch 查询优化技术" 检索文档（按相关性排序）：  LLM 上下文的反向顺序：  通过颠倒顺序，最相关的信息(1)会出现在上下文的最后，从而可能在生成答案时受到 LLM 的更多关注。</p><ol><li><p>"使用 bool 查询可有效组合多个搜索条件。"</p></li><li><p>"实施缓存策略，缩短查询响应时间。"</p></li><li><p>"优化索引映射，提高搜索性能。"</p></li><li><p>"优化索引映射，提高搜索性能。"</p></li><li><p>"实施缓存策略，缩短查询响应时间。"</p></li><li><p>"使用 bool 查询可有效组合多个搜索条件。"</p></li></ol></li><li><p>将上下文传递给 LLM 生成。</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>让我们运行查询并得到答案：</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>不错。没错。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>实验</h2><p>现在有一个重要问题需要回答。我们在这些实施中投入了如此多的精力和额外的复杂性，究竟得到了什么？</p><p>让我们来做个小小的比较。我们实施的 RAG 管道与基线混合搜索相比，没有任何增强功能。我们将进行一系列小测试，看看是否会发现任何实质性差异。我们将把刚刚实现的 RAG 称为 AdvancedRAG，把基本管道称为 SimpleRAG。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="简单的 RAG 管道" /><h4>结果摘要</h4><p>本表总结了两种 RAG 管道的五次测试结果。我根据答案的细节和质量来判断每种方法的相对优劣，但这完全是主观判断。现将实际答案转载于下表，供您参考。说了这么多，让我们来看看他们的表现如何！</p><p>SimpleRAG 无法回答问题 1&amp; 5。AdvancedRAG 对问题 2、3 和 4 的回答也要详细得多。基于更多的细节，我认为 AdvancedRAG 的答案质量更高。</p><p>测试</p><p>问题</p><p>高级 RAG 性能</p><p>SimpleRAG 性能</p><p>AdvancedRAG 延迟</p><p>SimpleRAG 延迟</p><p>优胜者</p><p>1</p><p>谁审核 Elastic？</p><p>正确确定普华永道为审计员。</p><p>未能确定审计员。</p><p>11.6s</p><p>4.4s</p><p>AdvancedRAG</p><p>2</p><p>2023 年的总收入是多少？</p><p>提供了正确的收入数字。包括往年收入的补充情况。</p><p>提供了正确的收入数字。</p><p>13.3s</p><p>2.8s</p><p>AdvancedRAG</p><p>3</p><p>增长主要依靠什么产品？多少钱？</p><p>正确指出弹性云是关键驱动因素。包括总体收入情况&amp; 。</p><p>正确指出弹性云是关键驱动因素。</p><p>14.1s</p><p>12.8s</p><p>AdvancedRAG</p><p>4</p><p>说明员工福利计划</p><p>全面介绍了退休计划、医疗计划和其他福利。包括不同年份的具体捐款额。</p><p>提供了很好的福利概览，包括薪酬、退休计划、工作环境和 Elastic Cares 计划。</p><p>26.6s</p><p>11.6s</p><p>AdvancedRAG</p><p>5</p><p>Elastic 收购了哪些公司？</p><p>正确列出了报告中提到的近期收购（CmdWatch、Build Security 和 Optimyze）。提供了一些收购日期和收购价格。</p><p>未能从提供的上下文中检索到相关信息。</p><p>11.9s</p><p>2.7s</p><p>AdvancedRAG</p><h4>测试 1：谁审核了 Elastic？</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>摘要</strong>：SimpleRAG 没有将普华永道确定为审计机构</p><p>好吧，这其实挺让人惊讶的。这看起来像是 SimpleRAG 的搜索失败。没有检索到与审计有关的文件。让我们在下一个测试中降低难度。</p><h4>测试 2：2023 年总收入</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>摘要</strong>：两个 RAG 都得到了正确答案：2023 年总收入为 1,068,989,000 美元</p><p>他们都在这里。看来，AdvancedRAG 可能获得了更多的文件？当然，答案会更加详细，并包含往年的信息。考虑到我们所做的改进，这是意料之中的，但现在下结论还为时过早。</p><p>让我们提高难度。</p><h4>测试 3：增长主要依赖于什么产品？多少钱？</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>摘要</strong>：两个 RAG 都正确地将弹性云确定为主要增长动力。不过，AdvancedRAG 包含更多细节，将订阅收入和客户增长考虑在内，并明确提及其他 Elastic 产品。</p><h4>测试 4：说明员工福利计划</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>摘要</strong>：AdvancedRAG 更深入、更详细地介绍了美国员工的 401K 计划，以及美国以外地区的缴费计划。报告还提到了 "健康与福利计划"，但没有提到 SimpleRAG 提到的 "Elastic Cares 计划"。</p><h4>测试 5：Elastic 收购了哪些公司？</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>摘要</strong>：SimpleRAG 无法检索到任何有关收购的相关信息，导致回答失败。AdvancedRAG 正确地列出了 CmdWatch、Build Security 和 Optimyze，它们是报告中列出的主要收购项目。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>结论</h2><p>根据我们的测试，我们的先进技术似乎增加了所提供信息的范围和深度，有可能提高 RAG 答案的质量。</p><p>此外，可靠性也可能有所提高，因为 AdvancedRAG 可以正确回答<code>Which companies did Elastic acquire?</code> 和<code>Who audits Elastic</code> 等措辞含糊的问题，而 SimpleRAG 则不能。</p><p>不过，值得注意的是，在 5 个案例中的 3 个案例中，基本的 RAG 管道（包括混合搜索，但不包括其他技术）设法得出了能够捕捉到大部分关键信息的答案。</p><p>我们应该注意到，由于在数据准备和查询阶段加入了 LLM，AdvancedRAG 的延迟一般是 SimpleRAG 的 2-5 倍。这是一笔不小的费用，可能使 AdvancedRAG 只适用于优先考虑应答质量而不是延迟的情况。</p><p>在数据准备阶段，使用 Claude Haiku 或 GPT-4o-mini 等更小巧、更便宜的 LLM，就能减轻巨大的延迟成本。将高级模型留待生成答案时使用。</p><p>这与 Wang 等人的研究结果一致。结果表明，任何改进都是相对渐进的。简而言之，简单的基线 RAG 就能让您获得大部分体面的最终产品，而且成本更低，速度更快。对我来说，这是一个有趣的结论。对于速度和效率至关重要的使用案例，SimpleRAG 是明智的选择。对于需要榨取每一滴性能的使用案例，AdvancedRAG 中包含的技术可能会提供一条出路。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="王家管道" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>附录</h2><h3>提示</h3><h4>RAG 问题解答提示</h4><p>提示 LLM 根据查询和上下文生成答案。</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>弹性查询生成器提示</h4><p>提示使用同义词丰富查询内容，并将其转换为 OR 格式。</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>潜在问题生成器提示</h4><p>提示生成潜在问题，丰富文件元数据。</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>HyDE 生成器提示</h4><p>使用 HyDE 生成假设文档的提示</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>混合搜索查询示例</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[高级 RAG 技术第 1 部分：数据处理]]></title>
    <description><![CDATA[讨论并实施可提高 RAG 性能的技术。第 1 部分（共 2 部分），重点介绍高级 RAG 管道的数据处理和摄取部分。]]></description>
    <content:encoded><![CDATA[<p><em>这是我们探索高级 RAG 技术的第一部分。 </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>点击此处查看第二部分！</em></a></p><p>最近发表的论文《<a href="https://arxiv.org/abs/2407.01219">在检索增强生成中寻找最佳实践</a>》对各种 RAG 增强技术的功效进行了实证评估，目的是为 RAG 找到一套最佳实践。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="王建议的 RAG 管道" /><p>我们将实施其中一些建议的最佳实践，即旨在提高搜索质量的实践<strong>（句子分块、HyDE、反向打包）</strong>。</p><p>为简洁起见，我们将省略那些侧重于提高效率的技术<strong>（查询分类和摘要）</strong>。</p><p>我们还将实施一些未涉及但我个人认为有用且有趣的技术<strong>（元数据包含、复合多字段嵌入、查询丰富化）</strong>。</p><p>最后，我们将进行一个简短的测试，看看搜索结果和生成答案的质量与基线相比是否有所提高。让我们开始吧！</p><h2>RAG 概览</h2><p>RAG 的目的是通过检索外部知识库中的信息来丰富生成的答案，从而增强 LLM。通过提供特定领域的信息，LLM 可以快速适应训练数据范围之外的用例；比微调成本低得多，也更容易保持更新。</p><p>提高 RAG 质量的措施通常集中在两个方面：</p><ol><li><p>提高知识库的质量和清晰度。</p></li><li><p>提高搜索查询的覆盖面和针对性。</p></li></ol><p>这两项措施将实现提高法律硕士获得相关事实和信息的几率的目标，从而减少产生幻觉或利用自身知识的可能性--这些知识可能已经过时或不相关。</p><p>方法的多样性难以用几句话说清楚。为了更清楚地说明问题，让我们直接进入实施阶段。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="高级 RAG 管道" /><h3>目录</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">概述</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">目录</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">设置</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">摄取、处理和嵌入文件</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">数据采集</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">句子级标记分块</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">元数据的纳入和生成</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">通过 TextRank 提取的关键词</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">GPT-4o 提出的潜在问题</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Spacy 提取的实体</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">复合多场嵌入</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">索引至弹性</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">猫休息</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">附录</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">定义</a></p></li></ul></li></ul><h2>设置</h2><p><em>所有代码均可 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>在 Searchlabs 软件仓库中</em></a>找到 <em>。</em></p><p>先说第一件事。您需要以下材料</p><ol><li><p>弹性云部署</p></li><li><p>LLM 应用程序接口--我们在本笔记本中使用了 Azure OpenAI 上的 GPT-4o 部署</p></li><li><p>Python 3.12.4 或更高版本</p></li></ol><p>我们将运行<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb"> main.ipynb 笔记本 中的所有代码 。</a></p><p>继续 git 克隆该 repo，导航至 supporting-blog-content/Advanced-rag-techniques，然后运行以下命令：</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>完成后，创建一个<em>.env</em>文件，并填写以下字段（在<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>.env.example</em></a> 中引用）。感谢我的合著者 Claude-3.5 提出的有益意见。</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>接下来，我们将选择要摄取的文档，并将其放在文档文件夹中。在本文中，我们将使用<a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf"> Elastic N.V. 的《 2023 年年度报告》</a> 。这是一份相当具有挑战性的密集文件，非常适合对我们的 RAG 技术进行压力测试。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="2023 年弹性年度报告" /><p>现在我们都准备好了，开始摄入。打开<em>main.ipynb</em>，执行前两个单元格以导入所有软件包并初始化所有服务。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h2>摄取、处理和嵌入文件</h2><h3>数据采集</h3><ul><li><p><em>个人感言：LlamaIndex 的便利性令我震惊。在还没有 LLM 和 LlamaIndex 的年代，录入各种格式的文档是一个痛苦的过程，需要从各处收集深奥的软件包。现在只需调用一个函数。狂野</em></p></li></ul><p><code>SimpleDirectoryReader</code> 将加载<code>directory_path.</code> 文件中的每个文档。对于<code>.pdf</code> 文件，它会返回一个文档对象列表，我将其转换为 Python 字典，因为我觉得它们更容易处理。</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>每个字典都包含<code>text</code> 字段中的关键内容。它还包含有用的元数据，如页码、文件名、文件大小和类型。</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>句子级标记分块</h3><p>首先要做的是将我们的文件缩减成标准长度的块状（以确保一致性和可管理性）。嵌入模型有独特的标记限制（可处理的最大输入尺寸）。标记是模型处理文本的基本单位。为防止信息丢失（内容截断或遗漏），我们应提供不超过这些限制的文本（将较长的文本分割成较小的片段）。</p><p>分块对性能有重大影响。在理想情况下，每个信息块都代表一个独立的信息片段，捕捉有关单个主题的上下文信息。分块方法包括字级分块（按字数分割文档）和语义分块（使用 LLM 识别逻辑断点）。</p><p>单词级的分块处理成本低、速度快、操作简单，但存在拆分句子从而破坏上下文的风险。语义分块的速度越来越慢，成本越来越高，尤其是在处理像116页的《弹性年度报告》这样的文档时。</p><p>让我们选择一种中间路线。句子级分块仍然简单，但比单词级分块能更有效地保留上下文，而且成本更低，速度更快。此外，我们还将采用一个滑动窗口来捕捉周围的一些上下文，并减轻分割段落的影响。</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p><code>Chunker</code> 类采用嵌入模型的标记化器对文本进行编码和解码。现在，我们将构建每块 512 个令牌的分块，其中有 20 个令牌重叠。为此，我们会将文本分割成句子，对这些句子进行标记化处理，然后将标记化处理后的句子添加到当前语块中，直到无法在不超出标记限制的情况下添加更多句子为止。</p><p>最后，将句子解码回原始文本进行嵌入，将其存储在名为<code>original_text</code> 的字段中。数据块存储在一个名为<code>chunk</code> 的字段中。为了减少噪音（又称无用文件），我们将丢弃长度小于 50 个 token 的文件。</p><p>让我们在文件上运行一下：</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>然后得到类似这样的文本块：</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>元数据的纳入和生成</h3><p>我们已将文件分块。现在是丰富数据的时候了。我想生成或提取额外的元数据。这些附加元数据可用于影响和提高搜索性能。</p><p>我们将定义一个<code>DocumentEnricher</code> 类，它的作用是接收文档列表（Python 字典）和处理器函数列表。这些函数将在文档的<code>original_text</code> 列中运行，并将其输出存储在新字段中。</p><p>首先，我们使用<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank</a> 提取关键词。TextRank 是一种基于图的算法，它能根据词与词之间的关系对关键短语和句子的重要性进行排序，从而从文本中提取关键短语和句子。</p><p>接下来，我们将<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">使用 GPT-4o 生成 potential_questions</a>。</p><p>最后，我们将使用<a href="https://spacy.io/"> Spacy</a> <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py">提取实体</a> 。</p><p>由于每项工作的代码都相当冗长和复杂，我就不在此赘述了。如果您感兴趣，这些文件已在下面的代码示例中标出。</p><p>让我们运行数据浓缩：</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>看看结果吧：</p><h4>通过 TextRank 提取的关键词</h4><p>这些关键短语是大块核心主题的替身。如果查询与网络安全有关，这块内容的得分就会提高。</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>GPT-4o 提出的潜在问题</h4><p>这些潜在问题可能与用户查询直接匹配，从而提高得分。我们会提示 GPT-4o 生成一些问题，这些问题可以用当前语块中的信息来回答。</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Spacy 提取的实体</h4><p>这些实体的作用与关键词类似，但可以捕捉到组织和个人的名称，而关键词提取可能会遗漏这些名称。</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>复合多场嵌入</h3><p>现在，我们已经用更多的元数据丰富了我们的文档，我们可以利用这些信息创建更强大、更能感知上下文的嵌入。</p><p>让我们回顾一下目前的进程。我们在每份文档中都有四个关注领域。</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>每个字段都代表了对文件背景的不同看法，可能突出了法律硕士应重点关注的关键领域。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="RAG 中的元数据丰富管道" /><p>我们的计划是嵌入每个字段，然后创建嵌入的加权和，即复合嵌入。</p><p>幸运的话，除了引入另一个可调整的超参数来控制搜索行为外，这种复合嵌入还能让系统变得更加了解上下文。</p><p>首先，让我们使用在 main.ipynb 笔记本开头导入的本地定义的嵌入模型，嵌入每个字段并就地更新每个文档。</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>每个嵌入函数都会返回嵌入的字段，即带有<code>_embedding</code> 后缀的原始输入字段。</p><p>现在我们来定义复合嵌入的权重：</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>通过权重，您可以根据用例和数据质量为每个组件分配优先级。直观地说，这些权重的大小取决于每个组件的语义值。由于大块文本本身的内容迄今为止最为丰富，我将其权重定为 70% 。由于实体最小，只是一个组织或个人名称列表，因此我将其权重定为 5% 。这些值的精确设置必须根据具体情况，根据经验来确定。</p><p>最后，让我们编写一个函数来应用权重，并创建我们的复合嵌入。为了节省空间，我们还将删除所有的组件嵌入。</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>至此，我们完成了文件处理工作。现在我们有了一个文档对象列表，看起来像这样：</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>索引至弹性</h4><p>让我们将文档批量上传到 Elastic Search。为此，我很早就在<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a> 中定义了一组 Elastic Helper 函数。这是一段非常冗长的代码，所以我们还是只看函数调用。</p><p><code>es_bulk_indexer.bulk_upload_documents</code> 利用 Elasticsearch 方便的动态映射，可以处理任何字典对象列表。</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>前往 Kibana，确认所有文件都已编入索引。应该有 224 个。对于这么大的文件来说，还算不错！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Kibana 索引" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h2>猫休息</h2><p>我们休息一下吧，文章有点沉重，我知道。看看我的猫</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="汉族管道" /><p>真可爱帽子不见了，我半信半疑是她偷藏起来的：(</p><p>祝贺你们走到这一步 :)</p><p>请看<a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">第二部分</a>，了解我们对 RAG 管道的测试和评估！</p><h2>附录</h2><h3>定义</h3><p><strong>1.句子分块</strong></p><ul><li><p>RAG 系统中使用的一种预处理技术，用于将文本划分为更小的、有意义的单元。</p></li><li><p><em>过程：</em> </p><ol><li><p>输入：大段文本（如文档、段落）</p></li><li><p>输出：较小的文本片段（通常是句子或小句子组）</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>创建细粒度、针对特定上下文的文本片段</p></li><li><p>允许更精确的索引和检索</p></li><li><p>提高 RAG 系统检索信息的相关性</p></li></ul></li><li><p><em>特点</em> </p><ul><li><p>分段具有语义意义</p></li><li><p>可独立索引和检索</p></li><li><p>通常保留一些上下文，以确保独立的可理解性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>提高检索精度</p></li><li><p>使 RAG 管道的扩容更有针对性</p></li></ul></li></ul><p><strong>2.HyDE（假设文档嵌入）</strong></p><ul><li><p>在 RAG 系统中使用 LLM 生成用于查询扩展的假设文档的技术。</p></li><li><p><em>过程：</em>  </p><ol><li><p>向 LLM 输入查询</p></li><li><p>LLM 生成回答查询的假设文档</p></li><li><p>嵌入生成的文件</p></li><li><p>使用嵌入进行向量搜索</p></li></ol></li><li><p><em>主要区别</em> </p><ul><li><p>传统 RAG：将查询与文档匹配</p></li><li><p>HyDE：将文档匹配到文档</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>提高检索性能，尤其是复杂或模糊查询的检索性能</p></li><li><p>捕捉比简短查询更丰富的语义上下文</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>利用 LLM 的知识扩展查询</p></li><li><p>有可能提高检索文件的相关性</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>需要额外的 LLM 推理，增加了延迟和成本</p></li><li><p>性能取决于生成的假设文件的质量</p></li></ul></li></ul><p><strong>3.反向包装</strong></p><ul><li><p>RAG 系统中使用的一种技术，用于在将搜索结果传递给 LLM 之前对其重新排序。</p></li><li><p><em>过程：</em> </p><ol><li><p>搜索引擎（如 Elasticsearch）按相关性降序返回文档。</p></li><li><p>顺序颠倒，将最相关的文件放在最后。</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>利用 LLM 的新旧偏差，LLM 往往更关注其上下文中的最新信息。</p></li><li><p>确保最相关的信息"最新鲜的" 在 LLM 的上下文窗口中。</p></li></ul></li><li><p><em>举例说明：</em>原始顺序：[最相关、第二最相关、第三最相关、......] 倒序：[......，最重要的第三项，最重要的第二项，最相关的］</p></li></ul><p><strong>4.查询分类</strong></p><ul><li><p>通过确定查询是需要 RAG 还是可以直接由 LLM 回答来优化 RAG 系统效率的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>针对使用中的 LLM 开发定制数据集</p></li><li><p>训练专门的分类模型</p></li><li><p>使用模型对收到的查询进行分类</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>避免不必要的 RAG 处理，提高系统效率</p></li><li><p>将查询引导至最合适的响应机制</p></li></ul></li><li><p><em>要求：</em> </p><ul><li><p>LLM 专用数据集和模型</p></li><li><p>不断改进以保持准确性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>减少简单查询的计算开销</p></li><li><p>有可能缩短非 RAG 查询的响应时间</p></li></ul></li></ul><p><strong>5.总结</strong></p><ul><li><p>在 RAG 系统中压缩检索文档的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>检索相关文件</p></li><li><p>生成每份文件的简明摘要</p></li><li><p>在 RAG 管道中使用摘要而非完整文件</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>关注基本信息，提高 RAG 性能</p></li><li><p>减少不相关内容的噪音和干扰</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高 LLM 答复的相关性</p></li><li><p>允许在上下文限制内纳入更多文件</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>总结时有可能丢失重要细节</p></li><li><p>生成摘要的额外计算开销</p></li></ul></li></ul><p><strong>6.元数据的纳入</strong></p><ul><li><p>一种用额外的上下文信息来丰富文档的技术。</p></li><li><p><em>元数据类型：</em>  </p><ul><li><p>关键词</p></li><li><p>标题</p></li><li><p>日期</p></li><li><p>作者详细信息</p></li><li><p>简介</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>增加 RAG 系统可用的背景信息</p></li><li><p>让法律硕士更清楚地了解文件内容和相关性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高检索的准确性</p></li><li><p>提高法律硕士评估文件实用性的能力</p></li></ul></li><li><p><em>实施：</em> </p><ul><li><p>可在文件预处理过程中完成</p></li><li><p>可能需要额外的数据提取或生成步骤</p></li></ul></li></ul><p><strong>7.复合多字段嵌入</strong></p><ul><li><p>RAG 系统的高级嵌入技术，可为不同的文档组件创建单独的嵌入。</p></li><li><p><em>过程：</em> </p><ol><li><p>确定相关字段（例如标题、关键词、简介、主要内容）</p></li><li><p>为每个字段生成单独的嵌入</p></li><li><p>合并或存储这些嵌入信息，以用于检索</p></li></ol></li><li><p><em>与标准方法的区别：</em> </p><ul><li><p>传统：对整个文档进行单一嵌入</p></li><li><p>复合：针对不同文档方面的多重嵌入</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>创建更细致入微、更能感知上下文的文档表示法</p></li><li><p>在文件中获取更多来源的信息</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高模糊或多方面查询的性能</p></li><li><p>允许在检索中更灵活地加权不同的文件内容</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>嵌入存储和检索流程的复杂性增加</p></li><li><p>可能需要更复杂的匹配算法</p></li></ul></li></ul><p><strong>8.丰富查询</strong></p><ul><li><p>一种用相关术语扩展原始查询以提高搜索覆盖率的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>分析原始查询</p></li><li><p>生成同义词和语义相关的短语</p></li><li><p>用这些附加术语来扩展查询</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>增加文件语料库中潜在匹配的范围</p></li><li><p>提高使用特定或技术语言查询的检索性能</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>可能检索到与原始查询条件不完全匹配的相关文档</p></li><li><p>有助于克服查询和文档之间的词汇不匹配问题</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>如果不认真执行，则有查询偏移的风险</p></li><li><p>可能会增加检索过程中的计算开销</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 和 LlamaIndex 保护 RAG 中的敏感信息和 PII 信息]]></title>
    <description><![CDATA[如何使用 Elasticsearch 和 LlamaIndex 保护 RAG 应用程序中的敏感数据和 PII 数据。]]></description>
    <content:encoded><![CDATA[<p></p><p></p><p>在本篇文章中，我们将探讨在 RAG（检索增强生成）流程中使用公共 LLM 时保护个人身份信息 (PII) 和敏感数据的方法。我们将探索使用开源库和正则表达式屏蔽 PII 和敏感数据，以及在调用公共 LLM 之前使用本地 LLM 屏蔽数据。</p><p>在开始之前，让我们回顾一下我们在本篇文章中使用的一些术语。</p><h2>术语</h2><p><a href="https://www.llamaindex.ai/">LlamaIndex</a>是用于构建 LLM（大型语言模型）应用程序的领先数据框架。LlamaIndex 为构建 RAG（检索增强生成）应用程序的各个阶段提供了抽象概念。像 LlamaIndex 和 LangChain 这样的框架提供了抽象，因此应用程序不会与任何特定 LLM 的应用程序接口紧密耦合。</p><p><a href="https://www.elastic.co/enterprise-search">Elasticsearch</a>由<a href="https://elastic.co/">Elastic</a> 提供。Elastic 是 Elasticsearch 背后的行业领导者，Elasticsearch 是一个可扩展的数据存储和矢量数据库，支持精确的全文搜索、语义理解的矢量搜索以及两全其美的混合搜索。Elasticsearch 是一个分布式 RESTful 搜索和分析引擎、可扩展数据存储和矢量数据库。我们在本博客中使用的 Elasticsearch 功能在 Elasticsearch 的免费开放版本中提供。</p><p><a href="https://www.promptingguide.ai/techniques/rag">检索增强生成（RAG）</a>是一种人工智能技术/模式，在这种模式下，LLM 可以利用外部知识生成对用户查询的回复。这样，法律硕士的答复就可以根据具体情况量身定做，而不是泛泛而谈。</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/semantic-search.html">嵌入</a>是文本/媒体意义的数字表示。它们是高维信息的低维表示。</p><h2>RAG 和数据保护</h2><p>一般来说，大型语言模型（LLM）善于根据模型中的可用信息生成响应，而这些信息可能是在互联网数据上训练出来的。然而，对于那些模型中没有信息的查询，则需要向法律硕士提供模型中没有的外部知识或具体细节。这些信息可能存在于您的数据库或内部知识系统中。检索增强生成（RAG）是一种技术，对于给定的用户查询，首先从外部（LLM）系统（如数据库）检索相关的上下文/信息，然后将上下文与用户查询一起发送给 LLM，以生成更具体、更相关的响应。</p><p>这使得 RAG 技术在问题解答、内容创建以及任何有利于深入理解上下文和细节的应用中都非常有效。</p><p>因此，在 RAG 管道中，您有可能将 PII（个人身份信息）等内部信息和敏感信息（如姓名、出生日期、账号等）暴露给公共 LLM。</p><p>虽然在使用 Elasticsearch 等矢量数据库时，数据是安全的（通过各种杠杆，如<a href="https://www.elastic.co/guide/en/cloud-enterprise/current/ece-configure-rbac.html">基于角色的访问控制</a>、<a href="https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search">文档级安全</a>等），但在向外部公共 LLM 发送数据时必须小心谨慎。</p><p>在使用大型语言模型 (LLM) 时，出于多种原因，保护个人身份信息 (PII) 和敏感数据至关重要：</p><ul><li><p><strong>隐私合规</strong>：许多地区都有严格的法规，如欧洲的《通用数据保护条例》（GDPR）或美国的《加利福尼亚消费者隐私法》（CCPA），这些法规都要求保护个人数据。要避免法律后果和罚款，就必须遵守这些法律。</p></li><li><p><strong>用户信任</strong>：确保敏感信息的保密性和完整性可建立用户信任。用户更愿意使用他们认为能保护其隐私的系统，并与之互动。</p></li><li><p><strong>数据安全</strong>：防止数据泄露至关重要。如果没有足够的保障措施，暴露在法律硕士面前的敏感数据很容易被窃取或滥用，从而导致身份被盗或金融欺诈等潜在危害。</p></li><li><p><strong>道德方面的考虑</strong>：从道德角度讲，尊重用户隐私并负责任地处理他们的数据非常重要。对 PII 处理不当会导致歧视、侮辱或其他负面社会影响。</p></li><li><p><strong>企业声誉</strong>：未能保护敏感数据的公司可能会声誉受损，这可能会对其业务造成长期负面影响，包括失去客户和收入。</p></li><li><p><strong>减少滥用风险</strong>：安全处理敏感数据有助于防止对数据或模型的恶意使用，例如在有偏见的数据上训练模型，或利用数据操纵或伤害个人。</p></li></ul><p>总之，为了确保法律合规、维护用户信任、确保数据安全、坚持道德标准、保护企业声誉和降低滥用风险，必须对 PII 和敏感数据进行强有力的保护。</p><h2>快速回顾</h2><p>在<a href="https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch">上一篇文章</a>中，我们讨论了如何使用 RAG 技术，将 Elasticsearch 作为向量数据库，同时使用 LlamaIndex 和本地运行的 Mistral LLM 来实现 Q&amp;A 体验。在此基础上，我们将继续努力。</p><p>阅读上一篇文章是可有可无的，因为我们现在将快速讨论/复述上一篇文章中的内容。</p><p>我们有一个样本数据集，内容是一家虚构的家庭保险公司的座席人员与客户之间的呼叫中心对话。我们开发了一个简单的 RAG 应用程序，可以回答 "客户因哪些与水有关的问题而提出索赔 "等问题。</p><p>从高度上看，流程是这样的。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3882da43bfdac0/6a17050867045bd5fe45c0e9/9d51295472f8bcca3d1973248acb724f8b94767e-1054x555.png" alt="RAG 流程" /><p>在索引阶段，我们使用 LlamaIndex 管道加载并索引文档。文档被分块并连同其嵌入一起存储在 Elasticsearch 向量数据库中。</p><p>在查询阶段，当用户提出问题时，LlamaIndex 会检索与查询相关的前 K 个相似文档。这些排名前 K 位的相关文档连同查询结果被发送到本地运行的 Mistral LLM，然后由其生成响应并发送回用户。请随时查看上一篇文章或<a href="https://github.com/srikanthmanvi/RAG-InsuranceCompany/tree/main">探索代码</a>。</p><p>在上一篇文章中，我们在本地运行了 LLM。不过，在生产过程中，您可能希望使用<a href="https://openai.com/">OpenAI</a>、<a href="https://mistral.ai/">Mistral</a>、<a href="https://www.anthropic.com/claude">Anthropic</a>等公司提供的外部 LLM。这可能是因为您的使用案例需要一个更大的基础模型，或者由于企业生产的需要（如可扩展性、可用性、性能等），在本地运行不是一种选择。</p><p>在 RAG 管道中引入外部 LLM 会使您面临不慎将敏感和 PII 泄露给 LLM 的风险。在本篇文章中，我们将探讨如何在将文档发送给外部法律硕士之前，将 PII 信息屏蔽作为 RAG 管道的一部分。</p><h2>持有公共法学硕士学位的 RAG</h2><p>在讨论如何在 RAG 管道中保护 PII 和敏感信息之前，我们将首先使用 LlamaIndex、Elasticsearch 向量数据库和 OpenAI LLM 构建一个简单的 RAG 应用程序。</p><h3>准备工作</h3><p>我们需要以下材料</p><ul><li><p>运行<strong>Elasticsearch</strong>作为向量数据库来存储嵌入。按照上一篇文章中关于<a href="https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch#install-elasticsearch">安装 Elasticsearch</a> 的说明进行操作。</p></li><li><p>开放人工智能应用程序接口密钥。</p></li></ul><h3>简单的 RAG 应用</h3><p>整个代码可在<a href="https://github.com/srikanthmanvi/RAG-InsuranceCompany/tree/protecting-pii">Github Repository</a>（branch:protection-pii）中找到，以供参考。克隆该 repo 是可选的，因为我们将在下文中详细介绍代码。</p><p>在您最喜欢的集成开发环境中，用以下 3 个文件创建一个新的 Python 应用程序。</p><ul><li><p><code>index.py</code> 与索引数据有关的代码的位置。</p></li><li><p><code>query.py</code> 与查询和 LLM 交互相关的代码都放在这里。</p></li><li><p><code>.env</code> 配置属性（如 API 密钥）的位置。</p></li></ul><p>我们需要安装一些软件包。首先，我们要在应用程序的根文件夹中创建一个新的 python<a href="https://docs.python.org/3/library/venv.html">虚拟环境</a>。</p>python3 -m venv .venv
<p>激活虚拟环境并安装以下所需软件包。</p>source .venv/bin/activate
pip install llama-index 
pip install llama-index-embeddings-openai
pip install llama-index-vector-stores-elasticsearch
pip install sentence-transformers
pip install python-dotenv
pip install openai
<p>在 .env 中配置 OpenAI 和 Elasticsearch 连接属性锉刀</p>OPENAI_API_KEY="REPLACEME"
ELASTIC_CLOUD_ID="REPLACEME"
ELASTIC_API_KEY="REPLACEME"
<h4>索引数据</h4><p>下载<a href="https://github.com/srikanthmanvi/RAG-InsuranceCompany/blob/main/conversations.json">conversations.json</a>文件，其中包含客户与我们虚构的房屋保险公司呼叫中心座席之间的<em>对话</em>。将该文件与 2 个 python 文件和 .env 文件一起放在应用程序的根目录中。文件。下面是该文件内容的示例。</p>{
"conversation_id": 103,
"customer_name": "Sophia Jones",
"agent_name": "Emily Wilson",
"policy_number": "JKL0123",
"conversation": "Customer: Hi, I'm Sophia Jones. My Date of Birth is November 15th, 1985, Address is 303 Cedar St, Miami, FL 33101, and my Policy Number is JKL0123.\nAgent: Hello, Sophia. How may I assist you today?\nCustomer: Hello, Emily. I have a question about my policy.\nCustomer: There's been a break-in at my home, and some valuable items are missing. Are they covered?\nAgent: Let me check your policy for coverage related to theft.\nAgent: Yes, theft of personal belongings is covered under your policy.\nCustomer: That's a relief. I'll need to file a claim for the stolen items.\nAgent: We'll assist you with the claim process, Sophia. Is there anything else I can help you with?\nCustomer: No, that's all for now. Thank you for your assistance, Emily.\nAgent: You're welcome, Sophia. Please feel free to reach out if you have any further questions or concerns.\nCustomer: I will. Have a great day!\nAgent: You too, Sophia. Take care.",
"summary": "A customer inquires about coverage for stolen items after a break-in at home, and the agent confirms that theft of personal belongings is covered under the policy. The agent offers assistance with the claim process, resulting in the customer expressing relief and gratitude."
}
<p>在<code>index.py</code> 中粘贴下面的代码，该代码负责索引数据。</p># index.py
# pip install sentence-transformers
# pip install llama-index-embeddings-openai
# pip install llama-index-embeddings-huggingface

import json
import os
from dotenv import load_dotenv
from llama_index.core import Document
from llama_index.core import Settings
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.vector_stores.elasticsearch import ElasticsearchStore


def get_documents_from_file(file):
   """Reads a json file and returns list of Documents"""

   with open(file=file, mode='rt') as f:
       conversations_dict = json.loads(f.read())

   # Build Document objects using fields of interest.
   documents = [Document(text=item['conversation'],
                         metadata={"conversation_id": item['conversation_id']})
                for
                item in conversations_dict]
   return documents

# Load .env file contents into env
load_dotenv('.env')
Settings.embed_model = HuggingFaceEmbedding(
   model_name="BAAI/bge-small-en-v1.5"
)

def main():
   # ElasticsearchStore is a VectorStore that
   # takes care of Elasticsearch Index and Data management.
   es_vector_store = ElasticsearchStore(index_name="convo_index",
                                        vector_field='conversation_vector',
                                        text_field='conversation',
                                        es_cloud_id=os.getenv("ELASTIC_CLOUD_ID"),
                                        es_api_key=os.getenv("ELASTIC_API_KEY"))

   # LlamaIndex Pipeline configured to take care of chunking, embedding
   # and storing the embeddings in the vector store.
   llamaindex_pipeline = IngestionPipeline(
       transformations=[
           SentenceSplitter(chunk_size=350, chunk_overlap=50),
           Settings.embed_model
       ],
       vector_store=es_vector_store
   )

   # Load data from a json file into a list of LlamaIndex Documents
   documents = get_documents_from_file(file="conversations.json")
   llamaindex_pipeline.run(documents=documents)
   print(".....Indexing Data Completed.....\n")

if __name__ == "__main__":
   main()
<p>运行上述代码可看到在 Elasticsearch 中创建了一个索引，将嵌入信息存储在名为<code>convo_index</code> 的 Elasticsearch 索引中。</p><p>如果您需要有关 LlamaIndex IngestionPipeline 的解释，请参阅上一篇文章中的<a href="https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch#indexing-data">创建 IngestionPipeline</a> 部分。</p><h4>查询</h4><p>在上一篇文章中，我们使用了本地 LLM 进行<a href="https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch#querying">查询</a>。</p><p>在本篇文章中，我们将使用公共 LLM OpenAI，如下所示。</p># query.py
from llama_index.core import VectorStoreIndex, QueryBundle, Settings
from llama_index.llms.openai import OpenAI
from index import es_vector_store

# Public LLM where we send user query and Related Documents
llm = OpenAI()

index = VectorStoreIndex.from_vector_store(es_vector_store)

# This query_engine, for a given user query retrieves top 10 similar documents from
# Elasticsearch vector database and sends the documents along with the user query to the LLM.
# Note that documents are sent as-is. So any PII/Sensitive data is sent to the LLM.
query_engine = index.as_query_engine(llm, similarity_top_k=10)

query="Give me summary of water related claims that customers raised."
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
result = query_engine.query(bundle)
print(result)
<p>上述代码将打印 OpenAI 的响应如下。</p><p>客户提出了各种与水有关的索赔，包括地下室水渍、水管爆裂、冰雹对屋顶造成的损坏等问题，以及由于未及时通知、维护问题、逐渐磨损和原有损坏等原因造成的拒赔。在每个案例中，客户都对索赔被拒表示沮丧，并寻求对其索赔进行公平的评估和决定。</p><h2>在 RAG 中屏蔽 PII</h2><p>到目前为止，我们所做的工作是将文档原样连同用户查询一起发送给 OpenAI。</p><p>在 RAG 管道中，从矢量存储中检索到相关上下文后，我们有机会在将查询和上下文发送到 LLM 之前屏蔽 PII 和敏感信息。</p><p>在向外部法律硕士发送 PII 信息之前，有多种方法可以掩盖 PII 信息，每种方法都有自己的优点。下面我们来看看其中的一些选择</p><ol><li><p>使用 spacy.io 或<a href="https://microsoft.github.io/presidio/">Presidio</a>（微软维护的开源库）等 NLP 库。</p></li><li><p>使用开箱即用的 LlamaIndex <code>NERPIINodePostprocessor.</code></p></li><li><p>通过 <code>PIINodePostprocessor</code></p></li></ol><p>使用上述任何一种方法实现屏蔽逻辑后，您就可以使用后处理器（您自己定制的后处理器或 LlamaIndex 开箱即用的后处理器）配置 LlamaIndex 的 IngestionPipeline。</p><h3>使用 NLP 库</h3><p>作为 RAG 管道的一部分，我们可以使用 NLP 库屏蔽敏感数据。我们将在本演示中使用 spacy.io 软件包。</p><p>创建一个新文件<code>query_masking_nlp.py</code> 并添加以下代码。</p># query_masking_nlp.py

# pip install spacy
# python3 - m spacy download en_core_web_sm
import re
from typing import List, Optional

import spacy
from llama_index.core import VectorStoreIndex, QueryBundle, Settings
from llama_index.core.postprocessor.types import BaseNodePostprocessor
from llama_index.core.schema import NodeWithScore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai import OpenAI
from index import es_vector_store

# Load the spaCy model
nlp = spacy.load("en_core_web_sm")

# Compile regex patterns for performance
phone_pattern = re.compile(r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b')
email_pattern = re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b')
date_pattern = re.compile(r'\b(\d{1,2}[-/]\d{1,2}[-/]\d{2,4}|\d{2,4}[-/]\d{1,2}[-/]\d{1,2})\b')
dob_pattern = re.compile(
r"(January|February|March|April|May|June|July|August|September|October|November|December)\s(\d{1,2})(st|nd|rd|th),\s(\d{4})")
address_pattern = re.compile(r'\d+\s+[\w\s]+\,\s+[A-Za-z]+\,\s+[A-Z]{2}\s+\d{5}(-\d{4})?')
zip_code_pattern =  re.compile(r'\b\d{5}(?:-\d{4})?\b')
policy_number_pattern = re.compile(r"[A-Z]{3}\d{4}\.$")  # 3 characters followed by 4 digits, in our case e.g XYZ9876

Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

# match = re.match(policy_number_pattern, "XYZ9876")
# print(match)


def mask_pii(text):
   """
   Masks Personally Identifiable Information (PII) in the given
   text using pre-defined regex patterns and spaCy's named entity recognition.
   Args:
       text (str): The input text containing potential PII.
   Returns:
       str: The text with PII masked.
   """

   # Process the text with spaCy for NER
   doc = nlp(text)

   # Mask entities identified by spaCy NER (e.g First/Last Names etc)
   for ent in doc.ents:
       if ent.label_ in ["PERSON", "ORG", "GPE"]:
           text = text.replace(ent.text, '[MASKED]')

   # Apply regex patterns after NER to avoid overlapping issues
   text = phone_pattern.sub('[PHONE MASKED]', text)
   text = email_pattern.sub('[EMAIL MASKED]', text)
   text = date_pattern.sub('[DATE MASKED]', text)
   text = address_pattern.sub('[ADDRESS MASKED]', text)
   text = dob_pattern.sub('[DOB MASKED]', text)
   text = zip_code_pattern.sub('[ZIP MASKED]', text)
   text = policy_number_pattern.sub('[POLICY MASKED]', text)

   return text


class CustomPostProcessor(BaseNodePostprocessor):
   """
   Custom Postprocessor which masks Personally Identifiable Information (PII).
   PostProcessor is called on the Documents before they are sent to the LLM.
   """
   def _postprocess_nodes(
           self, nodes: List[NodeWithScore], query_bundle: Optional[QueryBundle]
   ) -&gt; List[NodeWithScore]:
       # Masks PII
       for n in nodes:
          n.node.set_content(mask_pii(n.text))
       return nodes

   
# Use Public LLM to send user query and Related Documents
llm = OpenAI()
index = VectorStoreIndex.from_vector_store(es_vector_store)

# This query_engine, for a given user query retrieves top 10 similar documents from
# Elasticsearch vector database and sends the documents along with the user query to the LLM.
# Note that documents are masked based on custom logic defined in CustomPostProcessor._postprocess_nodes.
query_engine = index.as_query_engine(llm, similarity_top_k=10, node_postprocessors=[CustomPostProcessor()])



query = "Give me summary of water related claims that customers raised."
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
response = query_engine.query(bundle)
print(response)

<p>法律硕士的答复如下。</p>客户提出了各种与水有关的索赔，包括地下室水渍、水管爆裂、冰雹损坏屋顶以及暴雨期间的洪水等问题。这些索赔导致了基于缺乏及时通知、维护问题、逐渐磨损和预先存在的损坏等原因的拒赔而产生的挫折感。客户对这些拒赔表示失望、压力和经济负担，要求对其索赔进行公平评估和彻底审查。一些客户还面临索赔处理延迟的问题，这进一步引起了对保险公司服务的不满。<p>在上述代码中，当创建 Llama 索引查询引擎时，我们提供了一个 CustomPostProcessor。</p><p>QueryEngine 调用的逻辑在<code>CustomPostProcessor</code> 的<code>_postprocess_nodes</code> 方法中定义。我们正在使用 SpaCy.io 库来检测文件中的命名实体，然后使用一些正则表达式来替换这些名称以及敏感信息，然后再将文件发送到 LLM。</p><p>以下是自定义 PostProcessor 创建的原始会话和屏蔽会话的部分示例。</p><p>原文如此：</p>客户：你好，我是马修-洛佩兹（Matthew Lopez），出生日期是 1984 年 10 月 12 日，住在纽约州斯莫尔敦市雪松街 456 号，邮编 34567。我的保单号码是 TUV8901。探员下午好 马修我今天能为您提供什么帮助？客户：你好，我对贵公司拒绝我索赔的决定感到非常失望。<p>由 CustomPostProcessor 生成的屏蔽文本。</p>顾客：你好，我是 [蒙面]，[蒙面] 是 [生日蒙面]，我住在 34567 [蒙面] [蒙面] 的西达街 456 号。我的保单号码是 [屏蔽]。探员下午好，[蒙面]我今天能为您提供什么帮助？客户：你好，我对贵公司拒绝我索赔的决定感到非常失望。<p>请注意：</p><p><em>识别和屏蔽 PII 和敏感信息并不是一项简单的任务。要涵盖敏感信息的各种格式和语义，就必须充分了解自己的领域和数据。虽然上述代码可能适用于某些使用情况，但您可能需要根据自己的需求和测试情况进行修改。</em></p><h3>使用开箱即用的 LlamaIndex <code>NERPIINodePostprocessor</code></h3><p>LlamaIndex 通过引入以下功能，使保护 RAG 管道中的 PII 信息变得更加容易 <code>NERPIINodePostprocessor.</code></p>from llama_index.core import VectorStoreIndex, QueryBundle, Settings
from llama_index.core.postprocessor import NERPIINodePostprocessor
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.openai import OpenAI
from index import es_vector_store

Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

# Use Public LLM to send user query and Related Documents
llm = OpenAI()

ner_processor = NERPIINodePostprocessor()
index = VectorStoreIndex.from_vector_store(es_vector_store)

# This query_engine, for a given user query retrieves top 10 similar documents from
# Elasticsearch vector database and sends the documents along with the user query to the LLM.
# Note that documents masked using the NERPIINodePostprocessor so that PII/Sensitive data is not sent to the LLM.
query_engine = index.as_query_engine(llm, similarity_top_k=10, node_postprocessors=[ner_processor])

query = "Give me summary of fire related claims that customers raised."
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
response = query_engine.query(bundle)
print(response)
<p>答复如下</p>客户提出了与火灾有关的财产损失索赔。在一个案例中，由于纵火被排除在承保范围之外，车库火灾损失索赔被拒绝。另一位客户就其住宅遭受的火灾损失提出索赔，该损失属于其保单的承保范围。此外，一位客户报告了厨房火灾，并得到了火灾损失赔偿的保证。<h3>通过 <code>PIINodePostprocessor</code></h3><p>我们还可以利用本地或专用网络中运行的 LLM，在将数据发送到公共 LLM 之前完成屏蔽工作。</p><p>我们将使用运行在本地机器 Ollama 上的 Mistral 来进行屏蔽。</p><h4>本地运行 Mistral</h4><p>下载并安装<a href="https://ollama.com/">Ollama</a>。安装 Ollama 后，运行此命令下载并运行<a href="https://ollama.com/library/mistral">mistral</a></p>ollama run mistral
<p>首次下载并在本地运行模型可能需要几分钟时间。通过提出类似下面 "写一首关于云的诗 "的问题来验证 mistral 是否在运行，并验证诗歌是否符合您的要求。保持 ollama 运行，因为我们稍后需要通过代码与 mistral 模型交互。</p><p>新建一个名为<code>query_masking_local_LLM.py</code> 的文件，并添加以下代码。</p># pip install llama-index-llms-ollama
from llama_index.core import VectorStoreIndex, QueryBundle, Settings
from llama_index.core.postprocessor import PIINodePostprocessor
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.ollama import Ollama
from llama_index.llms.openai import OpenAI
from index import es_vector_store

Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

# Use Public LLM to send user query and Related Documents and Local LLM to mask
public_llm = OpenAI()
local_llm = Ollama(model="mistral")

pii_processor = PIINodePostprocessor(llm=local_llm)
index = VectorStoreIndex.from_vector_store(es_vector_store)

# This query_engine, for a given user query retrieves top 10 similar documents from
# Elasticsearch vector database and sends the documents along with the user query to the public LLM.
# Note that documents are masked using the local llm via PIINodePostprocessor
# so that PII/Sensitive data is not sent to the public LLM.
query_engine = index.as_query_engine(public_llm, similarity_top_k=10, node_postprocessors=[pii_processor])


query = "Give me summary of fire related claims that customers raised."
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
result = query_engine.query(bundle)
print(result)
<p>响应如下所示</p>客户提出了与火灾有关的财产损失索赔。在一个案例中，由于纵火被排除在承保范围之外，车库火灾损失索赔被拒绝。另一位客户就其住宅遭受的火灾损失提出索赔，该损失属于其保单的承保范围。此外，一位客户报告了厨房火灾，并得到了火灾损失赔偿的保证。<h3>结论</h3><p>在这篇文章中，我们介绍了在 RAG 流程中使用公共 LLM 时如何保护 PII 和敏感数据。我们展示了实现这一目标的多种方法。强烈建议在采用这些方法之前，根据您的用例和需求对其进行测试。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/rag-security-masking-pii</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/rag-security-masking-pii</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3882da43bfdac0/6a17050867045bd5fe45c0e9/9d51295472f8bcca3d1973248acb724f8b94767e-1054x555.png" length="0" type="image/png"/>
    <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 LlamaIndex、Elasticsearch 和 Mistral 的 RAG（检索增强生成]]></title>
    <description><![CDATA[了解如何使用 LlamaIndex、Elasticsearch 和本地运行的 Mistral 实现 RAG（检索增强生成）系统。]]></description>
    <content:encoded><![CDATA[<p>在本博客中，我们将讨论如何使用 RAG 技术（检索增强生成）和作为向量数据库的 Elasticsearch 来实现 Q&amp;A 体验。我们将使用 LlamaIndex 和本地运行的 Mistral LLM。</p><p>在开始之前，我们先来了解一些术语。</p><h3>术语</h3><p><a href="https://www.llamaindex.ai/">LlamaIndex</a>是用于构建 LLM（大型语言模型）应用程序的领先数据框架。LlamaIndex 为构建 RAG（检索增强生成）应用程序的各个阶段提供了抽象概念。像 LlamaIndex 和 LangChain 这样的框架提供了抽象，因此应用程序不会与任何特定 LLM 的应用程序接口紧密耦合。</p><p><a href="https://www.elastic.co/enterprise-search">Elasticsearch</a>由<a href="https://elastic.co/">Elastic</a> 提供。Elastic 是 Elasticsearch 背后的行业领导者，Elasticsearch 是一个搜索和分析引擎，支持精确的全文搜索、语义理解的矢量搜索以及两全其美的混合搜索。Elasticsearch 是一种可扩展的数据存储和矢量数据库。我们在本博客中使用的 Elasticsearch 功能在 Elasticsearch 的免费开放版本中提供。</p><p><a href="https://www.promptingguide.ai/techniques/rag">检索增强生成（RAG）</a>是一种人工智能技术/模式，它为 LLM 提供外部知识，以生成对用户查询的回复。这样，法律硕士的答复就可以根据具体情况量身定做，答复也更加具体。</p><p><a href="https://docs.mistral.ai/">Mistral</a>提供开源和优化的企业级 LLM 模型。在本教程中，我们将使用可在笔记本电脑上运行的开源模型<a href="https://docs.mistral.ai/models/#mistral-7b">mistral-7b。</a>如果你不想在笔记本电脑上运行模型，也可以使用他们的云版本，在这种情况下，你必须修改本博客中的代码，以使用正确的 API 密钥和软件包。</p><p><a href="https://ollama.com/">Ollama</a>可帮助您在笔记本电脑上本地运行 LLM。我们将使用 Ollama 在本地运行开源的 Mistral-7b 模型。</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.13/semantic-search.html">嵌入</a>是文本/媒体意义的数字表示。它们是高维信息的低维表示。</p><h3>使用 LlamaIndex、Elasticsearch&amp; Mistral 构建 RAG 应用程序：场景概述</h3><p><strong>场景</strong></p><p>我们有一个样本数据集（JSON 文件），内容是一家虚构的家庭保险公司的座席人员与客户之间的呼叫中心对话。我们将建立一个简单的 RAG 应用程序，它可以回答以下问题</p><p><code>Give me summary of water related issues.</code></p><h3>高位流量</h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3882da43bfdac0/6a17050867045bd5fe45c0e9/9d51295472f8bcca3d1973248acb724f8b94767e-1054x555.png" alt="RAG 流程" /><p>我们使用 Ollama 在本地运行 Mistral LLM。</p><p>接下来，我们以<code>Documents</code> 的形式将 JSON 文件中的<em>对话</em>加载到<a href="https://developers.llamaindex.ai/python/examples/vector_stores/elasticsearchindexdemo/">ElasticsearchStore</a>（由 Elasticsearch 支持的 VectorStore）中。在加载文档时，我们使用本地运行的 Mistral 模型创建嵌入。我们将这些嵌入和<em>对话</em>一起存储在 LlamaIndex Elasticsearch 向量<a href="https://developers.llamaindex.ai/python/examples/vector_stores/elasticsearchindexdemo/">存储（ElasticsearchStore</a>）中。</p><p>我们配置一个 LlamaIndex IngestionPipeline，并向其提供我们使用的本地 LLM，在本例中是通过 Ollama 运行的 Mistral。</p><p>当我们提出 "请简要介绍与水有关的问题 "这样的问题时、Elasticsearch 可进行语义搜索，并返回与水问题有关的<em>对话</em>。这些<em>对话</em>与原始问题一起发送给本地运行的 LLM，以生成答案。</p><h3>建立 RAG 应用程序的步骤</h3><h4>本地运行 Mistral</h4><p>下载并安装<a href="https://ollama.com/">Ollama</a>。安装 Ollama 后，运行此命令下载并运行<a href="https://ollama.com/library/mistral">mistral</a></p>ollama run mistral
<p>首次下载并在本地运行模型可能需要几分钟时间。通过提出类似下面 "写一首关于云的诗 "的问题来验证 mistral 是否在运行，并验证诗歌是否符合您的要求。保持 ollama 运行，因为我们稍后需要通过代码与 mistral 模型交互。</p><h4>安装 Elasticsearch</h4><p>通过创建云部署<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">（在此说明</a>）或在 docker 中运行<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/docker">（在此说明</a>），启动并运行 Elasticsearch。您也可以从<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/docker#self-hosted-production-deployments">这里</a>开始创建生产级的 Elasticsearch 自托管部署。</p><p>假设您使用的是云部署，请按照说明中的要求获取部署的 API 密钥和云 ID。我们稍后将使用它们。</p><h4>RAG 申请</h4><p>整个代码可在此<a href="https://github.com/srikanthmanvi/RAG-InsuranceCompany">Github 代码库中</a>找到，以供参考。克隆 repo 是可选项，因为我们将在下文中介绍代码。</p><p>在您最喜欢的集成开发环境中，用以下 3 个文件创建一个新的 Python 应用程序。</p><ul><li><p><code>index.py</code> 与索引数据有关的代码的位置。</p></li><li><p><code>query.py</code> 与查询和 LLM 交互相关的代码都放在这里。</p></li><li><p><code>.env</code> 配置属性（如 API 密钥）的位置。</p></li></ul><p>我们需要安装一些软件包。首先，我们要在应用程序的根文件夹中创建一个新的 python<a href="https://docs.python.org/3/library/venv.html">虚拟环境</a>。</p>python3 -m venv .venv
<p>激活虚拟环境并安装以下所需软件包。</p>source .venv/bin/activate
pip install llama-index 
pip install llama-index-embeddings-ollama
pip install llama-index-llms-ollama
pip install llama-index-vector-stores-elasticsearch
pip install sentence-transformers
pip install python-dotenv
<h4>索引数据</h4><p>下载<a href="https://github.com/srikanthmanvi/RAG-InsuranceCompany/blob/main/conversations.json">conversations.json</a>文件，其中包含客户与我们 fictionaly 房屋保险公司呼叫中心座席之间的<em>对话</em>。将该文件与 2 个 python 文件和 .env 文件一起放在应用程序的根目录中。文件。下面是该文件内容的示例。</p>{
    "conversation_id": 103,
    "customer_name": "Sophia Jones",
    "agent_name": "Emily Wilson",
    "policy_number": "JKL0123",
    "conversation": "Customer: Hi, I'm Sophia Jones. My Date of Birth is November 15th, 1985, Address is 303 Cedar St, Miami, FL 33101, and my Policy Number is JKL0123.\nAgent: Hello, Sophia. How may I assist you today?\nCustomer: Hello, Emily. I have a question about my policy.\nCustomer: There's been a break-in at my home, and some valuable items are missing. Are they covered?\nAgent: Let me check your policy for coverage related to theft.\nAgent: Yes, theft of personal belongings is covered under your policy.\nCustomer: That's a relief. I'll need to file a claim for the stolen items.\nAgent: We'll assist you with the claim process, Sophia. Is there anything else I can help you with?\nCustomer: No, that's all for now. Thank you for your assistance, Emily.\nAgent: You're welcome, Sophia. Please feel free to reach out if you have any further questions or concerns.\nCustomer: I will. Have a great day!\nAgent: You too, Sophia. Take care.",
    "summary": "A customer inquires about coverage for stolen items after a break-in at home, and the agent confirms that theft of personal belongings is covered under the policy. The agent offers assistance with the claim process, resulting in the customer expressing relief and gratitude."
}
<p>我们在<code>index.py</code> 中定义了一个名为<code>get_documents_from_file</code> 的函数，用于读取 json 文件并创建文档列表。<a href="https://docs.llamaindex.ai/en/stable/module_guides/loading/documents_and_nodes/">文档</a>对象是 LlamaIndex 处理信息的基本单位。</p># index.py
import json, os
from llama_index.core import Document, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.vector_stores.elasticsearch import ElasticsearchStore
from dotenv import load_dotenv

def get_documents_from_file(file):
   """Reads a json file and returns list of Documents"""

   with open(file=file, mode='rt') as f:
       conversations_dict = json.loads(f.read())
      
   # Build Document objects using fields of interest.
   documents = [Document(text=item['conversation'],
                         metadata={"conversation_id": item['conversation_id']})
                for
                item in conversations_dict]
   return documents
<p>创建摄取管道</p><p>首先，将在<code>Install Elasticsearch</code> 部分获得的 Elasticsearch CloudID 和 API 密钥添加到<code>.env</code> 文件中。<code>.env</code> 文件应如下所示（使用真实值）。</p>ELASTIC_CLOUD_ID=&lt;REPLACE WITH YOUR CLOUD ID&gt;
ELASTIC_API_KEY=&lt;REPLACE WITH YOUR API_KEY&gt;
<p>通过 LlamaIndex<a href="https://docs.llamaindex.ai/en/stable/module_guides/loading/ingestion_pipeline/">IngestionPipeline</a>，您可以使用多个组件组成一个管道。在<code>index.py</code> 文件中添加以下代码。</p># index.py

# Load .env file contents into env
# ELASTIC_CLOUD_ID and ELASTIC_API_KEY are expected to be in the .env file.
load_dotenv('.env')

# ElasticsearchStore is a VectorStore that
# takes care of ES Index and Data management.
es_vector_store = ElasticsearchStore(index_name="calls",
                                     vector_field='conversation_vector',
                                     text_field='conversation',
                                     es_cloud_id=os.getenv("ELASTIC_CLOUD_ID"),
                                     es_api_key=os.getenv("ELASTIC_API_KEY"))


def main():
    # Embedding Model to do local embedding using Ollama.
    ollama_embedding = OllamaEmbedding("mistral")

    # LlamaIndex Pipeline configured to take care of chunking, embedding
    # and storing the embeddings in the vector store.
    pipeline = IngestionPipeline(
        transformations=[
            SentenceSplitter(chunk_size=350, chunk_overlap=50),
            ollama_embedding,
        ],
        vector_store=es_vector_store
    )

    # Load data from a json file into a list of LlamaIndex Documents
    documents = get_documents_from_file(file="conversations.json")

    pipeline.run(documents=documents)
    print(".....Done running pipeline.....\n")


if __name__ == "__main__":
    main()

<p>如前所述，LlamaIndex IngestPipeline 可由多个组件组成。我们将在<code>pipeline = IngestionPipeline(...</code> 行的管道中添加 3 个组件。</p><ul><li><p><a href="https://docs.llamaindex.ai/en/stable/module_guides/loading/node_parsers/modules/?h=sentencesp#sentencesplitter">SentenceSplitter</a>：从<code>get_documents_from_file()</code> 的定义中可以看出，每个文档都有一个文本字段，用来保存 json 文件中的对话内容。该文本字段是一段较长的文本。为了使语义搜索工作顺利进行，需要将其分解成小块文本。<a href="https://docs.llamaindex.ai/en/stable/module_guides/loading/node_parsers/modules/?h=sentencesp#sentencesplitter">SentenceSplitter</a>类可以帮我们做到这一点。这些块在 LlamaIndex 术语中称为节点。节点中的元数据指向它们所属的文档。或者，也可以使用 Elasticsearch Ingestpipeline 进行分块，如本<a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">博客</a>所示。</p></li><li><p><a href="https://docs.llamaindex.ai/en/stable/module_guides/models/embeddings/">OllamaEmbedding</a>：嵌入模型可将一段文字转换成数字（也称为向量）。有了数字表示法，我们就可以进行<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">语义搜索</a>，搜索结果与词义相匹配，而不仅仅是进行文本搜索。我们为 IngestionPipeline 提供<code>OllamaEmbedding("mistral")</code> 。使用 SentenceSplitter 分割的语块会通过 Ollama 发送到本地机器上运行的 Mistral 模型，然后 mistral 会为这些语块创建嵌入。</p></li><li><p><a href="https://developers.llamaindex.ai/python/examples/vector_stores/elasticsearchindexdemo/">ElasticsearchStore</a>LlamaIndex ElasticsearchStore 向量存储会将正在创建的嵌入式内容备份到 Elasticsearch 索引中。ElasticsearchStore 负责创建和填充指定 Elasticsearch 索引的内容。在创建 ElasticsearchStore（由<code>es_vector_store</code> 引用）时，我们提供要创建的 Elasticsearch 索引的名称（本例中为<code>calls</code> ）、索引中要存储嵌入的字段（本例中为<code>conversation_vector</code> ）以及要存储文本的字段（本例中为<code>conversation</code> ）。总之，根据我们的配置<code>ElasticsearchStore</code> 在 Elasticsearch 中创建了一个新索引，并将<code>conversation_vector</code> 和<code>conversation</code> 作为字段（以及其他自动创建的字段）。</p></li></ul><p>将这一切联系起来，我们通过调用<code>pipeline.run(documents=documents)</code> 运行管道。</p><p>运行 index.py 脚本执行摄取管道：</p>python index.py
<p>管道运行完成后，我们应该会在 Elasticsearch 中看到一个名为<code>calls</code> 的新索引。使用开发控制台运行一个简单的 elasticsearch 查询，就能看到数据和嵌入式内容一起加载。</p>GET calls/_search?size=1
<p>概括地说，我们从 JSON 文件创建了文档，将文档分割成块，为这些块创建了嵌入，并将嵌入（和文本对话）存储在向量存储（ElasticsearchStore）中。</p><h4>查询</h4><p>llamaIndex<a href="https://docs.llamaindex.ai/en/stable/module_guides/indexing/vector_store_guide/">VectorStoreIndex</a>可以让你检索相关文档和查询数据。默认情况下，VectorStoreIndex 会将嵌入存储在<a href="https://docs.llamaindex.ai/en/stable/module_guides/indexing/vector_store_guide/">SimpleVectorStore</a> 的内存中。不过，也可以使用外部向量存储（如<a href="https://developers.llamaindex.ai/python/examples/vector_stores/elasticsearchindexdemo/">ElasticsearchStore</a>）来使嵌入持久化。</p><p>打开<code>query.py</code> 并粘贴以下代码</p># query.py
from llama_index.core import VectorStoreIndex, QueryBundle, Response, Settings
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.llms.ollama import Ollama
from index import es_vector_store

# Local LLM to send user query to
local_llm = Ollama(model="mistral")
Settings.embed_model= OllamaEmbedding("mistral")

index = VectorStoreIndex.from_vector_store(es_vector_store)
query_engine = index.as_query_engine(local_llm, similarity_top_k=10)

query="Give me summary of water related issues"
bundle = QueryBundle(query, embedding=Settings.embed_model.get_query_embedding(query))
result = query_engine.query(bundle)
print(result)
<p>我们定义了一个本地 LLM (<code>local_llm</code>) 来指向在 Ollama 上运行的 Mistral 模型。接下来，我们从之前创建的 ElasticssearchStore 向量存储中创建一个 VectorStoreIndex (<code>index</code>)，然后从索引中获取一个查询引擎。在创建查询引擎时，我们会引用本地 LLM 来进行响应，我们还会提供 (<code>similarity_top_k=10</code>) 来配置应从向量存储中检索并发送给 LLM 以获得响应的文档数量。</p><p>运行<code>query.py</code> 脚本执行 RAG 流程：</p>python query.py
<p>我们发送查询<code>Give me summary of water related issues</code> （可随意定制<code>query</code> ），法律硕士的回复应与提供的相关文件类似。</p>在所提供的背景下，我们看到客户询问与水有关的损害的承保范围。在两个案例中，洪水对地下室造成了破坏，在另一个案例中，屋顶漏水也是问题所在。代理商确认，这两种水渍都在他们各自的保险范围内。因此，与水有关的问题，包括洪水和屋顶漏水，通常都在房屋保险的承保范围之内。<h4>注意事项</h4><p>这篇博文是关于使用 Elasticsearch 的 RAG 技术的初学者介绍，因此省略了一些功能配置，而这些功能配置将使您能够将这一起点应用到生产中。在构建生产用例时，您需要考虑更复杂的方面，如使用<a href="https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search">文档级安全</a>保护数据，将数据分块作为 Elasticsearch<a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">Ingest 管道</a>的一部分，甚至在用于 GenAI/Chat/Q&amp;A 用例的相同数据上运行其他<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-overview.html">ML 作业</a>。</p><p>您还可以考虑使用<a href="https://www.elastic.co/guide/en/enterprise-search/current/connectors.html">Elastic Connectors</a> 从各种外部来源（如 Azure Blob Storage、Dropbox、Gmail 等）获取数据并创建嵌入。</p><p>Elastic 可实现上述所有功能，并为 GenAI 用例及其他应用提供全面的企业级解决方案。</p><h4>接下来呢？</h4><ul><li><p>您可能已经注意到，我们正在将 10 个相关会话与用户问题一起发送给 LLM，以便制定回复。这些对话可能包含 PII（个人身份信息），如姓名、出生日期、地址等。在我们的案例中，LLM 是本地的，因此数据泄漏不是问题。但是，如果您想使用在云中运行的 LLM（例如 OpenAI），则不宜发送包含 PII 信息的文本。在后续博客中，我们将介绍如何在向 RAG 流程中的外部 LLM 发送 PII 信息之前完成屏蔽。</p></li><li><p>在本篇文章中，我们使用了本地 LLM，在接下来的文章 "在 RAG 中屏蔽 PII 数据 "中，我们将介绍如何轻松地从本地 LLM 切换到公共 LLM。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/rag-with-llamaIndex-and-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3882da43bfdac0/6a17050867045bd5fe45c0e9/9d51295472f8bcca3d1973248acb724f8b94767e-1054x555.png" length="0" type="image/png"/>
    <pubDate>Fri, 12 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>