Skip to content

Commit fbb4eb8

Browse files
EricDahlvangTom Laird-McConnell
andauthored
Cherry-Pick to 4.12: Change ChoiceSet to support LG (#5309) (#5312)
* Change ChoiceSet to support LG (#5309) * Make ChoiceSet implement ITemplate<ChoiceSet> Fix ExpressionProperty Throw in Try method that shouldn 't throw Updated ConfirmInput and ChoiceInput to use ITemplate>.BindData() if expression is template reference. Added unit tests * update schema * fix schema definition for choices * Fix warnings as errors (#5274) Co-authored-by: Tom Laird-McConnell <tomlm@microsoft.com>
1 parent 9ddedd5 commit fbb4eb8

27 files changed

+801
-118
lines changed

libraries/Adapters/Microsoft.Bot.Builder.Adapters.Twilio/TwilioClientWrapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public virtual bool ValidateSignature(HttpRequest httpRequest, Dictionary<string
7171

7272
var twilioSignature = httpRequest.Headers.ContainsKey(TwilioSignature)
7373
? httpRequest.Headers[TwilioSignature].ToString()
74-
: throw new ArgumentNullException($"HttpRequest is missing \"{TwilioSignature}\"");
74+
: throw new ArgumentException($"HttpRequest is missing \"{TwilioSignature}\"");
7575

7676
if (string.IsNullOrWhiteSpace(urlString))
7777
{

libraries/AdaptiveExpressions/ExpressionProperties/ExpressionProperty.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,16 @@ public virtual (T Value, string Error) TryGetValue(object data)
134134
{
135135
if (_expression == null && ExpressionText != null)
136136
{
137-
_expression = Expression.Parse(this.ExpressionText.TrimStart('='));
137+
try
138+
{
139+
_expression = Expression.Parse(this.ExpressionText.TrimStart('='));
140+
}
141+
#pragma warning disable CA1031 // Do not catch general exception types
142+
catch (Exception err)
143+
#pragma warning restore CA1031 // Do not catch general exception types
144+
{
145+
return (default(T), err.Message);
146+
}
138147
}
139148

140149
if (_expression != null)

libraries/Microsoft.Bot.Builder.AI.Orchestrator/OrchestratorAdaptiveRecognizer.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ public OrchestratorAdaptiveRecognizer(string modelFolder, string snapshotFile, I
6464
_resolver = resolver;
6565
if (modelFolder == null)
6666
{
67-
throw new ArgumentNullException($"Missing `ModelFolder` information.");
67+
throw new ArgumentNullException(nameof(modelFolder));
6868
}
6969

7070
if (snapshotFile == null)
7171
{
72-
throw new ArgumentNullException($"Missing `SnapshotFile` information.");
72+
throw new ArgumentNullException(nameof(snapshotFile));
7373
}
7474

7575
_modelFolder = modelFolder;
@@ -226,12 +226,16 @@ private void InitializeModel()
226226
{
227227
if (_modelFolder == null)
228228
{
229-
throw new ArgumentNullException($"Missing `ModelFolder` information.");
229+
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
230+
throw new ArgumentNullException("ModelFolder");
231+
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
230232
}
231233

232234
if (_snapshotFile == null)
233235
{
234-
throw new ArgumentNullException($"Missing `SnapshotFile` information.");
236+
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
237+
throw new ArgumentNullException("SnapshotFile");
238+
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
235239
}
236240

237241
if (_resolver != null)

libraries/Microsoft.Bot.Builder.Azure.Blobs/BlobsTranscriptStore.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ public async Task<PagedResult<IActivity>> GetTranscriptActivitiesAsync(string ch
153153

154154
if (string.IsNullOrEmpty(channelId))
155155
{
156-
throw new ArgumentNullException($"missing {nameof(channelId)}");
156+
throw new ArgumentNullException(nameof(channelId));
157157
}
158158

159159
if (string.IsNullOrEmpty(conversationId))
160160
{
161-
throw new ArgumentNullException($"missing {nameof(conversationId)}");
161+
throw new ArgumentNullException(nameof(conversationId));
162162
}
163163

164164
var pagedResult = new PagedResult<IActivity>();
@@ -231,7 +231,7 @@ public async Task<PagedResult<TranscriptInfo>> ListTranscriptsAsync(string chann
231231

232232
if (string.IsNullOrEmpty(channelId))
233233
{
234-
throw new ArgumentNullException($"missing {nameof(channelId)}");
234+
throw new ArgumentNullException(nameof(channelId));
235235
}
236236

237237
string token = null;
@@ -291,12 +291,12 @@ public async Task DeleteTranscriptAsync(string channelId, string conversationId)
291291
{
292292
if (string.IsNullOrEmpty(channelId))
293293
{
294-
throw new ArgumentNullException($"{nameof(channelId)} should not be null");
294+
throw new ArgumentNullException(nameof(channelId));
295295
}
296296

297297
if (string.IsNullOrEmpty(conversationId))
298298
{
299-
throw new ArgumentNullException($"{nameof(conversationId)} should not be null");
299+
throw new ArgumentNullException(nameof(conversationId));
300300
}
301301

302302
string token = null;

libraries/Microsoft.Bot.Builder.Azure/AzureBlobTranscriptStore.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ public async Task LogActivityAsync(IActivity activity)
154154
{
155155
if (string.IsNullOrEmpty(channelId))
156156
{
157-
throw new ArgumentNullException($"missing {nameof(channelId)}");
157+
throw new ArgumentNullException(nameof(channelId));
158158
}
159159

160160
if (string.IsNullOrEmpty(conversationId))
161161
{
162-
throw new ArgumentNullException($"missing {nameof(conversationId)}");
162+
throw new ArgumentNullException(nameof(conversationId));
163163
}
164164

165165
var pagedResult = new PagedResult<IActivity>();
@@ -227,7 +227,7 @@ public async Task<PagedResult<TranscriptInfo>> ListTranscriptsAsync(string chann
227227
{
228228
if (string.IsNullOrEmpty(channelId))
229229
{
230-
throw new ArgumentNullException($"missing {nameof(channelId)}");
230+
throw new ArgumentNullException(nameof(channelId));
231231
}
232232

233233
var dirName = GetDirName(channelId);
@@ -291,12 +291,12 @@ public async Task DeleteTranscriptAsync(string channelId, string conversationId)
291291
{
292292
if (string.IsNullOrEmpty(channelId))
293293
{
294-
throw new ArgumentNullException($"{nameof(channelId)} should not be null");
294+
throw new ArgumentNullException(nameof(channelId));
295295
}
296296

297297
if (string.IsNullOrEmpty(conversationId))
298298
{
299-
throw new ArgumentNullException($"{nameof(conversationId)} should not be null");
299+
throw new ArgumentNullException(nameof(conversationId));
300300
}
301301

302302
var dirName = GetDirName(channelId, conversationId);

libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Actions/ContinueConversationLater.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ public ContinueConversationLater([CallerFilePath] string callerPath = "", [Calle
9696
date = date.ToUniversalTime();
9797
if (date <= DateTime.UtcNow)
9898
{
99-
throw new ArgumentOutOfRangeException($"{nameof(Date)} must be in the future");
99+
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
100+
throw new ArgumentOutOfRangeException(nameof(Date), $"{nameof(Date)} must be in the future");
101+
#pragma warning restore CA2208 // Instantiate argument exceptions correctly
100102
}
101103

102104
// create ContinuationActivity from the conversation reference.

libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Input/ChoiceInput.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,7 @@ protected override object OnInitializeOptions(DialogContext dc, object options)
155155
op = new ChoiceInputOptions();
156156
}
157157

158-
var (choices, error) = Choices.TryGetValue(dc.State);
159-
if (error != null)
160-
{
161-
throw new InvalidOperationException(error);
162-
}
163-
164-
op.Choices = choices;
158+
op.Choices = GetChoiceSetAsync(dc).GetAwaiter().GetResult();
165159
}
166160

167161
return base.OnInitializeOptions(dc, op);
@@ -219,14 +213,23 @@ protected override async Task<IActivity> OnRenderPromptAsync(DialogContext dc, I
219213
var prompt = await base.OnRenderPromptAsync(dc, state, cancellationToken).ConfigureAwait(false);
220214
var channelId = dc.Context.Activity.ChannelId;
221215
var choiceOptions = ChoiceOptions?.GetValue(dc.State) ?? DefaultChoiceOptions[locale];
216+
var options = dc.State.GetValue<ChoiceInputOptions>(ThisPath.Options);
217+
218+
return AppendChoices(prompt.AsMessageActivity(), channelId, options.Choices, Style.GetValue(dc.State), choiceOptions);
219+
}
222220

223-
var (choices, error) = Choices.TryGetValue(dc.State);
224-
if (error != null)
221+
private async Task<ChoiceSet> GetChoiceSetAsync(DialogContext dc)
222+
{
223+
if (Choices.ExpressionText != null && Choices.ExpressionText.TrimStart().StartsWith("${", StringComparison.InvariantCultureIgnoreCase))
225224
{
226-
throw new InvalidOperationException(error);
225+
// use ITemplate<ChocieSet> to bind (aka LG)
226+
return await new ChoiceSet(Choices.ExpressionText).BindAsync(dc, dc.State).ConfigureAwait(false);
227+
}
228+
else
229+
{
230+
// use Expression to bind
231+
return Choices.TryGetValue(dc.State).Value;
227232
}
228-
229-
return AppendChoices(prompt.AsMessageActivity(), channelId, choices, Style.GetValue(dc.State), choiceOptions);
230233
}
231234

232235
private string DetermineCulture(DialogContext dc, FindChoicesOptions opt = null)

libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Input/ChoiceSet.cs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
26
using Microsoft.Bot.Builder.Dialogs.Choices;
37
using Newtonsoft.Json;
48
using Newtonsoft.Json.Linq;
9+
using Newtonsoft.Json.Serialization;
510

611
namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Input
712
{
813
/// <summary>
914
/// Defines ChoiceSet collection.
1015
/// </summary>
1116
[JsonConverter(typeof(ChoiceSetConverter))]
12-
public class ChoiceSet : List<Choice>
17+
public class ChoiceSet : List<Choice>, ITemplate<ChoiceSet>
1318
{
19+
private string template;
20+
1421
/// <summary>
1522
/// Initializes a new instance of the <see cref="ChoiceSet"/> class.
1623
/// </summary>
@@ -33,18 +40,21 @@ public ChoiceSet(IEnumerable<Choice> choices)
3340
/// <param name="obj">Choice values.</param>
3441
public ChoiceSet(object obj)
3542
{
36-
// support string[] => choice[]
37-
if (obj is IEnumerable<string> strings)
43+
if (obj is string template)
44+
{
45+
this.template = template;
46+
}
47+
else if (obj is IEnumerable<string> strings)
3848
{
49+
// support string[] => choice[]
3950
foreach (var str in strings)
4051
{
4152
this.Add(new Choice(str));
4253
}
4354
}
44-
45-
// support JArray to => choice
46-
if (obj is JArray array)
55+
else if (obj is JArray array)
4756
{
57+
// support JArray to => choice
4858
if (array.HasValues)
4959
{
5060
foreach (var element in array)
@@ -79,5 +89,41 @@ public ChoiceSet(object obj)
7989
/// </summary>
8090
/// <param name="value"><see cref="JToken"/> expression.</param>
8191
public static implicit operator ChoiceSet(JToken value) => new ChoiceSet(value);
92+
93+
/// <inheritdoc/>
94+
public async Task<ChoiceSet> BindAsync(DialogContext dialogContext, object data = null, CancellationToken cancellationToken = default)
95+
{
96+
if (this.template == null)
97+
{
98+
return this;
99+
}
100+
101+
var languageGenerator = dialogContext.Services.Get<LanguageGenerator>() ?? throw new MissingMemberException(nameof(LanguageGeneration));
102+
var lgResult = await languageGenerator.GenerateAsync(dialogContext, this.template, dialogContext.State).ConfigureAwait(false);
103+
if (lgResult is ChoiceSet cs)
104+
{
105+
return cs;
106+
}
107+
else if (lgResult is string str)
108+
{
109+
try
110+
{
111+
var jObj = (JToken)JsonConvert.DeserializeObject(str);
112+
113+
if (jObj is JArray jarr)
114+
{
115+
return new ChoiceSet(jarr);
116+
}
117+
118+
return jObj.ToObject<ChoiceSet>();
119+
}
120+
catch (JsonReaderException)
121+
{
122+
return new ChoiceSet(str.Split('|').Select(t => t.Trim()));
123+
}
124+
}
125+
126+
return new ChoiceSet(lgResult);
127+
}
82128
}
83129
}

libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Input/ConfirmInput.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public ConfirmInput([CallerFilePath] string callerPath = "", [CallerLineNumber]
9393
/// <param name="dc">The <see cref="DialogContext"/> for the current turn of conversation.</param>
9494
/// <param name="cancellationToken">Optional, the <see cref="CancellationToken"/> that can be used by other objects or threads to receive notice of cancellation.</param>
9595
/// <returns>InputState which reflects whether input was recognized as valid or not.</returns>
96-
protected override Task<InputState> OnRecognizeInputAsync(DialogContext dc, CancellationToken cancellationToken = default)
96+
protected async override Task<InputState> OnRecognizeInputAsync(DialogContext dc, CancellationToken cancellationToken = default)
9797
{
9898
var input = dc.State.GetValue<object>(VALUE_PROPERTY);
9999
if (dc.Context.Activity.Type == ActivityTypes.Message)
@@ -120,11 +120,11 @@ protected override Task<InputState> OnRecognizeInputAsync(DialogContext dc, Canc
120120
}
121121
}
122122

123-
return Task.FromResult(InputState.Valid);
123+
return InputState.Valid;
124124
}
125125
else
126126
{
127-
return Task.FromResult(InputState.Unrecognized);
127+
return InputState.Unrecognized;
128128
}
129129
}
130130
else
@@ -137,7 +137,8 @@ protected override Task<InputState> OnRecognizeInputAsync(DialogContext dc, Canc
137137
if (!choiceOptions.IncludeNumbers.HasValue || choiceOptions.IncludeNumbers.Value)
138138
{
139139
// The text may be a number in which case we will interpret that as a choice.
140-
var confirmChoices = ConfirmChoices?.GetValue(dc.State) ?? new List<Choice>() { defaults.Item1, defaults.Item2 };
140+
var confirmChoices = await GetConfirmChoicesAsync(dc, defaults).ConfigureAwait(false);
141+
141142
var secondAttemptResults = ChoiceRecognizers.RecognizeChoices(input.ToString(), confirmChoices);
142143
if (secondAttemptResults.Count > 0)
143144
{
@@ -146,13 +147,13 @@ protected override Task<InputState> OnRecognizeInputAsync(DialogContext dc, Canc
146147
}
147148
else
148149
{
149-
return Task.FromResult(InputState.Unrecognized);
150+
return InputState.Unrecognized;
150151
}
151152
}
152153
}
153154
}
154155

155-
return Task.FromResult(InputState.Valid);
156+
return InputState.Valid;
156157
}
157158

158159
/// <summary>
@@ -169,7 +170,7 @@ protected override async Task<IActivity> OnRenderPromptAsync(DialogContext dc, I
169170
var culture = DetermineCulture(dc);
170171
var defaults = DefaultChoiceOptions[culture];
171172
var choiceOptions = ChoiceOptions?.GetValue(dc.State) ?? defaults.Item3;
172-
var confirmChoices = ConfirmChoices?.GetValue(dc.State) ?? new List<Choice>() { defaults.Item1, defaults.Item2 };
173+
var confirmChoices = await GetConfirmChoicesAsync(dc, defaults).ConfigureAwait(false);
173174

174175
var prompt = await base.OnRenderPromptAsync(dc, state, cancellationToken).ConfigureAwait(false);
175176
var (style, _) = Style.TryGetValue(dc.State);
@@ -189,5 +190,29 @@ private string DetermineCulture(DialogContext dc)
189190

190191
return culture;
191192
}
193+
194+
private async Task<ChoiceSet> GetConfirmChoicesAsync(DialogContext dc, (Choice, Choice, ChoiceFactoryOptions) defaults)
195+
{
196+
ChoiceSet confirmChoices = null;
197+
if (ConfirmChoices != null)
198+
{
199+
if (ConfirmChoices.ExpressionText != null && ConfirmChoices.ExpressionText.TrimStart().StartsWith("${", StringComparison.InvariantCultureIgnoreCase))
200+
{
201+
// use ITemplate<ChocieSet> to bind (aka LG)
202+
confirmChoices = await new ChoiceSet(ConfirmChoices.ExpressionText).BindAsync(dc, dc.State).ConfigureAwait(false);
203+
}
204+
else
205+
{
206+
// use expression to bind
207+
confirmChoices = ConfirmChoices.TryGetValue(dc.State).Value;
208+
}
209+
}
210+
else
211+
{
212+
confirmChoices = new ChoiceSet(new List<Choice>() { defaults.Item1, defaults.Item2 });
213+
}
214+
215+
return confirmChoices;
216+
}
192217
}
193218
}

libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Schemas/Dialogs/Microsoft.ChoiceInput.schema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
]
9898
},
9999
{
100-
"$ref": "schema:#/definitions/equalsExpression"
100+
"$ref": "schema:#/definitions/stringExpression"
101101
}
102102
]
103103
},

0 commit comments

Comments
 (0)