Edit

Share via


Quickstart: Generative search (RAG) using grounding data from Azure AI Search

In this quickstart, you send queries to a chat completion model for a conversational search experience over your indexed content on Azure AI Search. After setting up Azure OpenAI and Azure AI Search resources in the Azure portal, you run code to call the APIs.

Prerequisites

Configure access

Requests to the search endpoint must be authenticated and authorized. You can use API keys or roles for this task. Keys are easier to start with, but roles are more secure. This quickstart assumes roles.

You're setting up two clients, so you need permissions on both resources.

Azure AI Search is receiving the query request from your local system. Assign yourself the Search Index Data Reader role assignment if the hotels sample index already exists. If it doesn't exist, assign yourself Search Service Contributor and Search Index Data Contributor roles so that you can create and query the index.

Azure OpenAI is receiving the query and the search results from your local system. Assign yourself the Cognitive Services OpenAI User role on Azure OpenAI.

  1. Sign in to the Azure portal.

  2. Configure Azure AI Search for role-based access:

    1. In the Azure portal, find your Azure AI Search service.

    2. On the left menu, select Settings > Keys, and then select either Role-based access control or Both.

  3. Assign roles:

    1. On the left menu, select Access control (IAM).

    2. On Azure AI Search, select these roles to create, load, and query a search index, and then assign them to your Microsoft Entra ID user identity:

      • Search Index Data Contributor
      • Search Service Contributor
    3. On Azure OpenAI, select Access control (IAM) to assign this role to yourself on Azure OpenAI:

      • Cognitive Services OpenAI User

It can take several minutes for permissions to take effect.

Create an index

A search index provides grounding data for the chat model. We recommend the hotels-sample-index, which can be created in minutes and runs on any search service tier. This index is created using built-in sample data.

  1. In the Azure portal, find your search service.

  2. On the Overview home page, select Import data to start the wizard.

  3. On the Connect to your data page, select Samples from the dropdown list.

  4. Choose the hotels-sample.

  5. Select Next through the remaining pages, accepting the default values.

  6. Once the index is created, select Search management > Indexes from the left menu to open the index.

  7. Select Edit JSON.

  8. Scroll to the end of the index, where you can find placeholders for constructs that can be added to an index.

    "analyzers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": [],
    "normalizers": [],
    
  9. On a new line after "normalizers", paste in the following semantic configuration. This example specifies a "defaultConfiguration", which is important to the running of this quickstart.

    "semantic":{
       "defaultConfiguration":"semantic-config",
       "configurations":[
          {
             "name":"semantic-config",
             "prioritizedFields":{
                "titleField":{
                   "fieldName":"HotelName"
                },
                "prioritizedContentFields":[
                   {
                      "fieldName":"Description"
                   }
                ],
                "prioritizedKeywordsFields":[
                   {
                      "fieldName":"Category"
                   },
                   {
                      "fieldName":"Tags"
                   }
                ]
             }
          }
       ]
    },
    
  10. Save your changes.

  11. Run the following query in Search Explorer to test your index: complimentary breakfast.

    Output should look similar to the following example. Results that are returned directly from the search engine consist of fields and their verbatim values, along with metadata like a search score and a semantic ranking score and caption if you use semantic ranker. We used a select statement to return just the HotelName, Description, and Tags fields.

    {
    "@odata.count": 18,
    "@search.answers": [],
    "value": [
       {
          "@search.score": 2.2896252,
          "@search.rerankerScore": 2.506816864013672,
          "@search.captions": [
          {
             "text": "Head Wind Resort. Suite. coffee in lobby\r\nfree wifi\r\nview. The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a **complimentary continental breakfast** in the lobby, and free Wi-Fi throughout the hotel..",
             "highlights": ""
          }
          ],
          "HotelName": "Head Wind Resort",
          "Description": "The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a complimentary continental breakfast in the lobby, and free Wi-Fi throughout the hotel.",
          "Tags": [
          "coffee in lobby",
          "free wifi",
          "view"
          ]
       },
       {
          "@search.score": 2.2158256,
          "@search.rerankerScore": 2.288334846496582,
          "@search.captions": [
          {
             "text": "Swan Bird Lake Inn. Budget. continental breakfast\r\nfree wifi\r\n24-hour front desk service. We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins..",
             "highlights": ""
          }
          ],
          "HotelName": "Swan Bird Lake Inn",
          "Description": "We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins.",
          "Tags": [
          "continental breakfast",
          "free wifi",
          "24-hour front desk service"
          ]
       },
       {
          "@search.score": 0.92481667,
          "@search.rerankerScore": 2.221315860748291,
          "@search.captions": [
          {
             "text": "White Mountain Lodge & Suites. Resort and Spa. continental breakfast\r\npool\r\nrestaurant. Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings..",
             "highlights": ""
          }
          ],
          "HotelName": "White Mountain Lodge & Suites",
          "Description": "Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings.",
          "Tags": [
          "continental breakfast",
          "pool",
          "restaurant"
          ]
       },
       . . .
    ]}
    

Get service endpoints

In the remaining sections, you set up API calls to Azure OpenAI and Azure AI Search. Get the service endpoints so that you can provide them as variables in your code.

  1. Sign in to the Azure portal.

  2. Find your search service.

  3. On the Overview home page, copy the URL. An example endpoint might look like https://example.search.windows.net.

  4. Find your Azure OpenAI service.

  5. On the Overview home page, select the link to view the endpoints. Copy the URL. An example endpoint might look like https://example.openai.azure.com/.

Sign in to Azure

You're using Microsoft Entra ID and role assignments for the connection. Make sure you're logged in to the same tenant and subscription as Azure AI Search and Azure OpenAI. You can use the Azure CLI on the command line to show current properties, change properties, and to sign in. For more information, see Connect without keys.

Run each of the following commands in sequence.

az account show

az account set --subscription <PUT YOUR SUBSCRIPTION ID HERE>

az login --tenant <PUT YOUR TENANT ID HERE>

You should now be logged in to Azure from your local device.

Set up the .NET app

To follow along with the steps ahead, you can either clone the completed sample app from GitHub, or create the app yourself.

Clone the sample app

To access the completed sample app for this article:

  1. Clone the azure-search-dotnet-samples repo from GitHub.

    git clone https://github.com/Azure-Samples/azure-search-dotnet-samples
    
  2. Navigate into the quickstart-rag folder.

  3. Open the quickstart-rag folder in Visual Studio Code or open the solution file using Visual Studio.

Create the sample app

Complete the following steps to create a .NET console app to connect to an AI model.

  1. In an empty directory on your computer, use the dotnet new command to create a new console app:

    dotnet new console -o AISearchRag
    
  2. Change directory into the app folder:

    cd AISearchRag
    
  3. Install the required packages:

    dotnet add package Azure.AI.OpenAI
    dotnet add package Azure.Identity
    dotnet add package Azure.Search.Documents
    
  4. Open the app in Visual Studio Code (or your editor of choice).

    code .
    

Set up the query and chat thread

The following example demonstrates how to set up a minimal RAG scenario using Azure AI Search to provide an OpenAI model with contextual resources to improve the generated responses.

  1. In the minimal-query project of the sample repo, open the Program.cs file to view the first example. If you created the project yourself, add the following code to connect to and query the Azure AI Search and Azure OpenAI services.

    Note

    Make sure to replace the placeholders for the Azure OpenAI endpoint and model name, as well as the Azure AI Search endpoint and index name.

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Azure.Identity;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Models;
    using Azure.AI.OpenAI;
    using OpenAI.Chat;
    using System.Text.Json;
    using Microsoft.Extensions.Logging;
    
    // Azure resource endpoints and deployment info
    string azureSearchServiceEndpoint = "azure-ai-search-endpoint";
    string azureOpenAIEndpoint = "azure-ai-openai-endpoint";
    string azureDeploymentModel = "azure-ai-deployment-name";
    string indexName = "hotels-sample-index";
    
    // Set up Azure credentials and clients
    var credential = new DefaultAzureCredential();
    var searchClient = new SearchClient(new Uri(azureSearchServiceEndpoint), indexName, credential);
    var openAIClient = new AzureOpenAIClient(new Uri(azureOpenAIEndpoint), credential);
    
    // Prompt template for grounding the LLM response in search results
    string GROUNDED_PROMPT = @"You are a friendly assistant that recommends hotels based on activities and amenities.
        Answer the query using only the sources provided below in a friendly and concise bulleted manner
        Answer ONLY with the facts listed in the list of sources below.
        If there isn't enough information below, say you don't know.
        Do not generate answers that don't use the sources below.
        Query: {0}
        Sources: {1}";
    
    // The user's query
    string query = "Can you recommend a few hotels with complimentary breakfast?";
    
    // Configure search options: top 5 results, select relevant fields
    var options = new SearchOptions { Size = 5 };
    options.Select.Add("Description");
    options.Select.Add("HotelName");
    options.Select.Add("Tags");
    
    // Execute the search
    var searchResults = await searchClient.SearchAsync<SearchDocument>(query, options);
    var sources = new List<string>();
    
    await foreach (var result in searchResults.Value.GetResultsAsync())
    {
        var doc = result.Document;
        // Format each result as: HotelName:Description:Tags
        sources.Add($"{doc["HotelName"]}:{doc["Description"]}:{doc["Tags"]}");
    }
    string sourcesFormatted = string.Join("\n", sources);
    
    // Format the prompt with the query and sources
    string formattedPrompt = string.Format(GROUNDED_PROMPT, query, sourcesFormatted);
    
    // Create a chat client for the specified deployment/model
    ChatClient chatClient = openAIClient.GetChatClient(azureDeploymentModel);
    
    // Send the prompt to the LLM and stream the response
    var chatUpdates = chatClient.CompleteChatStreamingAsync(
        [ new UserChatMessage(formattedPrompt) ]
    );
    
    // Print the streaming response to the console
    await foreach (var chatUpdate in chatUpdates)
    {
        if (chatUpdate.Role.HasValue)
        {
            Console.Write($"{chatUpdate.Role} : ");
        }
        foreach (var contentPart in chatUpdate.ContentUpdate)
        {
            Console.Write(contentPart.Text);
        }
    }
    

    The preceding code accomplishes the following:

    • Searches an Azure Search index for hotels matching a user query about complimentary breakfast, retrieving hotel name, description, and tags.
    • Formats the search results into a structured list to serve as contextual sources for the generative AI model.
    • Constructs a prompt instructing the Azure OpenAI model to answer using only the provided sources.
    • Sends the prompt to the AI model and streams the generated response.
    • Outputs the AI’s response to the console, displaying both the role and content as it streams.
  2. Run the project to initiate a basic RAG scenario. The output from Azure OpenAI consists of recommendations for several hotels, such as the following example:

    Sure! Here are a few hotels that offer complimentary breakfast:
    
    - **Head Wind Resort**
    - Complimentary continental breakfast in the lobby
    - Free Wi-Fi throughout the hotel
    
    - **Double Sanctuary Resort**
    - Continental breakfast included
    
    - **White Mountain Lodge & Suites**
    - Continental breakfast available
    
    - **Swan Bird Lake Inn**
    - Continental-style breakfast each morning with a variety of food and drinks 
       such as caramel cinnamon rolls, coffee, orange juice, milk, cereal, 
       instant oatmeal, bagels, and muffins
    

To experiment further, change the query and rerun the last step to better understand how the model works with the grounding data. You can also modify the prompt to change the tone or structure of the output.

Troubleshooting

You might receive any of the following errors while testing:

  • Forbidden: Check Azure AI Search configuration to make sure role-based access is enabled.
  • Authorization failed: Wait a few minutes and try again. It can take several minutes for role assignments to become operational.
  • Resource not found: Check the resource URIs and make sure the API version on the chat model is valid.

Send a complex RAG query

Azure AI Search supports complex types for nested JSON structures. In the hotels-sample-index, Address is an example of a complex type, consisting of Address.StreetAddress, Address.City, Address.StateProvince, Address.PostalCode, and Address.Country. The index also has complex collection of Rooms for each hotel. If your index has complex types, your query can provide those fields if you first convert the search results output to JSON, and then pass the JSON to the chat model.

  1. In the complex-query project of the sample repo, open the Program.cs file. If you created the project yourself, replace your code with the following:

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Azure.Identity;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Models;
    using Azure.AI.OpenAI;
    using OpenAI.Chat;
    using System.Text.Json;
    using Microsoft.Extensions.Logging;
    
    // Azure resource endpoints and deployment info
    string azureSearchServiceEndpoint = "azure-ai-search-endpoint";
    string azureOpenAIEndpoint = "azure-ai-openai-endpoint";
    string azureDeploymentModel = "azure-ai-deployment-name";
    string indexName = "hotels-sample-index";
    
    // Set up Azure credentials and clients
    var credential = new DefaultAzureCredential();
    var searchClient = new SearchClient(new Uri(azureSearchServiceEndpoint), indexName, credential);
    var openAIClient = new AzureOpenAIClient(new Uri(azureOpenAIEndpoint), credential);
    
    // Prompt template for the OpenAI model
    string groundedPrompt =
        @"You are a friendly assistant that recommends hotels based on activities and amenities.
        Answer the query using only the sources provided below in a friendly and concise bulleted manner.
        Answer ONLY with the facts listed in the list of sources below.
        If there isn't enough information below, say you don't know.
        Do not generate answers that don't use the sources below.
        Query: {0}
        Sources: {1}";
    
    // The user query and fields to select from search
    var query = "Can you recommend a few hotels that offer complimentary breakfast? Tell me their description, address, tags, and the rate for one room that sleeps 4 people.";
    var selectedFields = new[] { "HotelName", "Description", "Address", "Rooms", "Tags" };
    
    // Configure search options
    var options = new SearchOptions { Size = 5 };
    foreach (var field in selectedFields)
    {
        options.Select.Add(field);
    }
    
    // Run Azure Cognitive Search
    var searchResults = await searchClient.SearchAsync<SearchDocument>(query, options);
    
    // Filter and format search results
    var sourcesFiltered = new List<Dictionary<string, object>>();
    await foreach (var result in searchResults.Value.GetResultsAsync())
    {
        sourcesFiltered.Add(
            selectedFields
                .Where(f => result.Document.TryGetValue(f, out _))
                .ToDictionary(f => f, f => result.Document[f])
        );
    }
    var sourcesFormatted = string.Join("\n", sourcesFiltered.ConvertAll(source => JsonSerializer.Serialize(source)));
    
    // Format the prompt for OpenAI
    string formattedPrompt = string.Format(groundedPrompt, query, sourcesFormatted);
    
    // Get a chat client for the OpenAI deployment
    ChatClient chatClient = openAIClient.GetChatClient(azureDeploymentModel);
    
    // Send the prompt to Azure OpenAI and stream the response
    var chatUpdates = chatClient.CompleteChatStreamingAsync(
        new[] { new UserChatMessage(formattedPrompt) }
    );
    
    // Output the streamed chat response
    await foreach (var chatUpdate in chatUpdates)
    {
        if (chatUpdate.Role.HasValue)
        {
            Console.Write($"{chatUpdate.Role} : ");
        }
        foreach (var contentPart in chatUpdate.ContentUpdate)
        {
            Console.Write(contentPart.Text);
        }
    }
    
  2. Run the project to initiate a basic RAG scenario. The output from Azure OpenAI consists of recommendations for several hotels, such as the following example:

    1. **Double Sanctuary Resort**
       - **Description**: 5-star luxury hotel with the biggest rooms in the city. Recognized as the #1 hotel in the area by Traveler magazine. Features include free WiFi, flexible check-in/out, a fitness center, and in-room espresso.
       - **Address**: 2211 Elliott Ave, Seattle, WA, 98121, USA
       - **Tags**: view, pool, restaurant, bar, continental breakfast
       - **Room Rate for 4 People**: 
         - Suite, 2 Queen Beds: $254.99 per night
    
    2. **Starlight Suites**
       - **Description**: Spacious all-suite hotel with complimentary airport shuttle and WiFi. Facilities include an indoor/outdoor pool, fitness center, and Florida Green certification. Complimentary coffee and HDTV are also available.
       - **Address**: 19575 Biscayne Blvd, Aventura, FL, 33180, USA
       - **Tags**: pool, coffee in lobby, free wifi
       - **Room Rate for 4 People**:
         - Suite, 2 Queen Beds (Cityside): $231.99 per night
         - Deluxe Room, 2 Queen Beds (Waterfront View): $148.99 per night
    
    3. **Good Business Hotel**
       - **Description**: Located one mile from the airport with free WiFi, an outdoor pool, and a complimentary airport shuttle. Close proximity to Lake Lanier and downtown. The business center includes printers, a copy machine, fax, and a work area.
       - **Address**: 4400 Ashford Dunwoody Rd NE, Atlanta, GA, 30346, USA
       - **Tags**: pool, continental breakfast, free parking
       - **Room Rate for 4 People**:
         - Budget Room, 2 Queen Beds (Amenities): $60.99 per night
         - Deluxe Room, 2 Queen Beds (Amenities): $139.99 per night
    

Troubleshooting

If you see output messages while debugging related to ManagedIdentityCredential and token acquisition failures, it could be that you have multiple tenants, and your Azure sign-in is using a tenant that doesn't have your search service. To get your tenant ID, search the Azure portal for "tenant properties" or run az login tenant list.

Once you have your tenant ID, run az login --tenant <YOUR-TENANT-ID> at a command prompt, and then rerun the script.

You can also log errors in your code by creating an instance of ILogger:

using var loggerFactory = LoggerFactory.Create(builder =>
{
   builder.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Program>();

Clean up

When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.

You can find and manage resources in the Azure portal by using the All resources or Resource groups link in the leftmost pane.

Prerequisites

Configure access

Requests to the search endpoint must be authenticated and authorized. You can use API keys or roles for this task. Keys are easier to start with, but roles are more secure. This quickstart assumes roles.

You're setting up two clients, so you need permissions on both resources.

Azure AI Search is receiving the query request from your local system. Assign yourself the Search Index Data Reader role assignment if the hotels sample index already exists. If it doesn't exist, assign yourself Search Service Contributor and Search Index Data Contributor roles so that you can create and query the index.

Azure OpenAI is receiving the query and the search results from your local system. Assign yourself the Cognitive Services OpenAI User role on Azure OpenAI.

  1. Sign in to the Azure portal.

  2. Configure Azure AI Search for role-based access:

    1. In the Azure portal, find your Azure AI Search service.

    2. On the left menu, select Settings > Keys, and then select either Role-based access control or Both.

  3. Assign roles:

    1. On the left menu, select Access control (IAM).

    2. On Azure AI Search, select these roles to create, load, and query a search index, and then assign them to your Microsoft Entra ID user identity:

      • Search Index Data Contributor
      • Search Service Contributor
    3. On Azure OpenAI, select Access control (IAM) to assign this role to yourself on Azure OpenAI:

      • Cognitive Services OpenAI User

It can take several minutes for permissions to take effect.

Create an index

A search index provides grounding data for the chat model. We recommend the hotels-sample-index, which can be created in minutes and runs on any search service tier. This index is created using built-in sample data.

  1. In the Azure portal, find your search service.

  2. On the Overview home page, select Import data to start the wizard.

  3. On the Connect to your data page, select Samples from the dropdown list.

  4. Choose the hotels-sample.

  5. Select Next through the remaining pages, accepting the default values.

  6. Once the index is created, select Search management > Indexes from the left menu to open the index.

  7. Select Edit JSON.

  8. Scroll to the end of the index, where you can find placeholders for constructs that can be added to an index.

    "analyzers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": [],
    "normalizers": [],
    
  9. On a new line after "normalizers", paste in the following semantic configuration. This example specifies a "defaultConfiguration", which is important to the running of this quickstart.

    "semantic":{
       "defaultConfiguration":"semantic-config",
       "configurations":[
          {
             "name":"semantic-config",
             "prioritizedFields":{
                "titleField":{
                   "fieldName":"HotelName"
                },
                "prioritizedContentFields":[
                   {
                      "fieldName":"Description"
                   }
                ],
                "prioritizedKeywordsFields":[
                   {
                      "fieldName":"Category"
                   },
                   {
                      "fieldName":"Tags"
                   }
                ]
             }
          }
       ]
    },
    
  10. Save your changes.

  11. Run the following query in Search Explorer to test your index: complimentary breakfast.

    Output should look similar to the following example. Results that are returned directly from the search engine consist of fields and their verbatim values, along with metadata like a search score and a semantic ranking score and caption if you use semantic ranker. We used a select statement to return just the HotelName, Description, and Tags fields.

    {
    "@odata.count": 18,
    "@search.answers": [],
    "value": [
       {
          "@search.score": 2.2896252,
          "@search.rerankerScore": 2.506816864013672,
          "@search.captions": [
          {
             "text": "Head Wind Resort. Suite. coffee in lobby\r\nfree wifi\r\nview. The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a **complimentary continental breakfast** in the lobby, and free Wi-Fi throughout the hotel..",
             "highlights": ""
          }
          ],
          "HotelName": "Head Wind Resort",
          "Description": "The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a complimentary continental breakfast in the lobby, and free Wi-Fi throughout the hotel.",
          "Tags": [
          "coffee in lobby",
          "free wifi",
          "view"
          ]
       },
       {
          "@search.score": 2.2158256,
          "@search.rerankerScore": 2.288334846496582,
          "@search.captions": [
          {
             "text": "Swan Bird Lake Inn. Budget. continental breakfast\r\nfree wifi\r\n24-hour front desk service. We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins..",
             "highlights": ""
          }
          ],
          "HotelName": "Swan Bird Lake Inn",
          "Description": "We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins.",
          "Tags": [
          "continental breakfast",
          "free wifi",
          "24-hour front desk service"
          ]
       },
       {
          "@search.score": 0.92481667,
          "@search.rerankerScore": 2.221315860748291,
          "@search.captions": [
          {
             "text": "White Mountain Lodge & Suites. Resort and Spa. continental breakfast\r\npool\r\nrestaurant. Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings..",
             "highlights": ""
          }
          ],
          "HotelName": "White Mountain Lodge & Suites",
          "Description": "Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings.",
          "Tags": [
          "continental breakfast",
          "pool",
          "restaurant"
          ]
       },
       . . .
    ]}
    

Get service endpoints

In the remaining sections, you set up API calls to Azure OpenAI and Azure AI Search. Get the service endpoints so that you can provide them as variables in your code.

  1. Sign in to the Azure portal.

  2. Find your search service.

  3. On the Overview home page, copy the URL. An example endpoint might look like https://example.search.windows.net.

  4. Find your Azure OpenAI service.

  5. On the Overview home page, select the link to view the endpoints. Copy the URL. An example endpoint might look like https://example.openai.azure.com/.

Set up environment variables for local development

  1. Create a .env file.

  2. Add the following environment variables to the .env file, replacing the values with your own service endpoints and keys.

    AZURE_SEARCH_ENDPOINT=<YOUR AZURE AI SEARCH ENDPOINT>
    AZURE_SEARCH_INDEX_NAME=hotels-sample-index
    
    AZURE_OPENAI_ENDPOINT=<YOUR AZURE OPENAI ENDPOINT>
    AZURE_OPENAI_VERSION=<YOUR AZURE OPENAI API VERSION>
    AZURE_DEPLOYMENT_MODEL=<YOUR DEPLOYMENT NAME>
    

Set up the Node.JS project

Set up project with Visual Studio Code and TypeScript.

  1. Start Visual Studio Code in a new directory.

    mkdir rag-quickstart && cd rag-quickstart
    code .
    
  2. Create a new package for ESM modules in your project directory.

    npm init -y
    npm pkg set type=module
    

    This creates a package.json file with default values.

  3. Install the following npm packages.

    npm install @azure/identity @azure/search-documents openai dotenv 
    
  4. Create a src directory in your project directory.

    mkdir src
    

Sign in to Azure

You're using Microsoft Entra ID and role assignments for the connection. Make sure you're logged in to the same tenant and subscription as Azure AI Search and Azure OpenAI. You can use the Azure CLI on the command line to show current properties, change properties, and to sign in. For more information, see Connect without keys.

Run each of the following commands in sequence.

az account show

az account set --subscription <PUT YOUR SUBSCRIPTION ID HERE>

az login --tenant <PUT YOUR TENANT ID HERE>

You should now be logged in to Azure from your local device.

Set up query and chat thread

Create a query script that uses the Azure AI Search index and the chat model to generate responses based on grounding data. The following steps guide you through setting up the query script.

  1. Create a query.js file in the src directory with the following code.

    // This is a RAG (Retrieval Augmented Generation) implementation that:
    // 1. Takes a user query about hotels
    // 2. Searches a hotel database using Azure AI Search
    // 3. Formats the search results for the LLM
    // 4. Sends the query and formatted results to Azure OpenAI
    // 5. Returns a grounded response based only on the retrieved information
    
    import { SearchClient } from "@azure/search-documents";
    import { AzureOpenAI } from "openai";
    import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
    
    function getClients() {
    
        const credential = new DefaultAzureCredential();
    
        // Search
        const azureSearchEndpoint = process.env.AZURE_SEARCH_ENDPOINT;
        const azureSearchIndexName = process.env.AZURE_SEARCH_INDEX_NAME;
    
        const searchClient = new SearchClient(
            azureSearchEndpoint,
            azureSearchIndexName,
            credential
        );
    
    
        // OpenAI
        const azureOpenAiEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
        const azureOpenAiApiVersion = process.env.AZURE_OPENAI_VERSION;
        const azureOpenAiDeploymentName = process.env.AZURE_DEPLOYMENT_MODEL;
    
        const scope = "https://cognitiveservices.azure.com/.default";
        const azureADTokenProvider = getBearerTokenProvider(credential, scope);
        const options = { azureADTokenProvider, deployment: azureOpenAiDeploymentName, apiVersion: azureOpenAiApiVersion, endpoint: azureOpenAiEndpoint }
        const openaiClient = new AzureOpenAI(options);
    
        return { openaiClient, searchClient, modelName: azureOpenAiDeploymentName };
    }
    
    async function queryAISearchForSources(searchClient, query) {
        console.log(`Searching for: "${query}"\n`);
        const searchResults = await searchClient.search(query, {
            top: 5,
            select: ["Description", "HotelName", "Tags"]
        });
    
        const sources = [];
        for await (const result of searchResults.results) {
            const doc = result.document;
            sources.push(
                `Hotel: ${doc.HotelName}\n` +
                `Description: ${doc.Description}\n` +
                `Tags: ${Array.isArray(doc.Tags) ? doc.Tags.join(', ') : doc.Tags}\n`
            );
        }
        const sourcesFormatted = sources.join("\n---\n");
        return sourcesFormatted;
    }
    async function queryOpenAIForResponse(openaiClient, query, sourcesFormatted, modelName) {
    
        const GROUNDED_PROMPT = `
     You are a friendly assistant that recommends hotels based on activities and amenities.
     Answer the query using only the sources provided below in a friendly and concise bulleted manner.
     Answer ONLY with the facts listed in the list of sources below.
     If there isn't enough information below, say you don't know.
     Do not generate answers that don't use the sources below.
    
    Query: {query}
    Sources: {sources}
    `;
    
        return openaiClient.chat.completions.create({
            model: modelName,
            messages: [
                {
                    role: "user",
                    content: GROUNDED_PROMPT.replace("{query}", query).replace("{sources}", sourcesFormatted),
                }
            ],
            temperature: 0.7,
            max_tokens: 800,
        });
    }
    
    async function main() {
    
        const { openaiClient, searchClient, modelName } = getClients();
    
        const query = "Can you recommend a few hotels with complimentary breakfast?";
    
        const sources = await queryAISearchForSources(searchClient, query);
        const response = await queryOpenAIForResponse(openaiClient, query, sources, modelName);
    
        // Print the response from the chat model
        const content = response.choices[0].message.content;
        if (content) {
            console.log(content);
        } else {
            console.log("No content available in the response.");
        }
    }
    
    main().catch((error) => {
        console.error("An error occurred:", error);
        process.exit(1);
    });
    

    The preceding code does the following:

    • Imports the necessary libraries for Azure AI Search and Azure OpenAI.
    • Uses environment variables to configure the Azure AI Search and Azure OpenAI clients.
    • Defines a function to get the clients for Azure AI Search and Azure OpenAI, using environment variables for configuration.
    • Defines a function to query Azure AI Search for sources based on the user query.
    • Defines a function to query Azure OpenAI for a response based on the user query and the sources retrieved from Azure AI Search.
    • The main function orchestrates the flow by calling the search and OpenAI functions, and then prints the response.
  2. Run the following command in a terminal to execute the query script:

    node -r dotenv/config query.js
    

    The .env is passed into the runtime using the -r dotenv/config.

  3. View the output, which consists of recommendations for several hotels. Here's an example of what the output might look like:

    Sure! Here are a few hotels that offer complimentary breakfast:
    
    - **Head Wind Resort**
    - Complimentary continental breakfast in the lobby
    - Free Wi-Fi throughout the hotel
    
    - **Double Sanctuary Resort**
    - Continental breakfast included
    
    - **White Mountain Lodge & Suites**
    - Continental breakfast available
    
    - **Swan Bird Lake Inn**
    - Continental-style breakfast each morning with a variety of food and drinks 
        such as caramel cinnamon rolls, coffee, orange juice, milk, cereal, 
        instant oatmeal, bagels, and muffins
    

Troubleshooting

If you get a Forbidden error message, check Azure AI Search configuration to make sure role-based access is enabled.

If you get an Authorization failed error message, wait a few minutes and try again. It can take several minutes for role assignments to become operational.

If you get a Resource not found error message, check the resource URIs and make sure the API version on the chat model is valid.

Otherwise, to experiment further, change the query and rerun the last step to better understand how the model works with the grounding data.

You can also modify the prompt to change the tone or structure of the output.

You might also try the query without semantic ranking by setting use_semantic_reranker=False in the query parameters step. Semantic ranking can noticably improve the relevance of query results and the ability of the LLM to return useful information. Experimentation can help you decide whether it makes a difference for your content.

Send a complex RAG query

Azure AI Search supports complex types for nested JSON structures. In the hotels-sample-index, Address is an example of a complex type, consisting of Address.StreetAddress, Address.City, Address.StateProvince, Address.PostalCode, and Address.Country. The index also has complex collection of Rooms for each hotel.

If your index has complex types, change your prompt to include formatting instructions:

Can you recommend a few hotels that offer complimentary breakfast? 
Tell me their description, address, tags, and the rate for one room that sleeps 4 people.
  1. Create a new file queryComplex.js.

  2. Copy the following code to the file:

    // This is a RAG (Retrieval Augmented Generation) implementation that:
    // 1. Takes a user query about hotels
    // 2. Searches a hotel database using Azure AI Search
    // 3. Formats the search results for the LLM
    // 4. Sends the query and formatted results to Azure OpenAI
    // 5. Returns a grounded response based only on the retrieved information
    
    import { SearchClient } from "@azure/search-documents";
    import { AzureOpenAI } from "openai";
    import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
    
    function getClients() {
    
        const credential = new DefaultAzureCredential();
    
        // Search
        const azureSearchEndpoint = process.env.AZURE_SEARCH_ENDPOINT;
        const azureSearchIndexName = process.env.AZURE_SEARCH_INDEX_NAME;
    
        const searchClient = new SearchClient(
            azureSearchEndpoint,
            azureSearchIndexName,
            credential
        );
    
    
        // OpenAI
        const azureOpenAiEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
        const azureOpenAiApiVersion = process.env.AZURE_OPENAI_VERSION;
        const azureOpenAiDeploymentName = process.env.AZURE_DEPLOYMENT_MODEL;
    
        const scope = "https://cognitiveservices.azure.com/.default";
        const azureADTokenProvider = getBearerTokenProvider(credential, scope);
        const options = { azureADTokenProvider, deployment: azureOpenAiDeploymentName, apiVersion: azureOpenAiApiVersion, endpoint: azureOpenAiEndpoint }
        const openaiClient = new AzureOpenAI(options);
    
        return { openaiClient, searchClient, modelName: azureOpenAiDeploymentName };
    }
    
    async function queryAISearchForSources(
        searchClient,
        query
    ) {
        console.log(`Searching for: "${query}"\n`);
    
        const selectedFields = ["HotelName", "Description", "Address", "Rooms", "Tags"];
        const searchResults = await searchClient.search(query, {
            top: 5,
            select: selectedFields,
            queryType: "semantic",
            semanticSearchOptions: {},
        });
    
        return searchResults;
    }
    async function queryOpenAIForResponse(
        openaiClient, 
        query, 
        sourcesFormatted, 
        modelName
    ){
    
        const GROUNDED_PROMPT = `
     You are a friendly assistant that recommends hotels based on activities and amenities.
     Answer the query using only the sources provided below in a friendly and concise bulleted manner.
     Answer ONLY with the facts listed in the list of sources below.
     If there isn't enough information below, say you don't know.
     Do not generate answers that don't use the sources below.
    
    Query: {query}
    Sources: {sources}
    `;
    
        return openaiClient.chat.completions.create({
            model: modelName,
            messages: [
                {
                    role: "user",
                    content: GROUNDED_PROMPT.replace("{query}", query).replace("{sources}", sourcesFormatted),
                }
            ],
            temperature: 0.7,
            max_tokens: 800,
        });
    }
    
    async function main() {
    
        const { openaiClient, searchClient, modelName } = getClients();
    
        const query = `
        Can you recommend a few hotels that offer complimentary breakfast? 
        Tell me their description, address, tags, and the rate for one room that sleeps 4 people.
        `;
    
        const sourcesResult = await queryAISearchForSources(searchClient, query);
        let sourcesFormatted = "";
    
        for await (const result of sourcesResult.results) {
            // Explicitly typing result to ensure compatibility
            sourcesFormatted += JSON.stringify(result.document) + "\n";
        }
    
        const response = await queryOpenAIForResponse(openaiClient, query, sourcesFormatted.trim(), modelName);
    
        // Print the response from the chat model
        const content = response.choices[0].message.content;
        if (content) {
            console.log(content);
        } else {
            console.log("No content available in the response.");
        }
    }
    
    main().catch((error) => {
        console.error("An error occurred:", error);
        process.exit(1);
    });
    
  3. Run the following command in a terminal to execute the query script:

    node -r dotenv/config queryComplex.js
    

    The .env is passed into the runtime using the -r dotenv/config.

  4. View the output from Azure OpenAI, and it adds content from complex types.

    Here are a few hotels that offer complimentary breakfast and have rooms that sleep 4 people:
    
    1. **Head Wind Resort**
       - **Description:** The best of old town hospitality combined with views of the river and 
       cool breezes off the prairie. Enjoy a complimentary continental breakfast in the lobby, 
       and free Wi-Fi throughout the hotel.
       - **Address:** 7633 E 63rd Pl, Tulsa, OK 74133, USA
       - **Tags:** Coffee in lobby, free Wi-Fi, view
       - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99
    
    2. **Double Sanctuary Resort**
       - **Description:** 5-star Luxury Hotel - Biggest Rooms in the city. #1 Hotel in the area 
       listed by Traveler magazine. Free WiFi, Flexible check in/out, Fitness Center & espresso 
       in room. Offers continental breakfast.
       - **Address:** 2211 Elliott Ave, Seattle, WA 98121, USA
       - **Tags:** View, pool, restaurant, bar, continental breakfast
       - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99
    
    3. **Swan Bird Lake Inn**
       - **Description:** Continental-style breakfast featuring a variety of food and drinks. 
       Locally made caramel cinnamon rolls are a favorite.
       - **Address:** 1 Memorial Dr, Cambridge, MA 02142, USA
       - **Tags:** Continental breakfast, free Wi-Fi, 24-hour front desk service
       - **Room for 4:** Budget Room, 2 Queen Beds (City View) - $85.99
    
    4. **Gastronomic Landscape Hotel**
       - **Description:** Known for its culinary excellence under the management of William Dough, 
       offers continental breakfast.
       - **Address:** 3393 Peachtree Rd, Atlanta, GA 30326, USA
       - **Tags:** Restaurant, bar, continental breakfast
       - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $66.99
    ...
       - **Tags:** Pool, continental breakfast, free parking
       - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $60.99
    
    Enjoy your stay! Let me know if you need any more information.
    

Troubleshooting errors

To debug Azure SDK errors, set the environment variable AZURE_LOG_LEVEL to one of the following: verbose, info, warning, error. This will enable detailed logging for the Azure SDK, which can help identify issues with authentication, network connectivity, or other problems.

Rerun the query script. You should now get informational statements from the SDKs in the output that provide more detail about any issues.

If you see output messages related to ManagedIdentityCredential and token acquisition failures, it could be that you have multiple tenants, and your Azure sign-in is using a tenant that doesn't have your search service. To get your tenant ID, search the Azure portal for "tenant properties" or run az login tenant list.

Once you have your tenant ID, run az login --tenant <YOUR-TENANT-ID> at a command prompt, and then rerun the script.

Clean up

When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.

You can find and manage resources in the Azure portal by using the All resources or Resource groups link in the leftmost pane.

Prerequisites

Download file

Download a Jupyter notebook from GitHub to send the requests in this quickstart. For more information, see Downloading files from GitHub.

You can also start a new file on your local system and create requests manually by using the instructions in this article.

Configure access

Requests to the search endpoint must be authenticated and authorized. You can use API keys or roles for this task. Keys are easier to start with, but roles are more secure. This quickstart assumes roles.

You're setting up two clients, so you need permissions on both resources.

Azure AI Search is receiving the query request from your local system. Assign yourself the Search Index Data Reader role assignment if the hotels sample index already exists. If it doesn't exist, assign yourself Search Service Contributor and Search Index Data Contributor roles so that you can create and query the index.

Azure OpenAI is receiving the query and the search results from your local system. Assign yourself the Cognitive Services OpenAI User role on Azure OpenAI.

  1. Sign in to the Azure portal.

  2. Configure Azure AI Search for role-based access:

    1. In the Azure portal, find your Azure AI Search service.

    2. On the left menu, select Settings > Keys, and then select either Role-based access control or Both.

  3. Assign roles:

    1. On the left menu, select Access control (IAM).

    2. On Azure AI Search, select these roles to create, load, and query a search index, and then assign them to your Microsoft Entra ID user identity:

      • Search Index Data Contributor
      • Search Service Contributor
    3. On Azure OpenAI, select Access control (IAM) to assign this role to yourself on Azure OpenAI:

      • Cognitive Services OpenAI User

It can take several minutes for permissions to take effect.

Update the hotels-sample-index

A search index provides grounding data for the chat model. We recommend the hotels-sample-index, which can be created in minutes and runs on any search service tier. This index is created using built-in sample data.

  1. In the Azure portal, find your search service.

  2. On the Overview home page, select Import data to start the wizard.

  3. On the Connect to your data page, select Samples from the dropdown list.

  4. Choose the hotels-sample.

  5. Select Next through the remaining pages, accepting the default values.

  6. Once the index is created, select Search management > Indexes from the left menu to open the index.

  7. Select Edit JSON.

  8. Scroll to the end of the index, where you can find placeholders for constructs that can be added to an index.

    "analyzers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": [],
    "normalizers": [],
    
  9. On a new line after "normalizers", paste in the following semantic configuration. This example specifies a "defaultConfiguration", which is important to the running of this quickstart.

    "semantic":{
       "defaultConfiguration":"semantic-config",
       "configurations":[
          {
             "name":"semantic-config",
             "prioritizedFields":{
                "titleField":{
                   "fieldName":"HotelName"
                },
                "prioritizedContentFields":[
                   {
                      "fieldName":"Description"
                   }
                ],
                "prioritizedKeywordsFields":[
                   {
                      "fieldName":"Category"
                   },
                   {
                      "fieldName":"Tags"
                   }
                ]
             }
          }
       ]
    },
    
  10. Save your changes.

  11. Run the following query in Search Explorer to test your index: complimentary breakfast.

    Output should look similar to the following example. Results that are returned directly from the search engine consist of fields and their verbatim values, along with metadata like a search score and a semantic ranking score and caption if you use semantic ranker. We used a select statement to return just the HotelName, Description, and Tags fields.

    {
    "@odata.count": 18,
    "@search.answers": [],
    "value": [
       {
          "@search.score": 2.2896252,
          "@search.rerankerScore": 2.506816864013672,
          "@search.captions": [
          {
             "text": "Head Wind Resort. Suite. coffee in lobby\r\nfree wifi\r\nview. The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a **complimentary continental breakfast** in the lobby, and free Wi-Fi throughout the hotel..",
             "highlights": ""
          }
          ],
          "HotelName": "Head Wind Resort",
          "Description": "The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a complimentary continental breakfast in the lobby, and free Wi-Fi throughout the hotel.",
          "Tags": [
          "coffee in lobby",
          "free wifi",
          "view"
          ]
       },
       {
          "@search.score": 2.2158256,
          "@search.rerankerScore": 2.288334846496582,
          "@search.captions": [
          {
             "text": "Swan Bird Lake Inn. Budget. continental breakfast\r\nfree wifi\r\n24-hour front desk service. We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins..",
             "highlights": ""
          }
          ],
          "HotelName": "Swan Bird Lake Inn",
          "Description": "We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins.",
          "Tags": [
          "continental breakfast",
          "free wifi",
          "24-hour front desk service"
          ]
       },
       {
          "@search.score": 0.92481667,
          "@search.rerankerScore": 2.221315860748291,
          "@search.captions": [
          {
             "text": "White Mountain Lodge & Suites. Resort and Spa. continental breakfast\r\npool\r\nrestaurant. Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings..",
             "highlights": ""
          }
          ],
          "HotelName": "White Mountain Lodge & Suites",
          "Description": "Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings.",
          "Tags": [
          "continental breakfast",
          "pool",
          "restaurant"
          ]
       },
       . . .
    ]}
    

Get service endpoints

In the remaining sections, you set up API calls to Azure OpenAI and Azure AI Search. Get the service endpoints so that you can provide them as variables in your code.

  1. Sign in to the Azure portal.

  2. Find your search service.

  3. On the Overview home page, copy the URL. An example endpoint might look like https://example.search.windows.net.

  4. Find your Azure OpenAI service.

  5. On the Overview home page, select the link to view the endpoints. Copy the URL. An example endpoint might look like https://example.openai.azure.com/.

Create a virtual environment

In this step, switch back to your local system and Visual Studio Code. We recommend that you create a virtual environment so that you can install the dependencies in isolation.

  1. In Visual Studio Code, open the folder containing Quickstart-RAG.ipynb.

  2. Press Ctrl-shift-P to open the command palette, search for "Python: Create Environment", and then select Venv to create a virtual environment in the current workspace.

  3. Select Quickstart-RAG\requirements.txt for the dependencies.

It takes several minutes to create the environment. When the environment is ready, continue to the next step.

Sign in to Azure

You're using Microsoft Entra ID and role assignments for the connection. Make sure you're logged in to the same tenant and subscription as Azure AI Search and Azure OpenAI. You can use the Azure CLI on the command line to show current properties, change properties, and to sign in. For more information, see Connect without keys.

Run each of the following commands in sequence.

az account show

az account set --subscription <PUT YOUR SUBSCRIPTION ID HERE>

az login --tenant <PUT YOUR TENANT ID HERE>

You should now be logged in to Azure from your local device.

Set up the query and chat thread

This section uses Visual Studio Code and Python to call the chat completion APIs on Azure OpenAI.

  1. Start Visual Studio Code and open the .ipynb file or create a new Python file.

  2. Install the following Python packages.

    ! pip install azure-search-documents==11.6.0b5 --quiet
    ! pip install azure-identity==1.16.1 --quiet
    ! pip install openai --quiet
    ! pip install aiohttp --quiet
    ! pip install ipykernel --quiet
    
  3. Set the following variables, substituting placeholders with the endpoints you collected in the previous step.

     AZURE_SEARCH_SERVICE: str = "PUT YOUR SEARCH SERVICE ENDPOINT HERE"
     AZURE_OPENAI_ACCOUNT: str = "PUT YOUR AZURE OPENAI ENDPOINT HERE"
     AZURE_DEPLOYMENT_MODEL: str = "gpt-4o"
    
  4. Set up clients, the prompt, query, and response.

    For the Azure Government cloud, modify the API endpoint on the token provider to "https://cognitiveservices.azure.us/.default".

    # Set up the query for generating responses
     from azure.identity import DefaultAzureCredential
     from azure.identity import get_bearer_token_provider
     from azure.search.documents import SearchClient
     from openai import AzureOpenAI
    
     credential = DefaultAzureCredential()
     token_provider = get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default")
     openai_client = AzureOpenAI(
         api_version="2024-06-01",
         azure_endpoint=AZURE_OPENAI_ACCOUNT,
         azure_ad_token_provider=token_provider
     )
    
     search_client = SearchClient(
         endpoint=AZURE_SEARCH_SERVICE,
         index_name="hotels-sample-index",
         credential=credential
     )
    
     # This prompt provides instructions to the model
     GROUNDED_PROMPT="""
     You are a friendly assistant that recommends hotels based on activities and amenities.
     Answer the query using only the sources provided below in a friendly and concise bulleted manner.
     Answer ONLY with the facts listed in the list of sources below.
     If there isn't enough information below, say you don't know.
     Do not generate answers that don't use the sources below.
     Query: {query}
     Sources:\n{sources}
     """
    
     # Query is the question being asked. It's sent to the search engine and the chat model
     query="Can you recommend a few hotels with complimentary breakfast?"
    
     # Search results are created by the search client
     # Search results are composed of the top 5 results and the fields selected from the search index
     # Search results include the top 5 matches to your query
     search_results = search_client.search(
         search_text=query,
         top=5,
         select="Description,HotelName,Tags"
     )
     sources_formatted = "\n".join([f'{document["HotelName"]}:{document["Description"]}:{document["Tags"]}' for document in search_results])
    
     # Send the search results and the query to the LLM to generate a response based on the prompt.
     response = openai_client.chat.completions.create(
         messages=[
             {
                 "role": "user",
                 "content": GROUNDED_PROMPT.format(query=query, sources=sources_formatted)
             }
         ],
         model=AZURE_DEPLOYMENT_MODEL
     )
    
     # Here is the response from the chat model.
     print(response.choices[0].message.content)
    

    Output is from Azure OpenAI, and it consists of recommendations for several hotels. Here's an example of what the output might look like:

    Sure! Here are a few hotels that offer complimentary breakfast:
    
    - **Head Wind Resort**
    - Complimentary continental breakfast in the lobby
    - Free Wi-Fi throughout the hotel
    
    - **Double Sanctuary Resort**
    - Continental breakfast included
    
    - **White Mountain Lodge & Suites**
    - Continental breakfast available
    
    - **Swan Bird Lake Inn**
    - Continental-style breakfast each morning with a variety of food and drinks 
     such as caramel cinnamon rolls, coffee, orange juice, milk, cereal, 
     instant oatmeal, bagels, and muffins
    

    If you get a Forbidden error message, check Azure AI Search configuration to make sure role-based access is enabled.

    If you get an Authorization failed error message, wait a few minutes and try again. It can take several minutes for role assignments to become operational.

    If you get a Resource not found error message, check the resource URIs and make sure the API version on the chat model is valid.

    Otherwise, to experiment further, change the query and rerun the last step to better understand how the model works with the grounding data.

    You can also modify the prompt to change the tone or structure of the output.

    You might also try the query without semantic ranking by setting use_semantic_reranker=False in the query parameters step. Semantic ranking can noticably improve the relevance of query results and the ability of the LLM to return useful information. Experimentation can help you decide whether it makes a difference for your content.

Send a complex RAG query

Azure AI Search supports complex types for nested JSON structures. In the hotels-sample-index, Address is an example of a complex type, consisting of Address.StreetAddress, Address.City, Address.StateProvince, Address.PostalCode, and Address.Country. The index also has complex collection of Rooms for each hotel.

If your index has complex types, your query can provide those fields if you first convert the search results output to JSON, and then pass the JSON to the chat model. The following example adds complex types to the request. The formatting instructions include a JSON specification.

import json

# Query is the question being asked. It's sent to the search engine and the LLM.
query="Can you recommend a few hotels that offer complimentary breakfast? 
Tell me their description, address, tags, and the rate for one room that sleeps 4 people."

# Set up the search results and the chat thread.
# Retrieve the selected fields from the search index related to the question.
selected_fields = ["HotelName","Description","Address","Rooms","Tags"]
search_results = search_client.search(
    search_text=query,
    top=5,
    select=selected_fields,
    query_type="semantic"
)
sources_filtered = [{field: result[field] for field in selected_fields} for result in search_results]
sources_formatted = "\n".join([json.dumps(source) for source in sources_filtered])

response = openai_client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": GROUNDED_PROMPT.format(query=query, sources=sources_formatted)
        }
    ],
    model=AZURE_DEPLOYMENT_MODEL
)

print(response.choices[0].message.content)

Output is from Azure OpenAI, and it adds content from complex types.

Here are a few hotels that offer complimentary breakfast and have rooms that sleep 4 people:

1. **Head Wind Resort**
   - **Description:** The best of old town hospitality combined with views of the river and 
   cool breezes off the prairie. Enjoy a complimentary continental breakfast in the lobby, 
   and free Wi-Fi throughout the hotel.
   - **Address:** 7633 E 63rd Pl, Tulsa, OK 74133, USA
   - **Tags:** Coffee in lobby, free Wi-Fi, view
   - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99

2. **Double Sanctuary Resort**
   - **Description:** 5-star Luxury Hotel - Biggest Rooms in the city. #1 Hotel in the area 
   listed by Traveler magazine. Free WiFi, Flexible check in/out, Fitness Center & espresso 
   in room. Offers continental breakfast.
   - **Address:** 2211 Elliott Ave, Seattle, WA 98121, USA
   - **Tags:** View, pool, restaurant, bar, continental breakfast
   - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99

3. **Swan Bird Lake Inn**
   - **Description:** Continental-style breakfast featuring a variety of food and drinks. 
   Locally made caramel cinnamon rolls are a favorite.
   - **Address:** 1 Memorial Dr, Cambridge, MA 02142, USA
   - **Tags:** Continental breakfast, free Wi-Fi, 24-hour front desk service
   - **Room for 4:** Budget Room, 2 Queen Beds (City View) - $85.99

4. **Gastronomic Landscape Hotel**
   - **Description:** Known for its culinary excellence under the management of William Dough, 
   offers continental breakfast.
   - **Address:** 3393 Peachtree Rd, Atlanta, GA 30326, USA
   - **Tags:** Restaurant, bar, continental breakfast
   - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $66.99
...
   - **Tags:** Pool, continental breakfast, free parking
   - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $60.99

Enjoy your stay! Let me know if you need any more information.

Troubleshooting errors

To debug authentication errors, insert the following code before the step that calls the search engine and the LLM.

import sys
import logging # Set the logging level for all azure-storage-* libraries
logger = logging.getLogger('azure.identity') 
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('[%(levelname)s %(name)s] %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

Rerun the query script. You should now get INFO and DEBUG statements in the output that provide more detail about the issue.

If you see output messages related to ManagedIdentityCredential and token acquisition failures, it could be that you have multiple tenants, and your Azure sign-in is using a tenant that doesn't have your search service. To get your tenant ID, search the Azure portal for "tenant properties" or run az login tenant list.

Once you have your tenant ID, run az login --tenant <YOUR-TENANT-ID> at a command prompt, and then rerun the script.

Clean up

When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.

You can find and manage resources in the Azure portal by using the All resources or Resource groups link in the leftmost pane.

Prerequisites

Download file

Download a .rest file from GitHub to send the requests in this quickstart. For more information, see Downloading files from GitHub.

You can also start a new file on your local system and create requests manually by using the instructions in this article.

Configure access

Requests to the search endpoint must be authenticated and authorized. You can use API keys or roles for this task. Keys are easier to start with, but roles are more secure. This quickstart assumes roles.

You're setting up two clients, so you need permissions on both resources.

Azure AI Search is receiving the query request from your local system. Assign yourself the Search Index Data Reader role assignment if the hotels sample index already exists. If it doesn't exist, assign yourself Search Service Contributor and Search Index Data Contributor roles so that you can create and query the index.

Azure OpenAI is receiving the query and the search results from your local system. Assign yourself the Cognitive Services OpenAI User role on Azure OpenAI.

  1. Sign in to the Azure portal.

  2. Configure Azure AI Search for role-based access:

    1. In the Azure portal, find your Azure AI Search service.

    2. On the left menu, select Settings > Keys, and then select either Role-based access control or Both.

  3. Assign roles:

    1. On the left menu, select Access control (IAM).

    2. On Azure AI Search, select these roles to create, load, and query a search index, and then assign them to your Microsoft Entra ID user identity:

      • Search Index Data Contributor
      • Search Service Contributor
    3. On Azure OpenAI, select Access control (IAM) to assign this role to yourself on Azure OpenAI:

      • Cognitive Services OpenAI User

It can take several minutes for permissions to take effect.

Get service endpoints and tokens

In the remaining sections, you set up API calls to Azure OpenAI and Azure AI Search. Get the service endpoints and tokens so that you can provide them as variables in your code.

  1. Sign in to the Azure portal.

  2. Find your search service.

  3. On the Overview home page, copy the URL. An example endpoint might look like https://example.search.windows.net.

  4. Find your Azure OpenAI service.

  5. On the Overview home page, select the link to view the endpoints. Copy the URL. An example endpoint might look like https://example.openai.azure.com/.

  6. Get personal access tokens from the Azure CLI on a command prompt. Here are the commands for each resource:

    • az account get-access-token --resource https://search.azure.com --query "accessToken" -o tsv
    • az account get-access-token --resource https://cognitiveservices.azure.com --query "accessToken" -o tsv

Set up the client

In this quickstart, you use a REST client and the Azure AI Search REST APIs to implement the RAG pattern.

We recommend Visual Studio Code with a REST client extension for this quickstart.

Tip

You can download the source code to start with a finished project or follow these steps to create your own.

  1. Start Visual Studio Code and open the quickstart-rag.rest file or create a new file.

  2. At the top, set environment variables for your search service, authorization, and index name.

    • For @searchUrl, paste in the search endpoint.
    • For @aoaiUrl, paste in the Azure OpenAI endpoint.
    • For @searchAccessToken, paste in the access token scoped to https://search.azure.com.
    • For @aoaiAccessToken, paste in the access token scoped to https://cognitiveservices.azure.com.
  3. To test the connection, send your first request.

    ### List existing indexes by name (verify the connection)
     GET  {{searchUrl}}/indexes?api-version=2025-05-01-preview&$select=name  HTTP/1.1
     Authorization: Bearer {{personalAccessToken}}
    
  4. Select Sent request.

    Screenshot of the REST client send request link.

  5. Output for this GET request should be a list of indexes. You should see the hotels-sample-index among them.

Set up the query and chat thread

This section uses Visual Studio Code and REST to call the chat completion APIs on Azure OpenAI.

  1. Set up a query request on the phrase "Can you recommend a few hotels with complimentary breakfast?". This query uses semantic ranking to return relevant matches, even if the verbatim text isn't an exact match. Results are held in the searchRequest variable for reuse on the next request.

    # @name searchRequest
     POST {{searchUrl}}/indexes/{{index-name}}/docs/search?api-version={{api-version}} HTTP/1.1
     Content-Type: application/json
     Authorization: Bearer {{searchAccessToken}}
    
     {
       "search": "Can you recommend a few hotels with complimentary breakfast?",
       "queryType": "semantic",
       "semanticConfiguration": "semantic-config",
       "select": "Description,HotelName,Tags",
       "top": 5
     }
    
     ### 3 - Use search results in Azure OpenAI call to a chat completion model
     POST {{aoaiUrl}}/openai/deployments/{{aoaiGptDeployment}}/chat/completions?api-version=2024-08-01-preview HTTP/1.1
     Content-Type: application/json
     Authorization: Bearer {{aoaiAccessToken}}
    
     {
       "messages": [
         {
           "role": "system", 
           "content": "You recommend hotels based on activities and amenities. Answer the query using only the search result. Answer in a friendly and concise manner. Answer ONLY with the facts provided. If there isn't enough information below, say you don't know."
         },
         {
           "role": "user",
           "content": "Based on the hotel search results, can you recommend hotels with breakfast? Here are all the hotels I found:\n\nHotel 1: {{searchRequest.response.body.value[0].HotelName}}\nDescription: {{searchRequest.response.body.value[0].Description}}\n\nHotel 2: {{searchRequest.response.body.value[1].HotelName}}\nDescription: {{searchRequest.response.body.value[1].Description}}\n\nHotel 3: {{searchRequest.response.body.value[2].HotelName}}\nDescription: {{searchRequest.response.body.value[2].Description}}\n\nHotel 4: {{searchRequest.response.body.value[3].HotelName}}\nDescription: {{searchRequest.response.body.value[3].Description}}\n\nHotel 5: {{searchRequest.response.body.value[4].HotelName}}\nDescription: {{searchRequest.response.body.value[4].Description}}\n\nPlease recommend which hotels offer breakfast based on their descriptions."
         }
       ],
       "max_tokens": 1000,
       "temperature": 0.7
     }`
    
  2. Send the request.

  3. Output should look similar to the following example:

       "value": [
         {
           "@search.score": 3.9269178,
           "@search.rerankerScore": 2.380699872970581,
           "HotelName": "Head Wind Resort",
           "Description": "The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a complimentary continental breakfast in the lobby, and free Wi-Fi throughout the hotel.",
           "Tags": [
             "coffee in lobby",
             "free wifi",
             "view"
           ]
         },
         {
           "@search.score": 1.5450059,
           "@search.rerankerScore": 2.1258809566497803,
           "HotelName": "Thunderbird Motel",
           "Description": "Book Now & Save. Clean, Comfortable rooms at the lowest price. Enjoy complimentary coffee and tea in common areas.",
           "Tags": [
             "coffee in lobby",
             "free parking",
             "free wifi"
           ]
         },
         {
           "@search.score": 2.2158256,
           "@search.rerankerScore": 2.121671438217163,
           "HotelName": "Swan Bird Lake Inn",
           "Description": "We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins.",
           "Tags": [
             "continental breakfast",
             "free wifi",
             "24-hour front desk service"
           ]
         },
         {
           "@search.score": 0.6395861,
           "@search.rerankerScore": 2.116753339767456,
           "HotelName": "Waterfront Scottish Inn",
           "Description": "Newly Redesigned Rooms & airport shuttle. Minutes from the airport, enjoy lakeside amenities, a resort-style pool & stylish new guestrooms with Internet TVs.",
           "Tags": [
             "24-hour front desk service",
             "continental breakfast",
             "free wifi"
           ]
         },
         {
           "@search.score": 4.885111,
           "@search.rerankerScore": 2.0008862018585205,
           "HotelName": "Double Sanctuary Resort",
           "Description": "5 star Luxury Hotel - Biggest Rooms in the city. #1 Hotel in the area listed by Traveler magazine. Free WiFi, Flexible check in/out, Fitness Center & espresso in room.",
           "Tags": [
             "view",
             "pool",
             "restaurant",
             "bar",
             "continental breakfast"
           ]
         }
       ]
    
  4. Set up a conversation turn with a chat completion model. This request includes a prompt that provides instructions for the response. The max_tokens value is large enough to accommodate the search results from the previous query.

     POST {{aoaiUrl}}/openai/deployments/{{aoaiGptDeployment}}/chat/completions?api-version=2024-08-01-preview HTTP/1.1
     Content-Type: application/json
     Authorization: Bearer {{aoaiAccessToken}}
    
     {
     "messages": [
     {
       "role": "system", 
       "content": "You  are a friendly assistant that recommends hotels based on activities and amenities. Answer the query using only the search result. Answer in a friendly and concise manner. Answer ONLY with the facts provided. If there isn't enough information below, say you don't know."
         },
     {
       "role": "user",
       "content": "Based on the hotel search results, can you recommend hotels with breakfast? Here are all the hotels I found:\n\nHotel 1: {{searchRequest.response.body.value[0].HotelName}}\nDescription: {{searchRequest.response.body.value[0].Description}}\n\nHotel 2: {{searchRequest.response.body.value[1].HotelName}}\nDescription: {{searchRequest.response.body.value[1].Description}}\n\nHotel 3: {{searchRequest.response.body.value[2].HotelName}}\nDescription: {{searchRequest.response.body.value[2].Description}}\n\nHotel 4: {{searchRequest.response.body.value[3].HotelName}}\nDescription: {{searchRequest.response.body.value[3].Description}}\n\nHotel 5: {{searchRequest.response.body.value[4].HotelName}}\nDescription: {{searchRequest.response.body.value[4].Description}}\n\nPlease recommend which hotels offer breakfast based on their descriptions."
     }
     ],
     "max_tokens": 1000,
     "temperature": 0.7
     }
    
  5. Send the request.

  6. Output should be an HTTP 200 Success status message. Included in the output is content that answers the question:

     "message": {
       "annotations": [],
       "content": "I recommend the following hotels that offer breakfast:\n\n1. **Head Wind Resort** - Offers a complimentary continental breakfast in the lobby.\n2. **Swan Bird Lake Inn** - Serves a continental-style breakfast each morning, including a variety of food and drinks. \n\nEnjoy your stay!",
       "refusal": null,
       "role": "assistant"
     }
    

Notice that the output is missing several hotels that mention breakfast in the Tags field. The Tags field is an array, and including this field breaks the JSON structure in the results. Because there are no string conversion capabilities in the REST client, extra code for manually converting the JSON to a string is required if arrays are to be included. We omit this step for this quickstart.

Clean up

When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.

You can find and manage resources in the Azure portal by using the All resources or Resource groups link in the leftmost pane.

Prerequisites

Configure access

Requests to the search endpoint must be authenticated and authorized. You can use API keys or roles for this task. Keys are easier to start with, but roles are more secure. This quickstart assumes roles.

You're setting up two clients, so you need permissions on both resources.

Azure AI Search is receiving the query request from your local system. Assign yourself the Search Index Data Reader role assignment if the hotels sample index already exists. If it doesn't exist, assign yourself Search Service Contributor and Search Index Data Contributor roles so that you can create and query the index.

Azure OpenAI is receiving the query and the search results from your local system. Assign yourself the Cognitive Services OpenAI User role on Azure OpenAI.

  1. Sign in to the Azure portal.

  2. Configure Azure AI Search for role-based access:

    1. In the Azure portal, find your Azure AI Search service.

    2. On the left menu, select Settings > Keys, and then select either Role-based access control or Both.

  3. Assign roles:

    1. On the left menu, select Access control (IAM).

    2. On Azure AI Search, select these roles to create, load, and query a search index, and then assign them to your Microsoft Entra ID user identity:

      • Search Index Data Contributor
      • Search Service Contributor
    3. On Azure OpenAI, select Access control (IAM) to assign this role to yourself on Azure OpenAI:

      • Cognitive Services OpenAI User

It can take several minutes for permissions to take effect.

Create an index

A search index provides grounding data for the chat model. We recommend the hotels-sample-index, which can be created in minutes and runs on any search service tier. This index is created using built-in sample data.

  1. In the Azure portal, find your search service.

  2. On the Overview home page, select Import data to start the wizard.

  3. On the Connect to your data page, select Samples from the dropdown list.

  4. Choose the hotels-sample.

  5. Select Next through the remaining pages, accepting the default values.

  6. Once the index is created, select Search management > Indexes from the left menu to open the index.

  7. Select Edit JSON.

  8. Scroll to the end of the index, where you can find placeholders for constructs that can be added to an index.

    "analyzers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": [],
    "normalizers": [],
    
  9. On a new line after "normalizers", paste in the following semantic configuration. This example specifies a "defaultConfiguration", which is important to the running of this quickstart.

    "semantic":{
       "defaultConfiguration":"semantic-config",
       "configurations":[
          {
             "name":"semantic-config",
             "prioritizedFields":{
                "titleField":{
                   "fieldName":"HotelName"
                },
                "prioritizedContentFields":[
                   {
                      "fieldName":"Description"
                   }
                ],
                "prioritizedKeywordsFields":[
                   {
                      "fieldName":"Category"
                   },
                   {
                      "fieldName":"Tags"
                   }
                ]
             }
          }
       ]
    },
    
  10. Save your changes.

  11. Run the following query in Search Explorer to test your index: complimentary breakfast.

    Output should look similar to the following example. Results that are returned directly from the search engine consist of fields and their verbatim values, along with metadata like a search score and a semantic ranking score and caption if you use semantic ranker. We used a select statement to return just the HotelName, Description, and Tags fields.

    {
    "@odata.count": 18,
    "@search.answers": [],
    "value": [
       {
          "@search.score": 2.2896252,
          "@search.rerankerScore": 2.506816864013672,
          "@search.captions": [
          {
             "text": "Head Wind Resort. Suite. coffee in lobby\r\nfree wifi\r\nview. The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a **complimentary continental breakfast** in the lobby, and free Wi-Fi throughout the hotel..",
             "highlights": ""
          }
          ],
          "HotelName": "Head Wind Resort",
          "Description": "The best of old town hospitality combined with views of the river and cool breezes off the prairie. Our penthouse suites offer views for miles and the rooftop plaza is open to all guests from sunset to 10 p.m. Enjoy a complimentary continental breakfast in the lobby, and free Wi-Fi throughout the hotel.",
          "Tags": [
          "coffee in lobby",
          "free wifi",
          "view"
          ]
       },
       {
          "@search.score": 2.2158256,
          "@search.rerankerScore": 2.288334846496582,
          "@search.captions": [
          {
             "text": "Swan Bird Lake Inn. Budget. continental breakfast\r\nfree wifi\r\n24-hour front desk service. We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins..",
             "highlights": ""
          }
          ],
          "HotelName": "Swan Bird Lake Inn",
          "Description": "We serve a continental-style breakfast each morning, featuring a variety of food and drinks. Our locally made, oh-so-soft, caramel cinnamon rolls are a favorite with our guests. Other breakfast items include coffee, orange juice, milk, cereal, instant oatmeal, bagels, and muffins.",
          "Tags": [
          "continental breakfast",
          "free wifi",
          "24-hour front desk service"
          ]
       },
       {
          "@search.score": 0.92481667,
          "@search.rerankerScore": 2.221315860748291,
          "@search.captions": [
          {
             "text": "White Mountain Lodge & Suites. Resort and Spa. continental breakfast\r\npool\r\nrestaurant. Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings..",
             "highlights": ""
          }
          ],
          "HotelName": "White Mountain Lodge & Suites",
          "Description": "Live amongst the trees in the heart of the forest. Hike along our extensive trail system. Visit the Natural Hot Springs, or enjoy our signature hot stone massage in the Cathedral of Firs. Relax in the meditation gardens, or join new friends around the communal firepit. Weekend evening entertainment on the patio features special guest musicians or poetry readings.",
          "Tags": [
          "continental breakfast",
          "pool",
          "restaurant"
          ]
       },
       . . .
    ]}
    

Get service endpoints

In the remaining sections, you set up API calls to Azure OpenAI and Azure AI Search. Get the service endpoints so that you can provide them as variables in your code.

  1. Sign in to the Azure portal.

  2. Find your search service.

  3. On the Overview home page, copy the URL. An example endpoint might look like https://example.search.windows.net.

  4. Find your Azure OpenAI service.

  5. On the Overview home page, select the link to view the endpoints. Copy the URL. An example endpoint might look like https://example.openai.azure.com/.

Set up environment variables for local development

  1. Create a .env file.

  2. Add the following environment variables to the .env file, replacing the values with your own service endpoints and keys.

    AZURE_SEARCH_ENDPOINT=<YOUR AZURE AI SEARCH ENDPOINT>
    AZURE_SEARCH_INDEX_NAME=hotels-sample-index
    
    AZURE_OPENAI_ENDPOINT=<YOUR AZURE OPENAI ENDPOINT>
    AZURE_OPENAI_VERSION=<YOUR AZURE OPENAI API VERSION>
    AZURE_DEPLOYMENT_MODEL=<YOUR DEPLOYMENT NAME>
    

Set up the Node.JS project

Set up project with Visual Studio Code and TypeScript.

  1. Start Visual Studio Code in a new directory.

    mkdir rag-quickstart && cd rag-quickstart
    code .
    
  2. Create a new package for ESM modules in your project directory.

    npm init -y
    npm pkg set type=module
    

    This creates a package.json file with default values.

  3. Install the following npm packages.

    npm install @azure/identity @azure/search-documents openai dotenv @types/node
    
  4. Create a src directory in your project directory.

    mkdir src
    
  5. Create a tsconfig.json file in the project directory for ESM with the following content.

    {
      "compilerOptions": {
        "target": "esnext",
        "module": "NodeNext",
        "moduleResolution": "nodenext",
        "rootDir": "./src",
        "outDir": "./dist/",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true,
        "declaration": true,
        "sourceMap": true,
        "resolveJsonModule": true,
        "moduleDetection": "force", // Add this for ESM
        "allowSyntheticDefaultImports": true // Helpful for ESM interop
      },
      "include": [
        "src/**/*.ts"
      ]
    }
    

Sign in to Azure

You're using Microsoft Entra ID and role assignments for the connection. Make sure you're logged in to the same tenant and subscription as Azure AI Search and Azure OpenAI. You can use the Azure CLI on the command line to show current properties, change properties, and to sign in. For more information, see Connect without keys.

Run each of the following commands in sequence.

az account show

az account set --subscription <PUT YOUR SUBSCRIPTION ID HERE>

az login --tenant <PUT YOUR TENANT ID HERE>

You should now be logged in to Azure from your local device.

Set up query and chat thread

Create a query script that uses the Azure AI Search index and the chat model to generate responses based on grounding data. The following steps guide you through setting up the query script.

  1. Create a query.ts file in the src directory with the following code.

    // This is a RAG (Retrieval Augmented Generation) implementation that:
    // 1. Takes a user query about hotels
    // 2. Searches a hotel database using Azure AI Search
    // 3. Formats the search results for the LLM
    // 4. Sends the query and formatted results to Azure OpenAI
    // 5. Returns a grounded response based only on the retrieved information
    
    import { SearchClient, AzureKeyCredential, SearchDocumentsResult } from "@azure/search-documents";
    import { AzureOpenAI } from "openai";
    import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
    
    function getClients(): { openaiClient: AzureOpenAI, searchClient: SearchClient<{ HotelName: string; Description: string; Tags: string[] | string }>, modelName: string }  {
    
        const credential = new DefaultAzureCredential();
    
        // Search
        const azureSearchEndpoint = process.env.AZURE_SEARCH_ENDPOINT!;
        const azureSearchIndexName = process.env.AZURE_SEARCH_INDEX_NAME!;
    
        const searchClient = new SearchClient<{ HotelName: string; Description: string; Tags: string[] | string }>(
            azureSearchEndpoint,
            azureSearchIndexName,
            credential
        );
    
    
        // OpenAI
        const azureOpenAiEndpoint = process.env.AZURE_OPENAI_ENDPOINT!;
        const azureOpenAiApiVersion = process.env.AZURE_OPENAI_VERSION!;
        const azureOpenAiDeploymentName = process.env.AZURE_DEPLOYMENT_MODEL!;
    
        const scope = "https://cognitiveservices.azure.com/.default";
        const azureADTokenProvider = getBearerTokenProvider(credential, scope);
        const options = { azureADTokenProvider, deployment: azureOpenAiDeploymentName, apiVersion: azureOpenAiApiVersion, endpoint: azureOpenAiEndpoint }
        const openaiClient = new AzureOpenAI(options);
    
        return { openaiClient, searchClient, modelName: azureOpenAiDeploymentName };
    }
    
    async function queryAISearchForSources(searchClient: SearchClient<{ HotelName: string; Description: string; Tags: string[] | string }>, query: string): Promise<string> {
        console.log(`Searching for: "${query}"\n`);
        const searchResults: SearchDocumentsResult<{ HotelName: string; Description: string; Tags: string[] | string }> = await searchClient.search(query, {
            top: 5,
            select: ["Description", "HotelName", "Tags"]
        });
    
        const sources: string[] = [];
        for await (const result of searchResults.results) {
            const doc = result.document;
            sources.push(
                `Hotel: ${doc.HotelName}\n` +
                `Description: ${doc.Description}\n` +
                `Tags: ${Array.isArray(doc.Tags) ? doc.Tags.join(', ') : doc.Tags}\n`
            );
        }
        const sourcesFormatted = sources.join("\n---\n");
        return sourcesFormatted;
    }
    async function queryOpenAIForResponse(
        openaiClient: AzureOpenAI, 
        query: string, 
        sourcesFormatted: string, 
        modelName: string
    ): Promise<{ choices: { message: { content: string | null } }[] }> {
    
        const GROUNDED_PROMPT = `
     You are a friendly assistant that recommends hotels based on activities and amenities.
     Answer the query using only the sources provided below in a friendly and concise bulleted manner.
     Answer ONLY with the facts listed in the list of sources below.
     If there isn't enough information below, say you don't know.
     Do not generate answers that don't use the sources below.
    
    Query: {query}
    Sources: {sources}
    `;
    
        return openaiClient.chat.completions.create({
            model: modelName,
            messages: [
                {
                    role: "user",
                    content: GROUNDED_PROMPT.replace("{query}", query).replace("{sources}", sourcesFormatted),
                }
            ],
            temperature: 0.7,
            max_tokens: 800,
        });
    }
    
    async function main():Promise<void> {
    
        const { openaiClient, searchClient, modelName } = getClients();
    
        const query = "Can you recommend a few hotels with complimentary breakfast?";
    
        const sources = await queryAISearchForSources(searchClient, query);
        const response = await queryOpenAIForResponse(openaiClient, query, sources, modelName);
    
        // Print the response from the chat model
        const content = response.choices[0].message.content;
        if (content) {
            console.log(content);
        } else {
            console.log("No content available in the response.");
        }
    }
    
    main().catch((error) => {
        console.error("An error occurred:", error);
        process.exit(1);
    });
    

    The preceding code does the following:

    • Imports the necessary libraries for Azure AI Search and Azure OpenAI.
    • Uses environment variables to configure the Azure AI Search and Azure OpenAI clients.
    • Defines a function to get the clients for Azure AI Search and Azure OpenAI, using environment variables for configuration.
    • Defines a function to query Azure AI Search for sources based on the user query.
    • Defines a function to query Azure OpenAI for a response based on the user query and the sources retrieved from Azure AI Search.
    • The main function orchestrates the flow by calling the search and OpenAI functions, and then prints the response.
  2. Build the TypeScript code to JavaScript.

    tsc
    

    This command compiles the TypeScript code in the src directory and outputs the JavaScript files in the dist directory.

  3. Run the following command in a terminal to execute the query script:

    node -r dotenv/config dist/query.js
    

    The .env is passed into the runtime using the -r dotenv/config.

  4. View the output consists of recommendations for several hotels. Here's an example of what the output might look like:

    Sure! Here are a few hotels that offer complimentary breakfast:
    
    - **Head Wind Resort**
    - Complimentary continental breakfast in the lobby
    - Free Wi-Fi throughout the hotel
    
    - **Double Sanctuary Resort**
    - Continental breakfast included
    
    - **White Mountain Lodge & Suites**
    - Continental breakfast available
    
    - **Swan Bird Lake Inn**
    - Continental-style breakfast each morning with a variety of food and drinks 
        such as caramel cinnamon rolls, coffee, orange juice, milk, cereal, 
        instant oatmeal, bagels, and muffins
    

Troubleshooting

If you get a Forbidden error message, check Azure AI Search configuration to make sure role-based access is enabled.

If you get an Authorization failed error message, wait a few minutes and try again. It can take several minutes for role assignments to become operational.

If you get a Resource not found error message, check the resource URIs and make sure the API version on the chat model is valid.

Otherwise, to experiment further, change the query and rerun the last step to better understand how the model works with the grounding data.

You can also modify the prompt to change the tone or structure of the output.

You might also try the query without semantic ranking by setting use_semantic_reranker=False in the query parameters step. Semantic ranking can noticably improve the relevance of query results and the ability of the LLM to return useful information. Experimentation can help you decide whether it makes a difference for your content.

Send a complex RAG query

Azure AI Search supports complex types for nested JSON structures. In the hotels-sample-index, Address is an example of a complex type, consisting of Address.StreetAddress, Address.City, Address.StateProvince, Address.PostalCode, and Address.Country. The index also has complex collection of Rooms for each hotel.

If your index has complex types, change your prompt to include formatting instructions:

Can you recommend a few hotels that offer complimentary breakfast? 
Tell me their description, address, tags, and the rate for one room that sleeps 4 people.
  1. Create a new file queryComplex.ts in the src directory.

  2. Copy the following code to the file:

    // This is a RAG (Retrieval Augmented Generation) implementation that:
    // 1. Takes a user query about hotels
    // 2. Searches a hotel database using Azure AI Search
    // 3. Formats the search results for the LLM
    // 4. Sends the query and formatted results to Azure OpenAI
    // 5. Returns a grounded response based only on the retrieved information
    
    import { SearchClient, SearchDocumentsResult } from "@azure/search-documents";
    import { AzureOpenAI } from "openai";
    import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity";
    
    function getClients(): { openaiClient: AzureOpenAI; searchClient: SearchClient<{ HotelName: string; Description: string; Tags: string[] | string; Address: string; Rooms: string }>; modelName: string }  {
    
        const credential = new DefaultAzureCredential();
    
        // Search
        const azureSearchEndpoint = process.env.AZURE_SEARCH_ENDPOINT!;
        const azureSearchIndexName = process.env.AZURE_SEARCH_INDEX_NAME!;
        const searchClient = new SearchClient<{ HotelName: string; Description: string; Tags: string[] | string; Address: string; Rooms: string }>(
            azureSearchEndpoint,
            azureSearchIndexName,
            credential
        );
    
        // OpenAI
        const azureOpenAiEndpoint = process.env.AZURE_OPENAI_ENDPOINT!;
        const azureOpenAiApiVersion = process.env.AZURE_OPENAI_VERSION!;
        const azureOpenAiDeploymentName = process.env.AZURE_DEPLOYMENT_MODEL!;
    
        const scope = "https://cognitiveservices.azure.com/.default";
        const azureADTokenProvider = getBearerTokenProvider(credential, scope);
        const options = { azureADTokenProvider, deployment: azureOpenAiDeploymentName, apiVersion: azureOpenAiApiVersion, endpoint: azureOpenAiEndpoint }
        const openaiClient = new AzureOpenAI(options);
    
        return { openaiClient, searchClient, modelName: azureOpenAiDeploymentName };
    }
    
    
    async function queryAISearchForSources(
        searchClient: SearchClient<{ HotelName: string; Description: string; Tags: string[] | string; Address: string; Rooms: string }>,
        query: string
    ): Promise<SearchDocumentsResult<{ HotelName: string; Description: string; Tags: string[] | string; Address: string; Rooms: string }>> {
        console.log(`Searching for: "${query}"\n`);
    
        const selectedFields: readonly ["HotelName", "Description", "Address", "Rooms", "Tags"] = ["HotelName", "Description", "Address", "Rooms", "Tags"];
        const searchResults = await searchClient.search(query, {
            top: 5,
            select: selectedFields,
            queryType: "semantic",
            semanticSearchOptions: {},
        });
    
        return searchResults;
    }
    async function queryOpenAIForResponse(
        openaiClient: AzureOpenAI, 
        query: string, 
        sourcesFormatted: string, 
        modelName: string
    ): Promise<{ choices: { message: { content: string | null } }[] }> {
    
        const GROUNDED_PROMPT = `
     You are a friendly assistant that recommends hotels based on activities and amenities.
     Answer the query using only the sources provided below in a friendly and concise bulleted manner.
     Answer ONLY with the facts listed in the list of sources below.
     If there isn't enough information below, say you don't know.
     Do not generate answers that don't use the sources below.
    
    Query: {query}
    Sources: {sources}
    `;
    
        return openaiClient.chat.completions.create({
            model: modelName,
            messages: [
                {
                    role: "user",
                    content: GROUNDED_PROMPT.replace("{query}", query).replace("{sources}", sourcesFormatted),
                }
            ],
            temperature: 0.7,
            max_tokens: 800,
        });
    }
    
    async function main(): Promise<void> {
    
        const { openaiClient, searchClient, modelName } = getClients();
    
        const query = `
        Can you recommend a few hotels that offer complimentary breakfast? 
        Tell me their description, address, tags, and the rate for one room that sleeps 4 people.
        `;
    
        const sourcesResult = await queryAISearchForSources(searchClient, query);
        let sourcesFormatted = "";
    
        for await (const result of sourcesResult.results) {
            // Explicitly typing result to ensure compatibility
            sourcesFormatted += JSON.stringify(result.document) + "\n";
        }
    
        const response = await queryOpenAIForResponse(openaiClient, query, sourcesFormatted.trim(), modelName);
    
        // Print the response from the chat model
        const content = response.choices[0].message.content;
        if (content) {
            console.log(content);
        } else {
            console.log("No content available in the response.");
        }
    }
    
    main().catch((error) => {
        console.error("An error occurred:", error);
        process.exit(1);
    });
    
  3. Build the TypeScript code to JavaScript.

    tsc
    

    This command compiles the TypeScript code in the src directory and outputs the JavaScript files in the dist directory.

  4. Run the following command in a terminal to execute the query script:

    node -r dotenv/config dist/queryComplex.js
    

    The .env is passed into the runtime using the -r dotenv/config.

  5. View the output from Azure OpenAI, and it adds content from complex types.

Here are a few hotels that offer complimentary breakfast and have rooms that sleep 4 people:

1. **Head Wind Resort**
   - **Description:** The best of old town hospitality combined with views of the river and 
   cool breezes off the prairie. Enjoy a complimentary continental breakfast in the lobby, 
   and free Wi-Fi throughout the hotel.
   - **Address:** 7633 E 63rd Pl, Tulsa, OK 74133, USA
   - **Tags:** Coffee in lobby, free Wi-Fi, view
   - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99

2. **Double Sanctuary Resort**
   - **Description:** 5-star Luxury Hotel - Biggest Rooms in the city. #1 Hotel in the area 
   listed by Traveler magazine. Free WiFi, Flexible check in/out, Fitness Center & espresso 
   in room. Offers continental breakfast.
   - **Address:** 2211 Elliott Ave, Seattle, WA 98121, USA
   - **Tags:** View, pool, restaurant, bar, continental breakfast
   - **Room for 4:** Suite, 2 Queen Beds (Amenities) - $254.99

3. **Swan Bird Lake Inn**
   - **Description:** Continental-style breakfast featuring a variety of food and drinks. 
   Locally made caramel cinnamon rolls are a favorite.
   - **Address:** 1 Memorial Dr, Cambridge, MA 02142, USA
   - **Tags:** Continental breakfast, free Wi-Fi, 24-hour front desk service
   - **Room for 4:** Budget Room, 2 Queen Beds (City View) - $85.99

4. **Gastronomic Landscape Hotel**
   - **Description:** Known for its culinary excellence under the management of William Dough, 
   offers continental breakfast.
   - **Address:** 3393 Peachtree Rd, Atlanta, GA 30326, USA
   - **Tags:** Restaurant, bar, continental breakfast
   - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $66.99
...
   - **Tags:** Pool, continental breakfast, free parking
   - **Room for 4:** Budget Room, 2 Queen Beds (Amenities) - $60.99

Enjoy your stay! Let me know if you need any more information.

Troubleshooting errors

To debug Azure SDK errors, set the environment variable AZURE_LOG_LEVEL to one of the following: verbose, info, warning, error. This will enable detailed logging for the Azure SDK, which can help identify issues with authentication, network connectivity, or other problems.

Rerun the query script. You should now get informational statements from the SDKs in the output that provide more detail about any issues.

If you see output messages related to ManagedIdentityCredential and token acquisition failures, it could be that you have multiple tenants, and your Azure sign-in is using a tenant that doesn't have your search service. To get your tenant ID, search the Azure portal for "tenant properties" or run az login tenant list.

Once you have your tenant ID, run az login --tenant <YOUR-TENANT-ID> at a command prompt, and then rerun the script.

Clean up

When you're working in your own subscription, it's a good idea at the end of a project to identify whether you still need the resources you created. Resources left running can cost you money. You can delete resources individually or delete the resource group to delete the entire set of resources.

You can find and manage resources in the Azure portal by using the All resources or Resource groups link in the leftmost pane.