Mulai cepat: Pencarian teks lengkap menggunakan Azure SDK

Pelajari cara menggunakan pustaka klien Azure.Search.Documents di Azure SDK untuk membuat, memuat, dan mengkueri indeks pencarian menggunakan data sampel untuk pencarian teks lengkap. Pencarian teks lengkap menggunakan Apache Lucene untuk pengindeksan dan kueri, dan algoritma peringkat BM25 untuk hasil penilaian.

Mulai cepat ini memiliki langkah-langkah untuk SDK berikut:

Prasyarat

  • Akun Azure dengan langganan aktif. Buat akun secara gratis.

  • Azure AI layanan Pencarian. Buat layanan jika Anda tidak memilikinya. Anda dapat menggunakan tingkat gratis untuk mulai cepat ini.

  • Kunci API dan titik akhir layanan. Masuk ke portal Azure dan temukan layanan pencarian Anda.

    Di Gambaran Umum, salin URL dan simpan ke Notepad untuk langkah selanjutnya. Contoh titik akhir mungkin terlihat sepertihttps://mydemo.search.windows.net.

    Di Kunci, salin dan simpan kunci admin untuk hak penuh untuk membuat dan menghapus objek. Ada dua kunci primer dan sekunder yang dapat dipertukarkan. Pilih salah satu.

    Get an HTTP endpoint and access key

Membuat, memuat, dan mengkueri indeks

Pilih bahasa pemrograman untuk langkah berikutnya. Pustaka klien Azure.Search.Documents tersedia di Azure SDK untuk .NET, Python, Java, dan JavaScript.

Buat aplikasi konsol menggunakan pustaka klien Azure.Search.Documents untuk membuat, memuat, dan mengkueri indeks pencarian. Atau, Anda dapat mengunduh kode sumber untuk memulai dengan proyek yang sudah selesai atau mengikuti langkah-langkah ini untuk membuat sendiri.

Menyiapkan lingkungan Anda

  1. Mulai Visual Studio dan buat proyek baru untuk aplikasi konsol.

  2. Pada Tools>Paket Pengelolah NuGet , Pilih Kelola Paket NuGet Packages untuk Solusi....

  3. Pilih Telusur.

  4. Cari paket Azure.Search.Documents dan pilih versi 11.0 atau yang lebih baru.

  5. Pilih Instal di sebelah kanan untuk menambahkan rakitan ke proyek dan solusi Anda.

Buat klien pencarian

  1. Pada Program.cs, cubah namespace menjadi AzureSearch.SDK.Quickstart.v11 dan kemudian tambahkan yang berikut ini using arahan.

    using Azure;
    using Azure.Search.Documents;
    using Azure.Search.Documents.Indexes;
    using Azure.Search.Documents.Indexes.Models;
    using Azure.Search.Documents.Models;
    
  2. Buat dua klien: SearchIndexClient membuat indeks, dan SearchClient memuat dan menanyakan indeks yang ada. Keduanya memerlukan titik akhir layanan dan kunci API admin untuk autentikasi dengan hak buat/hapus.

    Karena kode membangun URI untuk Anda, tentukan hanya nama layanan pencarian di properti "serviceName".

     static void Main(string[] args)
     {
         string serviceName = "<your-search-service-name>";
         string apiKey = "<your-search-service-admin-api-key>";
         string indexName = "hotels-quickstart";
    
         // Create a SearchIndexClient to send create/delete index commands
         Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
         AzureKeyCredential credential = new AzureKeyCredential(apiKey);
         SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
    
         // Create a SearchClient to load and query documents
         SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
         . . . 
     }
    

Buat indeks

Panduan memulai ini membuat indeks Hotel yang akan Anda muat dengan data hotel dan menjalankan kueri. pada langkah ini, tentukan bidang dalam file index. Setiap definisi bidang menyertakan nama, tipe data, dan atribut yang menentukan cara bidang digunakan.

Dalam contoh ini, metode sinkron pustaka Azure.Search.Documents digunakan untuk kesederhanaan dan keterbacaan. Namun, untuk skenario produksi, Anda harus menggunakan metode asinkron agar aplikasi Anda tetap skalabel dan responsif. Misalnya, Anda akan menggunakan CreateIndexAsync daripada CreateIndex.

  1. Tambahkan definisi kelas kosong ke proyek Anda: Hotel.cs

  2. Salin kode berikut ke dalam Hotel.cs untuk menentukan struktur dokumen hotel. Atribut di bidang menentukan bagaimana atribut digunakan dalam aplikasi. Contoh IsFilterableatribut harus ditetapkan ke setiap bidang yang mendukung ekspresi filter.

    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(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)]
            [JsonPropertyName("Description_fr")]
            public string DescriptionFr { 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; }
        }
    }
    

    Di Pustaka klien Azure.Search.Documents, Anda dapat menggunakan SearchableField dan SimpleField untuk merampingkan definisi bidang. Keduanya merupakan turunan dari SearchField dan berpotensi menyederhanakan kode Anda:

    • SimpleField dapat berupa tipe data apa pun, selalu tidak dapat ditelusuri (diabaikan untuk kueri penelusuran teks lengkap), dan dapat diambil kembali (tidak disembunyikan). Atribut lain tidak aktif secara default, tetapi dapat diaktifkan. Anda mungkin menggunakan SimpleField untuk ID dokumen atau bidang yang hanya digunakan dalam filter, faset, atau profil penilaian. Jika demikian, pastikan untuk menerapkan atribut apa pun yang diperlukan untuk skenario, seperti IsKey = true untuk ID dokumen. Untuk informasi lebih lanjut, lihat SimpleFieldAttribute.cs dalam kode sumber.

    • SearchableField harus berupa string, dan selalu dapat dicari dan diambil. Atribut lain tidak aktif secara default, tetapi dapat diaktifkan. Karena jenis bidang ini dapat dicari, ia mendukung sinonim dan kelengkapan lengkap properti penganalisis. atau informasi lebih lanjut, lihat SearchableFieldAttribute.csdalam kode sumber.

    Apakah Anda menggunakan dasar SearchField API atau salah satu model pembantu, Anda harus secara eksplisit mengaktifkan atribut filter, faset, dan sort. Contoh, IsFilterable, IsSortable, and IsFacetable harus secara eksplisit dikaitkan, seperti yang ditunjukkan pada sampel di atas.

  3. Tambahkan definisi kelas kosong kedua ke proyek Anda: Address.cs. Salin kode berikut ke dalam kelas.

    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; }
         }
     }
    
  4. Buat dua kelas lagi: Hotel.Methods.cs dan Address.Methods.cs untuk ToString() menimpa. Kelas-kelas ini digunakan untuk membuat hasil pencarian di keluaran konsol. Konten kelas ini tidak disediakan dalam artikel ini, tetapi Anda dapat menyalin kode dari file di GitHub.

  5. Pada Program.cs, membuat obyek SearchIndex, dan kemudian memanggil metode CreateIndex untuk mengekspresikan indeks dalam layanan pencarian Anda. Indeks juga termasuk SearchSuggester untuk mengaktifkan pelengkapan otomatis pada bidang yang ditentukan.

     // Create hotels-quickstart index
     private static void CreateIndex(string indexName, SearchIndexClient adminClient)
     {
         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);
    
         adminClient.CreateOrUpdateIndex(definition);
     }
    

Muat dokumen

Pencarian Azure AI mencari konten yang disimpan dalam layanan. Pada langkah ini, Anda akan memuat dokumen JSON yang sesuai dengan indeks hotel yang baru saja Anda buat.

Di Pencarian Azure AI, dokumen pencarian adalah struktur data yang merupakan input ke pengindeksan dan output dari kueri. Seperti yang diperoleh dari sumber data eksternal, input dokumen mungkin berupa baris dalam database, blob dalam penyimpanan Blob, atau dokumen JSON pada disk. Dalam contoh ini, pintasan diambil dan dokumen JSON disematkan untuk empat hotel dalam kode itu sendiri.

Saat mengunggah dokumen, Anda harus menggunakan obyek IndexDocumentsBatch. Objek IndexDocumentsBatch berisi kumpulan Tindakan, yang masing-masing berisi dokumen dan properti yang memberi tahu Azure AI Cari tindakan apa yang harus dilakukan (unggah, gabungkan, hapus, dan gabungkanOrUpload).

  1. Pada Program.cs, buat larik dokumen dan tindakan indeks, lalu berikan larik ke IndexDocumentsBatch. Dokumen di bawah ini sesuai dengan indeks hotel-quickstart, seperti yang ditentukan oleh kelas hotel.

    // 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 = "Secret Point Motel",
                    Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's 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.",
                    DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.",
                    Category = "Boutique",
                    Tags = new[] { "pool", "air conditioning", "concierge" },
                    ParkingIncluded = false,
                    LastRenovationDate = new DateTimeOffset(1970, 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 = "Twin Dome Motel",
                    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.",
                    DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
                    Category = "Boutique",
                    Tags = new[] { "pool", "free wifi", "concierge" },
                    ParkingIncluded = false,
                    LastRenovationDate = new DateTimeOffset(1979, 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 = "Triple Landscape Hotel",
                    Description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.",
                    DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
                    Category = "Resort and Spa",
                    Tags = new[] { "air conditioning", "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 Cliff Hotel",
                    Description = "Sublime Cliff 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 1800 palace.",
                    DescriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
                    Category = "Boutique",
                    Tags = new[] { "concierge", "view", "24-hour front desk service" },
                    ParkingIncluded = true,
                    LastRenovationDate = new DateTimeOffset(1960, 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}");
        }
    }
    

    Setelah Anda menginisialisasi IndexDocumentsBatch objek, Anda dapat mengirimkannya ke indeks dengan memanggil IndexDocuments pada obyek SearchClient Anda.

  2. Tambahkan baris berikut ke Main(). Pemuatan dokumen dilakukan menggunakan SearchClient, tetapi operasi juga memerlukan hak admin pada layanan, yang biasanya dikaitkan dengan SearchIndexClient. Salah satu cara untuk mengatur operasi ini adalah untuk mendapatkan SearchClient melalui SearchIndexClient (adminClient dalam contoh ini).

     SearchClient ingesterClient = adminClient.GetSearchClient(indexName);
    
     // Load documents
     Console.WriteLine("{0}", "Uploading documents...\n");
     UploadDocuments(ingesterClient);
    
  3. Karena ini adalah aplikasi konsol yang menjalankan semua perintah secara berurutan, tambahkan waktu tunggu 2 detik antara pengindeksan dan kueri.

    // 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);
    

    Penundaan 2 detik mengkompensasi pengindeksan, yang tidak sinkron, sehingga semua dokumen dapat diindeks sebelum kueri dieksekusi. Pengodean dalam penundaan biasanya hanya diperlukan dalam demo, pengujian, dan aplikasi contoh.

Mencari indeks

Anda bisa mendapatkan hasil kueri segera setelah dokumen pertama diindeks, tetapi pengujian aktual indeks Anda harus menunggu hingga semua dokumen diindeks.

Bagian ini menambahkan dua bagian fungsionalitas: logika kueri, dan hasil. Untuk pertanyaan, gunakan metode Cari. Metode ini mengambil teks pencarian (string kueri) dan opsi lainnya.

SearchResults Metode ini mengambil teks pencarian (string kueri) serta lainnya.

  1. Pada Program.cs, buatWriteDocuments metode yang mencetak hasil pencarian ke konsol.

    // 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. Buat metode RunQueries untuk menjalankan permintaan dan mengembalikan hasil. Hasilnya merupakan objek Hotel. Sampel ini menunjukkan metode tanda tangan dan permintaan pertama. Permintaan ini menunjukkan parameter terpilih yang memungkinkan anda untuk membuat hasil menggunakan bidang terpilih dari dokumen.

    // Run queries, use WriteDocuments to print output
    private static void RunQueries(SearchClient srchclient)
    {
        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 = srchclient.Search<Hotel>("*", options);
        WriteDocuments(response);
    
  3. Dalam permintaan kedua, cari sebuah istilah, tambahkan filter yang memilih dokumen di mana Peringkat yang lebih besar dari 4, lalu urutkan menurut Peringkat dalam urutan menurun. Filter merupakan ekspresi boolean yang dievaluasi atas bidang IsFilterable dalam indeks. Filter permintaan baik itu menyertakan atau mengecualikan nilai. Dengan demikian, tidak ada skor relevansi yang terkait dengan kueri filter.

    // 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 = srchclient.Search<Hotel>("hotels", options);
    WriteDocuments(response);
    
  4. Permintaan ketiga menunjukkan searchFields, digunakan untuk cakupan operasi pencarian teks penuh ke bidang tertentu.

    // 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 = srchclient.Search<Hotel>("pool", options);
    WriteDocuments(response);
    
  5. Permintaan keempat menunjukkan faset, di mana dapat digunakan untuk menyusun struktur navigasi bertatap muka.

     // 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 = srchclient.Search<Hotel>("*", options);
     WriteDocuments(response);
    
  6. Di permintaan kelima, kembalikan sebuah dokumen tertentu. Pencarian dokumen merupakan respons umum terhadap status OnClick dalam kumpulan hasil.

     // Query 5
     Console.WriteLine("Query #5: Look up a specific document...\n");
    
     Response<Hotel> lookupResponse;
     lookupResponse = srchclient.GetDocument<Hotel>("3");
    
     Console.WriteLine(lookupResponse.Value.HotelId);
    
  7. Permintaan terakhir memperlihatkan sintaks untuk kelengkapan otomatis, mensimulasikan input pengguna separuh "sa" yang menyelesaikan dua kemungkinan kecocokan di sourceFields yang terkait dengan saran yang Anda tentukan dalam indeks.

     // Query 6
     Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
    
     var autoresponse = srchclient.Autocomplete("sa", "sg");
     WriteDocuments(autoresponse);
    
  8. Tambahkan RunQueries ke Main().

    // Call the RunQueries method to invoke a series of queries
    Console.WriteLine("Starting queries...\n");
    RunQueries(srchclient);
    
    // End the program
    Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
    Console.ReadKey();
    

Permintaan sebelumnya memperlihatkan beberapa cara pencocokan istilah dalam permintaan:pencarian teks lengkap, filter, dan kelengkapan otomatis.

Pencarian dan filter teks lengkap dilakukan menggunakan metode SearchClient.Search. Sebuah permintaan penelusuran dapat diteruskan dalam searchText string, sementara ekspresi filter dapat diteruskan dalam Filter properti dari kelas SearchOptions. Untuk memfilter tanpa mencari, "*" cukup searchText lewati parameter metode Pencarian. Untuk mencari tanpa pemfilteran, biarkan Filter properti tidak diatur, atau tidak meneruskan SearchOptions instans sama sekali.

Jalankan program

Tekan F5 untuk membangun kembali aplikasi dan menjalankan program secara keseluruhan.

Output mencakup pesan dari Console.WriteLine, dengan penambahan informasi dan hasil permintaan.

Membersihkan sumber daya

Saat bekerja dengan langganan Anda sendiri, sebaiknya identifikasi apakah Anda masih membutuhkan sumber daya yang Anda buat di akhir proyek. Sumber daya yang dibiarkan berjalan dapat menghabiskan uang Anda. Anda dapat menghapus sumber daya satu per satu atau menghapus grup sumber daya untuk menghapus seluruh rangkaian sumber daya.

Anda dapat menemukan dan mengelola sumber daya di portal, menggunakan tautan Semua sumber daya atau Grup sumber daya di panel navigasi kiri.

Jika Anda menggunakan layanan gratis, ingatlah bahwa Anda terbatas pada tiga indeks, pengindeks, dan sumber data. Anda dapat menghapus item individu di portal agar tetap berada dalam batasan.

Langkah berikutnya

Dalam mulai cepat ini, Anda bekerja melalui serangkaian tugas untuk membuat indeks, memuatnya dengan dokumen, dan menjalankan kueri. Pada tahap yang berbeda, pintasan diambil untuk menyederhanakan kode agar mudah dibaca dan dipahami. Sekarang setelah Anda terbiasa dengan konsep dasar, coba tutorial yang memanggil API Pencarian Azure AI di aplikasi web.