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

Note

Azure AI Vyhledávač je k dispozici prostřednictvím portálu Azure, rozhraní REST API a Sady Azure SDK. Podporuje také Foundry IQ, spravovanou znalostní vrstvu, která transformuje podnikový obsah na opakovaně použitelné znalostní báze s podporou oprávnění pro agenty na portálu Microsoft Foundry.

V tomto příkladu se dozvíte, jak vytvořit vlastní dovednosti webového rozhraní API. Tato dovednost přijímá místa, veřejné postavy a organizace a vrací jejich popisy. 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 článek o rozhraní vlastních dovedností, pokud nejste obeznámeni s vstupním/výstupním rozhraním, které by vlastní dovednost měla implementovat.

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

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

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 možnost Nový>projekt z nabídky Soubor.

  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 žádné jiné speciální znaky.

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

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

  5. Zvolte funkci pro úroveň autorizace.

  6. Výběrem Vytvořit vytvoříte projekt funkce a HTTP triggerovanou funkci.

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

Visual Studio vytvoří projekt se šablonovým kódem pro zvolený druh 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
    }
}

Reference:Atribut HttpTrigger, rozhraní ILogger, třída HttpClient, JsonConvert.DeserializeObject

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

Výstraha

Pro produkční nasazení uložte přihlašovací údaje ve službě Azure Key Vault nebo proměnných prostředí. Tajné kódy ve zdrojovém kódu pevně nezakódujte.

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

Obsah požadavku

{
    "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é>Publikovat.

  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]

Obsah 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í ke kanálu

Teď, když máte novou vlastní dovednost, můžete ji přidat do sady dovedností. Následující příklad ukazuje, jak využít schopnosti, aby přidala popisy organizacím do dokumentu (je možné rozšířit tak, aby pracovala i s místy a lidmi). 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

Blahopřejeme! Vytvořili jste svou první vlastní dovednost. Teď můžete podle stejného vzoru přidat vlastní funkce. Další informace najdete v následujících zdrojích informací: