Exempel: Skapa en anpassad färdighet med API:et för entitetssökning i Bing

I det här exemplet får du lära dig hur du skapar en anpassad färdighet för webb-API:et. Den här färdigheten accepterar platser, offentliga personer och organisationer och returnerar beskrivningar för dem. I exemplet används en Azure-funktion för att omsluta API:et för entitetssökning i Bing så att det implementerar det anpassade kompetensgränssnittet.

Förutsättningar

  • Läs mer om artikeln om anpassat kunskapsgränssnitt om du inte är bekant med indata-/utdatagränssnittet som en anpassad färdighet ska implementera.

  • Skapa en Bing Search-resurs via Azure-portalen. En kostnadsfri nivå är tillgänglig och tillräcklig för det här exemplet.

  • Installera Visual Studio eller senare.

Skapa en Azure-funktion

Även om det här exemplet använder en Azure-funktion som värd för ett webb-API, krävs det inte. Så länge du uppfyller gränssnittskraven för en kognitiv färdighet är den metod du använder oväsentlig. Azure Functions gör det dock enkelt att skapa en anpassad färdighet.

Skapa ett projekt

  1. I Visual Studio väljer du Nytt>projekt på menyn Arkiv.

  2. Välj Azure Functions som mall och välj Nästa. Skriv ett namn på projektet och välj Skapa. Funktionsappens namn måste vara giltigt som ett C#-namnområde, så använd inte understreck, bindestreck eller andra icke-alfanumeriska tecken.

  3. Välj ett ramverk som har långsiktigt stöd.

  4. Välj HTTP-utlösare för den typ av funktion som ska läggas till i projektet.

  5. Välj Funktion för auktoriseringsnivån.

  6. Välj Skapa för att skapa funktionsprojektet och funktionen HTTP-utlöst.

Lägga till kod för att anropa API:et för entitet i Bing

Visual Studio skapar ett projekt med pannplåtskod för den valda funktionstypen. Attributet FunctionName i metoden anger namnet på funktionen. Attributet HttpTrigger anger att funktionen utlöses av en HTTP-förfrågan.

Ersätt innehållet i Function1.cs med följande kod:

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

Se till att ange ditt eget nyckelvärde i konstanten key baserat på den nyckel du fick när du registrerade dig för API:et för entitetssökning i Bing.

Testa funktionen från Visual Studio

Tryck på F5 för att köra programmet och testa funktionsbeteenden. I det här fallet använder vi funktionen nedan för att leta upp två entiteter. Använd en REST-klient för att utfärda ett anrop som det som visas nedan:

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

Request body

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

Response

Du bör se ett svar som liknar följande exempel:

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

Publicera funktionen i Azure

När du är nöjd med funktionsbeteendet kan du publicera det.

  1. I Solution Explorer högerklickar du på projektet och väljer Publicera. Välj Skapa ny>publicering.

  2. Om du inte redan har anslutit Visual Studio till ditt Azure-konto väljer du Lägg till ett konto.

  3. Följ anvisningarna på skärmen. Du uppmanas att ange ett unikt namn för din apptjänst, Azure-prenumerationen, resursgruppen, värdplanen och det lagringskonto som du vill använda. Du kan skapa en ny resursgrupp, en ny värdplan och ett lagringskonto om du inte redan har dessa. När du är klar väljer du Skapa

  4. När distributionen är klar lägger du märke till webbplats-URL:en. Det är adressen till din funktionsapp i Azure.

  5. I Azure-portalen navigerar du till resursgruppen och letar efter den EntitySearch funktion som du publicerade. Under avsnittet Hantera bör du se Värdnycklar. Välj ikonen Kopiera för standardvärdnyckeln.

Testa funktionen i Azure

Nu när du har standardvärdnyckeln testar du funktionen på följande sätt:

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

Begärandetext

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

Det här exemplet bör ge samma resultat som du såg tidigare när du körde funktionen i den lokala miljön.

Anslut till din pipeline

Nu när du har en ny anpassad färdighet kan du lägga till den i din kompetensuppsättning. Exemplet nedan visar hur du anropar färdigheten för att lägga till beskrivningar till organisationer i dokumentet (detta kan utökas till att även fungera på platser och personer). Ersätt [your-entity-search-app-name] med namnet på din app.

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

Här räknar vi med att den inbyggda entitetsigenkänningsfärdigheten finns i kompetensuppsättningen och att ha berikat dokumentet med listan över organisationer. Som referens är här en kompetenskonfiguration för entitetsextrahering som räcker för att generera de data vi behöver:

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

Nästa steg

Grattis! Du har skapat din första anpassade färdighet. Nu kan du följa samma mönster för att lägga till dina egna anpassade funktioner. Klicka på följande länkar om du vill veta mer.