|
| 1 | +--- |
| 2 | +author: haileytap |
| 3 | +ms.author: haileytapia |
| 4 | +ms.service: azure-ai-search |
| 5 | +ms.custom: |
| 6 | + - ignite-2023 |
| 7 | +ms.topic: include |
| 8 | +ms.date: 07/09/2025 |
| 9 | +--- |
| 10 | + |
| 11 | +[!INCLUDE [Semantic ranker introduction](semantic-ranker-intro.md)] |
| 12 | + |
| 13 | +## Set up the client |
| 14 | + |
| 15 | +In this quickstart, you use an IDE and the [**@azure/search-documents**](https://www.npmjs.com/package/@azure/search-documents) client library to add semantic ranking to an existing search index. |
| 16 | + |
| 17 | +We recommend [Visual Studio Code](https://code.visualstudio.com/) for this quickstart. |
| 18 | + |
| 19 | +> [!TIP] |
| 20 | +> You can [download the source code](https://github.com/Azure-Samples/azure-search-javascript-samples/tree/main/quickstart-semantic-ranking-js) to start with a finished project or follow these steps to create your own. |
| 21 | +
|
| 22 | +### Set up local development environment |
| 23 | + |
| 24 | +1. Start Visual Studio Code in a new directory. |
| 25 | + |
| 26 | + ```bash |
| 27 | + mkdir semantic-ranking-quickstart && cd semantic-ranking-quickstart |
| 28 | + code . |
| 29 | + ``` |
| 30 | + |
| 31 | +1. Create a new package for ESM modules in your project directory. |
| 32 | + |
| 33 | + ```bash |
| 34 | + npm init -y |
| 35 | + npm pkg set type=module |
| 36 | + ``` |
| 37 | + |
| 38 | +1. Install packages, including [azure-search-documents](/javascript/api/%40azure/search-documents). |
| 39 | + |
| 40 | + ```bash |
| 41 | + npm install @azure/identity @azure/search-documents dotenv |
| 42 | + ``` |
| 43 | + |
| 44 | + |
| 45 | +1. Create a `src` directory in your project directory. |
| 46 | + |
| 47 | + ```bash |
| 48 | + mkdir src |
| 49 | + ``` |
| 50 | + |
| 51 | +1. Rename `sample.env` to `.env`, and provide your search service endpoint. You can get the endpoint from the Azure portal on the search service **Overview** page. |
| 52 | + |
| 53 | + ```ini |
| 54 | + AZURE_SEARCH_ENDPOINT=https://ai-search-dib-2.search.windows.net |
| 55 | + AZURE_SEARCH_INDEX_NAME=hotels-sample-index |
| 56 | + SEMANTIC_CONFIGURATION_NAME=semantic-config |
| 57 | + ``` |
| 58 | + |
| 59 | +### Sign in to Azure |
| 60 | + |
| 61 | +If you signed in to the [Azure portal](https://portal.azure.com), you're signed into Azure. If you aren't sure, use the Azure CLI or Azure PowerShell to log in: `az login` or `az connect`. If you have multiple tenants and subscriptions, see [Quickstart: Connect without keys](../../search-get-started-rbac.md) for help on how to connect. |
| 62 | + |
| 63 | +## Create a common authentication file |
| 64 | + |
| 65 | +Create a file in `./src` called `config.js` to hold the environment variables and authentication credential. Copy in the following code. This file will be used by all the other files in this quickstart. |
| 66 | + |
| 67 | +```javascript |
| 68 | +import { DefaultAzureCredential } from "@azure/identity"; |
| 69 | +
|
| 70 | +// Configuration - use environment variables |
| 71 | +export const searchEndpoint = process.env.AZURE_SEARCH_ENDPOINT || "PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE"; |
| 72 | +export const searchApiKey = process.env.AZURE_SEARCH_API_KEY || "PUT-YOUR-SEARCH-SERVICE-ADMIN-API-KEY-HERE"; |
| 73 | +export const indexName = process.env.AZURE_SEARCH_INDEX_NAME || "hotels-sample-index"; |
| 74 | +
|
| 75 | +// Create credential |
| 76 | +export const credential = new DefaultAzureCredential(); |
| 77 | +
|
| 78 | +console.log(`Using Azure Search endpoint: ${searchEndpoint}`); |
| 79 | +console.log(`Using index name: ${indexName}\n\n`); |
| 80 | +``` |
| 81 | +
|
| 82 | +## Get configuration for the index |
| 83 | +
|
| 84 | +In this section, you get settings for the existing `hotels-sample-index` index on your search service. |
| 85 | +
|
| 86 | +1. Create a file in `./src` called `getIndexSettings.js` and copy in the following code. |
| 87 | +
|
| 88 | + ```javascript |
| 89 | + import { |
| 90 | + SearchIndexClient |
| 91 | + } from "@azure/search-documents"; |
| 92 | + import { searchEndpoint, indexName, credential } from "./config.js"; |
| 93 | + |
| 94 | + const indexClient = new SearchIndexClient(searchEndpoint, credential); |
| 95 | + |
| 96 | + console.log('Getting semantic search index settings...'); |
| 97 | + |
| 98 | + // Get the existing schema |
| 99 | + const index = await indexClient.getIndex(indexName); |
| 100 | + |
| 101 | + console.log(`Index name: ${index.name}`); |
| 102 | + console.log(`Number of fields: ${index.fields.length}`); |
| 103 | + |
| 104 | + for(const field of index.fields) { |
| 105 | + console.log(`Field: ${field.name}, Type: ${field.type}, Searchable: ${field.searchable}`); |
| 106 | + } |
| 107 | + |
| 108 | + if(index.semanticSearch && index.semanticSearch.configurations) { |
| 109 | + console.log(`Semantic search configurations: ${index.semanticSearch.configurations.length}`); |
| 110 | + for(const config of index.semanticSearch.configurations) { |
| 111 | + console.log(`Configuration name: ${config.name}`); |
| 112 | + console.log(`Title field: ${config.prioritizedFields.titleField?.name}`); |
| 113 | + } |
| 114 | + } else { |
| 115 | + console.log("No semantic configuration exists for this index."); |
| 116 | + } |
| 117 | + ``` |
| 118 | +
|
| 119 | +1. Run the code: |
| 120 | +
|
| 121 | + ```bash |
| 122 | + node -r dotenv/config src/getIndexSettings.js |
| 123 | + ``` |
| 124 | +
|
| 125 | +1. The output |
| 126 | +
|
| 127 | +1. Output is the name of the index, list of fields, and a statement indicating whether a semantic configuration exists. For the purposes of this quickstart, the message should say `No semantic configuration exists for this index`. |
| 128 | +
|
| 129 | +## Update the index with a semantic configuration |
| 130 | +
|
| 131 | +1. Create a file in `./src` called `updateIndexSettings.js` and copy in the following code to add a semantic configuration to the existing `hotels-sample-index` index on your search service. No search documents are deleted by this operation and your index is still operational after the configuration is added. |
| 132 | +
|
| 133 | + ```javascript |
| 134 | + import { |
| 135 | + SearchIndexClient |
| 136 | + } from "@azure/search-documents"; |
| 137 | + import { searchEndpoint, indexName, credential } from "./config.js"; |
| 138 | + |
| 139 | + const indexClient = new SearchIndexClient(searchEndpoint, credential); |
| 140 | + |
| 141 | + console.log('Getting semantic search index settings...'); |
| 142 | + |
| 143 | + // Get the existing schema |
| 144 | + const index = await indexClient.getIndex(indexName); |
| 145 | + |
| 146 | + console.log(`Index name: ${index.name}`); |
| 147 | + console.log(`Number of fields: ${index.fields.length}`); |
| 148 | + |
| 149 | + for(const field of index.fields) { |
| 150 | + console.log(`Field: ${field.name}, Type: ${field.type}, Searchable: ${field.searchable}`); |
| 151 | + } |
| 152 | + |
| 153 | + if(index.semanticSearch && index.semanticSearch.configurations) { |
| 154 | + console.log(`Semantic search configurations: ${index.semanticSearch.configurations.length}`); |
| 155 | + for(const config of index.semanticSearch.configurations) { |
| 156 | + console.log(`Configuration name: ${config.name}`); |
| 157 | + console.log(`Title field: ${config.prioritizedFields.titleField?.name}`); |
| 158 | + } |
| 159 | + } else { |
| 160 | + console.log("No semantic configuration exists for this index."); |
| 161 | + } |
| 162 | + ``` |
| 163 | + |
| 164 | +1. Run the code. |
| 165 | +
|
| 166 | + ```bash |
| 167 | + node -r dotenv/config src/updateIndexSettings.js |
| 168 | + ``` |
| 169 | +
|
| 170 | +1. Output is the semantic configuration you just added, `Semantic configuration updated successfully.`. |
| 171 | +
|
| 172 | +## Run semantic queries |
| 173 | +
|
| 174 | +Once the `hotels-sample-index` index has a semantic configuration, you can run queries that include semantic parameters. |
| 175 | +
|
| 176 | +1. Create a file in `./src` called `semanticQuery.js` and copy in the following code to create a semantic query of the index. This is the minimum requirement for invoking semantic ranking. |
| 177 | +
|
| 178 | + ```javascript |
| 179 | + import { SearchClient } from "@azure/search-documents"; |
| 180 | + import { credential, searchEndpoint, indexName, semanticConfigurationName } from "./config.js"; |
| 181 | + |
| 182 | + const searchClient = new SearchClient( |
| 183 | + searchEndpoint, |
| 184 | + indexName, |
| 185 | + credential |
| 186 | + ); |
| 187 | + |
| 188 | + const results = await searchClient.search("walking distance to live music", { |
| 189 | + queryType: "semantic", |
| 190 | + semanticSearchOptions: { |
| 191 | + configurationName: semanticConfigurationName |
| 192 | + }, |
| 193 | + select: ["HotelId", "HotelName", "Description"] |
| 194 | + }); |
| 195 | + |
| 196 | + let rowNumber = 1; |
| 197 | + for await (const result of results.results) { |
| 198 | + // Log each result |
| 199 | + const doc = result.document; |
| 200 | + const score = result.score; |
| 201 | + const rerankerScoreDisplay = result.rerankerScore; |
| 202 | + |
| 203 | + console.log(`Search result #${rowNumber++}:`); |
| 204 | + console.log(` Re-ranker Score: ${rerankerScoreDisplay}`); |
| 205 | + console.log(` HotelId: ${doc.HotelId}`); |
| 206 | + console.log(` HotelName: ${doc.HotelName}`); |
| 207 | + console.log(` Description: ${doc.Description || 'N/A'}\n`); |
| 208 | + } |
| 209 | + ``` |
| 210 | +
|
| 211 | +1. Run the code. |
| 212 | +
|
| 213 | + ```bash |
| 214 | + node -r dotenv/config src/semanticQuery.js |
| 215 | + ``` |
| 216 | +
|
| 217 | +1. Output should consist of 13 documents, ordered by the `rerankerScoreDisplay`. |
| 218 | +
|
| 219 | +### Return captions |
| 220 | +
|
| 221 | +Optionally, you can add captions to extract portions of the text and apply hit highlighting to the important terms and phrases. This query adds captions. |
| 222 | +
|
| 223 | +1. Create a file in `./src` called `semanticQueryReturnCaptions.js` and copy in the following code to add captions to the query. |
| 224 | +
|
| 225 | + ```javascript |
| 226 | + import { SearchClient } from "@azure/search-documents"; |
| 227 | + import { credential, searchEndpoint, indexName, semanticConfigurationName } from "./config.js"; |
| 228 | + |
| 229 | + const searchClient = new SearchClient( |
| 230 | + searchEndpoint, |
| 231 | + indexName, |
| 232 | + credential |
| 233 | + ); |
| 234 | + |
| 235 | + console.log(`Using semantic configuration: ${semanticConfigurationName}`); |
| 236 | + console.log("Search query: walking distance to live music"); |
| 237 | + |
| 238 | + const results = await searchClient.search("walking distance to live music", { |
| 239 | + queryType: "semantic", |
| 240 | + semanticSearchOptions: { |
| 241 | + configurationName: semanticConfigurationName, |
| 242 | + captions: { |
| 243 | + captionType: "extractive", |
| 244 | + highlight: true |
| 245 | + } |
| 246 | + }, |
| 247 | + select: ["HotelId", "HotelName", "Description"], |
| 248 | + }); |
| 249 | + |
| 250 | + console.log(`Found ${results.count} results with semantic search\n`); |
| 251 | + let rowNumber = 1; |
| 252 | + |
| 253 | + for await (const result of results.results) { |
| 254 | + // Log each result |
| 255 | + const doc = result.document; |
| 256 | + const rerankerScoreDisplay = result.rerankerScore; |
| 257 | + |
| 258 | + console.log(`Search result #${rowNumber++}:`); |
| 259 | + console.log(` Re-ranker Score: ${rerankerScoreDisplay}`); |
| 260 | + console.log(` HotelName: ${doc.HotelName}`); |
| 261 | + console.log(` Description: ${doc.Description || 'N/A'}\n`); |
| 262 | + |
| 263 | + // Caption handling with better debugging |
| 264 | + const captions = result.captions; |
| 265 | + |
| 266 | + if (captions && captions.length > 0) { |
| 267 | + const caption = captions[0]; |
| 268 | + |
| 269 | + if (caption.highlights) { |
| 270 | + console.log(` Caption with highlights: ${caption.highlights}`); |
| 271 | + } else if (caption.text) { |
| 272 | + console.log(` Caption text: ${caption.text}`); |
| 273 | + } else { |
| 274 | + console.log(` Caption exists but has no text or highlights content`); |
| 275 | + } |
| 276 | + } else { |
| 277 | + console.log(" No captions found for this result"); |
| 278 | + } |
| 279 | + console.log("-".repeat(60)); |
| 280 | + } |
| 281 | + ``` |
| 282 | +
|
| 283 | +1. Run the code. |
| 284 | +
|
| 285 | + ```bash |
| 286 | + node -r dotenv/config src/semanticQueryReturnCaptions.js |
| 287 | + ``` |
| 288 | +
|
| 289 | +1. Output should include a new caption element alongside search field. Captions are the most relevant passages in a result. If your index includes larger chunks of text, a caption is helpful for extracting the most interesting sentences. |
| 290 | +
|
| 291 | + ```console |
| 292 | + Search result #1: |
| 293 | + Re-ranker Score: 2.613231658935547 |
| 294 | + HotelName: Uptown Chic Hotel |
| 295 | + Description: Chic hotel near the city. High-rise hotel in downtown, within walking distance to theaters, art galleries, restaurants and shops. Visit Seattle Art Museum by day, and then head over to Benaroya Hall to catch the evening's concert performance. |
| 296 | + |
| 297 | + Caption with highlights: Chic hotel near the city. High-rise hotel in downtown, within walking distance to<em> theaters, </em>art galleries, restaurants and shops. Visit<em> Seattle Art Museum </em>by day, and then head over to<em> Benaroya Hall </em>to catch the evening's concert performance. |
| 298 | + ``` |
| 299 | +
|
| 300 | +### Return semantic answers |
| 301 | +
|
| 302 | +In this final query, return semantic answers. |
| 303 | +
|
| 304 | +Semantic ranker can produce an answer to a query string that has the characteristics of a question. The generated answer is extracted verbatim from your content so it won't include composed content like what you might expect from a chat completion model. If the semantic answer isn't useful for your scenario, you can omit `semantic_answers` from your code. |
| 305 | +
|
| 306 | +To produce a semantic answer, the question and answer must be closely aligned, and the model must find content that clearly answers the question. If potential answers fail to meet a confidence threshold, the model doesn't return an answer. For demonstration purposes, the question in this example is designed to get a response so that you can see the syntax. |
| 307 | +
|
| 308 | +1. Create a file in `./src` called `semanticAnswer.js` and copy in the following code to get semantic answers. |
| 309 | +
|
| 310 | + ```javascript |
| 311 | + import { SearchClient } from "@azure/search-documents"; |
| 312 | + import { credential, searchEndpoint, indexName, semanticConfigurationName } from "./config.js"; |
| 313 | + |
| 314 | + const searchClient = new SearchClient( |
| 315 | + searchEndpoint, |
| 316 | + indexName, |
| 317 | + credential |
| 318 | + ); |
| 319 | + |
| 320 | + const results = await searchClient.search("walking distance to live music", { |
| 321 | + queryType: "semantic", |
| 322 | + semanticSearchOptions: { |
| 323 | + configurationName: semanticConfigurationName, |
| 324 | + captions: { |
| 325 | + captionType: "extractive" |
| 326 | + }, |
| 327 | + answers: { |
| 328 | + answerType: "extractive" |
| 329 | + } |
| 330 | + }, |
| 331 | + select: ["HotelName", "Description", "Category"] |
| 332 | + }); |
| 333 | + |
| 334 | + console.log(`Answers:\n\n`); |
| 335 | + let rowNumber = 1; |
| 336 | + |
| 337 | + // Extract semantic answers from the search results |
| 338 | + const semanticAnswers = results.answers; |
| 339 | + for (const answer of semanticAnswers || []) { |
| 340 | + console.log(`Semantic answer result #${rowNumber++}:`); |
| 341 | + if (answer.highlights) { |
| 342 | + console.log(`Semantic Answer: ${answer.highlights}`); |
| 343 | + } else { |
| 344 | + console.log(`Semantic Answer: ${answer.text}`); |
| 345 | + } |
| 346 | + console.log(`Semantic Answer Score: ${answer.score}\n\n`); |
| 347 | + } |
| 348 | + |
| 349 | + console.log(`Search Results:\n\n`); |
| 350 | + rowNumber = 1; |
| 351 | + |
| 352 | + // Iterate through the search results |
| 353 | + for await (const result of results.results) { |
| 354 | + // Log each result |
| 355 | + const doc = result.document; |
| 356 | + const rerankerScoreDisplay = result.rerankerScore; |
| 357 | + |
| 358 | + console.log(`Search result #${rowNumber++}:`); |
| 359 | + console.log(`${rerankerScoreDisplay}`); |
| 360 | + console.log(`${doc.HotelName}`); |
| 361 | + console.log(`${doc.Description || 'N/A'}`); |
| 362 | + |
| 363 | + const captions = result.captions; |
| 364 | + |
| 365 | + if (captions && captions.length > 0) { |
| 366 | + const caption = captions[0]; |
| 367 | + if (caption.highlights) { |
| 368 | + console.log(`Caption: ${caption.highlights}\n`); |
| 369 | + } else { |
| 370 | + console.log(`Caption: ${caption.text}\n`); |
| 371 | + } |
| 372 | + } |
| 373 | + } |
| 374 | + ``` |
| 375 | +
|
| 376 | +1. Run the code. |
| 377 | +
|
| 378 | + ```bash |
| 379 | + node -r dotenv/config src/semanticAnswer.js |
| 380 | + ``` |
| 381 | +
|
| 382 | +1. Output should look similar to the following example, where the best answer to question is pulled from one of the results. |
| 383 | +
|
| 384 | + Recall that answers are *verbatim content* pulled from your index and might be missing phrases that a user would expect to see. To get *composed answers* as generated by a chat completion model, considering using a [RAG pattern](../../retrieval-augmented-generation-overview.md) or [agentic retrieval](../../search-agentic-retrieval-concept.md). |
| 385 | + |
| 386 | + ```console |
| 387 | + Semantic answer result #1: |
| 388 | + Semantic Answer: All of the suites feature full-sized kitchens stocked with cookware, separate living and sleeping areas and sofa beds. Some of the larger rooms have fireplaces and patios or balconies. Experience real country hospitality in the heart of bustling Nashville. The most vibrant<em> music scene </em>in the world is<em> just outside your front door.</em> |
| 389 | + Semantic Answer Score: 0.9860000014305115 |
| 390 | + ``` |
0 commit comments