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
英文地址,請注明出處:http://en.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為分布式追蹤、指標與…