Condividi tramite

快速入门:矢量搜索

在本快速入门中,你将使用 适用于 Python 的 Azure AI 搜索客户端库 来创建、加载和查询 向量索引。 Python 客户端库为索引操作提供了 REST API 的抽象。

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-python-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-python-samples/Quickstart-Vector-Search
    code .
    
  3. sample.env 中,将占位符值 AZURE_SEARCH_ENDPOINT 替换为在 Get endpoint 中获取到的 URL。

  4. sample.env 重命名为 .env

    mv sample.env .env
    
  5. 打开 vector-search-quickstart.ipynb

  6. Ctrl+Shift+P,选择 笔记本:选择笔记本内核,然后按照提示创建虚拟环境。 为依赖项选择 requirements.txt

    完成后,应该会在项目目录中看到一个 .venv 文件夹。

  7. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    

运行代码

  1. Install packages and set variables运行单元以加载环境变量并验证包安装。

  2. 按顺序运行其余单元格,以创建矢量索引、上传文档并运行不同类型的矢量查询。

输出

每个代码单元将输出打印到笔记本。 下面的示例是输出 Single vector search,其中显示了按相似性分数排名的向量搜索结果。

Total results: 7
- HotelId: 48, HotelName: Nordick's Valley Motel, Category: Boutique
- HotelId: 13, HotelName: Luxury Lion Resort, Category: Luxury
- HotelId: 4, HotelName: Sublime Palace Hotel, Category: Boutique
- HotelId: 49, HotelName: Swirling Currents Hotel, Category: Suite
- HotelId: 2, HotelName: Old Century Hotel, Category: Boutique

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 Create an index笔记本中的单元格创建索引架构,包括向量字段DescriptionVector

fields = [
    SimpleField(name="HotelId", type=SearchFieldDataType.String, key=True, filterable=True),
    SearchableField(name="HotelName", type=SearchFieldDataType.String, sortable=True),
    SearchableField(name="Description", type=SearchFieldDataType.String),
    SearchField(
        name="DescriptionVector",
        type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
        searchable=True,
        vector_search_dimensions=1536,
        vector_search_profile_name="my-vector-profile"
    ),
    SearchableField(name="Category", type=SearchFieldDataType.String, sortable=True, filterable=True, facetable=True),
    SearchField(name="Tags", type=SearchFieldDataType.Collection(SearchFieldDataType.String), searchable=True, filterable=True, facetable=True),
    # Additional fields omitted for brevity
]

要点:

  • 通过创建字段列表来定义索引。 每个字段都是使用定义字段类型和设置的帮助程序方法创建的。

  • 此特定索引支持多个搜索功能:

  • vector_search_dimensions 属性必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • 配置 VectorSearch 定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为了简化流程,本快速入门指南提供了包含预计算向量的示例酒店文档。 在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

Create documents payloadUpload the documents单元格将文档加载到索引中。

documents = [
    # List of hotel documents with embedded 1536-dimension vectors
    # Each document contains: HotelId, HotelName, Description, DescriptionVector,
    # Category, Tags, ParkingIncluded, LastRenovationDate, Rating, Address, Location
]

search_client = SearchClient(
    endpoint=search_endpoint,
    index_name=index_name,
    credential=credential
)

result = search_client.upload_documents(documents=documents)
for r in result:
    print(f"Key: {r.key}, Succeeded: {r.succeeded}, ErrorMessage: {r.error_message}")

您的代码通过 SearchClient 包提供的主要对象 azure-search-documents 与托管在 Azure AI 搜索服务中的特定搜索索引进行交互。 SearchClient 提供对索引操作的访问权限,例如:

  • 数据引入:upload_documents()、、 merge_documents()delete_documents()

  • 搜索操作:search()autocomplete()suggest()

查询索引

笔记本中的查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

Single vector search 单元格演示了一个基本方案,可在其中查找与矢量查询字符串非常匹配的文档说明。 VectorizedQuery 配置矢量搜索:

  • k_nearest_neighbors 根据矢量相似性限制返回的结果数。
  • fields 指定要搜索的向量字段。
vector_query = VectorizedQuery(
    vector=vector,
    k_nearest_neighbors=5,
    fields="DescriptionVector",
    kind="vector",
    exhaustive=True
)

results = search_client.search(
    vector_queries=[vector_query],
    select=["HotelId", "HotelName", "Description", "Category", "Tags"],
    top=5,
    include_total_count=True
)

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 字段 Single vector search with filter 上的 Tags 单元格筛选,筛选出任何不提供免费 Wi-Fi 的酒店。

# vector_query omitted for brevity

results = search_client.search(
    vector_queries=[vector_query],
    filter="Tags/any(tag: tag eq 'free wifi')",
    select=["HotelId", "HotelName", "Description", "Category", "Tags"],
    top=7,
    include_total_count=True
)

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 该 Single vector search with geo filter 单元格指定一个地理点(华盛顿特区,使用经度和纬度坐标)并返回在300公里范围内的酒店。 该 vector_filter_mode 参数确定筛选器何时运行。 在这种情况下,postFilter 在矢量搜索后运行筛选器。

# vector_query omitted for brevity

results = search_client.search(
    include_total_count=True,
    top=5,
    select=[
        "HotelId", "HotelName", "Category", "Description", "Address/City", "Address/StateProvince"
    ],
    facets=["Address/StateProvince"],
    filter="geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300",
    vector_filter_mode="postFilter",
    vector_queries=[vector_query]
)

混合搜索 在单个请求中合并全文和矢量查询。 该 Hybrid search 单元同时运行这两种查询类型,然后使用倒数排名融合(RRF)将结果合并到统一排名中。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

# vector_query omitted for brevity

results = search_client.search(
    search_text="historic hotel walk to restaurants and shopping",
    vector_queries=[vector_query],
    select=["HotelId", "HotelName", "Description", "Category", "Tags"],
    top=5,
    include_total_count=True
)

Semantic hybrid search 单元格演示 语义排名,这种排名基于语言理解对结果进行重新排序。

# vector_query omitted for brevity

results = search_client.search(
    search_text="historic hotel walk to restaurants and shopping",
    vector_queries=[vector_query],
    select=["HotelId", "HotelName", "Category", "Description"],
    query_type="semantic",
    semantic_configuration_name="my-semantic-config",
    top=5,
    include_total_count=True
)

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

  • 实际结果包含更多详细信息,包括语义说明和突出显示。 本快速入门修改了结果以提高可读性。 若要获取响应的完整结构,请使用 REST 运行请求。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

否则,可以运行 Clean up 代码单元以删除在本快速入门中创建的索引。

在本快速入门中,你将使用 适用于 Java 的 Azure AI 搜索客户端库 来创建、加载和查询 矢量索引。 Java 客户端库提供用于索引操作的 REST API 抽象。

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-java-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-java-samples/quickstart-vector-search
    code .
    
  3. src/main/resources/application.properties 中,将占位符值 azure.search.endpoint 替换为在 Get endpoint 中获取到的 URL。

  4. 安装依赖项。

    mvn clean dependency:copy-dependencies
    

    生成完成后,应会在项目目录中看到一个 target/dependency 文件夹。

  5. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    

运行代码

  1. 创建矢量索引。

    mvn compile exec:java "-Dexec.mainClass=com.example.search.CreateIndex"
    
  2. 加载包含预计算嵌入的文档。

    mvn compile exec:java "-Dexec.mainClass=com.example.search.UploadDocuments"
    
  3. 运行矢量搜索查询。

    mvn compile exec:java "-Dexec.mainClass=com.example.search.SearchSingle"
    
  4. (可选)运行其他查询变体。

    mvn compile exec:java "-Dexec.mainClass=com.example.search.SearchSingleWithFilter"
    mvn compile exec:java "-Dexec.mainClass=com.example.search.SearchSingleWithFilterGeo"
    mvn compile exec:java "-Dexec.mainClass=com.example.search.SearchHybrid"
    mvn compile exec:java "-Dexec.mainClass=com.example.search.SearchSemanticHybrid"
    

输出

CreateIndex.java 的输出显示了索引名称和确认信息。

Using Azure Search endpoint: https://<search-service-name>.search.azure.cn
Using Azure Search index: hotels-vector-quickstart
Creating index...
hotels-vector-quickstart created

输出 UploadDocuments.java 显示每个索引文档的成功状态。

Uploading documents...
Key: 1, Succeeded: true, ErrorMessage: none
Key: 2, Succeeded: true, ErrorMessage: none
Key: 3, Succeeded: true, ErrorMessage: none
Key: 4, Succeeded: true, ErrorMessage: none
Key: 48, Succeeded: true, ErrorMessage: none
Key: 49, Succeeded: true, ErrorMessage: none
Key: 13, Succeeded: true, ErrorMessage: none
Waiting for indexing... Current count: 0
All documents indexed successfully.

SearchSingle.java 的输出显示了按相似性分数排列的矢量搜索结果。

Single Vector search found 5
- HotelId: 48, HotelName: Nordick's Valley Motel, Tags: ["continental breakfast","air conditioning","free wifi"], Score 0.6605852
- HotelId: 13, HotelName: Luxury Lion Resort, Tags: ["bar","concierge","restaurant"], Score 0.6333684
- HotelId: 4, HotelName: Sublime Palace Hotel, Tags: ["concierge","view","air conditioning"], Score 0.605672
- HotelId: 49, HotelName: Swirling Currents Hotel, Tags: ["air conditioning","laundry service","24-hour front desk service"], Score 0.6026341
- HotelId: 2, HotelName: Old Century Hotel, Tags: ["pool","free wifi","air conditioning","concierge"], Score 0.57902366

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 以下代码 CreateIndex.java 创建索引架构,包括向量字段 DescriptionVector

// Define fields
List<SearchField> fields = Arrays.asList(
    new SearchField("HotelId", SearchFieldDataType.STRING)
        .setKey(true)
        .setFilterable(true),
    new SearchField("HotelName", SearchFieldDataType.STRING)
        .setSortable(true)
        .setSearchable(true),
    new SearchField("Description", SearchFieldDataType.STRING)
        .setSearchable(true),
    new SearchField("DescriptionVector",
        SearchFieldDataType.collection(SearchFieldDataType.SINGLE))
        .setSearchable(true)
        .setVectorSearchDimensions(1536)
        .setVectorSearchProfileName("my-vector-profile"),
    new SearchField("Category", SearchFieldDataType.STRING)
        .setSortable(true)
        .setFilterable(true)
        .setFacetable(true)
        .setSearchable(true),
    new SearchField("Tags", SearchFieldDataType.collection(
        SearchFieldDataType.STRING))
        .setSearchable(true)
        .setFilterable(true)
        .setFacetable(true),
    // Additional fields: ParkingIncluded, LastRenovationDate, Rating, Address, Location
);

var searchIndex = new SearchIndex(indexName, fields);

// Define vector search configuration
var hnswParams = new HnswParameters()
    .setM(16)
    .setEfConstruction(200)
    .setEfSearch(128);
var hnsw = new HnswAlgorithmConfiguration("hnsw-vector-config");
hnsw.setParameters(hnswParams);

var vectorProfile = new VectorSearchProfile(
    "my-vector-profile",
    "hnsw-vector-config");
var vectorSearch = new VectorSearch()
    .setAlgorithms(Arrays.asList(hnsw))
    .setProfiles(Arrays.asList(vectorProfile));
searchIndex.setVectorSearch(vectorSearch);

// Define semantic configuration
var prioritizedFields = new SemanticPrioritizedFields()
    .setTitleField(new SemanticField("HotelName"))
    .setContentFields(Arrays.asList(new SemanticField("Description")))
    .setKeywordsFields(Arrays.asList(new SemanticField("Category")));
var semanticConfig = new SemanticConfiguration(
    "semantic-config",
    prioritizedFields);
var semanticSearch = new SemanticSearch()
    .setConfigurations(Arrays.asList(semanticConfig));
searchIndex.setSemanticSearch(semanticSearch);

// Define suggesters
var suggester = new SearchSuggester("sg", Arrays.asList("HotelName"));
searchIndex.setSuggesters(Arrays.asList(suggester));

// Create the search index
SearchIndex result = searchIndexClient.createOrUpdateIndex(searchIndex);

要点:

  • 通过创建字段列表来定义索引。

  • 此特定索引支持多个搜索功能:

  • 该值 setVectorSearchDimensions() 必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • 配置 VectorSearch 定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为了简化流程,本快速入门指南提供了包含预计算向量的示例酒店文档。 在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

以下代码 UploadDocuments.java 将文档上传到您的搜索服务。

// Documents contain hotel data with 1536-dimension vectors for DescriptionVector
static final List<Map<String, Object>> DOCUMENTS = Arrays.asList(
    new HashMap<>() {{
        put("@search.action", "mergeOrUpload");
        put("HotelId", "1");
        put("HotelName", "Stay-Kay City Hotel");
        put("Description", "This classic hotel is fully-refurbished...");
        put("DescriptionVector", Arrays.asList(/* 1536 float values */));
        put("Category", "Boutique");
        put("Tags", Arrays.asList("view", "air conditioning", "concierge"));
        // Additional fields...
    }}
    // Additional hotel documents
);

// Upload documents to the index
IndexDocumentsResult result = searchClient.uploadDocuments(DOCUMENTS);
for (IndexingResult r : result.getResults()) {
    System.out.println("Key: %s, Succeeded: %s".formatted(r.getKey(), r.isSucceeded()));
}

您的代码通过 SearchClient 包提供的主要对象 azure-search-documents 与托管在 Azure AI 搜索服务中的特定搜索索引进行交互。 SearchClient 提供对操作的访问权限,例如:

  • 数据引入:uploadDocuments、、 mergeDocumentsdeleteDocuments

  • 搜索操作:searchautocompletesuggest

查询索引

搜索文件中的查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

SearchSingle.java 演示了一个基本方案,其中你想要查找与矢量查询字符串非常匹配的文档说明。 VectorizedQuery 配置矢量搜索:

  • setKNearestNeighborsCount() 根据矢量相似性限制返回的结果数。
  • setFields() 指定要搜索的向量字段。
var vectorQuery = new VectorizedQuery(QueryVector.getVectorList())
    .setKNearestNeighborsCount(5)
    .setFields("DescriptionVector")
    .setExhaustive(true);

var vectorSearchOptions = new VectorSearchOptions()
    .setQueries(vectorQuery)
    .setFilterMode(VectorFilterMode.POST_FILTER);

var searchOptions = new SearchOptions()
    .setTop(7)
    .setIncludeTotalCount(true)
    .setSelect("HotelId", "HotelName", "Description", "Category", "Tags")
    .setVectorSearchOptions(vectorSearchOptions);

var results = searchClient.search("*", searchOptions, Context.NONE);

for (SearchResult result : results) {
    SearchDocument document = result.getDocument(SearchDocument.class);
    System.out.println("HotelId: %s, HotelName: %s, Score: %s".formatted(
        document.get("HotelId"), document.get("HotelName"), result.getScore()));
}

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 SearchSingleWithFilter.java 字段上的 Tags 用于过滤掉不提供免费 Wi-Fi 的酒店。

var vectorQuery = new VectorizedQuery(QueryVector.getVectorList())
    .setKNearestNeighborsCount(5)
    .setFields("DescriptionVector")
    .setExhaustive(true);

var vectorSearchOptions = new VectorSearchOptions()
    .setQueries(vectorQuery)
    .setFilterMode(VectorFilterMode.POST_FILTER);

// Add filter for "free wifi" tag
var searchOptions = new SearchOptions()
    .setTop(7)
    .setIncludeTotalCount(true)
    .setSelect("HotelId", "HotelName", "Description", "Category", "Tags")
    .setFilter("Tags/any(tag: tag eq 'free wifi')")
    .setVectorSearchOptions(vectorSearchOptions);

var results = searchClient.search("*", searchOptions, Context.NONE);

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 SearchSingleWithGeoFilter.java 指定地理点(华盛顿特区,使用经度和纬度坐标)并在 300 公里内返回酒店。 在setFilterMode上调用的VectorSearchOptions方法确定筛选器何时运行。 在这种情况下,POST_FILTER 在矢量搜索后运行筛选器。

var searchOptions = new SearchOptions()
    .setTop(5)
    .setIncludeTotalCount(true)
    .setSelect("HotelId", "HotelName", "Category", "Description",
               "Address/City", "Address/StateProvince")
    .setFacets("Address/StateProvince")
    .setFilter("geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300")
    .setVectorSearchOptions(vectorSearchOptions);

混合搜索 在单个请求中合并全文和矢量查询。 SearchHybrid.java 同时运行这两种查询类型,然后使用倒数排名融合(RRF)将结果合并到统一排名中。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

var vectorQuery = new VectorizedQuery(QueryVector.getVectorList())
    .setKNearestNeighborsCount(5)
    .setFields("DescriptionVector")
    .setExhaustive(true);

var vectorSearchOptions = new VectorSearchOptions()
    .setQueries(vectorQuery)
    .setFilterMode(VectorFilterMode.POST_FILTER);

var searchOptions = new SearchOptions()
    .setTop(5)
    .setIncludeTotalCount(true)
    .setSelect("HotelId", "HotelName", "Description", "Category", "Tags")
    .setVectorSearchOptions(vectorSearchOptions);

// Pass both text query and vector search options
var results = searchClient.search(
    "historic hotel walk to restaurants and shopping",
    searchOptions, Context.NONE);

SearchSemanticHybrid.java 演示 语义排名,该排名基于语言理解重新调整结果。

var vectorQuery = new VectorizedQuery(QueryVector.getVectorList())
    .setKNearestNeighborsCount(5)
    .setFields("DescriptionVector")
    .setExhaustive(true);

var vectorSearchOptions = new VectorSearchOptions()
    .setQueries(vectorQuery)
    .setFilterMode(VectorFilterMode.POST_FILTER);

SemanticSearchOptions semanticSearchOptions = new SemanticSearchOptions()
    .setSemanticConfigurationName("semantic-config");

var searchOptions = new SearchOptions()
    .setTop(5)
    .setIncludeTotalCount(true)
    .setSelect("HotelId", "HotelName", "Category", "Description")
    .setQueryType(QueryType.SEMANTIC)
    .setSemanticSearchOptions(semanticSearchOptions)
    .setVectorSearchOptions(vectorSearchOptions);

var results = searchClient.search(
    "historic hotel walk to restaurants and shopping",
    searchOptions, Context.NONE);

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

  • 实际结果包含更多详细信息,包括语义说明和突出显示。 本快速入门修改了结果以提高可读性。 若要获取响应的完整结构,请使用 REST 运行请求。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

否则,请运行以下命令以删除在本快速入门中创建的索引。

mvn compile exec:java "-Dexec.mainClass=com.example.search.DeleteIndex"

在本快速入门中,你将使用 适用于 JavaScript 的 Azure AI 搜索客户端库 来创建、加载和查询 矢量索引。 JavaScript 客户端库为索引操作提供 REST API 的抽象。

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-javascript-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-javascript-samples/quickstart-vector-js
    code .
    
  3. sample.env 中,将占位符值 AZURE_SEARCH_ENDPOINT 替换为在 Get endpoint 中获取到的 URL。

  4. sample.env 重命名为 .env

    mv sample.env .env
    
  5. 安装依赖项。

    npm install
    

    安装完成后,应会在项目目录中看到一个 node_modules 文件夹。

  6. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    

运行代码

  1. 创建矢量索引。

    node -r dotenv/config src/createIndex.js
    
  2. 加载包含预计算嵌入的文档。

    node -r dotenv/config src/uploadDocuments.js
    
  3. 运行矢量搜索查询。

    node -r dotenv/config src/searchSingle.js
    
  4. (可选)运行其他查询变体。

    node -r dotenv/config src/searchSingleWithFilter.js
    node -r dotenv/config src/searchSingleWithFilterGeo.js
    node -r dotenv/config src/searchHybrid.js
    node -r dotenv/config src/searchSemanticHybrid.js
    

输出

createIndex.js 的输出显示了索引名称和确认信息。

Using Azure Search endpoint: https://<search-service-name>.search.azure.cn
Using Azure Search index: hotels-vector-quickstart
Creating index...
hotels-vector-quickstart created

输出 uploadDocuments.js 显示每个索引文档的成功状态。

Uploading documents...
Key: 1, Succeeded: true, ErrorMessage: none
Key: 2, Succeeded: true, ErrorMessage: none
Key: 3, Succeeded: true, ErrorMessage: none
Key: 4, Succeeded: true, ErrorMessage: none
Key: 48, Succeeded: true, ErrorMessage: none
Key: 49, Succeeded: true, ErrorMessage: none
Key: 13, Succeeded: true, ErrorMessage: none
Waiting for indexing... Current count: 0
All documents indexed successfully.

searchSingle.js 的输出显示了按相似性分数排列的矢量搜索结果。

Single Vector search found 5
- HotelId: 48, HotelName: Nordick's Valley Motel, Tags: ["continental breakfast","air conditioning","free wifi"], Score 0.6605852
- HotelId: 13, HotelName: Luxury Lion Resort, Tags: ["bar","concierge","restaurant"], Score 0.6333684
- HotelId: 4, HotelName: Sublime Palace Hotel, Tags: ["concierge","view","air conditioning"], Score 0.605672
- HotelId: 49, HotelName: Swirling Currents Hotel, Tags: ["air conditioning","laundry service","24-hour front desk service"], Score 0.6026341
- HotelId: 2, HotelName: Old Century Hotel, Tags: ["pool","free wifi","air conditioning","concierge"], Score 0.57902366

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 以下代码 createIndex.js 创建索引架构,包括向量字段 DescriptionVector

const searchFields = [
    { name: "HotelId", type: "Edm.String", key: true, sortable: true, filterable: true, facetable: true },
    { name: "HotelName", type: "Edm.String", searchable: true, filterable: true },
    { name: "Description", type: "Edm.String", searchable: true },
    {
        name: "DescriptionVector",
        type: "Collection(Edm.Single)",
        searchable: true,
        vectorSearchDimensions: 1536,
        vectorSearchProfileName: "vector-profile"
    },
    { name: "Category", type: "Edm.String", filterable: true, facetable: true },
    { name: "Tags", type: "Collection(Edm.String)", filterable: true },
    // Additional fields: ParkingIncluded, LastRenovationDate, Rating, Address, Location
];

const vectorSearch = {
    profiles: [
        {
            name: "vector-profile",
            algorithmConfigurationName: "vector-search-algorithm"
        }
    ],
    algorithms: [
        {
            name: "vector-search-algorithm",
            kind: "hnsw",
            parameters: { m: 4, efConstruction: 400, efSearch: 1000, metric: "cosine" }
        }
    ]
};

const semanticSearch = {
    configurations: [
        {
            name: "semantic-config",
            prioritizedFields: {
                contentFields: [{ name: "Description" }],
                keywordsFields: [{ name: "Category" }],
                titleField: { name: "HotelName" }
            }
        }
    ]
};

const searchIndex = {
    name: indexName,
    fields: searchFields,
    vectorSearch: vectorSearch,
    semanticSearch: semanticSearch,
    suggesters: [{ name: "sg", searchMode: "analyzingInfixMatching", sourceFields: ["HotelName"] }]
};

const result = await indexClient.createOrUpdateIndex(searchIndex);

要点:

  • 通过创建字段列表来定义索引。

  • 此特定索引支持多个搜索功能:

  • vectorSearchDimensions 属性必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • 配置 vectorSearch 定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为了简化流程,本快速入门指南提供了包含预计算向量的示例酒店文档。 在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

以下代码 uploadDocuments.js 将文档上传到您的搜索服务。

const DOCUMENTS = [
    // Array of hotel documents with embedded 1536-dimension vectors
    // Each document contains: HotelId, HotelName, Description, DescriptionVector,
    // Category, Tags, ParkingIncluded, LastRenovationDate, Rating, Address, Location
];

const searchClient = new SearchClient(searchEndpoint, indexName, credential);

const result = await searchClient.uploadDocuments(DOCUMENTS);
for (const r of result.results) {
    console.log(`Key: ${r.key}, Succeeded: ${r.succeeded}`);
}

您的代码通过 SearchClient 包提供的主要对象 @azure/search-documents 与托管在 Azure AI 搜索服务中的特定搜索索引进行交互。 SearchClient 提供对索引操作的访问权限,例如:

  • 数据引入:uploadDocuments、、 mergeDocumentsdeleteDocuments

  • 搜索操作:searchautocompletesuggest

查询索引

搜索文件中的查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

searchSingle.js 演示了一个基本方案,其中你想要查找与矢量查询字符串非常匹配的文档说明。 该 vectorQuery 对象配置矢量搜索:

  • kNearestNeighborsCount 根据矢量相似性限制返回的结果数。
  • fields 指定要搜索的向量字段。
const vectorQuery = {
    vector: vector,
    kNearestNeighborsCount: 5,
    fields: ["DescriptionVector"],
    kind: "vector",
    exhaustive: true
};

const searchOptions = {
    top: 7,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"],
    includeTotalCount: true,
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const results = await searchClient.search("*", searchOptions);

for await (const result of results.results) {
    const doc = result.document;
    console.log(`HotelId: ${doc.HotelId}, HotelName: ${doc.HotelName}, Score: ${result.score}`);
}

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 searchSingleWithFilter.js 字段上的 Tags 用于过滤掉不提供免费 Wi-Fi 的酒店。

const searchOptions = {
    top: 7,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"],
    includeTotalCount: true,
    filter: "Tags/any(tag: tag eq 'free wifi')", // Adding filter for "free wifi" tag
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const results = await searchClient.search("*", searchOptions);

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 searchSingleWithGeoFilter.js 指定地理点(华盛顿特区,使用经度和纬度坐标)并在 300 公里内返回酒店。 该 filterMode 属性确定筛选器何时运行。 在这种情况下,postFilter 在矢量搜索后运行筛选器。

const searchOptions = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Category", "Description", "Address/City", "Address/StateProvince"],
    facets: ["Address/StateProvince"],
    filter: "geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300",
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

混合搜索 在单个请求中合并全文和矢量查询。 searchHybrid.js 同时运行这两种查询类型,然后使用倒数排名融合(RRF)将结果合并到统一排名中。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

const vectorQuery = {
    vector: vector,
    kNearestNeighborsCount: 5,
    fields: ["DescriptionVector"],
    kind: "vector",
    exhaustive: true
};

const searchOptions = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"],
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

// Use search text for keyword search (hybrid search = vector + keyword)
const searchText = "historic hotel walk to restaurants and shopping";
const results = await searchClient.search(searchText, searchOptions);

searchSemanticHybrid.js 演示 语义排名,该排名基于语言理解重新调整结果。

const searchOptions = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Category", "Description"],
    queryType: "semantic",
    semanticSearchOptions: {
        configurationName: "semantic-config"
    },
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const searchText = "historic hotel walk to restaurants and shopping";
const results = await searchClient.search(searchText, searchOptions);

for await (const result of results.results) {
    console.log(`Score: ${result.score}, Re-ranker Score: ${result.rerankerScore}`);
}

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

  • 实际结果包含更多详细信息,包括语义说明和突出显示。 本快速入门修改了结果以提高可读性。 若要获取响应的完整结构,请使用 REST 运行请求。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

否则,请运行以下命令以删除在本快速入门中创建的索引。

node -r dotenv/config src/deleteIndex.js

在本快速入门中,你将使用 适用于 JavaScript 的 Azure AI 搜索客户端库(与 TypeScript 兼容)来创建、加载和查询 矢量索引。 JavaScript 客户端库为索引操作提供 REST API 的抽象。

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-javascript-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-javascript-samples/quickstart-vector-ts
    code .
    
  3. sample.env 中,将占位符值 AZURE_SEARCH_ENDPOINT 替换为在 Get endpoint 中获取到的 URL。

  4. sample.env 重命名为 .env

    mv sample.env .env
    
  5. 安装依赖项。

    npm install
    

    安装完成后,应会在项目目录中看到一个 node_modules 文件夹。

  6. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    

运行代码

  1. 创建矢量索引。

    npm run build && node -r dotenv/config dist/createIndex.js
    
  2. 加载包含预计算嵌入的文档。

    npm run build && node -r dotenv/config dist/uploadDocuments.js
    
  3. 运行矢量搜索查询。

    npm run build && node -r dotenv/config dist/searchSingle.js
    
  4. (可选)运行其他查询变体。

    npm run build && node -r dotenv/config dist/searchSingleWithFilter.js
    npm run build && node -r dotenv/config dist/searchSingleWithFilterGeo.js
    npm run build && node -r dotenv/config dist/searchHybrid.js
    npm run build && node -r dotenv/config dist/searchSemanticHybrid.js
    

注意事项

npm run build 命令将 TypeScript 文件 src/ 编译到 JavaScript 中 dist/

输出

createIndex.ts 的输出显示了索引名称和确认信息。

Using Azure Search endpoint: https://<search-service-name>.search.azure.cn
Using index name: hotels-vector-quickstart
Creating index...
hotels-vector-quickstart created

输出 uploadDocuments.ts 显示每个索引文档的成功状态。

Uploading documents...
Key: 1, Succeeded: true, ErrorMessage: none
Key: 2, Succeeded: true, ErrorMessage: none
Key: 3, Succeeded: true, ErrorMessage: none
Key: 4, Succeeded: true, ErrorMessage: none
Key: 48, Succeeded: true, ErrorMessage: none
Key: 49, Succeeded: true, ErrorMessage: none
Key: 13, Succeeded: true, ErrorMessage: none
All documents indexed successfully.

searchSingle.ts 的输出显示了按相似性分数排列的矢量搜索结果。

Single Vector search found 5
- HotelId: 48, HotelName: Nordick's Valley Motel, Tags: ["continental breakfast","air conditioning","free wifi"], Score 0.6605852
- HotelId: 13, HotelName: Luxury Lion Resort, Tags: ["bar","concierge","restaurant"], Score 0.6333684
- HotelId: 4, HotelName: Sublime Palace Hotel, Tags: ["concierge","view","air conditioning"], Score 0.605672
- HotelId: 49, HotelName: Swirling Currents Hotel, Tags: ["air conditioning","laundry service","24-hour front desk service"], Score 0.6026341
- HotelId: 2, HotelName: Old Century Hotel, Tags: ["pool","free wifi","air conditioning","concierge"], Score 0.57902366

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 以下代码 createIndex.ts 创建索引架构,包括向量字段 DescriptionVector

const searchFields: SearchField[] = [
    { name: "HotelId", type: "Edm.String", key: true, sortable: true, filterable: true, facetable: true },
    { name: "HotelName", type: "Edm.String", searchable: true, filterable: true },
    { name: "Description", type: "Edm.String", searchable: true },
    {
        name: "DescriptionVector",
        type: "Collection(Edm.Single)",
        searchable: true,
        vectorSearchDimensions: 1536,
        vectorSearchProfileName: "vector-profile"
    },
    { name: "Category", type: "Edm.String", filterable: true, facetable: true },
    { name: "Tags", type: "Collection(Edm.String)", filterable: true },
    // Additional fields: ParkingIncluded, LastRenovationDate, Rating, Address, Location
];

const vectorSearch: VectorSearch = {
    profiles: [
        {
            name: "vector-profile",
            algorithmConfigurationName: "vector-search-algorithm"
        }
    ],
    algorithms: [
        {
            name: "vector-search-algorithm",
            kind: "hnsw",
            parameters: { m: 4, efConstruction: 400, efSearch: 1000, metric: "cosine" }
        }
    ]
};

const semanticSearch: SemanticSearch = {
    configurations: [
        {
            name: "semantic-config",
            prioritizedFields: {
                contentFields: [{ name: "Description" }],
                keywordsFields: [{ name: "Category" }],
                titleField: { name: "HotelName" }
            }
        }
    ]
};

const searchIndex: SearchIndex = {
    name: indexName,
    fields: searchFields,
    vectorSearch: vectorSearch,
    semanticSearch: semanticSearch,
    suggesters: [{ name: "sg", searchMode: "analyzingInfixMatching", sourceFields: ["HotelName"] }]
};

const result = await indexClient.createOrUpdateIndex(searchIndex);

要点:

  • 通过创建字段列表来定义索引。

  • 此特定索引支持多个搜索功能:

  • vectorSearchDimensions 属性必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • 配置 vectorSearch 定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为了简化流程,本快速入门指南提供了包含预计算向量的示例酒店文档。 在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

以下代码 uploadDocuments.ts 将文档上传到您的搜索服务。

const DOCUMENTS = [
    // Array of hotel documents with embedded 1536-dimension vectors
    // Each document contains: HotelId, HotelName, Description, DescriptionVector,
    // Category, Tags, ParkingIncluded, LastRenovationDate, Rating, Address, Location
];

const searchClient = new SearchClient(searchEndpoint, indexName, credential);

const result = await searchClient.uploadDocuments(DOCUMENTS);
for (const r of result.results) {
    console.log(`Key: ${r.key}, Succeeded: ${r.succeeded}`);
}

您的代码通过 SearchClient 包提供的主要对象 @azure/search-documents 与托管在 Azure AI 搜索服务中的特定搜索索引进行交互。 SearchClient 提供对索引操作的访问权限,例如:

  • 数据引入:uploadDocuments、、 mergeDocumentsdeleteDocuments

  • 搜索操作:searchautocompletesuggest

查询索引

搜索文件中的查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

searchSingle.ts 演示了一个基本方案,其中你想要查找与矢量查询字符串非常匹配的文档说明。 该 VectorQuery 对象配置矢量搜索:

  • kNearestNeighborsCount 根据矢量相似性限制返回的结果数。
  • fields 指定要搜索的向量字段。
const vectorQuery: VectorQuery<HotelDocument> = {
    vector: vector,
    kNearestNeighborsCount: 5,
    fields: ["DescriptionVector"],
    kind: "vector",
    exhaustive: true
};

const searchOptions: SearchOptions<HotelDocument> = {
    top: 7,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"] as const,
    includeTotalCount: true,
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const results: SearchDocumentsResult<HotelDocument> = await searchClient.search("*", searchOptions);

for await (const result of results.results) {
    const doc = result.document;
    console.log(`HotelId: ${doc.HotelId}, HotelName: ${doc.HotelName}, Score: ${result.score}`);
}

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 searchSingleWithFilter.ts 字段上的 Tags 用于过滤掉不提供免费 Wi-Fi 的酒店。

const searchOptions: SearchOptions<HotelDocument> = {
    top: 7,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"] as const,
    includeTotalCount: true,
    filter: "Tags/any(tag: tag eq 'free wifi')", // Adding filter for "free wifi" tag
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const results: SearchDocumentsResult<HotelDocument> = await searchClient.search("*", searchOptions);

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 searchSingleWithGeoFilter.ts 指定地理点(华盛顿特区,使用经度和纬度坐标)并在 300 公里内返回酒店。 该 filterMode 属性确定筛选器何时运行。 在这种情况下,postFilter 在矢量搜索后运行筛选器。

const searchOptions: SearchOptions<HotelDocument> = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Category", "Description", "Address/City", "Address/StateProvince"] as const,
    facets: ["Address/StateProvince"],
    filter: "geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300",
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

混合搜索 在单个请求中合并全文和矢量查询。 searchHybrid.ts 同时运行这两种查询类型,然后使用倒数排名融合(RRF)将结果合并到统一排名中。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

const vectorQuery: VectorQuery<HotelDocument> = {
    vector: vector,
    kNearestNeighborsCount: 5,
    fields: ["DescriptionVector"],
    kind: "vector",
    exhaustive: true
};

const searchOptions: SearchOptions<HotelDocument> = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Description", "Category", "Tags"] as const,
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

// Use search text for keyword search (hybrid search = vector + keyword)
const searchText = "historic hotel walk to restaurants and shopping";
const results: SearchDocumentsResult<HotelDocument> = await searchClient.search(searchText, searchOptions);

searchSemanticHybrid.ts 演示 语义排名,该排名基于语言理解重新调整结果。

const searchOptions: SearchOptions<HotelDocument> = {
    top: 5,
    includeTotalCount: true,
    select: ["HotelId", "HotelName", "Category", "Description"] as const,
    queryType: "semantic" as const,
    semanticSearchOptions: {
        configurationName: "semantic-config"
    },
    vectorSearchOptions: {
        queries: [vectorQuery],
        filterMode: "postFilter"
    }
};

const searchText = "historic hotel walk to restaurants and shopping";
const results: SearchDocumentsResult<HotelDocument> = await searchClient.search(searchText, searchOptions);

for await (const result of results.results) {
    console.log(`Score: ${result.score}, Re-ranker Score: ${result.rerankerScore}`);
}

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

  • 实际结果包含更多详细信息,包括语义说明和突出显示。 本快速入门修改了结果以提高可读性。 若要获取响应的完整结构,请使用 REST 运行请求。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

否则,请运行以下命令以删除在本快速入门中创建的索引。

npm run build && node -r dotenv/config dist/deleteIndex.js

在本快速入门中,你将使用 适用于 .NET 的 Azure AI 搜索客户端库 来创建、加载和查询 矢量索引。 .NET 客户端库对索引操作的 REST API 提供了抽象。

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-dotnet-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-dotnet-samples/quickstart-vector-search
    code .
    
  3. VectorSearchCreatePopulateIndex/appsettings.json 中,将占位符值 Endpoint 替换为在 Get endpoint 中获取到的 URL。

  4. 请对VectorSearchExamples/appsettings.json重复上一步。

  5. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    

运行代码

  1. 运行第一个项目以创建并填充索引。

    cd VectorSearchCreatePopulateIndex
    dotnet run
    
  2. VectorSearchExamples/Program.cs中,取消注释要运行的查询方法。

  3. 运行第二个项目以针对索引执行这些查询。

    cd ..\VectorSearchExamples
    dotnet run
    

输出

第一个项目的输出包括确认索引创建和成功文档上传。

Creating or updating index 'hotels-vector-quickstart'...
Index 'hotels-vector-quickstart' updated.

Key: 1, Succeeded: True
Key: 2, Succeeded: True
Key: 3, Succeeded: True
Key: 4, Succeeded: True
Key: 48, Succeeded: True
Key: 49, Succeeded: True
Key: 13, Succeeded: True

第二个项目的输出显示每个已启用查询方法的搜索结果。 以下示例显示了单个矢量搜索结果。

Single Vector Search Results:
Score: 0.6605852, HotelId: 48, HotelName: Nordick's Valley Motel
Score: 0.6333684, HotelId: 13, HotelName: Luxury Lion Resort
Score: 0.605672, HotelId: 4, HotelName: Sublime Palace Hotel
Score: 0.6026341, HotelId: 49, HotelName: Swirling Currents Hotel
Score: 0.57902366, HotelId: 2, HotelName: Old Century Hotel

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 以下代码 VectorSearchCreatePopulateIndex/Program.cs 创建索引架构,包括向量字段 DescriptionVector

static async Task CreateSearchIndex(string indexName, SearchIndexClient indexClient)
{
    var addressField = new ComplexField("Address");
    addressField.Fields.Add(new SearchableField("StreetAddress") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft });
    addressField.Fields.Add(new SearchableField("City") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true });
    addressField.Fields.Add(new SearchableField("StateProvince") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true });
    addressField.Fields.Add(new SearchableField("PostalCode") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true });
    addressField.Fields.Add(new SearchableField("region") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true });

    var allFields = new List<SearchField>()
    {
        new SimpleField("HotelId", SearchFieldDataType.String) { IsKey = true, IsFacetable = true, IsFilterable = true },
        new SearchableField("HotelName") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft },
        new SearchableField("Description") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft },
        new VectorSearchField("DescriptionVector", 1536, "my-vector-profile"),
        new SearchableField("Category") { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true },
        new SearchableField("Tags", collection: true) { AnalyzerName = LexicalAnalyzerName.EnMicrosoft, IsFacetable = true, IsFilterable = true },
        new SimpleField("ParkingIncluded", SearchFieldDataType.Boolean) { IsFacetable = true, IsFilterable = true },
        new SimpleField("LastRenovationDate", SearchFieldDataType.DateTimeOffset) { IsSortable = true },
        new SimpleField("Rating", SearchFieldDataType.Double) { IsFacetable = true, IsFilterable = true, IsSortable = true },
        addressField,
        new SimpleField("Location", SearchFieldDataType.GeographyPoint) { IsFilterable = true, IsSortable = true },
    };

    // Create the suggester configuration
    var suggester = new SearchSuggester("sg", new[] { "Address/City", "Address/region" });

    // Create the semantic search
    var semanticSearch = new SemanticSearch()
    {
        Configurations =
        {
            new SemanticConfiguration(
                name: "semantic-config",
                prioritizedFields: new SemanticPrioritizedFields
                {
                    TitleField = new SemanticField("HotelName"),
                    KeywordsFields = { new SemanticField("Category") },
                    ContentFields = { new SemanticField("Description") }
                })
        }
    };

    // Add vector search configuration
    var vectorSearch = new VectorSearch();
    vectorSearch.Algorithms.Add(new HnswAlgorithmConfiguration(name: "my-hnsw-vector-config-1"));
    vectorSearch.Profiles.Add(new VectorSearchProfile(name: "my-vector-profile", algorithmConfigurationName: "my-hnsw-vector-config-1"));

    var definition = new SearchIndex(indexName)
    {
        Fields = allFields,
        Suggesters = { suggester },
        VectorSearch = vectorSearch,
        SemanticSearch = semanticSearch
    };

    // Create or update the index
    Console.WriteLine($"Creating or updating index '{indexName}'...");
    var result = await indexClient.CreateOrUpdateIndexAsync(definition);
    Console.WriteLine($"Index '{result.Value.Name}' updated.");
    Console.WriteLine();
}

要点:

  • 通过创建字段列表来定义索引。

  • 此特定索引支持多个搜索功能:

  • 第二个参数 VectorSearchField 指定 vectorSearchDimensions,它必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • 配置 VectorSearch 定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为了简化流程,本快速入门指南提供了包含预计算向量的示例酒店文档。 在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

以下代码将文档上传到 HotelData.json 搜索服务。

static async Task UploadDocs(SearchClient searchClient)
{
    var jsonPath = Path.Combine(Directory.GetCurrentDirectory(), "HotelData.json");

    // Read and parse hotel data
    var json = await File.ReadAllTextAsync(jsonPath);
    List<Hotel> hotels = new List<Hotel>();
    try
    {
        using var doc = JsonDocument.Parse(json);
        if (doc.RootElement.ValueKind != JsonValueKind.Array)
        {
            Console.WriteLine("HotelData.json root is not a JSON array.");
        }
        // Deserialize all hotel objects
        hotels = doc.RootElement.EnumerateArray()
            .Select(e => JsonSerializer.Deserialize<Hotel>(e.GetRawText()))
            .Where(h => h != null)
            .ToList();
    }
    catch (JsonException ex)
    {
        Console.WriteLine($"Failed to parse HotelData.json: {ex.Message}");
    }

    try
    {
        // Upload hotel documents to Azure Search
        var result = await searchClient.UploadDocumentsAsync(hotels);
        foreach (var r in result.Value.Results)
        {
            Console.WriteLine($"Key: {r.Key}, Succeeded: {r.Succeeded}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed to upload documents: " + ex);
    }
}

您的代码通过 SearchClient 包提供的主要对象 Azure.Search.Documents 与托管在 Azure AI 搜索服务中的特定搜索索引进行交互。 SearchClient 提供对索引操作的访问权限,例如:

  • 数据引入:UploadDocuments()、、 MergeDocuments()DeleteDocuments()

  • 搜索操作:Search()Autocomplete()Suggest()

查询索引

中的 VectorSearchExamples 查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

该方法 SearchSingleVector 演示了一个基本方案,其中你想要查找与矢量查询字符串非常匹配的文档说明。 VectorizedQuery 配置矢量搜索:

  • KNearestNeighborsCount 根据矢量相似性限制返回的结果数。
  • Fields 指定要搜索的向量字段。
public static async Task SearchSingleVector(SearchClient searchClient, ReadOnlyMemory<float> precalculatedVector)
{
    SearchResults<Hotel> response = await searchClient.SearchAsync<Hotel>(
        new SearchOptions
        {
            VectorSearch = new()
            {
                Queries = { new VectorizedQuery(precalculatedVector) { KNearestNeighborsCount = 5, Fields = { "DescriptionVector" } } }
            },
            Select = { "HotelId", "HotelName", "Description", "Category", "Tags" },
        });

    Console.WriteLine($"Single Vector Search Results:");
    await foreach (SearchResult<Hotel> result in response.GetResultsAsync())
    {
        Hotel doc = result.Document;
        Console.WriteLine($"Score: {result.Score}, HotelId: {doc.HotelId}, HotelName: {doc.HotelName}");
    }
    Console.WriteLine();
}

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 此方法在字段 SearchSingleVectorWithFilter 处进行筛选,以过滤掉不提供免费 Wi-Fi 的酒店。

public static async Task SearchSingleVectorWithFilter(SearchClient searchClient, ReadOnlyMemory<float> precalculatedVector)
{
    SearchResults<Hotel> responseWithFilter = await searchClient.SearchAsync<Hotel>(
        new SearchOptions
        {
            VectorSearch = new()
            {
                Queries = { new VectorizedQuery(precalculatedVector) { KNearestNeighborsCount = 5, Fields = { "DescriptionVector" } } }
            },
            Filter = "Tags/any(tag: tag eq 'free wifi')",
            Select = { "HotelId", "HotelName", "Description", "Category", "Tags" }
        });

    Console.WriteLine($"Single Vector Search With Filter Results:");
    await foreach (SearchResult<Hotel> result in responseWithFilter.GetResultsAsync())
    {
        Hotel doc = result.Document;
        Console.WriteLine($"Score: {result.Score}, HotelId: {doc.HotelId}, HotelName: {doc.HotelName}, Tags: {string.Join(String.Empty, doc.Tags)}");
    }
    Console.WriteLine();
}

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 该SingleSearchWithGeoFilter 方法指定一个地理点(例如华盛顿特区,使用经度和纬度坐标),并返回在300公里范围内的酒店。 默认情况下,筛选器在矢量搜索后运行。

public static async Task SingleSearchWithGeoFilter(SearchClient searchClient, ReadOnlyMemory<float> precalculatedVector)
{
    SearchResults<Hotel> responseWithGeoFilter = await searchClient.SearchAsync<Hotel>(
        new SearchOptions
        {
            VectorSearch = new()
            {
                Queries = { new VectorizedQuery(precalculatedVector) { KNearestNeighborsCount = 5, Fields = { "DescriptionVector" } } }
            },
            Filter = "geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300",
            Select = { "HotelId", "HotelName", "Description", "Address", "Category", "Tags" },
            Facets = { "Address/StateProvince" },
        });

    Console.WriteLine($"Vector query with a geo filter:");
    await foreach (SearchResult<Hotel> result in responseWithGeoFilter.GetResultsAsync())
    {
        Hotel doc = result.Document;
        Console.WriteLine($"HotelId: {doc.HotelId}");
        Console.WriteLine($"HotelName: {doc.HotelName}");
        Console.WriteLine($"Score: {result.Score}");
        Console.WriteLine($"City/State: {doc.Address.City}/{doc.Address.StateProvince}");
        Console.WriteLine($"Description: {doc.Description}");
        Console.WriteLine();
    }
    Console.WriteLine();
}

混合搜索 在单个请求中合并全文和矢量查询。 SearchHybridVectorAndText 方法同时运行这两种查询类型,然后使用倒数排名融合 (RRF) 将结果合并到统一排名中。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

public static async Task<SearchResults<Hotel>> SearchHybridVectorAndText(SearchClient searchClient, ReadOnlyMemory<float> precalculatedVector)
{
    SearchResults<Hotel> responseWithFilter = await searchClient.SearchAsync<Hotel>(
        "historic hotel walk to restaurants and shopping",
        new SearchOptions
        {
            VectorSearch = new()
            {
                Queries = { new VectorizedQuery(precalculatedVector) { KNearestNeighborsCount = 5, Fields = { "DescriptionVector" } } }
            },
            Select = { "HotelId", "HotelName", "Description", "Category", "Tags" },
            Size = 5,
        });

    Console.WriteLine($"Hybrid search results:");
    await foreach (SearchResult<Hotel> result in responseWithFilter.GetResultsAsync())
    {
        Hotel doc = result.Document;
        Console.WriteLine($"Score: {result.Score}");
        Console.WriteLine($"HotelId: {doc.HotelId}");
        Console.WriteLine($"HotelName: {doc.HotelName}");
        Console.WriteLine($"Description: {doc.Description}");
        Console.WriteLine($"Category: {doc.Category}");
        Console.WriteLine($"Tags: {string.Join(String.Empty, doc.Tags)}");
        Console.WriteLine();
    }
    Console.WriteLine();
    return responseWithFilter;
}

该方法 SearchHybridVectorAndSemantic 展示 语义排名,该排名是基于语言理解对结果重新排序。

public static async Task SearchHybridVectorAndSemantic(SearchClient searchClient, ReadOnlyMemory<float> precalculatedVector)
{
    SearchResults<Hotel> responseWithFilter = await searchClient.SearchAsync<Hotel>(
        "historic hotel walk to restaurants and shopping",
        new SearchOptions
        {
            IncludeTotalCount = true,
            VectorSearch = new()
            {
                Queries = { new VectorizedQuery(precalculatedVector) { KNearestNeighborsCount = 5, Fields = { "DescriptionVector" } } }
            },
            Select = { "HotelId", "HotelName", "Description", "Category", "Tags" },
            SemanticSearch = new SemanticSearchOptions
            {
                SemanticConfigurationName = "semantic-config"
            },
            QueryType = SearchQueryType.Semantic,
            Size = 5
        });

    Console.WriteLine($"Hybrid search results:");
    await foreach (SearchResult<Hotel> result in responseWithFilter.GetResultsAsync())
    {
        Hotel doc = result.Document;
        Console.WriteLine($"Score: {result.Score}");
        Console.WriteLine($"HotelId: {doc.HotelId}");
        Console.WriteLine($"HotelName: {doc.HotelName}");
        Console.WriteLine($"Description: {doc.Description}");
        Console.WriteLine($"Category: {doc.Category}");
        Console.WriteLine();
    }
    Console.WriteLine();
}

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

  • 实际结果包含更多详细信息,包括语义说明和突出显示。 本快速入门修改了结果以提高可读性。 若要获取响应的完整结构,请使用 REST 运行请求。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

在本快速入门中,你将使用 Azure AI 搜索 REST API 创建、加载和查询 矢量索引

在 Azure AI 搜索中,矢量索引具有一个索引架构,用于定义向量和非矢量字段、用于创建嵌入空间的算法的矢量搜索配置,以及查询时评估的矢量字段定义的设置。 索引 - 创建或更新 (REST API) 创建矢量索引。

小窍门

先决条件

配置访问权限

在开始之前,请确保您有权访问 Azure AI 搜索中的内容和操作。 本快速入门指南使用 Microsoft Entra ID 进行身份验证,并通过基于角色的访问来进行授权。 你必须是 所有者用户访问管理员 才能分配角色。 如果角色设置不可行,请改用基于密钥的身份验证方式

配置建议的基于角色的访问:

  1. 为搜索服务启用基于角色的访问

  2. 将以下角色分配给 用户帐户。

    • 搜索服务参与者

    • 搜索索引数据参与者

    • 搜索索引数据读取者

获取端点

每个 Azure AI 搜索服务都有一个终结点,该 终结点是唯一的 URL,用于标识并提供对服务的网络访问。 在后面的部分中,指定此终结点以编程方式连接到搜索服务。

若要获取端点,请执行以下步骤:

  1. 登录到 Azure 门户 并选择搜索服务。

  2. 在左窗格中,选择“ 概述”。

  3. 记下终结点,形式应与 https://my-service.search.azure.cn 类似。

设置环境

  1. 使用 Git 克隆示例存储库。

    git clone https://github.com/Azure-Samples/azure-search-rest-samples
    
  2. 导航到快速入门文件夹,并在 Visual Studio Code 中打开它。

    cd azure-search-rest-samples/Quickstart-vectors
    code .
    
  3. az-search-quickstart-vectors.rest 中,将占位符值 @baseUrl 替换为在 Get endpoint 中获取到的 URL。

  4. 若要使用 Microsoft Entra ID 进行无密钥身份验证,请登录到 Azure 帐户。 如果有多个订阅,请选择包含 Azure AI 搜索服务的订阅。

    az login
    
  5. 使用 Microsoft Entra ID 进行无钥匙身份验证时,请生成一个访问令牌。

    az account get-access-token --scope https://search.azure.cn/.default --query accessToken -o tsv
    
  6. 将占位符值 @token 替换为上一步的访问令牌。

运行代码

  1. ### List existing indexes by name中选择发送请求以验证您的连接。

    相邻窗格中应该会显示响应。 如果有现有索引,则会列出它们。 否则,列表将为空。 如果 HTTP 状态码是 200 OK,则可以继续。

  2. 按顺序发送剩余的请求,以创建矢量索引、上传文档并运行不同类型的矢量查询。

输出

每个查询请求返回 JSON 结果。 以下示例是请求的 ### Run a single vector query 输出,该输出显示按相似性分数排名的矢量搜索结果。

{
  "@odata.count": 5,
  "value": [
    {
      "@search.score": 0.6605852,
      "HotelId": "48",
      "HotelName": "Nordick's Valley Motel",
      "Description": "Only 90 miles (about 2 hours) from the nation's city and nearby most everything the historic valley has to offer. Hiking? Wine Tasting? Exploring the caverns? It's all nearby and we have specially priced packages to help make our B&B your home base for fun while visiting the valley.",
      "Category": "Boutique",
      "Tags": [
        "continental breakfast",
        "air conditioning",
        "free wifi"
      ]
    },
    {
      "@search.score": 0.6333684,
      "HotelId": "13",
      "HotelName": "Luxury Lion Resort",
      "Description": "Unmatched Luxury. Visit our downtown hotel to indulge in luxury accommodations. Moments from the stadium and transportation hubs, we feature the best in convenience and comfort.",
      "Category": "Luxury",
      "Tags": [
        "bar",
        "concierge",
        "restaurant"
      ]
    },
    {
      "@search.score": 0.605672,
      "HotelId": "4",
      "HotelName": "Sublime Palace Hotel",
      "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
      "Category": "Boutique",
      "Tags": [
        "concierge",
        "view",
        "air conditioning"
      ]
    },
    {
      "@search.score": 0.6026341,
      "HotelId": "49",
      "HotelName": "Swirling Currents Hotel",
      "Description": "Spacious rooms, glamorous suites and residences, rooftop pool, walking access to shopping, dining, entertainment and the city center. Each room comes equipped with a microwave, a coffee maker and a minifridge. In-room entertainment includes complimentary W-Fi and flat-screen TVs.",
      "Category": "Suite",
      "Tags": [
        "air conditioning",
        "laundry service",
        "24-hour front desk service"
      ]
    },
    {
      "@search.score": 0.57902366,
      "HotelId": "2",
      "HotelName": "Old Century Hotel",
      "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
      "Category": "Boutique",
      "Tags": [
        "pool",
        "free wifi",
        "air conditioning",
        "concierge"
      ]
    }
  ]
}

了解代码

注意事项

本部分中的代码片段可能已修改为可读性。 有关完整的工作示例,请参阅源代码。

运行代码后,让我们分解关键步骤:

  1. 创建矢量索引
  2. 将文档上传到索引
  3. 查询索引

创建矢量索引

在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。 本快速入门调用 索引 - 创建 (REST API) 以在搜索服务上生成命名 hotels-vector-quickstart 的矢量索引及其物理数据结构。

索引架构围绕酒店内容进行组织。 示例数据由虚构酒店矢量和非矢量描述组成。 以下摘录显示了请求的关键 ### Create a new index 结构。

PUT {{baseUrl}}/indexes/hotels-vector-quickstart?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "name": "hotels-vector-quickstart",
    "fields": [
        { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true },
        { "name": "HotelName", "type": "Edm.String", "searchable": true },
        { "name": "Description", "type": "Edm.String", "searchable": true },
        {
            "name": "DescriptionVector",
            "type": "Collection(Edm.Single)",
            "searchable": true,
            "dimensions": 1536,
            "vectorSearchProfile": "my-vector-profile"
        },
        { "name": "Category", "type": "Edm.String", "filterable": true, "facetable": true },
        { "name": "Tags", "type": "Collection(Edm.String)", "filterable": true, "facetable": true }
        // Additional fields omitted for brevity
    ],
    "vectorSearch": {
        "algorithms": [
            { "name": "hnsw-vector-config", "kind": "hnsw" }
        ],
        "profiles": [
            { "name": "my-vector-profile", "algorithm": "hnsw-vector-config" }
        ]
    },
    "semantic": {
        "configurations": [
            {
                "name": "semantic-config",
                "prioritizedFields": {
                    "titleField": { "fieldName": "HotelName" },
                    "prioritizedContentFields": [{ "fieldName": "Description" }]
                }
            }
        ]
    }
}

要点:

  • 此特定索引支持多个搜索功能:

  • dimensions 属性必须与嵌入模型的输出大小匹配。 本快速入门使用 1,536 个维度来匹配 text-embedding-ada-002 模型。

  • vectorSearch 部分定义近似近邻 (ANN) 算法。 支持的算法包括分层可导航小型世界(HNSW)和详尽的 K-近邻(KNN)。 有关详细信息,请参阅 矢量搜索中的相关性

将文档上传到索引

新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。

在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为简单起见,本快速入门以内联 JSON 形式提供示例酒店文档。 但是,在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。

本快速入门调用 Documents - Index (REST API) 将示例酒店文档添加到索引。 以下摘录显示了### Upload 7 documents请求的结构。

POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/index?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "value": [
        {
            "@search.action": "mergeOrUpload",
            "HotelId": "1",
            "HotelName": "Stay-Kay City Hotel",
            "Description": "This classic hotel is ideally located on the main commercial artery of the city...",
            "DescriptionVector": [-0.0347, 0.0289, ... ],  // 1536 floats
            "Category": "Boutique",
            "Tags": ["view", "air conditioning", "concierge"],
            "ParkingIncluded": false,
            "Rating": 3.60,
            "Address": { "City": "Beijing", "StateProvince": "NY" },
            "Location": { "type": "Point", "coordinates": [-73.975403, 40.760586] }
        }
        // Additional documents omitted for brevity
    ]
}

要点:

  • 数组中的每个 value 文档都表示一个酒店,并包含与索引架构匹配的字段。 该 @search.action 参数指定要对每个文档执行的操作。 本快速入门使用 mergeOrUpload,如果文档不存在,则添加文档;如果文档不存在,则更新文档。

  • 有效负载中的文档由索引架构中定义的字段组成。

查询索引

示例文件中的查询演示了不同的搜索模式。 示例矢量查询基于两个字符串:

  • 全文搜索字符串: "historic hotel walk to restaurants and shopping"

  • 矢量查询字符串: "quintessential lodging near running trails, eateries, retail" (矢量化为数学表示形式)

矢量查询字符串在语义上类似于全文搜索字符串,但它包括索引中不存在的术语。 仅关键字搜索矢量查询字符串将返回零结果。 但是,矢量搜索根据含义而不是确切关键字查找相关的匹配项。

以下示例以基本矢量查询开头,并逐步添加筛选器、关键字搜索和语义重新调整。

### Run a single vector query 请求演示了一个基本方案,你希望找到与矢量查询字符串非常匹配的文档说明。 该 vectorQueries 数组配置矢量搜索:

  • k 根据矢量相似性限制返回的结果数。
  • fields 指定要搜索的向量字段。
POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/search?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "count": true,
    "select": "HotelId, HotelName, Description, Category, Tags",
    "vectorQueries": [
        {
            "vector": [ ... ],  // 1536-dimensional vector of "quintessential lodging near running trails, eateries, retail"
            "k": 5,
            "fields": "DescriptionVector",
            "kind": "vector",
            "exhaustive": true
        }
    ]
}

使用筛选器进行单向量搜索

在 Azure AI 搜索中, 筛选器 适用于索引中的非函数字段。 请求 ### Run a vector query with a filter 会筛选字段 Tags 上的筛选器,以筛选出不提供免费 Wi-Fi 的任何酒店。

POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/search?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "count": true,
    "select": "HotelId, HotelName, Description, Category, Tags",
    "filter": "Tags/any(tag: tag eq 'free wifi')",
    "vectorFilterMode": "postFilter",
    "vectorQueries": [
        {
            "vector": [ ... ],  // 1536-dimensional vector
            "k": 7,
            "fields": "DescriptionVector",
            "kind": "vector",
            "exhaustive": true
        }
    ]
}

使用地理筛选器进行单向量搜索

可以指定 地理空间筛选器 ,以将结果限制为特定的地理区域。 该 ### Run a vector query with a geo filter 请求指定了一个地理点(华盛顿哥伦比亚特区,使用经度和纬度坐标),并返回距离该点 300 公里范围内的酒店。 该 vectorFilterMode 参数确定筛选器何时运行。 在这种情况下,postFilter 在矢量搜索后运行筛选器。

POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/search?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "count": true,
    "select": "HotelId, HotelName, Address/City, Address/StateProvince, Description",
    "filter": "geo.distance(Location, geography'POINT(-77.03241 38.90166)') le 300",
    "vectorFilterMode": "postFilter",
    "top": 5,
    "facets": [ "Address/StateProvince"],
    "vectorQueries": [
        {
            "vector": [ ... ],  // 1536-dimensional vector
            "k": 5,
            "fields": "DescriptionVector",
            "kind": "vector",
            "exhaustive": true
        }
    ]
}

混合搜索 在单个请求中合并全文和矢量查询。 请求 ### Run a hybrid query 同时运行这两种查询类型,然后使用 Reciprocal Rank Fusion(RRF)将结果合并为统一排名。 RRF 使用每个结果集中的结果排名反函数生成合并的排名。 请注意,混合搜索分数通常比单查询分数更小。

POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/search?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "count": true,
    "search": "historic hotel walk to restaurants and shopping",
    "select": "HotelId, HotelName, Category, Tags, Description",
    "top": 5,
    "vectorQueries": [
        {
            "vector": [ ... ],  // 1536-dimensional vector
            "k": 5,
            "fields": "DescriptionVector",
            "kind": "vector",
            "exhaustive": true
        }
    ]
}

请求 ### Run a hybrid query with semantic reranking 演示 语义排名,基于语言理解重新排序结果。

POST {{baseUrl}}/indexes/hotels-vector-quickstart/docs/search?api-version={{api-version}}  HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{token}}

{
    "count": true,
    "search": "historic hotel walk to restaurants and shopping",
    "select": "HotelId, HotelName, Category, Description",
    "queryType": "semantic",
    "semanticConfiguration": "semantic-config",
    "top": 5,
    "vectorQueries": [
        {
            "vector": [ ... ],  // 1536-dimensional vector
            "k": 7,
            "fields": "DescriptionVector",
            "kind": "vector",
            "exhaustive": true
        }
    ]
}

将这些结果与上一查询中的混合搜索结果进行比较。 如果未进行语义重排,则 Sublime Palace Hotel 排名第一,因为倒数排名融合 (RRF) 会结合文本和失量分数来生成合并结果。 语义重排序后,Swirling Currents酒店升至榜首。

语义排名器使用计算机理解模型来评估每个结果与查询意向匹配程度。 Swirling Currents Hotel 的描述提到 "walking access to shopping, dining, entertainment and the city center",这与搜索查询的 "walk to restaurants and shopping" 高度一致。 对于附近餐饮和购物的语义匹配使其优于 Sublime 故宫酒店,而后者并未在其描述中强调步行可达的便利设施。

要点:

  • 在混合搜索中,可以将矢量搜索与关键字的全文搜索相集成。 筛选器和语义排名仅适用于文本内容,而不适用于矢量。

清理资源

在自己的订阅中工作时,最好通过删除不再需要的资源来完成项目。 持续运行的资源可能会产生费用。

在 Azure 门户中,从左窗格中选择 “所有资源 ”或 “资源组 ”以查找和管理资源。 可以单独删除资源,也可以删除资源组以一次性删除所有资源。

否则,可以发送 ### Delete an index 请求以删除在本快速入门中创建的索引。