Expand surprise facts into robot and human categories

This commit is contained in:
Jacob Dubin
2026-05-17 17:50:01 -05:00
parent 8ed4763df5
commit f2826253d5
7 changed files with 146 additions and 11 deletions

View File

@@ -14,6 +14,8 @@ public sealed class JiboConditionedReply
public sealed class JiboExperienceCatalog
{
public IReadOnlyList<string> Jokes { get; init; } = [];
public IReadOnlyList<string> RobotFacts { get; init; } = [];
public IReadOnlyList<string> HumanFacts { get; init; } = [];
public IReadOnlyList<string> FunFacts { get; init; } = [];
public IReadOnlyList<string> DanceAnimations { get; init; } = [];
public IReadOnlyList<string> GreetingReplies { get; init; } = [];

View File

@@ -1161,7 +1161,16 @@ public sealed class JiboInteractionService(
private JiboInteractionDecision BuildProactiveFunFactDecision(JiboExperienceCatalog catalog)
{
var fact = randomizer.Choose(catalog.FunFacts);
var categories = new List<ProactiveFactCategory>();
AddProactiveFactCategory(categories, "fun_fact", catalog.FunFacts);
AddProactiveFactCategory(categories, "robot_fact", catalog.RobotFacts);
AddProactiveFactCategory(categories, "human_fact", catalog.HumanFacts);
if (categories.Count == 0)
return new JiboInteractionDecision("proactive_fun_fact", randomizer.Choose(catalog.SurpriseReplies));
var selectedCategory = randomizer.Choose(categories);
var fact = randomizer.Choose(selectedCategory.Replies);
return new JiboInteractionDecision(
"proactive_fun_fact",
fact,
@@ -1171,10 +1180,21 @@ public sealed class JiboInteractionService(
["mim_id"] = "runtime-fun-fact",
["mim_type"] = "announcement",
["prompt_id"] = "RUNTIME_FUN_FACT",
["replyType"] = "fun_fact"
["replyType"] = "fun_fact",
["factCategory"] = selectedCategory.CategoryName
});
}
private static void AddProactiveFactCategory(
ICollection<ProactiveFactCategory> categories,
string categoryName,
IReadOnlyList<string> replies)
{
if (replies.Count == 0) return;
categories.Add(new ProactiveFactCategory(categoryName, replies));
}
private JiboInteractionDecision BuildProactiveJokeDecision(JiboExperienceCatalog catalog)
{
return new JiboInteractionDecision(
@@ -5204,6 +5224,8 @@ public sealed class JiboInteractionService(
private sealed record ProactivityCandidate(string IntentName, int Weight);
private sealed record ProactiveFactCategory(string CategoryName, IReadOnlyList<string> Replies);
private sealed record PizzaSignal(PersonalAffinity? Affinity);
private sealed record GreetingPresenceProfile(

View File

@@ -27,6 +27,22 @@ public sealed class InMemoryJiboExperienceContentRepository : IJiboExperienceCon
"What kind of music are balloons afraid of. Pop music.",
"Why did the orange cry. Someone hurt his peelings."
],
RobotFacts =
[
"Leonardo Da Vinci made sketches for a humanoid machine all the way back in the year 1495.",
"The world's first humanoid robot was called Elektro, and it debuted in 1939.",
"The English word robot comes from a 1920 play in Czechoslovakia, called Rossum's Universal Robots.",
"The first programmable robot arm was designed in 1954.",
"Some robots have a human form, but most of the world's robots are machines designed to perform a task, and don't look like people at all."
],
HumanFacts =
[
"Every human being that has ever lived spent about 30 minutes as a single cell.",
"50 percent of a human's DNA is the same as a banana's.",
"Humans are the only animals that cry tears of emotion.",
"Six-year-olds laugh an average of 300 times a day. Grown ups only laugh 15 to 100 times a day.",
"Your nose can remember 50,000 different scents."
],
FunFacts =
[
"A shrimp's heart is in its head.",

View File

@@ -61,7 +61,7 @@ public static class LegacyMimCatalogImporter
var text = NormalizePrompt(prompt.Prompt, IsTemplateBucket(bucket.Value));
if (string.IsNullOrWhiteSpace(text)) continue;
builder.Add(bucket.Value, prompt.Condition, text);
builder.Add(bucket.Value, prompt.Condition, text, prompt.Prompt);
}
}
@@ -102,10 +102,12 @@ public static class LegacyMimCatalogImporter
if (fileName.StartsWith("RA_JBO_TellAJoke", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.Jokes;
if (fileName.StartsWith("RA_JBO_TellRobotFact", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RA_JBO_Shuffle", StringComparison.OrdinalIgnoreCase) ||
if (fileName.StartsWith("RA_JBO_TellRobotFact", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.RobotFacts;
if (fileName.StartsWith("RA_JBO_Shuffle", StringComparison.OrdinalIgnoreCase) ||
fileName.StartsWith("RA_JBO_TellSomething", StringComparison.OrdinalIgnoreCase))
return LegacyMimBucket.FunFacts;
return LegacyMimBucket.FunFactSource;
if (normalizedPath.Contains("/emotion-responses/", StringComparison.OrdinalIgnoreCase) ||
normalizedPath.Contains("/gqa-responses/", StringComparison.OrdinalIgnoreCase))
@@ -221,6 +223,8 @@ public static class LegacyMimCatalogImporter
return new JiboExperienceCatalog
{
Jokes = Merge(baseCatalog.Jokes, importedCatalog.Jokes),
RobotFacts = Merge(baseCatalog.RobotFacts, importedCatalog.RobotFacts),
HumanFacts = Merge(baseCatalog.HumanFacts, importedCatalog.HumanFacts),
FunFacts = Merge(baseCatalog.FunFacts, importedCatalog.FunFacts),
DanceAnimations = Merge(baseCatalog.DanceAnimations, importedCatalog.DanceAnimations),
GreetingReplies = Merge(baseCatalog.GreetingReplies, importedCatalog.GreetingReplies),
@@ -332,9 +336,12 @@ public static class LegacyMimCatalogImporter
GenericFallback,
Greeting,
Jokes,
RobotFacts,
HumanFacts,
HowAreYou,
Emotion,
FunFacts,
FunFactSource,
Personality,
PersonalReportKickOff,
PersonalReportOutro,
@@ -365,6 +372,8 @@ public static class LegacyMimCatalogImporter
private readonly List<string> _fallbacks = [];
private readonly List<string> _greetings = [];
private readonly List<string> _jokes = [];
private readonly List<string> _robotFacts = [];
private readonly List<string> _humanFacts = [];
private readonly List<string> _howAreYous = [];
private readonly List<string> _funFacts = [];
private readonly List<string> _newsCategoryIntroReplies = [];
@@ -380,7 +389,7 @@ public static class LegacyMimCatalogImporter
private readonly List<string> _weatherTomorrowHighLowReplies = [];
private readonly List<string> _weatherTomorrowIntroReplies = [];
public void Add(LegacyMimBucket bucket, string? condition, string text)
public void Add(LegacyMimBucket bucket, string? condition, string text, string? sourcePrompt = null)
{
switch (bucket)
{
@@ -399,6 +408,12 @@ public static class LegacyMimCatalogImporter
_jokes.Add(text);
return;
case LegacyMimBucket.RobotFacts:
AddDistinct(_robotFacts, text);
return;
case LegacyMimBucket.HumanFacts:
AddDistinct(_humanFacts, text);
return;
case LegacyMimBucket.HowAreYou:
if (_howAreYous.Any(value => string.Equals(value, text, StringComparison.OrdinalIgnoreCase)))
return;
@@ -425,6 +440,19 @@ public static class LegacyMimCatalogImporter
_personalities.Add(text);
return;
case LegacyMimBucket.FunFactSource:
switch (ResolveFunFactTarget(sourcePrompt ?? text))
{
case LegacyMimBucket.RobotFacts:
AddDistinct(_robotFacts, text);
return;
case LegacyMimBucket.HumanFacts:
AddDistinct(_humanFacts, text);
return;
default:
AddDistinct(_funFacts, text);
return;
}
case LegacyMimBucket.FunFacts:
if (_funFacts.Any(value => string.Equals(value, text, StringComparison.OrdinalIgnoreCase))) return;
@@ -488,6 +516,8 @@ public static class LegacyMimCatalogImporter
return new JiboExperienceCatalog
{
Jokes = [.. _jokes],
RobotFacts = [.. _robotFacts],
HumanFacts = [.. _humanFacts],
FunFacts = [.. _funFacts],
GreetingReplies = [.. _greetings],
HowAreYouReplies = [.. _howAreYous],
@@ -519,6 +549,23 @@ public static class LegacyMimCatalogImporter
target.Add(text);
}
private LegacyMimBucket ResolveFunFactTarget(string prompt)
{
var lowered = NormalizePrompt(prompt, false).ToLowerInvariant();
if (ContainsAny(lowered, "robot", "humanoid", "machine", "about me", "my cameras", "turing", "deep blue", "rossum"))
return LegacyMimBucket.RobotFacts;
if (ContainsAny(lowered, "human", "people", "grown ups", "human being", "humans"))
return LegacyMimBucket.HumanFacts;
return LegacyMimBucket.FunFacts;
}
private static bool ContainsAny(string text, params string[] values)
{
return values.Any(value => text.Contains(value, StringComparison.OrdinalIgnoreCase));
}
}
private sealed class LegacyMimDefinition

View File

@@ -9,3 +9,4 @@ It also includes a descriptor pack for questions like `are you kind`, `are you f
The newest seasonal pack adds holiday and seasonal prompts for `what holidays do you celebrate`, New Year's resolution questions, `happy holidays`, Halloween costume questions, spring suggestions, and holiday gift ideas.
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 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.

View File

@@ -192,9 +192,17 @@ public sealed class LegacyMimCatalogImporterTests
Assert.Contains("I love jokes. Did you hear about the theater actor who fell through the floorboards? He was just going through a stage.",
catalog.Jokes);
Assert.Contains("Sure I got one. What did the zero say to the eight. Nice belt.", catalog.Jokes);
Assert.Contains("Here's an interesting fact about me. I have two cameras but they're different focal lengths. One's for far things, and the other's for near things.",
Assert.Contains(catalog.RobotFacts, reply =>
reply.Contains("Leonardo Da Vinci made sketches", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.RobotFacts, reply =>
reply.Contains("first programmable robot arm", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.RobotFacts, reply =>
reply.Contains("robots have a human form", StringComparison.OrdinalIgnoreCase));
Assert.Contains(catalog.RobotFacts, reply =>
reply.Contains("two cameras but they're different focal lengths", StringComparison.OrdinalIgnoreCase));
Assert.Contains("A random fact for you. A shrimp's heart is in its head.", catalog.FunFacts);
Assert.Contains("An amazing but true fact for you. Dogs and elephants are the only animals that understand pointing.",
catalog.FunFacts);
Assert.Contains("True fact. Children have more taste buds than grown ups.", catalog.FunFacts);
}
[Fact]

View File

@@ -2915,10 +2915,30 @@ public sealed class JiboInteractionServiceTests
Assert.Equal("proactive_fun_fact", decision.IntentName);
Assert.Equal("chitchat-skill", decision.SkillName);
Assert.Equal("fun_fact", decision.SkillPayload!["replyType"]);
Assert.Equal("fun_fact", decision.SkillPayload["factCategory"]);
Assert.NotNull(decision.ReplyText);
Assert.NotEmpty(decision.ReplyText);
}
[Fact]
public async Task BuildDecisionAsync_Surprise_UsesHumanFactWhenRandomizerChoosesLastCategory()
{
var service = CreateService(randomizer: new FactCategoryLastRandomizer());
var decision = await service.BuildDecisionAsync(new TurnContext
{
RawTranscript = "surprise me",
NormalizedTranscript = "surprise me"
});
Assert.Equal("proactive_fun_fact", decision.IntentName);
Assert.Equal("chitchat-skill", decision.SkillName);
Assert.Equal("fun_fact", decision.SkillPayload!["replyType"]);
Assert.Equal("human_fact", decision.SkillPayload["factCategory"]);
Assert.NotNull(decision.ReplyText);
Assert.Contains("human", decision.ReplyText, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task BuildDecisionAsync_WordOfDayOfferPrompt_WithNoisyAffirmation_MapsToWordOfDayLaunch()
{
@@ -3872,11 +3892,12 @@ public sealed class JiboInteractionServiceTests
IPersonalMemoryStore? personalMemoryStore = null,
IWeatherReportProvider? weatherReportProvider = null,
INewsBriefingProvider? newsBriefingProvider = null,
IJiboExperienceContentRepository? contentRepository = null)
IJiboExperienceContentRepository? contentRepository = null,
IJiboRandomizer? randomizer = null)
{
return new JiboInteractionService(
new JiboExperienceContentCache(contentRepository ?? new InMemoryJiboExperienceContentRepository()),
new FirstItemRandomizer(),
randomizer ?? new FirstItemRandomizer(),
personalMemoryStore ?? new InMemoryPersonalMemoryStore(),
weatherReportProvider,
newsBriefingProvider);
@@ -3915,6 +3936,24 @@ public sealed class JiboInteractionServiceTests
}
}
private sealed class LastItemRandomizer : IJiboRandomizer
{
public T Choose<T>(IReadOnlyList<T> items)
{
return items[^1];
}
}
private sealed class FactCategoryLastRandomizer : IJiboRandomizer
{
public T Choose<T>(IReadOnlyList<T> items)
{
return typeof(T).Name == "ProactiveFactCategory"
? items[^1]
: items[0];
}
}
private sealed class CapturingWeatherReportProvider : IWeatherReportProvider
{
public WeatherReportRequest? LastRequest { get; private set; }