anya/tools/memory-manager.ts

221 lines
5.9 KiB
TypeScript
Raw Normal View History

2024-10-06 13:56:33 +05:30
import { z } from "zod";
import fs from "fs";
import { randomUUID } from "crypto";
import { Message } from "../interfaces/message";
import { zodFunction } from ".";
import { ask } from "./ask";
import { pathInDataDir } from "../config";
export const CreateMemorySchema = z.object({
memory: z.string(),
});
export type CreateMemory = z.infer<typeof CreateMemorySchema>;
export const UpdateMemorySchema = z.object({
id: z.string(),
memory: z.string(),
});
export type UpdateMemory = z.infer<typeof UpdateMemorySchema>;
export const DeleteMemorySchema = z.object({
id: z.string(),
});
export type DeleteMemory = z.infer<typeof DeleteMemorySchema>;
const memory_path = pathInDataDir("memories.json");
type Memories = Record<
string,
{
id: string;
memory: string;
created_at: string;
updated_at: string;
}[]
>;
// if the file doesn't exist, create it
if (!fs.existsSync(memory_path)) {
fs.writeFileSync(memory_path, "{}");
}
function getMemories(): Memories {
return JSON.parse(fs.readFileSync(memory_path, "utf-8"));
}
export function getMemoriesByManager(manager_id: string) {
return getMemories()[manager_id] || [];
}
function saveMemories(memories: Memories) {
fs.writeFileSync(memory_path, JSON.stringify(memories, null, 2));
}
export function createMemory(params: CreateMemory, manager_id: string) {
try {
const memories = getMemories();
memories[manager_id] = memories[manager_id] || [];
if (memories[manager_id].length >= 5) {
return { error: "You have reached the limit of memories." };
}
const uuid = randomUUID();
const start = Math.floor(Math.random() * (uuid.length - 4));
const new_mem = {
id: uuid.slice(start, start + 4),
memory: params.memory,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
memories[manager_id].push(new_mem);
saveMemories(memories);
return { id: new_mem.id };
} catch (error) {
return { error };
}
}
export function updateMemory(params: UpdateMemory, manager_id: string) {
try {
const memories = getMemories();
memories[manager_id] = memories[manager_id] || [];
const memory = memories[manager_id].find((m) => m.id === params.id);
if (!memory) {
return { error: "Memory not found" };
}
memory.memory = params.memory;
memory.updated_at = new Date().toISOString();
saveMemories(memories);
return {};
} catch (error) {
return { error };
}
}
export function deleteMemory(params: DeleteMemory, manager_id: string) {
try {
const memories = getMemories();
memories[manager_id] = memories[manager_id] || [];
memories[manager_id] = memories[manager_id].filter(
(m) => m.id !== params.id
);
saveMemories(memories);
return {};
} catch (error) {
return { error };
}
}
export const memory_tools = (manager_id: string) => [
zodFunction({
function: (args) => createMemory(args, manager_id),
name: "create_memory",
schema: CreateMemorySchema,
description: "Create a memory.",
}),
zodFunction({
function: (args) => updateMemory(args, manager_id),
name: "update_memory",
schema: UpdateMemorySchema,
description: "Update a memory.",
}),
zodFunction({
function: (args) => deleteMemory(args, manager_id),
name: "delete_memory",
schema: DeleteMemorySchema,
description: "Delete a memory.",
}),
];
const MemoryManagerSchema = z.object({
request: z.string(),
});
export type MemoryManager = z.infer<typeof MemoryManagerSchema>;
async function memoryManager(
params: MemoryManager,
context_message: Message,
manager_id: string
) {
try {
const current_memories = getMemories()[manager_id] || [];
const tools = memory_tools(manager_id);
const response = await ask({
model: "gpt-4o",
prompt: `You are a Memories Manager.
You manage memories for other managers.
Help the manager with their request based on the information provided.
- **Priority:** Only store useful and detailed memories.
- If the request is not useful or lacks detail, ask for more information or deny the request.
- When a manager reaches the memory limit, ask them to choose memories to delete. Ensure they inform their user about the deletion and confirm before proceeding.
- Ensure the memories are relevant to the requesting manager.
- Return the ID of any memory you save so the manager can refer to it later.
**Current Manager:** ${manager_id}
**Their Memories:**
${JSON.stringify(current_memories)}
`,
message: params.request,
name: manager_id,
seed: `memory-man-${manager_id}-${
context_message.author.username ?? context_message.author.id
}`,
tools,
});
return {
response: response.choices[0].message.content,
};
} catch (error) {
return { error };
}
}
export const memory_manager_init = (
context_message: Message,
manager_id: string
) => {
return zodFunction({
function: (args) => memoryManager(args, context_message, manager_id),
name: "memory_manager",
schema: MemoryManagerSchema,
description:
`Manages memories for a manager or yourself.
- Memories are isolated per manager; managers can't access each other's memories.
- **Use Cases:**
- Remembering important user preferences.
- Anything you want to recall later.
**Examples:**
- "Remember to use \`home_assistant_manager\` when the user asks to set text on a widget."
- "Remember that the user mainly cares about the P4 system service status."
Memories are limited and costly; use them wisely.
` +
manager_id ===
"self"
? `### Imporant Note
Make sure you only use this for your own memories and not for other memories that you can tell other managers to remember.
`
: "",
});
};
export const memory_manager_guide = (
manager_id: string
) => `# Memories Saved for You
${JSON.stringify(getMemoriesByManager(manager_id), null, 2)}
You can store up to 5 memories at a time. Use them wisely.
`;