Add initial project setup with environment variables, server logic, and memory handling
This commit is contained in:
@@ -30,18 +30,14 @@ const corsHeaders = {
|
||||
/*
|
||||
Stage 2: Process Input (Extract Memories)
|
||||
*/
|
||||
async function extractMemories(controller, messages, doc, relevantMemories?) {
|
||||
async function extractMemories(controller, messages, doc, user: User, allTags, relevantMemories?) {
|
||||
const startTime = Date.now();
|
||||
let addedCount = 0;
|
||||
let updatedCount = 0;
|
||||
let deletedCount = 0;
|
||||
const extractedMemories = [];
|
||||
|
||||
// Fetch existing memory tags from the database, that belong to the user
|
||||
const user : User = (await supabaseClient.auth.getUser()).data.user;
|
||||
|
||||
const tags = await supabaseClient
|
||||
.schema("mori")
|
||||
.from("tags")
|
||||
.select("*")
|
||||
.eq("user_id", user.id);
|
||||
|
||||
console.log("Fetched existing tags for user:", tags.data?.length || 0);
|
||||
console.log("Using cached tags for user:", allTags?.length || 0);
|
||||
|
||||
// Create and call OpenAI to process the input messages
|
||||
console.log("Creating OpenAI client for processing input");
|
||||
@@ -53,7 +49,7 @@ async function extractMemories(controller, messages, doc, relevantMemories?) {
|
||||
|
||||
console.log("Calling OpenAI API for processing...");
|
||||
const response = await openai.chat.completions.create({
|
||||
model: 'gpt-4.1-mini',
|
||||
model: 'gpt-4.1',
|
||||
temperature: 0.1,
|
||||
max_completion_tokens: 20000,
|
||||
messages: [
|
||||
@@ -63,7 +59,7 @@ async function extractMemories(controller, messages, doc, relevantMemories?) {
|
||||
role: "assistant",
|
||||
content: `I have access to the following reference data:
|
||||
|
||||
Available tags: ${JSON.stringify(tags.data?.map(t => t.name) || [])}
|
||||
Available tags: ${JSON.stringify(allTags?.map(t => t.name) || [])}
|
||||
|
||||
Existing memories: ${JSON.stringify(relevantMemories || [])}
|
||||
|
||||
@@ -88,50 +84,40 @@ Now I will analyze the conversation above and extract memories.`
|
||||
for (const change of processedData.changes || []) {
|
||||
|
||||
if (change.action === "ADD") {
|
||||
// First, fetch the tag rows that already exist
|
||||
let tags = [];
|
||||
for (const tagName of change.tags) {
|
||||
addedCount++;
|
||||
extractedMemories.push({
|
||||
action: 'ADD',
|
||||
content: change.content,
|
||||
context: change.context,
|
||||
tags: change.tags
|
||||
});
|
||||
// Fetch all existing tags in a single query using .in()
|
||||
const { data: existingTags } = await supabaseClient
|
||||
.schema("mori")
|
||||
.from("tags")
|
||||
.select("*")
|
||||
.in("name", change.tags)
|
||||
.eq("user_id", user.id);
|
||||
|
||||
const tagRow = await supabaseClient
|
||||
let tags = existingTags || [];
|
||||
|
||||
// Find tags that need to be created
|
||||
const existingTagNames = new Set(tags.map(t => t.name));
|
||||
const newTagNames = change.tags.filter(tagName => !existingTagNames.has(tagName));
|
||||
|
||||
// Batch insert all new tags in a single query
|
||||
if (newTagNames.length > 0) {
|
||||
const { data: insertedTags } = await supabaseClient
|
||||
.schema("mori")
|
||||
.from("tags")
|
||||
.select("*")
|
||||
.eq("name", tagName)
|
||||
.single();
|
||||
|
||||
if (tagRow.data) {
|
||||
tags.push(tagRow.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert any tags that do not already exist into the database
|
||||
for (const tagName of change.tags) {
|
||||
|
||||
// Ensure we don't duplicate tags
|
||||
let tagExists = false;
|
||||
for (const tag of tags) {
|
||||
if (tag.name === tagName) {
|
||||
tagExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tagExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const insertTag = await supabaseClient
|
||||
.schema("mori")
|
||||
.from("tags")
|
||||
.insert([{
|
||||
name: tagName,
|
||||
.insert(newTagNames.map(name => ({
|
||||
name: name,
|
||||
user_id: user.id
|
||||
}])
|
||||
.select()
|
||||
.single();
|
||||
})))
|
||||
.select();
|
||||
|
||||
if (insertTag.data) {
|
||||
tags.push(insertTag.data);
|
||||
if (insertedTags) {
|
||||
tags.push(...insertedTags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,18 +133,25 @@ Now I will analyze the conversation above and extract memories.`
|
||||
.select()
|
||||
.single();
|
||||
|
||||
// Now, link the tags to the memory in the memory_tags table
|
||||
for (const tag of tags) {
|
||||
// Batch insert all memory_tags links in a single query
|
||||
if (tags.length > 0 && insertMemory.data) {
|
||||
await supabaseClient
|
||||
.schema("mori")
|
||||
.from("memory_tags")
|
||||
.insert([{
|
||||
.insert(tags.map(tag => ({
|
||||
memory_id: insertMemory.data.id,
|
||||
tag_id: tag.id
|
||||
}]);
|
||||
})));
|
||||
}
|
||||
|
||||
} else if (change.action === "UPDATE") {
|
||||
updatedCount++;
|
||||
extractedMemories.push({
|
||||
action: 'UPDATE',
|
||||
content: change.content,
|
||||
context: change.context,
|
||||
memory_id: change.memory_id
|
||||
});
|
||||
// Update existing memory
|
||||
await supabaseClient
|
||||
.schema("mori")
|
||||
@@ -175,6 +168,11 @@ Now I will analyze the conversation above and extract memories.`
|
||||
// (delete old memory_tags links and create new ones)
|
||||
|
||||
} else if (change.action === "DELETE") {
|
||||
deletedCount++;
|
||||
extractedMemories.push({
|
||||
action: 'DELETE',
|
||||
memory_id: change.memory_id
|
||||
});
|
||||
// Delete memory (cascade should handle memory_tags)
|
||||
await supabaseClient
|
||||
.schema("mori")
|
||||
@@ -185,15 +183,22 @@ Now I will analyze the conversation above and extract memories.`
|
||||
}
|
||||
}
|
||||
|
||||
const processTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
extractedMemories,
|
||||
addedCount,
|
||||
updatedCount,
|
||||
deletedCount,
|
||||
processTime
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
Stage 1: Fetch Relevant Memories.
|
||||
Stage 1: Fetch Relevant Memories and Tags.
|
||||
*/
|
||||
async function fetchRelevantMemories(controller, messages, doc) {
|
||||
|
||||
// Fetch existing memory tags from the database, that belong to the user
|
||||
const user : User = (await supabaseClient.auth.getUser()).data.user;
|
||||
async function fetchRelevantMemories(controller, messages, doc, user: User) {
|
||||
const startTime = Date.now();
|
||||
|
||||
const tags = await supabaseClient
|
||||
.schema("mori")
|
||||
@@ -213,7 +218,7 @@ async function fetchRelevantMemories(controller, messages, doc) {
|
||||
|
||||
console.log("Calling OpenAI API for fetching relevant memories...");
|
||||
const response = await openai.chat.completions.create({
|
||||
model: 'gpt-4.1-mini',
|
||||
model: 'gpt-4.1',
|
||||
messages: [
|
||||
{ role: 'system', content: system_prompt },
|
||||
...messages,
|
||||
@@ -240,16 +245,20 @@ async function fetchRelevantMemories(controller, messages, doc) {
|
||||
p_user_id: user.id
|
||||
});
|
||||
|
||||
return relevantMemories;
|
||||
const fetchTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
relevantMemories,
|
||||
allTags: tags.data,
|
||||
selectedTags: relevantMemoryTagsParsed.selected_tags || [],
|
||||
fetchTime
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
Stage 3: Generate Response
|
||||
*/
|
||||
async function generateResponse(controller, messages, doc, relevantMemories) {
|
||||
|
||||
// Fetch existing memory tags from the database, that belong to the user
|
||||
const user : User = (await supabaseClient.auth.getUser()).data.user;
|
||||
async function generateResponse(controller, messages, doc, user: User, pipelineContext) {
|
||||
|
||||
console.log("Creating OpenAI client for generating a response");
|
||||
const openai = new OpenAI({
|
||||
@@ -265,14 +274,51 @@ async function generateResponse(controller, messages, doc, relevantMemories) {
|
||||
{ role: 'system', content: system_prompt },
|
||||
];
|
||||
|
||||
// Add relevant memories as context if available
|
||||
// Build pipeline awareness context
|
||||
const { relevantMemories, selectedTags, extractedMemories, addedCount, updatedCount, deletedCount } = pipelineContext;
|
||||
|
||||
let pipelineAwareness = `[Internal System Awareness - Not Part of Conversation]\n\n`;
|
||||
pipelineAwareness += `You are Mori, and you have a memory system that automatically remembers important information about ${user.user_metadata.username || 'the user'} across conversations.\n\n`;
|
||||
|
||||
// Info about retrieved memories
|
||||
if (relevantMemories && relevantMemories.length > 0) {
|
||||
responseMessages.push({
|
||||
role: 'assistant',
|
||||
content: `Context from previous conversations:\n${relevantMemories.map(m => `- ${m.content}`).join('\n')}\n\nI'll use this context naturally in our conversation.`
|
||||
pipelineAwareness += `RETRIEVED MEMORIES (what you already knew):\n`;
|
||||
pipelineAwareness += `You searched through memories using topics: ${selectedTags.join(', ')}\n`;
|
||||
pipelineAwareness += `Found ${relevantMemories.length} relevant memories:\n`;
|
||||
relevantMemories.forEach(m => {
|
||||
pipelineAwareness += `• ${m.content}\n`;
|
||||
});
|
||||
pipelineAwareness += `\n`;
|
||||
} else {
|
||||
pipelineAwareness += `No previous memories were retrieved for this conversation.\n\n`;
|
||||
}
|
||||
|
||||
// Info about newly extracted memories
|
||||
if (extractedMemories && extractedMemories.length > 0) {
|
||||
pipelineAwareness += `NEW MEMORIES (what you just learned and saved):\n`;
|
||||
extractedMemories.forEach(mem => {
|
||||
if (mem.action === 'ADD') {
|
||||
pipelineAwareness += `• Learned: ${mem.content}\n`;
|
||||
} else if (mem.action === 'UPDATE') {
|
||||
pipelineAwareness += `• Updated: ${mem.content}\n`;
|
||||
}
|
||||
});
|
||||
pipelineAwareness += `\n`;
|
||||
}
|
||||
|
||||
pipelineAwareness += `HOW TO USE THIS:\n`;
|
||||
pipelineAwareness += `- This awareness is internal. Don't report it.\n`;
|
||||
pipelineAwareness += `- Let it naturally inform your response\n`;
|
||||
pipelineAwareness += `- If the user explicitly asks you to remember something, you can acknowledge it naturally (e.g., "got it" or "I'll remember that")\n`;
|
||||
pipelineAwareness += `- Reference past memories naturally without saying "I retrieved" or "according to my memory"\n`;
|
||||
pipelineAwareness += `- You're a companion who pays attention, not a system reporting operations\n`;
|
||||
|
||||
// Inject pipeline awareness as assistant message
|
||||
responseMessages.push({
|
||||
role: 'assistant',
|
||||
content: pipelineAwareness
|
||||
});
|
||||
|
||||
responseMessages.push(...messages);
|
||||
|
||||
const stream = await openai.chat.completions.create({
|
||||
@@ -355,7 +401,7 @@ serve(async (req)=>{
|
||||
const stageFetchingData = `data: ${JSON.stringify({ type: 'stage', stage: 'fetching' })}\n\n`;
|
||||
controller.enqueue(new TextEncoder().encode(stageFetchingData));
|
||||
|
||||
const relevantMemories = await fetchRelevantMemories(controller, messages, doc);
|
||||
const { relevantMemories, allTags, selectedTags, fetchTime } = await fetchRelevantMemories(controller, messages, doc, user.data.user);
|
||||
|
||||
/*
|
||||
Stage 2: Extract Relevant Memories
|
||||
@@ -363,7 +409,7 @@ serve(async (req)=>{
|
||||
const stageProcessingData = `data: ${JSON.stringify({ type: 'stage', stage: 'processing' })}\n\n`;
|
||||
controller.enqueue(new TextEncoder().encode(stageProcessingData));
|
||||
|
||||
await extractMemories(controller, messages, doc, relevantMemories);
|
||||
const { extractedMemories, addedCount, updatedCount, deletedCount, processTime } = await extractMemories(controller, messages, doc, user.data.user, allTags, relevantMemories);
|
||||
|
||||
/*
|
||||
Stage 3: Stream the response back to the client
|
||||
@@ -371,7 +417,19 @@ serve(async (req)=>{
|
||||
const stageRespondingData = `data: ${JSON.stringify({ type: 'stage', stage: 'responding' })}\n\n`;
|
||||
controller.enqueue(new TextEncoder().encode(stageRespondingData));
|
||||
|
||||
await generateResponse(controller, messages, doc, relevantMemories);
|
||||
// Build complete pipeline context for Mori's awareness
|
||||
const pipelineContext = {
|
||||
relevantMemories,
|
||||
selectedTags,
|
||||
fetchTime,
|
||||
extractedMemories,
|
||||
addedCount,
|
||||
updatedCount,
|
||||
deletedCount,
|
||||
processTime
|
||||
};
|
||||
|
||||
await generateResponse(controller, messages, doc, user.data.user, pipelineContext);
|
||||
|
||||
// Send stage update: complete
|
||||
const completeData = `data: ${JSON.stringify({ type: 'stage', stage: 'complete' })}\n\n`;
|
||||
|
||||
Reference in New Issue
Block a user