Extract memory and personality decision builders
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jibo.Cloud.Application.Abstractions;
|
||||
using Jibo.Cloud.Domain.Models;
|
||||
using Jibo.Runtime.Abstractions;
|
||||
|
||||
namespace Jibo.Cloud.Application.Services;
|
||||
|
||||
public sealed partial class JiboInteractionService
|
||||
{
|
||||
private JiboInteractionDecision BuildRememberNameDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var name = TryExtractNameFact(transcript);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_name",
|
||||
"I can remember it if you say, my name is Alex.");
|
||||
|
||||
personalMemoryStore.SetName(ResolveTenantScope(turn), name);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_name",
|
||||
$"Nice to meet you, {name}. I will remember your name.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallNameDecision(TurnContext turn, GreetingPresenceProfile? presence = null)
|
||||
{
|
||||
var personScope = ResolveTenantScope(turn, presence?.PrimaryPersonId);
|
||||
var name = personalMemoryStore.GetName(personScope);
|
||||
if (string.IsNullOrWhiteSpace(name)) name = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
|
||||
name = ToDisplayName(name ?? string.Empty);
|
||||
|
||||
return string.IsNullOrWhiteSpace(name)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
"I do not know your name yet. You can say, my name is Alex.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
presence is not null && !string.IsNullOrWhiteSpace(presence.PrimaryPersonId)
|
||||
? $"I think you are {name}."
|
||||
: $"You told me your name is {name}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberBirthdayDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var birthday = TryExtractBirthdayFact(transcript);
|
||||
if (string.IsNullOrWhiteSpace(birthday))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_birthday",
|
||||
"I can remember it if you say, my birthday is March 14.");
|
||||
|
||||
var tenantScope = ResolveTenantScope(turn);
|
||||
personalMemoryStore.SetBirthday(tenantScope, birthday);
|
||||
var birthdayDate = TryParseBirthdayDate(birthday);
|
||||
if (birthdayDate is not null)
|
||||
{
|
||||
var birthdayLabel = ResolvePreferredBirthdayLabel(turn);
|
||||
cloudStateStore?.UpsertHoliday(new HolidayRecord
|
||||
{
|
||||
EventId = $"birthday-{tenantScope.LoopId}-{tenantScope.PersonId ?? "loop"}",
|
||||
Name = string.IsNullOrWhiteSpace(birthdayLabel) ? "Birthday" : $"{birthdayLabel}'s Birthday",
|
||||
Category = "birthday",
|
||||
Subcategory = "personal",
|
||||
LoopId = tenantScope.LoopId,
|
||||
MemberId = tenantScope.PersonId,
|
||||
IsEnabled = true,
|
||||
Date = birthdayDate.Value,
|
||||
Source = "birthday",
|
||||
CountryCode = "US"
|
||||
});
|
||||
}
|
||||
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_birthday",
|
||||
$"Got it. I will remember your birthday is {birthday}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallBirthdayDecision(TurnContext turn)
|
||||
{
|
||||
var birthday = personalMemoryStore.GetBirthday(ResolveTenantScope(turn));
|
||||
return string.IsNullOrWhiteSpace(birthday)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_birthday",
|
||||
"I do not know your birthday yet. You can say, my birthday is March 14.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_birthday",
|
||||
$"You told me your birthday is {birthday}.");
|
||||
}
|
||||
|
||||
private static DateOnly? TryParseBirthdayDate(string birthdayText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(birthdayText)) return null;
|
||||
|
||||
var normalized = birthdayText.Trim().ToLowerInvariant();
|
||||
var match = Regex.Match(
|
||||
normalized,
|
||||
@"\b(?<month>january|february|march|april|may|june|july|august|september|october|november|december)\s+(?<day>\d{1,2})(?:st|nd|rd|th)?\b",
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
if (!match.Success) return null;
|
||||
|
||||
var month = match.Groups["month"].Value.ToLowerInvariant() switch
|
||||
{
|
||||
"january" => 1,
|
||||
"february" => 2,
|
||||
"march" => 3,
|
||||
"april" => 4,
|
||||
"may" => 5,
|
||||
"june" => 6,
|
||||
"july" => 7,
|
||||
"august" => 8,
|
||||
"september" => 9,
|
||||
"october" => 10,
|
||||
"november" => 11,
|
||||
"december" => 12,
|
||||
_ => 0
|
||||
};
|
||||
if (month == 0) return null;
|
||||
|
||||
if (!int.TryParse(match.Groups["day"].Value, out var day) || day is < 1 or > 31) return null;
|
||||
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
var year = today.Year;
|
||||
if (day > DateTime.DaysInMonth(year, month)) return null;
|
||||
|
||||
DateOnly birthday;
|
||||
try
|
||||
{
|
||||
birthday = new DateOnly(year, month, day);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (birthday < today) birthday = birthday.AddYears(1);
|
||||
return birthday;
|
||||
}
|
||||
|
||||
private static string? ResolvePreferredBirthdayLabel(TurnContext turn)
|
||||
{
|
||||
var context = ResolveGreetingPresenceProfile(turn);
|
||||
return !string.IsNullOrWhiteSpace(context.PrimaryPersonId) &&
|
||||
context.LoopUserFirstNames.TryGetValue(context.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName)
|
||||
? ToDisplayName(firstName)
|
||||
: null;
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberImportantDateDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var importantDate = TryExtractImportantDateSet(transcript);
|
||||
if (importantDate is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_important_date",
|
||||
"I can remember it if you say, our anniversary is June 10.");
|
||||
|
||||
personalMemoryStore.SetImportantDate(ResolveTenantScope(turn), importantDate.Value.Label,
|
||||
importantDate.Value.Value);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_important_date",
|
||||
$"Got it. I will remember your {importantDate.Value.Label} is {importantDate.Value.Value}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallImportantDateDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var label = TryExtractImportantDateLookupLabel(transcript);
|
||||
if (string.IsNullOrWhiteSpace(label))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
"Ask me like this: when is our anniversary?");
|
||||
|
||||
var storedDate = personalMemoryStore.GetImportantDate(ResolveTenantScope(turn), label);
|
||||
return string.IsNullOrWhiteSpace(storedDate)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
$"I do not know your {label} yet.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
$"You told me your {label} is {storedDate}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberPreferenceDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var preference = TryExtractPreferenceSet(transcript);
|
||||
if (preference is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_preference",
|
||||
"I can remember it if you say, my favorite music is jazz.");
|
||||
|
||||
personalMemoryStore.SetPreference(ResolveTenantScope(turn), preference.Value.Category, preference.Value.Value);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_preference",
|
||||
$"Got it. I will remember your favorite {preference.Value.Category} is {preference.Value.Value}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallPreferenceDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var category = TryExtractPreferenceLookupCategory(transcript);
|
||||
if (string.IsNullOrWhiteSpace(category))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
"Ask me like this: what is my favorite music?");
|
||||
|
||||
var preference = personalMemoryStore.GetPreference(ResolveTenantScope(turn), category);
|
||||
return string.IsNullOrWhiteSpace(preference)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
$"I do not know your favorite {category} yet.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
$"You told me your favorite {category} is {preference}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberAffinityDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var affinitySet = TryExtractAffinitySet(transcript);
|
||||
if (affinitySet is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_affinity",
|
||||
"I can remember it if you say, I like pizza or I dislike mushrooms.");
|
||||
|
||||
personalMemoryStore.SetAffinity(ResolveTenantScope(turn), affinitySet.Value.Item, affinitySet.Value.Affinity);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_affinity",
|
||||
$"Got it. I will remember you {DescribeAffinityAsVerb(affinitySet.Value.Affinity)} {affinitySet.Value.Item}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallAffinityDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var lookup = TryExtractAffinityLookup(transcript);
|
||||
if (lookup is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
"Ask me like this: do I like pizza?");
|
||||
|
||||
var affinity = personalMemoryStore.GetAffinity(ResolveTenantScope(turn), lookup.Value.Item);
|
||||
if (affinity is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"I do not remember how you feel about {lookup.Value.Item} yet.");
|
||||
|
||||
if (lookup.Value.ExpectedAffinity is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.");
|
||||
|
||||
var matches = lookup.Value.ExpectedAffinity == PersonalAffinity.Dislike
|
||||
? affinity == PersonalAffinity.Dislike
|
||||
: affinity is PersonalAffinity.Like or PersonalAffinity.Love;
|
||||
|
||||
return matches
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"Yes. You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"Not exactly. You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Jibo.Cloud.Application.Abstractions;
|
||||
using Jibo.Cloud.Domain.Models;
|
||||
using Jibo.Runtime.Abstractions;
|
||||
|
||||
namespace Jibo.Cloud.Application.Services;
|
||||
|
||||
public sealed partial class JiboInteractionService
|
||||
{
|
||||
private static JiboInteractionDecision BuildRobotAgeDecision(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var referenceDate = DateOnly.FromDateTime((referenceLocalTime ?? DateTimeOffset.UtcNow).Date);
|
||||
var ageDescription = DescribePersonaAge(referenceDate, OpenJiboCloudBuildInfo.PersonaBirthday);
|
||||
return new JiboInteractionDecision(
|
||||
"robot_age",
|
||||
$"I count {OpenJiboCloudBuildInfo.PersonaBirthdayWords} as my birthday, so I am {ageDescription}.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildRobotBirthdayDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"robot_birthday",
|
||||
$"My birthday is {OpenJiboCloudBuildInfo.PersonaBirthdayWords}.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildTriggerIgnoredDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"trigger_ignored",
|
||||
string.Empty,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["skillId"] = "chitchat-skill",
|
||||
["cloudResponseMode"] = "completion_only"
|
||||
});
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildReactiveGreetingDecision(
|
||||
TurnContext turn,
|
||||
string greetingIntent,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var presence = ResolveGreetingPresenceProfile(turn);
|
||||
var displayName = ResolvePreferredGreetingName(turn, presence);
|
||||
var replyText = BuildReactiveGreetingReply(greetingIntent, displayName, referenceLocalTime);
|
||||
return new JiboInteractionDecision(
|
||||
greetingIntent,
|
||||
replyText,
|
||||
ContextUpdates: BuildGreetingContextUpdates("ReactiveGreeting", presence.PrimaryPersonId, false));
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactiveGreetingDecision(
|
||||
TurnContext turn,
|
||||
GreetingPresenceProfile presence,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var displayName = ResolvePreferredGreetingName(turn, presence);
|
||||
var greetingPrefix = ResolveTimeOfDayGreetingPrefix(referenceLocalTime);
|
||||
var replyText = string.IsNullOrWhiteSpace(displayName)
|
||||
? $"{greetingPrefix}. I am glad to see you."
|
||||
: $"{greetingPrefix}, {displayName}. Welcome back.";
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_greeting",
|
||||
replyText,
|
||||
ContextUpdates: BuildGreetingContextUpdates("ProactiveGreeting", presence.PrimaryPersonId, true));
|
||||
}
|
||||
|
||||
private static string BuildReactiveGreetingReply(
|
||||
string greetingIntent,
|
||||
string? displayName,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var namePrefix = string.IsNullOrWhiteSpace(displayName)
|
||||
? string.Empty
|
||||
: $", {displayName}";
|
||||
|
||||
return greetingIntent switch
|
||||
{
|
||||
"good_morning" => $"Good morning{namePrefix}. It is great to see you.",
|
||||
"good_afternoon" => $"Good afternoon{namePrefix}. I am glad you are here.",
|
||||
"good_evening" => $"Good evening{namePrefix}. It is nice to have you back.",
|
||||
"good_night" => $"Good night{namePrefix}. Sleep well.",
|
||||
"welcome_back" => string.IsNullOrWhiteSpace(displayName)
|
||||
? $"Welcome back. {ResolveTimeOfDayGreetingPrefix(referenceLocalTime)}."
|
||||
: $"Welcome back, {displayName}. {ResolveTimeOfDayGreetingPrefix(referenceLocalTime)}.",
|
||||
_ => $"Hello{namePrefix}. It is nice to see you."
|
||||
};
|
||||
}
|
||||
|
||||
private string? ResolvePreferredGreetingName(TurnContext turn, GreetingPresenceProfile presence)
|
||||
{
|
||||
var rememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn, presence.PrimaryPersonId));
|
||||
if (!string.IsNullOrWhiteSpace(rememberedName)) return ToDisplayName(rememberedName);
|
||||
|
||||
var tenantRememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
if (!string.IsNullOrWhiteSpace(tenantRememberedName)) return ToDisplayName(tenantRememberedName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(presence.PrimaryPersonId) &&
|
||||
presence.LoopUserFirstNames.TryGetValue(presence.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName))
|
||||
return ToDisplayName(firstName);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ToDisplayName(string value)
|
||||
{
|
||||
var trimmed = value.Trim();
|
||||
return string.IsNullOrWhiteSpace(trimmed)
|
||||
? string.Empty
|
||||
: CultureInfo.InvariantCulture.TextInfo.ToTitleCase(trimmed);
|
||||
}
|
||||
|
||||
private static bool ShouldHandleProactiveGreetingTrigger(
|
||||
TurnContext turn,
|
||||
string? triggerSource,
|
||||
GreetingPresenceProfile presence)
|
||||
{
|
||||
if (string.Equals(triggerSource, "SURPRISE", StringComparison.OrdinalIgnoreCase)) return false;
|
||||
|
||||
if (!presence.HasKnownIdentity) return false;
|
||||
|
||||
var lastGreetingUtc = ReadTimestampAttribute(turn, LastProactiveGreetingUtcMetadataKey);
|
||||
return !lastGreetingUtc.HasValue || DateTimeOffset.UtcNow - lastGreetingUtc.Value >= ProactiveGreetingCooldown;
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ReadTimestampAttribute(TurnContext turn, string key)
|
||||
{
|
||||
if (!turn.Attributes.TryGetValue(key, out var value) || value is null) return null;
|
||||
|
||||
return DateTimeOffset.TryParse(
|
||||
value.ToString(),
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.RoundtripKind,
|
||||
out var parsed)
|
||||
? parsed
|
||||
: null;
|
||||
}
|
||||
|
||||
private static IDictionary<string, object?> BuildGreetingContextUpdates(string route, string? speakerId,
|
||||
bool proactive)
|
||||
{
|
||||
var updates = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[ChitchatStateMachine.StateMetadataKey] = "complete",
|
||||
[ChitchatStateMachine.RouteMetadataKey] = "ScriptedResponse",
|
||||
[ChitchatStateMachine.EmotionMetadataKey] = string.Empty,
|
||||
[GreetingRouteMetadataKey] = route,
|
||||
[GreetingSpeakerMetadataKey] = speakerId ?? string.Empty,
|
||||
[proactive ? LastProactiveGreetingUtcMetadataKey : LastReactiveGreetingUtcMetadataKey] = DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
private static string ResolveTimeOfDayGreetingPrefix(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var hour = (referenceLocalTime ?? DateTimeOffset.UtcNow).Hour;
|
||||
return hour switch
|
||||
{
|
||||
>= 5 and < 12 => "Good morning",
|
||||
>= 12 and < 17 => "Good afternoon",
|
||||
_ => "Good evening"
|
||||
};
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildPizzaDecision()
|
||||
{
|
||||
return BuildPizzaAnimationDecision("pizza", "One pizza, coming right up.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildPizzaAnimationDecision(string intentName, string replyText)
|
||||
{
|
||||
var prompt = randomizer.Choose(PizzaMimPrompts);
|
||||
return new JiboInteractionDecision(
|
||||
intentName,
|
||||
replyText,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["esml"] = prompt.Esml,
|
||||
["mim_id"] = "RA_JBO_MakePizza",
|
||||
["mim_type"] = "announcement",
|
||||
["prompt_id"] = prompt.PromptId,
|
||||
["prompt_sub_category"] = "AN"
|
||||
});
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactivePizzaDayDecision(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var referenceDate = (referenceLocalTime ?? DateTimeOffset.UtcNow).Date;
|
||||
return BuildPizzaAnimationDecision(
|
||||
"proactive_pizza_day",
|
||||
$"Happy National Pizza Day for {referenceDate.ToString("MMMM d", CultureInfo.InvariantCulture)}. One pizza, coming right up.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactivePizzaPreferenceDecision()
|
||||
{
|
||||
return BuildPizzaAnimationDecision(
|
||||
"proactive_pizza_preference",
|
||||
"You mentioned pizza is a favorite, so I thought we should make one.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactivePizzaFactOfferDecision()
|
||||
{
|
||||
var listenContexts = new[] { "shared/yes_no" };
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_offer_pizza_fact",
|
||||
"Do you want to hear a fun pizza fact?",
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["mim_id"] = "runtime-chat",
|
||||
["mim_type"] = "question",
|
||||
["prompt_id"] = "RUNTIME_PROMPT",
|
||||
["prompt_sub_category"] = "Q",
|
||||
["listen_contexts"] = listenContexts
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactivePizzaFactDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_pizza_fact",
|
||||
"Americans consume about 100 acres of pizza every day, roughly 350 slices per second. That's a lot of pizza.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactiveFunFactDecision(JiboExperienceCatalog catalog)
|
||||
{
|
||||
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,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["mim_id"] = "runtime-fun-fact",
|
||||
["mim_type"] = "announcement",
|
||||
["prompt_id"] = "RUNTIME_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(
|
||||
"proactive_joke",
|
||||
randomizer.Choose(catalog.Jokes),
|
||||
"@be/joke",
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["replyType"] = "joke"
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactiveOfferDeclinedDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_offer_declined",
|
||||
"No problem. We can save the pizza fact for another time.");
|
||||
}
|
||||
|
||||
private string BuildGenericReply(JiboExperienceCatalog catalog, string transcript, string lowered)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(transcript)) return "I am listening.";
|
||||
|
||||
if (lowered.Contains("good morning", StringComparison.Ordinal))
|
||||
return "Good morning! It is nice to hear your voice.";
|
||||
|
||||
if (lowered.Contains("good afternoon", StringComparison.Ordinal))
|
||||
return "Good afternoon. I am happy to be here.";
|
||||
|
||||
return lowered.Contains("good night", StringComparison.Ordinal)
|
||||
? "Good night. Sleep tight."
|
||||
: randomizer.Choose(catalog.GenericFallbackReplies)
|
||||
.Replace("{transcript}", transcript, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedPersonalityDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedPersonalityDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedFavoriteAnimalDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedFavoriteAnimalDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedGreetingDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedGreetingDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayDecision(
|
||||
IReadOnlyList<string> replies,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayDecision(
|
||||
replies,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayTrackerDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayTrackerDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayGreetingDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayGreetingDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayTemplateDecision(
|
||||
TurnContext turn,
|
||||
GreetingPresenceProfile presence,
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
var selected = ScriptedResponseDecisionBuilder.SelectLegacyReply(
|
||||
catalog.HolidayReplies,
|
||||
randomizer,
|
||||
preferredSnippets);
|
||||
return new JiboInteractionDecision(
|
||||
intentName,
|
||||
RenderHolidayTemplate(selected, turn, presence),
|
||||
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
|
||||
}
|
||||
|
||||
private string SelectLegacyPersonalityReply(JiboExperienceCatalog catalog, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyPersonalityReply(catalog, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private string SelectLegacyGreetingReply(JiboExperienceCatalog catalog, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyGreetingReply(catalog, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private string SelectLegacyReply(IReadOnlyList<string> replies, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyReply(replies, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private string RenderHolidayTemplate(string template, TurnContext turn, GreetingPresenceProfile presence)
|
||||
{
|
||||
var ownerName = ResolvePreferredGreetingName(turn, presence);
|
||||
var speakerName = !string.IsNullOrWhiteSpace(ownerName) ? ownerName : "you";
|
||||
return template
|
||||
.Replace("${speaker}'s", $"{speakerName}'s", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("${speaker}", speakerName, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("${loop.owner}", string.IsNullOrWhiteSpace(ownerName) ? string.Empty : ownerName,
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(" ", " ", StringComparison.Ordinal)
|
||||
.Trim();
|
||||
}
|
||||
}
|
||||
@@ -803,542 +803,6 @@ public sealed partial class JiboInteractionService(
|
||||
SkillPayload: new Dictionary<string, object?> { ["esml"] = OpenJiboCloudBuildInfo.EsmlVersion });
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildRobotAgeDecision(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var referenceDate = DateOnly.FromDateTime((referenceLocalTime ?? DateTimeOffset.UtcNow).Date);
|
||||
var ageDescription = DescribePersonaAge(referenceDate, OpenJiboCloudBuildInfo.PersonaBirthday);
|
||||
return new JiboInteractionDecision(
|
||||
"robot_age",
|
||||
$"I count {OpenJiboCloudBuildInfo.PersonaBirthdayWords} as my birthday, so I am {ageDescription}.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildRobotBirthdayDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"robot_birthday",
|
||||
$"My birthday is {OpenJiboCloudBuildInfo.PersonaBirthdayWords}.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildTriggerIgnoredDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"trigger_ignored",
|
||||
string.Empty,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["skillId"] = "chitchat-skill",
|
||||
["cloudResponseMode"] = "completion_only"
|
||||
});
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildReactiveGreetingDecision(
|
||||
TurnContext turn,
|
||||
string greetingIntent,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var presence = ResolveGreetingPresenceProfile(turn);
|
||||
var displayName = ResolvePreferredGreetingName(turn, presence);
|
||||
var replyText = BuildReactiveGreetingReply(greetingIntent, displayName, referenceLocalTime);
|
||||
return new JiboInteractionDecision(
|
||||
greetingIntent,
|
||||
replyText,
|
||||
ContextUpdates: BuildGreetingContextUpdates("ReactiveGreeting", presence.PrimaryPersonId, false));
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactiveGreetingDecision(
|
||||
TurnContext turn,
|
||||
GreetingPresenceProfile presence,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var displayName = ResolvePreferredGreetingName(turn, presence);
|
||||
var greetingPrefix = ResolveTimeOfDayGreetingPrefix(referenceLocalTime);
|
||||
var replyText = string.IsNullOrWhiteSpace(displayName)
|
||||
? $"{greetingPrefix}. I am glad to see you."
|
||||
: $"{greetingPrefix}, {displayName}. Welcome back.";
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_greeting",
|
||||
replyText,
|
||||
ContextUpdates: BuildGreetingContextUpdates("ProactiveGreeting", presence.PrimaryPersonId, true));
|
||||
}
|
||||
|
||||
private static string BuildReactiveGreetingReply(
|
||||
string greetingIntent,
|
||||
string? displayName,
|
||||
DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var namePrefix = string.IsNullOrWhiteSpace(displayName)
|
||||
? string.Empty
|
||||
: $", {displayName}";
|
||||
|
||||
return greetingIntent switch
|
||||
{
|
||||
"good_morning" => $"Good morning{namePrefix}. It is great to see you.",
|
||||
"good_afternoon" => $"Good afternoon{namePrefix}. I am glad you are here.",
|
||||
"good_evening" => $"Good evening{namePrefix}. It is nice to have you back.",
|
||||
"good_night" => $"Good night{namePrefix}. Sleep well.",
|
||||
"welcome_back" => string.IsNullOrWhiteSpace(displayName)
|
||||
? $"Welcome back. {ResolveTimeOfDayGreetingPrefix(referenceLocalTime)}."
|
||||
: $"Welcome back, {displayName}. {ResolveTimeOfDayGreetingPrefix(referenceLocalTime)}.",
|
||||
_ => $"Hello{namePrefix}. It is nice to see you."
|
||||
};
|
||||
}
|
||||
|
||||
private string? ResolvePreferredGreetingName(TurnContext turn, GreetingPresenceProfile presence)
|
||||
{
|
||||
var rememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn, presence.PrimaryPersonId));
|
||||
if (!string.IsNullOrWhiteSpace(rememberedName)) return ToDisplayName(rememberedName);
|
||||
|
||||
var tenantRememberedName = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
if (!string.IsNullOrWhiteSpace(tenantRememberedName)) return ToDisplayName(tenantRememberedName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(presence.PrimaryPersonId) &&
|
||||
presence.LoopUserFirstNames.TryGetValue(presence.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName))
|
||||
return ToDisplayName(firstName);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ToDisplayName(string value)
|
||||
{
|
||||
var trimmed = value.Trim();
|
||||
return string.IsNullOrWhiteSpace(trimmed)
|
||||
? string.Empty
|
||||
: CultureInfo.InvariantCulture.TextInfo.ToTitleCase(trimmed);
|
||||
}
|
||||
|
||||
private static bool ShouldHandleProactiveGreetingTrigger(
|
||||
TurnContext turn,
|
||||
string? triggerSource,
|
||||
GreetingPresenceProfile presence)
|
||||
{
|
||||
if (string.Equals(triggerSource, "SURPRISE", StringComparison.OrdinalIgnoreCase)) return false;
|
||||
|
||||
if (!presence.HasKnownIdentity) return false;
|
||||
|
||||
var lastGreetingUtc = ReadTimestampAttribute(turn, LastProactiveGreetingUtcMetadataKey);
|
||||
return !lastGreetingUtc.HasValue || DateTimeOffset.UtcNow - lastGreetingUtc.Value >= ProactiveGreetingCooldown;
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ReadTimestampAttribute(TurnContext turn, string key)
|
||||
{
|
||||
if (!turn.Attributes.TryGetValue(key, out var value) || value is null) return null;
|
||||
|
||||
return DateTimeOffset.TryParse(
|
||||
value.ToString(),
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.RoundtripKind,
|
||||
out var parsed)
|
||||
? parsed
|
||||
: null;
|
||||
}
|
||||
|
||||
private static IDictionary<string, object?> BuildGreetingContextUpdates(string route, string? speakerId,
|
||||
bool proactive)
|
||||
{
|
||||
var updates = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[ChitchatStateMachine.StateMetadataKey] = "complete",
|
||||
[ChitchatStateMachine.RouteMetadataKey] = "ScriptedResponse",
|
||||
[ChitchatStateMachine.EmotionMetadataKey] = string.Empty,
|
||||
[GreetingRouteMetadataKey] = route,
|
||||
[GreetingSpeakerMetadataKey] = speakerId ?? string.Empty,
|
||||
[proactive ? LastProactiveGreetingUtcMetadataKey : LastReactiveGreetingUtcMetadataKey] = DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
return updates;
|
||||
}
|
||||
|
||||
private static string ResolveTimeOfDayGreetingPrefix(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var hour = (referenceLocalTime ?? DateTimeOffset.UtcNow).Hour;
|
||||
return hour switch
|
||||
{
|
||||
>= 5 and < 12 => "Good morning",
|
||||
>= 12 and < 17 => "Good afternoon",
|
||||
_ => "Good evening"
|
||||
};
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberNameDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var name = TryExtractNameFact(transcript);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_name",
|
||||
"I can remember it if you say, my name is Alex.");
|
||||
|
||||
personalMemoryStore.SetName(ResolveTenantScope(turn), name);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_name",
|
||||
$"Nice to meet you, {name}. I will remember your name.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallNameDecision(TurnContext turn, GreetingPresenceProfile? presence = null)
|
||||
{
|
||||
var personScope = ResolveTenantScope(turn, presence?.PrimaryPersonId);
|
||||
var name = personalMemoryStore.GetName(personScope);
|
||||
if (string.IsNullOrWhiteSpace(name)) name = personalMemoryStore.GetName(ResolveTenantScope(turn));
|
||||
|
||||
name = ToDisplayName(name ?? string.Empty);
|
||||
|
||||
return string.IsNullOrWhiteSpace(name)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
"I do not know your name yet. You can say, my name is Alex.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_name",
|
||||
presence is not null && !string.IsNullOrWhiteSpace(presence.PrimaryPersonId)
|
||||
? $"I think you are {name}."
|
||||
: $"You told me your name is {name}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberBirthdayDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var birthday = TryExtractBirthdayFact(transcript);
|
||||
if (string.IsNullOrWhiteSpace(birthday))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_birthday",
|
||||
"I can remember it if you say, my birthday is March 14.");
|
||||
|
||||
var tenantScope = ResolveTenantScope(turn);
|
||||
personalMemoryStore.SetBirthday(tenantScope, birthday);
|
||||
var birthdayDate = TryParseBirthdayDate(birthday);
|
||||
if (birthdayDate is not null)
|
||||
{
|
||||
var birthdayLabel = ResolvePreferredBirthdayLabel(turn);
|
||||
cloudStateStore?.UpsertHoliday(new HolidayRecord
|
||||
{
|
||||
EventId = $"birthday-{tenantScope.LoopId}-{tenantScope.PersonId ?? "loop"}",
|
||||
Name = string.IsNullOrWhiteSpace(birthdayLabel) ? "Birthday" : $"{birthdayLabel}'s Birthday",
|
||||
Category = "birthday",
|
||||
Subcategory = "personal",
|
||||
LoopId = tenantScope.LoopId,
|
||||
MemberId = tenantScope.PersonId,
|
||||
IsEnabled = true,
|
||||
Date = birthdayDate.Value,
|
||||
Source = "birthday",
|
||||
CountryCode = "US"
|
||||
});
|
||||
}
|
||||
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_birthday",
|
||||
$"Got it. I will remember your birthday is {birthday}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallBirthdayDecision(TurnContext turn)
|
||||
{
|
||||
var birthday = personalMemoryStore.GetBirthday(ResolveTenantScope(turn));
|
||||
return string.IsNullOrWhiteSpace(birthday)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_birthday",
|
||||
"I do not know your birthday yet. You can say, my birthday is March 14.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_birthday",
|
||||
$"You told me your birthday is {birthday}.");
|
||||
}
|
||||
|
||||
private static DateOnly? TryParseBirthdayDate(string birthdayText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(birthdayText)) return null;
|
||||
|
||||
var normalized = birthdayText.Trim().ToLowerInvariant();
|
||||
var match = Regex.Match(
|
||||
normalized,
|
||||
@"\b(?<month>january|february|march|april|may|june|july|august|september|october|november|december)\s+(?<day>\d{1,2})(?:st|nd|rd|th)?\b",
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
if (!match.Success) return null;
|
||||
|
||||
var month = match.Groups["month"].Value.ToLowerInvariant() switch
|
||||
{
|
||||
"january" => 1,
|
||||
"february" => 2,
|
||||
"march" => 3,
|
||||
"april" => 4,
|
||||
"may" => 5,
|
||||
"june" => 6,
|
||||
"july" => 7,
|
||||
"august" => 8,
|
||||
"september" => 9,
|
||||
"october" => 10,
|
||||
"november" => 11,
|
||||
"december" => 12,
|
||||
_ => 0
|
||||
};
|
||||
if (month == 0) return null;
|
||||
|
||||
if (!int.TryParse(match.Groups["day"].Value, out var day) || day is < 1 or > 31) return null;
|
||||
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
var year = today.Year;
|
||||
if (day > DateTime.DaysInMonth(year, month)) return null;
|
||||
|
||||
DateOnly birthday;
|
||||
try
|
||||
{
|
||||
birthday = new DateOnly(year, month, day);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (birthday < today) birthday = birthday.AddYears(1);
|
||||
return birthday;
|
||||
}
|
||||
|
||||
private static string? ResolvePreferredBirthdayLabel(TurnContext turn)
|
||||
{
|
||||
var context = ResolveGreetingPresenceProfile(turn);
|
||||
return !string.IsNullOrWhiteSpace(context.PrimaryPersonId) &&
|
||||
context.LoopUserFirstNames.TryGetValue(context.PrimaryPersonId, out var firstName) &&
|
||||
!string.IsNullOrWhiteSpace(firstName)
|
||||
? ToDisplayName(firstName)
|
||||
: null;
|
||||
}
|
||||
|
||||
private string RenderHolidayTemplate(string template, TurnContext turn, GreetingPresenceProfile presence)
|
||||
{
|
||||
var ownerName = ResolvePreferredGreetingName(turn, presence);
|
||||
var speakerName = !string.IsNullOrWhiteSpace(ownerName) ? ownerName : "you";
|
||||
return template
|
||||
.Replace("${speaker}'s", $"{speakerName}'s", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("${speaker}", speakerName, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("${loop.owner}", string.IsNullOrWhiteSpace(ownerName) ? string.Empty : ownerName,
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
.Replace(" ", " ", StringComparison.Ordinal)
|
||||
.Trim();
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberImportantDateDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var importantDate = TryExtractImportantDateSet(transcript);
|
||||
if (importantDate is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_important_date",
|
||||
"I can remember it if you say, our anniversary is June 10.");
|
||||
|
||||
personalMemoryStore.SetImportantDate(ResolveTenantScope(turn), importantDate.Value.Label,
|
||||
importantDate.Value.Value);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_important_date",
|
||||
$"Got it. I will remember your {importantDate.Value.Label} is {importantDate.Value.Value}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallImportantDateDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var label = TryExtractImportantDateLookupLabel(transcript);
|
||||
if (string.IsNullOrWhiteSpace(label))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
"Ask me like this: when is our anniversary?");
|
||||
|
||||
var storedDate = personalMemoryStore.GetImportantDate(ResolveTenantScope(turn), label);
|
||||
return string.IsNullOrWhiteSpace(storedDate)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
$"I do not know your {label} yet.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_important_date",
|
||||
$"You told me your {label} is {storedDate}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberPreferenceDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var preference = TryExtractPreferenceSet(transcript);
|
||||
if (preference is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_preference",
|
||||
"I can remember it if you say, my favorite music is jazz.");
|
||||
|
||||
personalMemoryStore.SetPreference(ResolveTenantScope(turn), preference.Value.Category, preference.Value.Value);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_preference",
|
||||
$"Got it. I will remember your favorite {preference.Value.Category} is {preference.Value.Value}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallPreferenceDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var category = TryExtractPreferenceLookupCategory(transcript);
|
||||
if (string.IsNullOrWhiteSpace(category))
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
"Ask me like this: what is my favorite music?");
|
||||
|
||||
var preference = personalMemoryStore.GetPreference(ResolveTenantScope(turn), category);
|
||||
return string.IsNullOrWhiteSpace(preference)
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
$"I do not know your favorite {category} yet.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_preference",
|
||||
$"You told me your favorite {category} is {preference}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRememberAffinityDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var affinitySet = TryExtractAffinitySet(transcript);
|
||||
if (affinitySet is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_affinity",
|
||||
"I can remember it if you say, I like pizza or I dislike mushrooms.");
|
||||
|
||||
personalMemoryStore.SetAffinity(ResolveTenantScope(turn), affinitySet.Value.Item, affinitySet.Value.Affinity);
|
||||
return new JiboInteractionDecision(
|
||||
"memory_set_affinity",
|
||||
$"Got it. I will remember you {DescribeAffinityAsVerb(affinitySet.Value.Affinity)} {affinitySet.Value.Item}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildRecallAffinityDecision(TurnContext turn, string transcript)
|
||||
{
|
||||
var lookup = TryExtractAffinityLookup(transcript);
|
||||
if (lookup is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
"Ask me like this: do I like pizza?");
|
||||
|
||||
var affinity = personalMemoryStore.GetAffinity(ResolveTenantScope(turn), lookup.Value.Item);
|
||||
if (affinity is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"I do not remember how you feel about {lookup.Value.Item} yet.");
|
||||
|
||||
if (lookup.Value.ExpectedAffinity is null)
|
||||
return new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.");
|
||||
|
||||
var matches = lookup.Value.ExpectedAffinity == PersonalAffinity.Dislike
|
||||
? affinity == PersonalAffinity.Dislike
|
||||
: affinity is PersonalAffinity.Like or PersonalAffinity.Love;
|
||||
|
||||
return matches
|
||||
? new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"Yes. You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.")
|
||||
: new JiboInteractionDecision(
|
||||
"memory_get_affinity",
|
||||
$"Not exactly. You told me you {DescribeAffinityAsVerb(affinity.Value)} {lookup.Value.Item}.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildPizzaDecision()
|
||||
{
|
||||
return BuildPizzaAnimationDecision("pizza", "One pizza, coming right up.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildPizzaAnimationDecision(string intentName, string replyText)
|
||||
{
|
||||
var prompt = randomizer.Choose(PizzaMimPrompts);
|
||||
return new JiboInteractionDecision(
|
||||
intentName,
|
||||
replyText,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["esml"] = prompt.Esml,
|
||||
["mim_id"] = "RA_JBO_MakePizza",
|
||||
["mim_type"] = "announcement",
|
||||
["prompt_id"] = prompt.PromptId,
|
||||
["prompt_sub_category"] = "AN"
|
||||
});
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactivePizzaDayDecision(DateTimeOffset? referenceLocalTime)
|
||||
{
|
||||
var referenceDate = (referenceLocalTime ?? DateTimeOffset.UtcNow).Date;
|
||||
return BuildPizzaAnimationDecision(
|
||||
"proactive_pizza_day",
|
||||
$"Happy National Pizza Day for {referenceDate.ToString("MMMM d", CultureInfo.InvariantCulture)}. One pizza, coming right up.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactivePizzaPreferenceDecision()
|
||||
{
|
||||
return BuildPizzaAnimationDecision(
|
||||
"proactive_pizza_preference",
|
||||
"You mentioned pizza is a favorite, so I thought we should make one.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactivePizzaFactOfferDecision()
|
||||
{
|
||||
var listenContexts = new[] { "shared/yes_no" };
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_offer_pizza_fact",
|
||||
"Do you want to hear a fun pizza fact?",
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["mim_id"] = "runtime-chat",
|
||||
["mim_type"] = "question",
|
||||
["prompt_id"] = "RUNTIME_PROMPT",
|
||||
["prompt_sub_category"] = "Q",
|
||||
["listen_contexts"] = listenContexts
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactivePizzaFactDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_pizza_fact",
|
||||
"Americans consume about 100 acres of pizza every day, roughly 350 slices per second. That's a lot of pizza.");
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildProactiveFunFactDecision(JiboExperienceCatalog catalog)
|
||||
{
|
||||
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,
|
||||
"chitchat-skill",
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["mim_id"] = "runtime-fun-fact",
|
||||
["mim_type"] = "announcement",
|
||||
["prompt_id"] = "RUNTIME_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(
|
||||
"proactive_joke",
|
||||
randomizer.Choose(catalog.Jokes),
|
||||
"@be/joke",
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["replyType"] = "joke"
|
||||
});
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildProactiveOfferDeclinedDecision()
|
||||
{
|
||||
return new JiboInteractionDecision(
|
||||
"proactive_offer_declined",
|
||||
"No problem. We can save the pizza fact for another time.");
|
||||
}
|
||||
|
||||
private static JiboInteractionDecision BuildCurrentLocationDecision(TurnContext turn)
|
||||
{
|
||||
var locationName = TryResolveCurrentLocationName(turn);
|
||||
@@ -1508,126 +972,6 @@ public sealed partial class JiboInteractionService(
|
||||
return new PizzaSignal(null);
|
||||
}
|
||||
|
||||
private string BuildGenericReply(JiboExperienceCatalog catalog, string transcript, string lowered)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(transcript)) return "I am listening.";
|
||||
|
||||
if (lowered.Contains("good morning", StringComparison.Ordinal))
|
||||
return "Good morning! It is nice to hear your voice.";
|
||||
|
||||
if (lowered.Contains("good afternoon", StringComparison.Ordinal))
|
||||
return "Good afternoon. I am happy to be here.";
|
||||
|
||||
return lowered.Contains("good night", StringComparison.Ordinal)
|
||||
? "Good night. Sleep tight."
|
||||
: randomizer.Choose(catalog.GenericFallbackReplies)
|
||||
.Replace("{transcript}", transcript, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedPersonalityDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedPersonalityDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedFavoriteAnimalDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedFavoriteAnimalDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedGreetingDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedGreetingDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayDecision(
|
||||
IReadOnlyList<string> replies,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayDecision(
|
||||
replies,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayTrackerDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayTrackerDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayGreetingDecision(
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.BuildScriptedHolidayGreetingDecision(
|
||||
catalog,
|
||||
randomizer,
|
||||
intentName,
|
||||
preferredSnippets);
|
||||
}
|
||||
|
||||
private JiboInteractionDecision BuildScriptedHolidayTemplateDecision(
|
||||
TurnContext turn,
|
||||
GreetingPresenceProfile presence,
|
||||
JiboExperienceCatalog catalog,
|
||||
string intentName,
|
||||
params string[] preferredSnippets)
|
||||
{
|
||||
var selected = ScriptedResponseDecisionBuilder.SelectLegacyReply(
|
||||
catalog.HolidayReplies,
|
||||
randomizer,
|
||||
preferredSnippets);
|
||||
return new JiboInteractionDecision(
|
||||
intentName,
|
||||
RenderHolidayTemplate(selected, turn, presence),
|
||||
ContextUpdates: ScriptedResponseDecisionBuilder.BuildScriptedResponseContextUpdates());
|
||||
}
|
||||
|
||||
private string SelectLegacyPersonalityReply(JiboExperienceCatalog catalog, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyPersonalityReply(catalog, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private string SelectLegacyGreetingReply(JiboExperienceCatalog catalog, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyGreetingReply(catalog, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private string SelectLegacyReply(IReadOnlyList<string> replies, params string[] preferredSnippets)
|
||||
{
|
||||
return ScriptedResponseDecisionBuilder.SelectLegacyReply(replies, randomizer, preferredSnippets);
|
||||
}
|
||||
|
||||
private static string ResolveSemanticIntent(
|
||||
string loweredTranscript,
|
||||
DateTimeOffset? referenceLocalTime,
|
||||
|
||||
Reference in New Issue
Block a user