widgetdc-cortex / apps /backend /src /services /KnowledgeAcquisitionService.ts
Kraft102's picture
Initial deployment - WidgeTDC Cortex Backend v2.1.0
529090e
import { neo4jService } from '../database/Neo4jService.js';
import { neuralStream, NeuralEvent } from './NeuralStream.js';
import { metricsService } from './MetricsService.js';
export interface KnowledgePacket {
title: string;
content: string;
source: string;
category: string;
tags: string[];
}
class KnowledgeAcquisitionService {
private static instance: KnowledgeAcquisitionService;
private constructor() {
console.log('🧠 [KnowledgeAcquisition] Cortex Ingestor Online.');
this.setupNeuralListeners();
}
public static getInstance(): KnowledgeAcquisitionService {
if (!KnowledgeAcquisitionService.instance) {
KnowledgeAcquisitionService.instance = new KnowledgeAcquisitionService();
}
return KnowledgeAcquisitionService.instance;
}
/**
* Lytter på nervesystemet efter ny viden fra OmniHarvester eller andre agenter
*/
private setupNeuralListeners() {
// Når OmniHarvester har fundet noget (SYSTEM_HEALED med context 'ActiveLearning')
// eller når vi manuelt pusher viden.
neuralStream.on('KNOWLEDGE_INGEST_REQ', async (event: NeuralEvent) => {
console.log('📥 [Acquisition] Receiving knowledge stream...', event.payload.title);
await this.ingestKnowledge(event.payload as unknown as KnowledgePacket);
});
}
/**
* KERNE-LOGIK: Konverterer rå tekst til Graf-Struktur
* Dette er "Memory Consolidation" processen.
*/
public async ingestKnowledge(packet: KnowledgePacket): Promise<boolean> {
const startTime = Date.now();
try {
// 1. Opret selve videns-noden (Fact / Document)
// Vi bruger MERGE for at undgå duplikater baseret på titel/source
const cypher = `
MERGE (k:Knowledge {title: $title})
SET k.content = $content,
k.source = $source,
k.category = $category,
k.ingestedAt = datetime(),
k.hash = $hash
// 2. Opret kategori-struktur
MERGE (c:Category {name: $category})
MERGE (k)-[:BELONGS_TO]->(c)
// 3. Auto-Tagging (Opretter tags og linker dem)
FOREACH (tagName IN $tags |
MERGE (t:Tag {name: tagName})
MERGE (k)-[:TAGGED_WITH]->(t)
)
`;
// Simpel hash for integritet
const hash = Buffer.from(packet.title + packet.source).toString('base64');
await neo4jService.runQuery(cypher, {
title: packet.title,
content: packet.content,
source: packet.source,
category: packet.category || 'General',
tags: packet.tags || [],
hash
});
// 4. THE MAGIC: Semantic Auto-Linking
// Vi søger efter eksisterende noder, der nævnes i den nye tekst, og linker dem.
await this.createSemanticLinks(packet.title, packet.content);
// 5. Metrics & Feedback
const duration = Date.now() - startTime;
metricsService.incrementCounter('knowledge_ingested');
console.log(`✅ [Acquisition] Absorbed: "${packet.title}" in ${duration}ms`);
neuralStream.emitEvent('SYSTEM_HEALED', 'LOW', {
action: 'MEMORY_CONSOLIDATED',
target: packet.title
}, 'KnowledgeAcquisition');
return true;
} catch (error) {
console.error('❌ [Acquisition] Failed to ingest:', error);
return false;
}
}
/**
* Finder andre noder i grafen, der nævnes i denne tekst, og skaber relationer.
* Dette gør grafen "tættere" og klogere over tid.
*/
private async createSemanticLinks(nodeTitle: string, content: string) {
// Find noder (Personer, Tech, Threats) hvis navne optræder i den nye tekst
// Undgå at linke til sig selv
const linkCypher = `
MATCH (k:Knowledge {title: $title})
MATCH (other)
WHERE other <> k
AND (other:Person OR other:Technology OR other:Threat OR other:Organization)
AND size(other.name) > 3 // Ignorer støj
AND $content CONTAINS other.name
MERGE (k)-[:MENTIONS]->(other)
RETURN count(other) as links
`;
const result = await neo4jService.runQuery(linkCypher, {
title: nodeTitle,
content: content
});
const linksCreated = (result[0]?.links as any)?.toNumber?.() || 0;
if (linksCreated > 0) {
console.log(`🔗 [Acquisition] Auto-linked "${nodeTitle}" to ${linksCreated} existing concepts.`);
}
}
// Stubs for backward compatibility
public async acquire(params: any): Promise<any> { return { success: false, message: 'Deprecated' }; }
public async batchAcquire(params: any): Promise<any> { return []; }
public async semanticSearch(query: string, limit: number): Promise<any> { return []; }
public async getVectorStats(): Promise<any> { return { totalRecords: 0 }; }
public async acquireFromTargets(ids?: string[]): Promise<any> { return []; }
public async acquireSingleTarget(id: string): Promise<any> { return null; }
}
export const knowledgeAcquisition = KnowledgeAcquisitionService.getInstance();