contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
218 lines (217 loc) • 7.77 kB
JavaScript
import { FileService } from '../services/fileService.js';
import crypto from 'crypto';
/**
* Local knowledge base for storing and retrieving project-specific learning
*/
export class LocalKnowledgeBase {
constructor() {
this.knowledgeDir = './.contaigents/knowledge';
this.knowledgeCache = new Map();
this.fileService = new FileService();
}
/**
* Store new knowledge
*/
async storeKnowledge(type, content, source, confidence = 0.8) {
const knowledge = {
id: crypto.randomUUID(),
type,
content,
confidence,
usage: 0,
lastUsed: Date.now(),
source
};
await this.saveKnowledge(knowledge);
this.invalidateCache();
return knowledge.id;
}
/**
* Retrieve knowledge by type
*/
async getKnowledgeByType(type) {
const allKnowledge = await this.loadAllKnowledge();
return allKnowledge.filter(k => k.type === type);
}
/**
* Search knowledge by content
*/
async searchKnowledge(query, limit = 10) {
const allKnowledge = await this.loadAllKnowledge();
const queryLower = query.toLowerCase();
return allKnowledge
.filter(k => k.content.toLowerCase().includes(queryLower))
.sort((a, b) => b.confidence * b.usage - a.confidence * a.usage)
.slice(0, limit);
}
/**
* Get relevant knowledge for context
*/
async getRelevantKnowledge(context, limit = 5) {
const allKnowledge = await this.loadAllKnowledge();
const projectType = context.metadata.type;
// Score knowledge based on relevance to current context
const scoredKnowledge = allKnowledge.map(k => ({
knowledge: k,
score: this.calculateRelevanceScore(k, context)
}));
return scoredKnowledge
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map(item => item.knowledge);
}
/**
* Update knowledge usage
*/
async useKnowledge(knowledgeId) {
const knowledge = await this.getKnowledgeById(knowledgeId);
if (knowledge) {
knowledge.usage++;
knowledge.lastUsed = Date.now();
await this.saveKnowledge(knowledge);
this.invalidateCache();
}
}
/**
* Update knowledge confidence
*/
async updateConfidence(knowledgeId, newConfidence) {
const knowledge = await this.getKnowledgeById(knowledgeId);
if (knowledge) {
knowledge.confidence = Math.max(0, Math.min(1, newConfidence));
await this.saveKnowledge(knowledge);
this.invalidateCache();
}
}
/**
* Learn from successful interactions
*/
async learnFromSuccess(action, context, result) {
const pattern = `Successful ${action} in ${context.metadata.type} project`;
const content = `Action: ${action}\nContext: ${JSON.stringify(context.metadata)}\nResult: ${JSON.stringify(result)}`;
await this.storeKnowledge('pattern', content, 'interaction', 0.7);
}
/**
* Learn from failures
*/
async learnFromFailure(action, context, error) {
const rule = `Avoid ${action} in ${context.metadata.type} project when ${error}`;
const content = `Failed Action: ${action}\nContext: ${JSON.stringify(context.metadata)}\nError: ${error}`;
await this.storeKnowledge('rule', content, 'error', 0.6);
}
/**
* Get knowledge statistics
*/
async getKnowledgeStats() {
const allKnowledge = await this.loadAllKnowledge();
const byType = {};
let totalConfidence = 0;
allKnowledge.forEach(k => {
byType[k.type] = (byType[k.type] || 0) + 1;
totalConfidence += k.confidence;
});
const mostUsed = allKnowledge
.sort((a, b) => b.usage - a.usage)
.slice(0, 5);
return {
total: allKnowledge.length,
byType,
averageConfidence: allKnowledge.length > 0 ? totalConfidence / allKnowledge.length : 0,
mostUsed
};
}
/**
* Clean up low-confidence, unused knowledge
*/
async cleanupKnowledge() {
const allKnowledge = await this.loadAllKnowledge();
const threshold = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days ago
const toRemove = allKnowledge.filter(k => k.confidence < 0.3 && k.usage < 2 && k.lastUsed < threshold);
for (const knowledge of toRemove) {
await this.deleteKnowledge(knowledge.id);
}
this.invalidateCache();
return toRemove.length;
}
/**
* Private helper methods
*/
async loadAllKnowledge() {
const cacheKey = 'all';
if (this.knowledgeCache.has(cacheKey)) {
return this.knowledgeCache.get(cacheKey);
}
try {
const tree = await this.fileService.getFileTree();
const knowledgeFiles = this.findKnowledgeFiles(tree);
const allKnowledge = [];
for (const file of knowledgeFiles) {
try {
const content = await this.fileService.readFile(file.path);
const knowledge = JSON.parse(content);
allKnowledge.push(knowledge);
}
catch (error) {
console.warn(`Failed to load knowledge from ${file.path}`);
}
}
this.knowledgeCache.set(cacheKey, allKnowledge);
return allKnowledge;
}
catch (error) {
console.error('Failed to load knowledge base:', error);
return [];
}
}
async getKnowledgeById(id) {
const allKnowledge = await this.loadAllKnowledge();
return allKnowledge.find(k => k.id === id) || null;
}
async saveKnowledge(knowledge) {
const filePath = `${this.knowledgeDir}/${knowledge.id}.json`;
await this.fileService.saveFile({
path: filePath,
content: JSON.stringify(knowledge, null, 2)
});
}
async deleteKnowledge(id) {
// Note: FileService doesn't have delete method, would need to implement
console.log(`Would delete knowledge ${id}`);
}
findKnowledgeFiles(tree) {
const files = [];
const traverse = (node) => {
if (node.type === 'file' && node.path.includes('.contaigents/knowledge') && node.path.endsWith('.json')) {
files.push({ path: node.path });
}
if (node.children) {
node.children.forEach(traverse);
}
};
if (Array.isArray(tree)) {
tree.forEach(traverse);
}
else {
traverse(tree);
}
return files;
}
calculateRelevanceScore(knowledge, context) {
let score = knowledge.confidence * 0.4 + (knowledge.usage / 10) * 0.3;
// Boost score if knowledge content matches project context
const contextStr = JSON.stringify(context.metadata).toLowerCase();
const knowledgeStr = knowledge.content.toLowerCase();
if (knowledgeStr.includes(context.metadata.type))
score += 0.2;
if (context.metadata.targetAudience?.some(audience => knowledgeStr.includes(audience)))
score += 0.1;
// Penalize very old knowledge
const daysSinceUsed = (Date.now() - knowledge.lastUsed) / (24 * 60 * 60 * 1000);
if (daysSinceUsed > 30)
score *= 0.8;
return Math.min(1, score);
}
invalidateCache() {
this.knowledgeCache.clear();
}
}