more Jibo patches
This commit is contained in:
@@ -89,6 +89,7 @@ Parallel tags:
|
|||||||
- the attached `jibo test 13` session includes both examples in one bundle:
|
- the attached `jibo test 13` session includes both examples in one bundle:
|
||||||
- a proactive or share-style prompt where spoken `yes` was treated as generic speech
|
- a proactive or share-style prompt where spoken `yes` was treated as generic speech
|
||||||
- a later update prompt where spoken `no` was accepted correctly
|
- a later update prompt where spoken `no` was accepted correctly
|
||||||
|
- the share prompt uses `surprises-date/offer_date_fact` with `$YESNO`, and the failing reply leaked `globals/*` rules back into a Nimbus relaunch
|
||||||
- Implementation notes:
|
- Implementation notes:
|
||||||
- compare the active listen rules, ASR hints, and local skill ownership for the share-style prompt versus OTA prompts
|
- compare the active listen rules, ASR hints, and local skill ownership for the share-style prompt versus OTA prompts
|
||||||
- make constrained yes-no detection cover this prompt family without regressing the already-working update `no` path
|
- make constrained yes-no detection cover this prompt family without regressing the already-working update `no` path
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ public sealed class JiboInteractionService(
|
|||||||
string.Equals(rule, "$YESNO", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "$YESNO", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(rule, "surprises-date/offer_date_fact", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,16 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
|||||||
: isWordOfDayGuess
|
: isWordOfDayGuess
|
||||||
? ["word-of-the-day/puzzle"]
|
? ["word-of-the-day/puzzle"]
|
||||||
: isYesNoTurn && isYesNoIntent ? [yesNoRule!] : rules;
|
: isYesNoTurn && isYesNoIntent ? [yesNoRule!] : rules;
|
||||||
var entities = ReadEntities(turn, messageType, isYesNoTurn && isYesNoIntent, isWordOfDayLaunch, isRadioLaunch, isWordOfDayGuess, wordOfDayGuess, radioStation);
|
var entities = ReadEntities(
|
||||||
|
turn,
|
||||||
|
messageType,
|
||||||
|
isYesNoTurn && isYesNoIntent,
|
||||||
|
ShouldIncludeCreateDomain(yesNoRule),
|
||||||
|
isWordOfDayLaunch,
|
||||||
|
isRadioLaunch,
|
||||||
|
isWordOfDayGuess,
|
||||||
|
wordOfDayGuess,
|
||||||
|
radioStation);
|
||||||
var listenMessage = new
|
var listenMessage = new
|
||||||
{
|
{
|
||||||
type = "LISTEN",
|
type = "LISTEN",
|
||||||
@@ -265,15 +274,21 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
|||||||
private static object ReadEntities(
|
private static object ReadEntities(
|
||||||
TurnContext turn,
|
TurnContext turn,
|
||||||
string? messageType,
|
string? messageType,
|
||||||
bool yesNoCreateTurn,
|
bool yesNoTurn,
|
||||||
|
bool includeCreateDomain,
|
||||||
bool wordOfDayLaunch,
|
bool wordOfDayLaunch,
|
||||||
bool radioLaunch,
|
bool radioLaunch,
|
||||||
bool wordOfDayGuess,
|
bool wordOfDayGuess,
|
||||||
string? guess,
|
string? guess,
|
||||||
string? radioStation)
|
string? radioStation)
|
||||||
{
|
{
|
||||||
if (yesNoCreateTurn)
|
if (yesNoTurn)
|
||||||
{
|
{
|
||||||
|
if (!includeCreateDomain)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object?>();
|
||||||
|
}
|
||||||
|
|
||||||
return new Dictionary<string, object?>
|
return new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["domain"] = "create"
|
["domain"] = "create"
|
||||||
@@ -331,9 +346,16 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
|||||||
.FirstOrDefault(static rule =>
|
.FirstOrDefault(static rule =>
|
||||||
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(rule, "surprises-date/offer_date_fact", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldIncludeCreateDomain(string? yesNoRule)
|
||||||
|
{
|
||||||
|
return string.Equals(yesNoRule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(yesNoRule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> ReadRuleValues(TurnContext turn)
|
private static IEnumerable<string> ReadRuleValues(TurnContext turn)
|
||||||
{
|
{
|
||||||
return ReadRuleValues(turn, "listenRules").Concat(ReadRuleValues(turn, "clientRules"));
|
return ReadRuleValues(turn, "listenRules").Concat(ReadRuleValues(turn, "clientRules"));
|
||||||
@@ -715,8 +737,7 @@ public sealed class ResponsePlanToSocketMessagesMapper
|
|||||||
.Replace("&", "&", StringComparison.Ordinal)
|
.Replace("&", "&", StringComparison.Ordinal)
|
||||||
.Replace("<", "<", StringComparison.Ordinal)
|
.Replace("<", "<", StringComparison.Ordinal)
|
||||||
.Replace(">", ">", StringComparison.Ordinal)
|
.Replace(">", ">", StringComparison.Ordinal)
|
||||||
.Replace("\"", """, StringComparison.Ordinal)
|
.Replace("\"", """, StringComparison.Ordinal);
|
||||||
.Replace("'", "'", StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? ReadPayloadString(IDictionary<string, object?>? payload, string key)
|
private static string? ReadPayloadString(IDictionary<string, object?>? payload, string key)
|
||||||
|
|||||||
@@ -710,6 +710,7 @@ public sealed class WebSocketTurnFinalizationService(
|
|||||||
string.Equals(rule, "$YESNO", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "$YESNO", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "create/is_it_a_keeper", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(rule, "settings/download_now_later", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(rule, "surprises-date/offer_date_fact", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
string.Equals(rule, "surprises-ota/want_to_download_now", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,26 @@ public sealed class JiboInteractionServiceTests
|
|||||||
Assert.Equal("No.", decision.ReplyText);
|
Assert.Equal("No.", decision.ReplyText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BuildDecisionAsync_SurprisesDateOfferPrompt_MapsShortAffirmationToYesIntent()
|
||||||
|
{
|
||||||
|
var service = CreateService();
|
||||||
|
|
||||||
|
var decision = await service.BuildDecisionAsync(new TurnContext
|
||||||
|
{
|
||||||
|
RawTranscript = "Yes!",
|
||||||
|
NormalizedTranscript = "Yes!",
|
||||||
|
Attributes = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["listenRules"] = new[] { "surprises-date/offer_date_fact", "globals/global_commands_launch" },
|
||||||
|
["listenAsrHints"] = new[] { "$YESNO" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("yes", decision.IntentName);
|
||||||
|
Assert.Equal("Yes.", decision.ReplyText);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BuildDecisionAsync_SkillPhraseVariant_MapsToKnownIntent()
|
public async Task BuildDecisionAsync_SkillPhraseVariant_MapsToKnownIntent()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Jibo.Cloud.Domain.Models;
|
|||||||
using Jibo.Cloud.Infrastructure.Content;
|
using Jibo.Cloud.Infrastructure.Content;
|
||||||
using Jibo.Cloud.Infrastructure.Persistence;
|
using Jibo.Cloud.Infrastructure.Persistence;
|
||||||
using Jibo.Cloud.Tests.Fixtures;
|
using Jibo.Cloud.Tests.Fixtures;
|
||||||
|
using Jibo.Runtime.Abstractions;
|
||||||
|
|
||||||
namespace Jibo.Cloud.Tests.WebSockets;
|
namespace Jibo.Cloud.Tests.WebSockets;
|
||||||
|
|
||||||
@@ -403,6 +404,85 @@ public sealed class JiboWebSocketServiceTests
|
|||||||
Assert.Equal("surprises-ota/want_to_download_now", listenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("rule").GetString());
|
Assert.Equal("surprises-ota/want_to_download_now", listenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("rule").GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientAsr_SurprisesDateOfferPrompt_MapsYesWithoutGlobalRuleLeak()
|
||||||
|
{
|
||||||
|
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
|
||||||
|
{
|
||||||
|
HostName = "neo-hub.jibo.com",
|
||||||
|
Path = "/listen",
|
||||||
|
Kind = "neo-hub-listen",
|
||||||
|
Token = "hub-share-yesno-token",
|
||||||
|
Text = """{"type":"LISTEN","transID":"trans-share-yes","data":{"rules":["surprises-date/offer_date_fact","globals/gui_nav","globals/mim_repeat","globals/global_commands_launch"],"asr":{"hints":["$YESNO"]}}}"""
|
||||||
|
});
|
||||||
|
|
||||||
|
var replies = await _service.HandleMessageAsync(new WebSocketMessageEnvelope
|
||||||
|
{
|
||||||
|
HostName = "neo-hub.jibo.com",
|
||||||
|
Path = "/listen",
|
||||||
|
Kind = "neo-hub-listen",
|
||||||
|
Token = "hub-share-yesno-token",
|
||||||
|
Text = """{"type":"CLIENT_ASR","transID":"trans-share-yes","data":{"text":"Yes!"}}"""
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(3, replies.Count);
|
||||||
|
|
||||||
|
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
|
||||||
|
Assert.Equal("yes", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString());
|
||||||
|
var rules = listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("rules");
|
||||||
|
Assert.Single(rules.EnumerateArray());
|
||||||
|
Assert.Equal("surprises-date/offer_date_fact", rules[0].GetString());
|
||||||
|
Assert.Equal("surprises-date/offer_date_fact", listenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("rule").GetString());
|
||||||
|
Assert.Equal(0, listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("entities").EnumerateObject().Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResponsePlanMapper_EscapesSpeechWithoutEncodingApostrophes()
|
||||||
|
{
|
||||||
|
var plan = new ResponsePlan
|
||||||
|
{
|
||||||
|
IntentName = "chat",
|
||||||
|
Actions =
|
||||||
|
{
|
||||||
|
new SpeakAction
|
||||||
|
{
|
||||||
|
Sequence = 0,
|
||||||
|
Text = "I'm glad you're here.",
|
||||||
|
Voice = "griffin"
|
||||||
|
},
|
||||||
|
new InvokeNativeSkillAction
|
||||||
|
{
|
||||||
|
Sequence = 1,
|
||||||
|
SkillName = "chitchat-skill",
|
||||||
|
Payload = new Dictionary<string, object?>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var turn = new TurnContext
|
||||||
|
{
|
||||||
|
Attributes = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["transID"] = "trans-apostrophe"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var replies = ResponsePlanToSocketMessagesMapper.Map(plan, turn, new CloudSession(), emitSkillActions: true);
|
||||||
|
using var payload = JsonDocument.Parse(replies[2].Text);
|
||||||
|
var esml = payload.RootElement
|
||||||
|
.GetProperty("data")
|
||||||
|
.GetProperty("action")
|
||||||
|
.GetProperty("config")
|
||||||
|
.GetProperty("jcp")
|
||||||
|
.GetProperty("config")
|
||||||
|
.GetProperty("play")
|
||||||
|
.GetProperty("esml")
|
||||||
|
.GetString();
|
||||||
|
|
||||||
|
Assert.Contains("I'm glad you're here.", esml, StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain("'", esml, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ClientAsr_OpenTheRadio_EmitsRadioRedirectAndSilentCompletion()
|
public async Task ClientAsr_OpenTheRadio_EmitsRadioRedirectAndSilentCompletion()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user