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

警告

语义内核矢量存储功能为 RC,在发布之前仍可能会发生需要重大更改的改进,但这种情况有限。

警告

语义内核矢量存储功能为预览版功能,在发布之前仍可能会发生需要重大更改的改进,但这种情况有限。

提示

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

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

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

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

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

使用矢量存储进行检索增强生成 (RAG)

矢量存储抽象是一个低级别 API,用于从向量存储添加和检索数据。 语义内核内置支持使用 RAG 的任一个矢量存储实现。 通过包装 IVectorSearchable<TRecord> 并将其公开为文本搜索实现,可达到此目的。

提示

若要详细了解如何使用 RAG 的矢量存储,请参阅 如何将矢量存储与语义内核文本搜索配合使用

提示

若要详细了解文本搜索,请参阅 什么是语义内核文本搜索?

矢量存储抽象

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

Microsoft.Extensions.VectorData.VectorStore

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

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

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

Microsoft.Extensions.VectorData.IVectorSearchable<TRecord>

  • SearchAsync<TRecord> 可用于执行以下任一操作:
    • 矢量搜索采用一些输入,这些输入可由已注册的嵌入生成器或数据库支持的向量数据库进行矢量化。
    • 将矢量作为输入的矢量搜索。

使用矢量存储进行检索增强生成 (RAG)

矢量存储抽象是一个低级别 API,用于从向量存储添加和检索数据。 语义内核内置支持使用 RAG 的任一个矢量存储实现。 这是通过用 VectorSearchBase[TKey, TModel]VectorizedSearchMixin[Tmodel]VectorizableTextSearchMixin[TModel] 包装 VectorTextSearch[TModel],并将其作为文本搜索实现进行公开来实现的。

提示

若要详细了解如何使用 RAG 的矢量存储,请参阅 如何将矢量存储与语义内核文本搜索配合使用

提示

若要详细了解文本搜索,请参阅 什么是语义内核文本搜索?

矢量存储抽象

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

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

矢量存储连接器入门

导入必要的 nuget 包

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

可以像这样添加抽象包。

dotnet add package Microsoft.Extensions.VectorData.Abstractions

定义数据模型

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

using Microsoft.Extensions.VectorData;

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

    [VectorStoreData(IsIndexed = true)]
    public string HotelName { get; set; }

    [VectorStoreData(IsFullTextIndexed = true)]
    public string Description { get; set; }

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

    [VectorStoreData(IsIndexed = true)]
    public string[] Tags { get; set; }
}
from dataclasses import dataclass, field
from typing import Annotated
from semantic_kernel.data.vector import (
    DistanceFunction,
    IndexKind,
    VectorStoreField,
    vectorstoremodel,
)

@vectorstoremodel
@dataclass
class Hotel:
    hotel_id: Annotated[str, VectorStoreField('key')] = field(default_factory=lambda: str(uuid4()))
    hotel_name: Annotated[str, VectorStoreField('data', is_filterable=True)]
    description: Annotated[str, VectorStoreField('data', is_full_text_searchable=True)]
    description_embedding: Annotated[list[float], VectorStoreField('vector', dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)]
    tags: Annotated[list[str], VectorStoreField('data', 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

如果要使用 Docker 在本地运行 Qdrant,请使用以下命令启动 Qdrant 容器以及此示例中使用的设置。

docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant:latest

若要验证 Qdrant 实例是否已正常运行,请访问 Qdrant docker 容器中内置的 Qdrant 仪表板:http://localhost:6333/dashboard

由于数据库支持许多不同类型的键和记录,因此可以使用泛型指定集合的键和记录的类型。 在本例中,记录的类型将是我们已经定义的 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"), ownsClient: true);

// 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.qdrant import QdrantCollection

# Create a collection specify the type of key and record stored in it via Generic parameters.
collection: QdrantCollection[str, Hotel] = QdrantCollection(
    record_type=Hotel,
    collection_name="skhotels" # this is optional, you can also specify the collection_name in the vectorstoremodel decorator.
)

由于数据库支持许多不同类型的键和记录,因此可以使用泛型指定集合的键和记录的类型。 在本例中,记录的类型将是我们已经定义的 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()
        );
    }
}

提示

有关每个矢量存储连接器支持的键和字段类型的详细信息,请参阅每个连接器的文档

创建集合并添加记录

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

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

// 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.
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.ensure_collection_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();

提示

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

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

// Generate a vector for your search text, using your chosen embedding generation implementation.
ReadOnlyMemory<float> searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");

// Do the search.
var searchResult = collection.SearchAsync(searchVector, top: 1);

// Inspect the returned hotel.
await foreach (var record in searchResult)
{
    Console.WriteLine("Found hotel description: " + record.Record.Description);
    Console.WriteLine("Found record score: " + record.Score);
}

执行矢量搜索

搜索方法可用于搜索集合中的记录。 它采用字符串,然后使用模型或集合中的嵌入生成设置进行矢量化,或者使用已生成的向量进行矢量化。

# Do a search.
search_result = await collection.search("I'm looking for a hotel where customer happiness is the priority.", vector_property_name="description_embedding", top=3)

# Inspect the returned hotels.
async for result in search_result.results:
    print(f"Found hotel description: {result.record.description}")

创建搜索函数

若要创建可用于搜索酒店的简单搜索函数,可以使用 create_search_function 集合上的方法。

名称和说明以及参数的名称和说明用于生成函数签名,使用函数调用时会将函数签名发送到 LLM。 调整参数对于使得 LLM 生成正确的函数调用非常有用。

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`."
)

还有其他许多参数,例如,这是更复杂的版本,请注意参数的自定义,以及 string_mapper 用于将记录转换为字符串的函数。

from semantic_kernel.function import KernelParameterMetadata

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`.",
    search_type="keyword_hybrid", # default is "vector"
    parameters=[
        KernelParameterMetadata(
            name="query",
            description="The terms you want to search for in the hotel database.",
            type="str",
            is_required=True,
            type_object=str,
        ),
        KernelParameterMetadata(
            name="tags",
            description="The tags you want to search for in the hotel database, use `*` to match all.",
            type="str",
            type_object=str,
            default_value="*",
        ),
        KernelParameterMetadata(
            name="top",
            description="Number of results to return.",
            type="int",
            default_value=5,
            type_object=int,
        ),
    ],
    # finally, we specify the `string_mapper` function that is used to convert the record to a string.
    # This is used to make sure the relevant information from the record is passed to the LLM.
    string_mapper=lambda x: f"Hotel {x.record.hotel_name}: {x.record.description}. Tags: {x.record.tags} (hotel_id: {x.record.hotel_id}) ", 
)

提示

有关更多示例(包括端到端示例),请参阅 语义内核示例存储库

// 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());

提示

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

后续步骤