Fix birthday, memory, pizza, and weather intent handling

This commit is contained in:
Jacob Dubin
2026-05-06 09:51:36 -05:00
parent b74ef3bfa2
commit ede694afdd
9 changed files with 57952 additions and 32 deletions

View File

@@ -385,8 +385,7 @@ public sealed class JiboInteractionService(
var payload = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
{
["skillId"] = "report-skill",
["localIntent"] = "requestWeatherPR",
["cloudSkill"] = "weather"
["localIntent"] = "requestWeatherPR"
};
var dateEntity = TryResolveWeatherDateEntity(transcript);
if (dateEntity is not null)
@@ -747,12 +746,12 @@ public sealed class JiboInteractionService(
return "memory_get_name";
}
if (IsUserBirthdaySetStatement(loweredTranscript))
if (IsUserBirthdaySetStatement(loweredTranscript) || IsUserBirthdaySetAttempt(loweredTranscript))
{
return "memory_set_birthday";
}
if (IsUserBirthdayRecallQuestion(loweredTranscript))
if (IsUserBirthdayRecallQuestion(loweredTranscript) || IsUserBirthdayRecallAttempt(loweredTranscript))
{
return "memory_get_birthday";
}
@@ -887,12 +886,12 @@ public sealed class JiboInteractionService(
return "cloud_version";
}
if (IsPreferenceSetStatement(loweredTranscript))
if (IsPreferenceSetStatement(loweredTranscript) || IsPreferenceSetAttempt(loweredTranscript))
{
return "memory_set_preference";
}
if (IsPreferenceRecallQuestion(loweredTranscript))
if (IsPreferenceRecallQuestion(loweredTranscript) || IsPreferenceRecallAttempt(loweredTranscript))
{
return "memory_get_preference";
}
@@ -1079,20 +1078,6 @@ public sealed class JiboInteractionService(
return "robot_personality";
}
if (MatchesAny(
loweredTranscript,
"can you cook us a pizza",
"flip a pizza",
"make a pizza",
"make pizza",
"show pizza",
"can you make pizza",
"let's make pizza",
"lets make pizza"))
{
return "pizza";
}
if (MatchesAny(
loweredTranscript,
"can you order pizza",
@@ -1102,11 +1087,31 @@ public sealed class JiboInteractionService(
"order a pizza",
"order us a pizza",
"order me a pizza",
"please order pizza"))
"please order pizza") ||
(loweredTranscript.Contains("order", StringComparison.Ordinal) &&
loweredTranscript.Contains("pizza", StringComparison.Ordinal)))
{
return "order_pizza";
}
if (MatchesAny(
loweredTranscript,
"can you cook us a pizza",
"flip a pizza",
"make a pizza",
"make pizza",
"show pizza",
"can you make pizza",
"let's make pizza",
"lets make pizza") ||
(loweredTranscript.Contains("pizza", StringComparison.Ordinal) &&
(loweredTranscript.Contains("make", StringComparison.Ordinal) ||
loweredTranscript.Contains("cook", StringComparison.Ordinal) ||
loweredTranscript.Contains("flip", StringComparison.Ordinal))))
{
return "pizza";
}
if (MatchesAny(loweredTranscript, "personal report", "my report", "daily report", "my update"))
{
return "personal_report";
@@ -1822,15 +1827,22 @@ public sealed class JiboInteractionService(
private static bool IsRobotBirthdayQuestion(string loweredTranscript)
{
return MatchesAny(
loweredTranscript,
"when is your birthday",
"when's your birthday",
"what's your birthday",
"what s your birthday",
"what is your birthday",
"when were you born",
"what day is your birthday");
var normalized = NormalizeCommandPhrase(loweredTranscript);
if (MatchesAny(
normalized,
"when is your birthday",
"when s your birthday",
"what s your birthday",
"what is your birthday",
"when were you born",
"what day is your birthday"))
{
return true;
}
return (normalized.Contains("your birthday", StringComparison.Ordinal) ||
normalized.Contains("your birth date", StringComparison.Ordinal))
&& !normalized.Contains("my birthday", StringComparison.Ordinal);
}
private static bool IsNameSetStatement(string loweredTranscript)
@@ -1889,6 +1901,22 @@ public sealed class JiboInteractionService(
return TryExtractBirthdayFact(loweredTranscript) is not null;
}
private static bool IsUserBirthdaySetAttempt(string loweredTranscript)
{
var normalized = NormalizeCommandPhrase(loweredTranscript);
return normalized.Contains("my birthday is", StringComparison.Ordinal);
}
private static bool IsUserBirthdayRecallAttempt(string loweredTranscript)
{
var normalized = NormalizeCommandPhrase(loweredTranscript);
return normalized.Contains("my birthday", StringComparison.Ordinal) &&
(normalized.StartsWith("when", StringComparison.Ordinal) ||
normalized.StartsWith("what", StringComparison.Ordinal) ||
normalized.StartsWith("tell me", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember", StringComparison.Ordinal));
}
private static string? TryExtractBirthdayFact(string transcript)
{
var normalized = NormalizeCommandPhrase(transcript);
@@ -1913,6 +1941,30 @@ public sealed class JiboInteractionService(
return TryExtractPreferenceSet(loweredTranscript) is not null;
}
private static bool IsPreferenceSetAttempt(string loweredTranscript)
{
var normalized = NormalizeCommandPhrase(loweredTranscript);
if (IsPreferenceRecallAttempt(normalized))
{
return false;
}
return normalized.Contains("my favorite", StringComparison.Ordinal) ||
normalized.Contains("my favourite", StringComparison.Ordinal) ||
PreferenceReverseMarkers.Any(marker => normalized.Contains(marker, StringComparison.Ordinal));
}
private static bool IsPreferenceRecallAttempt(string loweredTranscript)
{
var normalized = NormalizeCommandPhrase(loweredTranscript);
return normalized.StartsWith("what is my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what s my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("what is my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("what s my favourite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favorite", StringComparison.Ordinal) ||
normalized.StartsWith("do you remember my favourite", StringComparison.Ordinal);
}
private static string? TryExtractPreferenceLookupCategory(string transcript)
{
var normalized = NormalizeCommandPhrase(transcript);
@@ -2830,6 +2882,8 @@ public sealed class JiboInteractionService(
("country music", "Country"),
("country radio", "Country"),
("country", "Country"),
("football", "Sports"),
("sports", "Sports"),
("classic rock", "ClassicRock"),
("soft rock", "SoftRock"),
("hip hop", "HipHop"),

View File

@@ -175,6 +175,21 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("You told me your birthday is april 12.", recallDecision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_BirthdaySetAttemptWithoutValue_RoutesToBirthdayPrompt()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "my birthday is",
NormalizedTranscript = "my birthday is"
});
Assert.Equal("memory_set_birthday", decision.IntentName);
Assert.Equal("I can remember it if you say, my birthday is March 14.", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_PreferenceMemory_SetThenRecallWithinTenant()
{
@@ -212,6 +227,51 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("You told me your favorite music is jazz.", recallDecision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_PreferenceSetAttemptWithoutValue_RoutesToPreferencePrompt()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "my favorite music is",
NormalizedTranscript = "my favorite music is"
});
Assert.Equal("memory_set_preference", decision.IntentName);
Assert.Equal("I can remember it if you say, my favorite music is jazz.", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_PreferenceSetAttemptSportWithoutValue_RoutesToPreferencePrompt()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "my favorite sport.",
NormalizedTranscript = "my favorite sport."
});
Assert.Equal("memory_set_preference", decision.IntentName);
Assert.Equal("I can remember it if you say, my favorite music is jazz.", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_PreferenceRecallAttemptWithoutCategory_RoutesToRecallPrompt()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "what's my favorite",
NormalizedTranscript = "what's my favorite"
});
Assert.Equal("memory_get_preference", decision.IntentName);
Assert.Equal("Ask me like this: what is my favorite music?", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_PersonalMemory_IsTenantScoped()
{
@@ -545,6 +605,22 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("RA_JBO_OrderPizza", decision.SkillPayload!["mim_id"]);
}
[Fact]
public async Task BuildDecisionAsync_CanYouOrderAPizzaWithPunctuation_UsesLegacyOrderPizzaMimPayload()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "Can you order a pizza?",
NormalizedTranscript = "Can you order a pizza?"
});
Assert.Equal("order_pizza", decision.IntentName);
Assert.Equal("chitchat-skill", decision.SkillName);
Assert.Equal("RA_JBO_OrderPizza", decision.SkillPayload!["mim_id"]);
}
[Fact]
public async Task BuildDecisionAsync_ClientNluRequestOrderPizza_UsesLegacyOrderPizzaMimPayload()
{
@@ -579,7 +655,7 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("weather", decision.IntentName);
Assert.Equal("report-skill", decision.SkillName);
Assert.Equal("requestWeatherPR", decision.SkillPayload!["localIntent"]);
Assert.Equal("weather", decision.SkillPayload["cloudSkill"]);
Assert.False(decision.SkillPayload!.ContainsKey("cloudSkill"));
}
[Fact]
@@ -713,6 +789,25 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("My birthday is March 22, 2026.", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_ClientNluAskForDate_WithPrefixBirthdayTranscript_PrefersRobotBirthdayIntent()
{
var service = CreateService();
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "so what's your birthday",
NormalizedTranscript = "so what's your birthday",
Attributes = new Dictionary<string, object?>
{
["clientIntent"] = "askForDate"
}
});
Assert.Equal("robot_birthday", decision.IntentName);
Assert.Equal("My birthday is March 22, 2026.", decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_YesNoFollowUp_MapsShortAffirmationToYesIntent()
{

View File

@@ -1727,7 +1727,7 @@ public sealed class JiboWebSocketServiceTests
using var listenPayload = JsonDocument.Parse(replies[0].Text!);
Assert.Equal("requestWeatherPR", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("intent").GetString());
Assert.Equal("report-skill", listenPayload.RootElement.GetProperty("data").GetProperty("nlu").GetProperty("skill").GetString());
Assert.Equal("weather", listenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("cloudSkill").GetString());
Assert.Equal(JsonValueKind.Null, listenPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("cloudSkill").ValueKind);
using var redirectPayload = JsonDocument.Parse(replies[2].Text!);
Assert.Equal("report-skill", redirectPayload.RootElement.GetProperty("data").GetProperty("match").GetProperty("skillID").GetString());

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
{
"name": "neohubjibocom-neohubproactive-tidd36da4d442a611f1aba45cf821ea55ae",
"session": {
"hostName": "neo-hub.jibo.com",
"path": "/v1/proactive",
"kind": "neo-hub-proactive",
"token": "hub-usr_openjibo_owner-1777340189867"
},
"steps": [
{
"text": {
"type": "TRIGGER",
"ts": 1777341970615,
"msgID": "mid-d388c070-42a6-11f1-a414-5cf821ea55ae",
"transID": "tid-d36da4d4-42a6-11f1-aba4-5cf821ea55ae",
"data": {
"triggerSource": "SURPRISE",
"triggerData": {
"looperID": "5c0b221fdf9d450019c5e255"
}
}
},
"binary": null,
"expectedReplyTypes": []
},
{
"text": {
"type": "CONTEXT",
"ts": 1777341970702,
"msgID": "mid-d395f790-42a6-11f1-95f4-5cf821ea55ae",
"transID": "tid-d36da4d4-42a6-11f1-aba4-5cf821ea55ae",
"data": {
"runtime": {
"character": {
"emotion": {
"name": "NEUTRAL",
"valence": 0.45,
"confidence": 0.2
},
"motivation": {
"social": 1,
"playful": 0.5152989351851469
}
},
"perception": {
"speaker": "5c0b221fdf9d450019c5e255",
"peoplePresent": [
{
"id": "NOT_TRAINED",
"entityId": 16085,
"type": "fused",
"confidence": 1
}
]
},
"location": {
"city": "Pleasant Hill",
"state": "Missouri",
"stateAbbr": "MO",
"country": "United States",
"countryCode": "US",
"lat": 38.8358494,
"lng": -94.1427229,
"iso": "2026-04-27T21:06:10.626-05:00"
},
"loop": {
"loopId": "5c0b221fdf9d450019c5e253",
"users": [
{
"firstName": "Erin",
"lastName": "Picone",
"phoneticName": "Erin",
"gender": "female",
"birthdate": 649209600000,
"id": "5c0b221fdf9d450019c5e255",
"accountId": "5c0b20547c46170019235759"
}
],
"jibo": {
"color": "WHITE",
"birthdate": 1544234645598,
"id": "5c0b221fdf9d450019c5e254"
},
"owner": "5c0b221fdf9d450019c5e255"
},
"dialog": {
"referent": null
}
},
"skill": {
"id": null
},
"general": {
"release": "1.9.2"
}
}
},
"binary": null,
"expectedReplyTypes": []
}
]
}

File diff suppressed because it is too large Load Diff