diff --git a/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj b/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj index 46a5010dcedf..c805fd70cea1 100644 --- a/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj +++ b/sdk/core/Azure.Core.TestFramework/src/Azure.Core.TestFramework.csproj @@ -1,6 +1,8 @@  $(RequiredTargetFrameworks);net47 + $(NoWarn);SYSLIB0023 + $(NoWarn);CS8032 true diff --git a/sdk/personalizer/Azure.AI.Personalizer.sln b/sdk/personalizer/Azure.AI.Personalizer.sln index 791da89ce047..c0732360d38b 100644 --- a/sdk/personalizer/Azure.AI.Personalizer.sln +++ b/sdk/personalizer/Azure.AI.Personalizer.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31410.414 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32002.261 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.AI.Personalizer", "Azure.AI.Personalizer\src\Azure.AI.Personalizer.csproj", "{B940AE9B-814A-4A20-9C81-3B8E5345E3B4}" EndProject @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{0F88C67F-34D2-4C68-B5BF-08A547D4CC2E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\core\Azure.Core\src\Azure.Core.csproj", "{CFB35402-69EB-448F-82B7-2D284730B0A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {0F88C67F-34D2-4C68-B5BF-08A547D4CC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {0F88C67F-34D2-4C68-B5BF-08A547D4CC2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {0F88C67F-34D2-4C68-B5BF-08A547D4CC2E}.Release|Any CPU.Build.0 = Release|Any CPU + {CFB35402-69EB-448F-82B7-2D284730B0A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFB35402-69EB-448F-82B7-2D284730B0A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFB35402-69EB-448F-82B7-2D284730B0A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFB35402-69EB-448F-82B7-2D284730B0A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Azure.AI.Personalizer.csproj b/sdk/personalizer/Azure.AI.Personalizer/src/Azure.AI.Personalizer.csproj index 56be15a9d198..e08b8fa5a7c0 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Azure.AI.Personalizer.csproj +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Azure.AI.Personalizer.csproj @@ -1,4 +1,4 @@ - + Microsoft Azure.AI.Personalizer client library 2.0.0-beta.2 @@ -32,6 +32,8 @@ + + diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.Serialization.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.Serialization.cs index 4f513e9a97ba..7a6eb075af4c 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.Serialization.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.Serialization.cs @@ -18,32 +18,62 @@ internal static PersonalizerRankResult DeserializePersonalizerRankResult(JsonEle Optional> ranking = default; Optional eventId = default; Optional rewardActionId = default; - foreach (var property in element.EnumerateObject()) + if (element.ValueKind == JsonValueKind.Object) { - if (property.NameEquals("ranking")) + foreach (var property in element.EnumerateObject()) { - if (property.Value.ValueKind == JsonValueKind.Null) + if (property.NameEquals("ranking")) { - property.ThrowNonNullablePropertyIsNull(); + if (property.Value.ValueKind == JsonValueKind.Null) + { + property.ThrowNonNullablePropertyIsNull(); + continue; + } + List array = new List(); + foreach (var item in property.Value.EnumerateArray()) + { + array.Add(PersonalizerRankedAction.DeserializePersonalizerRankedAction(item)); + } + ranking = array; continue; } - List array = new List(); - foreach (var item in property.Value.EnumerateArray()) + if (property.NameEquals("eventId")) { - array.Add(PersonalizerRankedAction.DeserializePersonalizerRankedAction(item)); + eventId = property.Value.GetString(); + continue; + } + if (property.NameEquals("rewardActionId")) + { + rewardActionId = property.Value.GetString(); + continue; } - ranking = array; - continue; - } - if (property.NameEquals("eventId")) - { - eventId = property.Value.GetString(); - continue; } - if (property.NameEquals("rewardActionId")) + } + else if (element.ValueKind == JsonValueKind.Array) + { + // TODO: This part is not verified! + foreach (var property in element.EnumerateArray()) { - rewardActionId = property.Value.GetString(); - continue; + if (property.Equals("ranking")) + { + List array = new List(); + foreach (var item in property.EnumerateArray()) + { + array.Add(PersonalizerRankedAction.DeserializePersonalizerRankedAction(item)); + } + ranking = array; + continue; + } + if (property.Equals("eventId")) + { + eventId = property.GetString(); + continue; + } + if (property.Equals("rewardActionId")) + { + rewardActionId = property.GetString(); + continue; + } } } return new PersonalizerRankResult(Optional.ToList(ranking), eventId.Value, rewardActionId.Value); diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs index 5691244d0075..fda9873265d7 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs @@ -11,6 +11,7 @@ using Azure; using Azure.Core; using Azure.Core.Pipeline; +using Rl.Net; namespace Azure.AI.Personalizer { @@ -20,6 +21,8 @@ internal partial class MultiSlotClient private readonly ClientDiagnostics _clientDiagnostics; private readonly HttpPipeline _pipeline; private readonly bool _isLocalInference; + private readonly RankProcessor _rankProcessor; + internal MultiSlotRestClient RestClient { get; } /// Initializes a new instance of MultiSlotClient for mocking. @@ -53,11 +56,15 @@ public MultiSlotClient(string endpoint, TokenCredential credential, Personalizer /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local inference. + /// A configuration to use local reference. /// The options for configuring the client. - public MultiSlotClient(string endpoint, TokenCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public MultiSlotClient(string endpoint, TokenCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } /// Initializes a new instance of MultiSlotClient. @@ -85,11 +92,15 @@ public MultiSlotClient(string endpoint, AzureKeyCredential credential, Personali /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local inference. + /// A configuration to use local reference. /// The options for configuring the client. - public MultiSlotClient(string endpoint, AzureKeyCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public MultiSlotClient(string endpoint, AzureKeyCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } /// Initializes a new instance of MultiSlotClient. diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankClient.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankClient.cs index d0473d9cf981..625328bb08a3 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankClient.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankClient.cs @@ -11,6 +11,7 @@ using Azure; using Azure.Core; using Azure.Core.Pipeline; +using Rl.Net; namespace Azure.AI.Personalizer { @@ -20,6 +21,7 @@ internal partial class RankClient private readonly ClientDiagnostics _clientDiagnostics; private readonly HttpPipeline _pipeline; private readonly bool _isLocalInference; + private readonly RankProcessor _rankProcessor; internal RankRestClient RestClient { get; } /// Initializes a new instance of RankClient for mocking. @@ -53,11 +55,15 @@ public RankClient(string endpoint, TokenCredential credential, PersonalizerClien /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local inference. + /// A configuration to use local reference. /// The options for configuring the client. - public RankClient(string endpoint, TokenCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public RankClient(string endpoint, TokenCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } /// Initializes a new instance of RankClient. @@ -85,11 +91,16 @@ public RankClient(string endpoint, AzureKeyCredential credential, PersonalizerCl /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local reference. + /// A configuration to use local reference. /// The options for configuring the client. - public RankClient(string endpoint, AzureKeyCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public RankClient(string endpoint, AzureKeyCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); + } /// Initializes a new instance of RankClient. @@ -112,7 +123,14 @@ public virtual async Task> RankAsync(Personaliz scope.Start(); try { - return await RestClient.RankAsync(rankRequest, cancellationToken).ConfigureAwait(false); + if (_isLocalInference) + { + return _rankProcessor.Rank(rankRequest, cancellationToken); + } + else + { + return await RestClient.RankAsync(rankRequest, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankProcessor.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankProcessor.cs new file mode 100644 index 000000000000..e5409cc726a2 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/RankProcessor.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using Newtonsoft.Json; +using Rl.Net; + +namespace Azure.AI.Personalizer +{ + /// The Rank Processor. + internal partial class RankProcessor + { + private readonly LiveModel _liveModel; + internal PolicyRestClient RestClient { get; } + + /// Initializes a new instance of RankProcessor. + public RankProcessor(LiveModel liveModel) + { + this._liveModel = liveModel; + } + + /// Submit a Personalizer rank request. Receives a context and a list of actions. Returns which of the provided actions should be used by your application, in rewardActionId. + /// A Personalizer Rank request. + /// The cancellation token to use. + public Response Rank(PersonalizerRankOptions options, CancellationToken cancellationToken = default) + { + // Remove excluded actions in options + HashSet excludedSet = new HashSet(options.ExcludedActions); + options.Actions = options.Actions.Where(action => !excludedSet.Contains(action.Id)); + + // Convert options to the compatible parameter for ChooseRank + DecisionContext decisionContext = new DecisionContext(options); + var contextJson = JsonConvert.SerializeObject(decisionContext); + ActionFlags flags = options.DeferActivation == true ? ActionFlags.Deferred : ActionFlags.Default; + + // Call ChooseRank of local RL.Net + RankingResponse rankingResponse = _liveModel.ChooseRank(options.EventId, contextJson, flags); + + // Convert response to PersonalizerRankResult + var responseJson = JsonConvert.SerializeObject(rankingResponse); + var responseDocument = JsonDocument.Parse(responseJson); + var value = PersonalizerRankResult.DeserializePersonalizerRankResult(responseDocument.RootElement); + return Response.FromValue(value, default); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContext.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContext.cs new file mode 100644 index 000000000000..7b0144e43ec7 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContext.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context. + public class DecisionContext + { + /// The Decision Context. + public DecisionContext() + { + } + + /// Initializes a new instance of DecisionContext. + /// Personalizer Rank Options + public DecisionContext(PersonalizerRankOptions rankRequest) + { + List jsonFeatures = rankRequest.ContextFeatures.Select(f => JsonConvert.SerializeObject(f)).ToList(); + this.SharedFromUrl = jsonFeatures; + + this.Documents = rankRequest.Actions + .Select(action => + { + string ids = null; + List jsonFeatures = action.Features.Select(f => JsonConvert.SerializeObject(f)).ToList(); + + //if (action.Ids != null) + //{ + // ids = string.Join(",", action.Ids); + //} + + var doc = new DecisionContextDocument + { + ID = ids, + JSON = jsonFeatures, + }; + + //if (action.ActionSet != null && action.ActionSet?.Id != null) + // doc.Source = new DecisionContextDocumentSource + // { + // Set = action.ActionSet.Id.Id, + // Parameter = action.ActionSet.Parameter + // }; + + return doc; + }).ToArray(); + + //this.Slots = decisionRequest.Slots? + // .Select(slot => new DecisionContextDocument { + // SlotId = slot.Id, + // SlotJson = slot.JsonFeatures + // }).ToArray(); + } + + /// Properties from url + [JsonProperty("FromUrl", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(JsonRawStringListConverter))] +#pragma warning disable CA2227 // Collection properties should be read only + public List SharedFromUrl { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + + /// Properties of documents + [JsonProperty("_multi")] + public DecisionContextDocument[] Documents { get; set; } + + /// Properties of slots + [JsonProperty("_slots", NullValueHandling = NullValueHandling.Ignore)] + public DecisionContextDocument[] Slots { get; set; } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocument.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocument.cs new file mode 100644 index 000000000000..5ccc4f69dc12 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocument.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context Document. + public class DecisionContextDocument + { + /// + /// Supply _tag for online evaluation (VW/EvalOperation.cs) + /// + [JsonProperty("_tag", NullValueHandling = NullValueHandling.Ignore)] + public string ID + { + get; + set; + } + + /// + /// Provide source set feature. + /// + [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] + public DecisionContextDocumentSource Source { get; set; } + + /// + /// Generic json features. + /// + [JsonProperty("j", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(JsonRawStringListConverter))] +#pragma warning disable CA2227 // Collection properties should be read only + public List JSON { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + + /// + /// Keep as float[] arrays to improve marshalling speed. + /// + [JsonProperty("f", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary FloatFeatures { get; } + + /// + /// Slot ID. + /// + [JsonProperty("_id", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(JsonRawStringListConverter))] + public string SlotId { get; set; } + + /// + /// Generic slot json features. + /// + [JsonProperty("sj", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(JsonRawStringListConverter))] + public List SlotJson { get; } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocumentSource.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocumentSource.cs new file mode 100644 index 000000000000..c61e1c4e8819 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocumentSource.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context Document Source. + public class DecisionContextDocumentSource + { + /// The set. + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Set { get; set; } + + /// The parameter. + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Parameter { get; set; } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/DisposeHelper.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DisposeHelper.cs new file mode 100644 index 000000000000..9381df42de7b --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DisposeHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; + +namespace Azure.AI.Personalizer +{ + /// The class for Dispose helper + public static class DisposeHelper + { + /// Safe dispose + /// Configuration. + public static void SafeDispose(ref TDisposable disposable) where TDisposable : class, IDisposable + { + IDisposable localDisposable = Interlocked.Exchange(ref disposable, null); + if (localDisposable != null) + { + localDisposable.Dispose(); + } + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/JsonRawStringListConverter.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/JsonRawStringListConverter.cs new file mode 100644 index 000000000000..951a05b4e084 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/JsonRawStringListConverter.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Azure.AI.Personalizer +{ + /// Json raw string list converter + internal class JsonRawStringListConverter : JsonConverter + { + /// + /// Supports string only. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(List); + } + + /// + /// Not implemented. + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + /// + /// Outputs the string contents as JSON. + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var valueStringEnumerable = value as List; + if (valueStringEnumerable != null) + { + writer.WriteStartArray(); + foreach (var str in valueStringEnumerable) + writer.WriteRawValue(str); + writer.WriteEndArray(); + return; + } + + serializer.Serialize(writer, value); + } + + /// + /// List of independently parseable JSON fragments. + /// + public static IEnumerable JsonFragments(object value) + { + var valueStringList = value as List; + if (valueStringList == null) + throw new ArgumentException($"Unsupported type: {value}"); + + return valueStringList; + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs index f8f6c223eb18..fa10d7552440 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Core.Pipeline; +using Rl.Net; namespace Azure.AI.Personalizer { @@ -17,6 +18,8 @@ public class PersonalizerClient private readonly ClientDiagnostics _clientDiagnostics; private readonly HttpPipeline _pipeline; private readonly bool _isLocalInference; + private readonly RankProcessor _rankProcessor; + internal RankRestClient RankRestClient { get; set; } internal EventsRestClient EventsRestClient { get; set; } internal MultiSlotRestClient MultiSlotRestClient { get; set; } @@ -57,11 +60,15 @@ public PersonalizerClient(Uri endpoint, TokenCredential credential, Personalizer /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local inference. + /// A configuration to use local reference. /// The options for configuring the client. - public PersonalizerClient(Uri endpoint, TokenCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public PersonalizerClient(Uri endpoint, TokenCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } /// Initializes a new instance of PersonalizerClient. @@ -98,11 +105,15 @@ public PersonalizerClient(Uri endpoint, AzureKeyCredential credential, Personali /// Supported Cognitive Services endpoint. /// A credential used to authenticate to an Azure Service. /// A flag to determine whether to use local inference. + /// A configuration to use local reference. /// The options for configuring the client. - public PersonalizerClient(Uri endpoint, AzureKeyCredential credential, bool isLocalInference, PersonalizerClientOptions options = null) : + public PersonalizerClient(Uri endpoint, AzureKeyCredential credential, bool isLocalInference, Configuration configuration, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } /// Initializes a new instance of PersonalizerClient. @@ -134,7 +145,14 @@ public virtual async Task> RankAsync(Personaliz scope.Start(); try { - return await RankRestClient.RankAsync(options, cancellationToken).ConfigureAwait(false); + if (_isLocalInference) + { + return _rankProcessor.Rank(options, cancellationToken); + } + else + { + return await RankRestClient.RankAsync(options, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) { @@ -177,7 +195,14 @@ public virtual Response Rank(PersonalizerRankOptions opt scope.Start(); try { - return RankRestClient.Rank(options, cancellationToken); + if (_isLocalInference) + { + return _rankProcessor.Rank(options, cancellationToken); + } + else + { + return RankRestClient.Rank(options, cancellationToken); + } } catch (Exception e) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankOptions.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankOptions.cs index f11a8d2b5037..3997161ca47d 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankOptions.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankOptions.cs @@ -27,7 +27,7 @@ public partial class PersonalizerRankOptions /// should match the sequence your application would have used to display them. /// The first item in the array will be used as Baseline item in Offline Evaluations. /// - public IEnumerable Actions { get; } + public IEnumerable Actions { get; set; } /// /// The set of action ids to exclude from ranking. diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/RankProcessor.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/RankProcessor.cs new file mode 100644 index 000000000000..66dc3c4a8d93 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/RankProcessor.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Azure.AI.Personalizer +{ + internal partial class RankProcessor {} +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Azure.AI.Personalizer.Tests.csproj b/sdk/personalizer/Azure.AI.Personalizer/tests/Azure.AI.Personalizer.Tests.csproj index 67a686a2e33c..b3f25e8aca74 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/tests/Azure.AI.Personalizer.Tests.csproj +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Azure.AI.Personalizer.Tests.csproj @@ -4,6 +4,7 @@ $(NoWarn);CS1591 + $(NoWarn);CS8032 diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Infrastructure/PersonalizerTestBase.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Infrastructure/PersonalizerTestBase.cs index a27c34534ee9..c3449904f375 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/tests/Infrastructure/PersonalizerTestBase.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Infrastructure/PersonalizerTestBase.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Azure.Core.TestFramework; +using Rl.Net; namespace Azure.AI.Personalizer.Tests { @@ -18,10 +19,12 @@ public PersonalizerTestBase(bool isAsync): base(isAsync) Sanitizer = new PersonalizerRecordedTestSanitizer(); } - protected async Task GetPersonalizerClientAsync(bool isSingleSlot = false) + protected async Task GetPersonalizerClientAsync(bool isSingleSlot = false, bool isLocalInference = false) { - string endpoint = isSingleSlot ? TestEnvironment.SingleSlotEndpoint : TestEnvironment.MultiSlotEndpoint; - string apiKey = isSingleSlot ? TestEnvironment.SingleSlotApiKey : TestEnvironment.MultiSlotApiKey; + //string endpoint = isSingleSlot ? TestEnvironment.SingleSlotEndpoint : TestEnvironment.MultiSlotEndpoint; + //string apiKey = isSingleSlot ? TestEnvironment.SingleSlotApiKey : TestEnvironment.MultiSlotApiKey; + string endpoint = isSingleSlot ? "https://autoopte2etest3.ppe.cognitiveservices.azure.com" : TestEnvironment.MultiSlotEndpoint; + string apiKey = isSingleSlot ? "f9201cb5e8924b589c392ab202c3b56f" : TestEnvironment.MultiSlotApiKey; PersonalizerAdministrationClient adminClient = GetAdministrationClient(isSingleSlot); if (!isSingleSlot) { @@ -29,15 +32,48 @@ protected async Task GetPersonalizerClientAsync(bool isSingl } var credential = new AzureKeyCredential(apiKey); var options = InstrumentClientOptions(new PersonalizerClientOptions()); - PersonalizerClient personalizerClient = new PersonalizerClient(new Uri(endpoint), credential, options); + PersonalizerClient personalizerClient = null; + if (isLocalInference) + { + Configuration config = new Configuration(); + + // configure the personalizer loop + config["appid"] = "appId1"; + + // set up the model + config["model.source"] = "FILE_MODEL_DATA"; + config["model_file_loader.file_name"] = "modelfile.data"; + config["model.backgroundrefresh"] = "false"; + + config["interaction.http.api.host"] = "https://{personalizerEndPoint}/personalizer/v1.1-preview.2/logs/interactions"; + config["observation.http.api.host"] = "https://{personalizerEndPoint}/personalizer/v1.1-preview.2/logs/observations"; + config["http.api.key"] = "{personalizerApiKey}"; + + config["InitialCommandLine"] = "--cb_explore_adf --epsilon 0.2 --power_t 0 -l 0.001 --cb_type mtr -q ::"; + config["InitialExplorationEpsilon"] = "1"; + config["LearningMode"] = "Online"; + config["ProtocolVersion"] = "2"; + config["LearningMode"] = "Online"; + config["observation.sender.implementation"] = "OBSERVATION_HTTP_API_SENDER"; + config["interaction.sender.implementation"] = "INTERACTION_HTTP_API_SENDER"; + + personalizerClient = new PersonalizerClient(new Uri(endpoint), credential, true, config, options); + } + else + { + personalizerClient = new PersonalizerClient(new Uri(endpoint), credential, options); + } + personalizerClient = InstrumentClient(personalizerClient); return personalizerClient; } protected PersonalizerAdministrationClient GetAdministrationClient(bool isSingleSlot = false) { - string endpoint = isSingleSlot ? TestEnvironment.SingleSlotEndpoint : TestEnvironment.MultiSlotEndpoint; - string apiKey = isSingleSlot ? TestEnvironment.SingleSlotApiKey : TestEnvironment.MultiSlotApiKey; + //string endpoint = isSingleSlot ? TestEnvironment.SingleSlotEndpoint : TestEnvironment.MultiSlotEndpoint; + //string apiKey = isSingleSlot ? TestEnvironment.SingleSlotApiKey : TestEnvironment.MultiSlotApiKey; + string endpoint = isSingleSlot ? "https://autoopte2etest3.ppe.cognitiveservices.azure.com" : TestEnvironment.MultiSlotEndpoint; + string apiKey = isSingleSlot ? "f9201cb5e8924b589c392ab202c3b56f" : TestEnvironment.MultiSlotApiKey; var credential = new AzureKeyCredential(apiKey); var options = InstrumentClientOptions(new PersonalizerClientOptions()); PersonalizerAdministrationClient personalizerAdministrationClient = new PersonalizerAdministrationClient(new Uri(endpoint), credential, options); diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs index 259d24e9cdcc..8c9c9b37e03a 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs @@ -22,6 +22,15 @@ public async Task SingleSlotRankTests() await RankNullParameters(client); } + [Test] + public async Task SingleSlotRankLocalInferenceTests() + { + PersonalizerClient client = await GetPersonalizerClientAsync(isSingleSlot: true, isLocalInference: true); + //await RankNullParameters(client); + await RankServerFeaturesLocalInference(client); + //await RankNullParameters(client); + } + private async Task RankNullParameters(PersonalizerClient client) { IList actions = new List(); @@ -75,6 +84,39 @@ private async Task RankServerFeatures(PersonalizerClient client) } } + private async Task RankServerFeaturesLocalInference(PersonalizerClient client) + { + IList contextFeatures = new List() { + new { Features = new { day = "tuesday", time = "night", weather = "rainy" } }, + new { Features = new { userId = "1234", payingUser = true, favoriteGenre = "documentary", hoursOnSite = 0.12, lastwatchedType = "movie" } } + }; + IList actions = new List(); + actions.Add( + new PersonalizerRankableAction( + id: "Person1", + features: + new List() { new { videoType = "documentary", videoLength = 35, director = "CarlSagan" }, new { mostWatchedByAge = "30-35" } } + )); + actions.Add( + new PersonalizerRankableAction( + id: "Person2", + features: + new List() { new { videoType = "documentary", videoLength = 35, director = "CarlSagan" }, new { mostWatchedByAge = "40-45" } } + )); + IList excludeActions = new List { "Person1" }; + string eventId = "123456789"; + var request = new PersonalizerRankOptions(actions, contextFeatures, excludeActions, eventId); + // Action + PersonalizerRankResult response = await client.RankAsync(request); + // Assert + Assert.AreEqual(eventId, response.EventId); + Assert.AreEqual(actions.Count, response.Ranking.Count); + for (int i = 0; i < response.Ranking.Count; i++) + { + Assert.AreEqual(actions[i].Id, response.Ranking[i].Id); + } + } + private async Task RankWithNoOptions(PersonalizerClient client) { IList contextFeatures = new List() {