|
| 1 | +--- |
| 2 | +title: Quickstart RAG |
| 3 | +titleSuffix: Azure AI Search |
| 4 | +description: In this quickstart, learn how to use grounding data from Azure AI Search with a chat model on Azure OpenAI. |
| 5 | +author: HeidiSteen |
| 6 | +ms.author: heidist |
| 7 | +ms.service: cognitive-search |
| 8 | +ms.topic: quickstart |
| 9 | +ms.date: 07/22/2024 |
| 10 | +--- |
| 11 | + |
| 12 | +# Quickstart: Generative search (RAG) with grounding data from Azure AI Search |
| 13 | + |
| 14 | +This quickstart shows you how to send queries to a Large Language Model (LLM) for a conversational search experience over your indexed content on Azure AI Search. You use the Azure portal to set up the resources, and then run Python code to call the APIs. |
| 15 | + |
| 16 | +## Prerequisites |
| 17 | + |
| 18 | +- An Azure subscription. [Create one for free](https://azure.microsoft.com/free/). |
| 19 | + |
| 20 | +- [Azure AI Search](search-create-service-portal.md), Basic tier or higher so that you can [enable semantic ranking](semantic-how-to-enable-disable.md). Region must be the same one used for Azure OpenAI. |
| 21 | + |
| 22 | +- [Azure OpenAI](https://aka.ms/oai/access) resource with a deployment of `gpt-35-turbo`, `gpt-4`, or equivalent model, in the same region as Azure AI Search. |
| 23 | + |
| 24 | +- [Visual Studio Code](https://code.visualstudio.com/download) with the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) and the [Jupyter package](https://pypi.org/project/jupyter/). For more information, see [Python in Visual Studio Code](https://code.visualstudio.com/docs/languages/python). |
| 25 | + |
| 26 | +## Download file |
| 27 | + |
| 28 | +[Download a Jupyter notebook](https://github.com/Azure-Samples/azure-search-python-samples/tree/main/Quickstart-RAG) from GitHub to send the requests in this quickstart. For more information, see [Downloading files from GitHub](https://docs.github.com/get-started/start-your-journey/downloading-files-from-github). |
| 29 | + |
| 30 | +You can also start a new file on your local system and create requests manually by using the instructions in this article. |
| 31 | + |
| 32 | +## Configure access |
| 33 | + |
| 34 | +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. |
| 35 | + |
| 36 | +1. Configure Azure OpenAI to use a system-assigned managed identity: |
| 37 | + |
| 38 | + 1. In the Azure portal, find your Azure OpenAI resource. |
| 39 | + |
| 40 | + 1. On the left menu, select **Resource management** > **Identity**. |
| 41 | + |
| 42 | + 1. On the System assigned tab, set status to **On**. |
| 43 | + |
| 44 | +1. Configure Azure AI Search for role-based access and assign roles: |
| 45 | + |
| 46 | + 1. In the Azure portal, find your Azure AI Search service. |
| 47 | + |
| 48 | + 1. On the left menu, select **Settings** > **Keys**, and then select either **Role-based access control** or **Both**. |
| 49 | + |
| 50 | + 1. On the left menu, select **Access control (IAM)**. |
| 51 | + |
| 52 | + 1. Add the following role assignments for the Azure OpenAI managed identity: **Search Index Data Reader**, **Search Service Contributor**. |
| 53 | + |
| 54 | +1. Assign yourself to the **Cognitive Services OpenAI User** role on Azure OpenAI. This is the only role you need for query workloads. |
| 55 | + |
| 56 | +It can take several minutes for permissions to take effect. |
| 57 | + |
| 58 | +## Create an index |
| 59 | + |
| 60 | +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. |
| 61 | + |
| 62 | +1. In the Azure portal, find your search service. |
| 63 | + |
| 64 | +1. On the **Overview** home page, select [**Import data**](search-get-started-portal.md) to start the wizard. |
| 65 | + |
| 66 | +1. On the **Connect to your data** page, select **Samples** from the dropdown list. |
| 67 | + |
| 68 | +1. Choose the **hotels-sample**. |
| 69 | + |
| 70 | +1. Select **Next** through the remaining pages, accepting the default values. |
| 71 | + |
| 72 | +1. Once the index is created, select **Search management** > **Indexes** from the left menu to open the index. |
| 73 | + |
| 74 | +1. Select **Edit JSON**. |
| 75 | + |
| 76 | +1. Search for "semantic" to find the section in the index for a semantic configuration. Replace the "semantic" line with the following semantic configuration. This example specifies a `"defaultConfiguration"`, which is important to the running of this quickstart. |
| 77 | + |
| 78 | + ```json |
| 79 | + "semantic": { |
| 80 | + "defaultConfiguration": "semantic-config", |
| 81 | + "configurations": [ |
| 82 | + { |
| 83 | + "name": "semantic-config", |
| 84 | + "prioritizedFields": { |
| 85 | + "titleField": { |
| 86 | + "fieldName": "HotelName" |
| 87 | + }, |
| 88 | + "prioritizedContentFields": [ |
| 89 | + { |
| 90 | + "fieldName": "Description" |
| 91 | + } |
| 92 | + ], |
| 93 | + "prioritizedKeywordsFields": [ |
| 94 | + { |
| 95 | + "fieldName": "Category" |
| 96 | + }, |
| 97 | + { |
| 98 | + "fieldName": "Tags" |
| 99 | + } |
| 100 | + ] |
| 101 | + } |
| 102 | + } |
| 103 | + ] |
| 104 | + }, |
| 105 | + ``` |
| 106 | + |
| 107 | +1. **Save** your changes. |
| 108 | + |
| 109 | +1. Run the following query to test your index: `hotels near the ocean with beach access and good views`. |
| 110 | + |
| 111 | +## Get service endpoints |
| 112 | + |
| 113 | +1. Sign in to the [Azure portal](https://portal.azure.com). |
| 114 | + |
| 115 | +1. [Find your search service](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Search%2FsearchServices). |
| 116 | + |
| 117 | +1. On the **Overview** home page, copy the URL. An example endpoint might look like `https://example.search.windows.net`. |
| 118 | + |
| 119 | +1. [Find your Azure OpenAI service](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.CognitiveServices%2Faccounts). |
| 120 | + |
| 121 | +1. 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/`. |
| 122 | + |
| 123 | +## Set up the query and chat thread |
| 124 | + |
| 125 | +This section uses Visual Studio Code and Python to call the chat APIs on Azure OpenAI. |
| 126 | + |
| 127 | +1. Install the following Python packages. |
| 128 | + |
| 129 | + ```python |
| 130 | + ! pip install azure-search-documents==11.6.0b4 --quiet |
| 131 | + ! pip install azure-identity==1.16.0 --quiet |
| 132 | + ! pip install openai --quiet |
| 133 | + ``` |
| 134 | + |
| 135 | +1. Set the following variables, substituting placeholders with the endpoints you collected in the previous step. |
| 136 | + |
| 137 | + ```python |
| 138 | + AZURE_SEARCH_SERVICE: str = "PUT YOUR SEARCH SERVICE ENDPOINT HERE" |
| 139 | + AZURE_OPENAI_ACCOUNT: str = "PUT YOUR AZURE OPENAI ENDPOINT HERE" |
| 140 | + AZURE_DEPLOYMENT_MODEL: str = "gpt-35-turbo" |
| 141 | + ``` |
| 142 | + |
| 143 | +1. Specify query parameters. The query is a keyword search using semantic ranking. The search engine returns up to 50 matches, but the model returns just the top 5 in the response. If you can't enable semantic ranking on your search service, set the value to false. |
| 144 | + |
| 145 | + ```python |
| 146 | + # Set query parameters for grounding the conversation on your search index |
| 147 | + k=50 |
| 148 | + search_type="text" |
| 149 | + use_semantic_reranker=True |
| 150 | + sources_to_include=5 |
| 151 | + ``` |
| 152 | + |
| 153 | +1. Set up clients, a search functions prompts, and a chat. The function retrieves selected fields from the search index. |
| 154 | + |
| 155 | + ```python |
| 156 | + # Set up the query for generating responses |
| 157 | + from azure.core.credentials_async import AsyncTokenCredential |
| 158 | + from azure.identity.aio import get_bearer_token_provider |
| 159 | + from azure.search.documents.aio import SearchClient |
| 160 | + from azure.search.documents.models import VectorizableTextQuery, HybridSearch |
| 161 | + from openai import AsyncAzureOpenAI |
| 162 | + from enum import Enum |
| 163 | + from typing import List, Optional |
| 164 | + |
| 165 | + def create_openai_client(credential: AsyncTokenCredential) -> AsyncAzureOpenAI: |
| 166 | + token_provider = get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default") |
| 167 | + return AsyncAzureOpenAI( |
| 168 | + api_version="2024-04-01-preview", |
| 169 | + azure_endpoint=AZURE_OPENAI_ACCOUNT, |
| 170 | + azure_ad_token_provider=token_provider |
| 171 | + ) |
| 172 | + |
| 173 | + def create_search_client(credential: AsyncTokenCredential) -> SearchClient: |
| 174 | + return SearchClient( |
| 175 | + endpoint=AZURE_SEARCH_SERVICE, |
| 176 | + index_name="hotels-sample-index", |
| 177 | + credential=credential |
| 178 | + ) |
| 179 | + |
| 180 | + # This quickstart is only using text at the moment |
| 181 | + class SearchType(Enum): |
| 182 | + TEXT = "text" |
| 183 | + VECTOR = "vector" |
| 184 | + HYBRID = "hybrid" |
| 185 | + |
| 186 | + # This function retrieves the selected fields from the search index |
| 187 | + async def get_sources(search_client: SearchClient, query: str, search_type: SearchType, use_semantic_reranker: bool = True, sources_to_include: int = 5, k: int = 50) -> List[str]: |
| 188 | + search_type == SearchType.TEXT, |
| 189 | + response = await search_client.search( |
| 190 | + search_text=query, |
| 191 | + query_type="semantic" if use_semantic_reranker else "simple", |
| 192 | + top=sources_to_include, |
| 193 | + select="Description,HotelName,Tags" |
| 194 | + ) |
| 195 | + |
| 196 | + return [ document async for document in response ] |
| 197 | + |
| 198 | + # This prompt provides instructions to the model |
| 199 | + GROUNDED_PROMPT=""" |
| 200 | + You are a friendly assistant that recommends hotels based on activities and amenities. |
| 201 | + Answer the query using only the sources provided below in a friendly and concise bulleted manner. |
| 202 | + Answer ONLY with the facts listed in the list of sources below. |
| 203 | + If there isn't enough information below, say you don't know. |
| 204 | + Do not generate answers that don't use the sources below. |
| 205 | + Query: {query} |
| 206 | + Sources:\n{sources} |
| 207 | + """ |
| 208 | + |
| 209 | + # This class instantiates the chat |
| 210 | + class ChatThread: |
| 211 | + def __init__(self): |
| 212 | + self.messages = [] |
| 213 | + self.search_results = [] |
| 214 | + |
| 215 | + def append_message(self, role: str, message: str): |
| 216 | + self.messages.append({ |
| 217 | + "role": role, |
| 218 | + "content": message |
| 219 | + }) |
| 220 | + |
| 221 | + async def append_grounded_message(self, search_client: SearchClient, query: str, search_type: SearchType, use_semantic_reranker: bool = True, sources_to_include: int = 5, k: int = 50): |
| 222 | + sources = await get_sources(search_client, query, search_type, use_semantic_reranker, sources_to_include, k) |
| 223 | + sources_formatted = "\n".join([f'{document["HotelName"]}:{document["Description"]}:{document["Tags"]}' for document in sources]) |
| 224 | + self.append_message(role="user", message=GROUNDED_PROMPT.format(query=query, sources=sources_formatted)) |
| 225 | + self.search_results.append( |
| 226 | + { |
| 227 | + "message_index": len(self.messages) - 1, |
| 228 | + "query": query, |
| 229 | + "sources": sources |
| 230 | + } |
| 231 | + ) |
| 232 | + |
| 233 | + async def get_openai_response(self, openai_client: AsyncAzureOpenAI, model: str): |
| 234 | + response = await openai_client.chat.completions.create( |
| 235 | + messages=self.messages, |
| 236 | + model=model |
| 237 | + ) |
| 238 | + self.append_message(role="assistant", message=response.choices[0].message.content) |
| 239 | + |
| 240 | + def get_last_message(self) -> Optional[object]: |
| 241 | + return self.messages[-1] if len(self.messages) > 0 else None |
| 242 | + |
| 243 | + def get_last_message_sources(self) -> Optional[List[object]]: |
| 244 | + return self.search_results[-1]["sources"] if len(self.search_results) > 0 else None |
| 245 | + ``` |
| 246 | + |
| 247 | +1. Invoke the chat and call the search function, passing in a query string to search for. |
| 248 | + |
| 249 | + ```python |
| 250 | + import azure.identity.aio |
| 251 | + |
| 252 | + chat_thread = ChatThread() |
| 253 | + chat_deployment = AZURE_DEPLOYMENT_MODEL |
| 254 | + |
| 255 | + async with azure.identity.aio.DefaultAzureCredential() as credential, create_search_client(credential) as search_client, create_openai_client(credential) as openai_client: |
| 256 | + await chat_thread.append_grounded_message( |
| 257 | + search_client=search_client, |
| 258 | + query="Can you recommend a few hotels near the ocean with beach access and good views", |
| 259 | + search_type=SearchType(search_type), |
| 260 | + use_semantic_reranker=use_semantic_reranker, |
| 261 | + sources_to_include=sources_to_include, |
| 262 | + k=k) |
| 263 | + await chat_thread.get_openai_response(openai_client=openai_client, model=chat_deployment) |
| 264 | + |
| 265 | + print(chat_thread.get_last_message()["content"]) |
| 266 | + ``` |
| 267 | + |
| 268 | +## Clean up |
| 269 | + |
| 270 | +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. |
| 271 | + |
| 272 | +You can find and manage resources in the portal by using the **All resources** or **Resource groups** link in the leftmost pane. |
| 273 | + |
| 274 | +## Next steps |
| 275 | + |
| 276 | +As a next step, we recommend that you review the demo code for [Python](https://github.com/Azure/azure-search-vector-samples/tree/main/demo-python), [C#](https://github.com/Azure/azure-search-vector-samples/tree/main/demo-dotnet), or [JavaScript](https://github.com/Azure/azure-search-vector-samples/tree/main/demo-javascript). |
0 commit comments