什么是语义内核矢量存储连接器? (预览版)

警告

语义内核向量存储功能处于预览状态,需要中断性变更的改进可能仍发生在发布前的有限情况下。

提示

如果要查找有关旧版内存存储连接器的信息,请参阅 “内存存储”页

矢量数据库在不同的域和应用程序中有许多用例,这些用例涉及自然语言处理(NLP)、计算机视觉(CV)、建议系统(RS)以及需要语义理解和匹配数据的其他领域。

在向量数据库中存储信息的一个用例是使大型语言模型(LLM)能够生成更相关且连贯的响应。 大型语言模型经常面临诸如生成不准确或无关信息等挑战;缺乏事实一致性或常识;重复或矛盾自己:有偏见或冒犯性。 为了帮助克服这些挑战,可以使用矢量数据库来存储与所需域或流派相关的不同主题、关键字、事实、观点和/或源的信息。 矢量数据库允许你有效地查找与特定问题或主题相关的信息的子集。 然后,可以使用提示将来自矢量数据库的信息传递给大型语言模型,以生成更准确且更相关的内容。

例如,如果要编写有关 AI 中最新趋势的博客文章,则可以使用矢量数据库来存储有关该主题的最新信息,并将信息连同请求一起传递给 LLM,以便生成利用最新信息的博客文章。

语义内核和 .net 提供了一个抽象,用于与矢量存储进行交互,以及实现这些抽象的现用连接器列表。 功能包括创建、列出和删除记录集合,以及上传、检索和删除记录。 利用抽象,可以轻松地试验免费或本地托管的向量存储,然后在需要纵向扩展时切换到服务。

矢量存储抽象

矢量存储抽象中的主要接口如下。

Microsoft.Extensions.VectorData.IVectorStore

IVectorStore 包含跨向量存储中的所有集合的操作,例如 ListCollectionNames。 它还提供获取 IVectorStoreRecordCollection<TKey, TRecord> 实例的功能。

Microsoft.Extensions.VectorData.IVectorStoreRecordCollection<TKey, TRecord>

IVectorStoreRecordCollection<TKey, TRecord> 表示集合。 此集合可能或可能不存在,并且该接口提供用于检查集合是否存在、创建或删除该集合的方法。 该接口还提供更新插入、获取和删除记录的方法。 最后,接口继承自 IVectorizedSearch<TRecord> 提供矢量搜索功能。

Microsoft.Extensions.VectorData.IVectorizedSearch<TRecord>

IVectorizedSearch<TRecord> 包含用于执行矢量搜索的方法。 IVectorStoreRecordCollection<TKey, TRecord> 继承自 IVectorizedSearch<TRecord> 在仅在需要搜索且不需要记录或集合管理的情况下自行使用 IVectorizedSearch<TRecord>

IVectorizableTextSearch<TRecord>

IVectorizableTextSearch<TRecord> 包含用于执行矢量搜索的方法,其中矢量数据库能够自动生成嵌入内容。 例如,可以使用文本字符串调用此方法,数据库将为你生成嵌入内容,并针对矢量字段进行搜索。 这不受所有向量数据库的支持,因此仅由选择连接器实现。

矢量存储抽象

矢量存储抽象中的主要接口如下。

com.microsoft.semantickernel.data.vectorstorage.VectorStore

VectorStore 包含跨向量存储中的所有集合的操作,例如 listCollectionNames。 它还提供获取 VectorStoreRecordCollection<Key, Record> 实例的功能。

com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordCollection<Key, Record>

VectorStoreRecordCollection<Key, Record> 表示集合。 此集合可能或可能不存在,并且该接口提供用于检查集合是否存在、创建或删除该集合的方法。 该接口还提供更新插入、获取和删除记录的方法。 最后,接口继承自 VectorizedSearch<Record> 提供矢量搜索功能。

com.microsoft.semantickernel.data.vectorsearch.VectorizedSearch<Record>

VectorizedSearch<Record> 包含用于执行矢量搜索的方法。 VectorStoreRecordCollection<Key, Record> 继承自 VectorizedSearch<Record> 在仅在需要搜索且不需要记录或集合管理的情况下自行使用 VectorizedSearch<Record>

com.microsoft.semantickernel.data.vectorsearch.VectorizableTextSearch<Record>

VectorizableTextSearch<Record> 包含用于执行矢量搜索的方法,其中矢量数据库能够自动生成嵌入内容。 例如,可以使用文本字符串调用此方法,数据库将为你生成嵌入内容,并针对矢量字段进行搜索。 这不受所有向量数据库的支持,因此仅由选择连接器实现。

Vector Store 连接器入门

导入必要的 nuget 包

nuget 包中 Microsoft.Extensions.VectorData.Abstractions 提供了所有矢量存储接口和任何抽象相关类。 每个向量存储实现都在其自己的 nuget 包中可用。 有关已知实现的列表,请参阅 现用连接器页

可以像这样添加抽象包。

dotnet add package Microsoft.Extensions.VectorData.Abstractions --prerelease

警告

从语义内核版本 1.23.0 中,矢量存储抽象已从中删除 Microsoft.SemanticKernel.Abstractions ,并可在新的专用 Microsoft.Extensions.VectorData.Abstractions 包中使用。

请注意,从版本 1.23.0 开始, Microsoft.SemanticKernel.Abstractions 依赖于 Microsoft.Extensions.VectorData.Abstractions该版本,因此无需引用其他包。 但是,抽象现在将位于新 Microsoft.Extensions.VectorData 命名空间中。

从 1.22.0 或更高版本升级到 1.23.0 或更高版本时,需要在使用任何向量存储抽象类型的文件中添加一个附加using Microsoft.Extensions.VectorData;子句,例如IVectorStoreIVectorStoreRecordCollection、、VectorStoreRecordDataAttributeVectorStoreRecordKeyProperty等。

创建自己的实现时,已进行此更改以支持向量存储提供程序。 提供程序只能引用 Microsoft.Extensions.VectorData.Abstractions 包。 这减少了潜在的版本冲突,并允许语义内核在不影响矢量存储提供程序的情况下继续快速发展。

定义数据模型

语义内核向量存储连接器使用模型第一种方法与数据库交互。 这意味着第一步是定义映射到存储架构的数据模型。 为了帮助连接器创建记录集合并映射到存储架构,可以批注模型以指示每个属性的函数。

using Microsoft.Extensions.VectorData;

public class Hotel
{
    [VectorStoreRecordKey]
    public ulong HotelId { get; set; }

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

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

    [VectorStoreRecordVector(Dimensions: 4, DistanceFunction.CosineDistance, IndexKind.Hnsw)]
    public ReadOnlyMemory<float>? DescriptionEmbedding { get; set; }

    [VectorStoreRecordData(IsFilterable = true)]
    public string[] Tags { get; set; }
}
from dataclasses import dataclass, field
from typing import Annotated
from semantic_kernel.data import (
    DistanceFunction,
    IndexKind,
    VectorStoreRecordDataField,
    VectorStoreRecordDefinition,
    VectorStoreRecordKeyField,
    VectorStoreRecordVectorField,
    vectorstoremodel,
)

@vectorstoremodel
@dataclass
class Hotel:
    hotel_id: Annotated[str, VectorStoreRecordKeyField()] = field(default_factory=lambda: str(uuid4()))
    hotel_name: Annotated[str, VectorStoreRecordDataField(is_filterable=True)]
    description: Annotated[str, VectorStoreRecordDataField(is_full_text_searchable=True)]
    description_embedding: Annotated[list[float], VectorStoreRecordVectorField(dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)]
    tags: Annotated[list[str], VectorStoreRecordDataField(is_filterable=True)]
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordData;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordKey;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordVector;
import com.microsoft.semantickernel.data.vectorstorage.definition.DistanceFunction;
import com.microsoft.semantickernel.data.vectorstorage.definition.IndexKind;

import java.util.Collections;
import java.util.List;

public class Hotel {
    @VectorStoreRecordKey
    private String hotelId;

    @VectorStoreRecordData(isFilterable = true)
    private String name;

    @VectorStoreRecordData(isFullTextSearchable = true)
    private String description;

    @VectorStoreRecordVector(dimensions = 4, indexKind = IndexKind.HNSW, distanceFunction = DistanceFunction.COSINE_DISTANCE)
    private List<Float> descriptionEmbedding;

    @VectorStoreRecordData(isFilterable = true)
    private List<String> tags;

    public Hotel() { }

    public Hotel(String hotelId, String name, String description, List<Float> descriptionEmbedding, List<String> tags) {
        this.hotelId = hotelId;
        this.name = name;
        this.description = description;
        this.descriptionEmbedding = Collections.unmodifiableList(descriptionEmbedding);
        this.tags = Collections.unmodifiableList(tags);
    }

    public String getHotelId() { return hotelId; }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public List<Float> getDescriptionEmbedding() { return descriptionEmbedding; }
    public List<String> getTags() { return tags; }
}

提示

有关如何批注数据模型的详细信息,请参阅 定义数据模型

提示

有关对数据模型进行批注的替代方法,请参阅 使用记录定义定义架构。

连接到数据库并选择集合

定义数据模型后,下一步是为所选数据库创建 VectorStore 实例并选择记录集合。

在此示例中,我们将使用 Qdrant。 因此,需要导入 Qdrant nuget 包。

dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant --prerelease

由于数据库支持许多不同类型的键和记录,因此可以使用泛型指定集合的键和记录的类型。 在本例中,记录的类型将是 Hotel 我们已经定义的类,键的类型将是 ulong,因为 HotelId 该属性是一个 ulong 且 Qdrant 仅支持 Guidulong 键。

using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client;

// Create a Qdrant VectorStore object
var vectorStore = new QdrantVectorStore(new QdrantClient("localhost"));

// Choose a collection from the database and specify the type of key and record stored in it via Generic parameters.
var collection = vectorStore.GetCollection<ulong, Hotel>("skhotels");

由于数据库支持许多不同类型的键和记录,因此可以使用泛型指定集合的键和记录的类型。 在本例中,记录的类型将是 Hotel 我们已经定义的类,键的类型将是 str,因为 HotelId 该属性是一个 str 且 Qdrant 仅支持 strint 键。

from semantic_kernel.connectors.memory.qdrant import QdrantStore

# Create a Qdrant VectorStore object, this will look in the environment for Qdrant related settings, and will fall back to the default, which is to run in-memory.
vector_store = QdrantStore()

# Choose a collection from the database and specify the type of key and record stored in it via Generic parameters.
collection = vector_store.get_collection(
    collection_name="skhotels", 
    data_model_type=Hotel
)

由于数据库支持许多不同类型的键和记录,因此可以使用泛型指定集合的键和记录的类型。 在本例中,记录的类型将是 Hotel 我们已经定义的类,并且键的类型将是 String,因为 hotelId 该属性是一个 String 且 JDBC 存储仅支持 String 密钥。

import com.microsoft.semantickernel.data.jdbc.JDBCVectorStore;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreOptions;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreRecordCollectionOptions;
import com.microsoft.semantickernel.data.jdbc.mysql.MySQLVectorStoreQueryProvider;
import com.mysql.cj.jdbc.MysqlDataSource;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Create a MySQL data source
        var dataSource = new MysqlDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sk");
        dataSource.setPassword("root");
        dataSource.setUser("root");

        // Create a JDBC vector store
        var vectorStore = JDBCVectorStore.builder()
            .withDataSource(dataSource)
            .withOptions(
                JDBCVectorStoreOptions.builder()
                    .withQueryProvider(MySQLVectorStoreQueryProvider.builder()
                        .withDataSource(dataSource)
                        .build())
                    .build()
            )
            .build();

        // Get a collection from the vector store
        var collection = vectorStore.getCollection("skhotels",
            JDBCVectorStoreRecordCollectionOptions.<Hotel>builder()
                .withRecordClass(Hotel.class)
                .build()
        );
    }
}

提示

有关每个 Vector Store 连接器支持的键和字段类型的详细信息,请参阅 每个连接器的文档。

创建集合并添加记录

// Create the collection if it doesn't exist yet.
await collection.CreateCollectionIfNotExistsAsync();

// Upsert a record.
string descriptionText = "A place where everyone can be happy.";
ulong hotelId = 1;

// Create a record and generate a vector for the description using your chosen embedding generation implementation.
// Just showing a placeholder embedding generation method here for brevity.
await collection.UpsertAsync(new Hotel
{
    HotelId = hotelId,
    HotelName = "Hotel Happy",
    Description = descriptionText,
    DescriptionEmbedding = await GenerateEmbeddingAsync(descriptionText),
    Tags = new[] { "luxury", "pool" }
});

// Retrieve the upserted record.
Hotel? retrievedHotel = await collection.GetAsync(hotelId);

创建集合并添加记录

# Create the collection if it doesn't exist yet.
await collection.create_collection_if_not_exists()

# Upsert a record.
description = "A place where everyone can be happy."
hotel_id = "1"

await collection.upsert(Hotel(
    hotel_id = hotel_id,
    hotel_name = "Hotel Happy",
    description = description,
    description_embedding = await GenerateEmbeddingAsync(description),
    tags = ["luxury", "pool"]
))

# Retrieve the upserted record.
retrieved_hotel = await collection.get(hotel_id)
// Create the collection if it doesn't exist yet.
collection.createCollectionAsync().block();

// Upsert a record.
var description = "A place where everyone can be happy";
var hotelId = "1";
var hotel = new Hotel(
    hotelId, 
    "Hotel Happy", 
    description, 
    generateEmbeddingsAsync(description).block(), 
    List.of("luxury", "pool")
);

collection.upsertAsync(hotel, null).block();

// Retrieve the upserted record.
var retrievedHotel = collection.getAsync(hotelId, null).block();

提示

有关如何生成嵌入内容的详细信息,请参阅 嵌入生成

// Generate a vector for your search text, using your chosen embedding generation implementation.
// Just showing a placeholder method here for brevity.
var searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");
// Do the search.
var searchResult = await collection.VectorizedSearchAsync(searchVector, new() { Top = 1 }).Results.ToListAsync()

// Inspect the returned hotels.
Hotel hotel = searchResult.First().Record;
Console.WriteLine("Found hotel description: " + hotel.Description);
// Generate a vector for your search text, using your chosen embedding generation implementation.
// Just showing a placeholder method here for brevity.
var searchVector = generateEmbeddingsAsync("I'm looking for a hotel where customer happiness is the priority.").block();

// Do the search.
var searchResult = collection.searchAsync(searchVector, VectorSearchOptions.builder()
    .withTop(1).build()
).block();

Hotel record = searchResult.getResults().get(0).getRecord();
System.out.printf("Found hotel description: %s\n", record.getDescription());

提示

有关如何生成嵌入内容的详细信息,请参阅 嵌入生成

后续步骤