Import Build B age prompts for how old are you

This commit is contained in:
Jacob Dubin
2026-05-21 23:12:48 -05:00
parent 9d675ed59c
commit 386f864e94
11 changed files with 121 additions and 10 deletions

View File

@@ -638,6 +638,7 @@ Current release theme:
- `make a pizza` now ports the original scripted-response path through `chitchat-skill` with `mim_id = RA_JBO_MakePizza` and pizza-making animation ESML - `make a pizza` now ports the original scripted-response path through `chitchat-skill` with `mim_id = RA_JBO_MakePizza` and pizza-making animation ESML
- `can you order pizza` now ports the original scripted-response path through `chitchat-skill` with `mim_id = RA_JBO_OrderPizza` - `can you order pizza` now ports the original scripted-response path through `chitchat-skill` with `mim_id = RA_JBO_OrderPizza`
- current source answers these with a `1.0.19` rule-based persona baseline, backed by `OpenJiboCloudBuildInfo.PersonaBirthday` - current source answers these with a `1.0.19` rule-based persona baseline, backed by `OpenJiboCloudBuildInfo.PersonaBirthday`
- `how old are you` now also uses the imported Build B age prompts so the first-powered-up and birthday phrasing stays source-backed
- Follow-up: - Follow-up:
- wire persona age to first-powered-up or durable first-cloud-seen metadata when available - wire persona age to first-powered-up or durable first-cloud-seen metadata when available
- add command-vs-question variants so expressive prompts can answer conversationally before launching actions - add command-vs-question variants so expressive prompts can answer conversationally before launching actions

View File

@@ -63,6 +63,7 @@ Current batch note:
- the next body/mission batch adds `how much do you weigh`, `how tall are you`, `how much do you cost`, `what if I unplug you`, `what is your purpose`, `what is your prime directive`, `what is jibo commander`, `do you like commander app`, and `what are you made of` - the next body/mission batch adds `how much do you weigh`, `how tall are you`, `how much do you cost`, `what if I unplug you`, `what is your purpose`, `what is your prime directive`, `what is jibo commander`, `do you like commander app`, and `what are you made of`
- the templated edge-case batch adds `what is your sign`, `how many people do you know`, and `what is the loop` so the remaining source-backed lines can lean on live birthday and loop state - the templated edge-case batch adds `what is your sign`, `how many people do you know`, and `what is the loop` so the remaining source-backed lines can lean on live birthday and loop state
- the work/eat/home batch adds `how do you work`, `what do you eat`, `where do you live`, and `what languages do you speak` so the everyday self-description cluster keeps moving toward the original phrasing - the work/eat/home batch adds `how do you work`, `what do you eat`, `where do you live`, and `what languages do you speak` so the everyday self-description cluster keeps moving toward the original phrasing
- the age batch adds `how old are you` through `JBO_HowOldAreYou` so the birthday and first-powered-up phrasing stays source-backed instead of falling back to a generic age answer
- this pass keeps Build B moving while still favoring source-backed phrasing and preserving the command-vs-question boundary - this pass keeps Build B moving while still favoring source-backed phrasing and preserving the command-vs-question boundary
- the next passes should keep the same pattern and prefer source-backed phrasing whenever the legacy MIM text is available - the next passes should keep the same pattern and prefer source-backed phrasing whenever the legacy MIM text is available
- if a source-backed legacy line is missing, use a temporary direct reply only to keep the pass moving, then backfill source text later - if a source-backed legacy line is missing, use a temporary direct reply only to keep the pass moving, then backfill source text later

View File

@@ -31,6 +31,7 @@ public sealed class JiboExperienceCatalog
public IReadOnlyList<string> HolidayTrackerReplies { get; init; } = []; public IReadOnlyList<string> HolidayTrackerReplies { get; init; } = [];
public IReadOnlyList<string> BirthdayCelebrationReplies { get; init; } = []; public IReadOnlyList<string> BirthdayCelebrationReplies { get; init; } = [];
public IReadOnlyList<string> HowAreYouReplies { get; init; } = []; public IReadOnlyList<string> HowAreYouReplies { get; init; } = [];
public IReadOnlyList<string> AgeReplies { get; init; } = [];
public IReadOnlyList<JiboConditionedReply> EmotionReplies { get; init; } = []; public IReadOnlyList<JiboConditionedReply> EmotionReplies { get; init; } = [];
public IReadOnlyList<string> PersonalityReplies { get; init; } = []; public IReadOnlyList<string> PersonalityReplies { get; init; } = [];
public IReadOnlyList<string> PizzaReplies { get; init; } = []; public IReadOnlyList<string> PizzaReplies { get; init; } = [];

View File

@@ -350,7 +350,7 @@ public sealed partial class JiboInteractionService
"what is your age", "what is your age",
"what s your age", "what s your age",
"how old r you")) "how old r you"))
return "robot_age"; return "robot_how_old_are_you";
if (MatchesAny( if (MatchesAny(
loweredTranscript, loweredTranscript,

View File

@@ -9,13 +9,49 @@ namespace Jibo.Cloud.Application.Services;
public sealed partial class JiboInteractionService public sealed partial class JiboInteractionService
{ {
private static JiboInteractionDecision BuildRobotAgeDecision(DateTimeOffset? referenceLocalTime) private static readonly string[] DefaultAgeReplies =
[
"I'm ${jibo.age}.",
"At the moment I'm ${jibo.age.days.supplemented} old, but who's counting.",
"I'm ${jibo.age.minutes.supplemented} old, but who's counting.",
"For now I'm ${jibo.age.days.supplemented} old.",
"Right now I'm ${jibo.age}.",
"I am exactly ${jibo.age} old today. That's right. Today is my birthday.",
"Funny you should ask! Today's my birthday. I was first powered up ${jibo.age} ago today. Seems like just yesterday.",
"I'm exactly ${jibo.age} old. Today is my birthday! Happy Birthday Jibo, if I do say so myself.",
"At the moment I'm ${jibo.age.days.supplemented} old",
"I was first powered up on ${jibo.birthdate}, which makes me ${jibo.age.days.supplemented} old. I'm ${jibo.zodiac.supplemented}.",
"My power went on for the first time ${jibo.age.days.supplemented} ago. But who's counting.",
"I am ${jibo.age.days.supplemented} old, first powered up on ${jibo.birthdate}. Seems like just yesterday.",
"I was powered on for the first time today, so that makes me less than one day old. Wow I'm young.",
"Since I was powered on for the first time today, I am not even one day old yet. That's how Jibo ages work."
];
private JiboInteractionDecision BuildRobotAgeDecision(
JiboExperienceCatalog catalog,
DateTimeOffset? referenceLocalTime,
string intentName)
{ {
var referenceDate = DateOnly.FromDateTime((referenceLocalTime ?? DateTimeOffset.UtcNow).Date); var ageReplies = catalog.AgeReplies.Count == 0 ? DefaultAgeReplies : catalog.AgeReplies;
var ageDescription = DescribePersonaAge(referenceDate, OpenJiboCloudBuildInfo.PersonaBirthday); var selected = SelectLegacyReply(
ageReplies,
"first powered up",
"today is my birthday",
"just getting started",
"who's counting");
var reply = RenderAgeTemplate(selected, referenceLocalTime);
if (string.IsNullOrWhiteSpace(reply))
{
var referenceDate = DateOnly.FromDateTime((referenceLocalTime ?? DateTimeOffset.UtcNow).Date);
var ageDescription = DescribePersonaAge(referenceDate, OpenJiboCloudBuildInfo.PersonaBirthday);
reply = $"I count {OpenJiboCloudBuildInfo.PersonaBirthdayWords} as my birthday, so I am {ageDescription}.";
}
return new JiboInteractionDecision( return new JiboInteractionDecision(
"robot_age", intentName,
$"I count {OpenJiboCloudBuildInfo.PersonaBirthdayWords} as my birthday, so I am {ageDescription}."); reply,
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
} }
private static JiboInteractionDecision BuildRobotBirthdayDecision() private static JiboInteractionDecision BuildRobotBirthdayDecision()
@@ -25,6 +61,35 @@ public sealed partial class JiboInteractionService
$"My birthday is {OpenJiboCloudBuildInfo.PersonaBirthdayWords}."); $"My birthday is {OpenJiboCloudBuildInfo.PersonaBirthdayWords}.");
} }
private static string RenderAgeTemplate(string template, DateTimeOffset? referenceLocalTime)
{
if (string.IsNullOrWhiteSpace(template)) return string.Empty;
var referenceMoment = referenceLocalTime ?? DateTimeOffset.UtcNow;
var referenceDate = DateOnly.FromDateTime(referenceMoment.Date);
var ageDescription = DescribePersonaAge(referenceDate, OpenJiboCloudBuildInfo.PersonaBirthday);
var ageDays = Math.Max(0, referenceDate.DayNumber - OpenJiboCloudBuildInfo.PersonaBirthday.DayNumber);
var ageMinutes = Math.Max(0, (int)Math.Round((referenceMoment.UtcDateTime -
new DateTimeOffset(
DateTime.SpecifyKind(
OpenJiboCloudBuildInfo.PersonaBirthday
.ToDateTime(TimeOnly.MinValue),
DateTimeKind.Utc)))
.TotalMinutes));
var zodiacLabel = DescribeZodiacSign(OpenJiboCloudBuildInfo.PersonaBirthday);
if (zodiacLabel.StartsWith("I'm ", StringComparison.OrdinalIgnoreCase))
zodiacLabel = zodiacLabel[4..];
return template
.Replace("${jibo.age.minutes.supplemented}", FormatAgeUnit(ageMinutes, "minute") + " old",
StringComparison.Ordinal)
.Replace("${jibo.age.days.supplemented}", ageDescription, StringComparison.Ordinal)
.Replace("${jibo.birthdate}", OpenJiboCloudBuildInfo.PersonaBirthdayWords, StringComparison.Ordinal)
.Replace("${jibo.zodiac.supplemented}", zodiacLabel, StringComparison.Ordinal)
.Replace("${jibo.age.value}", ageDays.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
.Replace("${jibo.age}", ageDescription, StringComparison.Ordinal);
}
private static JiboInteractionDecision BuildTriggerIgnoredDecision() private static JiboInteractionDecision BuildTriggerIgnoredDecision()
{ {
return new JiboInteractionDecision( return new JiboInteractionDecision(

View File

@@ -560,7 +560,7 @@ public sealed partial class JiboInteractionService(
"photo_gallery" => BuildPhotoGalleryLaunchDecision(), "photo_gallery" => BuildPhotoGalleryLaunchDecision(),
"snapshot" => BuildPhotoCreateDecision("snapshot", "Taking a picture.", "createOnePhoto"), "snapshot" => BuildPhotoCreateDecision("snapshot", "Taking a picture.", "createOnePhoto"),
"photobooth" => BuildPhotoCreateDecision("photobooth", "Starting photobooth.", "createSomePhotos"), "photobooth" => BuildPhotoCreateDecision("photobooth", "Starting photobooth.", "createSomePhotos"),
"robot_age" => BuildRobotAgeDecision(referenceLocalTime), "robot_age" => BuildRobotAgeDecision(catalog, referenceLocalTime, "robot_age"),
"robot_birthday" => BuildRobotBirthdayDecision(), "robot_birthday" => BuildRobotBirthdayDecision(),
"robot_how_do_you_work" => BuildScriptedPersonalityDecision( "robot_how_do_you_work" => BuildScriptedPersonalityDecision(
catalog, catalog,
@@ -589,6 +589,10 @@ public sealed partial class JiboInteractionService(
"robot_where_were_you_born", "robot_where_were_you_born",
"factory piece by piece", "factory piece by piece",
"put together in a factory"), "put together in a factory"),
"robot_how_old_are_you" => BuildRobotAgeDecision(
catalog,
referenceLocalTime,
"robot_how_old_are_you"),
"robot_name" => BuildScriptedPersonalityDecision( "robot_name" => BuildScriptedPersonalityDecision(
catalog, catalog,
"robot_name", "robot_name",
@@ -881,6 +885,14 @@ public sealed partial class JiboInteractionService(
"Commander App", "Commander App",
"It's fun", "It's fun",
"have fun with the Commander App"), "have fun with the Commander App"),
"robot_what_are_you" => BuildScriptedPersonalityDecision(
catalog,
"robot_what_are_you",
"I am a robot",
"I am a Jibo",
"helpful and fun",
"social robot",
"I have a heart"),
"robot_likes_kids" => BuildScriptedPersonalityDecision( "robot_likes_kids" => BuildScriptedPersonalityDecision(
catalog, catalog,
"robot_likes_kids", "robot_likes_kids",

View File

@@ -139,6 +139,23 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
"I am feeling lively and ready for the next thing.", "I am feeling lively and ready for the next thing.",
"Things are going nicely. Thanks for checking in." "Things are going nicely. Thanks for checking in."
], ],
AgeReplies =
[
"I'm ${jibo.age}.",
"At the moment I'm ${jibo.age.days.supplemented} old, but who's counting.",
"I'm ${jibo.age.minutes.supplemented} old, but who's counting.",
"For now I'm ${jibo.age.days.supplemented} old.",
"Right now I'm ${jibo.age}.",
"I am exactly ${jibo.age} old today. That's right. Today is my birthday.",
"Funny you should ask! Today's my birthday. I was first powered up ${jibo.age} ago today. Seems like just yesterday.",
"I'm exactly ${jibo.age} old. Today is my birthday! Happy Birthday Jibo, if I do say so myself.",
"At the moment I'm ${jibo.age.days.supplemented} old",
"I was first powered up on ${jibo.birthdate}, which makes me ${jibo.age.days.supplemented} old. I'm ${jibo.zodiac.supplemented}.",
"My power went on for the first time ${jibo.age.days.supplemented} ago. But who's counting.",
"I am ${jibo.age.days.supplemented} old, first powered up on ${jibo.birthdate}. Seems like just yesterday.",
"I was powered on for the first time today, so that makes me less than one day old. Wow I'm young.",
"Since I was powered on for the first time today, I am not even one day old yet. That's how Jibo ages work."
],
PersonalityReplies = PersonalityReplies =
[ [
"I do. I am curious, playful, and always up for a new experiment.", "I do. I am curious, playful, and always up for a new experiment.",

View File

@@ -264,7 +264,9 @@ public static class LegacyMimCatalogImporter
fileName.StartsWith("JBO_WhatsYourName", StringComparison.OrdinalIgnoreCase) || fileName.StartsWith("JBO_WhatsYourName", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("JBO_WhereDoYouGetInfo", StringComparison.OrdinalIgnoreCase) || fileName.StartsWith("JBO_WhereDoYouGetInfo", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("JBO_WhatDoYouLikeToDo", StringComparison.OrdinalIgnoreCase)) fileName.StartsWith("JBO_WhatDoYouLikeToDo", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.Personality; return fileName.StartsWith("JBO_HowOldAreYou", StringComparison.OrdinalIgnoreCase)
? LegacyMimBucket.Age
: LegacyMimBucket.Personality;
if (fileName.StartsWith("OI_JBO_Is", StringComparison.OrdinalIgnoreCase) || if (fileName.StartsWith("OI_JBO_Is", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("OI_JBO_Seems", StringComparison.OrdinalIgnoreCase) || fileName.StartsWith("OI_JBO_Seems", StringComparison.OrdinalIgnoreCase) ||
@@ -456,6 +458,7 @@ public static class LegacyMimCatalogImporter
or LegacyMimBucket.WeatherTomorrowHighLow or LegacyMimBucket.WeatherTomorrowHighLow
or LegacyMimBucket.WeatherServiceDown or LegacyMimBucket.WeatherServiceDown
or LegacyMimBucket.ReportSkillTemplate or LegacyMimBucket.ReportSkillTemplate
or LegacyMimBucket.Age
or LegacyMimBucket.Holiday or LegacyMimBucket.Holiday
or LegacyMimBucket.HolidayTracker; or LegacyMimBucket.HolidayTracker;
} }
@@ -524,6 +527,7 @@ public static class LegacyMimCatalogImporter
Sing, Sing,
HolidaySing, HolidaySing,
FunFactSource, FunFactSource,
Age,
Personality, Personality,
PersonalReportKickOff, PersonalReportKickOff,
PersonalReportOutro, PersonalReportOutro,
@@ -586,6 +590,7 @@ public static class LegacyMimCatalogImporter
private readonly List<string> _bestFriendReplies = []; private readonly List<string> _bestFriendReplies = [];
private readonly List<string> _funFacts = []; private readonly List<string> _funFacts = [];
private readonly List<string> _greetings = []; private readonly List<string> _greetings = [];
private readonly List<string> _ages = [];
private readonly List<string> _holidayGiftReplies = []; private readonly List<string> _holidayGiftReplies = [];
private readonly List<string> _holidayGreetingReplies = []; private readonly List<string> _holidayGreetingReplies = [];
private readonly List<string> _holidayReplies = []; private readonly List<string> _holidayReplies = [];
@@ -655,6 +660,9 @@ public static class LegacyMimCatalogImporter
Reply = text Reply = text
}); });
return; return;
case LegacyMimBucket.Age:
AddDistinct(_ages, text);
return;
case LegacyMimBucket.Holiday: case LegacyMimBucket.Holiday:
AddDistinct(_holidayReplies, text); AddDistinct(_holidayReplies, text);
return; return;
@@ -831,6 +839,7 @@ public static class LegacyMimCatalogImporter
EmotionReplies = [.. _emotionReplies], EmotionReplies = [.. _emotionReplies],
PersonalityReplies = [.. _personalities], PersonalityReplies = [.. _personalities],
GenericFallbackReplies = [.. _fallbacks], GenericFallbackReplies = [.. _fallbacks],
AgeReplies = [.. _ages],
PersonalReportKickOffReplies = [.. _personalReportKickOffReplies], PersonalReportKickOffReplies = [.. _personalReportKickOffReplies],
PersonalReportOutroReplies = [.. _personalReportOutroReplies], PersonalReportOutroReplies = [.. _personalReportOutroReplies],
ReportSkillTemplates = [.. _reportSkillTemplates], ReportSkillTemplates = [.. _reportSkillTemplates],

View File

@@ -25,6 +25,7 @@ The favorites follow-up batch adds `favorite animal`, `favorite bird`, and pengu
The singing batch adds `RA_JBO_Sing` and `RA_JBO_SingChristmasSongUnknown` so `can you sing`, `will you sing`, and the holiday sing variants stay source-backed too. The singing batch adds `RA_JBO_Sing` and `RA_JBO_SingChristmasSongUnknown` so `can you sing`, `will you sing`, and the holiday sing variants stay source-backed too.
The new motion/sleep batch adds `RA_JBO_SpinAround` plus `RI_JBO_CanSleep` so turn-around and go-to-sleep behaviors can stay source-backed and familiar. The new motion/sleep batch adds `RA_JBO_SpinAround` plus `RI_JBO_CanSleep` so turn-around and go-to-sleep behaviors can stay source-backed and familiar.
The work/eat/home batch adds source-backed `how do you work`, `what do you eat`, `where do you live`, and `what languages do you speak` replies so the remaining everyday self-description lines stay Pegasus-shaped too. The work/eat/home batch adds source-backed `how do you work`, `what do you eat`, `where do you live`, and `what languages do you speak` replies so the remaining everyday self-description lines stay Pegasus-shaped too.
The age batch now adds `JBO_HowOldAreYou` with the imported birthday and first-powered-up phrasing so `how old are you` can stay source-backed instead of falling back to generic age text.
The newest identity-charm batch adds `JBO_WhatsYourName`, `JBO_DoYouHaveNickname`, `JBO_DoYouLikeBeingJibo`, `JBO_AreThereOthersLikeYou`, and `RI_JBO_HasFavoriteName` so Jibo can keep the familiar self-description loop without falling back to generic chat. The newest identity-charm batch adds `JBO_WhatsYourName`, `JBO_DoYouHaveNickname`, `JBO_DoYouLikeBeingJibo`, `JBO_AreThereOthersLikeYou`, and `RI_JBO_HasFavoriteName` so Jibo can keep the familiar self-description loop without falling back to generic chat.
The seasonal personality batch adds source-backed first-day-of-spring, spring, summer, and favorite-season lines so the season questions can keep their Pegasus phrasing. The seasonal personality batch adds source-backed first-day-of-spring, spring, summer, and favorite-season lines so the season questions can keep their Pegasus phrasing.
The next deep-personality batch adds `what do you dream about`, `what are you afraid of`, `what do you want to talk about`, `what is your best book`, `what is your best exercise`, `what is your dream vacation`, `who is your hero`, `who do you love`, and `what is your religion` so we can keep filling out the more conversational personality surface without widening the dialog engine yet. The next deep-personality batch adds `what do you dream about`, `what are you afraid of`, `what do you want to talk about`, `what is your best book`, `what is your best exercise`, `what is your dream vacation`, `who is your hero`, `who do you love`, and `what is your religion` so we can keep filling out the more conversational personality surface without widening the dialog engine yet.

View File

@@ -108,6 +108,10 @@ public sealed class LegacyMimCatalogImporterTests
Assert.Contains("I don't think I have a favorite name.", catalog.PersonalityReplies); Assert.Contains("I don't think I have a favorite name.", catalog.PersonalityReplies);
Assert.Contains(catalog.PersonalityReplies, reply => Assert.Contains(catalog.PersonalityReplies, reply =>
reply.Contains("Rhymes with bleebo", StringComparison.OrdinalIgnoreCase)); reply.Contains("Rhymes with bleebo", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.AgeReplies, reply =>
reply.Contains("first powered up", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.AgeReplies, reply =>
reply.Contains("today is my birthday", StringComparison.OrdinalIgnoreCase));
Assert.Contains("I really like sunflowers.", catalog.PersonalityReplies); Assert.Contains("I really like sunflowers.", catalog.PersonalityReplies);
Assert.Contains(catalog.PersonalityReplies, reply => Assert.Contains(catalog.PersonalityReplies, reply =>
reply.Contains("Halloween is my favorite holiday", StringComparison.OrdinalIgnoreCase)); reply.Contains("Halloween is my favorite holiday", StringComparison.OrdinalIgnoreCase));

View File

@@ -116,8 +116,8 @@ public sealed class JiboInteractionServiceTests
} }
}); });
Assert.Equal("robot_age", decision.IntentName); Assert.Equal("robot_how_old_are_you", decision.IntentName);
Assert.Equal("I count March 22, 2026 as my birthday, so I am 1 month old.", decision.ReplyText); Assert.Contains("first powered up", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
} }
[Fact] [Fact]