<?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[在 Elastic 内部 - 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[在 Elastic 内部 - 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/inside-elastic</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/inside-elastic</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/inside-elastic.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 01:24:03 GMT</lastBuildDate>
  <item>
    <title><![CDATA[我们如何构建 Elasticsearch simdvec，使其成为世界上速度最快的向量搜索之一]]></title>
    <description><![CDATA[我们如何打造 Elasticsearch simdvec——这是 Elasticsearch 中每一次向量搜索查询背后的手动调优 SIMD 内核库。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch simdvec 是 Elasticsearch 中每一次向量距离计算的核心引擎。它为 Elasticsearch 支持的每一种向量类型提供手动调优的 AVX-512 和 NEON 内核。其批量评分架构通过 x86 上的显式预取和 ARM 上的交错加载来隐藏内存延迟；当数据规模超出 CPU 缓存容量时，性能最高可比 FAISS 和 jvector 等库快 4 倍。在本文中，我们将介绍为何要打造它、其内部构成，以及它如何让 Elasticsearch 向量搜索跻身全球最快之列。</p><h2>我们如何构建 Elasticsearch simdvec</h2><p>Elasticsearch 中的每一次向量搜索查询，无论是<a href="https://arxiv.org/abs/1603.09320">分层导航小世界 (HNSW)</a> 遍历、倒排文件 (IVF) 扫描，还是重排序阶段，最终都会归结为同一个问题：在一次查询中，数百万次计算向量之间的距离。Elasticsearch 支持广泛的数据类型和量化策略，从 float32 到 int8、bfloat16、binary 以及 Better Binary Quantization (BBQ)。每种类型都在内存、吞吐量和召回率之间形成不同取舍。而这一切背后都有一个统一的引擎：simdvec。</p><p>我们打造 simdvec，是为了让每一次距离计算都尽可能逼近硬件允许的性能上限。在本文中，我们将介绍为何要打造它、它的内部构成，以及它在哪些场景中影响最大。</p><h3>设计得如同一辆赛车</h3><p>作为一级方程式赛车 (F1) 爱好者（我们其中一人曾效力于法拉利 F1 车队），我们看到了一个清晰的相似之处。F1 赛车的设计只有一个目标：取得最佳单圈成绩。发动机功率、空气动力学和底盘设计之所以重要，是因为它们都服务于这一结果。向量数据库也是如此：索引吞吐量、查询延迟和召回率决定了成功与否。</p><p>最终结果固然重要，但要达到最高性能水平，就需要每个组件都做到极致。它不能只是<em>“足够好”</em>，而必须是同类中的<em>“最佳”</em>。simdvec 正是以这种思路打造的，聚焦于系统中的一个关键部分：引擎。它是一个专门构建、针对<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">单指令多数据</a> (SIMD) 优化的内核库，提供手动调优的原生 C++ 距离函数，并通过 <a href="https://openjdk.org/projects/panama/">Panama</a> 外部函数接口 (FFI) 从 Java 调用这些函数。它支持批量评分、缓存行预取，以及 Elasticsearch 中使用的所有向量类型和布局。</p><p>这就是每个查询背后的引擎。</p><h3>为什么我们要自研</h3><p>我们在 2023 年从 Apache Lucene 中的 Panama Vector API 起步。它在处理 float32 点积时表现良好，但 Elasticsearch 的需求很快就超出了它的能力范围。Elasticsearch 需要支持一系列量化向量类型：int8、int4、bfloat16、单比特以及非对称 BBQ。每种类型都有不同的 SIMD 策略、打包布局和累加器要求。除了类型覆盖范围之外，Elasticsearch 的评分路径还需要的不只是单对向量吞吐量：HNSW 需要在一次过程中为多个图邻居评分，IVF 需要对数千个候选项进行带预取的批量评分，而基于磁盘的评分需要直接在 mmap 映射内存上工作，实现零拷贝。我们考察了现有方案，发现没有一个能完全满足这些要求。</p><p>于是，我们打造了 simdvec：通过 FFI 从 Java 调用手动调优的原生 C++ 内核，具备批量评分和预取能力，并支持 Elasticsearch 使用的每一种向量类型。通过自有这个库，我们可以控制完整技术栈。当我们添加 BBQ 这样的新量化类型时，它会获得经过调优的 SIMD 内核，并完整接入整个系统。我们无需等待上游库支持它，也无需在任何类型的性能上做出妥协。Elasticsearch 中的每一次向量查询——无论是 HNSW、IVF、重排序还是混合检索——都运行在这个引擎之上。这个引擎正是围绕我们实际使用的操作和类型量身打造的。</p><p>simdvec 针对 x86 和 ARM 分别提供原生库，每种库都有多个指令集架构 (ISA) 层级，并在启动时选择。通过 FFI 从 Java 调用的开销非常低，仅为<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#ffm-downcall-overhead-measurements">个位数纳秒级</a>。</p><h3>技术格局</h3><p>我们并不是唯一在打造 SIMD 优化向量距离内核的团队。这个生态系统非常丰富，我们希望了解 simdvec 的表现。这并非为了给项目排名，而是为了提供上下文，说明 Elasticsearch 的引擎处在什么位置。我们选择了三个项目作为参考点，每个代表一种不同的技术路径：</p><ul><li><p><strong>jvector：</strong>一个 Java 近似最近邻 (ANN) 库，使用 Panama Vector API 进行向量化距离计算，并在 x86 上提供可选的原生 C 加速。</p></li><li><p><strong>FAISS：</strong>一个广泛部署的开源矢量搜索框架，带有手动调整的 AVX2/AVX-512 内核。</p></li><li><p><strong>NumKong</strong>（原 SimSIMD）：一个包含 2,000 多个手动调优 SIMD 内核的综合库，覆盖距离函数、矩阵运算和地理空间计算。</p></li></ul><p>每个项目服务于不同的目标，有着不同的取舍。我们引用它们的参考数据，是为了给 simdvec 在 Elasticsearch 所需特定操作上的性能提供参照。</p><h3>我们如何衡量</h3><p>simdvec 和 <a href="https://github.com/ChrisHegarty/jvector-kernel-benchmarks">jvector 基准测试</a>使用 Java 编写，并采用 JMH（标准 JVM 微基准测试框架），测试中包含 FFI 开销。对于 <a href="https://github.com/ldematte/simsimd-benchmarks">NumKong 基准测试</a>和 <a href="https://github.com/ChrisHegarty/faiss-kernel-benchmarks">FAISS 基准测试</a>，我们使用 Google Benchmark（标准 C++ 微基准测试框架）编写了小型 C/C++ 测试程序。两个框架都会在预热和迭代校准后报告每次操作所需的纳秒数。我们通过硬件性能计数器验证了所有库在两个平台上都确实使用了 SIMD。所有基准测试代码均已公开在链接的 GitHub 存储库中；对于 simdvec，代码位于 <a href="https://github.com/elastic/elasticsearch">elasticsearch</a> 存储库中。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90a7d66553be3c9d/6a1701d166c4f942caf8bea5/aee116772df161cf86b7668f575ac34c733a23c5-1580x238.png" alt="表格列出两个平台：x86（AMD EPYC Turin Zen 5，AVX2 和 AVX-512，AWS c8a.4xlarge）；ARM（Graviton 4 Neoverse V2，NEON 和 SVE2，AWS c8g.4xlarge）。" /><p><strong>软件：</strong>JDK 25.0.2、JMH 1.37、GCC 14、Google Benchmark（最新版）。</p><h2>一次处理一个向量</h2><p>向量搜索中最基础的操作是计算两个向量之间的距离。每一次 HNSW 邻居评估、每一次 IVF 候选项评分、每一次重排序比较，都会归结为这个内层循环。</p><p>我们在两个平台上测量了 1024 维下的单对向量吞吐量，首先从 float32 开始。这是基准类型，也是生态系统中竞争最激烈的类型。我们将 simdvec 与 FAISS 和 jvector 进行了对比；我们排除了 NumKong，因为它在 float32 上使用 float64 累加器，速度慢 3.2 到 5.3 倍（取决于平台），这是以吞吐量换取数值精度。为了保持同类对比，我们改为在 int8 上测试 NumKong，因为此时它采用的累加器策略与 simdvec 相同。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3bc2ecaf3ad2b61/6a1701d2ab7f08039ddb9d0f/352cfa0fc18f123140f746d843404f80127fb1b7-1500x675.png" alt="横向条形图，标题为“float32 点积 — AMD Turin”，比较了五个实现：FAISS AVX-512，23.2 ns/op；ES simdvec AVX-512，28.3 ns/op；FAISS AVX2，36.4 ns/op；ES simdvec AVX2，38.9 ns/op；以及 jvector，43.9 ns/op。" /><p>在 x86 上，FAISS AVX-512 是最快的单对内核，耗时 23 ns。simdvec AVX-512 紧随其后，为 28 ns，这一差距反映了 FFI 调用开销。两者都使用 512 位 FMA，并采用多累加器展开。在 AVX2 层级，两者更接近，分别为 36 ns 和 39 ns，都受限于 256 位寄存器和内存加载宽度。jvector 使用 Java Panama Vector API，耗时 44 ns。Panama 能生成良好的 SIMD 代码，但手动调优的 C++ 内部函数仍然具有优势。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbbbe442631a575d/6a1701d42b835f81bcf4b083/95e44d9767f21ec4bccaed835e3c99aa180431ee-1500x495.png" alt="题为“float32 点积－Graviton 4 (ARM)”的横条形图显示 ES simdvec 为 70.2 ns/op，jvector 为 110.0 ns/op，FAISS 为 155.6 ns/op。" /><p>在 ARM 上，simdvec 以 70 ns 领先，明显快于 110 ns 的 jvector 和 156 ns 的 FAISS。simdvec 针对 aarch64 提供手动调优的 NEON 内核。jvector 没有 ARM 原生代码，依赖 Panama。FAISS 依赖编译器自动向量化，而非显式的 NEON 内部函数，这也解释了更大的性能差距。这体现了拥有自有内核库的一个实际优势：当 Elasticsearch 扩展到 Graviton 时，我们添加了专门构建的 NEON 内核。而 jvector 和 FAISS 尚未以同等程度优先投入 ARM 原生代码。</p><p>但 Elasticsearch 评分的远不止 float32。<strong>int8</strong> 量化可将内存占用降至原来的四分之一，bfloat16 降至原来的一半，BBQ 降至原来的三十二分之一。每种类型都需要自己的 SIMD 策略，而 simdvec 为所有这些类型都提供手动调优的原生内核。</p><p>在我们比较的库中，只有 NumKong 拥有可用于 int8 对比的内核。我们测量了 1024 维度下的 int8 点积、平方欧几里得距离和余弦计算。</p><p><strong>Int8 单对评分（1024 维，ns/vec op – 越低越好）</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ac255d158e93504/6a1701d5a292997e48d00eb1/a0b852fd5f51d57bd2488886600472ee65ab64da-1594x378.png" alt="表格对比了 x86 和 ARM 上点积、平方欧几里得距离和余弦运算的性能，并列出两种架构下每项操作对应的 ES、NumKong 和 diff 值。" /><p>在两种架构上，NumKong 在中小维度下持平或更快，差异主要来自更低的调用开销（直接 C 调用 vs Java FFI）。在更高维度下，simdvec 迎头赶上，因为更高效的内核实现（使用级联展开）摊薄了调用成本：随着维度增加，<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#single-pair-i8-nsop-2">这一差距会缩小并最终反转</a>。交叉点出现在 768 到 1536 维之间，具体取决于函数和架构。</p><p>尽管 Java FFI 存在略高的开销，simdvec 的表现仍足以媲美高度优化的 C/C++ 库。它不仅是唯一一个同时为 float32 <em>和</em> int8 提供优化内核的库，而且在 ARM 上保持领先，在 x86 上的 float32 方面也仅略逊于 FAISS，在 int8 上与 NumKong 在两个架构上都非常接近。对于 bfloat16、int4、binary 和 BBQ，虽然存在其他替代方案，但 simdvec 的优势在于，它能够针对每种类型的数据布局进行手动 SIMD 调优。</p><p>然而，生产环境下的搜索引擎不会一次只为一个向量评分，而是会在每次查询中为数千个向量评分。接下来的问题是：在如此规模下性能表现如何？</p><h3>一次处理数千个向量</h3><p>单对向量性能只是整体图景的一部分。在实践中，真正重要的是系统在负载下的行为。一次 HNSW 查询可能会为数百个图邻居评分。一次 IVF 扫描可能会为数千个倒排列表条目评分。一次重排序阶段可能会为数万个候选项评分。单对吞吐量固然重要，但更关键的是评分大量向量时的速度，以及当工作集超出 CPU 缓存时性能下降是否平缓。</p><p>simdvec 为每一种数据类型都提供了批量评分功能。这绝非简单的单对内核循环，而是使用了多累加器内层循环：在每个维度步长 (stride) 中仅加载一次查询向量，并让多个文档向量共享该向量，同时针对下一批次执行显式的缓存行预取。在本文撰写之时，jvector 和 FAISS 都没有提供等效功能。jvector 没有批量 API，调用者只能在循环中逐对评分。FAISS 暴露了 <code>fvec_inner_products_ny</code>，但在撰写本文时，其实现方式仍是循环调用单对向量距离函数，没有查询向量摊销，也没有预取。</p><p><strong>Float32。</strong>为了在内核层面衡量影响，我们使用随机访问模式来模拟类似 HNSW 的分散式图邻居查找，并让单个查询对数量不断增加的 1024 维 float32 文档向量进行评分。我们选择了 32、625 和 32,500 个向量这三种数据集规模，使工作集分别超出 L1、L2 和 L3 缓存。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df64ec77a1921ae/6a1701d714b270393ae3c4de/1d90267be1c63ac82b8ba588617ebedb0be0d1b6-1334x558.png" alt="两张条形图对比了 Elasticsearch simdvec、FAISS 和 jvector 在 AMD Turin（x86，AVX-512）和 Graviton 4（ARM，NEON）上，针对 32 个、625 个和 32,500 个向量的批量 float32 点积评分耗时。" /><p>当数据能放入缓存时，simdvec 在两个平台上都是最快的，但优势不大，因为此时内核算术运算占主导。真正的差距出现在工作集超出 L3 缓存之后。在 x86 上，simdvec 每个向量 95 ns，而 FAISS 需要 165 ns，jvector 需要 412 ns。在 ARM 上，模式相同：simdvec 保持在 162 ns，而 FAISS 攀升到 347 ns，jvector 到 476 ns。simdvec 中的预取和查询向量摊销能够以简单循环调用单对向量内核无法匹敌的方式掩盖内存延迟；而在真实搜索工作负载所处的大量访问主内存的场景中，这种优势会进一步扩大。</p><p><strong>Int8。</strong>同样的模式在量化类型上也成立。我们测量了 1024 维 int8 点积的批量评分，数据集大小同样选择为超出 L1、L2、L3 缓存边界，将 simdvec 的批量评分与 NumKong 循环调用的单对评分进行了对比。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda85e10007188d0/6a1701d9cf4f25d722b2cff7/9ee97d98b40d13b19370b76b33e67ea66bfb3250-1580x338.png" alt=" 标题为“x86 — 批量评分，int8 点积（ns/op，越低越好）”的表格，对比了 ES simdvec 和 NumKong 在三种向量规模（128、2,500 和 130,000）下的表现，并列出了相应的 ns/op 数值和加速比。" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972c311b7d08ba73/6a1701dadc55dec02ee00c87/601f50a03fa3e6263cbac9700281dfe3e511de60-1580x338.png" alt="标题为“ARM — 批量评分，int8 点积（ns/op，越低越好）”的表格，对比了 ES simdvec 和 NumKong 在三种向量规模（128、2,500 和 130,000）下的表现，并列出了相应的 ns/op 数值和加速比" /><p>在 x86 上，simdvec 快 1.2 倍到 1.9 倍，这得益于显式预取和批处理的结合。在 ARM 上，simdvec 再次胜出，在所有数据集大小下快 1.7 倍到 1.9 倍。优势来自每次批处理四个向量，通过交错访问模式提供内存级并行性。在这两种情况下，最引人注目的结果都出现在最大数据集规模上，而这也正是最关键的场景。</p><p>平方距离和余弦计算的结果也呈现类似模式：ARM 上加速 1.4 倍到 1.8 倍，x86 上加速 1.3 倍到 3.0 倍（详见<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md">此处</a>）。</p><h3>当内存成为瓶颈</h3><p>生产环境中的向量索引通常无法放入 CPU 缓存。一个包含 1,000 万个 1024 维 int8 向量的索引大小为 10 GB。为候选项评分意味着需要从 DRAM 流式读取数据，而这正是批量评分架构发挥作用的地方。</p><p>我们使用硬件性能计数器来测量批量评分过程中 CPU 内部的实际运行情况，结果发现，隐藏内存延迟需要两种截然不同的策略，每种架构各对应一种。</p><p><strong>在 x86 上，显式预取大幅减少了缓存未命中。</strong>批量内核会按顺序处理向量，先完整计算一个向量，再处理下一个，同时为下一批数据发出预取指令。未来所需的数据在 CPU 需要之前就被拉入 L1 缓存。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt368f29b5143d0f5a/6a1701dcc1e8a51780f8817b/a39548f8060c2a4a5154521a4047dd92d8cd77be-1580x309.png" alt="标题为“x86 (AMD Turin) — 每次 int8 操作的硬件计数器”的表格，对比了单对模式与批量模式下的 L1 缓存未命中、IPC 和 dTLB 未命中，并列出相应的改进倍数。" /><p>在 ARM 上，即使使用预取，顺序处理方法也表现不佳。取而代之的是，<strong>批量内核采用交错加载策略</strong>：在每个步幅位置交错加载四个向量的数据，为乱序执行引擎提供四个独立的内存流。CPU 并没有加快取数速度，而是在内存请求在途时，通过始终保持有计算任务可做来减少等待时间。详细分析可参阅<a href="https://github.com/elastic/elasticsearch/issues/145412">此 GitHub issue</a>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8751ac5b20b75508/6a1701deab7f08db83db9d13/832de3bc0d556a493b7bf3f250196018acfc1585-1580x238.png" alt="标题为“ARM (Graviton 4) — 每次 int8 操作的硬件计数器”的表格，对比了单对模式与批量模式下的 L1 缓存未命中和后端停顿，并列出相应的改进说明" /><p>这些数字讲述了两个不同的故事：</p><ol><li><p>在 x86 上，预取将缓存未命中从 13.9 万次降低到 1.9 万次，每周期指令数 (IPC) 提升了一倍以上。批量处理的优势会随着数据集规模增长而扩大：当工作集位于 L2 时为 1.2 倍，超出 L3 后则达到 2.8 倍，因为预取能够掩盖越来越高的 DRAM 往返访问开销。</p></li><li><p>在 ARM 上，缓存未命中几乎没有变化。真正变化的是利用率：后端停顿减少 40%，因为交错访问模式让流水线持续有任务可执行。这一优势在不同数据集规模下稳定保持在 1.8 倍，因为内存级并行性无论数据来自缓存还是 DRAM 都同样适用。</p></li></ol><p>两种架构，两种策略，一个结果：在生产规模下，即使向量散落在主内存各处，simdvec 也能让 CPU 流水线保持忙碌。</p><h2>这对 Elasticsearch 用户意味着什么</h2><p>这些内核层面的能力会不断叠加。一次向量搜索查询可能会执行数百万次距离操作：HNSW 图遍历、候选项评分、重排序。在数千个并发查询下，每次操作的纳秒级差异都会直接影响查询延迟和集群吞吐量。无论您使用 float32、int8、bfloat16 还是 BBQ，无论您的索引在内存中还是磁盘上，simdvec 都是底层的引擎，而每一次操作都运行在这个引擎上，并经过了哪怕一纳秒都不放过的极致精细调优。</p><p>关键结论是，在生产规模下，向量搜索性能并不主要由原始 SIMD 吞吐量决定，而是取决于系统能否在持续处理数百万次小型操作的同时，高效掩盖内存延迟。</p><p>simdvec 内核几乎会随每个 Elasticsearch 版本持续改进。当新的量化类型和硬件平台出现时，它们从第一天起就能获得经过调优的内核。而现有类型也会随着我们不断优化已发布的实现而持续变快。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Lorenzo Dematte,Simon Cooper]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18088409621369b/6a1701dfdc55de7297e00c8b/df9646091bafbbf0a6dfd212ff8a6bd1e8589708-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[宣布 Kibana 仪表板的只读权限]]></title>
    <description><![CDATA[在 Kibana 中引入只读仪表板，为仪表板创建者提供细粒度的共享控制，以保持结果的准确性并防止不必要的更改。]]></description>
    <content:encoded><![CDATA[<p>您去过那里。您花了一个小时构建完美的仪表板来监测日志：每个图表、每个过滤器和每个标签。您将其分享给了团队。几天后，您打开它，发现有些不对劲。一位同事对查询进行了微调。或者有人更改了日期范围。也许他们以为自己是在帮忙。现在您正在翻查修改记录，对每个数字都心存疑虑。听起来很熟悉？</p><p>正因如此，我们才构建了<strong>只读仪表板</strong>。这是您一直在要求的控制权。放心地共享仪表板，无需担心下一个拥有编辑权限的人会更改或破坏仪表板。</p><p>注意：只读权限在 Elastic Cloud Serverless 中可用，并且从 9.3 版本开始在 Elastic Cloud Hosted 和 Elastic Self-Managed 中可用。</p><h2>当“人人皆可编辑”成为障碍时</h2><p>在 Kibana 中，<em>共享</em>通常意味着空间层级的权限。如果有人可以在某个空间创建仪表板，他们也可以编辑或删除其他人的仪表板。这对协作来说原本是件好事，直到情况变得不妙。一次意外的编辑可能会导致错误的决策、失去信任和大量的清理工作。</p><p>我们听说过一些替代方案：<strong>“我们在仪表板名称中加上‘只读’，希望用户能注意到”。</strong>或者：<strong>“我们给它们贴上标签，然后祈祷好运。”</strong>希望并不是一种权限模型。您需要一种真正的方法来锁定仪表板，同时又不将所有人拒之门外。</p><h2>到底出了什么问题</h2><p>Deb 和 Kevin 都拥有对运营空间中的日志监控仪表板的编辑权限。Kevin 对图表进行了一些更改。当 Deb 回来后，发现数字与她之前提交的不符。她必须追溯哪些地方发生了变化（通常凭记忆），然后进行修正，还要弄清楚有多少份报告发出了错误数据。</p><h2>只读仪表板：合理的所有权和控制权</h2><p>只读仪表板可解决此问题，让您能够控制其他用户是否可以编辑该仪表板。共享仪表板时，您可以选择：<strong>编辑</strong>（默认，与当前相同）或<strong>查看</strong>。在<strong>查看</strong>模式下，只有你（和 Kibana 管理员）可以对其进行更改或删除。其他人可以打开它、使用它、信任它，但他们无法对其进行修改。</p><h3>您将获得的内容</h3><ul><li><p><strong>仪表板完整性：</strong>在<strong>查看</strong>模式下，该空间内具有编辑权限的其他用户无法修改或删除仪表板。如果他们尝试，会被告知仪表板已锁定。您的图表和逻辑将保持原样。</p></li><li><p><strong>您掌控一切：</strong>你是所有者。您随时都可以编辑、完善和更新。以“仅查看”的方式共享并不会将您锁定，而是会锁定其他人看到的版本。</p></li><li><p><strong>灵活的生命周期：</strong>您可以随时将仪表板切换回“可编辑”状态。Kibana 管理员仍然可以管理所有仪表板（例如，在仪表板所有者离开的情况下）。因此，不会出现无人管理的情况。</p></li></ul><p>您可以广泛共享最终确定的关键任务仪表板，并确信这些仪表板将保持一致。<strong>所有 Elastic 层级和产品</strong>（包括 Serverless）均提供此功能。</p><h3>谁能做什么？</h3><p>按角色快速参考：</p><ul><li><p><strong>仪表板所有者：</strong>您创建了它；您拥有完全的编辑权限。</p></li><li><p><strong>Kibana 管理员：</strong>可以管理所有仪表板。</p></li><li><p><strong>具有空间编辑权限的用户：</strong>可以创建和编辑自己的仪表板；但不能编辑或删除仅查看的仪表板。</p></li><li><p><strong>具有空间视图的用户：</strong>只能查看（和列出）仪表板。</p></li></ul><p>操作</p><p>仪表板所有者</p><p>Kibana 管理员</p><p>具有空间编辑权限的用户</p><p>具有空间视图的用户</p><p>列出并查看仪表板</p><p>✔</p><p>✔</p><p>✔</p><p>✔</p><p>创建新的仪表板</p><p>✔</p><p>✔</p><p>✔</p><p>✘</p><p>修改/删除可编辑的仪表板</p><p>✔</p><p>✔</p><p>✔</p><p>✘</p><p>修改/删除只读仪表板</p><p>✔</p><p>✔</p><p>✘</p><p>✘</p><h2>如何启用只读</h2><p>您可以在保存新仪表板时设置仅查看模式，也可以稍后从共享菜单中设置。</p><h3>保存新仪表板时</h3><ul><li><p>创建您的仪表板，然后单击“<strong>保存</strong>”。</p></li><li><p>在“另存为新仪表板”模态框中，找到“<strong>权限</strong>”。</p></li><li><p>从“<strong>可编辑</strong>”更改为“<strong>可查看</strong>”。</p></li><li><p>单击“<strong>保存</strong>”。完成。它对其他所有人都是只读的。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt120724e3b963289f/6a16f76354abb858cd133baf/42a71d1bb55f9d50bd079f53bf45a0e1999b27f7-1214x1306.png" alt=" 一个 Kibana 对话框，显示了保存仪表板的选项，其中选择了仅查看权限。" /><h2>对于您已拥有的仪表板</h2><ul><li><p>打开仪表板。</p></li><li><p>打开“<strong>共享仪表板</strong>”菜单。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8fa2f365687c2ca8/6a16f764a292990e4bd00e01/e8405938557c879b1d4c262b98cf5a7f66408c04-1246x264.png" alt="Kibana 仪表板工具栏显示了退出编辑模式、共享、调整设置、添加面板和保存等选项，重点是共享。" /><ul><li><p>在共享模式中，找到“<strong>权限</strong>”并切换到“<strong>可查看</strong>”。将立即应用更改；空间中的其他用户将无法再编辑或删除它。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt05e6051dbe4b8250/6a16f76667045b321445bf9d/849405bc32701f3ebe0def012d8ae3cf3813ea0a-996x750.png" alt="Kibana 共享面板，显示仪表板权限以及复制只读链接的选项。" /><ul><li><p>您可以将鼠标悬停在“<strong>共享</strong>”操作上，查看给定仪表板拥有的权限类型。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf8880996f84cdab3/6a16f7678b73cb3682189dfa/80541ddb1b1bc567b0aeff693944ea8b6871d6a7-1270x320.png" alt="Kibana 工具栏，其中“共享”按钮已突出显示，并有一个工具提示表明空间中的每个人都可以查看仪表板。" /><h3>查看哪些仪表板被锁定</h3><p>在主仪表板列表中，无法编辑或删除的仪表板有一个禁用选择复选框。这为找出“仅查看”的内容提供了一种简便的方法。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9c5fae7fa018c89a/6a16f768b0367da1b172bacb/24b2eba08df86174db949c662e7886c5aea1b460-1999x876.png" alt="Kibana 仪表板列表，显示了多个仪表板，包括创建者、时间戳，和已选中的一个项目。" /><p>在仪表板中，您还会发现“编辑”操作已禁用，并且会出现一个工具提示，说明仪表板已设置为“仅查看”。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50ef3c91d5640c0c/6a16f76a60084b08fc3c434d/e0a2f9da6dc854e876fc6dc2a7c3ef8b313b52ef-1358x330.png" alt="Kibana 仪表板工具栏显示了一个“编辑”按钮，并带有警告工具提示，表明用户没有权限修改仪表板。" /><h2>试用</h2><p>只读仪表板现已推出。创建仪表板，将其切换到“<strong>可查看</strong>”，然后共享。您的团队将获得单一可信来源，而您则高枕无忧。标题中不再包含“请勿编辑”字样。</p><p>我们很想听听您是如何使用只读仪表板的。在我们的<a href="https://discuss.elastic.co">社区论坛</a>中分享您的反馈。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/kibana-dashboards-read-only-permissions</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/kibana-dashboards-read-only-permissions</guid>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4d7f707011ca90f/6a16f76b8b73cb3125189dfe/11e578bc317aea30d2e10ccc0334a532f6af2ef9-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 中 HNSW 的自适应提前终止]]></title>
    <description><![CDATA[为 Elasticsearch 中的 HNSW 引入一种新的自适应提前终止策略。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch 使用<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">分层可导航小世界</a> (HNSW) 算法对邻近图进行矢量搜索。众所周知，HNSW 算法在 k 近邻 (KNN) 搜索结果的质量与相关计算成本之间实现了良好的平衡。</p><p>在 HNSW 中，搜索过程是通过在图中迭代扩展候选节点来推进的，同时维护一个迄今为止已发现的、规模受限的最近邻节点集合。每次扩展都会带来一定的影响（包括向量运算、磁盘随机寻址等操作），并且随着搜索的推进，这种影响所带来的边际效益往往会逐渐降低。</p><p>优化 HNSW 图遍历的一种方法是，当发现新真实邻近节点的边际概率不再提升时，立即停止搜索。因此，在 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a> 中，我们引入了新<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">提前终止机制</a>。当连续访问图节点的次数达到固定数量，但仍无法提供足够的新最近邻节点时，搜索过程就会停止。</p><p>本文将指导您了解我们如何改进 HNSW 中提到的提前终止机制，使其更适合不同的数据集和数据分布。</p><h2><strong>HNSW 中的提前终止</strong></h2><p>在 HNSW 中，搜索过程是通过在近邻图中迭代扩展候选节点来推进的，同时持续维护一个迄今已发现、规模受限的最近邻节点集合，直至搜索遍历完整个图，或者满足某些提前终止条件为止。</p><p>因此，提前终止不一定总是性能优化，它本身就是<strong>搜索算法不可或缺的组成部分</strong>。决定终止搜索的时机，直接决定了效率与召回率之间的权衡关系。在 Elasticsearch 中，针对 HNSW 图的查询已内置多种提前终止机制：</p><ul><li><p>访问节点的最大数量固定不变。</p></li><li><p>已达到固定的超时时间。</p></li></ul><p>这些规则虽然简单且可预测，但在很大程度上<strong>与搜索的实际操作无关</strong>。此外，它们主要用于确保最终用户在合理的时间内完成查询。</p><p>在<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">上一篇博文</a>中，我们介绍了 HNSW 冗余的概念。简而言之，当 HNSW 持续评估那些无法带来更多最近邻节点的新候选节点时，就会产生冗余计算。</p><h2><strong>耐心：衡量进展而非过程</strong></h2><p><em>“耐心”</em>这一概念将提前终止的判定标准重新定义为<strong>“衡量进展而非过程”</strong>。</p><p>而不是问：</p><p>“我们走了多少步？”</p><p>新的问题变成了：</p><p>"在我们彻底丧失希望之前，我们能够接受浪费多少计算量？"</p><p>在 HNSW 搜索过程中，早期探索阶段通常能显著提升前 k 个候选结果集的质量。在 HNSW 图探索的初始阶段，随着算法不断发现与查询向量距离更近的邻近节点，邻近节点集会持续更新。随着搜索逐步收敛，这类质量提升会逐渐减少。<a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">基于“耐心”机制的终止</a>策略会监测这一变化模式，当持续一段时间内未再出现显著改进时，即终止搜索过程。</p><p>在实际操作中，我们在遍历 HNSW 图的过程中，每跳转到一个候选节点时，都会计算队列饱和度。该指标用于衡量在访问最近一个图节点期间，未发生变化的最近邻节点所占的百分比（或者说，是上一轮迭代中引入的新邻节点数量的倒数）。若连续多次迭代中，这一比率持续过高，我们便会停止对图的遍历。</p><p>从概念层面来看，“耐心”机制将HNSW搜索视为一个<strong>收益递减的过程</strong>。当搜索收益趋于平稳时，继续遍历图结构所带来的增益将微乎其微。</p><p>这种框架之所以强大，是因为它将终止与<em>可观察到的结果</em>直接联系起来，而不是与任意的固定限制联系起来。</p><p>采用这种智能提前终止技术的优势在于，HNSW 图探索过程在保持近乎完美的相对召回率的同时，往往会访问更少数量的图节点。</p><p>为了直观地说明这一点，我们可以在几个数据集（FinancialQA 和 Quora）和模型（JinaV3 和 E5-small）上绘制基于耐心的提前终止（标注为 <em><code>et=static</code></em>）与默认 HNSW 行为（标注为 <em><code>et=no</code></em>）的对比图。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd0d692b9beb476a/6a170ef4dc55debf0be00e97/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" alt="HNSW 的自适应提前终止 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93509c251a1b641e/6a170ef6dc55dea2b3e00e9b/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" alt="HNSW 的自适应提前终止" /><h2><strong>静态阈值和 HNSW 动态</strong></h2><p>实际上，Elasticsearch 使用<strong>静态阈值</strong>来实现这一点。其中一个阈值指的是<strong>饱和阈值</strong>，即我们认为次优的饱和度比率。另一个阈值指的是，在队列达到次优饱和度的情况下，我们允许访问的连续图节点数，即<strong>耐心阈值</strong>。</p><p>当我们在 Elasticsearch 9.2 中引入这种提前终止策略时，我们决定选择保守的默认设置，以便在延迟和内存消耗方面仍能达到效果的同时，尽可能多地让系统召回。因此，我们将饱和阈值设为 100%，耐心阈值设为 KNN 查询中 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> 的（有界）30%。</p><p>在很多情况中，这些设置能取得不错的效果；然而，对于请求相同数量邻节点的两个查询而言，它们的收敛行为可能存在极大差异。有些查询会遇到密集的局部邻域，能迅速达到饱和状态；而有些查询则必须遍历漫长且稀疏的路径，才能找到具有竞争力的候选节点。事实证明，后一种情况最难以有效处理。</p><p>因此，我们有时会发现：</p><ul><li><p>简单查询的过度探索。</p></li><li><p>复杂查询的过早终止。</p></li></ul><p>因此，我们认为固定阈值编码了关于收敛的全局假设，而我们可以使 HNSW 更好地适应不同的动态。</p><h2><strong>实现 HNSW 的提前终止自适应</strong></h2><p>自适应提前终止从另一个角度解决了这个问题。该算法不是强制执行预定义的停止阈值，而是<strong>从搜索动态本身推断何时停止</strong>。</p><p>因此，我们不再比较连续两个候选节点间的队列饱和度比率，而是决定引入即时平滑发现率 （即最近一次访问 <em>i</em> 中，针对查询 <em>q</em> 新发现的邻近节点数量），同时结合图遍历过程中该发现率的滑动均值 和标准差（采用<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm">韦尔福德算法</a>计算）。这些关于发现率的统计量按每个查询独立计算，从而可根据不同查询的特性动态调整其“耐心”阈值。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfb1e123f1b026d/6a170ef7cf4f25d9bab2d216/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" alt="" /><p>先前静态设定的阈值将根据发现率统计数据实现自适应调整：饱和阈值调整为滚动均值加上标准差；同时，我们将耐心值设为与标准差呈反比变化的动态参数。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4d91f464a9dc9d/6a170ef8d7c0223420de656a/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" alt="" /><p>提前退出的规则保持不变；当瞬时发现率低于自适应饱和阈值时，即判定达到饱和状态。如果在连续访问的候选节点数量超过自适应耐心值所设定的次数后，饱和状态仍持续存在，则停止对图的遍历。</p><p>如此一来，我们实现了搜索行为不再依赖于 KNN 查询中的 <em><code>num_candidates</code></em> 参数（该参数可能始终被设定为固定值或保留默认值，而与提前终止机制无关），同时能够根据每个查询和向量分布进行动态适配。</p><p>在 FinancialQA 和 Quora 数据集上，采用自适应策略（标记为 <em><code>et=adaptive</code></em>）时，每个访问节点的召回率相较于静态策略（ <em><code>et=static</code></em>）和默认 HNSW 行为（<em><code>et=no</code></em>）均有显著提升。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteab7ba53ae14da0e/6a170ef9961e69e072c4cfd5/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" alt=" 自适应策略和默认 HNSW 行为" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fb9e672d3698200/6a170efb67045b7b2b45c2ab/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" alt="" /><p>在 Elasticsearch 9.3 中，HNSW 密集向量字段的自适应提前终止默认处于启用状态（最终可以通过<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">相同的索引级别设置</a>将其关闭）。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27b746cc1995e6b7/6a170efda29299de8ad010c6/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[通过 best_compression 提升搜索性能]]></title>
    <description><![CDATA[虽然 best_compression 通常被视为 Elastic Observability 和 Elastic Security 用例的存储节省功能，但本篇博客将展示其作为搜索性能调优工具的有效性。]]></description>
    <content:encoded><![CDATA[<p></p><p>在为高并发工作负载调优 Elasticsearch 时，标准方法是最大限度地增加 RAM，将工作文档集保存在内存中，以实现低搜索延迟。因此，<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules"><code>best_compression</code></a> 很少被考虑用于搜索工作负载，因为它主要被视为 Elastic Observability 和 Elastic Security 用例中优先考虑存储效率的节省存储措施。</p><p>在本博客中，我们证明当数据集大小显著超出操作系统页面缓存时，<code>best_compression</code>通过减少 I/O 瓶颈来提升搜索性能和资源效率。</p><h2><strong>设置</strong></h2><p>我们的用例是一个运行在 <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/ec-change-hardware-profile#ec-profiles-compute-optimized-arm">Elastic Cloud CPU 优化实例</a>上的高并发搜索应用程序。</p><ul><li><p>数据量：约 5 亿份文档</p></li><li><p>基础架构：6 个 Elastic Cloud（Elasticsearch 服务）实例（每个实例：1.76 TB 存储 | 60 GB 内存 | 31.9 个 vCPU）</p></li><li><p>内存与存储比率：约 5% 的总数据集可存储在 RAM 中</p></li></ul><h2><strong>症状：高延迟</strong></h2><p>我们观察到，当当前请求数在 19:00 左右激增时，搜索延迟显著恶化。如图 1 和图 2 所示，尽管每个 Elasticsearch 实例的流量峰值约为每分钟 400 个请求，但平均查询服务时间仍恶化至超过 60 毫秒。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf8ab7de934d6b410/6a170440c1e8a58db3f881c1/f9c6cc1882e7db24336c65c54bbc1d38dcdb7fa3-697x311.png" alt="每个 Elasticsearch 实例的每分钟请求数达到峰值" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32de2bed0afbacd5/6a1704422b835fa5ddf4b0e2/bbb705ae2fcd14c81d335bf322346caf3bf33765-996x618.png" alt="Elasticsearch 平均查询服务时间" /><p>在完成初始连接处理后，CPU 使用率保持相对较低，表明计算并非瓶颈。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5b45d4a1ff48f54/6a17044447d49cd7252d88af/cec15a28d2d22e9adedd2951bb2334b3717890a1-1494x730.png" alt="Elasticsearch CPU 使用率" /><p>查询量与页面错误之间出现了强相关性。随着请求增加，我们观察到页面错误比例上升，峰值约为每分钟 40 万次。这表明活跃数据集无法完全放入页面缓存。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd0a3d0c610700bdb/6a17044560084b6f403c4459/511f2f10300a9d10ba3d7a82b9a8c8d567ac5636-1492x678.png" alt="Elasticsearch 性能的页面错误次数" /><p>同时，JVM 堆使用率也显示正常且平稳。这排除了垃圾回收问题，并确认瓶颈在于 I/O。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3f888a03ce78eb04/6a170448964cea401008ba59/336bbad638f866304358dba1d06ee987de0f23cf-1490x568.png" alt="Elasticsearch 中的堆使用率" /><h2><strong>诊断：I/O 瓶颈</strong></h2><p>系统存在 I/O 瓶颈。<a href="https://www.elastic.co/blog/elasticsearch-caching-deep-dive-boosting-query-speed-one-cache-at-a-time">Elasticsearch 依赖操作系统页面缓存从内存提供索引数据</a>。当索引过大而无法放入缓存时，查询会触发开销很大的磁盘读取。虽然典型的解决方案是水平扩展（添加节点/RAM），但我们希望先充分利用现有资源的效率改进。</p><h2><strong>解决方案</strong></h2><p>默认情况下，Elasticsearch 对其索引段使用 <a href="https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)">LZ4</a> 压缩，在速度和大小之间取得平衡。我们假设，改用 <code>best_compression</code> （使用 <a href="https://en.wikipedia.org/wiki/Zstd">zstd</a>）会减少索引的大小。更小的占用空间使得更大比例的索引能够放入页面缓存，以微不足道的 CPU 增加（用于解压缩）换取磁盘 I/O 的减少。</p><p>为了启用 <code>best_compression</code>，我们使用索引设置 <code>index.codec: best_compression</code> 重新索引了数据。或者，也可以通过关闭索引、将索引编解码器重置为 <code>best_compression</code>，然后进行段合并，也可实现相同的结果。</p>POST my-index/_close
PUT my-index/_settings
{
    "codec": "best_compression"
}
  
POST my-index/_open  
POST my-index/_forcemerge?max_num_segments=1<h2><strong>结果</strong></h2><p>结果证实了我们的假设：存储效率的提高直接转化为搜索性能的大幅提升，而 CPU 利用率并未相应增加。</p><p>应用 <code>best_compression</code> 后，索引大小减少了约 25%。虽然低于在重复日志数据中观察到的减少幅度，但这 25% 的减少实际上将我们的页面缓存容量提升了相同的比例。</p><p>在下一次负载测试期间（从 17:00 开始），流量甚至更高，每个 Elasticsearch 节点的请求峰值达到每分钟 500 次。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta61ab3bd5ded5716/6a170449a6c2b9f711e795dd/fc1902f396cb2115c0013155ad07f6eb87389c60-660x309.png" alt="Elaticserach 的负载测试" /><p>尽管负载更高，但 CPU 利用率仍低于上一次运行。先前测试中较高的使用率可能是由于过多的页面错误处理和磁盘 I/O 管理开销所致。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fd1a44b02a4f787/6a17044b2b835ff996f4b0e6/15699ef4c65b3f0a9f8a3e1bae8bb18f7b647025-819x352.png" alt="通过 best_compression 提升 Elasticsearch CPU 利用率性能" /><p>至关重要的是，页面错误显著下降。即使在更高的吞吐量下，错误次数也稳定维持在每分钟低于 20 万次，而基准测试中的错误次数则超过 30 万次。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef0c621d76767115/6a17044c2b835fe49ef4b0ea/f76ca967976d740af88a9359b66041701abb46fc-764x340.png" alt="通过 best_compression 提升 Elasticsearch 性能，降低页面错误次数" /><p>尽管页面错误结果仍然不太理想，但查询服务时间却减少了约 50%，即使在负载更重的情况下也保持在 30 毫秒以下。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6579b3d005d04101/6a17044e66c4f9179cf8bf13/750ec1c59b8eb5069aed4c066d856ecea82d5bca-620x311.png" alt="使用 best_compression 后，Elasticsearch 平均查询服务时间性能提升" /><p></p><h2><strong>结论：为搜索启用 best_compression</strong></h2><p>对于搜索用例中数据量超过可用物理内存的情况，<code>best_compression</code> 是一个强大的性能调优工具。</p><p>应对缓存未命中的常规解决方案是通过扩展来增加 RAM。然而，通过减少索引占用空间，我们实现了相同的目标：最大化页面缓存中的文档数量。我们的下一步是探索<a href="https://www.elastic.co/blog/space-savings-a-lesser-known-benefit-of-index-sorting-in-elasticsearch"><strong>索引排序</strong></a>，以进一步优化存储并从现有资源中获得更多性能。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/improve-elasticsearch-performance-best-compression</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/improve-elasticsearch-performance-best-compression</guid>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Sherry Ger,Ryan Eno]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ff57fbb95c04412/6a17044fab7f081490db9d66/5141a8c2618337207d848ce16b258a86885955b2-1600x1034.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用判断列表评估搜索查询的相关性]]></title>
    <description><![CDATA[针对在 Elasticsearch 中开展可扩展的搜索测试，探究如何构建判定列表以客观评估搜索查询相关性，并提升召回率等性能指标。]]></description>
    <content:encoded><![CDATA[<p>从事搜索引擎开发的工程师们常常会遇到同一个问题：业务团队对某次特定搜索结果并不满意，因为他们期望排在搜索结果首位的文档，实际却出现在结果列表的第三或第四位。</p><p>然而，当你解决这一问题时，却可能因无法手动测试所有情况而不经意间破坏其他查询的功能。但你或你的 QA 团队该如何测试，以确认某一项查询的改动是否会对其他查询产生连锁反应呢？或者更关键的是，你们要如何确保所作的改动确实优化了某项查询呢？</p><h2>转向系统性评估</h2><p>这个时候，判断列表就可以派上用场。与其在每次更改时依赖手动和主观测试，不如定义一组与业务案例相关的固定查询及其相关结果。</p><p>这一组（测试用例或数据）将成为基准参照。每次实施改动时，你都用它来评估搜索效果是否确实得到了提升。</p><p>这种方法的价值在于：</p><ul><li><p><strong>消除不确定性</strong>：无需再费心猜测所做的更改是否会影响其他查询；数据会直接告诉你答案。</p></li><li><p><strong>停止人工测试</strong>：一旦判定集被记录下来，测试便会自动执行。</p></li><li><p><strong>佐证更改</strong>：你可以展示出明确的指标，以佐证某项更改所带来的益处。</p></li></ul><h2>如何开始建立判断列表</h2><p>最简单的开始方式之一是获取具有代表性的查询，并手动选择相关文件。有两种方法可以列出此列表：</p><ul><li><p><strong>二元判断：</strong>与查询关联的每一份文档都会被赋予一个<strong>简单标签</strong>：<em>相关</em>（通常标注分数为“1”）和不相关（标注分数为“0”）。</p></li><li><p><strong>分级判断：</strong>在此情境下，每份文档会依据不同等级获得相应分数。例如：采用 0 至 4 分的评分量表，类似于<a href="https://en.wikipedia.org/wiki/Likert_scale">李克特量表</a>，其中 0 分表示“完全不相关”，4 分表示“完全相关”，中间还设有“相关”“有点相关”等不同程度表述。</p></li></ul><p>当搜索意图具有明确界限时，二元判断（是/否）十分奏效，即判断该文档是否应出现在搜索结果中？</p><p>当存在模糊地带时，分级判断更为实用：某些结果相较于其他结果更优，因此你可以将结果划分为“优秀”“良好”和“毫无价值”等不同等级，并运用能体现结果排序权重及用户反馈的评估指标。然而，分级量表也存在弊端：不同评审者对评分等级的使用方式可能存在差异，这会导致判断结果的一致性降低。并且，由于分级指标对高分赋予了更大的权重，即便是一个微小的改动（比如将某项评分从 4 分改为 3 分），也可能在指标上引发远超评审者预期的巨大波动。这种额外引入的主观性使得分级判断结果更具干扰性，且随时间推移愈发难以把控。</p><h2>我需要自己对文件分类吗？</h2><p>不一定，因为有多种不同方法创建判定列表，且每种方法各有其优缺点：</p><ul><li><p><strong>明确判断：</strong>在这种情况下，领域专家会逐一审阅每个查询/文档，并手动判定其相关性（或相关程度）。尽管此方法能确保质量并实现把控，但其可扩展性较差。</p></li><li><p><strong>隐式判断：</strong>采用这种方法时，你会依据真实用户行为（如点击量、跳出率、购买行为等）来推断相关文档。此方法可实现数据的自动收集，但可能存在偏差。例如，用户往往更倾向于点击排名靠前的结果，即便这些结果并不相关。</p></li><li><p><strong>AI 生成的判断：</strong>最后这种方法是借助模型（如 LLM）自动评估查询和文档，人们通常称之为<a href="https://en.wikipedia.org/wiki/LLM-as-a-Judge">LLM 陪审团</a>。其优势在于速度快且易于扩展，不过数据质量取决于所用模型的性能，以及大语言模型训练数据与您业务<a href="http://interests.as/">需求</a>的契合程度。与人工评分一样，LLM 评审团也可能引入自身偏见或出现前后不一致的情况，因此，必须对照一小部分可信判断结果来验证其输出结果。LLM 模型本质上具有概率性，所以即便将<a href="https://www.ibm.com/think/topics/llm-temperature">温度</a>参数设置为 0，也常见同一结果被 LLM 模型给出不同评分的情况。</p></li></ul><p>以下是一些选择最佳方法来构建判断集的建议：</p><ul><li><p>明确界定哪些仅用户能恰当判断的要素对你而言至关重要（例如价格、品牌、语言、风格以及产品细节等）。如果这些要素至关重要，则至少需针对<em>判断列表</em>中的部分内容获取<strong>明确的判断结果</strong>。</p></li><li><p>当你的搜索引擎已有足够流量时，可运用<strong>隐式判断</strong>，即借助点击量、转化率以及停留时长等指标来洞察使用趋势。不过，你仍需谨慎解读这些数据，将其与显式判断结果进行对比，以规避潜在偏差（例如用户往往更倾向于点击排名靠前的结果，即便排名靠后的结果更具相关性）。</p></li></ul><p>为解决这一问题，位置偏差消除技术会对点击数据进行调整或重新加权，以更准确地反映用户的真实兴趣。以下是一些方法：</p><ul><li><p><strong>结果随机排序：</strong>针对部分用户调整搜索结果的排序，以此估算结果位置对点击量的影响。</p></li><li><p><strong>点击模型</strong>包括<a href="https://wiki.math.uwaterloo.ca/statwiki/index.php?title=a_Dynamic_Bayesian_Network_Click_Model_for_web_search_ranking">动态贝叶斯网络 </a><a href="https://wiki.math.uwaterloo.ca/statwiki/index.php?title=a_Dynamic_Bayesian_Network_Click_Model_for_web_search_ranking"><strong>DBN</strong></a> 和<a href="https://rsrikant.com/papers/kdd10.pdf">用户浏览模型 </a><a href="https://rsrikant.com/papers/kdd10.pdf"><strong>UBM</strong></a>。这些统计模型会借助滚动行为、停留时长、点击顺序以及返回结果页等模式，来估算用户点击行为反映真实兴趣（而非仅受结果位置影响）的概率。</p></li></ul><h2>示例：电影评分应用</h2><h3>准备工作</h3><p>要运行此示例，需要一个正在运行的<a href="https://www.elastic.co/downloads/elasticsearch">本地</a>或部署在 <a href="https://www.elastic.co/cloud/cloud-trial-overview">Elastic Cloud</a> 上（托管或无服务器）的 Elasticsearch 8.x 集群，以及访问 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis">REST API</a> 或 Kibana 的权限。</p><p>想象有一款应用程序，用户可以在其中上传自己对电影的看法，还可以搜索要观看的电影。由于文本由用户自己撰写，因此可能存在拼写错误和表达方式上的多种差异。因此，搜索引擎必须能够解读这种多样性，并为用户提供有用的结果。</p><p>为能在不影响整体搜索行为的前提下对查询进行迭代优化，贵公司业务团队基于最常执行的搜索查询，创建了以下二元判断集：</p><p>查询</p><p>DocID</p><p>文本</p><p>迪卡普里奥的表演</p><p>doc1</p><p>迪卡普里奥在《荒野猎人》中的表演令人惊叹。</p><p>迪卡普里奥的表演</p><p>doc2</p><p>《盗梦空间》中，莱昂纳多·迪卡普里奥饰演了他最具标志性的角色之一。</p><p>迪卡普里奥的表演</p><p>doc3</p><p>布拉德·皮特在这部犯罪惊悚片中表现出色。</p><p>迪卡普里奥的表演</p><p>doc4</p><p>一部充满惊险动作、视觉效果惊艳的冒险大片。</p><p>让人热泪盈眶的悲伤电影</p><p>doc5</p><p>这是一个令人心碎的关于爱与失去的故事，让我哭了好几个小时。</p><p>让人热泪盈眶的悲伤电影</p><p>doc6</p><p>有史以来最催泪的电影之一，记得带上纸巾！</p><p>让人热泪盈眶的悲伤电影</p><p>doc7</p><p>让你捧腹大笑的轻松喜剧</p><p>让人热泪盈眶的悲伤电影</p><p>doc8</p><p>一部充满动作与激情的科幻史诗巨作。</p><p>正在创建索引：</p>PUT movies
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text"
      }
    }
  }
}<p>批量请求：</p>POST /movies/_bulk
{ "index": { "_id": "doc1" } }
{ "text": "DiCaprio performance in The Revenant was breathtaking." }
{ "index": { "_id": "doc2" } }
{ "text": "Inception shows Leonardo DiCaprio in one of his most iconic roles." }
{ "index": { "_id": "doc3" } }
{ "text": "Brad Pitt delivers a solid performance in this crime thriller." }
{ "index": { "_id": "doc4" } }
{ "text": "An action-packed adventure with stunning visual effects." }
{ "index": { "_id": "doc5" } }
{ "text": "A heartbreaking story of love and loss that made me cry for hours." }
{ "index": { "_id": "doc6" } }
{ "text": "One of the saddest movies ever made -- bring tissues!" }
{ "index": { "_id": "doc7" } }
{ "text": "A lighthearted comedy that will make you laugh." }
{ "index": { "_id": "doc8" } }
{ "text": "A science-fiction epic full of action and excitement." }<p>以下是该应用程序正在使用的 Elasticsearch 查询：</p>GET movies/_search
{
 "query": {
   "match": {
     "text": {
       "query": "DiCaprio performance",
       "minimum_should_match": "100%"
     }
   }
 }
}<h3>从判断到指标</h3><p>就其本身而言，判断列表并不提供太多信息；它们只是我们查询结果的期望。它们真正的优势在于，当我们使用它们来计算客观指标以衡量我们的搜索性能时。</p><p>如今，大多数常用指标包含</p><ul><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>精度</strong></a><strong>：</strong>衡量所有搜索结果中真正相关的结果比例。</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>召回率</strong></a><strong>：</strong>衡量搜索引擎在检索出的 x 个结果中，找到的相关结果所占的比例。</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_discounted_cumulative_gain_dcg"><strong>折损累积增益（DCG）</strong></a><strong>：</strong>用于衡量结果排序的质量，该指标基于最相关的结果应排在前列这一原则进行评估。</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_mean_reciprocal_rank"><strong>平均倒数排名（MRR）</strong></a>：用于衡量首个相关结果所处的排名位置情况 。在列表中越靠前，其分数就越高。</p></li></ul><p>以同样的电影评分应用程序为例，我们将计算召回率指标，看看我们的查询是否遗漏了任何信息。</p><p>在 Elasticsearch 中，我们可以通过<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">排名评估 API</a>，使用<em>判断列表</em>来计算指标。该 API 将判断列表、查询以及想要评估的指标作为输入，并返回一个数值，该数值是对查询结果与判断列表进行对比后得出的结果。</p><p>让我们针对已提出的这两个查询运行判定列表：</p>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}<p>我们将向 rank_eval 发送两个请求：一个针对莱昂纳多·迪卡普里奥查询，另一个针对悲伤电影查询每个请求均包含一个查询及其对应的判定列表（评分）。我们无需对所有文档进行评分，因为未纳入评分范围的文档将被视为未作判定。在进行计算时，召回率仅考虑“相关文档集”，即那些在评分中被认定为相关的文档。</p><p>在此情形下，针对莱昂纳多·迪卡普里奥的查询召回率为 1，而悲伤电影查询的召回率为 0。这意味着对于第一个查询，我们能够获取到所有相关结果，而第二个查询则未获取到任何相关结果。因此，平均召回率为 0.5。</p>{
 "metric_score": 0.5,
 "details": {
   "dicaprio-performance": {
     "metric_score": 1,
     "unrated_docs": [],
     "hits": [
       {
         "hit": {
           "_index": "movies",
           "_id": "doc1",
           "_score": 2.4826927
         },
         "rating": 1
       },
       {
         "hit": {
           "_index": "movies",
           "_id": "doc2",
           "_score": 2.0780432
         },
         "rating": 1
       }
     ],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 2,
         "relevant_docs": 2
       }
     }
   },
   "sad-movies": {
     "metric_score": 0,
     "unrated_docs": [],
     "hits": [],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 0,
         "relevant_docs": 2
       }
     }
   }
 },
 "failures": {}
}<p>或许我们对 <strong>minimum_should_match</strong> 参数设置得过于严苛了，因为要求查询中的所有词汇都必须在文档中出现，这很可能会导致我们遗漏掉一些相关结果。不妨去掉 <strong>minimum_should_match</strong> 参数，这样只要文档中包含查询语句里的任意一个词汇，该文档就会被视为相关结果。</p>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}<p>如你所见，通过在两个查询中的其中一个里移除 <strong>minimum_should_match</strong> 参数，现在两个查询的平均召回率都达到了 1。</p>{
  "metric_score": 1,
  "details": {
    "dicaprio-performance": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc1",
            "_score": 2.0661702
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc3",
            "_score": 0.732218
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc2",
            "_score": 0.6271719
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    },
    "sad-movies": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc7",
            "_score": 2.1307156
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc5",
            "_score": 1.3160692
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc6",
            "_score": 1.190063
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    }
  },
  "failures": {}
}<p>总而言之，移除 minimum_should_match: 100% 这一条件后，我们得以使两个查询均实现完美召回率。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf4f08a8a2915180/6a170df61949f76cbfe7aaba/24d055da4348c63827ba7046fe8cafb6f47cadd8-546x628.png" alt="" /><p>我们做到了！对不对？</p><p>没那么快！</p><p>通过提升召回率，我们能够获取到更广泛的结果范围。然而，每一次调整都意味着需要权衡取舍。这正是为何要定义完整的测试用例，并运用不同指标来评估各项更改的原因。</p><p>使用判断列表和指标可以防止您在进行更改时盲目行事，因为现在您有数据可以支持这些更改。验证不再是手动和重复的，您可以在多个用例中测试您的更改。此外，A/B 测试允许您实时测试哪种配置最适合您的用户和业务案例，从而实现从技术指标到实际指标的完整循环。</p><h2>使用判断列表的最终建议</h2><p>运用判定列表开展工作，不仅关乎评估测量，更在于构建一个能让你自信迭代优化的框架。为实现这一目标，可遵循以下建议：</p><ol><li><p><strong>从小处着手，但一定要开始行动。</strong>你无需准备 10000 个查询，且每个查询都配有 50 个判断列表。你只需找出 5 到 10 个对业务场景最为关键的查询，并明确你期望在结果顶部看到的文档即可。这已经能为你提供一个基础。通常，你应优先从热门查询以及无结果的查询入手开展工作。你也可以先使用像精确率这样易于配置的指标进行测试，然后再逐步尝试更复杂的指标。</p></li><li><p><strong>与用户核实。</strong>在生产环境中通过 A/B 测试对数据指标进行补充验证。如此一来，你便能知晓那些在指标上表现良好的更改是否也切实产生了实际影响。</p></li><li><p><strong>保持列表有效性。</strong>你的商业案例会不断变化，关键问题也会随之变化。定期更新判断以反映新的需求。</p></li><li><p><strong>使其成为流程的一部分。</strong>将判断列表整合到开发管道之中。确保每次配置更改、同义词添加或文本分析操作，都能自动对照基础列表进行验证。</p></li><li><p><strong>将技术知识与战略相结合。</strong>不要仅仅满足于衡量精确率或召回率等技术指标。要利用评估结果为业务成果提供决策依据。</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/judgment-lists-search-query-relevance-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/judgment-lists-search-query-relevance-elasticsearch</guid>
    <category><![CDATA[相关性]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcadfd2fb1cc95b4c/6a170df7acf0887798be9bd0/25478d0ffb228afd5d65d82312998ec1c299c565-700x490.png" length="0" type="image/png"/>
    <pubDate>Thu, 11 Dec 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 改进代理人工智能工具的实验]]></title>
    <description><![CDATA[了解我们如何通过迭代实验，结合线性检索器、混合搜索和语义文本进行可扩展的 RAG 优化，从而改进 Elasticsearch 的人工智能代理工作流。]]></description>
    <content:encoded><![CDATA[<p>如今，在 Elastic，我们也像其他人一样，全力投入到聊天、代理和 RAG 中。在搜索部门，我们最近一直在开发代理生成器和工具注册表，目的都是为了简化在 Elasticsearch 中与数据 "聊天 "的过程。</p><p>请阅读<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> " 利用 Elasticsearch 构建人工智能代理工作流 "博客</a> ，了解更多有关这项工作的 "全貌"，或阅读<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch"> " 你的第一个弹性代理" 博客 ，了解更多有关这项工作的实用入门知识</a> ：从单个查询到人工智能驱动的聊天 》，了解更多实用入门知识。</p><p>不过，在本博客中，我们将放大一些，看看当您开始聊天时最先发生的事情之一，并向您介绍我们最近做出的一些改进。</p><h2>这里发生了什么？</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>当您与 Elasticsearch 数据聊天时，我们默认的人工智能代理会执行此标准流程：</p><ol><li><p>检查提示。</p></li><li><p>确定哪个索引可能包含该提示的答案。</p></li><li><p>根据提示为该索引生成查询。</p></li><li><p>使用该查询搜索该索引。</p></li><li><p>综合结果。</p></li><li><p>结果能否解决提示问题？如果是，请回答。如果不行，就重复，但要尝试不同的方法。</p></li></ol><p>这看起来并不新奇--它只是检索增强一代（RAG）。正如您所期望的那样，回复的质量在很大程度上取决于初始搜索结果的相关性。因此，在我们努力提高响应质量的过程中，我们一直在密切关注在第 3 步中生成和在第 4 步中运行的查询。我们注意到一个有趣的模式。</p><p>通常情况下，当我们的首次响应 "糟糕 "时，并不是因为我们运行了一个糟糕的查询。这是因为<em>我们选错了</em>要查询的索引。第 3 步和第 4 步通常不是我们的问题，问题在于第 2 步。</p><h2>我们在做什么？</h2><p>我们最初的实施很简单。我们建立了一个工具（名为 index_explorer），它可以有效地进行<code>_cat/indices</code> ，列出我们可用的所有索引，然后要求 LLM 识别这些索引中哪个最符合用户的信息/问题/提示。您可以 在这里 看到<a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113"> 最初的实施方案</a> 。</p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

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

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

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.<p>效果如何？我们不确定！我们有一些效果<em>不佳</em>的明显例子，但我们真正面临的第一个挑战是如何量化我们的现状。</p><h2>确定基线</h2><h3>从数据开始</h3><p>我们需要的是一个 "黄金数据集"，用于衡量工具在用户提示和已有索引集的情况下选择正确索引的效率。而我们手头并没有这样的数据集，所以我们生成了一个。</p><p>致谢：我们知道，这不是 "最佳做法"。但有时，前进总比骑自行车好。<a href="https://www.elastic.co/about/our-source-code#progress-perfection">进步，简单完美</a>。</p><p>我们利用<a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">这一提示</a>为多个不同领域生成了种子指数。然后，对于每个生成的域，我们使用<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2"> 该提示</a>又生成了几个索引（目的是用硬否定和难以分类的示例给 LLM 制造混乱）。接下来，我们手动编辑了每个生成的索引及其说明。最后，我们使用<a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">该提示</a>生成了测试查询：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd9cd78154195e3/6a17f117dbb4fff7b5fb57d2/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" alt="" /><p>和测试用例，如</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadf30a0aeafd56ef/6a17f1192f4a5c160ffa89eb/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" alt="" /><h3>创建测试线束</h3><p>从这里开始的过程非常简单。脚本工具可以</p><ol><li><p>使用目标 Elasticsearch 集群建立一片净土。</p></li><li><p>创建目标数据集中定义的所有索引。</p></li><li><p>针对每个测试场景，执行 i<code>ndex_explorer</code> 工具（很方便，我们有一个<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">执行工具 API</a>）。</p></li><li><p>将结果索引与预期索引进行比较，并捕捉结果。</p></li><li><p>完成所有测试方案后，将结果制成表格。</p></li></ol><h3>调查说...</h3><p>不出所料，最初的成果平平。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73367741359e258d/6a17f11a505ac39749ad8c40/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" alt="" /><p>总体而言，77.14% 能准确识别正确的索引。这是在 "最好的情况 "下，即所有指数都有好的、语义上有意义的名称。使用过 `PUT test2/_doc/foo{...}` 的人都知道，索引的名称并不总是有意义的。</p><p>因此，我们有了一个基准线，而且它显示出很大的改进空间。现在是时候来点科学知识了！🧪</p><h2>实验</h2><h3>假设 1：映射将有助于</h3><p>这样做的目的是确定一个索引，其中包含与原始提示相关的数据。而索引中最能描述其所含数据的部分就是索引的<em>映射</em>。即使不抓取索引内容的任何样本，只要知道该索引有一个 double 类型的价格字段，就意味着该数据代表了要出售的东西。文本类型的作者字段意味着一些非结构化语言数据。两者合在一起可能意味着数据是书籍/故事/诗歌。通过了解索引的属性，我们可以获得很多语义线索。因此，我在本地分支中调整了 `.index_explorer工具，将索引的完整映射（连同索引名称）发送给 LLM，由 LLM 做出决定。 </p><p>结果（来自 Kibana 日志）：</p>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>该工具的最初作者已经预见到了这一点。虽然索引映射是一座信息金矿，但它也是一个相当冗长的 JSON 数据块。而在实际情况中，您需要比较众多指数（我们的评估数据集定义了 20 个指数），这些 JSON blob 会不断增加。因此，我们希望为 LLM 的决策提供更多的背景信息，而不仅仅是所有选项的索引名称，但又不至于提供每个选项的完整映射。</p><h3>假设 2："扁平化 "映射（字段列表）是一种折中方案</h3><p>我们首先假设索引创建者会使用有语义的索引名称。如果我们将这一假设扩展到字段名呢？我们之前的实验之所以失败，是因为 JSON 映射包含了大量繁琐的元数据和模板。</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>例如，上面的代码块有 236 个字符，只定义了 Elasticsearch 映射中的一个字段。而字符串 "description_text "只有 16 个字符。字符数几乎增加了 15 倍，但在描述该字段对可用数据的含义方面却没有任何有意义的改进。如果我们要获取所有索引的映射，但在将其发送到 LLM 之前，将其 "扁平化 "为字段名列表，会怎么样？</p><p>我们试了一下。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>这太棒了！全面改进。但我们能做得更好吗？</p><h3>假设 3：映射 _meta 中的描述</h3><p>如果仅仅是字段名而没有额外的上下文就能带来如此大的跳跃，那么增加大量的上下文可能会更好！每个索引都附加描述并不一定是常规做法，但可以在映射的 _meta 对象中添加任何类型的索引级元数据。我们回到生成的索引，为数据集中的每个索引添加说明。只要描述不是太长，就应该比完整映射使用更少的标记，并能更好地说明索引中包含了哪些数据。我们的实验验证了这一假设。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>稍有改进，我们现在的&gt;90% 准确度全面提高。</p><h3>假设 4：总和大于部分</h3><p>字段名增加了我们的成果。说明增加了我们的成果。因此，<em>同时 </em>使用描述和字段名称应该会得到更好的结果，对吗？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>数据显示 "否"（与上次实验相比没有变化）。这里的主要理论是，由于描述是从索引字段/映射开始生成的，这两个上下文之间没有足够的不同信息，因此在将它们组合在一起时无助于添加任何 "新 "信息。此外，我们为 20 个测试指数发送的有效载荷越来越大。我们迄今为止所遵循的思路是无法扩展的。事实上，我们有充分的理由相信，在有成百上千个索引可供选择的 Elasticsearch 集群上，我们迄今为止进行的所有实验都不会奏效。任何随着索引总数的增加而线性增加发送到 LLM 的信息量的方法，可能都不是通用的策略。</p><p>我们真正需要的是一种方法，它能帮助我们从众多候选人中筛选出最相关的选项...</p><p>这就是一个搜索问题。</p><h3>假设 5：通过语义搜索进行选择</h3><p>如果一个索引的名称具有语义意义，那么它就可以存储为一个向量，并进行语义搜索。</p><p>如果索引的字段名具有语义意义，那么就可以将其存储为向量，并进行语义搜索。</p><p>如果一个索引有一个具有语义意义的描述，那么它也可以存储为一个向量，并进行语义搜索。</p><p>如今，Elasticsearch 索引并不能搜索到这些信息（也许我们应该这样做！），但要想解决这个问题却非常容易<a href="https://github.com/elastic/connectors/pull/3638"> 。</a>利用 Elastic 的连接器框架，我构建了一个连接器，可以为集群中的每个索引输出文档。输出文件将类似于</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>我将这些文件发送到一个新的索引，并在其中手动定义了映射：</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>这样就创建了一个单一的 semantic_content 字段，其他所有具有语义意义的字段都会被分块并编入索引。搜索该索引变得非常简单，只需.....：</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>修改后的<code>index_explorer</code> 工具现在速度<em>更快</em>，因为它不需要向 LLM 提出请求，而是可以为给定的查询请求单个嵌入，并执行高效的向量搜索操作。以最高点击率为选定索引，我们得到的结果是</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>这种方法具有可扩展性。这种方法效率很高。但这种方法比我们的基准线好不了多少。但这并不奇怪，因为这里的搜索方法太天真了。没有任何细微差别。不承认索引的名称和描述应比索引包含的任意字段名称更有分量。没有加权精确词性匹配而非同义匹配的功能。不过，要建立一个高度细致的查询，需要对手头的数据进行大量假设。到目前为止，我们已经对索引和字段名称的语义做了一些大的假设，但我们还需要更进一步，开始假设它们有<em>多大</em>的意义以及它们之间的关系。如果不这样做，我们可能无法可靠地将最佳匹配结果确定为我们的首要结果，但更有可能说最佳匹配结果就在前 N 个结果中的某个地方。我们需要的是一种能够在语义信息存在的语境中消费语义信息的东西，它可以与另一个可能以不同语义方式表示自己的实体进行比较，并在两者之间做出判断。比如法学硕士。</p><h3>假设 6：候选集减少</h3><p>还有很多实验我就不一一列举了，但关键的突破是放弃了纯粹从语义搜索中挑选最佳匹配项的愿望，转而利用语义搜索作为过滤器，从 LLM 的考虑范围中剔除不相关的索引。我们将线性检索、混合检索与 RRF 以及<code>semantic_text</code> 结合起来进行<a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">检索</a>，将结果限制在匹配指数的前 5 位。</p><p>然后，对于每个匹配项，我们都将索引名称、描述和字段名称添加到 LLM 的信息中。结果非常好：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>这是迄今为止精度最高的实验！由于这种方法不会使信息大小与索引总数成正比，因此这种方法的可扩展性要好得多。</p><h2>成果</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>第一个明确的结果是，我们的基线<em>可以</em>改进。现在回想起来，这一点似乎显而易见，但在实验开始之前，我们曾认真讨论过是否应该完全放弃<code>index_explorer</code> 工具，而依靠用户的明确配置来限制搜索空间。虽然这仍然是一个可行且有效的选择，但这项研究表明，在无法获得此类用户输入的情况下，实现索引选择自动化的道路大有可为。</p><p>下一个明确的结果是，一味地增加描述性文字的数量，其回报率会越来越低。在这项研究之前，我们一直在讨论是否应该投资扩展 Elasticsearch 存储<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">字段级元数据</a>的能力。如今，这些<code>meta</code> 值的上限是 50 个字符，而且有一种假设认为，我们需要增加这个值，以便能够从语义上理解我们的字段。但情况显然不是这样，法律硕士似乎只需填写字段名称就可以了。我们以后可能会进一步调查这个问题，但现在已经没有紧迫感了。</p><p>相反，这也清楚地证明了 "可搜索 "索引元数据的重要性。在这些实验中，我们破解了指数的索引。但是，我们可以研究将其直接构建到 Elasticsearch 中，构建应用程序接口来进行管理，或者至少围绕其建立一个惯例。我们将权衡各种选择并进行内部讨论，敬请期待。</p><p>最后，这项工作证实了我们花时间进行试验和做出数据驱动决策的价值。事实上，它帮助我们再次确认，我们的代理生成器产品需要一些强大的产品内评估功能。如果我们需要专门为一个选取指数的工具构建整个测试线束，那么我们的客户绝对需要在进行迭代调整时对其定制工具进行定性评估的方法。</p><p>我很期待看到我们的成果，希望你们也是！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[您的第一个弹性代理：从单一查询到人工智能驱动的聊天]]></title>
    <description><![CDATA[了解如何使用 Elastic 的人工智能代理生成器创建专门的人工智能代理。在本博客中，我们将构建一个金融人工智能代理。]]></description>
    <content:encoded><![CDATA[<p>借助 Elastic 的全新<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">代理生成器</a>，您可以创建专门的人工智能代理，使其成为特定业务领域的专家。该功能使您不再局限于简单的仪表盘和搜索栏，而是将数据从被动的资源转变为主动的对话伙伴。</p><p>想象一下，一位财务经理需要在与客户会面之前加快速度。现在，他们只需向定制的代理直接提问，而无需手动挖掘新闻源和交叉参考投资组合仪表板。这就是"聊天优先" 方法的好处。经理与他们的数据直接对话，询问诸如"ACME 公司的最新消息是什么，它对我客户的持股有何影响？"并在几秒钟内得到综合的专家答复。</p><p>今天，我们正在打造一个金融专家，其应用就像您的数据一样多种多样。同样的能力可以造就一名网络安全分析师来寻找威胁，造就一名现场可靠性工程师来诊断故障，或者造就一名营销经理来优化营销活动。无论在哪个领域，核心任务都是一样的：将您的数据转化为您可以与之交谈的专家。</p><h2>步骤 0：我们的数据集</h2><p>我们当前的数据集是一个基于金融的合成数据集，包含金融账户、资产头寸、新闻和财务报告。虽然它是合成的，但复制了真实金融数据集的简化版本。</p><p><code>financial_accounts</code>:具有风险特征的客户组合</p><p><code>financial_holdings</code>:有购买记录的股票/ETF/债券仓位</p><p><code>financial_asset_details</code>:股票/ETF/债券的详细信息</p><p><code>financial_news</code>:人工智能生成的带有情感分析的市场文章</p><p><code>financial_reports</code>:公司收益和分析师报告</p><p>您可以根据<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">此处的</a>随附笔记本自行加载该数据集。</p><h2>步骤 1：基础--作为 ES|QL 的业务逻辑</h2><p>每一项人工智能技能都以坚实的逻辑为起点。对于我们的财务经理代理，我们需要教它如何回答一个常见问题："我担心市场情绪。你能告诉我哪些客户最容易受到坏消息的影响吗？这个问题超出了简单的搜索范围。这要求我们将市场情绪与客户投资组合联系起来。</p><p>我们需要找到负面文章中提到的资产，识别持有这些资产的每一位客户，计算其风险敞口的当前市值，然后对结果进行排序，优先考虑风险最高的客户。这种复杂的多连接分析是我们先进的 ES|QL 工具的完美工作。</p><p>下面是我们要使用的完整查询。它看起来令人印象深刻，但概念却简单明了。</p><h2>分解：接合点和护栏</h2><p>在这个查询中，有两个重要的概念使代理生成器发挥作用。</p><h3>1.查找联接</h3><p>多年来，Elasticsearch 最受欢迎的功能之一就是根据一个共同的键来连接来自不同索引的数据。有了 ES|QL，<code>LOOKUP JOIN</code> 。</p><p>在我们的新查询中，我们会执行一连串的三个<code>LOOKUP JOIN</code>'s：首先将负面新闻与资产详细信息连接起来，然后将这些资产与客户持有的资产连接起来，最后再与客户的账户信息连接起来。这样，在一次高效的查询中，就能从四个不同的索引中获得极其丰富的结果。这意味着我们可以将不同的数据集结合起来，创建一个具有洞察力的单一答案，而无需事先将所有数据反规范化为一个巨大的索引。</p><h3>2.作为 LLM 护栏的参数</h3><p>您会发现查询使用了<code>?time_duration</code> 。这不仅是一个变量，还是人工智能的护栏。虽然大型语言模型 (LLM) 是生成查询的好帮手，但让它们自由支配数据可能会导致查询效率低下甚至错误。</p><p>通过创建参数化查询，我们迫使 LLM 按照人类专家已经定义的经过测试、高效且正确的业务逻辑工作。这与多年来开发人员使用搜索模板安全地向应用程序公开查询功能的方式类似。代理可以解释用户的请求，如"this week" 来填充<code>time_duration</code> 参数，但它必须使用我们的查询结构来获取答案。这使我们在灵活性和控制性之间取得了完美的平衡。</p><p>最终，这种查询可以让了解数据的专家将其知识封装到一个工具中。其他人和人工智能代理只需提供一个参数，就能使用该工具获得相关结果，而无需了解底层的复杂性。</p><h2>步骤 2：技能--将查询转化为可重复使用的工具</h2><p>在我们将 ES|QL 查询注册为<strong>工具</strong>之前，它只是一个文本。在代理生成器中，工具不仅仅是一个已保存的查询；它还是一个"技能" ，人工智能代理可以理解并选择使用。神奇之处在于我们提供的<strong>自然语言描述</strong>。该描述是连接用户问题和底层查询逻辑的桥梁。让我们注册一下刚刚创建的查询。</p><h3>用户界面路径</h3><p>在 Kibana 中创建工具的过程非常简单。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte73e11c1d87593fa/6a17f2134202294dae29f6f2/a29c53a73b99af5972273c51218ea9004a9b0abb-1600x812.png" alt="如何在 Kibana 中创建工具。" /><p>1.导航至<strong>代理</strong></p><ul><li><p>单击 "<strong> 工具 </strong>"或 "<strong>管理工具</strong>"，然后单击 "<strong>新建工具</strong>"按钮。</p></li></ul><p>2.在表格中填写以下详细信息：</p><ul><li><p><strong>工具 ID：</strong> <code>find_client_exposure_to_negative_news</code></p></li></ul><p>             i.这是工具的唯一 ID</p><ul><li><p><strong>描述</strong> "查找客户投资组合受负面新闻影响的情况。该工具会扫描最近的新闻和报道，查找负面情绪，识别相关资产，并找到持有该资产的所有客户。它会返回一个按头寸当前市值排序的列表，以突出潜在风险最高的头寸。"</p></li></ul><p>             i.法律硕士就是通过阅读这些内容来判断这个工具是否适合这项工作。</p><ul><li><p><strong>标签</strong>：<code>retrieval</code> 和 <code>risk-analysis</code></p></li></ul><p>         标签用于帮助对多个工具进行分组</p><ul><li><p><strong>配置：</strong>粘贴步骤 1 中的完整 ES|QL 查询</p></li></ul><p>            i.这是代理将使用的搜索</p><p>3.单击<strong>从查询中推断参数</strong>。用户界面会自动查找<code>?time_duration</code> ，并将其列在下面。为每项功能添加一个简单的说明，以帮助代理（和其他用户）了解其用途。</p><ul><li><p><code>time_duration</code>:搜索负面新闻的时间范围。格式为"X 小时" 默认为 8760 小时</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7afbb0589c1828ad/6a17f2146864a44e7cb688a9/deb422d97863f78dbe08bfa2e3c708d1f75166ff-1600x938.png" alt="使用 ESQL 查询配置工具，包括其逻辑和所需参数。 " /><p>4.测试一下！</p><ul><li><p>单击保存&amp; 测试。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd09afbef6e21a93/6a17f2162f4a5c73b1fa89fd/57e768b88327821e70bd616744822f98fa367362-732x136.png" alt="&amp; 测试按钮。" /><ul><li><p>您将看到一个新的快捷方式，可以在此测试查询，以确保其工作符合预期。</p></li></ul><p>             i.在<code>time_duration</code> 中输入所需的范围，这里我们使用 "8760 小时"。</p><ul><li><p>点击 "提交"，如果一切顺利，您将看到一个 JSON 响应。要确保它按预期运行，请向下滚动并查看<code>values</code> 对象。这就是返回实际匹配文档的地方。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bdc3f093363f2a/6a17f217be60861c9c00488a/7e0c5171a4f7ffdfc1830f1a05a9acb987870b75-1600x722.png" alt="点击提交后出现的 JSON 响应。" /><p>5.点击右上角的 "X "关闭测试窗口。现在，您的新工具将出现在列表中，随时可以分配给代理。</p><h3>应用程序接口路径</h3><p>对于喜欢自动化或需要以编程方式管理工具的开发人员来说，只需调用一个 API 就能实现同样的效果。只需向带有工具定义的<code>/api/agent_builder/tools</code> 端点发送<code>POST</code> 请求即可。</p>POST kbn://api/agent_builder/tools
{
  "id": "find_client_exposure_to_negative_news",
  "type": "esql",
  "description": "Finds client portfolio exposure to negative news. This tool scans recent news and reports for negative sentiment, identifies the associated asset, and finds all clients holding that asset. It returns a list sorted by the current market value of the position to highlight the highest potential risk.",
  "configuration": {
    "query": """
        FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) &gt;= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50
      """,
    "params": {
      "time_duration": {
        "type": "keyword",
        "description": """The timeframe to search back for negative news. Format is "X hours" DEFAULT TO 8760 hours """
      }
    }
  },
  "tags": [
    "retrieval",
    "risk-analysis"
  ]
}<h2>步骤 3：大脑--创建您的定制代理</h2><p>我们开发了一种可重复使用的技能（工具）。现在，我们需要创建<strong>代理</strong>，即实际使用它的角色。代理是一个 LLM 的组合，是你授予它访问权限的一套特定工具，最重要的是，它还包含一套<strong>自定义指令</strong>，作为它的章程，定义了它的个性、规则和目的。</p><h3>提示的艺术</h3><p>要创建一个可靠的专业代理，最重要的一点就是要及时。一套精心设计的指令是普通聊天机器人与专注、专业的助手之间的区别所在。在这里，你可以设置防护栏、定义输出并赋予代理任务。</p><p>对于<code>Financial Manager</code> 代理，我们将使用以下提示。</p>You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

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

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

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

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

**Start by greeting the financial manager and offering assistance.**<p>让我们来分析一下为什么这个提示如此有效：</p><ul><li><p><strong>它定义了一个成熟的角色： </strong>第一句话立即将代理人定位为"专业的数据智能助理，" 定下了专业、干练的基调。</p></li><li><p><strong>它提供了一个推理框架： </strong>通过告诉代理"Understand（理解）、Plan（计划）、Execute（执行）和 Synthesize（综合），" ，我们给了它一个标准的操作程序。这提高了它处理复杂、多步骤问题的能力。</p></li><li><p><strong>它促进了互动对话： </strong> "提出澄清性问题的指令" 使代理更加稳健。这将最大限度地减少对模棱两可的请求做出不正确的假设，从而获得更准确的答复。</p></li></ul><h3>用户界面路径</h3><p>1.导航至<strong>代理。</strong></p><ul><li><p>单击 "<strong> 工具 </strong>"或 "<strong>管理工具</strong>"，然后单击 "<strong>新建工具</strong>"按钮。</p></li></ul><p>2.填写基本信息：</p><ul><li><p><strong>代理编号：</strong> <code>financial_assistant</code>.</p></li><li><p><strong>说明 </strong>复制上面的提示。</p></li><li><p><strong>标签</strong> <code>Finance</code>.</p></li><li><p><strong>显示名称：</strong> <code>Financial Assistant</code> 。</p></li><li><p><strong>显示说明： </strong><code>An assistant for analyzing and understanding your financial data</code> 。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ac12cbd2b689dee/6a17f219dbb4ff262bfb57ef/18ea73f1cae620129c0afa0e7ba9e2a3390224a7-1600x1189.png" alt="创建财务助理--填写代理人 ID 字段。" /><p>3.回到顶部，点击 "<strong>工具</strong>"。</p><ul><li><p>勾选<code>find_client_exposure_to_negative_news</code> 工具旁边的复选框。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcd23556e556a76c5/6a17f21baf47b63a9fcde0a0/0c1e4ecbbd51d0dd10c6e861dbe9a9ccddeb35f6-1600x149.png" alt="" /><p>4.单击<strong>保存</strong>。</p><h3>应用程序接口路径</h3><p>您可以通过<code>POST</code> 请求<code>/api/agent_builder/agents</code> 端点来创建完全相同的代理。请求正文包含所有相同的信息：ID、名称、描述、全套指令以及允许代理使用的工具列表。</p>POST kbn://api/agent_builder/agents
    {
      "id": "financial_assistant",
      "name": "Financial Assistant",
      "description": "An assistant for analyzing and understanding your financial data",
      "labels": [
        "Finance"
      ],
      "avatar_color": "#16C5C0",
      "avatar_symbol": "💰",
      "configuration": {
        "instructions": """You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

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

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

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

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

**Start by greeting the financial manager and offering assistance.**
""",
        "tools": [
          {
            "tool_ids": [
              "platform.core.search",
              "platform.core.list_indices",
              "platform.core.get_index_mapping",
              "platform.core.get_document_by_id",
              "find_client_exposure_to_negative_news"
            ]
          }
        ]
      }
    }<h2>步骤 4：回报--进行对话</h2><p>我们已将业务逻辑封装在一个工具和一个"大脑" 中，准备在我们的 Agent 中使用它。是时候见证这一切了。现在，我们可以使用专门的代理与数据聊天了。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8826539b16e46f4/6a17f21d505ac35924ad8c5c/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" alt="创建财务助理后与弹性代理生成器对话。" /><h3>用户界面路径</h3><ol><li><p>导航至 Kibana 中的<strong>代理 </strong>。</p></li><li><p>使用聊天窗口右下角的下拉菜单，从默认的<strong>Elastic AI 代理</strong>切换到我们新创建的<strong>财务助理 </strong>代理。</p></li><li><p>请提出一个问题，以便代理人使用我们的专业工具：</p><ol><li><p><em>我担心市场情绪。您能告诉我哪些客户最容易受到坏消息的影响吗？</em></p></li></ol></li></ol><p>片刻之后，代理将返回一个格式完美、内容完整的答案。由于法律硕士的性质，您的答案格式可能会略有不同，但这次运行中，代理返回的答案是一样的：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1e163fd7c4416bd/6a17f21f6864a4e35bb688ad/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" alt="由 Elastic Agent Builder 创建的回复，为：最易受负面新闻影响的客户提供财务助理。" /><h3>刚刚发生了什么？代理人的推理</h3><p>该特工并不只是"知道" 答案。它以选择最佳工具为中心，执行了一个多步骤计划。下面我们来看看它的思考过程：</p><ul><li><p><strong>识别意图：</strong>它将您问题中的关键字，如"风险" 和"负面新闻、" 与<code>find_client_exposure_to_negative_news</code> 工具的描述相匹配。</p></li><li><p><strong>执行计划：</strong>它从您的请求中提取了时间范围，并对该专业工具进行了<strong>一次调用</strong>。</p></li><li><p><strong>委托工作：</strong>然后，该工具就能完成所有繁重的工作：链式连接、值计算和排序。</p></li><li><p><strong>合成结果：</strong>最后，代理按照提示规则，将来自工具的原始数据格式化为清晰、人类可读的摘要。</p></li></ul><p>如果我们拓展思维，看到更多细节，我们就不只是猜测了。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93f6075be8495418/6a17f221af47b65eadcde0a4/6a4da9262d3f88c60bfd8f8bf9b67c3b84e961ba-1600x607.png" alt="这 50 份文件记录了财务助理发现的受负面新闻影响最大的客户。" /><h3>应用程序接口路径</h3><p>您也可以通过编程来启动同样的对话。只需将输入问题发送到<code>converse</code> API 端点，确保指定我们的<code>financial_manager</code> 的<code>agent_id</code> 。</p>POST kbn://api/agent_builder/converse
{
  "input": "Show me our largest positions affected by negative news",
  "agent_id": "financial_assistant"
}<h2>致开发人员：与应用程序接口集成</h2><p>虽然 Kibana UI 为构建和管理代理提供了美妙而直观的体验，但您今天所看到的一切也都可以通过编程来实现。代理生成器基于一套应用程序接口（API）构建，允许您将此功能直接集成到自己的应用程序、CI/CD 管道或自动化脚本中。</p><p>您将使用的三个核心端点是</p><ul><li><p><strong><code>/api/agent_builder/tools</code></strong>:创建、列出和管理可重复使用的技能的终端。</p></li><li><p><strong><code>/api/agent_builder/agents</code></strong>:角色：定义代理角色的终端，包括重要的说明和工具分配。</p></li><li><p><strong><code>/api/agent_builder/converse</code></strong>:与代理互动、开始对话和获取答案的终端。</p></li></ul><p>有关使用这些应用程序接口执行本教程中每一步的完整实践演示，请查看我们 GitHub 软件仓库中的配套<strong>Jupyter Notebook</strong> <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">。</a></p><h2>总结：轮到你来建设</h2><p>我们首先使用 ES|QL 查询，并将其转换为可重复使用的技能。然后，我们建立了一个专门的人工智能代理，赋予它明确的任务和规则，并赋予它这种技能。它是一个复杂的助手，能够理解复杂的问题，并执行多步骤分析，提供精确的数据驱动型答案。</p><p>这一工作流程是 Elastic 中新的<strong>代理生成器</strong>的核心。它的设计足够简单，非技术用户可以通过用户界面创建代理，但又足够细致，开发人员可以在我们的应用程序接口基础上构建定制的人工智能驱动应用程序。最重要的是，它可以让您安全可靠地将 LLM 连接到自己的数据，由您定义的专家逻辑进行管理，并与您的数据进行聊天。</p><h2>准备好使用代理与您的数据聊天了吗？</h2><p>巩固所学知识的最好方法就是动手实践。在我们的<a href="https://www.elastic.co/training/elastic-ai-agents-mcp"><strong>免费互动实践研讨会</strong></a>上，尝试我们今天讨论的所有内容。您将在专门的沙盒环境中经历整个流程以及更多。</p><p>在今后的博客中，我们将向您展示如何使用独立应用程序与我们的<code>Financial Assistant</code> 代理交互，并深入探讨使这一切成为可能的<strong>模型上下文协议 (MCP)</strong>。在另一篇博客中，我们将讨论 Agent Builder 对开发中的 Agent2Agent（或 A2A）协议的支持。</p><p>敬请期待，祝您建筑愉快！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe5e78eeb775d715/6a17f2230b0bed719ddd369a/ca853555eaa213f10f1db8c0ab0a2bbacee97b88-1456x816.png" length="0" type="image/png"/>
    <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 构建人工智能代理工作流]]></title>
    <description><![CDATA[了解代理生成器（Agent Builder），它是 Elasticsearch 中的一个新人工智能层，为构建人工智能代理工作流提供了一个框架，使用混合搜索为代理提供推理和行动所需的上下文。]]></description>
    <content:encoded><![CDATA[<p>在 Elastic，我们通过人工智能助手、高级 RAG 和矢量数据库的改进，为 LLM 和对话界面带来了语境。最近，随着人工智能代理的兴起，我们发现对相关上下文的需求日益增长，并了解到高效的<strong> 人工智能代理需要出色的搜索</strong>。因此，我们在 Elastic Stack 中构建了新的本地功能，旨在帮助开发可利用 Elasticsearch 中数据的人工智能代理。我们希望与大家分享我们在这一历程中取得的进展，以及我们对下一步发展的展望。</p><h2>代理生成器：构建数据驱动型人工智能代理的基础</h2><p>人工智能代理的承诺很简单：给它一个目标，它就能完成工作。但对于开发商来说，现实却是一系列复杂的挑战。首先，代理的能力取决于其对环境的感知以及为实现用户目标而提供的工具。那么，如何从纷繁复杂的企业数据中提供正确的上下文是一项巨大的挑战。最后，所有这一切都必须由一个可靠的推理循环来协调，该循环可以进行规划、执行和学习。</p><p>为了解决这个问题，开发人员需要从头开始构建一个复杂而脆弱的堆栈。如今的代理架构需要将多个不同的部分拼接在一起：一个 LLM、一个向量数据库、一个元数据存储、用于日志记录和跟踪的独立系统，以及一些评估它们是否都能正常工作的方法。这不仅复杂，而且成本高昂、容易出错，并且难以建立用户所需的高质量、值得信赖的人工智能系统。</p><p>因此，我们想让它变得更简单。为此，我们的方法是将有效的上下文驱动型代理的重要部分直接集成到 Elasticsearch 的核心中，并提供一套名为<strong>Elastic AI Agent Builder</strong> 的新功能。这一新层提供了一个框架，其中包含创建由 Elasticsearch 支持的人工智能代理所需的所有基本构件：一套开放的基元、基于标准的协议和对数据的安全访问--因此您可以根据真实世界的数据和要求构建代理系统：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2779dae5df010328/6a17e15eabe0f24f18dfe931/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" alt="" /><p><strong>提供人工智能体验</strong>：这是终极目标。以我们的搜索人工智能平台和您的数据为基础，您可以构建任何类型的生成式人工智能应用程序：从定制聊天界面到与 LangChain 等代理框架或 Salesforce 等业务应用程序的集成。</p><p><strong>由 Agents&amp; 工具提供支持</strong>：在平台之上，我们提供了一个简洁的抽象层。您可以直接与代理和工具互动，并根据具体需求进行定制。您还可以通过强大的应用程序接口和开放标准（如 MCP 和 A2A）访问平台的功能。</p><p><strong>由搜索人工智能平台支持</strong>：这是我们集成了各种组件的核心引擎。先进的矢量数据库、代理逻辑、查询结构、安全功能、评估跟踪都在这里，由 Elastic 管理和优化。</p><p><strong>释放数据的力量</strong>：任何优秀代理商的基础都是优秀的数据。我们的平台首先能够摄取或联合访问您的所有企业数据</p><h2>平台中的代理建设</h2><p>Agent Builder 集成到搜索人工智能平台中，为代理开发提供了一个完整的框架。它建立在五个关键支柱之上，每个支柱都旨在解决构建和部署生产级人工智能系统的一个关键方面。让我们来分析一下，代理如何定义目标，工具如何提供功能，开放标准如何确保互操作性，评估如何提供透明度，安全如何提供信任。</p><h3>代理商</h3><p>代理是 Elasticsearch 这一新层中最高级别的构建模块。代理定义了要实现的目标、可用于执行的工具集以及可操作的数据源。代理并不局限于对话式交互，它们还可以支持完整的工作流、任务自动化或面向用户的体验。</p><p>当一项查询被提交给代理机构时，它遵循一个结构化的循环：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt774ffd7df65bd01d/6a17e15f25daabd5cc08a17f/627ad1744b629bbe27359325702f40d97e40d1f4-704x852.png" alt="" /><ol><li><p>解释您的意见和目标</p></li><li><p>选择正确的执行工具和参数</p></li><li><p>工具响应的原因</p></li><li><p>决定是返回结果还是继续进一步调用工具</p></li></ol><p>Elastic 负责这一循环的协调、上下文和执行。开发人员专注于定义代理应该做<em>什么</em>：目标、工具和数据，而系统则管理<em>如何</em>进行推理和执行工作流程。</p><p><em>默认代理</em></p><p>我们在该平台上构建的第一个代理是 Kibana 中的原生会话代理，让您能够立即与数据进行交互。它在提供即用体验的同时，还具有完全的可扩展性，无需额外配置即可立即开始与数据交互。</p><p>您可以直接在 Kibana 中通过新的聊天用户体验或通过 API 与此体验进行交互。</p><p>通过 API 查询默认代理只需一次调用：</p>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}<p>由于会话是有状态的，因此您可以使用会话 ID 继续与代理交互，或检索完整的会话历史记录：</p>POST kbn://api/agent_builder/converse
{
    "input": "What about the second top?",
    "conversation_id": "ec757c6c-c3ed-4a83-8e2c-756238f008bb"
}

## get the full conversation
GET kbn://api/agent_builder/conversations/ec757c6c-c3ed-4a83-8e2c-756238f008bb<p><em>海关代理</em></p><p>开发人员还可以通过简单的应用程序接口创建自己的定制代理。代理封装了指令、工具和数据访问，创建了量身定制的推理引擎。</p><p>创建自定义代理只需调用一次应用程序接口。下面的示例显示了一个例子，"配置 "字段包含所有关键细节，如说明或可用工具：</p>POST kbn://api/agent_builder/agents
{
  "id": "custom_agent",
  "name": "My Custom Agent",
  "description": "Description of the custom agent",
  "configuration": {
      "instructions": "You are a log expert specialising in ...",
      "tools": 
...
   }
}<p>一旦创建，就可以直接查询代理：</p>POST kbn://api/agent_builder/converse
{
    "input": "What news about DIA?",
    "agent_id": "custom_agent"
}<p>这种方法将代理从一个需要从头开始构建的复杂系统转变为一个简单、声明式的业务逻辑单元，使您能够更快地交付智能自动化。</p><p>如需深入了解如何从头开始构建专门的代理，请参阅我们的详细分步指南：<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">您的第一个弹性代理：从单一查询到人工智能驱动的聊天</a>。</p><h3>工具</h3><p>如果说代理确定了要完成的<em>任务</em>，那么工具则确定了<em>如何</em>完成。</p><p>工具为代理执行和检索信息或执行操作暴露了特定的弹性核心功能。工具可以包括获取索引或获取映射等核心功能，也可以包括从自然语言到 ES|QL 等更高级的功能。</p><p>Elasticsearch 随附一套针对常见需求进行了优化的默认工具。但真正的灵活性来自于自己的创造。通过定义工具，您可以决定将哪些查询、索引和字段通过 ES|QL 暴露给代理，从而对速度、准确性和安全性进行精确控制。</p><p>注册新工具也很简单，只需调用一次应用程序接口。您可以创建一个工具，利用我们的<a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">ES|QL（Elasticsearch 查询语言）</a>查找特定金融资产的相关新闻：</p>POST kbn://api/agent_builder/tools
{
  "id": "news_on_asset",
  "type": "esql",
  "description": "Find news and reports about a particular asset where ...",
  "configuration": {
    "query": "FROM financial_news, financial_reports | where MATCH(company_symbol, ?symbol) OR MATCH(entities, ?symbol) | limit 5",
    "params": {
      "symbol": {
        "type": "keyword",
        "description": "The asset symbol"
      }
    }
  ...
  }
...
}<p>注册后，您就可以将新工具分配给您的自定义代理，为他们提供一套经过精心设计的能力，让他们在合适的时候进行推理和调用。</p><p>我们提供了一个平台，可根据您的特定需求创建定制工具，例如使用 ES|QL，将代理从通用代理转变为特定领域的专家，立足于您独特的数据和业务领域。</p><h3>开放标准和互操作性</h3><p>Elasticsearch 代理和工具通过开放式标准 API 公开，因此很容易作为基础模块集成到更广泛的代理框架生态系统中。我们的方法很简单：没有黑盒子。我们希望您能够利用 Elastic 在搜索方面的核心优势，并将其与互补功能和其他代理系统搭配使用。</p><p>为了实现这一点，我们正在通过应用程序接口、新兴协议和开放标准公开我们的能力。</p><p><em>模型上下文协议（MCP）</em></p><p><a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">模型上下文协议（MCP）</a>正迅速成为跨系统连接工具的开放标准。通过支持 MCP，Elasticsearch 可以将对话式人工智能与您的数据库、索引和外部 API 相连接。通过 Elastic Stack 内置的远程 MCP 服务器，任何兼容 MCP 的客户端都可以访问 Elastic 的工具，并将其用作大型代理工作流程的构建模块。</p><p>这不是一条单行道。您还可以从外部 MCP 服务器导入工具，使其在 Elasticsearch 中可用。不久之后，MCP 服务器将可能适用于几乎所有功能，而且比我们自己创建的任何功能都要全面得多。Elastic 提供大规模的搜索和检索功能，您可以将其与其他平台的专业功能相结合，构建有效的代理。</p><p><em>代理对代理（A2A）</em></p><p>我们还在努力提供代理对代理 (A2A) 支持。MCP 是连接工具，而 A2A 则是连接代理。有了 A2A 服务器，您构建的 Elastic 代理就能与其他系统的代理直接对话：共享上下文、委派任务和协调工作流。</p><p>将其视为推理层的互操作性。您的弹性代理可以处理搜索和检索，然后将任务交给专门的支持或 IT 代理，并无缝地返回结果。这样就形成了一个由合作代理组成的生态系统，每个代理都在做自己最擅长的事情。</p><p>最终，采用 MCP 和 A2A 加强了我们对 Elasticsearch 作为一流公民角色的承诺，确保在更广泛的代理生态系统中实现开放式集成。</p><h3>追踪和评估</h3><p>随着搜索与代理的整合，有效评估的挑战变得至关重要。要在真实的企业环境中自信地部署代理，就必须确保代理不仅准确，而且高效可靠。如何衡量性能、诊断不良响应或改进基线？一切从可见度开始。</p><p>因此，我们从一开始就设计了透明的代理 API。考虑一下这个简单的代理互动：</p>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}<p>回复不仅包括最终答案，还包括完整的执行跟踪，详细说明代理选择了哪些工具、使用了哪些参数以及每一步的结果。</p>{
  "conversation_id": "db5c0c8b-12bf-4928-a57e-d99129ad2fea",
  "steps": [
    {
      "type": "tool_call",
      "tool_call_id": "tooluse_Nfqr3mwtR92HTRIsTcGXZQ",
      "tool_id": ".index_explorer",
      "params": {
        "query": "indices containing portfolio data"
      },
      "results": [...]
    }
    // ... more steps ...
  ],
  "response": {
    "message": "Based on the information I've gathered...."
  }
}<p>全面的跟踪和日志记录对持续改进循环至关重要，不久之后，您就可以直接在 Elasticsearch 中存储和查看这些代理跟踪。更妙的是，这些跟踪记录是基于 OpenTelemetry 协议构建的，确保了它们的标准化和可移植性，以便与您选择的可观测性平台集成。</p><p>这种详细程度是真正持续改进循环的基础。它使您能够建立一套全面的测试、调试故障、识别失败模式以防止回归，并捕捉成功模式以微调性能。归根结底，这种数据驱动的方法是将有前途的原型转化为生产级、值得信赖的人工智能系统的关键。</p><h3>安全性</h3><p>随着代理和工具的功能越来越强大，安全性不再是可有可无的，而是基础性的。要公开应用程序接口、自动执行任务和工作流程，就必须信任企业系统。特别是当代理开始自动执行更多的工作流程时，确保这些流程安全并满足企业要求的能力就显得尤为重要。</p><p>上述功能都继承了 Elastic 目前已有的控制功能，包括针对 API 调用和 API 密钥管理的<a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">基于角色的访问控制 (RBAC)</a>。我们还将同样的控制扩展到 MCP 等新协议。这意味着支持 OAuth 等标准，以及插入自定义身份验证机制的能力。</p><p>我们的目标是让您灵活地尝试使用代理和工具，同时保持组织所需的安全性、合规性和管理水平。</p><h2>下一步行动</h2><p>我们不仅要增加功能，还要扩展 Elasticsearch 的代理上下文工程。我们计划在这些原则的基础上继续发展：</p><p>1.致力于开放源码&amp; 标准</p><p>我们致力于开放源代码和开放标准，确保这些功能与外部代理框架保持互操作性。您始终能够在生态系统中连接、扩展和组成代理，同时将数据和工作流程置于您的控制之下。</p><p>2.背景的价值</p><p>人工智能代理的背景是其最大的资产。在代理执行搜索和工作流操作时管理上下文是一项极具挑战性的任务。我们正在利用 Elastic 的核心优势来解决上下文工程问题，确保您的代理始终可以获得最相关的信息。</p><p>3.关注代理数据流</p><p>展望未来，代理将成为越来越大的数据源，包括代理的输出（生成的文档、报告、可视化）和代理的执行轨迹（其思维、工具调用、内存/上下文）。Elastic 非常适合处理此类数据，我们正在研究如何利用这些数据进行分析、评估和自动改进。</p><p>4.设计的安保和安全</p><p>人工智能代理带来了全新的安全保障挑战。Elastic 一直是安全解决方案的领导者，我们将继续构建企业级防护、访问控制和"零信任" 原则。</p><p>5.嵌入平台</p><p>构建人工智能代理的功能已嵌入 Elasticsearch 平台。这意味着平台级功能，如跟踪、评估、可视化和分析，都适用于代理。希望根据代理执行情况开发仪表板--这是内置功能。希望通过情感分析来评估人工智能代理的性能--该平台可以实现这一点。这样就能围绕人工智能体验构建一个完整的生命周期。</p><p>Elastic 的目标是为您提供建立对话式人工智能和自动化工作流程的接口，这些接口完全集成、可扩展并以您的数据为基础。更多技术细节和进展情况将很快与大家分享。</p><p>代理生成器 "现已推出私人预览版。<a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">与我们联系</a>，申请访问。有问题或反馈？在我们的<a href="https://elasticstack.slack.com/archives/C09GRHEQ4AG"><strong>Slack 工作区</strong></a>或<a href="https://discuss.elastic.co/c/search/84"><strong>讨论区</strong></a>与我们的开发人员社区联系。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Anish Mathur,Dana Juratoni]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16a3d8736bf086e0/6a17e1616864a45410b686c7/71876470119e02a45bcbfcbf27a3e110328bbd14-1020x654.png" length="0" type="image/png"/>
    <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[矢量搜索过滤：保持相关性]]></title>
    <description><![CDATA[仅靠矢量搜索来查找与查询最相似的结果是不够的。要缩小搜索结果的范围，通常需要进行筛选。本文介绍了在 Elasticsearch 和 Apache Lucene 中如何对矢量搜索进行过滤。]]></description>
    <content:encoded><![CDATA[<p>矢量搜索不足以找到相关结果。使用过滤标准非常常见，这有助于缩小搜索结果的范围并过滤掉不相关的结果。</p><p>了解筛选在矢量搜索中是如何工作的，将有助于你平衡性能和召回率之间的权衡，并发现一些优化方法，使矢量搜索在使用筛选时性能更佳。</p><h2>为什么要过滤？</h2><p>矢量搜索彻底改变了我们在大型数据集中查找相关信息的方式，使我们能够发现与查询语义相似的项目。</p><p>然而，仅仅找到相似的物品是不够的。我们经常需要根据特定的标准或属性来缩小搜索结果的范围。</p><p>想象一下，您正在一家电子商务商店中搜索产品。纯矢量搜索可能会显示视觉上相似的商品，但您可能还想根据价格范围、品牌、可用性或客户评价进行筛选。如果不进行筛选，您就会看到大量类似的产品，很难准确找到您要找的产品。</p><p>过滤功能可对搜索结果进行精确控制，确保检索到的项目不仅在语义上一致，而且符合所有必要的要求。这将带来更加准确、高效和用户友好的搜索体验。</p><p>这正是 Elasticsearch 和 Apache Lucene 的优势所在--对各种数据类型进行有效过滤是它们与其他矢量数据库的主要区别之一。</p><h2>精确矢量搜索的筛选</h2><p>进行精确矢量搜索主要有两种方法：</p><ul><li><p>为 dense_vector 字段使用<code>flat</code> 索引类型。这使得<code>knn</code> 搜索使用精确搜索而不是近似搜索。</p></li><li><p>使用<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions"> script_score 查询</a> ，该 查询 使用向量函数计算分数。这可用于任何索引类型。</p></li></ul><p>在执行精确向量搜索时，所有向量都会与查询进行比较。在这种情况下，过滤将有助于提高性能，因为只需要比较通过过滤的向量。</p><p>这不会影响结果质量，因为所有向量都会被考虑在内。我们只是提前过滤掉不感兴趣的结果，从而减少操作次数。</p><p>这一点非常重要，因为当应用筛选器得到的文档数量很少时，执行精确搜索比近似搜索更有效。</p><p>经验法则是，当通过过滤器的文件少于 10k 时，应使用精确搜索。<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>索引的比较速度更快，因此当基于索引的数据少于 100k 时，使用精确搜索是合理的。详情请查看<a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">本博文</a>。</p><p>如果您的筛选器总是限制性很强，您可以考虑使用<code>flat</code> 索引类型而不是基于 HNSW 的索引类型，将索引重点放在精确搜索而不是近似搜索上。更多详情，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">index_options 的属性</a>。</p><h2>近似矢量搜索的筛选</h2><p>在执行近似向量搜索时，我们需要用结果的准确性来换取性能。像 HNSW 这样的矢量搜索数据结构可在数百万个矢量上高效搜索近似近邻。它们的重点是通过进行最少的向量比较来检索最相似的向量，而向量比较的计算成本很高。</p><p>这意味着其他过滤属性不属于矢量数据的一部分。不同的数据类型有自己的索引结构，如术语字典、发布列表和 doc 值等，可以有效地查找和过滤这些数据。</p><p>既然这些数据结构与矢量搜索机制是分开的，那么我们如何将过滤功能应用于矢量搜索呢？有两种选择：在矢量搜索后应用过滤器（后过滤）或在矢量搜索前应用过滤器（预过滤）。</p><p>每种方案都各有利弊。让我们深入了解它们！</p><h3>后过滤</h3><p>后过滤在矢量搜索完成后应用过滤器。这意味着，在找到前 k 个最相似的向量结果后，才会应用筛选器。</p><p>显然，在对结果进行筛选后，我们可能会得到少于 k 个结果。当然，我们可以从矢量搜索中获取更多的结果（k 值更高），但我们无法确定在应用过滤器后是否会得到 k 或更多的结果。</p><p>后过滤的优势在于它不会改变矢量搜索的运行时行为--矢量搜索不知道过滤的存在。但是，它确实会改变检索结果的最终数量。</p><p>下面是使用<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">knn 查询</a>进行后过滤的示例。检查过滤子句是否与 knn 查询分开：</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p>使用后置<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">过滤器</a>还可对 knn 搜索进行后置过滤：</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>请记住，您需要在 knn 搜索中使用明确的后置过滤器部分。如果不使用后置过滤器，knn 搜索<a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features"> 会将最近邻</a> 搜索 结果 与其他查询或过滤器 结合起来 ，而不是进行后置过滤器。</p><h3>预过滤</h3><p>在矢量搜索前应用筛选器将首先检索出满足筛选条件的文档，然后将这些信息传递给矢量搜索。</p><p>Lucene 使用<a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a>高效地存储满足筛选条件的文档。然后，矢量搜索会遍历 HNSW 图，并将满足条件的文档考虑在内。在将候选文件添加到结果中之前，它会检查该候选文件是否包含在有效文件的 BitSet 中。</p><p>不过，即使候选文件不是有效文件，也必须对其进行探索并与查询进行比较。HNSW 的有效性取决于图中向量之间的联系--如果我们停止探索某个候选向量，就意味着我们可能也会跳过它的邻近向量。</p><p>就像开车去加油站一样。如果放弃任何一条没有加油站的道路，您就不可能到达目的地。其他道路可能不是你所需要的，但它们将你<em>连接</em>到目的地。HNSW 图形上的向量也是如此！</p><p>因此，应用预过滤比不应用过滤的性能要低。我们需要对搜索中访问的<em>所有</em>向量进行处理，并丢弃不符合筛选条件的向量。我们正在做更多的工作，花更多的时间来获得最高 K 值的结果。</p><p>下面是在 Elasticsearch 查询 DSL 中进行预过滤的示例。检查过滤子句是否已成为 knn 部分的一部分：</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p><a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">knn 搜索</a>和<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">knn 查询</a>均可使用预过滤功能：</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>预过滤优化</h4><p>我们可以进行一些优化，以确保预过滤的性能。</p><p>如果筛选条件非常严格，我们可以切换到精确搜索。当需要比较的向量很少时，对满足筛选条件的少数文档进行精确搜索会更快。</p><p>这是<a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a>和 Elasticsearch 自动应用的优化。</p><p>另一种优化方法是忽略不符合筛选条件的向量。相反，该方法会检查滤波向量的邻近向量是否通过滤波。这种方法不考虑过滤后的向量，而是继续探索与当前路径相连的向量，从而有效减少了比较次数。</p><p>这种算法就是 ACORN-1，<a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">本篇博文</a>将详细介绍其过程。</p><h2>使用文档级安全过滤</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">文档级别安全（DLS）</a>是 Elasticsearch 的一项功能，可指定用户角色可检索的文档。</p><p>DLS 通过查询来执行。一个角色可以有一个与索引相关联的查询，这实际上限制了属于该角色的用户可以从索引中检索的文档。</p><p>角色查询用作过滤器，用于<a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">检索与之匹配的文档</a>，并作为 BitSet 缓存。然后，这个 BitSet 会被用来封装底层的 Lucene 阅读器，因此只有从查询返回的文档才会被认为是<em>实时的</em>，也就是说，它们存在于索引中，并且没有被删除。</p><p>由于要<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">从阅读器获取</a>实时文档来执行 knn 查询，因此只考虑用户可用的文档。如果有预检器，DLS 文件将被<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204"> 添加到 预检器 中</a> 。</p><p>这意味着，DLS 过滤可以作为近似矢量搜索的预过滤，具有相同的性能影响和优化效果。</p><p>使用精确搜索的 DLS 与应用任何过滤器的好处相同--从 DLS 检索的文档越少，精确搜索的性能就越高。还要考虑 DLS 返回的文档数量--如果 DLS 的作用非常有限，可以考虑使用精确搜索而不是近似搜索。</p><h2>基准</h2><p>在 Elasticsearch，我们希望确保矢量搜索过滤的效率。我们有<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">一个专门的向量过滤基准</a>，通过不同的过滤执行近似向量搜索，以确保向量搜索尽可能快地检索到相关结果。</p><p>查看 ACORN-1 推出时的<a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">改进</a>情况。在只有 2% 个向量通过过滤器的测试中，查询延迟时间缩短到原来的 55% ：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>结论</h2><p>过滤是搜索不可或缺的一部分。确保过滤在矢量搜索中的性能，并了解权衡和优化，是高效和准确搜索的关键所在。</p><p>过滤会影响向量搜索的性能：</p><ul><li><p>使用过滤功能时，精确搜索速度更快。如果过滤条件足够严格，应考虑使用精确搜索而不是近似搜索。这是 Elasticsearch 的自动优化功能。</p></li><li><p>使用预过滤时，近似搜索速度较慢。通过预过滤，我们可以得到与过滤器匹配的前 k 个结果，但搜索速度会减慢。</p></li><li><p>后过滤并不一定能检索到前 k 个结果，因为在应用过滤器时，这些结果可能已被过滤器过滤。</p></li></ul><p>快乐过滤</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>