Contoh: Membuat keterampilan kustom menggunakan API Bing Entity Search

Dalam contoh ini, pelajari cara membuat keterampilan kustom API web. Keterampilan ini akan menerima lokasi, tokoh publik, dan organisasi, dan mengembalikan deskripsi untuk mereka. Contoh tersebut menggunakan Azure Function untuk membungkus API Bing Entity Search sehingga mengimplementasikan antarmuka keahlian khusus.

Prasyarat

Buat Fungsi Azure

Meskipun contoh ini menggunakan Azure Function untuk menghosting API web, itu tidak diperlukan. Selama Anda memenuhi persyaratan antarmuka untuk keterampilan kognitif, pendekatan yang Anda ambil tidaklah penting. Namun, Azure Functions memudahkan untuk membuat keterampilan kustom.

Membuat aplikasi fungsi

  1. Di Visual Studio, pilih Proyek > Baru dari menu File.

  2. Pada dialog Proyek Baru, pilih Azure Functions sebagai templat dan pilih Berikutnya. Ketik nama untuk proyek Anda dan pilih Buat. Nama aplikasi fungsi harus valid sebagai namespace layanan C#, jadi jangan gunakan garis bawah, tanda hubung, atau karakter non-alfanumerik lainnya.

  3. Pilih jenis yang akan menjadi Pemicu HTTP

  4. Untuk Akun Penyimpanan, Anda dapat memilih Tidak Ada, karena Anda tidak memerlukan penyimpanan apa pun untuk fungsi ini.

  5. Pilih Buat untuk membuat proyek fungsi dan fungsi yang dipicu HTTP.

Ubah kode untuk memanggil Layanan Bing Entity Search

Visual Studio membuat proyek dan kelas yang berisi kode boilerplate untuk jenis fungsi yang dipilih. Atribut FunctionName pada metode menetapkan nama fungsi. Atribut HttpTrigger menetapkan bahwa fungsi dipicu oleh permintaan HTTP.

Sekarang, ganti semua isi file Function1.cs dengan kode berikut:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace SampleSkills
{
    /// <summary>
    /// Sample custom skill that wraps the Bing entity search API to connect it with a 
    /// AI enrichment pipeline.
    /// </summary>
    public static class BingEntitySearch
    {
        #region Credentials
        // IMPORTANT: Make sure to enter your credential and to verify the API endpoint matches yours.
        static readonly string bingApiEndpoint = "https://api.bing.microsoft.com/v7.0/entities";
        static readonly string key = "<enter your api key here>";  
        #endregion

        #region Class used to deserialize the request
        private class InputRecord
        {
            public class InputRecordData
            {
                public string Name { get; set; }
            }

            public string RecordId { get; set; }
            public InputRecordData Data { get; set; }
        }

        private class WebApiRequest
        {
            public List<InputRecord> Values { get; set; }
        }
        #endregion

        #region Classes used to serialize the response

        private class OutputRecord
        {
            public class OutputRecordData
            {
                public string Name { get; set; } = "";
                public string Description { get; set; } = "";
                public string Source { get; set; } = "";
                public string SourceUrl { get; set; } = "";
                public string LicenseAttribution { get; set; } = "";
                public string LicenseUrl { get; set; } = "";
            }

            public class OutputRecordMessage
            {
                public string Message { get; set; }
            }

            public string RecordId { get; set; }
            public OutputRecordData Data { get; set; }
            public List<OutputRecordMessage> Errors { get; set; }
            public List<OutputRecordMessage> Warnings { get; set; }
        }

        private class WebApiResponse
        {
            public List<OutputRecord> Values { get; set; }
        }
        #endregion

        #region Classes used to interact with the Bing API
        private class BingResponse
        {
            public BingEntities Entities { get; set; }
        }
        private class BingEntities
        {
            public BingEntity[] Value { get; set; }
        }

        private class BingEntity
        {
            public class EntityPresentationinfo
            {
                public string[] EntityTypeHints { get; set; }
            }

            public class License
            {
                public string Url { get; set; }
            }

            public class ContractualRule
            {
                public string _type { get; set; }
                public License License { get; set; }
                public string LicenseNotice { get; set; }
                public string Text { get; set; }
                public string Url { get; set; }
            }

            public ContractualRule[] ContractualRules { get; set; }
            public string Description { get; set; }
            public string Name { get; set; }
            public EntityPresentationinfo EntityPresentationInfo { get; set; }
        }
        #endregion

        #region The Azure Function definition

        [FunctionName("EntitySearch")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("Entity Search function: C# HTTP trigger function processed a request.");

            var response = new WebApiResponse
            {
                Values = new List<OutputRecord>()
            };

            string requestBody = new StreamReader(req.Body).ReadToEnd();
            var data = JsonConvert.DeserializeObject<WebApiRequest>(requestBody);

            // Do some schema validation
            if (data == null)
            {
                return new BadRequestObjectResult("The request schema does not match expected schema.");
            }
            if (data.Values == null)
            {
                return new BadRequestObjectResult("The request schema does not match expected schema. Could not find values array.");
            }

            // Calculate the response for each value.
            foreach (var record in data.Values)
            {
                if (record == null || record.RecordId == null) continue;

                OutputRecord responseRecord = new OutputRecord
                {
                    RecordId = record.RecordId
                };

                try
                {
                    responseRecord.Data = GetEntityMetadata(record.Data.Name).Result;
                }
                catch (Exception e)
                {
                    // Something bad happened, log the issue.
                    var error = new OutputRecord.OutputRecordMessage
                    {
                        Message = e.Message
                    };

                    responseRecord.Errors = new List<OutputRecord.OutputRecordMessage>
                    {
                        error
                    };
                }
                finally
                {
                    response.Values.Add(responseRecord);
                }
            }

            return (ActionResult)new OkObjectResult(response);
        }

        #endregion

        #region Methods to call the Bing API
        /// <summary>
        /// Gets metadata for a particular entity based on its name using Bing Entity Search
        /// </summary>
        /// <param name="entityName">The name of the entity to extract data for.</param>
        /// <returns>Asynchronous task that returns entity data. </returns>
        private async static Task<OutputRecord.OutputRecordData> GetEntityMetadata(string entityName)
        {
            var uri = bingApiEndpoint + "?q=" + entityName + "&mkt=en-us&count=10&offset=0&safesearch=Moderate";
            var result = new OutputRecord.OutputRecordData();

            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage {
                Method = HttpMethod.Get,
                RequestUri = new Uri(uri)
            })
            {
                request.Headers.Add("Ocp-Apim-Subscription-Key", key);

                HttpResponseMessage response = await client.SendAsync(request);
                string responseBody = await response?.Content?.ReadAsStringAsync();

                BingResponse bingResult = JsonConvert.DeserializeObject<BingResponse>(responseBody);
                if (bingResult != null)
                {
                    // In addition to the list of entities that could match the name, for simplicity let's return information
                    // for the top match as additional metadata at the root object.
                    return AddTopEntityMetadata(bingResult.Entities?.Value);
                }
            }

            return result;
        }

        private static OutputRecord.OutputRecordData AddTopEntityMetadata(BingEntity[] entities)
        {
            if (entities != null)
            {
                foreach (BingEntity entity in entities.Where(
                    entity => entity?.EntityPresentationInfo?.EntityTypeHints != null
                        && (entity.EntityPresentationInfo.EntityTypeHints[0] == "Person"
                            || entity.EntityPresentationInfo.EntityTypeHints[0] == "Organization"
                            || entity.EntityPresentationInfo.EntityTypeHints[0] == "Location")
                        && !String.IsNullOrEmpty(entity.Description)))
                {
                    var rootObject = new OutputRecord.OutputRecordData
                    {
                        Description = entity.Description,
                        Name = entity.Name
                    };

                    if (entity.ContractualRules != null)
                    {
                        foreach (var rule in entity.ContractualRules)
                        {
                            switch (rule._type)
                            {
                                case "ContractualRules/LicenseAttribution":
                                    rootObject.LicenseAttribution = rule.LicenseNotice;
                                    rootObject.LicenseUrl = rule.License.Url;
                                    break;
                                case "ContractualRules/LinkAttribution":
                                    rootObject.Source = rule.Text;
                                    rootObject.SourceUrl = rule.Url;
                                    break;
                            }
                        }
                    }

                    return rootObject;
                }
            }

            return new OutputRecord.OutputRecordData();
        }
        #endregion
    }
}

Pastikan untuk memasukkan nilai kunci Anda sendiri di konstanta key berdasarkan kunci yang Anda dapatkan saat mendaftar ke API Bing entity search.

Sampel ini mencakup semua kode yang diperlukan dalam satu file untuk kenyamanan. Anda dapat menemukan versi yang sedikit lebih terstruktur dari keterampilan yang sama di repositori keterampilan daya.

Tentu saja, Anda dapat mengganti nama file dari Function1.cs menjadi BingEntitySearch.cs.

Menguji fungsi dari Visual Studio

Tekan F5 untuk menjalankan program dan menguji perilaku fungsi. Dalam hal ini, kita akan menggunakan fungsi di bawah ini untuk mencari dua entitas. Gunakan Postman atau Fiddler untuk menerbitkan panggilan seperti yang ditunjukkan di bawah ini:

POST https://localhost:7071/api/EntitySearch

Isi permintaan

{
    "values": [
        {
            "recordId": "e1",
            "data":
            {
                "name":  "Pablo Picasso"
            }
        },
        {
            "recordId": "e2",
            "data":
            {
                "name":  "Microsoft"
            }
        }
    ]
}

Respons

Anda akan melihat respons yang mirip dengan contoh berikut:

{
    "values": [
        {
            "recordId": "e1",
            "data": {
                "name": "Pablo Picasso",
                "description": "Pablo Ruiz Picasso was a Spanish painter [...]",
                "source": "Wikipedia",
                "sourceUrl": "http://en.wikipedia.org/wiki/Pablo_Picasso",
                "licenseAttribution": "Text under CC-BY-SA license",
                "licenseUrl": "http://creativecommons.org/licenses/by-sa/3.0/"
            },
            "errors": null,
            "warnings": null
        },
        "..."
    ]
}

Menerbitkan fungsi ke Azure

Ketika Anda puas dengan perilaku fungsi, Anda dapat menerbitkannya.

  1. Di Penjelajah Solusi, klik kanan proyek dan pilih Terbitkan. Pilih Buat Baru > Terbitkan.

  2. Jika Anda belum menghubungkan Visual Studio ke akun Azure Anda, pilih Tambahkan akun....

  3. Ikuti permintaan di layar. Anda diminta untuk menentukan nama unik untuk layanan aplikasi Anda, langganan Azure, grup sumber daya, paket hosting, dan akun penyimpanan yang ingin Anda gunakan. Anda dapat membuat grup sumber daya baru, paket hosting baru, dan akun penyimpanan jika Anda belum memilikinya. Setelah selesai, pilih Buat

  4. Setelah penyebaran selesai, perhatikan URL Situs. Ini adalah alamat aplikasi fungsi Anda di Azure.

  5. Di portal Microsoft Azure, navigasikan ke Grup Sumber Daya, dan cari Fungsi EntitySearch yang Anda terbitkan. Pada bagian Kelola, Anda akan melihat Kunci Host. Pilih ikon Salin untuk kunci host default.

Menguji fungsi di Azure

Sekarang setelah Anda memiliki kunci host default, uji fungsi Anda sebagai berikut:

POST https://[your-entity-search-app-name].azurewebsites.net/api/EntitySearch?code=[enter default host key here]

Isi Permintaan

{
    "values": [
        {
            "recordId": "e1",
            "data":
            {
                "name":  "Pablo Picasso"
            }
        },
        {
            "recordId": "e2",
            "data":
            {
                "name":  "Microsoft"
            }
        }
    ]
}

Contoh ini akan menghasilkan hasil yang sama seperti yang Anda lihat sebelumnya saat menjalankan fungsi di lingkungan lokal.

Sambungkan ke alur Anda

Sekarang setelah Anda memiliki keahlian khusus baru, Anda dapat menambahkannya set keterampilan Anda. Contoh di bawah ini Contoh di bawah ini menunjukkan cara memanggil keterampilan untuk menambahkan deskripsi ke organisasi dalam dokumen (ini dapat diperluas untuk juga berfungsi di lokasi dan orang). Ganti [your-entity-search-app-name] dengan nama aplikasi Anda.

{
    "skills": [
      "[... your existing skills remain here]",  
      {
        "@odata.type": "#Microsoft.Skills.Custom.WebApiSkill",
        "description": "Our new Bing entity search custom skill",
        "uri": "https://[your-entity-search-app-name].azurewebsites.net/api/EntitySearch?code=[enter default host key here]",
          "context": "/document/merged_content/organizations/*",
          "inputs": [
            {
              "name": "name",
              "source": "/document/merged_content/organizations/*"
            }
          ],
          "outputs": [
            {
              "name": "description",
              "targetName": "description"
            }
          ]
      }
  ]
}

Di sini, kami mengandalkan keterampilan pengenalan entitas bawaan untuk hadir dalam kumpulan keterampilan dan telah memperkaya dokumen dengan daftar organisasi. Sebagai referensi, berikut adalah konfigurasi keterampilan ekstraksi entitas yang akan cukup dalam menghasilkan data yang kita butuhkan:

{
    "@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
    "name": "#1",
    "description": "Organization name extraction",
    "context": "/document/merged_content",
    "categories": [ "Organization" ],
    "defaultLanguageCode": "en",
    "inputs": [
        {
            "name": "text",
            "source": "/document/merged_content"
        },
        {
            "name": "languageCode",
            "source": "/document/language"
        }
    ],
    "outputs": [
        {
            "name": "organizations",
            "targetName": "organizations"
        }
    ]
},

Langkah berikutnya

Selamat! Anda telah membuat keterampilan khusus pertama Anda. Sekarang Anda dapat mengikuti pola yang sama untuk menambahkan fungsionalitas kustom Anda sendiri. Klik tautan berikut ini untuk mempelajari selengkapnya.