Refine persona routing and update 1.0.19 plan
This commit is contained in:
@@ -590,15 +590,15 @@ Current release theme:
|
|||||||
|
|
||||||
### 22. Command Vs Question Reply Style
|
### 22. Command Vs Question Reply Style
|
||||||
|
|
||||||
- Status: `ready`
|
- Status: `implemented`
|
||||||
- Tags: `content`, `polish`
|
- Tags: `content`, `polish`
|
||||||
- User goals:
|
- Result:
|
||||||
- `dance` should behave like a willing action
|
- `dance` still launches the dance animation path
|
||||||
- `do you like to dance` should answer the question before or instead of treating it like the same command
|
- `do you like to dance` now responds conversationally as a personality question instead of launching the action
|
||||||
- Implementation notes:
|
- birthday phrasing now takes precedence over an `askForDate` client-intent misclassification
|
||||||
- evolve reply collections into command/question variants
|
- Follow-up:
|
||||||
- start with dance or another expressive skill
|
- expand command-vs-question splits to more expressive intents (pizza, surprise, photo prompts)
|
||||||
- keep the first version rule-based
|
- add Pegasus phrase and MIM-backed variants for richer style coverage
|
||||||
|
|
||||||
## Suggested Order
|
## Suggested Order
|
||||||
|
|
||||||
@@ -614,16 +614,17 @@ Use [regression-test-plan.md](regression-test-plan.md) as the detailed checklist
|
|||||||
|
|
||||||
For `1.0.19`:
|
For `1.0.19`:
|
||||||
|
|
||||||
1. Harden stop or volume if the `1.0.18` live pass exposes stock-OS quirks / harden $YESNO interaction
|
1. Command-vs-question personality split (`dance` command vs `do you like to dance` question style; expand this pattern)
|
||||||
2. Make a pizza. How old are you? When's your birthday? Do you have a personality? (`implemented` in the first `1.0.19` slice; continue refining with persistent identity metadata and richer persona variants.)
|
2. First memory-backed personal facts with tenant-scoped storage (birthday/preferences foundation)
|
||||||
3. Holidays and seasonal personality slice so persona evolution remains visible and testable
|
3. Proactivity selector baseline with source-backed first offers
|
||||||
4. Multi-tenant internal storage foundation for memory/personality data (account/loop/device scoped) with cloud-ready persistence boundaries
|
4. Dialog parsing expansion and ambiguity guardrails
|
||||||
5. Update, backup, and restore proof
|
5. Holidays and seasonal personality behavior built on the new memory/proactivity foundation
|
||||||
6. STT upgrade and noise screening
|
6. Update, backup, and restore proof
|
||||||
7. Hosted capture/storage plan / indexing for group testing
|
7. STT upgrade and noise screening
|
||||||
8. Binary-safe media storage / sync to cloud drive: OneDrive, Google Drive, Box, etc.
|
8. Hosted capture/storage plan / indexing for group testing
|
||||||
9. Provider-backed news and weather
|
9. Binary-safe media storage / sync to cloud drive: OneDrive, Google Drive, Box, etc.
|
||||||
10. Proactivity, dialog parsing/NLP, memory/history, Lasso, identity, and onboarding as larger discovery-driven tracks
|
10. Provider-backed news and weather
|
||||||
|
11. Lasso, identity, and onboarding as larger discovery-driven tracks
|
||||||
|
|
||||||
For `1.0.20` and beyond:
|
For `1.0.20` and beyond:
|
||||||
|
|
||||||
|
|||||||
@@ -59,12 +59,17 @@ This slice is intentionally small and user-visible. It creates immediate persona
|
|||||||
|
|
||||||
## Next Slices
|
## Next Slices
|
||||||
|
|
||||||
1. Update/backup/restore end-to-end proof (operator-run and documented)
|
1. Command-vs-question personality split (start with dance/twerk-style prompts, keep commands action-oriented and questions conversational)
|
||||||
2. Holidays and seasonal personality slice (first scoped calendar + response set)
|
2. First memory-backed personal facts (tenant-scoped birthday/preferences storage contracts + initial implementation)
|
||||||
3. Multi-tenant memory storage foundation (tenant model + persistence contracts + initial implementation)
|
3. Proactivity selector baseline (source-backed first proactive offers with safe throttling and stock-compatible payloads)
|
||||||
4. STT noise-screening and short-utterance reliability pass
|
4. Dialog parsing expansion (more phrase variants, ambiguity handling, and transcript-to-intent guardrails)
|
||||||
5. Provider-backed news/weather expansion using Pegasus-backed contracts
|
5. Holidays and seasonal personality slice (time-scoped content backed by the new memory/proactivity path)
|
||||||
6. Capture indexing and retention boundary for group testing
|
6. Update/backup/restore end-to-end proof (operator-run and documented)
|
||||||
|
7. STT noise-screening and short-utterance reliability pass
|
||||||
|
8. Provider-backed news/weather expansion using Pegasus-backed contracts
|
||||||
|
9. Capture indexing and retention boundary for group testing
|
||||||
|
|
||||||
|
For slices 1-5, use Pegasus phrase lists, MIM IDs, and behavior patterns as the source anchor before broadening into OpenJibo-native improvements.
|
||||||
|
|
||||||
## Definition Of Done
|
## Definition Of Done
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ public sealed class JiboExperienceCatalog
|
|||||||
public IReadOnlyList<string> NewsBriefings { get; init; } = [];
|
public IReadOnlyList<string> NewsBriefings { get; init; } = [];
|
||||||
public IReadOnlyList<string> GenericFallbackReplies { get; init; } = [];
|
public IReadOnlyList<string> GenericFallbackReplies { get; init; } = [];
|
||||||
public IReadOnlyList<string> DanceReplies { get; init; } = [];
|
public IReadOnlyList<string> DanceReplies { get; init; } = [];
|
||||||
|
public IReadOnlyList<string> DanceQuestionReplies { get; init; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public sealed class JiboInteractionService(
|
|||||||
return semanticIntent switch
|
return semanticIntent switch
|
||||||
{
|
{
|
||||||
"joke" => BuildJokeDecision(catalog),
|
"joke" => BuildJokeDecision(catalog),
|
||||||
|
"dance_question" => BuildDanceQuestionDecision(catalog),
|
||||||
"dance" => BuildRandomDanceDecision(catalog),
|
"dance" => BuildRandomDanceDecision(catalog),
|
||||||
"twerk" => BuildDanceDecision("twerk", "rom-twerk", "Watch me twerk."),
|
"twerk" => BuildDanceDecision("twerk", "rom-twerk", "Watch me twerk."),
|
||||||
"time" => BuildClockLaunchDecision("time", "clock", "askForTime", "Showing the time."),
|
"time" => BuildClockLaunchDecision("time", "clock", "askForTime", "Showing the time."),
|
||||||
@@ -167,6 +168,11 @@ public sealed class JiboInteractionService(
|
|||||||
return BuildDanceDecision("dance", dance, replyText);
|
return BuildDanceDecision("dance", dance, replyText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JiboInteractionDecision BuildDanceQuestionDecision(JiboExperienceCatalog catalog)
|
||||||
|
{
|
||||||
|
return new JiboInteractionDecision("dance_question", randomizer.Choose(catalog.DanceQuestionReplies));
|
||||||
|
}
|
||||||
|
|
||||||
private static JiboInteractionDecision BuildDanceDecision(string intentName, string dance, string replyText)
|
private static JiboInteractionDecision BuildDanceDecision(string intentName, string dance, string replyText)
|
||||||
{
|
{
|
||||||
return new JiboInteractionDecision(
|
return new JiboInteractionDecision(
|
||||||
@@ -260,6 +266,11 @@ public sealed class JiboInteractionService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsRobotBirthdayQuestion(loweredTranscript))
|
||||||
|
{
|
||||||
|
return "robot_birthday";
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(clientIntent, "askForTime", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(clientIntent, "askForTime", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "time";
|
return "time";
|
||||||
@@ -500,6 +511,11 @@ public sealed class JiboInteractionService(
|
|||||||
return "photo_gallery";
|
return "photo_gallery";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsDanceQuestion(loweredTranscript))
|
||||||
|
{
|
||||||
|
return "dance_question";
|
||||||
|
}
|
||||||
|
|
||||||
if (MatchesAny(loweredTranscript, "twerk"))
|
if (MatchesAny(loweredTranscript, "twerk"))
|
||||||
{
|
{
|
||||||
return "twerk";
|
return "twerk";
|
||||||
@@ -525,17 +541,6 @@ public sealed class JiboInteractionService(
|
|||||||
return "robot_age";
|
return "robot_age";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MatchesAny(
|
|
||||||
loweredTranscript,
|
|
||||||
"when is your birthday",
|
|
||||||
"when's your birthday",
|
|
||||||
"what is your birthday",
|
|
||||||
"when were you born",
|
|
||||||
"what day is your birthday"))
|
|
||||||
{
|
|
||||||
return "robot_birthday";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MatchesAny(
|
if (MatchesAny(
|
||||||
loweredTranscript,
|
loweredTranscript,
|
||||||
"do you have a personality",
|
"do you have a personality",
|
||||||
@@ -564,7 +569,12 @@ public sealed class JiboInteractionService(
|
|||||||
if (MatchesAny(
|
if (MatchesAny(
|
||||||
loweredTranscript,
|
loweredTranscript,
|
||||||
"can you order pizza",
|
"can you order pizza",
|
||||||
|
"can you order a pizza",
|
||||||
|
"could you order a pizza",
|
||||||
"order pizza",
|
"order pizza",
|
||||||
|
"order a pizza",
|
||||||
|
"order us a pizza",
|
||||||
|
"order me a pizza",
|
||||||
"please order pizza"))
|
"please order pizza"))
|
||||||
{
|
{
|
||||||
return "order_pizza";
|
return "order_pizza";
|
||||||
@@ -1078,6 +1088,31 @@ public sealed class JiboInteractionService(
|
|||||||
return candidates.Any(candidate => loweredTranscript.Contains(candidate, StringComparison.Ordinal));
|
return candidates.Any(candidate => loweredTranscript.Contains(candidate, StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDanceQuestion(string loweredTranscript)
|
||||||
|
{
|
||||||
|
return MatchesAny(
|
||||||
|
loweredTranscript,
|
||||||
|
"do you like to dance",
|
||||||
|
"do you like dancing",
|
||||||
|
"what kind of dance do you like",
|
||||||
|
"what kind of dancing do you like",
|
||||||
|
"do you enjoy dancing");
|
||||||
|
}
|
||||||
|
|
||||||
|
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") ||
|
||||||
|
loweredTranscript.Contains("birthday", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static string? TryResolveRadioGenre(string loweredTranscript)
|
private static string? TryResolveRadioGenre(string loweredTranscript)
|
||||||
{
|
{
|
||||||
foreach (var (phrase, station) in RadioGenreAliases)
|
foreach (var (phrase, station) in RadioGenreAliases)
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
|
|||||||
"Watch me dance.",
|
"Watch me dance.",
|
||||||
"Here's my favorite dance move."
|
"Here's my favorite dance move."
|
||||||
],
|
],
|
||||||
|
DanceQuestionReplies =
|
||||||
|
[
|
||||||
|
"I love to dance. Tell me to dance and I will show you a move.",
|
||||||
|
"Absolutely. Dancing is one of my favorite things to do.",
|
||||||
|
"Dancing is my kind of fun. Say dance and I am in."
|
||||||
|
],
|
||||||
GreetingReplies =
|
GreetingReplies =
|
||||||
[
|
[
|
||||||
"Hi there. It is really good to talk with you.",
|
"Hi there. It is really good to talk with you.",
|
||||||
|
|||||||
@@ -41,6 +41,22 @@ public sealed class JiboInteractionServiceTests
|
|||||||
Assert.Equal("<speak>Okay.<break size='0.2'/> Watch this.<anim cat='dance' filter='music, rom-upbeat' /></speak>", decision.SkillPayload!["esml"]);
|
Assert.Equal("<speak>Okay.<break size='0.2'/> Watch this.<anim cat='dance' filter='music, rom-upbeat' /></speak>", decision.SkillPayload!["esml"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BuildDecisionAsync_DoYouLikeToDance_UsesQuestionReplyStyleInsteadOfTriggeringDanceAnimation()
|
||||||
|
{
|
||||||
|
var service = CreateService();
|
||||||
|
|
||||||
|
var decision = await service.BuildDecisionAsync(new TurnContext
|
||||||
|
{
|
||||||
|
RawTranscript = "do you like to dance",
|
||||||
|
NormalizedTranscript = "do you like to dance"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("dance_question", decision.IntentName);
|
||||||
|
Assert.Null(decision.SkillName);
|
||||||
|
Assert.Equal("I love to dance. Tell me to dance and I will show you a move.", decision.ReplyText);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BuildDecisionAsync_TwerkQuestion_PrefersSpecificTwerkIntent()
|
public async Task BuildDecisionAsync_TwerkQuestion_PrefersSpecificTwerkIntent()
|
||||||
{
|
{
|
||||||
@@ -90,6 +106,21 @@ public sealed class JiboInteractionServiceTests
|
|||||||
Assert.Equal("My birthday is March 22, 2026.", decision.ReplyText);
|
Assert.Equal("My birthday is March 22, 2026.", decision.ReplyText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BuildDecisionAsync_WhatsYourBirthday_DoesNotFallThroughToDateIntent()
|
||||||
|
{
|
||||||
|
var service = CreateService();
|
||||||
|
|
||||||
|
var decision = await service.BuildDecisionAsync(new TurnContext
|
||||||
|
{
|
||||||
|
RawTranscript = "what's your birthday",
|
||||||
|
NormalizedTranscript = "what's your birthday"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("robot_birthday", decision.IntentName);
|
||||||
|
Assert.Equal("My birthday is March 22, 2026.", decision.ReplyText);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BuildDecisionAsync_DoYouHaveAPersonality_UsesCatalogBackedPersonalityReply()
|
public async Task BuildDecisionAsync_DoYouHaveAPersonality_UsesCatalogBackedPersonalityReply()
|
||||||
{
|
{
|
||||||
@@ -162,6 +193,22 @@ public sealed class JiboInteractionServiceTests
|
|||||||
Assert.Contains("I can't do that yet", decision.SkillPayload["esml"]?.ToString(), StringComparison.Ordinal);
|
Assert.Contains("I can't do that yet", decision.SkillPayload["esml"]?.ToString(), StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BuildDecisionAsync_OrderAPizza_UsesLegacyOrderPizzaMimPayload()
|
||||||
|
{
|
||||||
|
var service = CreateService();
|
||||||
|
|
||||||
|
var decision = await service.BuildDecisionAsync(new TurnContext
|
||||||
|
{
|
||||||
|
RawTranscript = "order a pizza",
|
||||||
|
NormalizedTranscript = "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]
|
[Fact]
|
||||||
public async Task BuildDecisionAsync_ClientNluRequestOrderPizza_UsesLegacyOrderPizzaMimPayload()
|
public async Task BuildDecisionAsync_ClientNluRequestOrderPizza_UsesLegacyOrderPizzaMimPayload()
|
||||||
{
|
{
|
||||||
@@ -200,6 +247,25 @@ public sealed class JiboInteractionServiceTests
|
|||||||
Assert.Equal("askForDate", decision.SkillPayload!["clockIntent"]);
|
Assert.Equal("askForDate", decision.SkillPayload!["clockIntent"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BuildDecisionAsync_ClientNluAskForDate_WithBirthdayTranscript_PrefersRobotBirthdayIntent()
|
||||||
|
{
|
||||||
|
var service = CreateService();
|
||||||
|
|
||||||
|
var decision = await service.BuildDecisionAsync(new TurnContext
|
||||||
|
{
|
||||||
|
RawTranscript = "what's your birthday",
|
||||||
|
NormalizedTranscript = "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]
|
[Fact]
|
||||||
public async Task BuildDecisionAsync_YesNoFollowUp_MapsShortAffirmationToYesIntent()
|
public async Task BuildDecisionAsync_YesNoFollowUp_MapsShortAffirmationToYesIntent()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2863,7 +2863,7 @@ public sealed class JiboWebSocketServiceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ClientAsrOrderPizzaFlow_UsesLegacyOrderPizzaMim()
|
public async Task ClientAsrOrderAPizzaFlow_UsesLegacyOrderPizzaMim()
|
||||||
{
|
{
|
||||||
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
|
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
|
||||||
{
|
{
|
||||||
@@ -2880,7 +2880,7 @@ public sealed class JiboWebSocketServiceTests
|
|||||||
Path = "/listen",
|
Path = "/listen",
|
||||||
Kind = "neo-hub-listen",
|
Kind = "neo-hub-listen",
|
||||||
Token = "hub-client-asr-order-pizza-token",
|
Token = "hub-client-asr-order-pizza-token",
|
||||||
Text = """{"type":"CLIENT_ASR","transID":"trans-order-pizza-shape","data":{"text":"can you order pizza"}}"""
|
Text = """{"type":"CLIENT_ASR","transID":"trans-order-pizza-shape","data":{"text":"order a pizza"}}"""
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal(3, replies.Count);
|
Assert.Equal(3, replies.Count);
|
||||||
|
|||||||
Reference in New Issue
Block a user