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
|
||||
|
||||
- Status: `ready`
|
||||
- Status: `implemented`
|
||||
- Tags: `content`, `polish`
|
||||
- User goals:
|
||||
- `dance` should behave like a willing action
|
||||
- `do you like to dance` should answer the question before or instead of treating it like the same command
|
||||
- Implementation notes:
|
||||
- evolve reply collections into command/question variants
|
||||
- start with dance or another expressive skill
|
||||
- keep the first version rule-based
|
||||
- Result:
|
||||
- `dance` still launches the dance animation path
|
||||
- `do you like to dance` now responds conversationally as a personality question instead of launching the action
|
||||
- birthday phrasing now takes precedence over an `askForDate` client-intent misclassification
|
||||
- Follow-up:
|
||||
- expand command-vs-question splits to more expressive intents (pizza, surprise, photo prompts)
|
||||
- add Pegasus phrase and MIM-backed variants for richer style coverage
|
||||
|
||||
## Suggested Order
|
||||
|
||||
@@ -614,16 +614,17 @@ Use [regression-test-plan.md](regression-test-plan.md) as the detailed checklist
|
||||
|
||||
For `1.0.19`:
|
||||
|
||||
1. Harden stop or volume if the `1.0.18` live pass exposes stock-OS quirks / harden $YESNO interaction
|
||||
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.)
|
||||
3. Holidays and seasonal personality slice so persona evolution remains visible and testable
|
||||
4. Multi-tenant internal storage foundation for memory/personality data (account/loop/device scoped) with cloud-ready persistence boundaries
|
||||
5. Update, backup, and restore proof
|
||||
6. STT upgrade and noise screening
|
||||
7. Hosted capture/storage plan / indexing for group testing
|
||||
8. Binary-safe media storage / sync to cloud drive: OneDrive, Google Drive, Box, etc.
|
||||
9. Provider-backed news and weather
|
||||
10. Proactivity, dialog parsing/NLP, memory/history, Lasso, identity, and onboarding as larger discovery-driven tracks
|
||||
1. Command-vs-question personality split (`dance` command vs `do you like to dance` question style; expand this pattern)
|
||||
2. First memory-backed personal facts with tenant-scoped storage (birthday/preferences foundation)
|
||||
3. Proactivity selector baseline with source-backed first offers
|
||||
4. Dialog parsing expansion and ambiguity guardrails
|
||||
5. Holidays and seasonal personality behavior built on the new memory/proactivity foundation
|
||||
6. Update, backup, and restore proof
|
||||
7. STT upgrade and noise screening
|
||||
8. Hosted capture/storage plan / indexing for group testing
|
||||
9. Binary-safe media storage / sync to cloud drive: OneDrive, Google Drive, Box, etc.
|
||||
10. Provider-backed news and weather
|
||||
11. Lasso, identity, and onboarding as larger discovery-driven tracks
|
||||
|
||||
For `1.0.20` and beyond:
|
||||
|
||||
|
||||
@@ -59,12 +59,17 @@ This slice is intentionally small and user-visible. It creates immediate persona
|
||||
|
||||
## Next Slices
|
||||
|
||||
1. Update/backup/restore end-to-end proof (operator-run and documented)
|
||||
2. Holidays and seasonal personality slice (first scoped calendar + response set)
|
||||
3. Multi-tenant memory storage foundation (tenant model + persistence contracts + initial implementation)
|
||||
4. STT noise-screening and short-utterance reliability pass
|
||||
5. Provider-backed news/weather expansion using Pegasus-backed contracts
|
||||
6. Capture indexing and retention boundary for group testing
|
||||
1. Command-vs-question personality split (start with dance/twerk-style prompts, keep commands action-oriented and questions conversational)
|
||||
2. First memory-backed personal facts (tenant-scoped birthday/preferences storage contracts + initial implementation)
|
||||
3. Proactivity selector baseline (source-backed first proactive offers with safe throttling and stock-compatible payloads)
|
||||
4. Dialog parsing expansion (more phrase variants, ambiguity handling, and transcript-to-intent guardrails)
|
||||
5. Holidays and seasonal personality slice (time-scoped content backed by the new memory/proactivity path)
|
||||
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
|
||||
|
||||
|
||||
@@ -22,4 +22,5 @@ public sealed class JiboExperienceCatalog
|
||||
public IReadOnlyList<string> NewsBriefings { get; init; } = [];
|
||||
public IReadOnlyList<string> GenericFallbackReplies { 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
|
||||
{
|
||||
"joke" => BuildJokeDecision(catalog),
|
||||
"dance_question" => BuildDanceQuestionDecision(catalog),
|
||||
"dance" => BuildRandomDanceDecision(catalog),
|
||||
"twerk" => BuildDanceDecision("twerk", "rom-twerk", "Watch me twerk."),
|
||||
"time" => BuildClockLaunchDecision("time", "clock", "askForTime", "Showing the time."),
|
||||
@@ -167,6 +168,11 @@ public sealed class JiboInteractionService(
|
||||
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)
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
@@ -260,6 +266,11 @@ public sealed class JiboInteractionService(
|
||||
};
|
||||
}
|
||||
|
||||
if (IsRobotBirthdayQuestion(loweredTranscript))
|
||||
{
|
||||
return "robot_birthday";
|
||||
}
|
||||
|
||||
if (string.Equals(clientIntent, "askForTime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "time";
|
||||
@@ -500,6 +511,11 @@ public sealed class JiboInteractionService(
|
||||
return "photo_gallery";
|
||||
}
|
||||
|
||||
if (IsDanceQuestion(loweredTranscript))
|
||||
{
|
||||
return "dance_question";
|
||||
}
|
||||
|
||||
if (MatchesAny(loweredTranscript, "twerk"))
|
||||
{
|
||||
return "twerk";
|
||||
@@ -525,17 +541,6 @@ public sealed class JiboInteractionService(
|
||||
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(
|
||||
loweredTranscript,
|
||||
"do you have a personality",
|
||||
@@ -564,7 +569,12 @@ public sealed class JiboInteractionService(
|
||||
if (MatchesAny(
|
||||
loweredTranscript,
|
||||
"can you order pizza",
|
||||
"can you order a pizza",
|
||||
"could you order a pizza",
|
||||
"order pizza",
|
||||
"order a pizza",
|
||||
"order us a pizza",
|
||||
"order me a pizza",
|
||||
"please order pizza"))
|
||||
{
|
||||
return "order_pizza";
|
||||
@@ -1078,6 +1088,31 @@ public sealed class JiboInteractionService(
|
||||
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)
|
||||
{
|
||||
foreach (var (phrase, station) in RadioGenreAliases)
|
||||
|
||||
@@ -29,6 +29,12 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
|
||||
"Watch me dance.",
|
||||
"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 =
|
||||
[
|
||||
"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"]);
|
||||
}
|
||||
|
||||
[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]
|
||||
public async Task BuildDecisionAsync_TwerkQuestion_PrefersSpecificTwerkIntent()
|
||||
{
|
||||
@@ -90,6 +106,21 @@ public sealed class JiboInteractionServiceTests
|
||||
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]
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
public async Task BuildDecisionAsync_ClientNluRequestOrderPizza_UsesLegacyOrderPizzaMimPayload()
|
||||
{
|
||||
@@ -200,6 +247,25 @@ public sealed class JiboInteractionServiceTests
|
||||
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]
|
||||
public async Task BuildDecisionAsync_YesNoFollowUp_MapsShortAffirmationToYesIntent()
|
||||
{
|
||||
|
||||
@@ -2863,7 +2863,7 @@ public sealed class JiboWebSocketServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClientAsrOrderPizzaFlow_UsesLegacyOrderPizzaMim()
|
||||
public async Task ClientAsrOrderAPizzaFlow_UsesLegacyOrderPizzaMim()
|
||||
{
|
||||
await _service.HandleMessageAsync(new WebSocketMessageEnvelope
|
||||
{
|
||||
@@ -2880,7 +2880,7 @@ public sealed class JiboWebSocketServiceTests
|
||||
Path = "/listen",
|
||||
Kind = "neo-hub-listen",
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user