Przykład: tworzenie umiejętności niestandardowych przy użyciu interfejsu API wyszukiwania jednostek Bing

W tym przykładzie dowiesz się, jak utworzyć niestandardową umiejętność internetowego interfejsu API. Ta umiejętność będzie akceptować lokalizacje, osoby publiczne i organizacje oraz zwracać im opisy. W tym przykładzie użyto funkcji platformy Azure do opakowania interfejsu API wyszukiwania jednostek Bing w celu zaimplementowania niestandardowego interfejsu umiejętności.

Wymagania wstępne

  • Przeczytaj artykuł dotyczący niestandardowego interfejsu umiejętności, jeśli nie znasz interfejsu wejściowego/wyjściowego, który powinien implementować niestandardowa umiejętność.

  • Utwórz zasób wyszukiwania Bing za pośrednictwem witryny Azure Portal. Warstwa Bezpłatna jest dostępna i wystarczająca dla tego przykładu.

  • Zainstaluj program Visual Studio lub nowszy.

Tworzenie funkcji platformy Azure

Mimo że w tym przykładzie użyto funkcji platformy Azure do hostowania internetowego interfejsu API, nie jest to wymagane. Tak długo, jak spełniasz wymagania interfejsu dla umiejętności poznawczych, podejście, które podejmujesz, jest niematerialne. Jednak usługa Azure Functions ułatwia tworzenie niestandardowych umiejętności.

Tworzenie projektu

  1. W programie Visual Studio wybierz pozycję Nowy>projekt z menu Plik.

  2. Wybierz usługę Azure Functions jako szablon i wybierz pozycję Dalej. Wpisz nazwę projektu i wybierz pozycję Utwórz. Nazwa aplikacji funkcji musi być prawidłowa jako przestrzeń nazw języka C#, dlatego nie używaj podkreśleń, łączników ani innych znaków innych niż alfanumeryczne.

  3. Wybierz strukturę, która ma długoterminową obsługę.

  4. Wybierz pozycję Wyzwalacz HTTP dla typu funkcji, który ma zostać dodany do projektu.

  5. Wybierz pozycję Funkcja dla poziomu autoryzacji.

  6. Wybierz pozycję Utwórz , aby utworzyć projekt funkcji i funkcję wyzwalaną przez protokół HTTP.

Dodawanie kodu w celu wywołania interfejsu API jednostki Bing

Program Visual Studio tworzy projekt ze standardowym kodem dla wybranego typu funkcji. Atrybut FunctionName metody ustawia nazwę funkcji. Atrybut HttpTrigger określa, że funkcja jest wyzwalana przez żądanie HTTP.

Zastąp zawartość Function1.cs następującym kodem:

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

Pamiętaj, aby wprowadzić własną wartość klucza w stałej key na podstawie klucza, który został otrzymany podczas rejestrowania się w interfejsie API wyszukiwania jednostek Bing.

Testowanie funkcji z poziomu programu Visual Studio

Naciśnij klawisz F5 , aby uruchomić program i przetestować zachowania funkcji. W tym przypadku użyjemy poniższej funkcji, aby wyszukać dwie jednostki. Użyj klienta REST, aby wystawić wywołanie podobne do pokazanego poniżej:

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

Request body

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

Response

Powinna zostać wyświetlona odpowiedź podobna do poniższego przykładu:

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

Publikowanie funkcji na platformie Azure

Jeśli zachowanie funkcji jest zadowalające, możesz go opublikować.

  1. W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz polecenie Opublikuj. Wybierz pozycję Utwórz nową>publikację.

  2. Jeśli program Visual Studio nie został jeszcze połączony z kontem platformy Azure, wybierz pozycję Dodaj konto....

  3. Postępuj zgodnie z instrukcjami wyświetlanymi na ekranie. Zostanie wyświetlona prośba o podanie unikatowej nazwy usługi App Service, subskrypcji platformy Azure, grupy zasobów, planu hostingu i konta magazynu, którego chcesz użyć. Jeśli jeszcze nie masz, możesz utworzyć nową grupę zasobów, nowy plan hostingu i konto magazynu. Po zakończeniu wybierz pozycję Utwórz

  4. Po zakończeniu wdrażania zwróć uwagę na adres URL witryny. Jest to adres aplikacji funkcji na platformie Azure.

  5. W witrynie Azure Portal przejdź do grupy zasobów i wyszukaj opublikowaną EntitySearch funkcję. W sekcji Zarządzanie powinny być widoczne klucze hosta. Wybierz ikonę Kopiuj dla domyślnegoklucza hosta.

Testowanie funkcji na platformie Azure

Teraz, gdy masz domyślny klucz hosta, przetestuj funkcję w następujący sposób:

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

Treść żądania

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

W tym przykładzie powinien zostać wygenerowany ten sam wynik, który był wcześniej wyświetlany podczas uruchamiania funkcji w środowisku lokalnym.

Połączenie do potoku

Teraz, gdy masz nową niestandardową umiejętność, możesz dodać ją do zestawu umiejętności. W poniższym przykładzie pokazano, jak wywołać umiejętność dodawania opisów do organizacji w dokumencie (można to rozszerzyć, aby również pracować nad lokalizacjami i osobami). Zastąp [your-entity-search-app-name] ciąg nazwą aplikacji.

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

W tym miejscu liczymy na wbudowaną umiejętność rozpoznawania jednostek, która będzie obecna w zestawie umiejętności i wzbogaciła dokument o listę organizacji. Poniżej przedstawiono konfigurację umiejętności wyodrębniania jednostek, która byłaby wystarczająca do generowania potrzebnych danych:

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

Następne kroki

Gratulacje! Udało Ci się utworzyć swoją pierwszą niestandardową umiejętność. Teraz możesz postępować zgodnie z tym samym wzorcem, aby dodać własną funkcjonalność niestandardową. Kliknij następujące linki, aby dowiedzieć się więcej.