Beispiel: Erstellen einer benutzerdefinierten Qualifikation mit der Bing-Entitätssuche-APIExample: Create a custom skill using the Bing Entity Search API

In diesem Beispiel erfahren Sie, wie Sie eine benutzerdefinierte Qualifikation einer Web-API erstellen.In this example, learn how to create a web API custom skill. Diese Qualifikation akzeptiert Orte, Personen des öffentlichen Lebens und Organisationen und gibt Beschreibungen für diese zurück.This skill will accept locations, public figures, and organizations, and return descriptions for them. In dem Beispiel wird eine Azure-Funktion verwendet, um die Bing-Entitätssuche-API so einzubinden, dass sie die Schnittstelle der benutzerdefinierten Qualifikation implementiert.The example uses an Azure Function to wrap the Bing Entity Search API so that it implements the custom skill interface.

VoraussetzungenPrerequisites

  • Lesen Sie den Artikel über die Schnittstelle für benutzerdefinierte Qualifikationen, wenn Sie nicht mit der Eingabe/Ausgabe-Schnittstelle vertraut sind, die eine benutzerdefinierte Qualifikation implementieren sollte.Read about custom skill interface article if you aren't familiar with the input/output interface that a custom skill should implement.

  • Sie benötigen ein Cognitive Services-API-Konto mit Zugriff auf die Bing-Suche-APIs.You must have a Cognitive Services API account with access to the Bing Search APIs. Falls Sie nicht über ein Azure-Abonnement verfügen, können Sie ein kostenloses Konto erstellen.If you don't have an Azure subscription, you can create an account for free. Vor dem Fortfahren benötigen Sie den Zugriffsschlüssel, der nach dem Aktivieren Ihrer kostenlosen Testversion bereitgestellt wird. Alternativ hierzu können Sie auch den Schlüssel eines kostenpflichtigen Abonnements aus Ihrem Azure-Dashboard verwenden.Before continuing, You will need the access key provided after activating your free trial, or a paid subscription key from your Azure dashboard.

  • Installieren Sie Visual Studio 2019 oder höher zusammen mit der Workload für die Azure-Entwicklung.Install Visual Studio 2019 or later, including the Azure development workload.

Erstellen einer Azure FunctionCreate an Azure Function

Obwohl in diesem Beispiel eine Azure-Funktion zum Hosten einer Web-API verwendet wird, ist dies nicht erforderlich.Although this example uses an Azure Function to host a web API, it isn't required. Solange Sie die Schnittstellenanforderungen für einen kognitiven Skill erfüllen, ist der gewählte Ansatz unerheblich.As long as you meet the interface requirements for a cognitive skill, the approach you take is immaterial. Mit Azure Functions ist das Erstellen eines benutzerdefinierten Skills jedoch einfacher.Azure Functions, however, make it easy to create a custom skill.

Erstellen einer Funktionen-AppCreate a function app

  1. Wählen Sie in Visual Studio im Menü „Datei“ die Optionen Neu > Projekt aus.In Visual Studio, select New > Project from the File menu.

  2. Wählen Sie im Dialogfeld „Neues Projekt“ die Option Installiert, erweitern Sie Visual C# > Cloud, und wählen Sie Azure Functions aus. Geben Sie unter „Name“ einen Namen für Ihr Projekt ein, und wählen Sie OK.In the New Project dialog, select Installed, expand Visual C# > Cloud, select Azure Functions, type a Name for your project, and select OK. Der Name der Funktions-App muss als C#-Namespace gültig sein. Verwenden Sie daher keine Unterstriche, Bindestriche oder andere nicht alphanumerische Zeichen.The function app name must be valid as a C# namespace, so don't use underscores, hyphens, or any other non-alphanumeric characters.

  3. Wählen Sie Azure Functions v2 (.NET Core) aus.Select Azure Functions v2 (.NET Core). Sie können dies auch mit Version 1 erreichen, aber der nachfolgende Code basiert auf der Vorlage der Version 2.You could also do it with version 1, but the code written below is based on the v2 template.

  4. Wählen Sie für den Typ HTTP-Trigger aus.Select the type to be HTTP Trigger

  5. Für das Speicherkonto können Sie Kein auswählen, da Sie für diese Funktion keinen Speicher benötigen.For Storage Account, you may select None, as you won't need any storage for this function.

  6. Wählen Sie OK, um das Funktionsprojekt und die per HTTP ausgelöste Funktion zu erstellen.Select OK to create the function project and HTTP triggered function.

Ändern des Codes zum Aufrufen des Bing-Entitätssuche-DienstsModify the code to call the Bing Entity Search Service

Visual Studio erstellt ein Projekt, das eine Klasse mit den Codebausteinen für den gewählten Funktionstyp enthält.Visual Studio creates a project and in it a class that contains boilerplate code for the chosen function type. Mit dem FunctionName-Attribut der Methode wird der Name der Funktion festgelegt.The FunctionName attribute on the method sets the name of the function. Mit dem HttpTrigger-Attribut wird angegeben, dass die Funktion mit einer HTTP-Anforderung ausgelöst wird.The HttpTrigger attribute specifies that the function is triggered by an HTTP request.

Ersetzen Sie alle Inhalte der Datei Function1.cs nun durch den folgenden Code:Now, replace all of the content of the file Function1.cs with the following code:

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 
    /// cognitive search 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.cognitive.microsoft.com/bing/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
    }
}

Wichtig: Geben Sie in der Konstanten key Ihren eigenen Schlüsselwert ** ein (auf der Grundlage des Schlüssels, den Sie bei der Anmeldung für die Bing-Entitätssuche-API erhalten haben).Make sure to enter your own key value in the key constant based on the key you got when signing up for the Bing entity search API.

Der Einfachheit halber ist der gesamte erforderliche Code in diesem Beispiel in einer einzelnen Datei enthalten.This sample includes all necessary code in a single file for convenience. Eine strukturiertere Version der gleichen Qualifikation finden Sie im Repository azure-search-power-skills.You can find a slightly more structured version of that same skill in the power skills repository.

Sie können die Datei natürlich von Function1.cs in BingEntitySearch.cs umbenennen.Of course, you may rename the file from Function1.cs to BingEntitySearch.cs.

Testen der Funktion aus Visual StudioTest the function from Visual Studio

Drücken Sie F5, um das Programm- und Testfunktionsverhalten auszuführen.Press F5 to run the program and test function behaviors. In diesem Fall verwenden wir die folgende Funktion, um nach zwei Entitäten zu suchen.In this case, we'll use the function below to look up two entities. Verwenden Sie Postman oder Fiddler, um einen Aufruf wie den unten gezeigten auszugeben:Use Postman or Fiddler to issue a call like the one shown below:

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

AnforderungstextRequest body

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

responseResponse

Die Antwort sollte in etwa wie hier dargestellt aussehen:You should see a response similar to the following example:

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

Veröffentlichen der Funktion in AzurePublish the function to Azure

Wenn Sie mit dem Funktionsverhalten zufrieden sind, können Sie sie veröffentlichen.When you're satisfied with the function behavior, you can publish it.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Veröffentlichen aus.In Solution Explorer, right-click the project and select Publish. Wählen Sie Neu erstellen > Veröffentlichen.Choose Create New > Publish.

  2. Wenn Sie Visual Studio noch nicht mit Ihrem Azure-Konto verbunden haben, wählen Sie Konto hinzufügen... aus.If you haven't already connected Visual Studio to your Azure account, select Add an account....

  3. Befolgen Sie die Anweisungen auf dem Bildschirm.Follow the on-screen prompts. Sie werden zur Angabe eines eindeutigen Namens für Ihren App-Dienst sowie des Azure-Abonnements, der Ressourcengruppe, des Hostingplans und des gewünschten Speicherkontos aufgefordert.You're asked to specify a unique name for your app service, the Azure subscription, the resource group, the hosting plan, and the storage account you want to use. Sie können eine neue Ressourcengruppe, einen neuen Hostingplan und ein Speicherkonto erstellen, wenn Sie noch nicht über diese verfügen.You can create a new resource group, a new hosting plan, and a storage account if you don't already have these. Wenn Sie fertig sind, klicken Sie auf Erstellen.When finished, select Create

  4. Beachten Sie die Website-URL nach Abschluss der Bereitstellung.After the deployment is complete, notice the Site URL. Es ist die Adresse der Funktions-App in Azure.It is the address of your function app in Azure.

  5. Navigieren Sie im Azure-Portal zur Ressourcengruppe, und suchen Sie nach der von Ihnen veröffentlichten Funktion EntitySearch.In the Azure portal, navigate to the Resource Group, and look for the EntitySearch Function you published. Im Abschnitt Verwalten sollten Sie Hostschlüssel angezeigt werden.Under the Manage section, you should see Host Keys. Wählen Sie das Symbol Kopieren für den Hostschlüssel Standard aus.Select the Copy icon for the default host key.

Testen der Funktion in AzureTest the function in Azure

Nachdem Sie nun den Standardhostschlüssel haben, testen Sie Ihre Funktion wie folgt:Now that you have the default host key, test your function as follows:

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

AnforderungstextRequest Body

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

Dieses Beispiel sollte das gleiche Ergebnis liefern wie die Ausführung der Funktion in der lokalen Umgebung.This example should produce the same result you saw previously when running the function in the local environment.

Herstellen einer Verbindung mit Ihrer PipelineConnect to your pipeline

Nun, da Sie über einen neuen benutzerdefinierten Skill verfügen, können Sie diesen zu Ihrem Skillset hinzufügen.Now that you have a new custom skill, you can add it to your skillset. Das folgende Beispiel zeigt, wie Sie die Qualifikation aufrufen, um Beschreibungen zu Organisationen im Dokument hinzuzufügen. (Das Beispiel könnte auch auf Orte und Personen ausgedehnt werden.)The example below shows you how to call the skill to add descriptions to organizations in the document (this could be extended to also work on locations and people). Ersetzen Sie [your-entity-search-app-name] durch den Namen Ihrer App.Replace [your-entity-search-app-name] with the name of your 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"
            }
          ]
      }
  ]
}

Hier wird vorausgesetzt, dass die integrierte Qualifikation „Entitätserkennung“ im Skillset vorhanden ist und das Dokument mit der Liste der Organisationen angereichert wurde.Here, we're counting on the built-in entity recognition skill to be present in the skillset and to have enriched the document with the list of organizations. Zu Referenzzwecken sehen Sie hier eine Konfiguration der Qualifikation „Entitätsextraktion“, die zum Generieren der benötigten Daten ausreichend wäre:For reference, here's an entity extraction skill configuration that would be sufficient in generating the data we need:

{
    "@odata.type": "#Microsoft.Skills.Text.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ächste SchritteNext steps

Glückwunsch!Congratulations! Sie haben Ihre erste benutzerdefinierte Anreicherungsfunktion erstellt.You've created your first custom enricher. Nun können Sie Ihre eigene benutzerdefinierte Funktionalität nach dem gleichen Muster hinzufügen.Now you can follow the same pattern to add your own custom functionality.