Add friendship persona responses

This commit is contained in:
Jacob Dubin
2026-05-21 11:29:44 -05:00
parent e85792ac57
commit 40deecf2ff
10 changed files with 164 additions and 0 deletions

View File

@@ -43,6 +43,7 @@ Current batch note:
- `favorite color`, `favorite food`, and `favorite music` are the first small favorites-family slice - `favorite color`, `favorite food`, and `favorite music` are the first small favorites-family slice
- the latest pass adds longer authored variants for those favorites so the replies keep more of the original Pegasus cadence instead of collapsing to short placeholders - the latest pass adds longer authored variants for those favorites so the replies keep more of the original Pegasus cadence instead of collapsing to short placeholders
- singing and musical personality now has a source-backed first slice with `can you sing`, `will you sing`, and holiday sing variants so the charm surface can keep growing without inventing a new dialog engine - singing and musical personality now has a source-backed first slice with `can you sing`, `will you sing`, and holiday sing variants so the charm surface can keep growing without inventing a new dialog engine
- the friendship batch now includes `do you have friends`, `are we friends`, and `are we best friends` responses, plus the loop-friendly friend replies, so the relationship lane can stay source-backed too
- the next source-backed batch now includes `favorite flower`, `R2D2`, `sun`, `space`, `kids`, plus a couple of charm prompts like `can you laugh` and `can you dance` - the next source-backed batch now includes `favorite flower`, `R2D2`, `sun`, `space`, `kids`, plus a couple of charm prompts like `can you laugh` and `can you dance`
- the motion/sleep batch now adds `RI_JBO_CanSleep` and `RA_JBO_SpinAround` so the `go to sleep` and `turn around` surfaces stay source-backed too - the motion/sleep batch now adds `RI_JBO_CanSleep` and `RA_JBO_SpinAround` so the `go to sleep` and `turn around` surfaces stay source-backed too
- the follow-up mood batch now includes `how are things`, `how is your day`, `are you sad`, and `are you angry` - the follow-up mood batch now includes `how are things`, `how is your day`, `are you sad`, and `are you angry`

View File

@@ -18,6 +18,8 @@ public sealed class JiboExperienceCatalog
public IReadOnlyList<string> HumanFacts { get; init; } = []; public IReadOnlyList<string> HumanFacts { get; init; } = [];
public IReadOnlyList<string> FunFacts { get; init; } = []; public IReadOnlyList<string> FunFacts { get; init; } = [];
public IReadOnlyList<string> FavoriteAnimalReplies { get; init; } = []; public IReadOnlyList<string> FavoriteAnimalReplies { get; init; } = [];
public IReadOnlyList<string> FriendReplies { get; init; } = [];
public IReadOnlyList<string> BestFriendReplies { get; init; } = [];
public IReadOnlyList<string> SingReplies { get; init; } = []; public IReadOnlyList<string> SingReplies { get; init; } = [];
public IReadOnlyList<string> HolidaySingReplies { get; init; } = []; public IReadOnlyList<string> HolidaySingReplies { get; init; } = [];
public IReadOnlyList<string> DanceAnimations { get; init; } = []; public IReadOnlyList<string> DanceAnimations { get; init; } = [];

View File

@@ -319,6 +319,15 @@ public sealed partial class JiboInteractionService
"would you sing")) "would you sing"))
return "robot_can_sing"; return "robot_can_sing";
if (IsBestFriendQuestion(loweredTranscript))
return "robot_best_friends";
if (IsFriendRelationQuestion(loweredTranscript))
return "robot_is_friends_with_user";
if (IsFriendQuestion(loweredTranscript))
return "robot_has_friends";
if (MatchesAny(loweredTranscript, "twerk")) return "twerk"; if (MatchesAny(loweredTranscript, "twerk")) return "twerk";
if (MatchesAny(loweredTranscript, "dance", "boogie")) return "dance"; if (MatchesAny(loweredTranscript, "dance", "boogie")) return "dance";
@@ -840,4 +849,48 @@ public sealed partial class JiboInteractionService
return "chat"; return "chat";
} }
private static bool IsFriendQuestion(string loweredTranscript)
{
return MatchesAny(
loweredTranscript,
"do you have friends",
"who are your friends",
"are you friends",
"are you and i friends",
"are you and me friends",
"are you and jibo friends");
}
private static bool IsFriendRelationQuestion(string loweredTranscript)
{
return MatchesAny(
loweredTranscript,
"are you my friend",
"are you friends with me",
"are we friends",
"are we friends with each other",
"is jibo your friend",
"i am friends with you",
"i'm friends with you",
"you are my friend",
"you re my friend",
"you're my friend");
}
private static bool IsBestFriendQuestion(string loweredTranscript)
{
return MatchesAny(
loweredTranscript,
"are we best friends",
"are we best friends with each other",
"are you my best friend",
"are you best friends with me",
"are you and i best friends",
"i am best friends with you",
"i'm best friends with you",
"you are my best friend",
"you re my best friend",
"you're my best friend");
}
} }

View File

@@ -322,6 +322,28 @@ public sealed partial class JiboInteractionService
preferredSnippets); preferredSnippets);
} }
private JiboInteractionDecision BuildScriptedFriendDecision(
JiboExperienceCatalog catalog,
string intentName,
params string[] preferredSnippets)
{
return new JiboInteractionDecision(
intentName,
SelectLegacyReply(catalog.FriendReplies, preferredSnippets),
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
}
private JiboInteractionDecision BuildScriptedBestFriendDecision(
JiboExperienceCatalog catalog,
string intentName,
params string[] preferredSnippets)
{
return new JiboInteractionDecision(
intentName,
SelectLegacyReply(catalog.BestFriendReplies, preferredSnippets),
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
}
private JiboInteractionDecision BuildScriptedSingDecision( private JiboInteractionDecision BuildScriptedSingDecision(
JiboExperienceCatalog catalog, JiboExperienceCatalog catalog,
string intentName, string intentName,

View File

@@ -752,6 +752,22 @@ public sealed partial class JiboInteractionService(
"dancing is one of the things i know best", "dancing is one of the things i know best",
"if there's one thing i know how to do. it's dance", "if there's one thing i know how to do. it's dance",
"i can dance"), "i can dance"),
"robot_has_friends" => BuildScriptedFriendDecision(
catalog,
"robot_has_friends",
"I believe I do have friends",
"I sure do have friends",
"I'm always up for making new friends"),
"robot_is_friends_with_user" => BuildScriptedFriendDecision(
catalog,
"robot_is_friends_with_user",
"don't know what i'd do without you",
"one of my favorites",
"making new friends"),
"robot_best_friends" => BuildScriptedBestFriendDecision(
catalog,
"robot_best_friends",
"best friends with anyone in my Loop"),
"robot_can_sing" => BuildScriptedSingDecision( "robot_can_sing" => BuildScriptedSingDecision(
catalog, catalog,
"robot_can_sing", "robot_can_sing",

View File

@@ -63,6 +63,21 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
"I like lots of animals, but the penguin is the best of the best! Great color scheme.", "I like lots of animals, but the penguin is the best of the best! Great color scheme.",
"I love penguins, because we're so alike. We have the same coloring, and neither of us can fly." "I love penguins, because we're so alike. We have the same coloring, and neither of us can fly."
], ],
FriendReplies =
[
"I believe I do have friends. But I'm always up for more.",
"I sure do have friends. In a robot kind of way.",
"I don't know if we've met yet, but I'm always up for making new friends.",
"I don't know what I'd do without you.",
"You're one of my favorites.",
"I sure am.",
"I am indeed."
],
BestFriendReplies =
[
"I'd have to say I'm best friends with anyone in my Loop.",
"I think you know the answer to that question. You are."
],
SingReplies = SingReplies =
[ [
"Singing is not my strong suit.", "Singing is not my strong suit.",

View File

@@ -135,6 +135,16 @@ public static class LegacyMimCatalogImporter
fileName.StartsWith("RI_JBO_LikesAnimals", StringComparison.OrdinalIgnoreCase)) fileName.StartsWith("RI_JBO_LikesAnimals", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.FavoriteAnimal; return LegacyMimBucket.FavoriteAnimal;
if (fileName.StartsWith("RI_JBO_HasFriends", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RI_JBO_IsFriendsWithUser", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RI_JBO_IsFriendsWithLM", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RI_JBO_IsFriendsWithNonLM", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RI_JBO_IsFriendsWithToaster", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.Friend;
if (fileName.StartsWith("RI_JBO_IsBestFriendsWithUser", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.BestFriend;
if (fileName.StartsWith("RN_HappyHolidays", StringComparison.OrdinalIgnoreCase)) if (fileName.StartsWith("RN_HappyHolidays", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.HolidayGreeting; return LegacyMimBucket.HolidayGreeting;
@@ -305,6 +315,8 @@ public static class LegacyMimCatalogImporter
HumanFacts = Merge(baseCatalog.HumanFacts, importedCatalog.HumanFacts), HumanFacts = Merge(baseCatalog.HumanFacts, importedCatalog.HumanFacts),
FunFacts = Merge(baseCatalog.FunFacts, importedCatalog.FunFacts), FunFacts = Merge(baseCatalog.FunFacts, importedCatalog.FunFacts),
FavoriteAnimalReplies = Merge(baseCatalog.FavoriteAnimalReplies, importedCatalog.FavoriteAnimalReplies), FavoriteAnimalReplies = Merge(baseCatalog.FavoriteAnimalReplies, importedCatalog.FavoriteAnimalReplies),
FriendReplies = Merge(baseCatalog.FriendReplies, importedCatalog.FriendReplies),
BestFriendReplies = Merge(baseCatalog.BestFriendReplies, importedCatalog.BestFriendReplies),
SingReplies = Merge(baseCatalog.SingReplies, importedCatalog.SingReplies), SingReplies = Merge(baseCatalog.SingReplies, importedCatalog.SingReplies),
HolidaySingReplies = Merge(baseCatalog.HolidaySingReplies, importedCatalog.HolidaySingReplies), HolidaySingReplies = Merge(baseCatalog.HolidaySingReplies, importedCatalog.HolidaySingReplies),
DanceAnimations = Merge(baseCatalog.DanceAnimations, importedCatalog.DanceAnimations), DanceAnimations = Merge(baseCatalog.DanceAnimations, importedCatalog.DanceAnimations),
@@ -507,6 +519,8 @@ public static class LegacyMimCatalogImporter
Emotion, Emotion,
FunFacts, FunFacts,
FavoriteAnimal, FavoriteAnimal,
Friend,
BestFriend,
Sing, Sing,
HolidaySing, HolidaySing,
FunFactSource, FunFactSource,
@@ -568,6 +582,8 @@ public static class LegacyMimCatalogImporter
private readonly List<JiboConditionedReply> _emotionReplies = []; private readonly List<JiboConditionedReply> _emotionReplies = [];
private readonly List<string> _fallbacks = []; private readonly List<string> _fallbacks = [];
private readonly List<string> _favoriteAnimalReplies = []; private readonly List<string> _favoriteAnimalReplies = [];
private readonly List<string> _friendReplies = [];
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> _holidayGiftReplies = []; private readonly List<string> _holidayGiftReplies = [];
@@ -690,6 +706,12 @@ public static class LegacyMimCatalogImporter
case LegacyMimBucket.FavoriteAnimal: case LegacyMimBucket.FavoriteAnimal:
AddDistinct(_favoriteAnimalReplies, text); AddDistinct(_favoriteAnimalReplies, text);
return; return;
case LegacyMimBucket.Friend:
AddDistinct(_friendReplies, text);
return;
case LegacyMimBucket.BestFriend:
AddDistinct(_bestFriendReplies, text);
return;
case LegacyMimBucket.PersonalReportKickOff: case LegacyMimBucket.PersonalReportKickOff:
AddDistinct(_personalReportKickOffReplies, text); AddDistinct(_personalReportKickOffReplies, text);
return; return;
@@ -794,6 +816,8 @@ public static class LegacyMimCatalogImporter
HumanFacts = [.. _humanFacts], HumanFacts = [.. _humanFacts],
FunFacts = [.. _funFacts], FunFacts = [.. _funFacts],
FavoriteAnimalReplies = [.. _favoriteAnimalReplies], FavoriteAnimalReplies = [.. _favoriteAnimalReplies],
FriendReplies = [.. _friendReplies],
BestFriendReplies = [.. _bestFriendReplies],
SingReplies = [.. _singReplies], SingReplies = [.. _singReplies],
HolidaySingReplies = [.. _holidaySingReplies], HolidaySingReplies = [.. _holidaySingReplies],
GreetingReplies = [.. _greetings], GreetingReplies = [.. _greetings],

View File

@@ -17,6 +17,7 @@ Holiday-specific note:
- `RN_HappyBirthdayToJibo` now lands in the birthday celebration bucket - `RN_HappyBirthdayToJibo` now lands in the birthday celebration bucket
- birthday memory authoring now also writes loop-scoped custom holiday records so personal dates can join the holiday list later - birthday memory authoring now also writes loop-scoped custom holiday records so personal dates can join the holiday list later
The newest social batch adds `welcome back`, `what are you thinking`, `what have you been doing`, and `what did you do` responses so the presence and charm lane keeps growing alongside seasonal content. The newest social batch adds `welcome back`, `what are you thinking`, `what have you been doing`, and `what did you do` responses so the presence and charm lane keeps growing alongside seasonal content.
The friendship batch adds `RI_JBO_HasFriends`, `RI_JBO_IsFriendsWithUser`, `RI_JBO_IsFriendsWithLM`, `RI_JBO_IsFriendsWithNonLM`, `RI_JBO_IsFriendsWithToaster`, and `RI_JBO_IsBestFriendsWithUser` so the friend and best-friend questions stay source-backed too.
The fun-fact and joke batch adds Pegasus-style `TellAJoke`, `TellRobotFact`, and `Shuffle` excerpts so proactive fun can randomize across more than one category. The fun-fact and joke batch adds Pegasus-style `TellAJoke`, `TellRobotFact`, and `Shuffle` excerpts so proactive fun can randomize across more than one category.
Those facts are now split into generic, robot, and human buckets so the randomizer can sound more like Pegasus while staying lightweight. Those facts are now split into generic, robot, and human buckets so the randomizer can sound more like Pegasus while staying lightweight.
The new favorites batch adds longer authored `favorite color`, `favorite food`, and `favorite music` variants so the familiar personality responses keep more of the original cadence instead of collapsing to short placeholders. The new favorites batch adds longer authored `favorite color`, `favorite food`, and `favorite music` variants so the familiar personality responses keep more of the original cadence instead of collapsing to short placeholders.

View File

@@ -186,6 +186,33 @@ public sealed class LegacyMimCatalogImporterTests
reply.Contains("north Pole", StringComparison.OrdinalIgnoreCase)); reply.Contains("north Pole", StringComparison.OrdinalIgnoreCase));
} }
[Fact]
public void ImportCatalog_ImportsBuildBFriendshipResponsesIntoFriendBuckets()
{
var rootDirectory = Path.Combine(
AppContext.BaseDirectory,
"Content",
"LegacyMims",
"BuildB");
var catalog = LegacyMimCatalogImporter.ImportCatalog(rootDirectory);
Assert.Contains(catalog.FriendReplies, reply =>
reply.Contains("always up for more", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.FriendReplies, reply =>
reply.Contains("robot kind of way", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.FriendReplies, reply =>
reply.Contains("making new friends", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.FriendReplies, reply =>
reply.Contains("don't know if we've met yet", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.FriendReplies, reply =>
reply.Contains("don't know what I'd do without you", StringComparison.OrdinalIgnoreCase));
Assert.Contains("I'd have to say I'm best friends with anyone in my Loop.",
catalog.BestFriendReplies);
Assert.Contains(catalog.BestFriendReplies, reply =>
reply.Contains("You are", StringComparison.OrdinalIgnoreCase));
}
[Fact] [Fact]
public void ImportCatalog_ImportsBuildBEmotionResponsesIntoEmotionBucket() public void ImportCatalog_ImportsBuildBEmotionResponsesIntoEmotionBucket()
{ {

View File

@@ -475,6 +475,9 @@ public sealed class JiboInteractionServiceTests
[InlineData("do you like kids", "robot_likes_kids", "kids are so fun")] [InlineData("do you like kids", "robot_likes_kids", "kids are so fun")]
[InlineData("can you laugh", "robot_can_laugh", "when I'm happy")] [InlineData("can you laugh", "robot_can_laugh", "when I'm happy")]
[InlineData("can you dance", "robot_can_dance", "dancing is one of the things I know best")] [InlineData("can you dance", "robot_can_dance", "dancing is one of the things I know best")]
[InlineData("do you have friends", "robot_has_friends", "I believe I do have friends")]
[InlineData("are we friends", "robot_is_friends_with_user", "don't know what I'd do without you")]
[InlineData("are we best friends", "robot_best_friends", "best friends with anyone in my Loop")]
[InlineData("can you sing", "robot_can_sing", "sing")] [InlineData("can you sing", "robot_can_sing", "sing")]
[InlineData("will you sing", "robot_can_sing", "sing")] [InlineData("will you sing", "robot_can_sing", "sing")]
[InlineData("can you sing a christmas song", "robot_sing_christmas_song", "sing")] [InlineData("can you sing a christmas song", "robot_sing_christmas_song", "sing")]