다음을 통해 공유


빠른 시작: 전체 텍스트 검색

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색을 위한 샘플 데이터로 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Azure CLI를 설치합니다.

  • Search Service ContributorSearch Index Data Contributor 역할을 사용자 계정에 할당합니다. Azure Portal의 액세스 제어(IAM)>역할 할당 추가에서 역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

리소스 정보 검색

Azure AI 검색 서비스에서 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. Essentials 아래의 Url 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart을 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 새 콘솔 애플리케이션을 만듭니다.

    dotnet new console
    
  3. 다음을 사용하여 .NET용 Azure AI 검색 클라이언트 라이브러리(Azure.Search.Documents)를 설치합니다.

    dotnet add package Azure.Search.Documents
    
  4. Microsoft Entra ID를 사용한 권장 키 없는 인증의 경우 다음을 사용하여 Azure.Identity 패키지를 설치합니다.

    dotnet add package Azure.Identity
    
  5. 권장되는 Microsoft Entra ID를 사용한 키 없는 인증의 경우 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서는 새 콘솔 애플리케이션을 만들고 Azure AI 검색 클라이언트 라이브러리를 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가합니다. 콘솔에서 결과를 보려면 프로그램을 실행합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
  1. Program.cs에서 다음 코드를 붙여넣습니다. 검색 서비스 이름과 관리자 API 키로 serviceNameapiKey 변수를 편집합니다.

    using System;
    using Azure;
    using Azure.Identity;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
    namespace AzureSearch.Quickstart
    
    {
        class Program
        {
            static void Main(string[] args)
            {    
                // Your search service endpoint
                Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
    
                // Use the recommended keyless credential instead of the AzureKeyCredential credential.
                DefaultAzureCredential credential = new();
                //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
    
                // Create a SearchIndexClient to send create/delete index commands
                SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
    
                // Create a SearchClient to load and query documents
                string indexName = "hotels-quickstart";
                SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
                // Delete index if it exists
                Console.WriteLine("{0}", "Deleting index...\n");
                DeleteIndexIfExists(indexName, searchIndexClient);
    
                // Create index
                Console.WriteLine("{0}", "Creating index...\n");
                CreateIndex(indexName, searchIndexClient);
    
                SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
    
                // Load documents
                Console.WriteLine("{0}", "Uploading documents...\n");
                UploadDocuments(ingesterClient);
    
                // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only)
                Console.WriteLine("Waiting for indexing...\n");
                System.Threading.Thread.Sleep(2000);
    
                // Call the RunQueries method to invoke a series of queries
                Console.WriteLine("Starting queries...\n");
                RunQueries(searchClient);
    
                // End the program
                Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
                Console.ReadKey();
            }
    
            // Delete the hotels-quickstart index to reuse its name
            private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient)
            {
                searchIndexClient.GetIndexNames();
                {
                    searchIndexClient.DeleteIndex(indexName);
                }
            }
            // Create hotels-quickstart index
            private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
            {
                FieldBuilder fieldBuilder = new FieldBuilder();
                var searchFields = fieldBuilder.Build(typeof(Hotel));
    
                var definition = new SearchIndex(indexName, searchFields);
    
                var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
                definition.Suggesters.Add(suggester);
    
                searchIndexClient.CreateOrUpdateIndex(definition);
            }
    
            // Upload documents in a single Upload request.
            private static void UploadDocuments(SearchClient searchClient)
            {
                IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "1",
                            HotelName = "Stay-Kay City Hotel",
                            Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                            Category = "Boutique",
                            Tags = new[] { "view", "air conditioning", "concierge" },
                            ParkingIncluded = false,
                            LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero),
                            Rating = 3.6,
                            Address = new Address()
                            {
                                StreetAddress = "677 5th Ave",
                                City = "New York",
                                StateProvince = "NY",
                                PostalCode = "10022",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "2",
                            HotelName = "Old Century Hotel",
                            Description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                            Category = "Boutique",
                            Tags = new[] { "pool", "free wifi", "concierge" },
                            ParkingIncluded = false,
                            LastRenovationDate = new DateTimeOffset(2019, 2, 18, 0, 0, 0, TimeSpan.Zero),
                            Rating = 3.60,
                            Address = new Address()
                            {
                                StreetAddress = "140 University Town Center Dr",
                                City = "Sarasota",
                                StateProvince = "FL",
                                PostalCode = "34243",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "3",
                            HotelName = "Gastronomic Landscape Hotel",
                            Description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                            Category = "Suite",
                            Tags = new[] { "restaurant", "bar", "continental breakfast" },
                            ParkingIncluded = true,
                            LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero),
                            Rating = 4.80,
                            Address = new Address()
                            {
                                StreetAddress = "3393 Peachtree Rd",
                                City = "Atlanta",
                                StateProvince = "GA",
                                PostalCode = "30326",
                                Country = "USA"
                            }
                        }),
                    IndexDocumentsAction.Upload(
                        new Hotel()
                        {
                            HotelId = "4",
                            HotelName = "Sublime Palace Hotel",
                            Description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                            Category = "Boutique",
                            Tags = new[] { "concierge", "view", "air conditioning" },
                            ParkingIncluded = true,
                            LastRenovationDate = new DateTimeOffset(2020, 2, 06, 0, 0, 0, TimeSpan.Zero),
                            Rating = 4.60,
                            Address = new Address()
                            {
                                StreetAddress = "7400 San Pedro Ave",
                                City = "San Antonio",
                                StateProvince = "TX",
                                PostalCode = "78216",
                                Country = "USA"
                            }
                        })
                    );
    
                try
                {
                    IndexDocumentsResult result = searchClient.IndexDocuments(batch);
                }
                catch (Exception)
                {
                    // If for some reason any documents are dropped during indexing, you can compensate by delaying and
                    // retrying. This simple demo just logs the failed document keys and continues.
                    Console.WriteLine("Failed to index some of the documents: {0}");
                }
            }
    
            // Run queries, use WriteDocuments to print output
            private static void RunQueries(SearchClient searchClient)
            {
                SearchOptions options;
                SearchResults<Hotel> response;
    
                // Query 1
                Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
                options = new SearchOptions()
                {
                    IncludeTotalCount = true,
                    Filter = "",
                    OrderBy = { "" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Rating");
    
                response = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // Query 2
                Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
                options = new SearchOptions()
                {
                    Filter = "Rating gt 4",
                    OrderBy = { "Rating desc" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Rating");
    
                response = searchClient.Search<Hotel>("hotels", options);
                WriteDocuments(response);
    
                // Query 3
                Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
                options = new SearchOptions()
                {
                    SearchFields = { "Tags" }
                };
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Tags");
    
                response = searchClient.Search<Hotel>("pool", options);
                WriteDocuments(response);
    
                // Query 4 - Use Facets to return a faceted navigation structure for a given query
                // Filters are typically used with facets to narrow results on OnClick events
                Console.WriteLine("Query #4: Facet on 'Category'...\n");
    
                options = new SearchOptions()
                {
                    Filter = ""
                };
    
                options.Facets.Add("Category");
    
                options.Select.Add("HotelId");
                options.Select.Add("HotelName");
                options.Select.Add("Category");
    
                response = searchClient.Search<Hotel>("*", options);
                WriteDocuments(response);
    
                // Query 5
                Console.WriteLine("Query #5: Look up a specific document...\n");
    
                Response<Hotel> lookupResponse;
                lookupResponse = searchClient.GetDocument<Hotel>("3");
    
                Console.WriteLine(lookupResponse.Value.HotelId);
    
    
                // Query 6
                Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n");
    
                var autoresponse = searchClient.Autocomplete("sa", "sg");
                WriteDocuments(autoresponse);
    
            }
    
            // Write search results to console
            private static void WriteDocuments(SearchResults<Hotel> searchResults)
            {
                foreach (SearchResult<Hotel> result in searchResults.GetResults())
                {
                    Console.WriteLine(result.Document);
                }
    
                Console.WriteLine();
            }
    
            private static void WriteDocuments(AutocompleteResults autoResults)
            {
                foreach (AutocompleteItem result in autoResults.Results)
                {
                    Console.WriteLine(result.Text);
                }
    
                Console.WriteLine();
            }
        }
    }
    
  2. 같은 폴더에 Hotel.cs라는 이름의 새 파일을 만들고 다음 코드를 붙여넣습니다. 이 코드는 호텔 문서의 구조를 정의합니다.

    using System;
    using System.Text.Json.Serialization;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            [SimpleField(IsKey = true, IsFilterable = true)]
            public string HotelId { get; set; }
    
            [SearchableField(IsSortable = true)]
            public string HotelName { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
            public string Description { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Category { get; set; }
    
            [SearchableField(IsFilterable = true, IsFacetable = true)]
            public string[] Tags { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public bool? ParkingIncluded { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public DateTimeOffset? LastRenovationDate { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public double? Rating { get; set; }
    
            [SearchableField]
            public Address Address { get; set; }
        }
    }
    
  3. Hotel.cs라는 이름의 새 파일을 만들고 다음 코드를 붙여넣어 호텔 문서의 구조를 정의합니다. 필드의 특성은 필드가 애플리케이션에서 사용되는 방식을 결정합니다. 예를 들어 IsFilterable 특성은 필터 식을 지원하는 모든 필드에 할당해야 합니다.

    using System;
    using System.Text.Json.Serialization;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            [SimpleField(IsKey = true, IsFilterable = true)]
            public string HotelId { get; set; }
    
            [SearchableField(IsSortable = true)]
            public string HotelName { get; set; }
    
            [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
            public string Description { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Category { get; set; }
    
            [SearchableField(IsFilterable = true, IsFacetable = true)]
            public string[] Tags { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public bool? ParkingIncluded { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public DateTimeOffset? LastRenovationDate { get; set; }
    
            [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public double? Rating { get; set; }
    
            [SearchableField]
            public Address Address { get; set; }
        }
    }
    
  4. Address.cs라는 이름의 새 파일을 만들고 다음 코드를 붙여넣어 주소 문서의 구조를 정의합니다.

    using Azure.Search.Documents.Indexes;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Address
        {
            [SearchableField(IsFilterable = true)]
            public string StreetAddress { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string City { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string StateProvince { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string PostalCode { get; set; }
    
            [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
            public string Country { get; set; }
        }
    }
    
  5. Hotel.Methods.cs라는 이름의 새 파일을 만들고 다음 코드를 붙여넣어 ToString() 클래스에 대한 Hotel 재정의를 정의합니다.

    using System;
    using System.Text;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Hotel
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!String.IsNullOrEmpty(HotelId))
                {
                    builder.AppendFormat("HotelId: {0}\n", HotelId);
                }
    
                if (!String.IsNullOrEmpty(HotelName))
                {
                    builder.AppendFormat("Name: {0}\n", HotelName);
                }
    
                if (!String.IsNullOrEmpty(Description))
                {
                    builder.AppendFormat("Description: {0}\n", Description);
                }
    
                if (!String.IsNullOrEmpty(Category))
                {
                    builder.AppendFormat("Category: {0}\n", Category);
                }
    
                if (Tags != null && Tags.Length > 0)
                {
                    builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags));
                }
    
                if (ParkingIncluded.HasValue)
                {
                    builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no");
                }
    
                if (LastRenovationDate.HasValue)
                {
                    builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate);
                }
    
                if (Rating.HasValue)
                {
                    builder.AppendFormat("Rating: {0}\n", Rating);
                }
    
                if (Address != null && !Address.IsEmpty)
                {
                    builder.AppendFormat("Address: \n{0}\n", Address.ToString());
                }
    
                return builder.ToString();
            }
        }
    }
    
  6. Address.Methods.cs라는 이름의 새 파일을 만들고 다음 코드를 붙여넣어 ToString() 클래스에 대한 Address 재정의를 정의합니다.

    using System;
    using System.Text;
    using System.Text.Json.Serialization;
    
    namespace AzureSearch.Quickstart
    {
        public partial class Address
        {
            public override string ToString()
            {
                var builder = new StringBuilder();
    
                if (!IsEmpty)
                {
                    builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country);
                }
    
                return builder.ToString();
            }
    
            [JsonIgnore]
            public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) &&
                                   String.IsNullOrEmpty(City) &&
                                   String.IsNullOrEmpty(StateProvince) &&
                                   String.IsNullOrEmpty(PostalCode) &&
                                   String.IsNullOrEmpty(Country);
        }
    }
    
  7. 다음 명령을 사용하여 애플리케이션을 빌드하고 실행합니다.

    dotnet run
    

출력에는 쿼리 정보 및 결과가 추가된 Console.WriteLine의 메시지가 포함됩니다.

코드 설명

이전 섹션에서는 새 콘솔 애플리케이션을 만들고 Azure AI 검색 클라이언트 라이브러리를 설치했습니다. 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가했습니다. 콘솔에서 결과를 확인하기 위해 프로그램을 실행했습니다.

이 섹션에서는 콘솔 애플리케이션에 추가한 코드에 대해 설명합니다.

검색 클라이언트 만들기

Program.cs에서 두 개의 클라이언트를 만들었습니다.

두 클라이언트 모두 이전에 리소스 정보 섹션에서 설명한 검색 서비스 엔드포인트와 자격 증명이 필요합니다.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
    // Your search service endpoint
    Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);

    // Create a SearchClient to load and query documents
    string indexName = "hotels-quickstart";
    SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
    
    // REDACTED FOR BREVITY . . . 
}

인덱스 만들기

이 빠른 시작에서는 호텔 데이터를 로드하고 쿼리를 실행할 수 있는 호텔 인덱스를 빌드합니다. 이 단계에서는 인덱스의 필드를 정의합니다. 각 필드 정의에는 이름, 데이터 형식 및 필드가 사용되는 방식을 결정하는 특성이 포함됩니다.

이 예제에서는 간단한 설명과 가독성을 위해 Azure.Search.Documents 라이브러리의 동기 메서드를 사용합니다. 그러나 프로덕션 시나리오의 경우 비동기 메서드를 사용하여 앱의 확장성 및 응답성을 유지해야 합니다. 예를 들어 CreateIndex 대신 CreateIndexAsync를 사용해야 합니다.

구조 정의

호텔 문서의 구조와 주소를 정의하기 위해 Hotel.csAddress.cs라는 두 개의 도우미 클래스를 만들었습니다. Hotel 클래스에는 호텔 ID, 이름, 설명, 범주, 태그, 주차, 리노베이션 날짜, 평점 및 주소에 대한 필드가 포함되어 있습니다. Address 클래스에는 도로명 주소, 도시, 주/도, 우편 번호, 국가/지역에 대한 필드가 포함되어 있습니다.

Azure.Search.Documents 클라이언트 라이브러리에서 SearchableFieldSimpleField를 사용하여 필드 정의를 간소화할 수 있습니다. 둘 다 SearchField의 파생물이며 잠재적으로 코드를 단순화할 수 있습니다.

  • SimpleField는 모든 데이터 형식이 될 수 있으며, 항상 쿼리할 수 없으며(전체 텍스트 검색 쿼리의 경우 무시됨) 쿼리할 수 있습니다(숨겨지지 않음). 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 필터, 패싯 또는 점수 매기기 프로필에만 사용되는 문서 ID 또는 필드에는 SimpleField를 사용할 수 있습니다. 그렇다면 문서 ID에 대한 IsKey = true와 같이 시나리오에 필요한 특성을 적용해야 합니다. 자세한 내용은 소스 코드의 SimpleFieldAttribute.cs를 참조하세요.

  • SearchableField는 문자열이어야 하며, 항상 검색할 수 있고 조회할 수 있습니다. 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 이 필드 형식은 검색할 수 있으므로 동의어와 분석기 속성의 전체 보충을 지원합니다. 자세한 내용은 소스 코드의 SearchableFieldAttribute.cs를 참조하세요.

기본 SearchField API를 사용하든지 도우미 모델 중 하나를 사용하든지 간에 필터, 패싯 및 정렬 특성을 명시적으로 사용하도록 설정해야 합니다. 예를 들어, IsFilterable, IsSortableIsFacetable은 이전 샘플에 표시된 대로 명시적으로 특성이 지정되어야 합니다.

검색 인덱스 만들기

Program.cs에서 SearchIndex 개체를 만든 다음 CreateIndex 메서드를 호출하여 검색 서비스의 인덱스를 표현합니다. 인덱스에는 지정된 필드에서 자동 완성을 사용하도록 설정하는 SearchSuggester도 포함되어 있습니다.

// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
    FieldBuilder fieldBuilder = new FieldBuilder();
    var searchFields = fieldBuilder.Build(typeof(Hotel));

    var definition = new SearchIndex(indexName, searchFields);

    var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
    definition.Suggesters.Add(suggester);

    searchIndexClient.CreateOrUpdateIndex(definition);
}

문서 로드

Azure AI 검색은 서비스에 저장된 콘텐츠를 검색합니다. 이 단계에서는 만든 호텔 인덱스에 맞는 JSON 문서를 로드합니다.

Azure AI 검색에서 검색 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 외부 데이터 소스에서 가져온, 문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 디스크의 JSON 문서일 수 있습니다. 이 예에서는 손쉬운 방법을 사용하여 4개 호텔에 대한 JSON 문서를 코드 자체에 포함합니다.

문서를 업로드할 때 IndexDocumentsBatch 개체를 사용해야 합니다. IndexDocumentsBatch 개체에는 Actions 컬렉션이 포함되며 컬렉션마다 수행할 작업(upload, merge, delete 및 mergeOrUpload)을 Azure AI 검색에 알려주는 속성과 문서가 포함됩니다.

Program.cs에서 문서와 인덱스 작업의 배열을 만든 다음 해당 배열을 IndexDocumentsBatch에 전달합니다. 다음 문서는 호텔 클래스에 정의된 호텔-빠른 시작 인덱스를 따릅니다.

// Upload documents in a single Upload request.
private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "1",
                HotelName = "Stay-Kay City Hotel",
                Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                Category = "Boutique",
                Tags = new[] { "view", "air conditioning", "concierge" },
                ParkingIncluded = false,
                LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero),
                Rating = 3.6,
                Address = new Address()
                {
                    StreetAddress = "677 5th Ave",
                    City = "New York",
                    StateProvince = "NY",
                    PostalCode = "10022",
                    Country = "USA"
                }
            }),
        // REDACTED FOR BREVITY
}

IndexDocumentsBatch 개체를 초기화 한 후에는 SearchClient 개체에서 IndexDocuments를 호출하여 이 개체를 인덱스에 전송할 수 있습니다.

Main()에서 SearchClient를 사용하여 문서를 로드하지만, 이 작업에는 일반적으로 SearchIndexClient와 관련된 서비스에 대한 관리자 권한도 필요합니다. 이 작업을 설정하는 한 가지 방법은 SearchIndexClient(이 예에서는 searchIndexClient)을 통해 SearchClient를 가져오는 것입니다.

SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);

// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);

모든 명령을 순차적으로 실행하는 콘솔 앱이 있으므로 인덱싱과 쿼리 사이에 2초의 대기 시간을 추가합니다.

// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
Console.WriteLine("Waiting for indexing...\n");
System.Threading.Thread.Sleep(2000);

2초 지연은 비동기식 인덱싱을 보정합니다. 따라서 쿼리가 실행되기 전에 모든 문서를 인덱싱할 수 있습니다. 지연 시 코딩은 일반적으로 데모, 테스트, 샘플 애플리케이션에서만 필요합니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

이 섹션에서는 쿼리 논리 및 결과라는 두 가지 기능을 추가합니다. 쿼리에는 Search 메서드를 사용합니다. 이 메서드는 검색 텍스트(쿼리 문자열)뿐 아니라 다른 옵션을 사용합니다.

SearchResults 클래스는 결과를 나타냅니다.

Program.cs에서 WriteDocuments 메서드는 검색 결과를 콘솔에 출력합니다.

// Write search results to console
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

private static void WriteDocuments(AutocompleteResults autoResults)
{
    foreach (AutocompleteItem result in autoResults.Results)
    {
        Console.WriteLine(result.Text);
    }

    Console.WriteLine();
}

쿼리 예 1

RunQueries 메서드는 쿼리를 실행하고 결과를 반환합니다. 결과는 Hotel 개체입니다. 이 샘플에서는 메서드 서명과 첫 번째 쿼리를 보여줍니다. 이 쿼리는 문서에서 선택한 필드를 사용하여 결과를 작성할 수 있도록 하는 Select 매개 변수를 보여 줍니다.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    SearchOptions options;
    SearchResults<Hotel> response;
    
    // Query 1
    Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");

    options = new SearchOptions()
    {
        IncludeTotalCount = true,
        Filter = "",
        OrderBy = { "" }
    };

    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Address/City");

    response = searchClient.Search<Hotel>("*", options);
    WriteDocuments(response);
    // REDACTED FOR BREVITY
}

쿼리 예 2

두 번째 쿼리에서는 용어를 검색하고 등급이 4보다 큰 문서를 선택하는 필터를 추가한 다음, 등급별로 내림차순으로 정렬합니다. 필터는 인덱스의 IsFilterable 필드를 통해 평가되는 부울 식입니다. 필터는 포함 또는 제외 값을 쿼리합니다. 따라서 필터 쿼리와 관련된 관련성 점수가 없습니다.

// Query 2
Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");

options = new SearchOptions()
{
    Filter = "Rating gt 4",
    OrderBy = { "Rating desc" }
};

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Rating");

response = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);

쿼리 예 3

세 번째 쿼리는 전체 텍스트 검색 작업의 범위를 특정 필드로 지정하는 데 사용되는 searchFields를 보여 줍니다.

// Query 3
Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");

options = new SearchOptions()
{
    SearchFields = { "Tags" }
};

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Tags");

response = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);

쿼리 예 4

네 번째 쿼리는 facets를 보여 줍니다. 이는 패싯 탐색 구조를 구성하는 데 사용할 수 있습니다.

// Query 4
Console.WriteLine("Query #4: Facet on 'Category'...\n");

options = new SearchOptions()
{
    Filter = ""
};

options.Facets.Add("Category");

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Category");

response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);

쿼리 예 5

다섯 번째 쿼리에서 특정 문서를 반환합니다. 문서 조회는 결과 집합의 OnClick 이벤트에 대한 일반적인 응답입니다.

// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");

Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");

Console.WriteLine(lookupResponse.Value.HotelId);

쿼리 예 6

마지막 쿼리는 인덱스에서 정의한 제안기와 연결된 sourceFields에서 가능한 두 개의 일치 항목으로 확인되는 sa의 부분 사용자 입력을 시뮬레이션하는 자동 구문을 보여줍니다.

// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");

var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 SearchClient.Search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열로 전달할 수 있는 반면, 필터 식은 SearchOptions 클래스의 Filter 속성으로 전달할 수 있습니다. 검색하지 않고 필터링하려면 "*" 메서드의 searchText 매개 변수에 대한 를 전달합니다. 필터링하지 않고 검색하려면 Filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색을 위한 샘플 데이터로 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Azure CLI를 설치합니다.

  • Search Service ContributorSearch Index Data Contributor 역할을 사용자 계정에 할당합니다. Azure Portal의 액세스 제어(IAM)>역할 할당 추가에서 역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

리소스 정보 검색

Azure AI 검색 서비스에서 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. Essentials 아래의 Url 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증환경 변수 설정에 대해 자세히 알아봅니다.

설정

이 빠른 시작의 샘플은 Java 런타임에서 작동합니다. Azul Zulu OpenJDK와 같은 Java 개발 키트를 설치해야 합니다. OpenJDK의 Microsoft 빌드 또는 선호하는 JDK도 작동해야 합니다.

  1. Apache Maven을 설치합니다. 그런 다음 mvn -v을(를) 실행하여 성공적인 설치를 확인합니다.

  2. pom.xml 파일을 프로젝트의 루트에 만들고, 다음 코드를 복사합니다.

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>azure.search.sample</groupId>
        <artifactId>azuresearchquickstart</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <build>
            <sourceDirectory>src</sourceDirectory>
            <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-search-documents</artifactId>
                <version>11.7.3</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-core</artifactId>
                <version>1.53.0</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.15.1</version>
            </dependency>
        </dependencies>
    </project>
    
  3. 다음을 사용하여 Java용 Azure AI 검색 클라이언트 라이브러리(Azure.Search.Documents) 및 Java용 Azure ID 클라이언트 라이브러리를 포함한 종속성을 설치합니다.

    mvn clean dependency:copy-dependencies
    
  4. 권장되는 Microsoft Entra ID를 사용한 키 없는 인증의 경우 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI 검색 클라이언트 라이브러리와 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가합니다. 콘솔에서 결과를 보려면 프로그램을 실행합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. App.java라는 이름의 새 파일을 만들고 다음 코드를 App.java에 붙여넣습니다.

    import java.util.Arrays;
    import java.util.ArrayList;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    import java.time.LocalDateTime;
    import java.time.LocalDate;
    import java.time.LocalTime;
    import com.azure.core.util.Configuration;
    import com.azure.core.util.Context;
    import com.azure.identity.DefaultAzureCredential;
    import com.azure.identity.DefaultAzureCredentialBuilder;
    import com.azure.search.documents.SearchClient;
    import com.azure.search.documents.SearchClientBuilder;
    import com.azure.search.documents.indexes.SearchIndexClient;
    import com.azure.search.documents.indexes.SearchIndexClientBuilder;
    import com.azure.search.documents.indexes.models.IndexDocumentsBatch;
    import com.azure.search.documents.models.SearchOptions;
    import com.azure.search.documents.indexes.models.SearchIndex;
    import com.azure.search.documents.indexes.models.SearchSuggester;
    import com.azure.search.documents.util.AutocompletePagedIterable;
    import com.azure.search.documents.util.SearchPagedIterable;
    
    public class App {
    
        public static void main(String[] args) {
            // Your search service endpoint
            "https://<Put your search service NAME here>.search.windows.net/";
    
            // Use the recommended keyless credential instead of the AzureKeyCredential credential.
            DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
            //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>");
    
            // Create a SearchIndexClient to send create/delete index commands
            SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .buildClient();
    
            // Create a SearchClient to load and query documents
            String indexName = "hotels-quickstart-java";
            SearchClient searchClient = new SearchClientBuilder()
                .endpoint(searchServiceEndpoint)
                .credential(credential)
                .indexName(indexName)
                .buildClient();
    
            // Create Search Index for Hotel model
            searchIndexClient.createOrUpdateIndex(
                new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
                .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
    
            // Upload sample hotel documents to the Search Index
            uploadDocuments(searchClient);
    
            // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
            System.out.println("Waiting for indexing...\n");
            try
            {
                Thread.sleep(2000);
            }
            catch (InterruptedException e)
            {
            }
    
            // Call the RunQueries method to invoke a series of queries
            System.out.println("Starting queries...\n");
            RunQueries(searchClient);
    
            // End the program
            System.out.println("Complete.\n");
        }
    
        // Upload documents in a single Upload request.
        private static void uploadDocuments(SearchClient searchClient)
        {
            var hotelList = new ArrayList<Hotel>();
    
            var hotel = new Hotel();
            hotel.hotelId = "1";
            hotel.hotelName = "Stay-Kay City Hotel";
            hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "view", "air conditioning", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.6;
            hotel.address = new Address();
            hotel.address.streetAddress = "677 5th Ave";
            hotel.address.city = "New York";
            hotel.address.stateProvince = "NY";
            hotel.address.postalCode = "10022";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "2";
            hotel.hotelName = "Old Century Hotel";
            hotel.description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
            hotel.category = "Boutique";
            hotel.tags = new String[] { "pool", "free wifi", "concierge" };
            hotel.parkingIncluded = false;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2019, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 3.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "140 University Town Center Dr";
            hotel.address.city = "Sarasota";
            hotel.address.stateProvince = "FL";
            hotel.address.postalCode = "34243";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "3";
            hotel.hotelName = "Gastronomic Landscape Hotel";
            hotel.description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.";
            hotel.category = "Suite";
            hotel.tags = new String[] { "restaurant", "bar", "continental breakfast" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.80;
            hotel.address = new Address();
            hotel.address.streetAddress = "3393 Peachtree Rd";
            hotel.address.city = "Atlanta";
            hotel.address.stateProvince = "GA";
            hotel.address.postalCode = "30326";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            hotel = new Hotel();
            hotel.hotelId = "4";
            hotel.hotelName = "Sublime Palace Hotel";
            hotel.description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.";
            hotel.category = "Boutique";
            hotel.tags = new String[] { "concierge", "view", "air conditioning" };
            hotel.parkingIncluded = true;
            hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2020, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC);
            hotel.rating = 4.60;
            hotel.address = new Address();
            hotel.address.streetAddress = "7400 San Pedro Ave";
            hotel.address.city = "San Antonio";
            hotel.address.stateProvince = "TX";
            hotel.address.postalCode = "78216";
            hotel.address.country = "USA";
            hotelList.add(hotel);
    
            var batch = new IndexDocumentsBatch<Hotel>();
            batch.addMergeOrUploadActions(hotelList);
            try
            {
                searchClient.indexDocuments(batch);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                // If for some reason any documents are dropped during indexing, you can compensate by delaying and
                // retrying. This simple demo just logs failure and continues
                System.err.println("Failed to index some of the documents");
            }
        }
    
        // Write search results to console
        private static void WriteSearchResults(SearchPagedIterable searchResults)
        {
            searchResults.iterator().forEachRemaining(result ->
            {
                Hotel hotel = result.getDocument(Hotel.class);
                System.out.println(hotel);
            });
    
            System.out.println();
        }
    
        // Write autocomplete results to console
        private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
        {
            autocompleteResults.iterator().forEachRemaining(result ->
            {
                String text = result.getText();
                System.out.println(text);
            });
    
            System.out.println();
        }
    
        // Run queries, use WriteDocuments to print output
        private static void RunQueries(SearchClient searchClient)
        {
            // Query 1
            System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
            SearchOptions options = new SearchOptions();
            options.setIncludeTotalCount(true);
            options.setFilter("");
            options.setOrderBy("");
            options.setSelect("HotelId", "HotelName", "Address/City");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 2
            System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
            options = new SearchOptions();
            options.setFilter("Rating gt 4");
            options.setOrderBy("Rating desc");
            options.setSelect("HotelId", "HotelName", "Rating");
    
            WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
    
            // Query 3
            System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
            options = new SearchOptions();
            options.setSearchFields("Tags");
    
            options.setSelect("HotelId", "HotelName", "Tags");
    
            WriteSearchResults(searchClient.search("pool", options, Context.NONE));
    
            // Query 4
            System.out.println("Query #4: Facet on 'Category'...\n");
    
            options = new SearchOptions();
            options.setFilter("");
            options.setFacets("Category");
            options.setSelect("HotelId", "HotelName", "Category");
    
            WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
            // Query 5
            System.out.println("Query #5: Look up a specific document...\n");
    
            Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
            System.out.println(lookupResponse.hotelId);
            System.out.println();
    
             // Query 6
            System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
    
            WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
        }
    }
    
  2. Hotel.java라는 이름의 새 파일을 만들고 다음 코드를 Hotel.java에 붙여넣습니다.

    import com.azure.search.documents.indexes.SearchableField;
    import com.azure.search.documents.indexes.SimpleField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    import java.time.OffsetDateTime;
    
    /**
     * Model class representing a hotel.
     */
    @JsonInclude(Include.NON_NULL)
    public class Hotel {
        /**
         * Hotel ID
         */
        @JsonProperty("HotelId")
        @SimpleField(isKey = true)
        public String hotelId;
    
        /**
         * Hotel name
         */
        @JsonProperty("HotelName")
        @SearchableField(isSortable = true)
        public String hotelName;
    
        /**
         * Description
         */
        @JsonProperty("Description")
        @SearchableField(analyzerName = "en.microsoft")
        public String description;
    
        /**
         * Category
         */
        @JsonProperty("Category")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String category;
    
        /**
         * Tags
         */
        @JsonProperty("Tags")
        @SearchableField(isFilterable = true, isFacetable = true)
        public String[] tags;
    
        /**
         * Whether parking is included
         */
        @JsonProperty("ParkingIncluded")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Boolean parkingIncluded;
    
        /**
         * Last renovation time
         */
        @JsonProperty("LastRenovationDate")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public OffsetDateTime lastRenovationDate;
    
        /**
         * Rating
         */
        @JsonProperty("Rating")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Double rating;
    
        /**
         * Address
         */
        @JsonProperty("Address")
        public Address address;
    
        @Override
        public String toString()
        {
            try
            {
                return new ObjectMapper().writeValueAsString(this);
            }
            catch (JsonProcessingException e)
            {
                e.printStackTrace();
                return "";
            }
        }
    }
    
  3. Address.java라는 이름의 새 파일을 만들고 다음 코드를 Address.java에 붙여넣습니다.

    import com.azure.search.documents.indexes.SearchableField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    /**
     * Model class representing an address.
     */
    @JsonInclude(Include.NON_NULL)
    public class Address {
        /**
         * Street address
         */
        @JsonProperty("StreetAddress")
        @SearchableField
        public String streetAddress;
    
        /**
         * City
         */
        @JsonProperty("City")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String city;
    
        /**
         * State or province
         */
        @JsonProperty("StateProvince")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String stateProvince;
    
        /**
         * Postal code
         */
        @JsonProperty("PostalCode")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String postalCode;
    
        /**
         * Country
         */
        @JsonProperty("Country")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String country;
    }
    
  4. 새 콘솔 애플리케이션 실행:

    javac Address.java App.java Hotel.java -cp ".;target\dependency\*"
    java -cp ".;target\dependency\*" App
    

코드 설명

이전 섹션에서는 새 콘솔 애플리케이션을 만들고 Azure AI 검색 클라이언트 라이브러리를 설치했습니다. 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가했습니다. 콘솔에서 결과를 확인하기 위해 프로그램을 실행했습니다.

이 섹션에서는 콘솔 애플리케이션에 추가한 코드에 대해 설명합니다.

검색 클라이언트 만들기

App.java에서 두 개의 클라이언트를 만들었습니다.

  • SearchIndexClient가 인덱스를 만듭니다.
  • SearchClient는 기존 인덱스를 로드하고 쿼리합니다.

두 클라이언트 모두 이전에 리소스 정보 섹션에서 설명한 검색 서비스 엔드포인트와 자격 증명이 필요합니다.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
    // Your search service endpoint
    String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";

    // Use the recommended keyless credential instead of the AzureKeyCredential credential.
    DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
    //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");

    // Create a SearchIndexClient to send create/delete index commands
    SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .buildClient();
    
    // Create a SearchClient to load and query documents
    String indexName = "hotels-quickstart-java";
    SearchClient searchClient = new SearchClientBuilder()
        .endpoint(searchServiceEndpoint)
        .credential(credential)
        .indexName(indexName)
        .buildClient();

    // Create Search Index for Hotel model
    searchIndexClient.createOrUpdateIndex(
        new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
        .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

    // REDACTED FOR BREVITY . . . 
}

인덱스 만들기

이 빠른 시작에서는 호텔 데이터를 로드하고 쿼리를 실행할 수 있는 호텔 인덱스를 빌드합니다. 이 단계에서는 인덱스의 필드를 정의합니다. 각 필드 정의에는 이름, 데이터 형식 및 필드가 사용되는 방식을 결정하는 특성이 포함됩니다.

이 예제에서는 간단한 설명과 가독성을 위해 Azure.Search.Documents 라이브러리의 동기 메서드를 사용합니다. 그러나 프로덕션 시나리오의 경우 비동기 메서드를 사용하여 앱의 확장성 및 응답성을 유지해야 합니다. 예를 들어 CreateIndex 대신 CreateIndexAsync를 사용해야 합니다.

구조 정의

호텔 문서의 구조와 주소를 정의하기 위해 Hotel.javaAddress.java라는 두 개의 도우미 클래스를 만들었습니다. 호텔 클래스에는 호텔 ID, 이름, 설명, 범주, 태그, 주차, 리노베이션 날짜, 평점 및 주소에 대한 필드가 포함되어 있습니다. 주소 클래스에는 도로명 주소, 도시, 주/도, 우편 번호, 국가/지역에 대한 필드가 포함되어 있습니다.

Azure.Search.Documents 클라이언트 라이브러리에서 SearchableFieldSimpleField를 사용하여 필드 정의를 간소화할 수 있습니다.

  • SimpleField는 모든 데이터 형식이 될 수 있으며, 항상 쿼리할 수 없으며(전체 텍스트 검색 쿼리의 경우 무시됨) 쿼리할 수 있습니다(숨겨지지 않음). 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 필터, 패싯 또는 점수 매기기 프로필에만 사용되는 문서 ID 또는 필드에는 SimpleField를 사용할 수 있습니다. 그렇다면 문서 ID에 대한 IsKey = true와 같이 시나리오에 필요한 특성을 적용해야 합니다.
  • SearchableField는 문자열이어야 하며, 항상 검색할 수 있고 조회할 수 있습니다. 다른 특성은 기본적으로 꺼져 있지만 사용하도록 설정할 수 있습니다. 이 필드 형식은 검색할 수 있으므로 동의어와 분석기 속성의 전체 보충을 지원합니다.

기본 SearchField API를 사용하든지 도우미 모델 중 하나를 사용하든지 간에 필터, 패싯 및 정렬 특성을 명시적으로 사용하도록 설정해야 합니다. 예를 들어, isFilterable, isSortable, isFacetable은 이전 샘플에서 표시된 대로 명시적으로 특성이 지정되어야 합니다.

검색 인덱스 만들기

App.java에서 SearchIndex 메서드에서 main 개체를 만든 다음 createOrUpdateIndex 메서드를 호출하여 검색 서비스에 인덱스를 만듭니다. 인덱스에는 지정된 필드에서 자동 완성을 사용하도록 설정하는 SearchSuggester도 포함되어 있습니다.

// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
    new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
    .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));

문서 로드

Azure AI 검색은 서비스에 저장된 콘텐츠를 검색합니다. 이 단계에서는 만든 호텔 인덱스에 맞는 JSON 문서를 로드합니다.

Azure AI 검색에서 검색 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 외부 데이터 소스에서 가져온, 문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 디스크의 JSON 문서일 수 있습니다. 이 예에서는 손쉬운 방법을 사용하여 4개 호텔에 대한 JSON 문서를 코드 자체에 포함합니다.

문서를 업로드할 때 IndexDocumentsBatch 개체를 사용해야 합니다. IndexDocumentsBatch 개체에는 IndexActions 컬렉션이 포함되며 컬렉션마다 수행할 작업(upload, merge, delete 및 mergeOrUpload)을 Azure AI 검색에 알려주는 속성과 문서가 포함됩니다.

App.java에서 문서를 만들고 작업을 인덱싱한 다음 이를 IndexDocumentsBatch에 전달합니다. 다음 문서는 호텔 클래스에 정의된 호텔-빠른 시작 인덱스를 따릅니다.

private static void uploadDocuments(SearchClient searchClient)
{
    var hotelList = new ArrayList<Hotel>();

    var hotel = new Hotel();
    hotel.hotelId = "1";
    hotel.hotelName = "Stay-Kay City Hotel";
    hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
    hotel.category = "Boutique";
    hotel.tags = new String[] { "view", "air conditioning", "concierge" };
    hotel.parkingIncluded = false;
    hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
    hotel.rating = 3.6;
    hotel.address = new Address();
    hotel.address.streetAddress = "677 5th Ave";
    hotel.address.city = "New York";
    hotel.address.stateProvince = "NY";
    hotel.address.postalCode = "10022";
    hotel.address.country = "USA";
    hotelList.add(hotel);
    
    // REDACTED FOR BREVITY

    var batch = new IndexDocumentsBatch<Hotel>();
    batch.addMergeOrUploadActions(hotelList);
    try
    {
        searchClient.indexDocuments(batch);
    }
    catch (Exception e)
    {
        e.printStackTrace();
        // If for some reason any documents are dropped during indexing, you can compensate by delaying and
        // retrying. This simple demo just logs failure and continues
        System.err.println("Failed to index some of the documents");
    }
}

IndexDocumentsBatch 개체를 초기화하면 개체에서 SearchClient를 호출하여 인덱스로 보낼 수 있습니다.

main()에서 SearchClient를 사용하여 문서를 로드하지만, 이 작업에는 일반적으로 SearchIndexClient와 관련된 서비스에 대한 관리자 권한도 필요합니다. 이 작업을 설정하는 한 가지 방법은 SearchIndexClient(이 예에서는 searchIndexClient)을 통해 SearchClient를 가져오는 것입니다.

uploadDocuments(searchClient);

모든 명령을 순차적으로 실행하는 콘솔 앱이 있으므로 인덱싱과 쿼리 사이에 2초의 대기 시간을 추가합니다.

// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
    Thread.sleep(2000);
}
catch (InterruptedException e)
{
}

2초 지연은 비동기식 인덱싱을 보정합니다. 따라서 쿼리가 실행되기 전에 모든 문서를 인덱싱할 수 있습니다. 지연 시 코딩은 일반적으로 데모, 테스트, 샘플 애플리케이션에서만 필요합니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

이 섹션에서는 쿼리 논리 및 결과라는 두 가지 기능을 추가합니다. 쿼리에는 검색 메서드를 사용합니다. 이 메서드는 검색 텍스트(쿼리 문자열)과 다른 옵션을 사용합니다.

App.java에서 WriteDocuments 메서드는 검색 결과를 콘솔에 출력합니다.

// Write search results to console
private static void WriteSearchResults(SearchPagedIterable searchResults)
{
    searchResults.iterator().forEachRemaining(result ->
    {
        Hotel hotel = result.getDocument(Hotel.class);
        System.out.println(hotel);
    });

    System.out.println();
}

// Write autocomplete results to console
private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
{
    autocompleteResults.iterator().forEachRemaining(result ->
    {
        String text = result.getText();
        System.out.println(text);
    });

    System.out.println();
}

쿼리 예 1

RunQueries 메서드는 쿼리를 실행하고 결과를 반환합니다. 결과는 Hotel 개체입니다. 이 샘플에서는 메서드 서명과 첫 번째 쿼리를 보여줍니다. 이 쿼리는 문서에서 선택한 필드를 사용하여 결과를 작성할 수 있도록 하는 Select 매개 변수를 보여 줍니다.

// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
    // Query 1
    System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");

    SearchOptions options = new SearchOptions();
    options.setIncludeTotalCount(true);
    options.setFilter("");
    options.setOrderBy("");
    options.setSelect("HotelId", "HotelName", "Address/City");

    WriteSearchResults(searchClient.search("*", options, Context.NONE));
}

쿼리 예 2

두 번째 쿼리에서는 용어를 검색하고 등급이 4보다 큰 문서를 선택하는 필터를 추가한 다음, 등급별로 내림차순으로 정렬합니다. 필터는 인덱스의 isFilterable 필드를 통해 평가되는 부울 식입니다. 필터는 포함 또는 제외 값을 쿼리합니다. 따라서 필터 쿼리와 관련된 관련성 점수가 없습니다.

// Query 2
System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");

options = new SearchOptions();
options.setFilter("Rating gt 4");
options.setOrderBy("Rating desc");
options.setSelect("HotelId", "HotelName", "Rating");

WriteSearchResults(searchClient.search("hotels", options, Context.NONE));

쿼리 예 3

세 번째 쿼리는 전체 텍스트 검색 작업의 범위를 특정 필드로 지정하는 데 사용되는 searchFields를 보여 줍니다.

// Query 3
System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");

options = new SearchOptions();
options.setSearchFields("Tags");

options.setSelect("HotelId", "HotelName", "Tags");

WriteSearchResults(searchClient.search("pool", options, Context.NONE));

쿼리 예 4

네 번째 쿼리는 facets를 보여 줍니다. 이는 패싯 탐색 구조를 구성하는 데 사용할 수 있습니다.

// Query 4
System.out.println("Query #4: Facet on 'Category'...\n");

options = new SearchOptions();
options.setFilter("");
options.setFacets("Category");
options.setSelect("HotelId", "HotelName", "Category");

WriteSearchResults(searchClient.search("*", options, Context.NONE));

쿼리 예 5

다섯 번째 쿼리에서 특정 문서를 반환합니다.

// Query 5
System.out.println("Query #5: Look up a specific document...\n");

Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
System.out.println(lookupResponse.hotelId);
System.out.println();

쿼리 예 6

마지막 쿼리는 인덱스에서 정의한 제안기와 연결된 에서 가능한 두 개의 일치 항목으로 확인되는 sourceFields의 부분 사용자 입력을 시뮬레이션하는 자동 구문을 보여줍니다.

// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");

WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 SearchClient.search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열로 전달할 수 있는 반면, 필터 식은 filter 클래스의 속성으로 전달할 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색을 위한 샘플 데이터로 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Azure CLI를 설치합니다.

  • Search Service ContributorSearch Index Data Contributor 역할을 사용자 계정에 할당합니다. Azure Portal의 액세스 제어(IAM)>역할 할당 추가에서 역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

리소스 정보 검색

Azure AI 검색 서비스에서 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. Essentials 아래의 Url 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart을 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 package.json을 만듭니다.

    npm init -y
    
  3. JavaScript용 Azure AI 검색 클라이언트 라이브러리(Azure.Search.Documents)를 설치하려면 다음을 사용합니다.

    npm install @azure/search-documents
    
  4. 권장 암호 없는 인증의 경우 다음을 사용하여 Azure ID 클라이언트 라이브러리를 설치합니다.

    npm install @azure/identity
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI 검색 클라이언트 라이브러리와 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가합니다. 콘솔에서 결과를 보려면 프로그램을 실행합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
  1. index.js라는 이름의 새 파일을 만들고 다음 코드를 index.js에 붙여넣습니다.

    // Import from the @azure/search-documents library
    import { SearchIndexClient, odata } from "@azure/search-documents";
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    // Defining the index definition
    const indexDefinition = {
        "name": "hotels-quickstart",
        "fields": [
            {
                "name": "HotelId",
                "type": "Edm.String",
                "key": true,
                "filterable": true
            },
            {
                "name": "HotelName",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": true,
                "facetable": false
            },
            {
                "name": "Description",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "en.lucene"
            },
            {
                "name": "Description_fr",
                "type": "Edm.String",
                "searchable": true,
                "filterable": false,
                "sortable": false,
                "facetable": false,
                "analyzerName": "fr.lucene"
            },
            {
                "name": "Category",
                "type": "Edm.String",
                "searchable": true,
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Tags",
                "type": "Collection(Edm.String)",
                "searchable": true,
                "filterable": true,
                "sortable": false,
                "facetable": true
            },
            {
                "name": "ParkingIncluded",
                "type": "Edm.Boolean",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "LastRenovationDate",
                "type": "Edm.DateTimeOffset",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Rating",
                "type": "Edm.Double",
                "filterable": true,
                "sortable": true,
                "facetable": true
            },
            {
                "name": "Address",
                "type": "Edm.ComplexType",
                "fields": [
                    {
                        "name": "StreetAddress",
                        "type": "Edm.String",
                        "filterable": false,
                        "sortable": false,
                        "facetable": false,
                        "searchable": true
                    },
                    {
                        "name": "City",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "StateProvince",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "PostalCode",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    },
                    {
                        "name": "Country",
                        "type": "Edm.String",
                        "searchable": true,
                        "filterable": true,
                        "sortable": true,
                        "facetable": true
                    }
                ]
            }
        ],
        "suggesters": [
            {
                "name": "sg",
                "searchMode": "analyzingInfixMatching",
                "sourceFields": [
                    "HotelName"
                ]
            }
        ]
    };
    async function main() {
        // Your search service endpoint
        const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
        // Use the recommended keyless credential instead of the AzureKeyCredential credential.
        const credential = new DefaultAzureCredential();
        //const credential = new AzureKeyCredential(Your search service admin key);
        // Create a SearchIndexClient to send create/delete index commands
        const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential);
        // Creating a search client to upload documents and issue queries
        const indexName = "hotels-quickstart";
        const searchClient = searchIndexClient.getSearchClient(indexName);
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
        console.log('Creating index...');
        let index = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    async function deleteIndexIfExists(searchIndexClient, indexName) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        }
        catch {
            console.log('Index does not exist yet.');
        }
    }
    async function sendQueries(searchClient) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata `Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("sublime palace", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. hotels.json이라는 이름의 파일을 만들고 다음 코드를 hotels.json에 붙여넣습니다.

    {
        "value": [
            {
                "HotelId": "1",
                "HotelName": "Stay-Kay City Hotel",
                "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                "Category": "Boutique",
                "Tags": ["view", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "2022-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "HotelId": "2",
                "HotelName": "Old Century Hotel",
                "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                "Category": "Boutique",
                "Tags": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "2019-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "HotelId": "3",
                "HotelName": "Gastronomic Landscape Hotel",
                "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                "Category": "Suite",
                "Tags": ["restaurant", "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "HotelId": "4",
                "HotelName": "Sublime Palace Hotel",
                "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                "Category": "Boutique",
                "Tags": ["concierge", "view", "air conditioning"],
                "ParkingIncluded": true,
                "LastRenovationDate": "2020-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. hotels_quickstart_index.json이라는 이름의 파일을 만들고 다음 코드를 hotels_quickstart_index.json에 붙여넣습니다.

    {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String",
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String",
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String",
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String",
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    }
    
  4. 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    
  5. 다음 명령으로 JavaScript 코드를 실행합니다.

    node index.js
    

코드 설명

인덱스 만들기

hotels_quickstart_index.json 파일은 다음 단계에서 로드하는 문서와 Azure AI 검색이 작동하는 방식을 정의합니다. 각 필드는 name으로 식별되고 지정된 type을 갖습니다. 또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

인덱스 정의가 있으면 main 함수에서 인덱스 정의에 액세스할 수 있도록 index.js의 위쪽에 있는 hotels_quickstart_index.json을 가져와야 합니다.

const indexDefinition = require('./hotels_quickstart_index.json');

그런 다음, main 함수 내에서 Azure AI 검색의 인덱스를 만들고 관리하는 데 사용되는 SearchIndexClient를 만듭니다.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

다음으로, 인덱스가 이미 있으면 이를 삭제해야 합니다. 이 작업은 테스트/데모 코드에 대한 일반적인 사례입니다.

이 작업은 인덱스 삭제를 시도하는 간단한 함수를 정의하여 수행합니다.

async function deleteIndexIfExists(indexClient, indexName) {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

이 함수를 실행하기 위해 인덱스 정의에서 인덱스 이름을 추출하고 indexNameindexClient와 함께 deleteIndexIfExists() 함수에 전달합니다.

const indexName = indexDefinition["name"];

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

그러면 createIndex() 메서드를 사용하여 인덱스를 만들 준비가 되었습니다.

console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);

console.log(`Index named ${index.name} has been created.`);

문서 로드

Azure AI 검색에서 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 이러한 데이터를 인덱스에 푸시하거나 인덱서를 사용할 수 있습니다. 이 경우 프로그래밍 방식으로 문서를 인덱스로 푸시합니다.

문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 이 샘플처럼 디스크의 JSON 문서일 수 있습니다. indexDefinition에서 한 것과 유사하게, hotels.json의 맨 위에 을 가져와야 메인 함수에서 데이터에 액세스할 수 있습니다.

const hotelData = require('./hotels.json');

데이터를 검색 인덱스로 인덱싱하려면 이제 SearchClient를 만들어야 합니다. SearchIndexClient는 인덱스를 만들고 관리하는 데 사용되지만, SearchClient는 문서를 업로드하고 인덱스를 쿼리하는 데 사용됩니다.

SearchClient를 만드는 방법은 두 가지입니다. 첫 번째 옵션은 SearchClient를 처음부터 만드는 것입니다.

 const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));

또는 getSearchClient()SearchIndexClient 메서드를 사용하여 SearchClient를 만들 수 있습니다.

const searchClient = indexClient.getSearchClient(indexName);

이제 클라이언트가 정의되었으므로 문서를 검색 인덱스에 업로드합니다. 이 경우, 동일한 키를 가진 문서가 이미 있는 경우 문서를 업로드하거나 기존 문서와 병합하는 mergeOrUploadDocuments() 메서드를 사용합니다.

console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

인덱스 검색

인덱스가 만들어지고 문서가 업로드되면 쿼리를 인덱스에 보낼 준비가 되었습니다. 이 섹션에서는 사용할 수 있는 다양한 쿼리 기능을 보여 주기 위해 5개의 다른 쿼리를 검색 인덱스에 보냅니다.

쿼리는 다음과 같이 메인 함수에서 호출하는 sendQueries() 함수에 작성됩니다.

await sendQueries(searchClient);

쿼리는 search()searchClient 메서드를 사용하여 보냅니다. 첫 번째 매개 변수는 검색 텍스트이고, 두 번째 매개 변수는 검색 옵션을 지정합니다.

쿼리 예 1

첫 번째 쿼리는 모든 항목을 검색하는 것과 동일한 *를 검색하고 인덱스에서 세 개의 필드를 선택합니다. 불필요한 데이터를 다시 끌어오면 쿼리의 대기 시간이 늘어날 수 있으므로 필요한 필드만 선택(select)하는 것이 좋습니다.

이 쿼리의 searchOptions에도 includeTotalCounttrue로 설정되어 일치하는 결과의 개수를 반환합니다.

async function sendQueries(searchClient) {
    console.log('Query #1 - search everything:');
    let searchOptions = {
        includeTotalCount: true,
        select: ["HotelId", "HotelName", "Rating"]
    };

    let searchResults = await searchClient.search("*", searchOptions);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

아래에 설명된 나머지 쿼리도 sendQueries() 함수에 추가해야 합니다. 이 경우 읽기 쉽도록 구분됩니다.

쿼리 예 2

다음 쿼리에서는 "wifi"라는 검색 용어를 지정하고, 상태가 'FL'인 결과만 반환하는 필터도 포함합니다. 결과는 Hotel의 Rating을 기준으로 정렬됩니다.

console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: ["HotelId", "HotelName", "Rating"]
};

searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예 3

다음으로, searchFields 매개 변수를 사용하여 검색을 검색 가능한 단일 필드로 제한합니다. 이 접근 방식은 특정 필드의 일치에만 관심이 있는 경우 쿼리를 더 효율적으로 만들 수 있는 좋은 옵션입니다.

console.log('Query #3 - Limit searchFields:');
searchOptions = {
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("Sublime Palace", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}
console.log();

쿼리 예 4

쿼리에 포함하는 또 다른 일반적인 옵션은 facets입니다. 패싯을 사용하면 사용자가 필터링할 수 있는 값을 쉽게 알 수 있도록 필터를 UI에 빌드할 수 있습니다.

console.log('Query #4 - Use facets:');
searchOptions = {
    facets: ["Category"],
    select: ["HotelId", "HotelName", "Rating"],
    searchFields: ["HotelName"]
};

searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예 5

최종 쿼리는 getDocument()searchClient 메서드를 사용합니다. 이렇게 하면 해당 키를 통해 문서를 효율적으로 검색할 수 있습니다.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 searchClient.search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열에 전달될 수 있으며, 필터 식은 filter 클래스의 SearchOptions 속성에 전달될 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

이 빠른 시작에서는 PowerShell 및 Azure AI Search REST API 를 사용하여 전체 텍스트 검색에 대한 검색 인덱스를 만들고, 로드하고, 쿼리합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

액세스 구성

역할 할당이 있는 API 키 또는 Microsoft Entra ID를 사용하여 Azure AI Search 서비스에 연결할 수 있습니다. 키는 시작하기가 더 쉽지만 역할이 더 안전합니다.

권장되는 역할 기반 액세스를 구성하려면 다음을 수행합니다.

  1. Azure Portal에 로그인하고 검색 서비스를 선택합니다.

  2. 왼쪽 창에서 설정>키를 선택합니다.

  3. API 액세스 제어에서 둘 다를 선택합니다.

    이 옵션을 사용하면 키 기반 인증과 키 없는 인증을 모두 사용할 수 있습니다. 역할을 할당한 후 이 단계로 돌아가 서 역할 기반 액세스 제어를 선택할 수 있습니다.

  4. 왼쪽 창에서 액세스 제어(IAM)를 선택합니다.

  5. 추가>역할 할당 추가를 선택합니다.

  6. 사용자 계정에 Search Service 기여자검색 인덱스 데이터 기여자 역할을 할당합니다.

자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

엔드포인트 가져오기

다음 섹션에서는 다음 엔드포인트를 지정하여 Azure AI Search 서비스에 대한 연결을 설정합니다. 이러한 단계에서는 역할 기반 액세스를 구성한 것으로 가정합니다.

서비스 엔드포인트를 얻으려면 다음을 수행합니다.

  1. Azure Portal에 로그인하고 검색 서비스를 선택합니다.

  2. 왼쪽 창에서 개요를 선택합니다.

  3. URL을 기록해 두되, https://my-service.search.windows.net와 유사해야 합니다.

Azure AI Search 서비스에 REST API를 호출하려면 먼저 인증하고 서비스에 연결해야 합니다. 2단계와 3단계에서 사용되는 Azure CLI 명령을 지원하는 PowerShell에서 다음 단계를 수행합니다.

검색 서비스에 연결하려면 다음을 수행합니다.

  1. 로컬 시스템에서 PowerShell을 엽니다.

  2. Azure 구독에 로그인합니다. 구독이 여러 개 있는 경우 검색 서비스가 포함된 구독을 선택합니다.

    az login
    
  3. 액세스 토큰을 $token 저장할 개체를 만듭니다.

    $token = az account get-access-token --resource https://search.azure.com/ --query accessToken --output tsv
    
  4. $headers 토큰 및 콘텐츠 형식을 저장할 개체를 만듭니다.

    $headers = @{
    'Authorization' = "Bearer $token"
    'Content-Type' = 'application/json' 
    'Accept' = 'application/json' }
    

    세션당 한 번만 헤더를 설정해야 하지만 각 요청에 헤더를 추가해야 합니다.

  5. $url 검색 서비스의 인덱스 컬렉션을 대상으로 하는 개체를 만듭니다. <YOUR-SEARCH-SERVICE>에서 얻은 값으로 를 바꾸십시오.

    $url = "<YOUR-SEARCH-SERVICE>/indexes?api-version=2025-09-01&`$select=name"
    
  6. 실행 Invoke-RestMethod 하여 검색 서비스에 GET 요청을 보냅니다. 서비스의 응답을 보려면 ConvertTo-Json를 포함합니다.

    Invoke-RestMethod -Uri $url -Headers $headers | ConvertTo-Json
    

    서비스가 비어 있고 인덱스가 없는 경우 응답은 다음 예제와 유사합니다. 그렇지 않으면 인덱스 정의의 JSON 표현이 표시됩니다.

    {
        "@odata.context":  "https://my-service.search.windows.net/$metadata#indexes",
        "value":  [
    
                  ]
    }
    

검색 인덱스 만들기

Azure AI Search에 콘텐츠를 추가하기 전에 콘텐츠를 저장하고 구성하는 방법을 정의하는 인덱스 만들기를 수행해야 합니다. 인덱스는 개념적으로 관계형 데이터베이스의 테이블과 유사하지만 전체 텍스트 검색과 같은 검색 작업을 위해 특별히 설계되었습니다.

이전 섹션에서 시작한 것과 동일한 PowerShell 세션에서 다음 명령을 실행합니다.

인덱스 만들기:

  1. 인덱스 스키마를 $body 정의하는 개체를 만듭니다.

    $body = @"
    {
        "name": "hotels-quickstart",  
        "fields": [
            {"name": "HotelId", "type": "Edm.String", "key": true, "filterable": true},
            {"name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false},
            {"name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzer": "en.lucene"},
            {"name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
            {"name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true},
            {"name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true},
            {"name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true},
            {"name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true},
            {"name": "Address", "type": "Edm.ComplexType", 
                "fields": [
                {"name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true},
                {"name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                {"name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                {"name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                {"name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}
            ]
         }
      ]
    }
    "@
    
  2. 개체를 $url 업데이트하여 새 인덱스 대상으로 지정합니다. <YOUR-SEARCH-SERVICE>에서 얻은 값으로 를 바꾸십시오.

    $url = "<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart?api-version=2025-09-01"
    
  3. 검색 서비스에서 인덱스 만들기를 실행 Invoke-RestMethod 합니다.

    Invoke-RestMethod -Uri $url -Headers $headers -Method Put -Body $body | ConvertTo-Json
    

    응답에는 인덱스 스키마의 JSON 표현이 포함되어야 합니다.

인덱스 만들기 요청 정보

이 빠른 시작에서는 인덱스 - 만들기(REST API) 를 호출하여 검색 서비스에서 명명된 hotels-quickstart 검색 인덱스 및 해당 물리적 데이터 구조를 빌드합니다.

인덱스 스키마 내에서 fields 컬렉션은 호텔 문서의 구조를 정의합니다. 각 필드에는 name가 있으며, 데이터 type 및 인덱싱과 쿼리 중 동작을 결정하는 특성이 있습니다. 이 HotelId 필드는 Azure AI Search에서 인덱스의 각 문서를 고유하게 식별하는 데 필요한 키로 표시됩니다.

인덱스 스키마에 대한 핵심 사항은 다음과 같습니다.

  • 문자열 필드(Edm.String)를 사용하여 숫자 데이터를 전체 텍스트로 검색할 수 있도록 합니다. 다른 지원되는 데이터 형식(예: Edm.Int32필터링 가능, 정렬 가능, 패싯 가능 및 검색 가능)이지만 검색할 수는 없습니다.

  • 대부분의 필드는 단순 데이터 형식이지만 필드와 같이 중첩된 데이터를 나타내는 복합 형식을 Address 정의할 수 있습니다.

  • 필드 특성은 허용되는 작업을 결정합니다. REST API는 기본적으로 많은 작업을 허용합니다. 예를 들어 모든 문자열은 검색 가능하고 검색할 수 있습니다. REST API를 사용하면 동작을 사용하지 않도록 설정해야 하는 경우에만 특성을 사용할 수 있습니다.

인덱스 로드

새로 만든 인덱스는 비어 있습니다. 인덱스를 채우고 검색 가능하도록 하려면 인덱스 스키마를 준수하는 JSON 문서를 업로드해야 합니다.

Azure AI Search에서 문서는 인덱싱을 위한 입력과 쿼리에 대한 출력의 역할을 합니다. 간단히 하기 위해 이 빠른 시작에서는 인라인 JSON으로 샘플 호텔 문서를 제공합니다. 그러나 프로덕션 시나리오에서는 콘텐츠가 연결된 데이터 원본에서 가져오고 인덱서를 사용하여 JSON으로 변환되는 경우가 많습니다.

인덱스로 문서를 업로드하려면 다음을 수행합니다.

  1. 네 개의 $body 샘플 문서의 JSON 페이로드를 저장할 개체를 만듭니다.

    $body = @"
        {
            "value": [
            {
            "@search.action": "upload",
            "HotelId": "1",
            "HotelName": "Stay-Kay City Hotel",
            "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
            "Category": "Boutique",
            "Tags": [ "view", "air conditioning", "concierge" ],
            "ParkingIncluded": false,
            "LastRenovationDate": "2022-01-18T00:00:00Z",
            "Rating": 3.60,
            "Address": 
                {
                "StreetAddress": "677 5th Ave",
                "City": "New York",
                "StateProvince": "NY",
                "PostalCode": "10022",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "2",
            "HotelName": "Old Century Hotel",
            "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
            "Category": "Boutique",
            "Tags": [ "pool", "free wifi", "concierge" ],
            "ParkingIncluded": false,
            "LastRenovationDate": "2019-02-18T00:00:00Z",
            "Rating": 3.60,
            "Address": 
                {
                "StreetAddress": "140 University Town Center Dr",
                "City": "Sarasota",
                "StateProvince": "FL",
                "PostalCode": "34243",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "3",
            "HotelName": "Gastronomic Landscape Hotel",
            "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
            "Category": "Suite",
            "Tags": [ "restaurant", "bar", "continental breakfast" ],
            "ParkingIncluded": true,
            "LastRenovationDate": "2015-09-20T00:00:00Z",
            "Rating": 4.80,
            "Address": 
                {
                "StreetAddress": "3393 Peachtree Rd",
                "City": "Atlanta",
                "StateProvince": "GA",
                "PostalCode": "30326",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "4",
            "HotelName": "Sublime Palace Hotel",
            "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
            "Category": "Boutique",
            "Tags": [ "concierge", "view", "air conditioning" ],
            "ParkingIncluded": true,
            "LastRenovationDate": "2020-02-06T00:00:00Z",
            "Rating": 4.60,
            "Address": 
                {
                "StreetAddress": "7400 San Pedro Ave",
                "City": "San Antonio",
                "StateProvince": "TX",
                "PostalCode": "78216",
                "Country": "USA"
                }
            }
          ]
        }
    "@
    
  2. $url 인덱싱 엔드포인트를 대상으로 개체를 업데이트합니다. <YOUR-SEARCH-SERVICE>에서 얻은 값으로 를 바꾸십시오.

    $url = "<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs/index?api-version=2025-09-01"
    
  3. 실행 Invoke-RestMethod 하여 검색 서비스에 업로드 요청을 보냅니다.

    Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $body | ConvertTo-Json
    

    응답에는 업로드된 각 문서의 키와 상태가 포함되어야 합니다.

업로드 요청 정보

이 빠른 시작에서는 문서 - 인덱스(REST API) 를 호출하여 4개의 샘플 호텔 문서를 인덱싱에 추가합니다. 이전 요청과 비교하여 URI가 docs 컬렉션 및 index 작업을 포함하도록 확장됩니다.

배열의 value 각 문서는 호텔을 나타내며 인덱스 스키마와 일치하는 필드를 포함합니다. 매개 변수는 @search.action 각 문서에 대해 수행할 작업을 지정합니다. 이 예제에서는 문서가 없는 경우 문서를 추가하거나 문서가 있는 경우 업데이트하는 데 사용합니다 upload.

인덱스 쿼리

이제 문서가 인덱스에 로드되었으므로 전체 텍스트 검색을 사용하여 필드 내에서 특정 용어 또는 구를 찾을 수 있습니다.

인덱스로 전체 텍스트 쿼리를 실행하려면 다음을 수행합니다.

  1. 검색 매개 변수를 $url 지정하도록 개체를 업데이트합니다. <YOUR-SEARCH-SERVICE>에서 얻은 값으로 를 바꾸십시오.

    $url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2025-09-01&search=attached restaurant&searchFields=Description,Tags&$select=HotelId,HotelName,Tags,Description&$count=true'
    
  2. 실행 Invoke-RestMethod 하여 검색 서비스에 쿼리 요청을 보냅니다.

    Invoke-RestMethod -Uri $url -Headers $headers | ConvertTo-Json
    

    응답은 일치하는 호텔 문서 하나, 관련성 점수 및 선택한 필드를 보여주는 다음 예제와 유사해야 합니다.

    {
      "@odata.context": "https://my-service.search.windows.net/indexes('hotels-quickstart')/$metadata#docs(*)",
      "@odata.count": 1,
      "value": [
        {
          "@search.score": 0.5575875,
          "HotelId": "3",
          "HotelName": "Gastronomic Landscape Hotel",
          "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
          "Tags": "restaurant bar continental breakfast"
        }
      ]
    }
    

쿼리 요청 정보

이 빠른 시작에서는 문서 - REST API(Search Post) 를 호출하여 검색 조건과 일치하는 호텔 문서를 찾습니다. URI는 여전히 docs 컬렉션을 대상으로 하지만 더 이상 index 작업을 포함하지 않습니다.

전체 텍스트 검색 요청에는 쿼리 텍스트를 포함한 매개변수가 항상 포함 search 됩니다. 쿼리 텍스트에는 하나 이상의 용어, 구 또는 연산자가 포함될 수 있습니다. 또한 search다른 매개 변수를 지정하여 검색 동작 및 결과를 구체화할 수 있습니다.

쿼리는 각 호텔 문서의 DescriptionTags 필드에서 "부속 레스토랑"이라는 용어를 검색합니다. 매개 변수는 응답에서 반환되는 필드를 $select, HotelId, HotelName, Tags로 제한합니다. 매개 변수는 $count 일치하는 문서의 총 수를 요청합니다.

기타 쿼리 예제

다음 명령을 실행하여 쿼리 구문을 탐색합니다. 문자열 검색을 수행하고, 식을 사용하고 $filter , 결과 집합을 제한하고, 특정 필드를 선택하는 등의 작업을 수행할 수 있습니다. <YOUR-SEARCH-SERVICE>엔드포인트 가져오기에서 가져오는 값으로 바꾸는 것을 잊지 마세요.

# Query example 1
# Search the index for the terms 'restaurant' and 'wifi'
# Return only the HotelName, Description, and Tags fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2025-09-01&search=restaurant wifi&$count=true&$select=HotelName,Description,Tags'

# Query example 2 
# Use a filter to find hotels rated 4 or higher
# Return only the HotelName and Rating fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2025-09-01&search=*&$filter=Rating gt 4&$select=HotelName,Rating'

# Query example 3
# Take the top two results
# Return only the HotelName and Category fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2025-09-01&search=boutique&$top=2&$select=HotelName,Category'

# Query example 4
# Sort by a specific field (Address/City) in ascending order
# Return only the HotelName, Address/City, Tags, and Rating fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2025-09-01&search=pool&$orderby=Address/City asc&$select=HotelName, Address/City, Tags, Rating'

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색을 위한 샘플 데이터로 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

완성된 Notebook을 다운로드하여 실행할 수 있습니다.

필수 구성 요소

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Azure CLI를 설치합니다.

  • Search Service ContributorSearch Index Data Contributor 역할을 사용자 계정에 할당합니다. Azure Portal의 액세스 제어(IAM)>역할 할당 추가에서 역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

리소스 정보 검색

Azure AI 검색 서비스에서 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. Essentials 아래의 Url 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증환경 변수 설정에 대해 자세히 알아봅니다.

환경 설정

Jupyter Notebook에서 샘플 코드를 실행합니다. 그러므로 Jupyter Notebook을 실행하려면 환경을 설정해야 합니다.

  1. GitHub에서 샘플 Notebook을 다운로드하거나 복사합니다.

  2. Visual Studio Code에서 Notebook을 엽니다.

  3. 이 자습서에 필요한 패키지를 설치하는 데 사용할 새로운 Python 환경을 만듭니다.

    중요합니다

    전역 Python 설치에 패키지를 설치하지 마세요. Python 패키지를 설치할 때 항상 가상 환경 또는 conda 환경을 사용해야 합니다. 그렇지 않으면 Python의 전역 설치가 중단될 수 있습니다.

    py -3 -m venv .venv
    .venv\scripts\activate
    

    설정하는 데 1분 정도 걸릴 수 있습니다. 문제가 발생하면 VS Code의 Python 환경을 참조하세요.

  4. 아직 Jupyter Notebook과 Jupyter Notebook용 IPython 커널이 없다면 설치합니다.

    pip install jupyter
    pip install ipykernel
    python -m ipykernel install --user --name=.venv
    
  5. Notebook 커널을 선택합니다.

    1. Notebook의 오른쪽 상단에서 커널 선택을 선택합니다.
    2. 목록에 .venv가 표시되면 선택합니다. 보이지 않으면 다른 커널 선택>Python 환경>.venv를 선택합니다.

검색 인덱스 만들기, 로드 및 쿼리

이 섹션에서는 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가합니다. 콘솔에서 결과를 보려면 프로그램을 실행합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

  1. 이전 섹션에서 설명한 대로 Notebook이 .venv 커널에서 열려 있는지 확인합니다.

  2. azure-search-documents를 포함하여 필요한 패키지를 설치하려면 첫 번째 코드 셀을 실행합니다.

    ! pip install azure-search-documents==11.6.0b1 --quiet
    ! pip install azure-identity --quiet
    ! pip install python-dotenv --quiet
    
  3. 인증 방법에 따라 두 번째 코드 셀의 콘텐츠를 다음 코드로 바꿉니다.

    참고

    이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

    from azure.core.credentials import AzureKeyCredential
    from azure.identity import DefaultAzureCredential, AzureAuthorityHosts
    
    search_endpoint: str = "https://<Put your search service NAME here>.search.windows.net/"
    authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
    credential = DefaultAzureCredential(authority=authority)
    
    index_name: str = "hotels-quickstart-python"
    
  4. 인덱스 만들기 코드 셀에서 다음 두 줄을 제거합니다. 자격 증명은 이미 이전 코드 셀에 설정되어 있습니다.

    from azure.core.credentials import AzureKeyCredential
    credential = AzureKeyCredential(search_api_key)
    
  5. 인덱스 만들기 코드 셀을 실행하여 검색 인덱스를 만듭니다.

  6. 나머지 코드 셀을 순차적으로 실행하여 문서를 로드하고 쿼리를 실행합니다.

코드 설명

인덱스 만들기

SearchIndexClient는 Azure AI 검색에 대한 인덱스를 만들고 관리하는 데 사용됩니다. 각 필드는 name으로 식별되고 지정된 type을 갖습니다.

또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

문서 페이로드 만들기 및 문서 업로드

업로드나 병합 업로드와 같은 작업 유형에 대해 인덱스 작업을 사용합니다. 이 문서는 GitHub의 HotelsData 샘플에서 가져왔습니다.

인덱스 검색

첫 번째 문서의 인덱싱이 완료되는 즉시 쿼리 결과를 얻을 수 있지만 인덱스에 대한 실제 테스트는 모든 문서의 인덱싱이 완료될 때까지 기다려야 합니다.

search.client 클래스search 메서드를 사용합니다.

Notebook의 샘플 쿼리는 다음과 같습니다.

  • 기본 쿼리: 빈 쿼리(search=*)을 실행하여 임의의 문서의 순위가 지정되지 않은 목록(검색 점수 = 1.0)을 반환합니다. 조건이 없으므로 모든 문서가 결과에 포함됩니다.
  • 용어 쿼리: 검색 식에 전체 용어를 추가합니다("wifi"). 이 쿼리는 select 문의 해당 필드만 결과에 포함되도록 지정합니다. 반환되는 필드를 제한하면 유선을 통해 다시 보내지는 데이터의 양이 최소화되고 검색 대기 시간이 줄어듭니다.
  • 필터링된 쿼리: 평점이 4보다 큰 호텔만 내림차순으로 정렬하여 반환하는 필터 식을 추가합니다.
  • 필드 범위 지정: search_fields를 추가하여 쿼리 실행 범위를 특정 필드로 지정합니다.
  • 패싯: 검색 결과에서 발견된 긍정적 일치 항목에 대한 패싯을 생성합니다. 일치하는 항목이 0개도 없습니다. 검색 결과에 wifi라는 용어가 포함되지 않으면 wifi는 패싯 탐색 구조에 나타나지 않습니다.
  • 문서 조회: 키를 기준으로 문서를 반환합니다. 이 작업은 사용자가 검색 결과에서 항목을 선택할 때 드릴스루를 제공하려는 경우에 유용합니다.
  • 자동 완성: 사용자가 검색 상자에 입력하는 동안 잠재적으로 일치할 수 있는 검색어를 제공합니다. 자동 완성은 제안기(sg)를 사용하여 제안기 요청에 대한 잠재적 일치 항목이 포함된 필드를 파악합니다. 이 빠른 시작에서는 이러한 필드가 Tags, Address/City, Address/Country입니다. 자동 완성을 시뮬레이션하려면 sa 문자를 부분 문자열로 전달합니다. SearchClient의 자동 완성 메서드는 잠재적으로 일치하는 용어 항목을 다시 보냅니다.

인덱스 제거

이 인덱스 작업이 끝나면 정리 코드 셀을 실행하여 인덱스를 삭제할 수 있습니다. 불필요한 인덱스를 삭제하면 더 많은 빠른 시작과 자습서를 단계별로 살펴볼 수 있는 공간이 확보됩니다.

이 빠른 시작에서는 Azure AI Search REST API 를 사용하여 전체 텍스트 검색에 대한 검색 인덱스를 만들고, 로드하고, 쿼리합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

액세스 구성

역할 할당이 있는 API 키 또는 Microsoft Entra ID를 사용하여 Azure AI Search 서비스에 연결할 수 있습니다. 키는 시작하기가 더 쉽지만 역할이 더 안전합니다.

권장되는 역할 기반 액세스를 구성하려면 다음을 수행합니다.

  1. Azure Portal에 로그인하고 검색 서비스를 선택합니다.

  2. 왼쪽 창에서 설정>키를 선택합니다.

  3. API 액세스 제어에서 둘 다를 선택합니다.

    이 옵션을 사용하면 키 기반 인증과 키 없는 인증을 모두 사용할 수 있습니다. 역할을 할당한 후 이 단계로 돌아가 서 역할 기반 액세스 제어를 선택할 수 있습니다.

  4. 왼쪽 창에서 액세스 제어(IAM)를 선택합니다.

  5. 추가>역할 할당 추가를 선택합니다.

  6. 사용자 계정에 Search Service 기여자검색 인덱스 데이터 기여자 역할을 할당합니다.

자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

엔드포인트 및 토큰 가져오기

다음 섹션에서는 다음 엔드포인트 및 토큰을 지정하여 Azure AI Search 서비스에 대한 연결을 설정합니다. 이러한 단계에서는 역할 기반 액세스를 구성한 것으로 가정합니다.

서비스 엔드포인트 및 토큰을 가져오려면 다음을 수행합니다.

  1. Azure Portal에 로그인하고 검색 서비스를 선택합니다.

  2. 왼쪽 창에서 개요를 선택합니다.

  3. URL을 기록해 두되, https://my-service.search.windows.net와 유사해야 합니다.

  4. 로컬 시스템에서 터미널을 엽니다.

  5. Azure 구독에 로그인합니다. 구독이 여러 개 있는 경우 검색 서비스가 포함된 구독을 선택합니다.

    az login
    
  6. Microsoft Entra 토큰을 기록해 둡다.

    az account get-access-token --scope https://search.azure.com/.default
    

파일 설정

Azure AI Search 서비스에 REST API를 호출하려면 먼저 서비스 엔드포인트, 인증 토큰 및 최종 요청을 저장할 파일을 만들어야 합니다. Visual Studio Code의 REST 클라이언트 확장은 이 작업을 지원합니다.

요청 파일을 설정하려면 다음을 수행합니다.

  1. 로컬 시스템에서 Visual Studio Code를 엽니다.

  2. .rest 또는 .http 파일을 만듭니다.

  3. 파일에 다음의 자리 표시자와 요청을 붙여넣으세요.

    @baseUrl = PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE
    @token = PUT-YOUR-PERSONAL-IDENTITY-TOKEN-HERE
    
    ### List existing indexes by name
    GET {{baseUrl}}/indexes?api-version=2025-09-01  HTTP/1.1
        Authorization: Bearer {{token}}
    
  4. @baseUrl@token 자리 표시자를 Get 엔드포인트 및 토큰에서 얻은 값으로 바꿉니다. 따옴표를 포함하지 마세요.

  5. 아래에서 ### List existing indexes by name요청 보내기를 선택합니다.

    인접한 창에 응답이 표시됩니다. 기존 인덱스가 있는 경우 인덱스가 나열됩니다. 그러지 않으면 목록은 비어 있습니다. HTTP 코드가 200 OK인 경우 다음 단계를 수행할 준비가 된 것입니다.

    검색 서비스 요청에 대해 구성된 REST 클라이언트를 보여 주는 스크린샷.

검색 인덱스 만들기

Azure AI Search에 콘텐츠를 추가하기 전에 콘텐츠를 저장하고 구성하는 방법을 정의하는 인덱스 만들기를 수행해야 합니다. 인덱스는 개념적으로 관계형 데이터베이스의 테이블과 유사하지만 전체 텍스트 검색과 같은 검색 작업을 위해 특별히 설계되었습니다.

인덱스 만들기:

  1. 다음 요청을 파일에 붙여넣습니다.

    ### Create a new index
    POST {{baseUrl}}/indexes?api-version=2025-09-01  HTTP/1.1
        Content-Type: application/json
        Authorization: Bearer {{token}}
    
        {
            "name": "hotels-quickstart",  
            "fields": [
                {"name": "HotelId", "type": "Edm.String", "key": true, "filterable": true},
                {"name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false},
                {"name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzer": "en.lucene"},
                {"name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                {"name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true},
                {"name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true},
                {"name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true},
                {"name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true},
                {"name": "Address", "type": "Edm.ComplexType", 
                    "fields": [
                    {"name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true},
                    {"name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                    {"name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                    {"name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true},
                    {"name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}
                    ]
                }
            ]
        }
    
  2. 아래에서 ### Create a new index요청 보내기를 선택합니다.

    인덱스 스키마의 HTTP/1.1 201 Created JSON 표현이 본문에 포함된 응답을 받아야 합니다.

인덱스 만들기 요청 정보

이 빠른 시작에서는 인덱스 - 만들기(REST API) 를 호출하여 검색 서비스에서 명명된 hotels-quickstart 검색 인덱스 및 해당 물리적 데이터 구조를 빌드합니다.

인덱스 스키마 내에서 fields 컬렉션은 호텔 문서의 구조를 정의합니다. 각 필드에는 name가 있으며, 데이터 type 및 인덱싱과 쿼리 중 동작을 결정하는 특성이 있습니다. 이 HotelId 필드는 Azure AI Search에서 인덱스의 각 문서를 고유하게 식별하는 데 필요한 키로 표시됩니다.

인덱스 스키마에 대한 핵심 사항은 다음과 같습니다.

  • 문자열 필드(Edm.String)를 사용하여 숫자 데이터를 전체 텍스트로 검색할 수 있도록 합니다. 다른 지원되는 데이터 형식(예: Edm.Int32필터링 가능, 정렬 가능, 패싯 가능 및 검색 가능)이지만 검색할 수는 없습니다.

  • 대부분의 필드는 단순 데이터 형식이지만 필드와 같이 중첩된 데이터를 나타내는 복합 형식을 Address 정의할 수 있습니다.

  • 필드 특성은 허용되는 작업을 결정합니다. REST API는 기본적으로 많은 작업을 허용합니다. 예를 들어 모든 문자열은 검색 가능하고 검색할 수 있습니다. REST API를 사용하면 동작을 사용하지 않도록 설정해야 하는 경우에만 특성을 사용할 수 있습니다.

인덱스 로드

새로 만든 인덱스는 비어 있습니다. 인덱스를 채우고 검색 가능하도록 하려면 인덱스 스키마를 준수하는 JSON 문서를 업로드해야 합니다.

Azure AI Search에서 문서는 인덱싱을 위한 입력과 쿼리에 대한 출력의 역할을 합니다. 간단히 하기 위해 이 빠른 시작에서는 인라인 JSON으로 샘플 호텔 문서를 제공합니다. 그러나 프로덕션 시나리오에서는 콘텐츠가 연결된 데이터 원본에서 가져오고 인덱서를 사용하여 JSON으로 변환되는 경우가 많습니다.

인덱스로 문서를 업로드하려면 다음을 수행합니다.

  1. 다음 요청을 파일에 붙여넣습니다.

    ### Upload documents
    POST {{baseUrl}}/indexes/hotels-quickstart/docs/index?api-version=2025-09-01  HTTP/1.1
        Content-Type: application/json
        Authorization: Bearer {{token}}
    
        {
            "value": [
            {
            "@search.action": "upload",
            "HotelId": "1",
            "HotelName": "Stay-Kay City Hotel",
            "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
            "Category": "Boutique",
            "Tags": [ "view", "air conditioning", "concierge" ],
            "ParkingIncluded": false,
            "LastRenovationDate": "2022-01-18T00:00:00Z",
            "Rating": 3.60,
            "Address": 
                {
                "StreetAddress": "677 5th Ave",
                "City": "New York",
                "StateProvince": "NY",
                "PostalCode": "10022",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "2",
            "HotelName": "Old Century Hotel",
            "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
             "Category": "Boutique",
            "Tags": [ "pool", "free wifi", "concierge" ],
            "ParkingIncluded": false,
            "LastRenovationDate": "2019-02-18T00:00:00Z",
            "Rating": 3.60,
            "Address": 
                {
                "StreetAddress": "140 University Town Center Dr",
                "City": "Sarasota",
                "StateProvince": "FL",
                "PostalCode": "34243",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "3",
            "HotelName": "Gastronomic Landscape Hotel",
            "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
            "Category": "Suite",
            "Tags": [ "restaurant", "bar", "continental breakfast" ],
            "ParkingIncluded": true,
            "LastRenovationDate": "2015-09-20T00:00:00Z",
            "Rating": 4.80,
            "Address": 
                {
                "StreetAddress": "3393 Peachtree Rd",
                "City": "Atlanta",
                "StateProvince": "GA",
                "PostalCode": "30326",
                "Country": "USA"
                } 
            },
            {
            "@search.action": "upload",
            "HotelId": "4",
            "HotelName": "Sublime Palace Hotel",
            "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
            "Category": "Luxury",
            "Tags": [ "concierge", "view", "air conditioning" ],
            "ParkingIncluded": true,
            "LastRenovationDate": "2020-02-06T00:00:00Z",
            "Rating": 4.60,
            "Address": 
                {
                "StreetAddress": "7400 San Pedro Ave",
                "City": "San Antonio",
                "StateProvince": "TX",
                "PostalCode": "78216",
                "Country": "USA"
                }
            }
          ]
        }
    
  2. 아래에서 ### Upload documents요청 보내기를 선택합니다.

    본문에 업로드된 각 문서의 키와 상태 정보를 포함하는 HTTP/1.1 200 OK 응답을 받게 될 것입니다.

업로드 요청 정보

이 빠른 시작에서는 문서 - 인덱스(REST API) 를 호출하여 4개의 샘플 호텔 문서를 인덱싱에 추가합니다. 이전 요청과 비교하여 URI가 docs 컬렉션 및 index 작업을 포함하도록 확장됩니다.

배열의 value 각 문서는 호텔을 나타내며 인덱스 스키마와 일치하는 필드를 포함합니다. 매개 변수는 @search.action 각 문서에 대해 수행할 작업을 지정합니다. 이 예제에서는 문서가 없는 경우 문서를 추가하거나 문서가 있는 경우 업데이트하는 데 사용합니다 upload.

인덱스 쿼리

이제 문서가 인덱스에 로드되었으므로 전체 텍스트 검색을 사용하여 필드 내에서 특정 용어 또는 구를 찾을 수 있습니다.

인덱스로 전체 텍스트 쿼리를 실행하려면 다음을 수행합니다.

  1. 다음 요청을 파일에 붙여넣습니다.

    ### Run a query
    POST {{baseUrl}}/indexes/hotels-quickstart/docs/search?api-version=2025-09-01  HTTP/1.1
      Content-Type: application/json
      Authorization: Bearer {{token}}
    
      {
          "search": "attached restaurant",
          "select": "HotelId, HotelName, Tags, Description",
          "searchFields": "Description, Tags",
          "count": true
      }
    
  2. 아래에서 ### Run a query요청 보내기를 선택합니다.

    다음 예제와 유사한 응답을 받아야 합니다 HTTP/1.1 200 OK. 예는 일치하는 호텔 문서 하나와 관련성 점수, 선택된 필드를 보여 줍니다.

    {
      "@odata.context": "https://my-service.search.windows.net/indexes('hotels-quickstart')/$metadata#docs(*)",
      "@odata.count": 1,
      "value": [
        {
          "@search.score": 0.5575875,
          "HotelId": "3",
          "HotelName": "Gastronomic Landscape Hotel",
          "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel\u2019s restaurant services.",
          "Tags": [
            "restaurant",
            "bar",
            "continental breakfast"
          ]
        }
      ]
    }
    

쿼리 요청 정보

이 빠른 시작에서는 문서 - REST API(Search Post) 를 호출하여 검색 조건과 일치하는 호텔 문서를 찾습니다. 이제 URI가 /docs/search 작업을 타겟으로 합니다.

전체 텍스트 검색 요청에는 쿼리 텍스트를 포함한 매개변수가 항상 포함 search 됩니다. 쿼리 텍스트에는 하나 이상의 용어, 구 또는 연산자가 포함될 수 있습니다. 또한 search다른 매개 변수를 지정하여 검색 동작 및 결과를 구체화할 수 있습니다.

쿼리는 각 호텔 문서의 DescriptionTags 필드에서 "부속 레스토랑"이라는 용어를 검색합니다. 매개 변수는 응답에서 반환되는 필드를 select, HotelId, HotelName, Tags로 제한합니다. 매개 변수는 count 일치하는 문서의 총 수를 요청합니다.

이 빠른 시작에서는 Azure.Search.Documents 클라이언트 라이브러리를 사용하여 전체 텍스트 검색을 위한 샘플 데이터로 검색 인덱스 만들기, 로드 및 쿼리를 수행합니다. 전체 텍스트 검색은 인덱싱 및 쿼리에 Apache Lucene을 사용하고 BM25 순위 알고리즘을 사용하여 결과를 채점합니다.

이 빠른 시작에서는 azure-search-sample-data 리포지토리의 가상 호텔 데이터를 사용하여 인덱스를 채웁다.

소스 코드를 다운로드하여 완료된 프로젝트로 시작하거나 다음 단계에 따라 직접 만들 수 있습니다.

필수 구성 요소

Microsoft Entra ID 필수 구성 요소

Microsoft Entra ID를 사용하는 권장 키 없는 인증의 경우 다음을 수행해야 합니다.

  • Azure CLI를 설치합니다.

  • Search Service ContributorSearch Index Data Contributor 역할을 사용자 계정에 할당합니다. Azure Portal의 액세스 제어(IAM)>역할 할당 추가에서 역할을 할당할 수 있습니다. 자세한 내용은 역할을 사용하여 Azure AI 검색에 연결을 참조하세요.

리소스 정보 검색

Azure AI 검색 서비스에서 애플리케이션을 인증하려면 다음 정보를 검색해야 합니다.

변수 이름
SEARCH_API_ENDPOINT 이 값은 Azure Portal에서 찾을 수 있습니다. 검색 서비스를 선택한 다음 왼쪽 메뉴에서 개요를 선택합니다. Essentials 아래의 Url 값은 필요한 엔드포인트입니다. 엔드포인트의 예는 다음과 같습니다. https://mydemo.search.windows.net

키 없는 인증환경 변수 설정에 대해 자세히 알아봅니다.

설정

  1. 애플리케이션을 포함할 새 폴더 full-text-quickstart을 만들고 다음 명령을 사용하여 해당 폴더에서 Visual Studio Code를 엽니다.

    mkdir full-text-quickstart && cd full-text-quickstart
    
  2. 다음 명령을 사용하여 package.json을 만듭니다.

    npm init -y
    
  3. 다음 명령을 사용하여 package.json을 ECMAScript로 업데이트합니다.

    npm pkg set type=module
    
  4. JavaScript용 Azure AI 검색 클라이언트 라이브러리(Azure.Search.Documents)를 설치하려면 다음을 사용합니다.

    npm install @azure/search-documents
    
  5. 권장 암호 없는 인증의 경우 다음을 사용하여 Azure ID 클라이언트 라이브러리를 설치합니다.

    npm install @azure/identity
    

검색 인덱스 만들기, 로드 및 쿼리

이전 설정 섹션에서 Azure AI 검색 클라이언트 라이브러리와 기타 종속성을 설치했습니다.

이 섹션에서는 검색 인덱스를 만들고, 문서를 로드하고, 쿼리를 실행하는 코드를 추가합니다. 콘솔에서 결과를 보려면 프로그램을 실행합니다. 코드에 대한 자세한 설명은 코드 설명 섹션을 참조하세요.

이 빠른 시작의 샘플 코드는 권장되는 키 없는 인증에 Microsoft Entra ID를 사용합니다. API 키를 사용하려면 DefaultAzureCredential 개체를 AzureKeyCredential 개체로 바꾸면 됩니다.

const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
const credential = new DefaultAzureCredential();
  1. index.ts라는 이름의 새 파일을 만들고 다음 코드를 index.ts에 붙여넣습니다.

    // Import from the @azure/search-documents library
    import {
        SearchIndexClient,
        SearchClient,
        SearchFieldDataType,
        AzureKeyCredential,
        odata,
        SearchIndex
    } from "@azure/search-documents";
    
    // Import from the Azure Identity library
    import { DefaultAzureCredential } from "@azure/identity";
    
    // Importing the hotels sample data
    import hotelData from './hotels.json' assert { type: "json" };
    
    // Load the .env file if it exists
    import * as dotenv from "dotenv";
    dotenv.config();
    
    // Defining the index definition
    const indexDefinition: SearchIndex = {
    	"name": "hotels-quickstart",
    	"fields": [
    		{
    			"name": "HotelId",
    			"type": "Edm.String" as SearchFieldDataType,
    			"key": true,
    			"filterable": true
    		},
    		{
    			"name": "HotelName",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": true,
    			"facetable": false
    		},
    		{
    			"name": "Description",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": false,
    			"sortable": false,
    			"facetable": false,
    			"analyzerName": "en.lucene"
    		},
    		{
    			"name": "Category",
    			"type": "Edm.String" as SearchFieldDataType,
    			"searchable": true,
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Tags",
    			"type": "Collection(Edm.String)",
    			"searchable": true,
    			"filterable": true,
    			"sortable": false,
    			"facetable": true
    		},
    		{
    			"name": "ParkingIncluded",
    			"type": "Edm.Boolean",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "LastRenovationDate",
    			"type": "Edm.DateTimeOffset",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Rating",
    			"type": "Edm.Double",
    			"filterable": true,
    			"sortable": true,
    			"facetable": true
    		},
    		{
    			"name": "Address",
    			"type": "Edm.ComplexType",
    			"fields": [
    				{
    					"name": "StreetAddress",
    					"type": "Edm.String" as SearchFieldDataType,
    					"filterable": false,
    					"sortable": false,
    					"facetable": false,
    					"searchable": true
    				},
    				{
    					"name": "City",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "StateProvince",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "PostalCode",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				},
    				{
    					"name": "Country",
    					"type": "Edm.String" as SearchFieldDataType,
    					"searchable": true,
    					"filterable": true,
    					"sortable": true,
    					"facetable": true
    				}
    			]
    		}
    	],
    	"suggesters": [
    		{
    			"name": "sg",
    			"searchMode": "analyzingInfixMatching",
    			"sourceFields": [
    				"HotelName"
    			]
    		}
    	]
    };
    
    async function main() {
    
    	// Your search service endpoint
    	const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
    
    	// Use the recommended keyless credential instead of the AzureKeyCredential credential.
    	const credential = new DefaultAzureCredential();
    	//const credential = new AzureKeyCredential(Your search service admin key);
    
    	// Create a SearchIndexClient to send create/delete index commands
    	const searchIndexClient: SearchIndexClient = new SearchIndexClient(
    		searchServiceEndpoint,
    		credential
    	);
    
    	// Creating a search client to upload documents and issue queries
    	const indexName: string  = "hotels-quickstart";
        const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName);
    
        console.log('Checking if index exists...');
        await deleteIndexIfExists(searchIndexClient, indexName);
    
        console.log('Creating index...');
        let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition);
        console.log(`Index named ${index.name} has been created.`);
    
        console.log('Uploading documents...');
        let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
        console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `);
    
        // waiting one second for indexing to complete (for demo purposes only)
        sleep(1000);
    
        console.log('Querying the index...');
        console.log();
        await sendQueries(searchClient);
    }
    
    async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) {
        try {
            await searchIndexClient.deleteIndex(indexName);
            console.log('Deleting index...');
        } catch {
            console.log('Index does not exist yet.');
        }
    }
    
    async function sendQueries(searchClient: SearchClient<any>) {
        // Query 1
        console.log('Query #1 - search everything:');
        let searchOptions: any = {
            includeTotalCount: true,
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        let searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log(`Result count: ${searchResults.count}`);
        console.log();
    
    
        // Query 2
        console.log('Query #2 - search with filter, orderBy, and select:');
        let state = 'FL';
        searchOptions = {
            filter: odata`Address/StateProvince eq ${state}`,
            orderBy: ["Rating desc"],
            select: ["HotelId", "HotelName", "Rating"]
        };
    
        searchResults = await searchClient.search("wifi", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 3
        console.log('Query #3 - limit searchFields:');
        searchOptions = {
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("sublime palace", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 4
        console.log('Query #4 - limit searchFields and use facets:');
        searchOptions = {
            facets: ["Category"],
            select: ["HotelId", "HotelName", "Rating"],
            searchFields: ["HotelName"]
        };
    
        searchResults = await searchClient.search("*", searchOptions);
        for await (const result of searchResults.results) {
            console.log(`${JSON.stringify(result.document)}`);
        }
        console.log();
    
        // Query 5
        console.log('Query #5 - Lookup document:');
        let documentResult = await searchClient.getDocument('3');
        console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`);
        console.log();
    }
    
    function sleep(ms: number) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    main().catch((err) => {
        console.error("The sample encountered an error:", err);
    });
    
  2. hotels.json이라는 이름의 파일을 만들고 다음 코드를 hotels.json에 붙여넣습니다.

    {
        "value": [
            {
                "HotelId": "1",
                "HotelName": "Stay-Kay City Hotel",
                "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.",
                "Category": "Boutique",
                "Tags": ["view", "air conditioning", "concierge"],
                "ParkingIncluded": false,
                "LastRenovationDate": "2022-01-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "677 5th Ave",
                    "City": "New York",
                    "StateProvince": "NY",
                    "PostalCode": "10022"
                }
            },
            {
                "HotelId": "2",
                "HotelName": "Old Century Hotel",
                "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.",
                "Category": "Boutique",
                "Tags": ["pool", "free wifi", "concierge"],
                "ParkingIncluded": "false",
                "LastRenovationDate": "2019-02-18T00:00:00Z",
                "Rating": 3.6,
                "Address": {
                    "StreetAddress": "140 University Town Center Dr",
                    "City": "Sarasota",
                    "StateProvince": "FL",
                    "PostalCode": "34243"
                }
            },
            {
                "HotelId": "3",
                "HotelName": "Gastronomic Landscape Hotel",
                "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                "Category": "Suite",
                "Tags": ["restaurant, "bar", "continental breakfast"],
                "ParkingIncluded": "true",
                "LastRenovationDate": "2015-09-20T00:00:00Z",
                "Rating": 4.8,
                "Address": {
                    "StreetAddress": "3393 Peachtree Rd",
                    "City": "Atlanta",
                    "StateProvince": "GA",
                    "PostalCode": "30326"
                }
            },
            {
                "HotelId": "4",
                "HotelName": "Sublime Palace Hotel",
                "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.",
                "Category": "Boutique",
                "Tags": ["concierge", "view", "air conditioning"],
                "ParkingIncluded": true,
                "LastRenovationDate": "2020-02-06T00:00:00Z",
                "Rating": 4.6,
                "Address": {
                    "StreetAddress": "7400 San Pedro Ave",
                    "City": "San Antonio",
                    "StateProvince": "TX",
                    "PostalCode": "78216"
                }
            }
        ]
    }
    
  3. TypeScript 코드를 변환하려면 tsconfig.json 파일을 만들고 ECMAScript에 대한 다음 코드를 복사합니다.

    {
        "compilerOptions": {
          "module": "NodeNext",
          "target": "ES2022", // Supports top-level await
          "moduleResolution": "NodeNext",
          "skipLibCheck": true, // Avoid type errors from node_modules
          "strict": true // Enable strict type-checking options
        },
        "include": ["*.ts"]
    }
    
  4. TypeScript에서 JavaScript로 변환합니다.

    tsc
    
  5. 다음 명령을 사용하여 Azure에 로그인합니다.

    az login
    
  6. 다음 명령으로 JavaScript 코드를 실행합니다.

    node index.js
    

코드 설명

인덱스 만들기

hotels_quickstart_index.json 파일을 만듭니다. 이 파일은 다음 단계에서 로드하는 문서를 Azure AI 검색에서 처리하는 방식을 정의합니다. 각 필드는 name으로 식별되고 지정된 type을 갖습니다. 또한 각 필드에는 Azure AI 검색에서 필드를 검색, 필터링 및 정렬하고 패싯을 수행할 수 있는지 여부를 지정하는 일련의 인덱스 특성도 있습니다. 대부분의 필드는 단순 데이터 형식이지만 AddressType과 같은 일부 형식은 인덱스에서 다양한 데이터 구조를 만들 수 있게 해주는 복합 형식입니다. 인덱스 만들기(REST)에 설명된 지원되는 데이터 형식 및 인덱스 특성에 대해 자세히 알아볼 수 있습니다.

메인 함수가 인덱스 정의에 액세스할 수 있도록 hotels_quickstart_index.json을 가져오려고 합니다.

import indexDefinition from './hotels_quickstart_index.json';

interface HotelIndexDefinition {
    name: string;
    fields: SimpleField[] | ComplexField[];
    suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;

그런 다음, main 함수 내에서 Azure AI 검색의 인덱스를 만들고 관리하는 데 사용되는 SearchIndexClient를 만듭니다.

const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));

다음으로, 인덱스가 이미 있으면 이를 삭제해야 합니다. 이 작업은 테스트/데모 코드에 대한 일반적인 사례입니다.

이 작업은 인덱스 삭제를 시도하는 간단한 함수를 정의하여 수행합니다.

async function deleteIndexIfExists(indexClient: SearchIndexClient, indexName: string): Promise<void> {
    try {
        await indexClient.deleteIndex(indexName);
        console.log('Deleting index...');
    } catch {
        console.log('Index does not exist yet.');
    }
}

이 함수를 실행하기 위해 인덱스 정의에서 인덱스 이름을 추출하고 indexNameindexClient와 함께 deleteIndexIfExists() 함수에 전달합니다.

// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;

console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);

그러면 createIndex() 메서드를 사용하여 인덱스를 만들 준비가 되었습니다.

console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);

console.log(`Index named ${index.name} has been created.`);

문서 로드

Azure AI 검색에서 문서는 인덱싱에 대한 입력과 쿼리의 출력 모두에 해당하는 데이터 구조입니다. 이러한 데이터를 인덱스에 푸시하거나 인덱서를 사용할 수 있습니다. 이 경우 프로그래밍 방식으로 문서를 인덱스로 푸시합니다.

문서 입력은 데이터베이스의 행, Blob Storage의 Blob 또는 이 샘플처럼 디스크의 JSON 문서일 수 있습니다. 다음 콘텐츠를 사용하여 hotels.json을 다운로드하거나 자체 hotels.json 파일을 만들 수 있습니다.

indexDefinition을 사용하여 수행한 작업과 마찬가지로 main 함수에서 데이터에 액세스할 수 있도록 hotels.json의 위쪽에 있는 을 가져와야 합니다.

import hotelData from './hotels.json';

interface Hotel {
    HotelId: string;
    HotelName: string;
    Description: string;
    Category: string;
    Tags: string[];
    ParkingIncluded: string | boolean;
    LastRenovationDate: string;
    Rating: number;
    Address: {
        StreetAddress: string;
        City: string;
        StateProvince: string;
        PostalCode: string;
    };
};

const hotels: Hotel[] = hotelData["value"];

데이터를 검색 인덱스로 인덱싱하려면 이제 SearchClient를 만들어야 합니다. SearchIndexClient는 인덱스를 만들고 관리하는 데 사용되지만, SearchClient는 문서를 업로드하고 인덱스를 쿼리하는 데 사용됩니다.

SearchClient를 만드는 방법은 두 가지입니다. 첫 번째 옵션은 SearchClient를 처음부터 만드는 것입니다.

 const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));

또는 getSearchClient()SearchIndexClient 메서드를 사용하여 SearchClient를 만들 수 있습니다.

const searchClient = indexClient.getSearchClient<Hotel>(indexName);

이제 클라이언트가 정의되었으므로 문서를 검색 인덱스에 업로드합니다. 이 경우, 동일한 키를 가진 문서가 이미 있는 경우 문서를 업로드하거나 기존 문서와 병합하는 mergeOrUploadDocuments() 메서드를 사용합니다. 최소한 첫 번째 문서가 있으므로 작업이 성공했는지 확인합니다.

console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);

console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);

tsc && node index.ts를 사용하여 프로그램을 다시 실행합니다. 1단계에서 본 것과 약간 다른 메시지 세트가 표시됩니다. 이번에는 인덱스가 있으며, 앱에서 새 인덱스를 만들어 데이터를 이 인덱스에 게시하기 전에 해당 인덱스를 삭제하라는 메시지가 표시됩니다.

다음 단계에서 쿼리를 실행하기 전에 프로그램에서 1초 동안 대기하도록 함수를 정의합니다. 이 작업은 인덱싱이 완료되고 쿼리에 대한 인덱스에서 문서를 사용할 수 있도록 하기 위해 테스트/데모 용도로만 수행됩니다.

function sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

프로그램을 1초 동안 기다리게 하려면 sleep 함수를 호출합니다.

sleep(1000);

인덱스 검색

인덱스가 만들어지고 문서가 업로드되면 쿼리를 인덱스에 보낼 준비가 되었습니다. 이 섹션에서는 사용할 수 있는 다양한 쿼리 기능을 보여 주기 위해 5개의 다른 쿼리를 검색 인덱스에 보냅니다.

쿼리는 다음과 같이 메인 함수에서 호출하는 sendQueries() 함수에 작성됩니다.

await sendQueries(searchClient);

쿼리는 search()searchClient 메서드를 사용하여 보냅니다. 첫 번째 매개 변수는 검색 텍스트이고, 두 번째 매개 변수는 검색 옵션을 지정합니다.

쿼리 예 1

첫 번째 쿼리는 모든 항목을 검색하는 것과 동일한 *를 검색하고 인덱스에서 세 개의 필드를 선택합니다. 불필요한 데이터를 다시 끌어오면 쿼리의 대기 시간이 늘어날 수 있으므로 필요한 필드만 선택(select)하는 것이 좋습니다.

이 쿼리에 대한 searchOptions에도 includeTotalCount로 설정된 true가 있으며, 이 경우 일치하는 결과의 수를 반환합니다.

async function sendQueries(
    searchClient: SearchClient<Hotel>
): Promise<void> {

    // Query 1
    console.log('Query #1 - search everything:');
    const selectFields: SearchFieldArray<Hotel> = [
        "HotelId",
        "HotelName",
        "Rating",
    ];
    const searchOptions1 = { 
        includeTotalCount: true, 
        select: selectFields 
    };

    let searchResults = await searchClient.search("*", searchOptions1);
    for await (const result of searchResults.results) {
        console.log(`${JSON.stringify(result.document)}`);
    }
    console.log(`Result count: ${searchResults.count}`);

    // remaining queries go here
}

아래에 설명된 나머지 쿼리도 sendQueries() 함수에 추가해야 합니다. 이 경우 읽기 쉽도록 구분됩니다.

쿼리 예 2

다음 쿼리에서는 "wifi"라는 검색 용어를 지정하고, 상태가 'FL'인 결과만 반환하는 필터도 포함합니다. 결과는 Hotel의 Rating을 기준으로 정렬됩니다.

console.log('Query #2 - search with filter, orderBy, and select:');
let state = 'FL';
const searchOptions2 = {
    filter: odata`Address/StateProvince eq ${state}`,
    orderBy: ["Rating desc"],
    select: selectFields
};
searchResults = await searchClient.search("wifi", searchOptions2);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예 3

다음으로, searchFields 매개 변수를 사용하여 검색을 검색 가능한 단일 필드로 제한합니다. 이 접근 방식은 특정 필드의 일치에만 관심이 있는 경우 쿼리를 더 효율적으로 만들 수 있는 좋은 옵션입니다.

console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("Sublime Palace", searchOptions3);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예 4

쿼리에 포함하는 또 다른 일반적인 옵션은 facets입니다. 패싯을 사용하면 UI의 결과에서 자기 주도형 드릴다운을 제공할 수 있습니다. 패싯 결과를 결과 창의 확인란으로 전환할 수 있습니다.

console.log('Query #4 - limit searchFields and use facets:');
const searchOptions4 = {
    facets: ["Category"],
    select: selectFields,
    searchFields: ["HotelName"] as const
};

searchResults = await searchClient.search("*", searchOptions4);
for await (const result of searchResults.results) {
    console.log(`${JSON.stringify(result.document)}`);
}

쿼리 예 5

최종 쿼리는 getDocument()searchClient 메서드를 사용합니다. 이렇게 하면 해당 키를 통해 문서를 효율적으로 검색할 수 있습니다.

console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)

쿼리 요약

이전 쿼리는 전체 텍스트 검색, 필터 및 자동 완성과 같은 쿼리에서 용어를 일치시키는 여러 방법을 보여줍니다.

전체 텍스트 검색 및 필터는 searchClient.search 메서드를 사용하여 수행됩니다. 검색 쿼리는 searchText 문자열에 전달될 수 있으며, 필터 식은 filter 클래스의 SearchOptions 속성에 전달될 수 있습니다. 검색하지 않고 필터링하려면 searchText 메서드의 search 매개 변수에 대한 "*"를 전달합니다. 필터링하지 않고 검색하려면 filter 속성을 설정하지 않고 그대로 두거나 SearchOptions 인스턴스에 전달하지 않아야 합니다.

자원을 정리하세요

자체 구독에서 작업할 때 만든 리소스가 여전히 필요한지 여부를 결정하여 프로젝트를 완료하는 것이 좋습니다. 실행 상태로 방치된 리소스는 비용이 들 수 있습니다. 리소스를 개별적으로 삭제하거나 리소스 그룹을 삭제하여 전체 리소스 집합을 삭제할 수 있습니다.

Azure Portal의 왼쪽 창에서 모든 리소스 또는 리소스 그룹을 선택하여 리소스를 찾고 관리할 수 있습니다.

무료 서비스를 사용하는 경우 인덱스, 인덱서, 데이터 원본 3개로 제한됩니다. Azure Portal에서 개별 항목을 삭제하여 제한 이하로 유지할 수 있습니다.