mindcraft/src/agent/history.js

119 lines
No EOL
4.1 KiB
JavaScript

import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
import { NPCData } from './npc/data.js';
import settings from '../../settings.js';
export class History {
constructor(agent) {
this.agent = agent;
this.name = agent.name;
this.memory_fp = `./bots/${this.name}/memory.json`;
this.full_history_fp = undefined;
mkdirSync(`./bots/${this.name}/histories`, { recursive: true });
this.turns = [];
// Natural language memory as a summary of recent messages + previous memory
this.memory = '';
// Maximum number of messages to keep in context before saving chunk to memory
this.max_messages = settings.max_messages;
// Number of messages to remove from current history and save into memory
this.summary_chunk_size = 5;
// chunking reduces expensive calls to promptMemSaving and appendFullHistory
// and improves the quality of the memory summary
}
getHistory() { // expects an Examples object
return JSON.parse(JSON.stringify(this.turns));
}
async summarizeMemories(turns) {
console.log("Storing memories...");
this.memory = await this.agent.prompter.promptMemSaving(turns);
if (this.memory.length > 500) {
this.memory = this.memory.slice(0, 500);
this.memory += '...(Memory truncated to 500 chars. Compress it more next time)';
}
console.log("Memory updated to: ", this.memory);
}
async appendFullHistory(to_store) {
if (this.full_history_fp === undefined) {
const string_timestamp = new Date().toLocaleString().replace(/[/:]/g, '-').replace(/ /g, '').replace(/,/g, '_');
this.full_history_fp = `./bots/${this.name}/histories/${string_timestamp}.json`;
writeFileSync(this.full_history_fp, '[]', 'utf8');
}
try {
const data = readFileSync(this.full_history_fp, 'utf8');
let full_history = JSON.parse(data);
full_history.push(...to_store);
writeFileSync(this.full_history_fp, JSON.stringify(full_history, null, 4), 'utf8');
} catch (err) {
console.error(`Error reading ${this.name}'s full history file: ${err.message}`);
}
}
async add(name, content) {
let role = 'assistant';
if (name === 'system') {
role = 'system';
}
else if (name !== this.name) {
role = 'user';
content = `${name}: ${content}`;
}
this.turns.push({role, content});
if (this.turns.length >= this.max_messages) {
let chunk = this.turns.splice(0, this.summary_chunk_size);
while (this.turns.length > 0 && this.turns[0].role === 'assistant')
chunk.push(this.turns.shift()); // remove until turns starts with system/user message
await this.summarizeMemories(chunk);
await this.appendFullHistory(chunk);
}
}
async save() {
try {
const data = {
memory: this.memory,
turns: this.turns,
self_prompt: this.agent.self_prompter.on ? this.agent.self_prompter.prompt : null,
last_sender: this.agent.last_sender
};
writeFileSync(this.memory_fp, JSON.stringify(data, null, 2));
console.log('Saved memory to:', this.memory_fp);
} catch (error) {
console.error('Failed to save history:', error);
throw error;
}
}
load() {
try {
if (!existsSync(this.memory_fp)) {
console.log('No memory file found.');
return null;
}
const data = JSON.parse(readFileSync(this.memory_fp, 'utf8'));
this.memory = data.memory || '';
this.turns = data.turns || [];
console.log('Loaded memory:', this.memory);
return data;
} catch (error) {
console.error('Failed to load history:', error);
throw error;
}
}
clear() {
this.turns = [];
this.memory = '';
}
}