Spring AI集成Elasticsearch向量检索时filter过滤失效问题排查与解决方案

使用vectorStore.similaritySearch遇到问题

最近需要做一个功能,用到了es做向量数据库。在使用vectorStore.similaritySearch查询的时候,发现filterExpression中加的条件并没有完全生效,导致查询出来的数据不准确,出现了不符合metadata筛选条件的数据。然后研究了一下,发现了问题所在。

先说结论,Spring AI调用eselasticsearchClient.search方法查询的时候,使用的是filter过滤,用的是queryString。导致出现特殊字符的时候,没有转义的话,会出现歧义调用或者报错。
org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore#doSimilaritySearch
在这里插入图片描述

插入数据

下面是添加数据到es的部分代码,实际代码是批量处理,这里改了一些。text做完向量化之后,会存到embedding字段。而metadata部分会存到metadata字段,是一个对象类型。这一部分没有遇到问题,数据都正常插入了。

Document document = Document.builder().id(entity.bizid()).text(entity.description());.metadata("a1", entity.a1()).metadata("a2", entity.a2()).build();
// 使用vectorStore.add的时候,会自动调用embedding模型
vectorStore.add(documentList);

在这里插入图片描述
在这里插入图片描述

查询数据

我现在的需求是metadata里面的数据,都需要精确查询(完全匹配),就好比数据库中的where a1 = 'xxx'。当我a1加上了某08_1表啥≠“2”(调)或 “7”(叠加)时条件时,发现查询出来的数据,出现了a1为其他值的情况,这明显不符合项目要求。
查询数据的代码,做了部分修改:

public List<Document> query(@RequestBody QueryDTO query) {SearchRequest.Builder searchBuilder = SearchRequest.builder().query(query.description()).similarityThreshold(0.7);FilterExpressionBuilder b = new FilterExpressionBuilder();FilterExpressionBuilder.Op finalOp = null;// 构建过滤表达式// 如果a1有值,就加上a1条件,key实际上会被处理成metadata.a1.keywordif (query.a1() != null) {finalOp = b.eq("a1.keyword", query.a1());}// 同上,但是可能会存在a1也有值的情况,所以下面要做个判断if (query.a2() != null && !query.a2().isEmpty()) {finalOp = (finalOp != null) ? b.and(finalOp, b.eq("a2.keyword", query.a2())) : b.eq("a2.keyword", query.a2());}// 最后传入过滤表达式if (finalOp != null) {searchBuilder.filterExpression(finalOp.build());}return vectorStore.similaritySearch(searchBuilder.build());}

定位问题

最后源码定位到org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore#doSimilaritySearch方法,里面使用了filter过滤,用的是queryString
在这里插入图片描述
最后请求的body体,query_vector太长做了删减,原本是1024维

{"knn":[ {"field":"embedding","query_vector":[-0.043929047882556915, 0.015229480341076851],"k":4,"num_candidates":6,"filter":[ {"query_string": {"query": "metadata.errorMessage.keyword:某08_1表啥≠“2”(调)或 “7”(叠加)时"}}],"similarity":0.699999988079071}],"size":4
}

co.elastic.clients.transport.rest_client.RestClientHttpClient#performRequest处打个断点,执行new String(restRequest.getEntity().getContent().readAllBytes())就可以拿到请求体内容
在这里插入图片描述

不管是代码还是最后发送的请求体来看,都确定了使用的是query_string,而query_string对特殊字符是有要求的,这就是前面查询出其他数据的原因。

query_string和term区别

问了AI,AI的答复:

特点query_stringterm
用途搜一句话、一段话,支持复杂搜索(像百度搜索)精确查找一个完全一样的词、数字或状态
怎么用写一个“搜索命令”:字段:要搜的内容直接告诉它值:字段: 完全一样的值
搜什么text 类型的长文本(如文章内容、错误信息)keyword 类型的短词、数字、状态(如状态码、ID)
是否分词会把“要搜的内容”拆开(分词)再找不分词,必须完全一样才能找到
性能较慢(要分析、计算相关度)很快(直接匹配,结果可缓存)
对特殊字符 @, #, !, *, (, ) 等的处理非常麻烦!
这些符号有特殊含义(如 AND, OR)。
如果当普通字用,必须:
1. 用 双引号 " " 把整个词或句子括起来,或者
2. 用 反斜杠 \ 一个个转义(在JSON里要写 \\)。
否则会报错!
完全不用管!
直接把包含特殊字符的完整字符串写进去就行。
因为它不分词,也不解析语法,就把整个值当普通文本比对。
例子找包含 user@abc 的文档:
"query_string": { "query": "email:\"user@abc.com\"" }
(必须加引号)
找邮箱是 user@abc.com 的文档:
"term": { "email.keyword": "user@abc.com" }
(直接写,无需处理)

一句话总结:

  • query_string:用来全文搜索,功能强但复杂,遇到特殊字符容易出错,必须小心处理
  • term:用来精确匹配,简单、快速、可靠,特殊字符不是问题,直接用就行

es官网query-string-syntax中也有相关介绍,遇到这些特殊字符,都要进行处理。注意官网的NOTE,我这边还没有试这种情况。
在这里插入图片描述

也就是说,符合我要求的,实际上是term,使用query_string的话,还要转义,就算不用转义,速度也更慢。

解决办法

  1. 转义
    所有可能出现的特殊字符,就是官网提到的那些,都加反斜杠转义
  2. 双引号包裹
    某08_1表啥≠“2”(调)或 “7”(叠加)时改成"某08_1表啥≠“2”(调)或 “7”(叠加)时"
  3. 改源码
    复制ElasticsearchVectorStore代码,建一个全类名一样的类,拷贝过去。query_string改成term。这种有个缺点,就是限制死了term查询,不友好。更倾向于其他的方式。
    改源码的话,需要从getFilterExpression里面拿到过滤表达式,自行用term重新拼装,处理起来比较复杂,这种不推荐
  4. 不使用vectorStore.similaritySearch,自行调用es代码查询
    注入EmbeddingModelElasticsearchClient,然后自己实现这个调用过程,这种是最灵活的,推荐使用,因为有些场景就是需要使用termmetadata.别忘了加
    	// 先做向量搜索float[] vectors = embeddingModel.embed(query.description());// 下面三个参数是配置的,ElasticsearchVectorStore的options属性对象里面可以拿到,但是是private的String index = "jap-index";Integer topK = 4;String embeddingFieldName = "embedding";// 查询esSearchResponse<Document> res = this.elasticsearchClient.search(sr -> sr.index(index).knn(knn -> knn.queryVector(EmbeddingUtils.toList(vectors)).similarity(query.similarityThreshold()).k(topK).field(embeddingFieldName).numCandidates((int) (1.5 * topK)).filter(fl -> fl.term(t ->// metadata.别忘了加t.field("metadata.a1.keyword").value(query.errorMessage())))).size(topK), Document.class);// 拿结果List<Hit<Document>> hits = res.hits().hits();
    
    需要注意的一点是,index等参数因为optionsprivate的,所以需要通过其他方式拿到。
    • 配置文件拿,这种前提是通过application配置文件方式配置的向量数据库(我不是这种)
    • 自行创建bean方式,可以把这个配置类存放到某个地方或者注入到容器(我是这种)
      	    @Beanpublic VectorStore vectorStore(RestClient restClient, EmbeddingModel embeddingModel) {// 可以把这个类也存起来,或者注册成beanElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions();options.setIndexName("jap-index");    // Optional: defaults to "spring-ai-document-index"options.setSimilarity(cosine);           // Optional: defaults to COSINEoptions.setDimensions(1024);             // Optional: defaults to model dimensions or 1536return ElasticsearchVectorStore.builder(restClient, embeddingModel).options(options)                     // Optional: use custom options.initializeSchema(true)               // Optional: defaults to false.batchingStrategy(new TokenCountBatchingStrategy()) // Optional: defaults to TokenCountBatchingStrategy.build();}		
      
    • 反射方式拿ElasticsearchVectorStore(也就是注入的VectorStore)的options属性,不推荐
    • 复制类,全类名一样的,拷贝代码,改成options改成public,不推荐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/94026.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94026.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安灯系统(Andon System)

安灯系统是源自丰田生产系统(TPS)的一种可视化生产管理工具&#xff0c;其名称"Andon"来自日语的"提灯"&#xff0c;原指用于报警的灯笼&#xff0c;现已成为制造业现场管理的核心工具之一。一、安灯系统的定义安灯系统是一种实时监控生产异常的可视化管理…

MyBatis与MySQL

要理解 MyBatis 语法及其与 MySQL 的区别&#xff0c;首先需要明确两者的本质定位&#xff1a;MyBatis 是 Java 的持久层框架&#xff08;负责 Java 对象与数据库数据的映射&#xff09;&#xff0c;而MySQL 是关系型数据库管理系统&#xff08;负责数据的存储和 SQL 执行&…

Vulnhub Noob靶机复现(附提权)

一、安装靶机 下载地址&#xff1a;https://download.vulnhub.com/noob/Noob.ova 下载好后使用VM打开配置如下。 二、主机发现 使用nmap扫描确认靶机ip(192.168.29.138) nmap -sn 192.168.29.1/24 三、端口扫描 使用nmap工具扫描全部端口以防遗漏。 nmap -A -p- 192.168.…

文心4.5开源测评:国产大模型的轻量化革命与全栈突破

> 当算力成本成为AI落地的最大拦路虎,一款仅需2.1GB显存、支持32K上下文的轻量级大模型如何撬动产业智能化的大门? ^ - ^ 2025年6月30日,百度正式开源文心大模型4.5系列,以**10款全维度模型矩阵**(0.3B至424B参数)刷新国产开源模型的技术边界。这不仅是参数规模的跃进…

【自存用】mumu模拟器+mitmproxy配置

一、 安装证书 下载mitmproxy进行安装。cmd 输入 mitmdump产生证书在C:\Users\账号名.mitmproxy找到mitmproxy-ca.p12,双击进入证书导入向导&#xff0c;一直点下一页&#xff0c;直到选择证书存储的地方选择【受信任的根证书颁发机构】&#xff0c;后面的继续点【是】或【完成…

Java中的字符串 - String 类

在C语言中若要表示字符串只能使用字符数组或者字符指针&#xff0c;Java语言则专门提供了 String 类&#xff0c;在面向对象编程中具有重要地位。在开发和校招笔试中&#xff0c;字符串也是常客。 目录 一、字符串的构造 二、常用方法 2.1 字符串的拼接 2.2 字符串之间的比…

[网安工具] Web 漏洞扫描工具 —— AWVS · 使用手册

&#x1f31f;想了解其它网安工具&#xff1f;看看这个&#xff1a;[网安工具] 网络安全工具管理 —— 工具仓库 管理手册 Acunetix | Web Application Security ScannerAcunetix is an end-to-end web security scanner that offers a 360 view of an organization’s securi…

丑数-优先队列/三指针/动态规划

丑数 Solution 核心思路&#xff1a; 注意的几个点&#xff1a; 1.优先队列改变排序&#xff1a; priority_queue<int,vector<int>,greater<int>> q;2.用来判断是否访问过&#xff0c;可以用unordered_set 注意set的插入用的是insert而不是push unorder…

FPGA(或者数字电路)中组合逻辑和时序逻辑是怎么划分的

1.组合逻辑 在FPGA中&#xff0c;组合逻辑是哪些没有触发器作为存储单元的电路 LUT查找表就是组合逻辑电路&#xff0c;无时钟信号参与。 加法器&#xff0c;逻辑门&#xff0c;多路选择器&#xff0c;译码器2.时序逻辑电路 输出依赖于当前输入&#xff0c;还依赖于过去 触发器…

【音视频】WebRTC 中的RTP、RTCP、SDP、Candidate

一、RTP 1.1 RTP协议介绍 在 WebRTC 中&#xff0c;RTP&#xff08;Real-time Transport Protocol&#xff0c;实时传输协议&#xff09;是音视频媒体数据传输的核心协议&#xff0c;负责实时数据的封装、传输与解封装&#xff0c;为实时交互提供时序、同步、分片重组等关键能…

accept函数及示例

这次我们介绍 accept 函数&#xff0c;它是 TCP 服务器用来接受客户端连接请求的核心系统调用。1. 函数介绍 accept 是一个 Linux 系统调用&#xff0c;专门用于TCP 服务器&#xff08;使用 SOCK_STREAM 套接字&#xff09;。它的主要功能是从监听套接字&#xff08;通过 liste…

【Java】在一个前台界面中动态展示多个数据表的字段及数据

企业的生产环境中&#xff0c;如果不允许直接操作数据表中的数据&#xff0c;则需要开发一个前台界面&#xff0c;在必要时实现对多个数据表中数据的增删改查&#xff0c; 此时就需要后端将Oracle表字段及数据查询返回前端动态展示…… 一、Oracle特定元数据查询 使用JDBC获取O…

MySQL(174)如何理解MySQL的多版本并发控制(MVCC)?

MySQL的多版本并发控制&#xff08;MVCC, Multi-Version Concurrency Control&#xff09;是一种用于实现高并发性的机制&#xff0c;它允许多个事务同时读取和写入数据&#xff0c;而不会相互阻塞。MVCC主要在InnoDB存储引擎中实现&#xff0c;通过维护数据的多个版本来实现一…

Docker--将非root用户添加docker用户组,解决频繁sudo执行输入密码的问题

一、为什么要有docker用户组&#xff1f; 根本原因&#xff1a; Linux的设备访问权限控制机制 Docker守护进程&#xff08;dockerd&#xff09;运行时会创建一个特殊的Unix套接字文件&#xff0c;如&#xff1a;/var/run/docker.sock。 这个文件就像一个“门”&#xff0c;所有…

C语言---函数的递归与迭代

递归的理解与限制条件 所谓函数递归就是递推加回归的过程&#xff0c;就是函数自己调用自己。递归的思想就是把复杂的问题拆分成与原来那个大问题相似的子问题来求解&#xff0c;大事化小&#xff0c;像剥洋葱一样&#xff0c;最终把问题解决。 递归的限制条件&#xff1a; 一个…

freqtrade在docker运行一个dryrun实例

检查配置 freqtrade trade --config user_data/config.json --strategy MlStrategy config文件,这个配置做期货为主&#xff0c;静态配置了交易对&#xff0c;同时端口和第一个bot要不一样&#xff0c;不然没有办法进行监控&#xff0c;甚至要冲突了。10S钟进行循环&#xff0c…

单片机学习笔记.PWM

PWM原理&#xff1a; 频率占空比&#xff1a;精度占空比变化步距 电机驱动电路&#xff1a;利用PWM实现呼吸灯代码 sbit LEDP2^0;//引脚定义unsigned char Time,i;//变量定义void Delay(unsigned int t)//定义延时 {while(t--); }main函数里&#xff1a;int main() {unsigned c…

【Git】解决使用SSH连接远程仓库时需要多次输入密码的问题

问题产生的原因&#xff1a;你的SSH私钥设置了密码短语&#xff08;passphrase&#xff09;。解决问题的方法&#xff1a;使用SSH代理&#xff08;ssh-agent&#xff09;&#xff0c;ssh-agent是一个后台运行程序&#xff0c;它会记住你解锁过的SSH私钥的密码短语&#xff0c;这…

机器学习—逻辑回归

一介绍逻辑回归是处理二分类问题的线性模型&#xff0c;通过sigmoid函数将线性输出映射到[0,1]&#xff0c;输出事件发生概率&#xff0c;广泛用于预测与分类。如果做坐标的话&#xff0c;特征就是p1和p2&#xff0c;结果就是y红的与绿的 二Sigma函数代码说明Sigmoid 函数定义&…

深入解读OpenTelemetry分布式链路追踪:原理与实践指南

深入解读OpenTelemetry分布式链路追踪&#xff1a;原理与实践指南 分布式系统在微服务架构下&#xff0c;服务调用链越来越复杂&#xff0c;追踪单次请求在各个微服务之间的执行情况成为运维与性能优化的关键。作为新一代开源标准&#xff0c;OpenTelemetry为分布式追踪、指标与…