Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом примере вы узнаете, как создать пользовательский навык для веб-API. Эта функция будет принимать в обработку местоположения, публичные фигуры и организации и возвращать для них описания. Этот пример использует Функцию Azure, чтобы обернуть API Поиска сущностей Bing для реализации интерфейса пользовательского навыка.
Предварительные требования
Прочитайте статью об интерфейсе пользовательского навыка, если вы не знакомы с интерфейсом ввода-вывода, который должен реализовывать пользовательский навык.
Создайте ресурс поиска Bing с помощью портал Azure. Для этого примера доступен и достаточен бесплатный уровень.
Установите Visual Studio или более поздней версии.
Создание функции Azure
Хотя этот пример использует Функцию Azure для размещения веб-API, это необязательно. При условии соблюдения требований к интерфейсу для когнитивного навыка выбранный вами подход не имеет значения. Однако Функции Azure упрощают создание пользовательских навыков.
Создание проекта
В меню "Файл" Visual Studio выберите Создать>Проект.
Выберите Функции Azure в качестве шаблона и нажмите кнопку "Далее". Введите имя проекта и нажмите кнопку Создать. Имя приложения-функции должно быть допустимым в качестве пространства имен C#, поэтому не используйте символы подчеркивания, дефисы или другие специальные символы.
Выберите платформу с долгосрочной поддержкой.
Выберите HTTP-триггер для типа функции, добавляемой в проект.
Выберите функцию для уровня авторизации.
Нажмите кнопку Создать, чтобы создать проект функции и функцию, активируемую с помощью HTTP.
Добавьте код для вызова API сущностей Bing
Visual Studio создает проект с стандартным кодом для выбранного типа функции. Атрибут метода FunctionName задает имя функции. Атрибут HttpTrigger указывает, что функция вызывается HTTP-запросом.
Замените содержимое Function1.cs следующим кодом:
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
}
}
Обязательно введите собственное значение ключа в key константе на основе ключа, который вы получили при регистрации в API поиска сущностей Bing.
Тестирование функции из Visual Studio
Нажмите клавишу F5 для запуска программы и тестирования поведения функции. В этом случае мы будем использовать указанную ниже функцию для поиска двух сущностей. Используйте клиент REST для вызова, как показано ниже:
POST https://localhost:7071/api/EntitySearch
Основное содержание запроса
{
"values": [
{
"recordId": "e1",
"data":
{
"name": "Pablo Picasso"
}
},
{
"recordId": "e2",
"data":
{
"name": "Microsoft"
}
}
]
}
Отклик
Вы должны увидеть отклик, аналогичный следующему примеру:
{
"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
},
"..."
]
}
Публикация функции в Azure
Если вы удовлетворены поведением функции, ее можно опубликовать.
Щелкните правой кнопкой мыши проект в обозревателе решений и выберите пункт Опубликовать. Выберите Создать>Опубликовать.
Если вы еще не подключили Visual Studio к учетной записи Azure, выберите Добавить учетную запись...
Следуйте инструкциям на экране. Вам предложат указать уникальное имя для службы приложений, подписку Azure, группу ресурсов, план размещения и учетную запись хранения, которую необходимо использовать. Если у вас ещё нет новой группы ресурсов, нового плана размещения и учётной записи хранения, их можно создать. По завершении выберите Создать.
После завершения развертывания запишите URL-адрес сайта. Это адрес приложения-функции в Azure.
На портале Azure перейдите в группу ресурсов и найдите опубликованную функцию
EntitySearch. В разделе Управление должны отображаться ключи узла. Выберите значок Copy для ключа узла по умолчанию.
Тестирование функции в Azure
Теперь, когда у вас есть ключ узла по умолчанию, протестируйте функцию следующим образом:
POST https://[your-entity-search-app-name].azurewebsites.net/api/EntitySearch?code=[enter default host key here]
Тело запроса
{
"values": [
{
"recordId": "e1",
"data":
{
"name": "Pablo Picasso"
}
},
{
"recordId": "e2",
"data":
{
"name": "Microsoft"
}
}
]
}
Этот пример должен дать тот же результат, как и при запуске функции в локальной среде.
Подключение к конвейеру
Теперь, когда у вас есть новый пользовательский навык, можно добавить его в ваш набор навыков. В примере ниже показано, как использовать навык для добавления описаний к организациям в документе (его можно расширить для работы с локациями и людьми). Замените [your-entity-search-app-name] на название приложения.
{
"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"
}
]
}
]
}
Здесь мы рассчитываем на наличие встроенного навыка распознавания сущностей в пакете навыков, благодаря которому документ был обогащен списком организаций. Для справки ниже приведена конфигурация навыков извлечения сущностей, которой может быть достаточно для создания необходимых данных.
{
"@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"
}
]
},
Следующие шаги
Поздравляем! Вы создали свой первый пользовательский навык. Теперь вы можете использовать ту же схему для добавления пользовательских функций. Дополнительные сведения см. по следующим ссылкам.