領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多大型語言模型 (LLM) 的出現推動了生成式 AI 的發展,並使其一個關鍵元件浮出水面,為廣大受眾所知:嵌入。
嵌入是資料在高維空間中的向量表示,捕捉了它們的語義。向量表示可以更高效、更有效地搜尋相似專案(向量搜尋)。向量搜尋通常用於構建檢索增強生成 (RAG) 系統,因此對向量資料庫的需求很大。
隨著新型向量資料庫的興起,現有資料庫引擎逐漸整合了向量搜尋功能,從而形成了兩種主要型別的資料庫:
專用向量資料庫源於在高效能維度空間中搜索相似項的需求。它們為此目的進行了最佳化,通常使用專門的索引技術來提高搜尋效能。示例包括 Pinecone、Weaviate、Milvus 和 Qdrant。所有這些專案都出現在2020年代初期。
向量搜尋通常需要一個向量(一個單精度float數字陣列)、一個名稱空間(類似於表或集合)和一個Top K(要返回的結果數量)引數。向量搜尋然後執行近似最近鄰(ANN)或k-最近鄰(kNN)搜尋。這些資料庫允許額外的過濾相似性和元資料欄位,但是,搜尋的核心圍繞向量表示。
現有資料庫引擎,如Postgres (pgvector)、Oracle和MongoDB,已逐漸將向量搜尋功能新增到其引擎中。它們不是專用向量資料庫,而是具有向量搜尋功能的通用資料庫。它們的優勢在於能夠處理各種資料型別和查詢,特別是在將向量搜尋與傳統查詢結合時。它們還擁有悠久的歷史,支援管理任務,並具有完善的備份和恢復、擴充套件和維護操作模型。另一個需要考慮的方面是,這些資料庫已在生產中使用,包含大量現有資料。
Spring AI 與向量儲存有廣泛的整合。
顯而易見的問題是:“為什麼Spring AI支援向量搜尋而Spring Data不支援?”這又有什麼關係呢?
Spring AI的目標是透過提供一致的程式設計模型和抽象來簡化構建AI驅動應用程式的過程。它專注於將AI功能整合到Spring應用程式中,併為使用各種AI模型和服務提供統一的API。
AI是一個熱門話題:一些資料庫供應商已將其向量搜尋整合貢獻給Spring AI,以實現檢索增強生成等用例。這是開源如何推動資料庫領域創新和協作的一個很好的例子。
當我們考慮AI炒作週期達到頂峰之後的情況時,我們面臨的是第二天的運營。資料有生命週期,新的LLM模型不斷出現和消失,有些比其他模型更適合某些任務或語言。雖然Spring AI的VectorStore在一定程度上能夠反映資料生命週期,但這絕不是它的主要關注點。
這時Spring Data就發揮作用了。Spring Data的核心是資料模型、訪問和資料生命週期。它提供了一致的程式設計模型來訪問不同的資料儲存,包括關係資料庫、NoSQL資料庫等。Spring Data的重點是簡化資料訪問和管理,使其更容易以一致的方式使用各種資料來源。
那麼,在Spring Data中擁有向量搜尋功能不是很有意義嗎?
是的,會的。
透過Spring Data 3.5,我們引入了Vector型別,以簡化實體中向量資料的使用。
向量資料型別在典型的領域模型中並不常見。最接近向量資料的是地理空間資料型別,例如Point、Polygon等,但即使是這些也不常見。領域模型更多地由反映其所用領域的原始型別和值型別組成。
向量屬性要麼使用供應商特定的型別(如Cassandra的CqlVector),要麼使用某種陣列,如float[]。在後一種情況下,使用陣列會引入相當多的意外複雜性:Java陣列是指標。它們的底層實際陣列資料是可變的。攜帶陣列也不是很常見。
您的領域模型可以利用Vector屬性型別,從而降低意外修改底層資料的風險,併為原本的float[]賦予語義上下文。對於Spring Data處理物件對映的模組,Spring Data會處理Vector屬性的持久化和檢索。對於JPA,您將需要額外的轉換器。
Vector vector = Vector.of(0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f);
Vector.of(…)建立不可變的Vector例項並複製給定的輸入陣列。雖然這適用於大多數場景,但希望減少GC壓力的效能敏感型安排可以保留對原始陣列的引用
Vector vector = Vector.unsafe(new float[]{0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f});
如果您想使用double[],可以透過呼叫toFloatArray()或toDoubleArray()來獲取float[]陣列的安全(複製)變體。或者,您可以透過getSource()訪問Vector的源。
根據您的資料儲存,您可能需要為您的資料模型配備額外的註解,以指示例如維度數量或其精度。
當執行向量搜尋操作時,每個資料庫都使用非常不同的API。讓我們看看MongoDB和Apache Cassandra。
在MongoDB中,向量搜尋透過其聚合框架使用,需要一個聚合階段
class Comment {
String id;
String language;
String comment;
Vector embedding;
// getters, setters, …
}
VectorSearchOperation vectorSearch = VectorSearchOperation.search("euclidean-index")
.path("embedding")
.vector(0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f) // float[], Vector, or Mongo's BinaryVector
.limit(10) // Top-K
.numCandidates(200)
.filter(where("language").is("DE"))
.searchType(SearchType.ANN)
.withSearchScore();
AggregationResults<Document> results = template.aggregate(newAggregation(vectorSearch),
Comment.class, Document.class);
VectorSearchOperation提供了一個流暢的API,以方便的方式引導您完成操作的每個步驟,反映底層的MongoDB API。
我們來看看Apache Cassandra。Cassandra使用CQL (Cassandra Query Language) 對資料庫執行查詢。Cassandra向量搜尋使用相同的方法。使用Cassandra,Spring Data使用者可以選擇使用Spring Data Cassandra的Query API或原生CQL API來執行向量搜尋
@Table("comments")
class CommentVectorSearchResult {
@Id String id;
double score;
String language;
String comment;
// getters, setters, …
}
CassandraTemplate template = …
Query query = Query.select(Columns.from("id", "language", "comment")
.select("embedding", it -> it.similarity(Vector.of(1.1f, 2.2f)).cosine().as("score")))
.sort(VectorSort.ann("embedding", Vector.of(1.1f, 2.2f)))
.limit(10);
List<VectorSearchResult> select = template.select(query, VectorSearchResult.class);
CQL變體如下所示
CassandraTemplate template = …
List<Map<String, Object>> result = template.getCqlOperations().queryForList("""
SELECT id, language, comment, similarity_cosine(embedding, ?0) AS score
FROM my_table
ORDER BY embedding ANN OF ?0
LIMIT ?1
""", CqlVector.newInstance(1.1f, 2.2f), 10);
總而言之,這些是Spring Data中如何使用向量搜尋的小而簡單的例子。
從領域模型的角度來看,表示搜尋結果與表示領域模型本身是不同的。顯然,人們可能更喜歡Java Records而不是類,但這並不是差異所在。您是否注意到額外的CommentVectorSearchResult類或List<Document>(MongoDB的原生文件型別)?Cassandra沒有可用於消費結果的獨立原始型別,因此我們需要CommentVectorSearchResult作為專用型別來對映此特定的Cassandra搜尋結果。我們不僅想訪問領域資料,還想訪問分數。MongoDB的Java驅動程式附帶了一個本質上是Map的Document型別。
這與我們設想的現代程式設計模型不符。
搜尋專案時,結果不是領域物件的列表,而是搜尋結果的列表。我們如何表示它們?我們不能有一個統一的程式設計模型,將表達我想要的簡單性與底層資料庫的強大功能結合起來嗎?
如果它是搜尋結果,那麼它就是SearchResult<T>。如果儲存庫方法可以返回SearchResults<T>會怎樣?搜尋是一個與查詢(查詢)實體略有不同的概念。除此之外,搜尋方法的運作方式將類似於現有的查詢方法。
interface CommentRepository extends Repository<Comment, String> {
@VectorSearch(indexName = "euclidean-index")
SearchResults<Comment> searchTop10ByLanguageAndEmbeddingNear(String language, Vector vector,
Similarity similarityThreshold);
@VectorSearch(indexName = "euclidean-index")
SearchResults<Comment> searchByLanguageAndEmbeddingWithin(String language, Vector vector,
Range<Similarity> range, Limit topK);
}
SearchResults<Comment> results = repository.searchTop10ByLanguageAndEmbeddingNear("DE", Vector.of(0.0001f, 1.12345f, 2.23456f, 3.34567f, 4.45678f), Similarity.of(0.9));
for (SearchResult<Comment> result : results) {
Score score= result.getScore();
Comment comment = result.getContent();
// …
}
上面的示例展示了一個執行向量搜尋的搜尋方法。在MongoDB中搜索需要一個特定於MongoDB的索引提示。除此之外,查詢派生會建立預過濾器以按language進行過濾。透過利用Near和Within關鍵字,Spring Data MongoDB能夠將給定的Vector和Similarity謂詞與實際的向量搜尋操作相關聯。結果以SearchResults的形式返回,提供對找到的實體及其分數或相似度值的訪問。
使用Postgres或Oracle進行向量搜尋甚至更簡單。以下示例透過Hibernate的hibernate-vector模組展示了Spring Data JPA儲存庫中的向量搜尋方法
interface CommentRepository extends Repository<Comment, String> {
SearchResults<Comment> searchTop10ByLanguageAndEmbeddingNear(String language, Vector vector,
Score scoreThreshold);
SearchResults<Comment> searchByLanguageAndEmbeddingWithin(String language, Vector vector,
Range<Similarity> range, Limit topK);
}
Near和Within搜尋方法需要一個Vector和一個Score、Similarity(Score的子型別)或Range<Score>引數來確定相似度/距離的計算方式。傳統上,查詢方法旨在表達查詢的謂詞,而典型的向量搜尋更多地是關於Top-K限制。這是我們將來必須考慮的問題。
搜尋方法也可以像查詢方法一樣利用註解。以下示例展示了Spring Data JPA中的搜尋方法
interface CommentRepository extends Repository<Comment, Integer> {
@Query("""
SELECT c, cosine_distance(c.embedding, :embedding) as distance FROM Comment c
WHERE c.language = :language
AND cosine_distance(c.embedding, :embedding) <= :distance
ORDER BY cosine_distance(c.embedding, :embedding) asc
""")
SearchResults<Comment> searchAnnotatedByLanguage(String language, Vector embedding, Score distance);
}
雖然不明顯,但pgvector和Oracle計算距離以計算分數。Spring Data允許直接消費原生分數,或者在使用具有適當ScoringFunction的Similarity.of(…)作為引數時,Spring Data將原生分數標準化為0到1之間的相似度範圍。
// Using a native score
SearchResults<Comment> results = repository.searchAnnotatedByLanguage(…, Score.of(0.1, ScoringFunction.cosine()));
// SearchResult.score will be an instance of Similarity
SearchResults<Comment> results = repository.searchAnnotatedByLanguage(…, Similarity.of(0.9, ScoringFunction.cosine()));
關於JPA中的Vector的最後一點說明。在使用JPA時,您可以在領域模型中使用Vector來儲存向量,前提是您已配置AttributeConverter將Vector轉換為資料庫型別。但是,當使用Hibernate的距離方法(如cosine_distance)時,Hibernate不考慮任何Attribute Converter,因此您的模型必須使用float[]或double[]作為嵌入型別
@Table
class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String language;
private String comment;
@JdbcTypeCode(SqlTypes.VECTOR)
@Array(length = 5)
private float[] embedding;
}
我們探索了向量搜尋的世界,以及它如何與Spring AI中的向量搜尋起源一起融入Spring生態系統。對Vector型別的支援將隨Spring Data 3.5 (2025.0) 的2025年5月版本一起釋出。
向量搜尋方法是Spring Data 4.0 (2025.1) 釋出列車的預覽功能,首批實現在JPA中透過Hibernate Vector、MongoDB和Apache Cassandra。我們很高興聽到您對向量搜尋方法的看法以及我們如何進一步改進它們。