Learning By Example
Here we will walk through progressively more complex systems built using WorkingMemory and CognitiveSteps. The cognitive steps used are at the bottom of this page.
Simple Example
Using WorkingMemory and cognitive steps can be thought of as building up a set of memories, and then performing functional, append-only manipulations on those memories. Here is a simple example that initializes a WorkingMemory with memories of being a helpful AI assistant and applies a cognitive step to process new messages.
import { WorkingMemory, ChatMessageRoleEnum, Memory } from "@opensouls/engine";
import { externalDialog } from "./lib/cogntiveSteps.js";
// Initialize WorkingMemory with initial memories
let workingMemory = new WorkingMemory({
soulName: "A Helpful Assistant",
memories: [
{
role: ChatMessageRoleEnum.System,
content: "You are modeling the mind of a helpful AI assistant",
},
],
});
// Then, during an event loop, withReply(...) would be called with a memory of each new message:
async function withReply(workingMemory: WorkingMemory, newMessage: Memory): Promise<WorkingMemory> {
// externalDialog is a cognitiveStep defined in another function.
const [updatedMemory, response] = await externalDialog(workingMemory, newMessage);
console.log("AI:", response);
return updatedMemory;
}
Chain of thought
Complex dialog agents require more thoughtful cognitive modeling than a direct reply. AI souls from the Soul Engine feels so uncanny because their feelings and internal cognitive processes are modeled. Here's a 3 step process that models the way a soul might formulate a message.
async function withIntrospectiveReply(workingMemory: WorkingMemory, newMessage: Memory): Promise<WorkingMemory> {
// Add the new message to the working memory
let updatedMemory = workingMemory.withMemory(newMessage);
// Process the internal monologue about feelings towards the last message
[updatedMemory] = await internalMonologue(updatedMemory, "How do they feel about the last message?");
// Process further thoughts about the feelings and the last user message
[updatedMemory] = await internalMonologue(updatedMemory, "Thinks about the feelings and the last user message");
// Generate the external dialog based on the updated working memory
const [finalMemory, response] = await externalDialog(updatedMemory, "Respond to Booto");
console.log("Samantha:", response);
return finalMemory;
}
Decision making
Moving beyond a simple dialog agent, cognitive steps easily support decision making.
In this example, we tell an agentic detective to think through a set of case memories before making a decision on what action to take.
async function caseDecision(caseMemories: ChatMessage[]): Promise<string> {
// Initialize WorkingMemory with the initial memory of being a detective
let workingMemory = new WorkingMemory({
soulName: "Detective",
memories: [
{
role: ChatMessageRoleEnum.System,
content: "You are modeling the mind of a detective who is currently figuring out a complicated case",
},
],
});
// Add case memories to the WorkingMemory
workingMemory = workingMemory.concat(caseMemories)
// Process the analysis of the evidence
[workingMemory] = await internalMonologue(workingMemory, "The detective analyzes the evidence");
// Formulate a hypothesis based on the analysis
[workingMemory] = await internalMonologue(workingMemory, "The detective makes a hypothesis based on the analysis");
// Decide the next step based on the hypothesis
const [finalMemory, decision] = await decision(
workingMemory,
{
instructions: "Decides the next step based on the hypothesis",
choices: ["interview suspect", "search crime scene", "check alibi"],
}
);
return decision;
}
Brainstorming
Similar to decision making which narrows effective context scope, brainstorming can expand scope. As opposed to choosing from a list of options, a new list of options is generated.
In this example, we ask a chef to consider a basket of ingredients, then brainstorm what dishes could be made.
async function makeDishSuggestions(ingredientsMemories: ChatMessage[]): Promise<string[]> {
// Initialize WorkingMemory with the initial memory of being a chef
let workingMemory = new WorkingMemory({
soulName: "Chef",
memories: [
{
role: ChatMessageRoleEnum.System,
content: "You are modeling the mind of a chef who is preparing a meal.",
},
],
})
// Add ingredients memories to the WorkingMemory
workingMemory = workingMemory.concat(ingredientsMemories)
// Consider the ingredients
[workingMemory] = await internalMonologue(workingMemory, "The chef considers the ingredients");
// Brainstorm meal ideas
const [, mealIdeas] = await brainstorm(
workingMemory,
"Brainstorm meal ideas based on the ingredients",
);
return mealIdeas;
}
Branching
Here's an example of a simplified internal monologue which makes a progressive sequence of branching decisions and while maintaining prior context.
import { WorkingMemory, ChatMessageRoleEnum } from "@opensouls/engine";
const initialMemories = [
{
role: ChatMessageRoleEnum.System,
content: "You are modeling the mind of a protagonist who is deciding on actions in a quest",
},
];
let workingMemory = new WorkingMemory({
soulName: "Protagonist",
memories: initialMemories,
});
let quest;
// The protagonist considers the quests
[workingMemory, quest] = await decision(workingMemory, {
instructions: "Protagonist considers the quests",
choices: ["slay dragon", "find artifact"],
});
if (quest === "slay dragon") {
// Branch 1: Slay the dragon
[workingMemory, quest] = await decision(workingMemory, {
instructions: "Protagonist decides how to prepare for the quest",
choices: ["gather weapons", "train skills"],
});
if (quest === "gather weapons") {
// implement gather tooling for character
} else {
// implement training tooling for character
}
} else {
// Branch 2: Find the artifact
[workingMemory, quest] = await decision(workingMemory, {
instructions: "Protagonist decides how to find the artifact",
choices: ["search old records", "ask elders"],
});
if (quest === "search old records") {
// search for clues about the artifact
} else {
// ask the elders about the artifact
}
}
console.log(quest);