Příklad: Vytvoření vlastní dovednosti pomocí rozhraní API Bingu pro vyhledávání entit

V tomto příkladu se dozvíte, jak vytvořit vlastní dovednosti webového rozhraní API. Tato dovednost bude přijímat místa, veřejné postavy a organizace a vracet popisy pro ně. Příklad používá funkci Azure Functions k zabalení rozhraní API Bingu pro vyhledávání entit tak, aby implementuje vlastní rozhraní dovedností.

Požadavky

  • Přečtěte si o článku o rozhraní vlastních dovedností, pokud neznáte vstupní/výstupní rozhraní, které by vlastní dovednost měla implementovat.

  • Vytvořte prostředek Bingu pro vyhledávání prostřednictvím webu Azure Portal. Pro tento příklad je dostupná úroveň Free a dostatečná.

  • Nainstalujte sadu Visual Studio nebo novější.

Vytvoření funkce Azure

I když tento příklad používá funkci Azure Functions k hostování webového rozhraní API, nevyžaduje se. Pokud splňujete požadavky rozhraní pro kognitivní dovednost, je přístup, který berete, neměnný. Azure Functions ale usnadňuje vytvoření vlastní dovednosti.

Vytvoření projektu

  1. V sadě Visual Studio vyberte v nabídce Soubor nový >projekt.

  2. Jako šablonu zvolte Azure Functions a vyberte Další. Zadejte název projektu a vyberte Vytvořit. Název aplikace funkcí musí být platný jako obor názvů jazyka C#, takže nepoužívejte podtržítka, pomlčky ani jiné jiné než alfanumerické znaky.

  3. Vyberte architekturu, která má dlouhodobou podporu.

  4. Jako typ funkce, která se má přidat do projektu, zvolte trigger HTTP.

  5. Zvolte funkci pro úroveň autorizace.

  6. Výběrem možnosti Vytvořit vytvoříte projekt funkce a aktivovanou funkci HTTP.

Přidání kódu pro volání rozhraní API entity Bingu

Visual Studio vytvoří projekt s často používaným kódem pro vybraný typ funkce. Atribut FunctionName metody nastavuje název funkce. Atribut HttpTrigger určuje, že je funkce aktivována požadavkem HTTP.

Obsah Function1.cs nahraďte následujícím kódem:

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
    }
}

Nezapomeňte do konstanty key zadat vlastní hodnotu klíče na základě klíče, který jste získali při registraci rozhraní API bingu pro vyhledávání entit.

Testování funkce ze sady Visual Studio

Stisknutím klávesy F5 spusťte program a otestujte chování funkce. V tomto případě pomocí níže uvedené funkce vyhledáme dvě entity. Pomocí klienta REST můžete vydat volání, jako je volání uvedené níže:

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

Request body

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

Reakce

Měla by se zobrazit odpověď podobná následujícímu příkladu:

{
    "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
        },
        "..."
    ]
}

Publikování funkce do Azure

Až budete s chováním funkce spokojeni, můžete ho publikovat.

  1. V Průzkumníku řešení klikněte pravým tlačítkem na požadovaný projekt a vyberte Publikovat. Zvolte Vytvořit nové>publikování.

  2. Pokud jste ještě nepřipojili Visual Studio k účtu Azure, vyberte Přidat účet... .

  3. Postupujte podle výzev na obrazovce. Zobrazí se výzva k zadání jedinečného názvu služby App Service, předplatného Azure, skupiny prostředků, plánu hostování a účtu úložiště, který chcete použít. Pokud ještě nemáte, můžete vytvořit novou skupinu prostředků, nový plán hostování a účet úložiště. Po dokončení vyberte Vytvořit.

  4. Po dokončení nasazení si všimněte adresy URL webu. Je to adresa vaší aplikace funkcí v Azure.

  5. Na webu Azure Portal přejděte do skupiny prostředků a vyhledejte EntitySearch funkci, kterou jste publikovali. V části Správa byste měli vidět klíče hostitele. Vyberte ikonu Kopírovat pro výchozí klíč hostitele.

Testování funkce v Azure

Teď, když máte výchozí klíč hostitele, otestujte funkci následujícím způsobem:

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

Text požadavku

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

Tento příklad by měl vytvořit stejný výsledek, který jste viděli dříve při spuštění funkce v místním prostředí.

Připojení do kanálu

Teď, když máte novou vlastní dovednost, můžete ji přidat do sady dovedností. Následující příklad ukazuje, jak volat dovednosti pro přidání popisů organizacím v dokumentu (to se dá rozšířit tak, aby fungovalo i na místech a lidech). Nahraďte [your-entity-search-app-name] názvem aplikace.

{
    "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"
            }
          ]
      }
  ]
}

Tady počítáme s předdefinovanou dovedností pro rozpoznávání entit, která se v sadě dovedností nachází, a abychom dokument obohatili seznamem organizací. Tady je konfigurace dovedností extrakce entit, která by byla dostatečná při generování potřebných dat:

{
    "@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"
        }
    ]
},

Další kroky

Gratulujeme! Vytvořili jste svou první vlastní dovednost. Teď můžete použít stejný vzor a přidat vlastní funkce. Další informace získáte kliknutím na následující odkazy.