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 46a5010dcedf6..a7c6323d46e22 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,7 @@  $(RequiredTargetFrameworks);net47 + $(NoWarn);CS8032 true diff --git a/sdk/core/Azure.Core.TestFramework/src/TestRecording.cs b/sdk/core/Azure.Core.TestFramework/src/TestRecording.cs index 26236d7d7a65a..c9a7dcc300d31 100644 --- a/sdk/core/Azure.Core.TestFramework/src/TestRecording.cs +++ b/sdk/core/Azure.Core.TestFramework/src/TestRecording.cs @@ -196,10 +196,12 @@ public TestRandom Random #if NET6_0_OR_GREATER var liveSeed = RandomNumberGenerator.GetInt32(int.MaxValue); #else +#pragma warning disable SYSLIB0023 var csp = new RNGCryptoServiceProvider(); var bytes = new byte[4]; csp.GetBytes(bytes); var liveSeed = BitConverter.ToInt32(bytes, 0); +#pragma warning restore SYSLIB0023 #endif _random = new TestRandom(Mode, liveSeed); break; diff --git a/sdk/personalizer/Azure.AI.Personalizer.sln b/sdk/personalizer/Azure.AI.Personalizer.sln index 791da89ce0472..298d122e919f5 100644 --- a/sdk/personalizer/Azure.AI.Personalizer.sln +++ b/sdk/personalizer/Azure.AI.Personalizer.sln @@ -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 701ab24615552..3a4a5faa2a622 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 @@ -33,6 +33,7 @@ + diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerErrorCode.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerErrorCode.cs index 8fa9cdc7fb79b..f089ff68ef640 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerErrorCode.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerErrorCode.cs @@ -15,7 +15,7 @@ namespace Azure.AI.Personalizer.Models { private readonly string _value; - /// Determines if two values are the same. + /// Initializes a new instance of . /// is null. public PersonalizerErrorCode(string value) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationJobStatus.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationJobStatus.cs index 085e5f3299124..389201f0f3dd5 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationJobStatus.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationJobStatus.cs @@ -15,7 +15,7 @@ namespace Azure.AI.Personalizer { private readonly string _value; - /// Determines if two values are the same. + /// Initializes a new instance of . /// is null. public PersonalizerEvaluationJobStatus(string value) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationType.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationType.cs index 9a313a1f08e82..96785da12f5e7 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationType.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerEvaluationType.cs @@ -15,7 +15,7 @@ namespace Azure.AI.Personalizer { private readonly string _value; - /// Determines if two values are the same. + /// Initializes a new instance of . /// is null. public PersonalizerEvaluationType(string value) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLearningMode.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLearningMode.cs index 8a4b6c3777042..94137b19b95c1 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLearningMode.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLearningMode.cs @@ -15,7 +15,7 @@ namespace Azure.AI.Personalizer { private readonly string _value; - /// Determines if two values are the same. + /// Initializes a new instance of . /// is null. public PersonalizerLearningMode(string value) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLogProperties.Serialization.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLogProperties.Serialization.cs new file mode 100644 index 0000000000000..8260d0d25584e --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerLogProperties.Serialization.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System.Text.Json; +using Azure.Core; + +namespace Azure.AI.Personalizer +{ + public partial class PersonalizerLogProperties + { + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerPolicySource.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerPolicySource.cs index 0c7d1f64f6f70..905b6286b041c 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerPolicySource.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerPolicySource.cs @@ -15,7 +15,7 @@ namespace Azure.AI.Personalizer { private readonly string _value; - /// Determines if two values are the same. + /// Initializes a new instance of . /// is null. public PersonalizerPolicySource(string value) { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.cs index e6e3b8dbf8908..629d36afdcb00 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankResult.cs @@ -35,14 +35,14 @@ internal PersonalizerRankResult(IReadOnlyList ranking, } /// The calculated ranking for the current request. - public IReadOnlyList Ranking { get; } + public IReadOnlyList Ranking { get; set; } /// The eventId for the round trip from request to response. - public string EventId { get; } + public string EventId { get; set; } /// /// The action chosen by the Personalizer service. /// This is the action your application should display, and for which to report the reward. /// This might not be the first found in 'ranking'. /// - public string RewardActionId { get; } + public string RewardActionId { get; set; } } } diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankedAction.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankedAction.cs index 9d55861dba4d2..304775da49e61 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankedAction.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/PersonalizerRankedAction.cs @@ -25,8 +25,8 @@ internal PersonalizerRankedAction(string id, float? probability) } /// Id of the action. - public string Id { get; } + public string Id { get; set; } /// Probability of the action. - public float? Probability { get; } + public float? Probability { get; set; } } } diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/ServiceStatus.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/ServiceStatus.cs index 2e47a345afb45..bec504a098b8d 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/ServiceStatus.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/Models/ServiceStatus.cs @@ -15,8 +15,11 @@ internal ServiceStatus() { } + /// Gets the service. public string Service { get; } + /// Gets the api status. public string ApiStatus { get; } + /// Gets the api status message. public string ApiStatusMessage { get; } } } diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Generated/MultiSlotClient.cs index 5691244d00759..78699b565ae09 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,18 @@ 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 = false, Configuration configuration = null, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + if (isLocalInference) + { + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); + } } /// Initializes a new instance of MultiSlotClient. @@ -85,11 +95,18 @@ 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 = false, Configuration configuration = null, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + if (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 d0473d9cf9810..aceb366812419 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,18 @@ 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 = false, Configuration configuration = null, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + if (isLocalInference) + { + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); + } } /// Initializes a new instance of RankClient. @@ -85,11 +94,19 @@ 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 = false, Configuration configuration = null, PersonalizerClientOptions options = null) : this(endpoint, credential, options) { _isLocalInference = isLocalInference; + if (isLocalInference) + { + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); + } + } /// Initializes a new instance of RankClient. @@ -112,7 +129,14 @@ public virtual async Task> RankAsync(Personaliz scope.Start(); try { - return await RestClient.RankAsync(rankRequest, cancellationToken).ConfigureAwait(false); + if (_isLocalInference) + { + return _rankProcessor.Rank(rankRequest); + } + else + { + return await RestClient.RankAsync(rankRequest, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) { 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 0000000000000..158498cf988ea --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContext.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context. + public class DecisionContext + { + /// The Decision Context used to serialize an object. + public DecisionContext() + { + } + + /// Initializes a new instance of DecisionContext. + /// The context feature + /// Rankable actions + public DecisionContext(IEnumerable contextFeatures, List rankableActions) + { + this.ContextFeatures = contextFeatures.Select(f => JsonSerializer.Serialize(f)).ToList(); + this.Documents = rankableActions + .Select(action => + { + List actionFeatures = action.Features.Select(f => JsonSerializer.Serialize(f)).ToList(); + + return new DecisionContextDocument(action.Id, actionFeatures); + }).ToArray(); + } + + /// Properties from url + [JsonPropertyName("FromUrl")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonConverter(typeof(JsonRawStringListConverter))] + public List ContextFeatures { get; } + + /// Properties of documents + [JsonPropertyName("_multi")] + public DecisionContextDocument[] Documents { get; set; } + + /// Properties of slots + [JsonPropertyName("_slots")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + 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 0000000000000..593e15c2b529e --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/DecisionContextDocument.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using System.Collections.Generic; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context Document. + public class DecisionContextDocument + { + /// Initializes a new instance of DecisionContextDocument. + /// Id of the decision context document + /// The json features + public DecisionContextDocument(string id, List Json) + { + ID = id; + JSON = Json; + } + /// + /// Supply _tag for online evaluation + /// + [JsonPropertyName("_tag")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string ID + { + get; + set; + } + + /// + /// Provide source set feature. + /// + [JsonPropertyName("s")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public DecisionContextDocumentSource Source { get; set; } + + /// + /// Generic json features. + /// + [JsonPropertyName("j")] + [JsonConverter(typeof(JsonRawStringListConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List JSON { get; } + + /// + /// Keep as float[] arrays to improve marshalling speed. + /// + [JsonPropertyName("f")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary FloatFeatures { get; } + + /// + /// Slot ID. + /// + [JsonPropertyName("_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string SlotId { get; set; } + + /// + /// Generic slot json features. + /// + [JsonPropertyName("sj")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [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 0000000000000..b114f161426ba --- /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 System.Text.Json.Serialization; + +namespace Azure.AI.Personalizer +{ + /// The Decision Context Document Source. + public class DecisionContextDocumentSource + { + /// The set. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string Set { get; set; } + + /// The parameter. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string Parameter { get; set; } + } +} 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 0000000000000..4af15c0627374 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/JsonRawStringListConverter.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Text.Json.Serialization; +using System; +using System.Collections.Generic; + +namespace Azure.AI.Personalizer +{ + /// Json raw string list converter + internal class JsonRawStringListConverter : JsonConverter> + { + /// + /// Not implemented. + /// + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + /// + /// Outputs the string contents as JSON. + /// + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + if (value != null) + { + writer.WriteStartArray(); + foreach (var str in value) + writer.WriteRawValue(str); + writer.WriteEndArray(); + return; + } + + JsonSerializer.Serialize(writer, value); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs index f729f7305b832..668b8b25f6459 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerClient.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using Azure.Core; @@ -24,6 +22,8 @@ public class PersonalizerClient private string stringEndpoint; private string apiKey; + private readonly RankProcessor _rankProcessor; + internal RankRestClient RankRestClient { get; set; } internal EventsRestClient EventsRestClient { get; set; } internal MultiSlotRestClient MultiSlotRestClient { get; set; } @@ -79,7 +79,10 @@ public PersonalizerClient(Uri endpoint, TokenCredential credential, bool isLocal { //Intialize liveModel and call Rank processor //ToDo:TASK 13057958: Working on changes to support token authentication in RLClient - //Configuration config = GetConfigurationForLiveModel("Token", "token"); + Configuration configuration = GetConfigurationForLiveModel("Token", "token"); + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } } @@ -127,7 +130,10 @@ public PersonalizerClient(Uri endpoint, AzureKeyCredential credential, bool isLo if (isLocalInference) { //Intialize liveModel and Rankprocessor - Configuration config = GetConfigurationForLiveModel("apiKey", apiKey); + Configuration configuration = GetConfigurationForLiveModel("apiKey", apiKey); + LiveModel liveModel = new LiveModel(configuration); + liveModel.Init(); + _rankProcessor = new RankProcessor(liveModel); } } @@ -162,8 +168,7 @@ public virtual async Task> RankAsync(Personaliz { if (_isLocalInference) { - //return RankProcessor result here - return null; + return _rankProcessor.Rank(options); } else { @@ -213,8 +218,7 @@ public virtual Response Rank(PersonalizerRankOptions opt { if (_isLocalInference) { - //return RankProcessor result here - return null; + return _rankProcessor.Rank(options); } else { diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankableAction.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankableAction.cs index 939a97032509a..b0dd9493f2c6f 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankableAction.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/PersonalizerRankableAction.cs @@ -15,5 +15,10 @@ public partial class PersonalizerRankableAction /// Need to be JSON serializable. https://docs.microsoft.com/azure/cognitive-services/personalizer/concepts-features. /// public IList Features { get; } + + /// + /// The index of the action in the original request + /// + public int Index { get; set; } } } 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 0000000000000..ca6c84111c655 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/RankProcessor.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Rl.Net; + +namespace Azure.AI.Personalizer +{ + /// The Rank Processor. + internal 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. + public Response Rank(PersonalizerRankOptions options) + { + string eventId = options.EventId; + if (String.IsNullOrEmpty(eventId)) + { + eventId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + + HashSet excludedSet = new HashSet(options.ExcludedActions); + + // Store the original action list + List originalActions = new List(); + List rankableActions = new List(); + List excludedActions = new List(); + int idx = 0; + foreach (var action in options.Actions) + { + PersonalizerRankableAction actionCopy = new PersonalizerRankableAction(action.Id, action.Features); + actionCopy.Index = idx; + originalActions.Add(actionCopy); + if (excludedSet.Contains(actionCopy.Id)) + { + excludedActions.Add(actionCopy); + } + else + { + rankableActions.Add(actionCopy); + } + ++idx; + } + + // Convert options to the compatible parameter for ChooseRank + var contextJson = RlObjectConverter.ConvertToContextJson(options.ContextFeatures, rankableActions); + ActionFlags flags = options.DeferActivation == true ? ActionFlags.Deferred : ActionFlags.Default; + + // Call ChooseRank of local RL.Net + RankingResponse rankingResponse = _liveModel.ChooseRank(eventId, contextJson, flags); + + // Convert response to PersonalizerRankResult + var value = RlObjectConverter.GenerateRankResult(originalActions, rankableActions, excludedActions, rankingResponse, options.EventId); + + return Response.FromValue(value, default); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/src/Models/RlObjectConverter.cs b/sdk/personalizer/Azure.AI.Personalizer/src/Models/RlObjectConverter.cs new file mode 100644 index 0000000000000..1b99c1e3663d9 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/src/Models/RlObjectConverter.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using Rl.Net; +using System.Collections.Generic; +using System.Linq; + +namespace Azure.AI.Personalizer +{ + /// The converter between objects for Rl.Net objects and the sdk + internal static class RlObjectConverter + { + /// + /// Convert PersonalizerRankOptions object to a json context string for Rl.Net + /// + public static string ConvertToContextJson(IEnumerable contextFeatures, List rankableActions) + { + DecisionContext decisionContext = new DecisionContext(contextFeatures, rankableActions); + return JsonSerializer.Serialize(decisionContext); + } + + /// + /// Create rank result based on Rl.Net response + /// + public static PersonalizerRankResult GenerateRankResult(List originalActions, + List rankableActions, List excludedActions, + RankingResponse rankingResponse, string eventId) + { + var rankedIndices = rankingResponse?.Select(actionProbability => ((int)actionProbability.ActionIndex + 1)).ToArray(); + + var rankingProbabilities = rankingResponse?.Select(actionProbability => + actionProbability.Probability).ToArray(); + + return GenerateRankResultInner(originalActions, rankableActions, excludedActions, rankedIndices, rankingProbabilities, eventId); + } + + private static PersonalizerRankResult GenerateRankResultInner(List originalActions, + List rankableActions, List excludedActions, int[] rankedIndices, float[] rankingProbabilities, string eventId, int multiSlotChosenActionIndex = -1) + { + // excluded actions are not passed into VW + // rankedIndices[0] is the index of the VW chosen action (1 based index) + // ccb response that is converted into a cb response: the chosen action index is a field in the vw response. + // multiSlotChosenActionIndex is part of the multi slot response (0 based index) + int chosenActionIndex = multiSlotChosenActionIndex == -1 ? rankedIndices[0] - 1 : multiSlotChosenActionIndex; + + // take care of actions that are excluded in their original positions + if (excludedActions != null && excludedActions.Count > 0) + { + var newRanking = new int[originalActions.Count]; + var probabilities = new float[originalActions.Count]; + + // at the original position + // point the original position of ranked item + for (int i = 0; i < rankableActions.Count; i++) + { + //RankableActions is Actions - ExcludedActions + newRanking[rankableActions[i].Index] = rankableActions[rankedIndices[i] - 1].Index + 1; + probabilities[rankableActions[i].Index] = rankingProbabilities[i]; + } + + // update excluded positions + foreach (var l in excludedActions) + newRanking[l.Index] = l.Index + 1; + + rankedIndices = newRanking; + rankingProbabilities = probabilities; + } + + var personalizerRankResult = new PersonalizerRankResult + { + EventId = eventId + }; + // finalize decision response ranking + personalizerRankResult.Ranking = rankedIndices?.Select((index, i) => + { + var action = originalActions[index - 1]; + return new PersonalizerRankedAction() + { + Id = action.Id, + Probability = rankingProbabilities[i] + }; + }).ToList(); + + //setting RewardActionId to be the VW chosen action. + personalizerRankResult.RewardActionId = originalActions.ElementAt(chosenActionIndex)?.Id; + + return personalizerRankResult; + } + } +} 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 67a686a2e33ca..b39fb2167a974 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,7 +4,11 @@ $(NoWarn);CS1591 + $(NoWarn);CS8032 + + + diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/DecisionContextTests.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/DecisionContextTests.cs new file mode 100644 index 0000000000000..88150d6e686eb --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/DecisionContextTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using NUnit.Framework; + +namespace Azure.AI.Personalizer.Tests +{ + public class DecisionContextTests + { + [Test] + public void DecisionContextConstructorTest() + { + IEnumerable contextFeatures = new List() { + new { Features = new { day = "Monday", time = "morning", weather = "sunny" } }, + }; + List actions = new List(); + actions.Add + (new PersonalizerRankableAction( + id: "Person", + features: + new List() { new { videoType = "documentary", videoLength = 35, director = "CarlSagan" }, new { mostWatchedByAge = "30-35" } } + )); + DecisionContext decisionContext = new DecisionContext(contextFeatures, actions); + Assert.AreEqual(decisionContext.ContextFeatures.Count, 1); + Assert.IsTrue(decisionContext.ContextFeatures[0].Equals("{\"Features\":{\"day\":\"Monday\",\"time\":\"morning\",\"weather\":\"sunny\"}}")); + Assert.AreEqual(decisionContext.Documents.Length, 1); + Assert.AreEqual(decisionContext.Documents[0].JSON.Count, 2); + Assert.IsTrue(decisionContext.Documents[0].JSON[0].Equals("{\"videoType\":\"documentary\",\"videoLength\":35,\"director\":\"CarlSagan\"}")); + Assert.IsTrue(decisionContext.Documents[0].JSON[1].Equals("{\"mostWatchedByAge\":\"30-35\"}")); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/JsonRawStringListConverterTests.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/JsonRawStringListConverterTests.cs new file mode 100644 index 0000000000000..5c9e907c7ef55 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/JsonRawStringListConverterTests.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using NUnit.Framework; + +namespace Azure.AI.Personalizer.Tests +{ + public class JsonRawStringListConverterTests + { + [Test] + public void WriteTest() + { + MemoryStream memStream = new MemoryStream(100); + Utf8JsonWriter writer = new Utf8JsonWriter(memStream); + JsonRawStringListConverter converter = new JsonRawStringListConverter(); + List value = new List(); + value.Add("{\"videoType\":\"documentary\"}"); + value.Add("{\"day\":\"Monday\"}"); + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + string json = Encoding.UTF8.GetString(memStream.ToArray()); + Assert.IsTrue(json.Equals("[{\"videoType\":\"documentary\"},{\"day\":\"Monday\"}]")); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs index 259d24e9cdcc7..3759c42f6350d 100644 --- a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RankTests.cs @@ -17,6 +17,18 @@ public RankTests(bool isAsync) : base(isAsync) public async Task SingleSlotRankTests() { PersonalizerClient client = await GetPersonalizerClientAsync(isSingleSlot: true); + await SingleSlotRankTests(client); + } + + [Test] + public async Task SingleSlotRankLocalInferenceTests() + { + PersonalizerClient client = await GetPersonalizerClientAsync(isSingleSlot: true, isLocalInference: true); + await SingleSlotRankTests(client); + } + + private async Task SingleSlotRankTests(PersonalizerClient client) + { await RankNullParameters(client); await RankServerFeatures(client); await RankNullParameters(client); diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RlObjectConverterTests.cs b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RlObjectConverterTests.cs new file mode 100644 index 0000000000000..fcced91dc2069 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/Personalizer/RlObjectConverterTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using NUnit.Framework; + +namespace Azure.AI.Personalizer.Tests +{ + public class RlObjectConverterTests + { + [Test] + public void ConvertToContextJsonTest() + { + IEnumerable contextFeatures = new List() { + new { Features = new { day = "Monday", time = "morning", weather = "sunny" } }, + }; + List actions = new List(); + actions.Add + (new PersonalizerRankableAction( + id: "Person", + features: + new List() { new { videoType = "documentary", videoLength = 35, director = "CarlSagan" }, new { mostWatchedByAge = "30-35" } } + )); + string contextJson = RlObjectConverter.ConvertToContextJson(contextFeatures, actions); + string expectedJson = + "{\"FromUrl\":[{" + + "\"Features\":{" + + "\"day\":\"Monday\"," + + "\"time\":\"morning\"," + + "\"weather\":\"sunny\"}}]," + + "\"_multi\":[{" + + "\"_tag\":\"Person\"," + + "\"j\":[{" + + "\"videoType\":\"documentary\"," + + "\"videoLength\":35," + + "\"director\":\"CarlSagan\"" + + "}," + + "{\"mostWatchedByAge\":\"30-35\"}" + + "]" + + "}]" + + "}"; + Assert.IsTrue(contextJson.Equals(expectedJson)); + } + } +} diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTests.json b/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTests.json new file mode 100644 index 0000000000000..22e88a549dc52 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTests.json @@ -0,0 +1,225 @@ +{ + "Entries": [ + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "139", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-8a2352f590c938419932183af7ad2039-475eb6f9bad6944a-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "72fe1c6ec5e1742467bce38ccf58b021", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "actions": [ + { + "id": "Person", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + } + ] + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "a7d4ebf4-e94b-4332-8066-a1936f394b5a", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "124", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:04 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "13492" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person", + "probability": 1.0 + } + ], + "eventId": "4e9af3e0604e4fc1af091c9e76c0c93d-8iJRK", + "rewardActionId": "Person" + } + }, + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "527", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-9eed4f0ce6b0c340802623beef2c0a21-5e9e395f17eb1343-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "f451845d717dfcfd636af66ed165bf61", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "contextFeatures": [ + { + "Features": { + "day": "tuesday", + "time": "night", + "weather": "rainy" + } + }, + { + "Features": { + "userId": "1234", + "payingUser": true, + "favoriteGenre": "documentary", + "hoursOnSite": 0.12, + "lastwatchedType": "movie" + } + } + ], + "actions": [ + { + "id": "Person1", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + }, + { + "id": "Person2", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "40-45" + } + ] + } + ], + "excludedActions": [ + "Person1" + ], + "eventId": "123456789" + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "f4625cd9-6ee1-4f1a-b6de-af954ff2465e", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "132", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:19 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "15290" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person1", + "probability": 0.0 + }, + { + "id": "Person2", + "probability": 1.0 + } + ], + "eventId": "123456789", + "rewardActionId": "Person2" + } + }, + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "139", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-cd2c7cfe73673242856c322a2fa711db-f43e2530c4f71446-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "66317083bf6f0fc05cfb3b5eae739abb", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "actions": [ + { + "id": "Person", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + } + ] + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "e4c82d6d-7ee8-4eef-85fe-d31735c47995", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "124", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:33 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "13443" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person", + "probability": 1.0 + } + ], + "eventId": "c638ab102e9f464dbc35f2f102f59f35-8iJSd", + "rewardActionId": "Person" + } + } + ], + "Variables": { + "PERSONALIZER_API_KEY_SINGLE_SLOT": "Sanitized", + "PERSONALIZER_ENDPOINT_SINGLE_SLOT": "https://singleslotrecordsdktests.cognitiveservices.azure.com/", + "RandomSeed": "1112192962" + } +} \ No newline at end of file diff --git a/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTestsAsync.json b/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTestsAsync.json new file mode 100644 index 0000000000000..22e88a549dc52 --- /dev/null +++ b/sdk/personalizer/Azure.AI.Personalizer/tests/SessionRecords/RankTests/SingleSlotRankLocalInferenceTestsAsync.json @@ -0,0 +1,225 @@ +{ + "Entries": [ + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "139", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-8a2352f590c938419932183af7ad2039-475eb6f9bad6944a-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "72fe1c6ec5e1742467bce38ccf58b021", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "actions": [ + { + "id": "Person", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + } + ] + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "a7d4ebf4-e94b-4332-8066-a1936f394b5a", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "124", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:04 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "13492" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person", + "probability": 1.0 + } + ], + "eventId": "4e9af3e0604e4fc1af091c9e76c0c93d-8iJRK", + "rewardActionId": "Person" + } + }, + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "527", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-9eed4f0ce6b0c340802623beef2c0a21-5e9e395f17eb1343-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "f451845d717dfcfd636af66ed165bf61", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "contextFeatures": [ + { + "Features": { + "day": "tuesday", + "time": "night", + "weather": "rainy" + } + }, + { + "Features": { + "userId": "1234", + "payingUser": true, + "favoriteGenre": "documentary", + "hoursOnSite": 0.12, + "lastwatchedType": "movie" + } + } + ], + "actions": [ + { + "id": "Person1", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + }, + { + "id": "Person2", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "40-45" + } + ] + } + ], + "excludedActions": [ + "Person1" + ], + "eventId": "123456789" + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "f4625cd9-6ee1-4f1a-b6de-af954ff2465e", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "132", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:19 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "15290" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person1", + "probability": 0.0 + }, + { + "id": "Person2", + "probability": 1.0 + } + ], + "eventId": "123456789", + "rewardActionId": "Person2" + } + }, + { + "RequestUri": "https://singleslotrecordsdktests.cognitiveservices.azure.com/personalizer/v1.1-preview.1/rank", + "RequestMethod": "POST", + "RequestHeaders": { + "Accept": "application/json", + "Content-Length": "139", + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": "Sanitized", + "traceparent": "00-cd2c7cfe73673242856c322a2fa711db-f43e2530c4f71446-00", + "User-Agent": "azsdk-net-AI.Personalizer/2.0.0-alpha.20210805.1 (.NET Framework 4.8.4300.0; Microsoft Windows 10.0.19043 )", + "x-ms-client-request-id": "66317083bf6f0fc05cfb3b5eae739abb", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": { + "actions": [ + { + "id": "Person", + "features": [ + { + "videoType": "documentary", + "videoLength": 35, + "director": "CarlSagan" + }, + { + "mostWatchedByAge": "30-35" + } + ] + } + ] + }, + "StatusCode": 201, + "ResponseHeaders": { + "apim-request-id": "e4c82d6d-7ee8-4eef-85fe-d31735c47995", + "Cache-Control": [ + "no-cache", + "no-store", + "must-revalidate" + ], + "Content-Length": "124", + "Content-Type": "application/json; charset=utf-8", + "Date": "Thu, 05 Aug 2021 20:55:33 GMT", + "Expires": "0", + "pragma": "no-cache", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-envoy-upstream-service-time": "13443" + }, + "ResponseBody": { + "ranking": [ + { + "id": "Person", + "probability": 1.0 + } + ], + "eventId": "c638ab102e9f464dbc35f2f102f59f35-8iJSd", + "rewardActionId": "Person" + } + } + ], + "Variables": { + "PERSONALIZER_API_KEY_SINGLE_SLOT": "Sanitized", + "PERSONALIZER_ENDPOINT_SINGLE_SLOT": "https://singleslotrecordsdktests.cognitiveservices.azure.com/", + "RandomSeed": "1112192962" + } +} \ No newline at end of file