From e4df1460c9fd8d03a581dc96483087233ae538a1 Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Thu, 5 Sep 2024 11:50:46 -0700
Subject: [PATCH 01/40] RAG tutorial stubbed out files
---
articles/search/toc.yml | 18 +++
.../search/tutorial-rag-build-solution-app.md | 32 +++++
...utorial-rag-build-solution-index-schema.md | 98 +++++++++++++
.../tutorial-rag-build-solution-models.md | 39 ++++++
.../tutorial-rag-build-solution-optimize.md | 30 ++++
.../tutorial-rag-build-solution-pipeline.md | 132 ++++++++++++++++++
.../tutorial-rag-build-solution-query.md | 86 ++++++++++++
.../tutorial-rag-build-solution-relevance.md | 35 +++++
.../search/tutorial-rag-build-solution.md | 56 ++++++++
9 files changed, 526 insertions(+)
create mode 100644 articles/search/tutorial-rag-build-solution-app.md
create mode 100644 articles/search/tutorial-rag-build-solution-index-schema.md
create mode 100644 articles/search/tutorial-rag-build-solution-models.md
create mode 100644 articles/search/tutorial-rag-build-solution-optimize.md
create mode 100644 articles/search/tutorial-rag-build-solution-pipeline.md
create mode 100644 articles/search/tutorial-rag-build-solution-query.md
create mode 100644 articles/search/tutorial-rag-build-solution-relevance.md
create mode 100644 articles/search/tutorial-rag-build-solution.md
diff --git a/articles/search/toc.yml b/articles/search/toc.yml
index 82a7e085a8c..df726714f84 100644
--- a/articles/search/toc.yml
+++ b/articles/search/toc.yml
@@ -89,6 +89,24 @@
href: search-howto-index-encrypted-blobs.md
- name: Create a custom analyzer
href: tutorial-create-custom-analyzer.md
+ - name: RAG tutorials
+ items:
+ - name: Build a RAG solution
+ href: tutorial-rag-build-solution.md
+ - name: Choose models
+ href: tutorial-rag-build-solution-models.md
+ - name: Design an index
+ href: tutorial-rag-build-solution-index-schema.md
+ - name: Build an indexing pipeline
+ href: tutorial-rag-build-solution-pipeline.md
+ - name: Search and generate answers
+ href: tutorial-rag-build-solution-query.md
+ - name: Maximize relevance
+ href: tutorial-rag-build-solution-relevance.md
+ - name: Optimize (reduce storage and costs)
+ href: tutorial-rag-build-solution-optimize.md
+ - name: Deploy and secure an app
+ href: tutorial-rag-build-solution-app.md
- name: Skills tutorials
items:
- name: C#
diff --git a/articles/search/tutorial-rag-build-solution-app.md b/articles/search/tutorial-rag-build-solution-app.md
new file mode 100644
index 00000000000..07a2540d21b
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-app.md
@@ -0,0 +1,32 @@
+---
+title: 'RAG Tutorial: Deploy an app'
+titleSuffix: Azure AI Search
+description: Learn how to build a generative search (RAG) app using LLMs and your proprietary grounding data in Azure AI Search.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Deployment checklist for next-level testing (RAG tutorial - Azure AI Search)
+
+In this lesson, review options for setting up simple web front-end for a RAG prototype. A simple app is useful for scenario testing with users and stakeholders. This lesson also provides a deployment checklist for broader distribution.
+
+Key points:
+
+- Revisit index schema (any fields you can omit, or make non-retrievable)
+- Provide source code for a simple web app that has input, output
+- Steps for deploying
+- Create an index alias so that you can swap out changes on the backend
+- Link to securing app/granting access
+- Link to "Get started with chat security" article and sample
+- Link to ???
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-index-schema.md b/articles/search/tutorial-rag-build-solution-index-schema.md
new file mode 100644
index 00000000000..1dea5389889
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-index-schema.md
@@ -0,0 +1,98 @@
+---
+title: 'RAG Tutorial: Design an index'
+titleSuffix: Azure AI Search
+description: Design an index for RAG patterns in Azure AI Search.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Design an index (RAG tutorial - Azure AI Search)
+
+In this tutorial, you create an index that's designed for conversational search.
+
+Key points:
+
+- index contains searchable vector and text content, plus configurations
+- schema for rag is designed for producing chunks of content
+- schema should be flat (no complex types or structures)
+- schema determines what queries you can create (be generous in attribute assignments)
+- schema must cover all the queries you want to run. You can only query one index at a time (no joins), but you can create indexes that preserve parent-child relationship, and then use nested queries or parallel queries in your search logic to pull from both.
+- schema has impact on storage/size. Consider narrow data types, attribution, vector configuration.
+
+
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-models.md b/articles/search/tutorial-rag-build-solution-models.md
new file mode 100644
index 00000000000..bbe5729fca6
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-models.md
@@ -0,0 +1,39 @@
+---
+title: 'RAG tutorial: Set up models'
+titleSuffix: Azure AI Search
+description: Set up an embedding model and chat model for generative search (RAG).
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Choose embedding and chat models (RAG tutorial - Azure AI Search)
+
+In this tutorial, review your options for choosing embedding models for vectors and chat models for conversational search over your data.
+
+Key points:
+
+- Model location requirements (Azure cloud).
+- For chunking, use Text Split skill with overlap,
+- For semantic chunking, add Document Intelligence
+- For embedding during indexing, use Azure OpenAI, Azure AI Vision, model catalog, custom skill with HTTP endpoint to external model
+- For queries, same as above, but you're creating "vectorizers"
+- For chat, same location requirements and providers, except for Azure AI Vision. You specify a chat model in your query logic.
+
+
+
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-optimize.md b/articles/search/tutorial-rag-build-solution-optimize.md
new file mode 100644
index 00000000000..5693646f700
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-optimize.md
@@ -0,0 +1,30 @@
+---
+title: 'RAG Tutorial: Reduce storage and costs'
+titleSuffix: Azure AI Search
+description: Compress vectors using binary or scalar quantization, remove copies of stored vectors.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Optimize using vector compression and reduced storage (RAG tutorial - Azure AI Search)
+
+In this tutorial, learn the techniques for reducing index size, with a focus on vector compression and storage.
+
+Key points:
+
+- scalar
+- stored false, retrievable false
+- filterable, sortable false for non-vector
+- narrow data types
+- hnsw vs eknn, does hnsw product a smaller footprint?
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-pipeline.md b/articles/search/tutorial-rag-build-solution-pipeline.md
new file mode 100644
index 00000000000..9d58ee3194c
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-pipeline.md
@@ -0,0 +1,132 @@
+---
+title: 'RAG Tutorial: Build an indexing pipeline'
+titleSuffix: Azure AI Search
+description: Create an indexer-driven pipeline that loads, chunks, embeds, and ingests content for RAG solutions on Azure AI Search.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Build an indexing pipeline (RAG tutorial - Azure AI Search)
+
+In this tutorial, learn how to build an automated indexing pipeline for a RAG solution on Azure AI Search.
+
+In Azure AI Search, an indexer is a component that automates indexing, and it's required if you're using [integrated vectorization](vector-search-integrated-vectorization.md) to chunk and embed content during indexing. This tutorial uses integrated vectorization to generate embeddings for the search index.
+
+Key points:
+
+- Dependency on a supported data source
+- Indexer pulls from the data source, pushes to the index
+- Skillset has two skills: text split and embedding
+- embedding model is also be used for vectorization at query time (assume text-to-vector conversion)
+- Large PDF files can't be chunked. Indexer shows success, but doesn't even attempt to chunk/ingest the docs. Individual files have to be less than 16 MB.
+- Check your data in the index (hide vectors). Duplicated content is expected due to overlap, repetition of parent info. It won't affect your LLM
+
+
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-query.md b/articles/search/tutorial-rag-build-solution-query.md
new file mode 100644
index 00000000000..e046f2372b7
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-query.md
@@ -0,0 +1,86 @@
+---
+title: 'RAG Tutorial: Search using an LLM'
+titleSuffix: Azure AI Search
+description: Learn how to build queries and engineer prompts for LLM-enabled search on Azure AI Search. Queries used in generative search provide the inputs to an LLM chat engine.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Search your data using a chat model (RAG tutorial - Azure AI Search)
+
+In this tutorial, learn how to provide queries and prompts to a chat model for generative search.
+
+Key points:
+
+- You can swap out models to see which one works best for your query. No reindexing or upstream modifications required.
+- Basic query (takeaway is prompt, scoping to grounding data, calling two clients)
+- Basic query is hybrid for the purposes of this tutorial
+- Query parent-child, one index
+- Query parent-child, two indexes
+- Filters
+
+
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
diff --git a/articles/search/tutorial-rag-build-solution-relevance.md b/articles/search/tutorial-rag-build-solution-relevance.md
new file mode 100644
index 00000000000..d9836125d42
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution-relevance.md
@@ -0,0 +1,35 @@
+---
+title: 'RAG Tutorial: Relevance tuning'
+titleSuffix: Azure AI Search
+description: Learn how to use the relevance tuning capabilities to return high quality results for generative search.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# Maximize relevance (RAG tutorial - Azure AI Search)
+
+In this tutorial, learn how to improve the relevance of search results used in RAG solutions. Azure AI Search includes a broad range of relevance tuning capabilities. Learn which ones are best for solving specific problems.
+
+Key points:
+
+- How to measure relevance (?) to determine if changes are improving results
+- Try different algorithms (HNSW vs eKnn)
+- Change query structure (hybrid with vector/non over same content (double-down), hybrid over multiple fields)
+- semantic ranking
+- scoring profiles
+- thresholds for minimum score
+- set weights
+- filters
+- analyzers and normalizers
+- advanced query formats (regular expressions, fuzzy search)
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution.md b/articles/search/tutorial-rag-build-solution.md
new file mode 100644
index 00000000000..a50c114bffa
--- /dev/null
+++ b/articles/search/tutorial-rag-build-solution.md
@@ -0,0 +1,56 @@
+---
+title: Build a RAG solution
+titleSuffix: Azure AI Search
+description: Learn how to build a generative search (RAG) app using LLMs and your proprietary grounding data in Azure AI Search.
+
+manager: nitinme
+author: HeidiSteen
+ms.author: heidist
+ms.service: cognitive-search
+ms.topic: tutorial
+ms.date: 09/12/2024
+
+---
+
+# How to build a RAG solution using Azure AI Search
+
+This tutorial series demonstrates a RAG pattern in Azure AI Search. It covers the components built in Azure AI Search, dependencies, optimizations, and deployment tasks.
+
+Sample data is a collection of PDFs that are uploaded to Azure Storage.
+
+## In this series
+
+- Design an index for conversational search
+
+- Choose models for embeddings and chat
+
+- Design an indexing pipeline that loads, chunks, embeds, and ingests searchable content
+
+- Retrieve searchable content using queries and a chat model
+
+- Optimize for relevance
+
+- Optimize for speed and lower costs
+
+- Deploy and secure an app
+
+We omitted a few aspects of a RAG pattern to reduce complexity:
+
+- No chat history and context. Chat history must be stored and managed separately from your grounding data, which means extra steps and code. This tutorial assumes atomic question and answers from the LLM.
+
+- No per-user user access controls over results (what we refer to as "security trimming"). For more information and resources, start with [Security trimming](search-security-trimming-for-azure-search.md) and make sure to review the links at the end of the article.
+
+This series covers the fundamentals of RAG solution development. Once you understand the basics, continue with accelerators and other code samples that provide more abstraction or are otherwise better suited for production environments and more complex workloads.
+
+## Why use Azure AI Search for RAG?
+
+LLMs like GPT are constrained by quotas and bandwidth on the amount of data they can accept on a request. You should use Azure AI Search because the quality of content passed to an LLM can make or break a RAG solution.
+
+To deliver on highest quality results, Azure AI Search provides a best-in-class search engine with AI integration and comprehensive relevance tuning. The search engine supports vector similarity search (multiple algorithms), keyword search, fuzzy search, geospatial search, and filters. You can build hybrid query requests that include all of these components, and you can control how much each query contributes to the overall request.
+
+You could use Azure AI Studio and a playground, but you might need to build your own solution if you want to control indexing and data ingestion, or have more control over queries and relevance tuning.
+
+## Next step
+
+> [!div class="nextstepaction"]
+> TBD
\ No newline at end of file
From 947e4c039710623283fc708ebb8e05a5c7ff1c3e Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Thu, 5 Sep 2024 11:57:19 -0700
Subject: [PATCH 02/40] fix TOC validation error
---
articles/search/toc.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/articles/search/toc.yml b/articles/search/toc.yml
index df726714f84..9dac08e592b 100644
--- a/articles/search/toc.yml
+++ b/articles/search/toc.yml
@@ -1,3 +1,4 @@
+items:
- name: Azure AI Search Documentation
href: index.yml
- name: Overview
From 74c8205085799d1796387fe4800ddf7e8d1b5219 Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Thu, 5 Sep 2024 13:31:14 -0700
Subject: [PATCH 03/40] fixed acrolinx warnings and revised some key points
---
articles/search/tutorial-rag-build-solution-app.md | 7 ++++---
.../tutorial-rag-build-solution-index-schema.md | 3 +++
.../search/tutorial-rag-build-solution-models.md | 12 ++++++------
.../search/tutorial-rag-build-solution-pipeline.md | 6 ++++--
articles/search/tutorial-rag-build-solution.md | 4 ++--
5 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/articles/search/tutorial-rag-build-solution-app.md b/articles/search/tutorial-rag-build-solution-app.md
index 07a2540d21b..16e1ac99ec0 100644
--- a/articles/search/tutorial-rag-build-solution-app.md
+++ b/articles/search/tutorial-rag-build-solution-app.md
@@ -1,7 +1,7 @@
---
title: 'RAG Tutorial: Deploy an app'
titleSuffix: Azure AI Search
-description: Learn how to build a generative search (RAG) app using LLMs and your proprietary grounding data in Azure AI Search.
+description: Review steps and tasks for deploying a chat app built on an Azure AI Search RAG pattern.
manager: nitinme
author: HeidiSteen
@@ -18,9 +18,10 @@ In this lesson, review options for setting up simple web front-end for a RAG pro
Key points:
-- Revisit index schema (any fields you can omit, or make non-retrievable)
+- Is your index ready for deployment? Clean up (can you remove fields? Make some non-retrievable?). Rebuild is probably required.
+- Remove any permissions you don't need (for example, to models or data sources you aren't using)
- Provide source code for a simple web app that has input, output
-- Steps for deploying
+- Provide steps for deploying
- Create an index alias so that you can swap out changes on the backend
- Link to securing app/granting access
- Link to "Get started with chat security" article and sample
diff --git a/articles/search/tutorial-rag-build-solution-index-schema.md b/articles/search/tutorial-rag-build-solution-index-schema.md
index 1dea5389889..64502fdaf91 100644
--- a/articles/search/tutorial-rag-build-solution-index-schema.md
+++ b/articles/search/tutorial-rag-build-solution-index-schema.md
@@ -24,6 +24,9 @@ Key points:
- schema determines what queries you can create (be generous in attribute assignments)
- schema must cover all the queries you want to run. You can only query one index at a time (no joins), but you can create indexes that preserve parent-child relationship, and then use nested queries or parallel queries in your search logic to pull from both.
- schema has impact on storage/size. Consider narrow data types, attribution, vector configuration.
+- show schemas for parent-child all-up and paired indexes via index projections
+- note metadata for filters
+- TBD: add fields for location and use entity recognition to pull this values out of the PDFs? Not sure how it will query, but goal would be to add some structure to the schema.
-
## Next step
> [!div class="nextstepaction"]
-> TBD
\ No newline at end of file
+> [Design an index](tutorial-rag-build-solution-index-schema.md)
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-pipeline.md b/articles/search/tutorial-rag-build-solution-pipeline.md
index 9d58ee3194c..625775a2138 100644
--- a/articles/search/tutorial-rag-build-solution-pipeline.md
+++ b/articles/search/tutorial-rag-build-solution-pipeline.md
@@ -20,12 +20,14 @@ In Azure AI Search, an indexer is a component that automates indexing, and it's
Key points:
-- Dependency on a supported data source
+- Dependency on a supported data source. Use Azure blob storage for this tutorial.
- Indexer pulls from the data source, pushes to the index
-- Skillset has two skills: text split and embedding
+- Skillset (example 1) has two skills: text split and embedding
- embedding model is also be used for vectorization at query time (assume text-to-vector conversion)
- Large PDF files can't be chunked. Indexer shows success, but doesn't even attempt to chunk/ingest the docs. Individual files have to be less than 16 MB.
- Check your data in the index (hide vectors). Duplicated content is expected due to overlap, repetition of parent info. It won't affect your LLM
+- Skillset (example 2) add a custom skill that points to external embedding model, or document intelligence.
+- Skillset (example 3) add an entity recognition skill to lift locations?
## Next step
> [!div class="nextstepaction"]
-> TBD
\ No newline at end of file
+> [Create an indexing pipeline](tutorial-rag-build-solution-pipeline.md)
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-models.md b/articles/search/tutorial-rag-build-solution-models.md
index fb800e18550..a65083ff16e 100644
--- a/articles/search/tutorial-rag-build-solution-models.md
+++ b/articles/search/tutorial-rag-build-solution-models.md
@@ -14,17 +14,28 @@ ms.date: 09/12/2024
# Choose embedding and chat models (RAG tutorial - Azure AI Search)
-In this tutorial, review your options for choosing embedding models for vectors and chat models for conversational search over your data.
+A RAG solution built on Azure AI Search takes a dependency on embedding models for vectorization, and chat models for conversational search over your data.
+
+In this tutorial, review your options for choosing embedding and chat models for Learn how to set up connections so that Azure AI Search can connect securely during indexing and at query time for generative AI responses and text-to-vector conversions of query strings.
+
+Objective:
+
+- Identify an embedding model and chat model for your RAG workflow.
Key points:
-- Model location requirement (Azure cloud).
-- For chunking, use Text Split skill with overlap -- or --
-- For semantic chunking, add Document Intelligence
-- For embedding during indexing, use Azure OpenAI, Azure AI Vision, model catalog, custom skill with HTTP endpoint to external model
-- For queries, same as above, but you're creating "vectorizers". It's doing the same thing.
-- For chat, same location requirements and providers, except no Azure AI Vision. You specify a chat model in your query logic. Unlike embedding, you can swap this out to what they do.
-- To do's for accessing models: permissions, endpoints. Include a "configure access" step.
+- Built-in integration for models hosted in the Azure cloud.
+- For chunking, use the native Text Split skill with overlapping text -- or -- for semantic chunking, use Document Intelligence.
+- For embedding during indexing, use a skill that points to Azure OpenAI, Azure AI Vision, or the model catalog. Alternatively, use custom skill with HTTP endpoint to external model.
+- For queries, same embedding models as above, but you're wrapping it in a "vectorizer" instead of a "skill".
+- Use the same embedding model for indexing and text-to-vector queries. If you want to try a different model, it's a rebuild. An indexer pipeline like the one used in this tutorial makes this step easy.
+- For chat, same location requirements and providers, except no Azure AI Vision. You specify a chat model in your query logic. Unlike embedding, you can swap these around at query time to see what they do.
+
+Tasks:
+
+- H2: Identify the models for which we have skills/vectorizers and provide locations (model catalog, Azure OpenAI, etc). Crosslink to model deployment instructions. Include steps for getting endpoints, model version, deployment name, REST API version.
+- H2: How to use other models (create a custom skill, create a custom vectorizer).
+- H2: How to configure access. Set up an Azure AI Search managed identity, give it permissions on Azure-hosted models.
+
## Next step
> [!div class="nextstepaction"]
diff --git a/articles/search/tutorial-rag-build-solution-models.md b/articles/search/tutorial-rag-build-solution-models.md
index 30911d141a8..51ae6a40e46 100644
--- a/articles/search/tutorial-rag-build-solution-models.md
+++ b/articles/search/tutorial-rag-build-solution-models.md
@@ -13,7 +13,7 @@ ms.date: 09/12/2024
---
-# Choose embedding and chat models (RAG tutorial - Azure AI Search)
+# Tutorial: Choose embedding and chat models (RAG in Azure AI Search)
A RAG solution built on Azure AI Search takes a dependency on embedding models for vectorization, and on chat models for conversational search over your data.
@@ -32,23 +32,23 @@ If you don't have an Azure subscription, create a [free account](https://azure.m
- The Azure portal, used to deploy models and configure role assignments in the Azure cloud.
-- An **Owner** role on your Azure subscription is necessary for creating role assignments. Your model provider has more role requirements for deploying and accessing models. Those are noted in the following steps.
+- An **Owner** role on your Azure subscription, necessary for creating role assignments. Your model provider has more role requirements for deploying and accessing models. Those are noted in the following steps.
-- A model provider, such as [Azure OpenAI](/azure/ai-services/openai/how-to/create-resource), Azure AI Vision created using [a multi-service account](/azure/ai-services/multi-service-resource), or [Azure AI Studio](https://ai.azure.com/).
+- A model provider, such as [Azure OpenAI](/azure/ai-services/openai/how-to/create-resource), Azure AI Vision via an [Azure AI multi-service account](/azure/ai-services/multi-service-resource), or [Azure AI Studio](https://ai.azure.com/).
- We use Azure OpenAI in this tutorial, but list the other Azure resources so that you know your options for integrated embedding.
+ We use Azure OpenAI in this tutorial. Other providers are listed so that you know your options for integrated embedding.
- Azure AI Search, Basic tier or higher provides a [managed identity](search-howto-managed-identities-data-sources.md) used in role assignments.
-To complete all of the tutorials in this series, the region must support both Azure AI Search and the model provider. See supported regions for:
+- A shared region. To complete all of the tutorials in this series, the region must support both Azure AI Search and the model provider. See supported regions for:
-- [Azure OpenAI regions](/azure/ai-services/openai/concepts/models#model-summary-table-and-region-availability)
+ - [Azure OpenAI regions](/azure/ai-services/openai/concepts/models#model-summary-table-and-region-availability)
-- [Azure AI Vision regions](/azure/ai-services/computer-vision/overview-image-analysis?tabs=4-0#region-availability)
+ - [Azure AI Vision regions](/azure/ai-services/computer-vision/overview-image-analysis?tabs=4-0#region-availability)
-- [Azure AI Studio](/azure/ai-studio/reference/region-support) regions.
+ - [Azure AI Studio](/azure/ai-studio/reference/region-support) regions.
-Azure AI Search is currently facing limited availability in some regions, such as West Europe and West US 2/3. Check the [Azure AI Search region list](search-region-support.md) to confirm availability.
+ Azure AI Search is currently facing limited availability in some regions, such as West Europe and West US 2/3. Check the [Azure AI Search region list](search-region-support.md) to confirm availability.
> [!TIP]
> Currently, the following regions provide the most overlap and have the most capacity: **East US**, **East US2**, and **South Central** in the Americas; **France Central** or **Switzerland North** in Europe; **Australia East** in Asia Pacific.
@@ -73,7 +73,7 @@ Azure AI Search provides skill and vectorizer support for the following embeddin
2 Deployed models in the model catalog are accessed over an AML endpoint. We use the existing AML skill for this connection.
-You can use other models besides those listed here. For more information, see [Use non-Azure models for embeddings](#use-non-azure-models-for-embeddings-and-chat) in this article.
+You can use other models besides those listed here. For more information, see [Use non-Azure models for embeddings](#use-non-azure-models-for-embeddings) in this article.
> [!NOTE]
> Inputs to an embedding models are typically chunked data. In an Azure AI Search RAG pattern, chunking is handled in the indexer pipeline, covered in [another tutorial](tutorial-rag-build-solution-pipeline.md) in this series.
@@ -108,8 +108,8 @@ You must have [**Cognitive Services OpenAI Contributor**]( /azure/ai-services/op
1. Specify a deployment name. We recommend "text-embedding-ada-002".
1. Accept the defaults.
1. Select **Deploy**.
-1. Repeat the previous steps for GPT-35-Turbo.
-1. Make a note of the model names and endpoint. Embedding skills and vectorizers assemble the full endpoint internally, so you only need the resource name. For example, given `https://MY-FAKE-ACCOUNT.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-05-15`, the endpoint you should provide in skill and vectorizer definitions is `https://MY-FAKE-ACCOUNT.openai.azure.com`.
+1. Repeat the previous steps for **gpt-35-turbo**.
+1. Make a note of the model names and endpoint. Embedding skills and vectorizers assemble the full endpoint internally, so you only need the resource URI. For example, given `https://MY-FAKE-ACCOUNT.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-05-15`, the endpoint you should provide in skill and vectorizer definitions is `https://MY-FAKE-ACCOUNT.openai.azure.com`.
## Configure search engine access to Azure models
diff --git a/articles/search/tutorial-rag-build-solution-pipeline.md b/articles/search/tutorial-rag-build-solution-pipeline.md
index c15c4d85bc4..3e6ff17a781 100644
--- a/articles/search/tutorial-rag-build-solution-pipeline.md
+++ b/articles/search/tutorial-rag-build-solution-pipeline.md
@@ -12,7 +12,7 @@ ms.date: 09/12/2024
---
-# Build an indexing pipeline (RAG tutorial - Azure AI Search)
+# Tutorial: Build an indexing pipeline (RAG in Azure AI Search)
In this tutorial, learn how to build an automated indexing pipeline for a RAG solution on Azure AI Search.
diff --git a/articles/search/tutorial-rag-build-solution-query.md b/articles/search/tutorial-rag-build-solution-query.md
index 627ac2605d1..8d1ac67ec7d 100644
--- a/articles/search/tutorial-rag-build-solution-query.md
+++ b/articles/search/tutorial-rag-build-solution-query.md
@@ -12,7 +12,7 @@ ms.date: 09/12/2024
---
-# Search your data using a chat model (RAG tutorial - Azure AI Search)
+# Tutorial: Search your data using a chat model (RAG in Azure AI Search)
In this tutorial, learn how to send queries and prompts to a chat model for generative search.
@@ -94,4 +94,4 @@ Only fields marked as "retrievable" in the search index can appear in results. I
## Next step
> [!div class="nextstepaction"]
-> [Maximize relevance](tutorial-rag-build-solution-relevance.md)
+> [Maximize relevance](tutorial-rag-build-solution-maximize-relevance.md)
diff --git a/articles/search/tutorial-rag-build-solution.md b/articles/search/tutorial-rag-build-solution.md
index 6e2f6a57793..302046a97e0 100644
--- a/articles/search/tutorial-rag-build-solution.md
+++ b/articles/search/tutorial-rag-build-solution.md
@@ -16,7 +16,7 @@ ms.date: 09/12/2024
This tutorial series demonstrates a pattern for building RAG solutions on Azure AI Search. It covers the components built in Azure AI Search, dependencies, optimizations, and deployment tasks.
-Sample data is a [collection of PDFs](https://github.com/Azure-Samples/azure-search-sample-data/tree/main/nasa-e-book/earth_book_2019_text_pages) uploaded to Azure Storage.
+Sample data is a [collection of PDFs](https://github.com/Azure-Samples/azure-search-sample-data/tree/main/nasa-e-book/earth_book_2019_text_pages) uploaded to Azure Storage.
Sample code can be found in [this Python notebook](LINK-TBD), but we recommend using this series for context, insights, and alternative approaches.
From 0c0d9e834ec09a6790f46f03bdcbf91a55eb69ca Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Tue, 10 Sep 2024 08:28:19 -0700
Subject: [PATCH 10/40] checkpoint
---
...utorial-rag-build-solution-index-schema.md | 114 +++++---
...ial-rag-build-solution-minimize-storage.md | 2 +-
.../tutorial-rag-build-solution-models.md | 2 +-
.../tutorial-rag-build-solution-pipeline.md | 268 +++++++++++++++++-
.../search/tutorial-rag-build-solution.md | 2 +-
5 files changed, 349 insertions(+), 39 deletions(-)
diff --git a/articles/search/tutorial-rag-build-solution-index-schema.md b/articles/search/tutorial-rag-build-solution-index-schema.md
index 81ae0b42975..e5d6ed83d6a 100644
--- a/articles/search/tutorial-rag-build-solution-index-schema.md
+++ b/articles/search/tutorial-rag-build-solution-index-schema.md
@@ -12,7 +12,7 @@ ms.date: 09/12/2024
---
-# Tutorial: Design an index (RAG in Azure AI Search)
+# Tutorial: Design an index for RAG in Azure AI Search
An index contains searchable text and vector content, plus configurations. In a RAG pattern that uses a chat model for responses, you want an index that contains chunks of content that can be passed to an LLM at query time.
@@ -35,29 +35,29 @@ The output of this exercise is an index definition in JSON. At this point, it's
In conversational search, LLMs compose the response that the user sees, not the search engine, so you don't need to think about what fields to show in your search results, and whether the representations of individual search documents are coherent to the user. Depending on the question, the LLM might return verbatim content from your index, or more likely, repackage the content for a better answer.
-### Focus on chunks
+### Organized around chunks
When LLMs generate a response, they operate on chunks of content for message inputs, and while they need to know where the chunk came from for citation purposes, what matters most is the quality of message inputs and its relevance to the user's question. Whether the chunks come from one document or a thousand, the LLM ingests the information or *grounding data*, and formulates the response using instructions provided in a system prompt.
Chunks are the focus of the schema, and each chunk is the defining element of a search document in a RAG pattern. You can think of your index as a large collection of chunks, as opposed to traditional search documents that probably have more structure, such as fields containing uniform content for a name, descriptions, categories, and addresses.
-### Focus on content
+### Content-aware
In addition to structural considerations, like chunked content, you also want to consider the substance of your content because it also informs what fields are indexed.
-In this tutorial, we use PDFs and content from the NASA Earth Book. This content is descriptive and informative, with numerous references to geographies, countries, and areas across the world. To capture this information in our index and potentially use it in queries, we can include skills in our indexing pipeline that recognize and extract this information, loading it into a searchable and filterable `locations` field.
+In this tutorial, sample data consists of PDFs and content from the NASA Earth Book. This content is descriptive and informative, with numerous references to geographies, countries, and areas across the world. To capture this information in our index and potentially use it in queries, we include skills in our indexing pipeline that recognize and extract this information, loading it into a searchable and filterable `locations` field.
The original ebook is large, over 100 pages and 35 MB in size. We broke it up into smaller PDFs, one per page of text, to stay under the REST API payload limit of 16 MB per API call.
For simplicity, we omit image vectorization for this exercise.
-### Focus on parent-child indexes
+### Parent-child fields in one or two indexes
-Chunked content typically derives from a larger document. And although the schema is organized around chunks, you also want to capture properties and content at the parent level. Examples of these properties might include the parent file path, title, authors, publication date, summary.
+Chunked content typically derives from a larger document. And although the schema is organized around chunks, you also want to capture properties and content at the parent level. Examples of these properties might include the parent file path, title, authors, publication date, or a summary.
An inflection point in schema design is whether to have two indexes for parent and child/chunked content, or a single index that repeats parent elements for each chunk.
-In this tutorial, because all of the chunks of text originate from a single parent (NASA Earth Book), you don't need a separate index dedicated to up level parent fields. If you index from multiple parent PDFs, you might want a parent-child index pair to capture level-specific fields and then send lookup queries to the parent index to retrieve those fields relevant to each chunk. We include an example of that parent-child index template in this exercise for comparison.
+In this tutorial, because all of the chunks of text originate from a single parent (NASA Earth Book), you don't need a separate index dedicated to up level parent fields. However, if you index from multiple parent PDFs, you might want a parent-child index pair to capture level-specific fields and then send lookup queries to the parent index to retrieve those fields relevant to each chunk.
### Checklist of schema considerations
@@ -76,13 +76,13 @@ Although Azure AI Search can't join indexes, you can create indexes that preserv
> [!NOTE]
> Schema design affects storage and costs. This exercise is focused on schema fundamentals. In the [Minimize storage and costs](tutorial-rag-build-solution-minimize-storage.md) tutorial, you revisit schema design to consider narrow data types, attribution, and vector configurations that offer more efficient.
-## Create a basic index
+## Create an index for RAG workloads
-A minimal index for LLM is designed to store chunks of content. It includes vector fields if you want similarity search for highly relevant results, and nonvector fields for human-readable inputs to the LLM for conversational search. Nonvector chunked content in the search results becomes the grounding data sent to the LLM.
+A minimal index for LLM is designed to store chunks of content. It typically includes vector fields if you want similarity search for highly relevant results. It also includes nonvector fields for human-readable inputs to the LLM for conversational search. Nonvector chunked content in the search results becomes the grounding data sent to the LLM.
1. Open Visual Studio Code and create a new file. It doesn't have to be a Python file type for this exercise.
-1. Here's a minimal index definition for RAG solutions that support vector and hybrid search. Review it for an introduction to required elements: name, fields, and a `vectorSearch` configuration for the vector fields.
+1. Here's a minimal index definition for RAG solutions that support vector and hybrid search. Review it for an introduction to required elements: index name, fields, and a configuration section for vector fields.
```json
{
@@ -91,7 +91,7 @@ A minimal index for LLM is designed to store chunks of content. It includes vect
{ "name": "id", "type": "Edm.String", "key": true },
{ "name": "chunked_content", "type": "Edm.String", "searchable": true, "retrievable": true },
{ "name": "chunked_content_vectorized", "type": "Edm.Single", "dimensions": 1536, "vectorSearchProfile": "my-vector-profile", "searchable": true, "retrievable": false, "stored": false },
- { "name": "metadata", "type": "Edm.String", "retrievable": true, "searchable": true }
+ { "name": "metadata", "type": "Edm.String", "retrievable": true, "searchable": true, "filterable": true }
],
"vectorSearch": {
"algorithms": [
@@ -104,13 +104,62 @@ A minimal index for LLM is designed to store chunks of content. It includes vect
}
```
- Fields must include key field (`"id"`) and should include vector chunks for similarity search, and nonvector chunks for the LLM. Metadata about the source file might be file path, creation date, or content type.
-
- Vector fields have [specific types](/rest/api/searchservice/supported-data-types#edm-data-types-for-vector-fields) and extra attributes for embedding model dimensions and configuration. `Edm.Single` is a data type that works for the more commonly used LLMs. For more information about vector fields, see [Create a vector index](vector-search-how-to-create-index.md).
-
-1. Here's the index schema for the tutorial and the Earth Book content. It's similar to the basic schema, but adds a parent ID, metadata (`title`), strings (`chunks`), and vectors for similarity search (`text_vectors`). It also includes a `locations` field for storing generated content that's created in the [indexing pipeline](tutorial-rag-build-solution-pipeline.md).
+ Fields must include key field (`"id"`) and should include vector chunks for similarity search, and nonvector chunks for inputs to the LLM.
+
+ Vector fields have [specific types](/rest/api/searchservice/supported-data-types#edm-data-types-for-vector-fields) and extra attributes for embedding model dimensions and configuration. `Edm.Single` is a data type that works for commonly used LLMs. For more information about vector fields, see [Create a vector index](vector-search-how-to-create-index.md).
+
+ Metadata fields might be file path, creation date, or content type and are useful for [filters](vector-search-filters.md).
+
+1. Here's the index schema for the [tutorial source code](https://github.com/Azure-Samples/azure-search-python-samples/blob/main/Tutorial-RAG/Tutorial-rag.ipynb) and the [Earth Book content](https://github.com/Azure-Samples/azure-search-sample-data/tree/main/nasa-e-book/earth_book_2019_text_pages).
+
+ Like the basic schema, it's organized around chunks. The `chunk_id` uniquely identifies each chunk. The `text_vector` field is an embedding of the chunk. The nonvector `chunk` field is a readable string. The `title` maps to a unique metadata storage path for the blobs. The `parent_id` is the only parent-level field, and it's a base64-encoded version of the parent file URI.
+
+ The schema also includes a `locations` field for storing generated content that's created by the [indexing pipeline](tutorial-rag-build-solution-pipeline.md).
+
+ ```python
+ index_name = "py-rag-tutorial-idx"
+ index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)
+ fields = [
+ SearchField(name="parent_id", type=SearchFieldDataType.String),
+ SearchField(name="title", type=SearchFieldDataType.String),
+ SearchField(name="locations", type=SearchFieldDataType.Collection(SearchFieldDataType.String), filterable=True),
+ SearchField(name="chunk_id", type=SearchFieldDataType.String, key=True, sortable=True, filterable=True, facetable=True, analyzer_name="keyword"),
+ SearchField(name="chunk", type=SearchFieldDataType.String, sortable=False, filterable=False, facetable=False),
+ SearchField(name="text_vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), vector_search_dimensions=1536, vector_search_profile_name="myHnswProfile")
+ ]
+
+ # Configure the vector search configuration
+ vector_search = VectorSearch(
+ algorithms=[
+ HnswAlgorithmConfiguration(name="myHnsw"),
+ ],
+ profiles=[
+ VectorSearchProfile(
+ name="myHnswProfile",
+ algorithm_configuration_name="myHnsw",
+ vectorizer="myOpenAI",
+ )
+ ],
+ vectorizers=[
+ AzureOpenAIVectorizer(
+ name="myOpenAI",
+ kind="azureOpenAI",
+ azure_open_ai_parameters=AzureOpenAIParameters(
+ resource_uri=AZURE_OPENAI_ACCOUNT,
+ deployment_id="text-embedding-ada-002",
+ model_name="text-embedding-ada-002"
+ ),
+ ),
+ ],
+ )
+
+ # Create the search index
+ index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search)
+ result = index_client.create_or_update_index(index)
+ print(f"{result.name} created")
+ ```
- ```json
+
+
+1. For an index schema that more closely mimics structured content, you would have separate indexes for parent and child (chunked) fields. You would need index projections to coordinate the indexing of the two indexes simultaneously. Queries execute against the child index. Query logic includes a lookup query, using the parent_idto retrieve content from the parent index.
+
+ Fields in the child index:
+
+ - id
+ - chunk
+ - chunkVectcor
+ - parent_id
+ Fields in the parent index (everything that you want "one of"):
+
+ - parent_id
+ - parent-level fields (name, title, category)
+ -->
## Next step
diff --git a/articles/search/tutorial-rag-build-solution-minimize-storage.md b/articles/search/tutorial-rag-build-solution-minimize-storage.md
index 49fb9dad15d..2c158ce74c7 100644
--- a/articles/search/tutorial-rag-build-solution-minimize-storage.md
+++ b/articles/search/tutorial-rag-build-solution-minimize-storage.md
@@ -12,7 +12,7 @@ ms.date: 09/12/2024
---
-# Tutorial: Minimize storage and costs using vector compression and narrow data types (RAG in Azure AI Search)
+# Tutorial: Minimize storage and costs (RAG in Azure AI Search)
In this tutorial, learn the techniques for reducing index size, with a focus on vector compression and storage.
diff --git a/articles/search/tutorial-rag-build-solution-models.md b/articles/search/tutorial-rag-build-solution-models.md
index 51ae6a40e46..9279010e933 100644
--- a/articles/search/tutorial-rag-build-solution-models.md
+++ b/articles/search/tutorial-rag-build-solution-models.md
@@ -13,7 +13,7 @@ ms.date: 09/12/2024
---
-# Tutorial: Choose embedding and chat models (RAG in Azure AI Search)
+# Tutorial: Choose embedding and chat models for RAG in Azure AI Search
A RAG solution built on Azure AI Search takes a dependency on embedding models for vectorization, and on chat models for conversational search over your data.
diff --git a/articles/search/tutorial-rag-build-solution-pipeline.md b/articles/search/tutorial-rag-build-solution-pipeline.md
index 3e6ff17a781..35e0fb193f1 100644
--- a/articles/search/tutorial-rag-build-solution-pipeline.md
+++ b/articles/search/tutorial-rag-build-solution-pipeline.md
@@ -12,14 +12,276 @@ ms.date: 09/12/2024
---
-# Tutorial: Build an indexing pipeline (RAG in Azure AI Search)
+# Tutorial: Build an indexing pipeline for RAG on Azure AI Search
-In this tutorial, learn how to build an automated indexing pipeline for a RAG solution on Azure AI Search.
+Learn how to build an automated indexing pipeline for a RAG solution on Azure AI Search. Indexing automation is through an indexer that drives indexing and skillset execution, providing [integrated data chunking and vectorization](vector-search-integrated-vectorization.md) on a one-time or recurring basis for incremental updates.
-An indexer drives indexing and skillset execution that provides [integrated data chunking and vectorization](vector-search-integrated-vectorization.md) on a one-time or recurring basis for incremental updates. You create an indexer, data source connection, skillset, and provide the index schema you created in the previous tutorial. This exercise uses Azure Blob storage as the data source.
+In this tutorial, you:
+
+> [!div class="checklist"]
+> - Provide the index schema from the previous tutorial
+> - Create a data source connection
+> - Create an indexer
+> - Create a skillset
+> - Run the indexer and check results
If you don't have an Azure subscription, create a [free account](https://azure.microsoft.com/free/?WT.mc_id=A261C142F) before you begin.
+> [!TIP]
+> You can use the [Import and vectorize data wizard](search-import-data-portal.md) to create your pipeline. For some quickstarts, see [Image search](search-get-started-portal-image-search.md) and [Vector search](search-get-started-portal-import-vectors.md).
+
+## Prerequisites
+
+- [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).
+
+- Azure Storage general purpose account. This exercise uploads PDF files into blob storage for automated indexing.
+
+- Azure AI Search, Basic tier or above for managed identity and semantic ranking. Choose a region that's shared with Azure OpenAI.
+
+- Azure OpenAI, with a deployment of text-embedding-002. For more information about embedding models used in RAG solutions, see [Choose embedding models for RAG in Azure AI Search](tutorial-rag-build-solution-models.md)
+
+## Download file
+
+[Download a Jupyter notebook](https://github.com/Azure-Samples/azure-search-python-samples/blob/main/Tutorial-RAG/Tutorial-rag.ipynb) 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).
+
+## Provide the index schema
+
+Here's the index schema from the [previous tutorial](search\tutorial-rag-build-solution-index-schema.md). It's organized around vectorized and nonvectorized chunks. It includes a `locations` field that stores AI-generated content created by the skillset.
+
+```python
+index_name = "py-rag-tutorial-idx"
+index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)
+fields = [
+ SearchField(name="parent_id", type=SearchFieldDataType.String),
+ SearchField(name="title", type=SearchFieldDataType.String),
+ SearchField(name="locations", type=SearchFieldDataType.Collection(SearchFieldDataType.String), filterable=True),
+ SearchField(name="chunk_id", type=SearchFieldDataType.String, key=True, sortable=True, filterable=True, facetable=True, analyzer_name="keyword"),
+ SearchField(name="chunk", type=SearchFieldDataType.String, sortable=False, filterable=False, facetable=False),
+ SearchField(name="text_vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), vector_search_dimensions=1536, vector_search_profile_name="myHnswProfile")
+ ]
+
+# Configure the vector search configuration
+vector_search = VectorSearch(
+ algorithms=[
+ HnswAlgorithmConfiguration(name="myHnsw"),
+ ],
+ profiles=[
+ VectorSearchProfile(
+ name="myHnswProfile",
+ algorithm_configuration_name="myHnsw",
+ vectorizer="myOpenAI",
+ )
+ ],
+ vectorizers=[
+ AzureOpenAIVectorizer(
+ name="myOpenAI",
+ kind="azureOpenAI",
+ azure_open_ai_parameters=AzureOpenAIParameters(
+ resource_uri=AZURE_OPENAI_ACCOUNT,
+ deployment_id="text-embedding-ada-002",
+ model_name="text-embedding-ada-002"
+ ),
+ ),
+ ],
+)
+
+# Create the search index on Azure AI Search
+index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search)
+result = index_client.create_or_update_index(index)
+print(f"{result.name} created")
+```
+
+## Create a data source connection
+
+In this step, set up a connection to Azure Blob Storage. The indexer retrieves PDFs from a container. You can create the container and upload files in the Azure portal.
+
+1. Sign in to the Azure portal and find your Azure Storage account.
+
+1. Create a container and upload the PDFs from [earth_book_2019_text_pages](https://github.com/Azure-Samples/azure-search-sample-data/tree/main/nasa-e-book/earth_book_2019_text_pages).
+
+1. Make sure Azure AI Search has **Storage Blob Data Reader** permissions on the resource.
+
+1. Next, in Visual Studio Code, define an indexer data source that provides connection information during indexing.
+
+ ```python
+ from azure.search.documents.indexes import SearchIndexerClient
+ from azure.search.documents.indexes.models import (
+ SearchIndexerDataContainer,
+ SearchIndexerDataSourceConnection
+ )
+
+ # Create a data source
+ indexer_client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)
+ container = SearchIndexerDataContainer(name="nasa-ebook-pdfs-all")
+ data_source_connection = SearchIndexerDataSourceConnection(
+ name="py-rag-tutorial-ds",
+ type="azureblob",
+ connection_string=AZURE_STORAGE_CONNECTION,
+ container=container
+ )
+ data_source = indexer_client.create_or_update_data_source_connection(data_source_connection)
+
+ print(f"Data source '{data_source.name}' created or updated")
+ ```
+
+## Create a skillset
+
+Skills are the basis for integrated data chunking and vectorization. At a minimum, you want a Text Split skill to chunk your content, and an embedding skill that create vector representations of your chunked content.
+
+In this skillset, an extra skill is used to create structured data in the index. The Entity Recognition skill is used to identify locations, which can range from proper names to generic references, such as "ocean" or "mountain". Having structured data gives you more options for creating interesting queries and boosting relevance.
+
+```python
+from azure.search.documents.indexes.models import (
+ SplitSkill,
+ InputFieldMappingEntry,
+ OutputFieldMappingEntry,
+ AzureOpenAIEmbeddingSkill,
+ EntityRecognitionSkill,
+ SearchIndexerIndexProjections,
+ SearchIndexerIndexProjectionSelector,
+ SearchIndexerIndexProjectionsParameters,
+ IndexProjectionMode,
+ SearchIndexerSkillset,
+ CognitiveServicesAccountKey
+)
+
+# Create a skillset
+skillset_name = "py-rag-tutorial-ss"
+
+split_skill = SplitSkill(
+ description="Split skill to chunk documents",
+ text_split_mode="pages",
+ context="/document",
+ maximum_page_length=2000,
+ page_overlap_length=500,
+ inputs=[
+ InputFieldMappingEntry(name="text", source="/document/content"),
+ ],
+ outputs=[
+ OutputFieldMappingEntry(name="textItems", target_name="pages")
+ ],
+)
+
+embedding_skill = AzureOpenAIEmbeddingSkill(
+ description="Skill to generate embeddings via Azure OpenAI",
+ context="/document/pages/*",
+ resource_uri=AZURE_OPENAI_ACCOUNT,
+ deployment_id="text-embedding-ada-002",
+ model_name="text-embedding-ada-002",
+ dimensions=1536,
+ inputs=[
+ InputFieldMappingEntry(name="text", source="/document/pages/*"),
+ ],
+ outputs=[
+ OutputFieldMappingEntry(name="embedding", target_name="text_vector")
+ ],
+)
+
+entity_skill = EntityRecognitionSkill(
+ description="Skill to recognize entities in text",
+ context="/document/pages/*",
+ categories=["Location"],
+ default_language_code="en",
+ inputs=[
+ InputFieldMappingEntry(name="text", source="/document/pages/*")
+ ],
+ outputs=[
+ OutputFieldMappingEntry(name="locations", target_name="locations")
+ ]
+)
+
+index_projections = SearchIndexerIndexProjections(
+ selectors=[
+ SearchIndexerIndexProjectionSelector(
+ target_index_name=index_name,
+ parent_key_field_name="parent_id",
+ source_context="/document/pages/*",
+ mappings=[
+ InputFieldMappingEntry(name="chunk", source="/document/pages/*"),
+ InputFieldMappingEntry(name="text_vector", source="/document/pages/*/text_vector"),
+ InputFieldMappingEntry(name="title", source="/document/metadata_storage_name"),
+ ],
+ ),
+ ],
+ parameters=SearchIndexerIndexProjectionsParameters(
+ projection_mode=IndexProjectionMode.SKIP_INDEXING_PARENT_DOCUMENTS
+ ),
+)
+
+cognitive_services_account = CognitiveServicesAccountKey(key=AZURE_AI_MULTISERVICE_KEY)
+
+skills = [split_skill, embedding_skill, entity_skill]
+
+skillset = SearchIndexerSkillset(
+ name=skillset_name,
+ description="Skillset to chunk documents and generating embeddings",
+ skills=skills,
+ index_projections=index_projections,
+ cognitive_services_account=cognitive_services_account
+)
+
+client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)
+client.create_or_update_skillset(skillset)
+print(f"{skillset.name} created")
+```
+
+## Create and run the indexer
+
+```python
+from azure.search.documents.indexes.models import (
+ SearchIndexer,
+ FieldMapping
+)
+
+# Create an indexer
+indexer_name = "py-rag-tutorial-idxr"
+
+indexer_parameters = None
+
+indexer = SearchIndexer(
+ name=indexer_name,
+ description="Indexer to index documents and generate embeddings",
+ skillset_name=skillset_name,
+ target_index_name=index_name,
+ data_source_name=data_source.name,
+ # Map the metadata_storage_name field to the title field in the index to display the PDF title in the search results
+ field_mappings=[FieldMapping(source_field_name="metadata_storage_name", target_field_name="title")],
+ parameters=indexer_parameters
+)
+
+indexer_client = SearchIndexerClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL)
+indexer_result = indexer_client.create_or_update_indexer(indexer)
+
+# Run the indexer
+indexer_client.run_indexer(indexer_name)
+print(f' {indexer_name} is created and running. Give the indexer a few minutes before running a query.')
+```
+
+## Run hybrid search to check results
+
+```python
+from azure.search.documents import SearchClient
+from azure.search.documents.models import VectorizableTextQuery
+
+# Hybrid Search
+query = "where are the nasa headquarters located?"
+
+search_client = SearchClient(endpoint=AZURE_SEARCH_SERVICE, credential=AZURE_SEARCH_CREDENTIAL, index_name=index_name)
+vector_query = VectorizableTextQuery(text=query, k_nearest_neighbors=1, fields="text_vector", exhaustive=True)
+
+results = search_client.search(
+ search_text=query,
+ vector_queries= [vector_query],
+ select=["parent_id", "chunk_id", "chunk, locations"],
+ top=1
+)
+
+for result in results:
+ print(f"Score: {result['@search.score']}")
+ print(f"Content: {result['chunk']}")
+```
+
+
-
-## Next step
-
-> [!div class="nextstepaction"]
-> [Deploy and secure an app](tutorial-rag-build-solution-app.md)
\ No newline at end of file
diff --git a/articles/search/tutorial-rag-build-solution-query.md b/articles/search/tutorial-rag-build-solution-query.md
index 13fb0cb3a95..27f66410181 100644
--- a/articles/search/tutorial-rag-build-solution-query.md
+++ b/articles/search/tutorial-rag-build-solution-query.md
@@ -77,7 +77,7 @@ search_client = SearchClient(
# Provide instructions to the model
GROUNDED_PROMPT="""
-You are an AI assistant that helps users find the information their looking for.
+You are an AI assistant that helps users learn from the information found in the source material.
Answer the query using only the sources provided below.
Use bullets if the answer has multiple points.
If the answer is longer than 3 sentences, provide a summary.
@@ -117,21 +117,49 @@ print(response.choices[0].message.content)
In this example, the answer is based on a single input (`top=1`) consisting of the one chunk determined by the search engine to be the most relevant. Instructions in the prompt tell the LLM to use only the information in the `sources`, or formatted search results. Results from the first query`"how much of earth is covered by water"` should look similar to the following example.
-```
-About 72% of the Earth's surface is covered in water, according to page-79.pdf. The provided sources do not give further information on this topic.
-```
+:::image type="content" source="media/tutorial-rag-solution/chat-results-1.png" alt-text="Screenshot of an LLM response to a simple question using a single match from search results.":::
### Changing the inputs
-Increasing or decreasing the number of inputs to the LLM can have a large impact on the response. Try running the same query again after setting `top=3`. When you increase the inputs, the model returns different results each time, even if the query doesn't change. Here's one example of what the model returns after increasing the inputs to 3.
+Increasing or decreasing the number of inputs to the LLM can have a large effect on the response. Try running the same query again after setting `top=3`. When you increase the inputs, the model returns different results each time, even if the query doesn't change. Here's one example of what the model returns after increasing the inputs to 3.
-```
-About 71% of the earth is covered by water, while the remaining 29% is land. Canada has numerous water bodies like lakes, ponds, and streams, giving it a unique landscape. The Nunavut territory is unsuitable for agriculture due to being snow-covered most of the year and frozen during the summer thaw. Don Juan Pond in the McMurdo Dry Valleys of Antarctica is the saltiest body of water on earth with a salinity level over 40%, much higher than the Dead Sea and Great Salt Lake. It rarely snows in the valley and Don Juan's calcium chloride–rich waters rarely freeze. NASA studies our planet's physical processes, including the water cycle, carbon cycle, ocean circulation, heat movement, and light interaction. NASA has a unique vantage point of observing the earth and making sense of it from space.
-```
+:::image type="content" source="media/tutorial-rag-solution/chat-results-2.png" alt-text="Screenshot of an LLM response to a simple question using a larger result set.":::
+
+Because the model is bound to just the grounding data, the answer is larger also more vague. You can use relevance tuning to potentially generate more focused answers.
### Changing the prompt
-You can control the format of the output, tone, and whether you want the model to supplement the answer with its own training data by changing the prompt.
+You can also change the prompt to control the format of the output, tone, and whether you want the model to supplement the answer with its own training data by changing the prompt. Here's another example of LLM output if we refocus the prompt.
+
+```python
+# Provide instructions to the model
+GROUNDED_PROMPT="""
+You are an AI assistant that helps users pull facts from the source material.
+Answer the query cocisely, using bulleted points.
+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.
+Do not exceed 5 bullets.
+Query: {query}
+Sources:\n{sources}
+"""
+```
+
+Output from changing just the prompt, retaining `top=3` from the previous query, might look like this example.
+
+:::image type="content" source="media/tutorial-rag-solution/chat-results-3.png" alt-text="Screenshot of an LLM response to a change in prompt composition.":::
+
+In this tutorial, assessing the quality of the answer is subjective, but since the model is working with the same results as the previous query, the answer feels incomplete given the body of content available. Let's try the request one last time, increasing `top=10`.
+
+:::image type="content" source="media/tutorial-rag-solution/chat-results-4.png" alt-text="Screenshot of an LLM response to a simple question using top set to 10.":::
+
+There are several observations to note:
+
+- Raising the `top` value can exhaust available quota on the model. If there's no quota, an error message is returned.
+
+- Improving the relevance of the search results from Azure AI Search is the most effective approach for maximizing the utility of your LLM.
+
+In the next series of tutorials, the focus shifts to maximizing relevance and optimizing query performance for speed and concision. We revisit the schema definition and query logic to implement relevance features, but the rest of the pipeline and models remain intact.
-## Next step
+
From b1f3f966b35e82ae33a87144e668932da66372fe Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Wed, 11 Sep 2024 10:42:54 -0700
Subject: [PATCH 15/40] acrolinx fixes
---
articles/search/tutorial-rag-build-solution-index-schema.md | 6 +++---
articles/search/tutorial-rag-build-solution-pipeline.md | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/articles/search/tutorial-rag-build-solution-index-schema.md b/articles/search/tutorial-rag-build-solution-index-schema.md
index b02a7cf69c5..e8312eeb031 100644
--- a/articles/search/tutorial-rag-build-solution-index-schema.md
+++ b/articles/search/tutorial-rag-build-solution-index-schema.md
@@ -73,8 +73,8 @@ In Azure AI Search, an index that works best for RAG workloads has these qualiti
Although Azure AI Search can't join indexes, you can create indexes that preserve parent-child relationship, and then use sequential or parallel queries in your search logic to pull from both. This exercise includes templates for parent-child elements in the same index and in separate indexes, where information from the parent index is retrieved using a lookup query.
-> [!NOTE]
-> Schema design affects storage and costs. This exercise is focused on schema fundamentals. In the [Minimize storage and costs](tutorial-rag-build-solution-minimize-storage.md) tutorial, you revisit schema design to consider narrow data types, attribution, and vector configurations that offer more efficient.
+
## Create an index for RAG workloads
@@ -278,7 +278,7 @@ A minimal index for LLM is designed to store chunks of content. It typically inc
Fields in the child index:
- - id
+ - ID
- chunk
- chunkVectcor
- parent_id
diff --git a/articles/search/tutorial-rag-build-solution-pipeline.md b/articles/search/tutorial-rag-build-solution-pipeline.md
index 1ddd05d67b1..5a88e073ac3 100644
--- a/articles/search/tutorial-rag-build-solution-pipeline.md
+++ b/articles/search/tutorial-rag-build-solution-pipeline.md
@@ -46,7 +46,7 @@ If you don't have an Azure subscription, create a [free account](https://azure.m
## Provide the index schema
-Open or create a Jupyter notebook (.ipynb) in Visual Studio code to contain the scripts that comprise the pipeline. Initial steps install packages and collect variables for the connections. After you complete the set up steps, you're ready to begin with the components of the indexing pipeline.
+Open or create a Jupyter notebook (`.ipynb`) in Visual Studio Code to contain the scripts that comprise the pipeline. Initial steps install packages and collect variables for the connections. After you complete the setup steps, you're ready to begin with the components of the indexing pipeline.
Let's start with the index schema from the [previous tutorial](tutorial-rag-build-solution-index-schema.md). It's organized around vectorized and nonvectorized chunks. It includes a `locations` field that stores AI-generated content created by the skillset.
@@ -309,7 +309,7 @@ www.nasa.gov
np-2018-05-2546-hQ
```
-Try a few more queries to get a sense of what the search engine returns directly so that you can compare it with an LLM-enabled response. Re-run the previous script with this query: "how much of the earth is covered in water"?
+Try a few more queries to get a sense of what the search engine returns directly so that you can compare it with an LLM-enabled response. Rerun the previous script with this query: "how much of the earth is covered in water"?
Results from this second query should look similar to the following results, which are lightly edited for concision.
From fb10a268f88bda7122e2f9f18e10ba48c0e6a353 Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Wed, 11 Sep 2024 10:46:57 -0700
Subject: [PATCH 16/40] fixed links
---
articles/search/retrieval-augmented-generation-overview.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/articles/search/retrieval-augmented-generation-overview.md b/articles/search/retrieval-augmented-generation-overview.md
index 7f02f0c06aa..229db99076d 100644
--- a/articles/search/retrieval-augmented-generation-overview.md
+++ b/articles/search/retrieval-augmented-generation-overview.md
@@ -30,7 +30,7 @@ The decision about which information retrieval system to use is critical because
Azure AI Search is a [proven solution for information retrieval](/azure/developer/python/get-started-app-chat-template?tabs=github-codespaces) in a RAG architecture. It provides indexing and query capabilities, with the infrastructure and security of the Azure cloud. Through code and other components, you can design a comprehensive RAG solution that includes all of the elements for generative AI over your proprietary content.
> [!NOTE]
-> New to copilot and RAG concepts? Watch [Vector search and state of the art retrieval for Generative AI apps](https://ignite.microsoft.com/sessions/18618ca9-0e4d-4f9d-9a28-0bc3ef5cf54e?source=sessions).
+> New to copilot and RAG concepts? Watch [Vector search and state of the art retrieval for Generative AI apps](https://www.youtube.com/watch?v=lSzc1MJktAo).
## Approaches for RAG with Azure AI Search
From 6ccabca9771c8d2df70f009933258a72055a9413 Mon Sep 17 00:00:00 2001
From: Jon Burchel
Date: Wed, 11 Sep 2024 14:38:52 -0400
Subject: [PATCH 17/40] Update toc.yml
---
articles/ai-studio/toc.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/articles/ai-studio/toc.yml b/articles/ai-studio/toc.yml
index 3154ede0456..08ef55e4148 100644
--- a/articles/ai-studio/toc.yml
+++ b/articles/ai-studio/toc.yml
@@ -8,6 +8,7 @@ items:
href: what-is-ai-studio.md
- name: Azure AI Studio architecture
href: concepts/architecture.md
+ - name: AI Studio or AML: Which should I choose?
- name: Region support
href: reference/region-support.md
- name: Azure AI FAQ
From 3f235b6bab07ded8794411dbf813be66c1bbdaad Mon Sep 17 00:00:00 2001
From: Jon Burchel
Date: Wed, 11 Sep 2024 14:43:27 -0400
Subject: [PATCH 18/40] Updates AML TOC
---
articles/machine-learning/toc.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/articles/machine-learning/toc.yml b/articles/machine-learning/toc.yml
index 7a2ff4de7dd..1d58d1713bf 100644
--- a/articles/machine-learning/toc.yml
+++ b/articles/machine-learning/toc.yml
@@ -5,6 +5,8 @@
- name: What is Azure Machine Learning?
displayName: AML, services, overview, introduction
href: overview-what-is-azure-machine-learning.md
+ - name: AI Studio or AML: Which experience should I choose?
+ href: /ai/ai-studio-experiences-overview
- name: Azure Machine Learning CLI and Python SDK
href: concept-v2.md
# v1
From 35d809bbd301594d755c7bed341c41119858e551 Mon Sep 17 00:00:00 2001
From: Jon Burchel
Date: Wed, 11 Sep 2024 14:53:17 -0400
Subject: [PATCH 19/40] Update toc.yml
---
articles/ai-studio/toc.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/articles/ai-studio/toc.yml b/articles/ai-studio/toc.yml
index 08ef55e4148..a4a473838fd 100644
--- a/articles/ai-studio/toc.yml
+++ b/articles/ai-studio/toc.yml
@@ -8,7 +8,7 @@ items:
href: what-is-ai-studio.md
- name: Azure AI Studio architecture
href: concepts/architecture.md
- - name: AI Studio or AML: Which should I choose?
+ - name: "AI Studio or AML: Which should I choose?"
- name: Region support
href: reference/region-support.md
- name: Azure AI FAQ
From c651cc9a4767ae874b4897b26c5f9507cd28c65f Mon Sep 17 00:00:00 2001
From: Jon Burchel
Date: Wed, 11 Sep 2024 14:53:31 -0400
Subject: [PATCH 20/40] Update toc.yml
---
articles/machine-learning/toc.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/articles/machine-learning/toc.yml b/articles/machine-learning/toc.yml
index 1d58d1713bf..d210c9c88e9 100644
--- a/articles/machine-learning/toc.yml
+++ b/articles/machine-learning/toc.yml
@@ -5,7 +5,7 @@
- name: What is Azure Machine Learning?
displayName: AML, services, overview, introduction
href: overview-what-is-azure-machine-learning.md
- - name: AI Studio or AML: Which experience should I choose?
+ - name: "AI Studio or AML: Which experience should I choose?"
href: /ai/ai-studio-experiences-overview
- name: Azure Machine Learning CLI and Python SDK
href: concept-v2.md
From 9ee22123a2fe3aa561b28cd02768f905bf51b523 Mon Sep 17 00:00:00 2001
From: Jon Burchel
Date: Wed, 11 Sep 2024 15:31:17 -0400
Subject: [PATCH 21/40] Update toc.yml
---
articles/machine-learning/toc.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/articles/machine-learning/toc.yml b/articles/machine-learning/toc.yml
index d210c9c88e9..3a698ee8b68 100644
--- a/articles/machine-learning/toc.yml
+++ b/articles/machine-learning/toc.yml
@@ -5,7 +5,7 @@
- name: What is Azure Machine Learning?
displayName: AML, services, overview, introduction
href: overview-what-is-azure-machine-learning.md
- - name: "AI Studio or AML: Which experience should I choose?"
+ - name: "AI Studio or AML: Which should I choose?"
href: /ai/ai-studio-experiences-overview
- name: Azure Machine Learning CLI and Python SDK
href: concept-v2.md
From e4d306f2670bef83fe52dac8b00094f5fd6a6117 Mon Sep 17 00:00:00 2001
From: Heidi Steen <1612796+HeidiSteen@users.noreply.github.com>
Date: Wed, 11 Sep 2024 13:56:50 -0700
Subject: [PATCH 22/40] resized images, edits to query doc
---
.../tutorial-rag-solution/chat-results-1.png | Bin 42866 -> 70888 bytes
...utorial-rag-build-solution-index-schema.md | 2 +-
.../tutorial-rag-build-solution-pipeline.md | 2 +-
.../tutorial-rag-build-solution-query.md | 26 ++++++++++++------
4 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/articles/search/media/tutorial-rag-solution/chat-results-1.png b/articles/search/media/tutorial-rag-solution/chat-results-1.png
index da16cc90554028a5642417f8500c7290091926ec..3eba5857fee65e86f3e1facd6f7cdb138945f326 100644
GIT binary patch
literal 70888
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