tasq/node_modules/@claude-flow/neural/dist/reasoningbank-adapter.js

463 lines
18 KiB
JavaScript

/**
* V3 ReasoningBank Adapter
*
* Provides agentic-flow@alpha compatible ReasoningBank interface:
* - 4-step pipeline: RETRIEVE, JUDGE, DISTILL, CONSOLIDATE
* - Trajectory tracking and verdict judgment
* - Memory distillation from successful trajectories
* - Pattern consolidation with deduplication and pruning
*
* Based on Algorithm 3 & 4 from ReasoningBank paper.
*
* Performance Targets:
* - Pattern retrieval: <5ms
* - Verdict judgment: <10ms
* - Memory distillation: <50ms
* - Consolidation: <100ms
*/
import { createSONAManager } from './sona-manager.js';
import { createPatternLearner } from './pattern-learner.js';
// ============================================================================
// ReasoningBank Adapter Implementation
// ============================================================================
export class ReasoningBankAdapter {
config;
sonaManager;
patternLearner;
patterns = new Map();
newPatternsSinceConsolidation = 0;
initialized = false;
constructor(config) {
this.config = {
dbPath: config?.dbPath || '.agentdb/reasoningbank.db',
enableLearning: config?.enableLearning ?? true,
enableReasoning: config?.enableReasoning ?? true,
sonaMode: config?.sonaMode || 'balanced',
duplicateThreshold: config?.duplicateThreshold ?? 0.95,
contradictionThreshold: config?.contradictionThreshold ?? 0.85,
pruneAgeDays: config?.pruneAgeDays ?? 30,
minConfidenceKeep: config?.minConfidenceKeep ?? 0.3,
consolidateTriggerThreshold: config?.consolidateTriggerThreshold ?? 100,
maxItemsSuccess: config?.maxItemsSuccess ?? 5,
maxItemsFailure: config?.maxItemsFailure ?? 3,
confidencePriorSuccess: config?.confidencePriorSuccess ?? 0.8,
confidencePriorFailure: config?.confidencePriorFailure ?? 0.5,
};
this.sonaManager = createSONAManager(this.config.sonaMode);
this.patternLearner = createPatternLearner({
maxPatterns: 5000,
matchThreshold: 0.7,
qualityThreshold: 0.5,
});
}
async initialize() {
if (this.initialized)
return;
await this.sonaManager.initialize();
this.initialized = true;
}
// ==========================================================================
// Step 1: RETRIEVE - Top-k memory injection with MMR diversity
// ==========================================================================
/**
* Retrieve relevant patterns for a query
*/
async retrieve(queryEmbedding, options) {
const k = options?.k ?? 5;
const domain = options?.domain;
const minConfidence = options?.minConfidence ?? 0;
const useMmr = options?.useMmr ?? true;
const mmrLambda = options?.mmrLambda ?? 0.7;
// Get all patterns, filter by domain and confidence
let candidates = Array.from(this.patterns.values());
if (domain) {
candidates = candidates.filter(p => p.domain === domain);
}
if (minConfidence > 0) {
candidates = candidates.filter(p => p.confidence >= minConfidence);
}
if (candidates.length === 0) {
return [];
}
// Compute similarities
const similarities = candidates.map(pattern => ({
pattern,
similarity: this.cosineSimilarity(queryEmbedding, pattern.embedding),
}));
if (!useMmr) {
// Simple top-k
return similarities
.sort((a, b) => b.similarity - a.similarity)
.slice(0, k)
.map(s => s.pattern);
}
// Maximal Marginal Relevance (MMR) for diversity
return this.mmrSelect(queryEmbedding, similarities, k, mmrLambda);
}
// ==========================================================================
// Step 2: JUDGE - LLM-as-judge trajectory evaluation
// ==========================================================================
/**
* Judge a trajectory's success
*/
async judge(trajectory) {
// Compute quality metrics
const qualityScore = trajectory.qualityScore;
const stepCount = trajectory.steps.length;
const avgReward = stepCount > 0
? trajectory.steps.reduce((sum, s) => sum + s.reward, 0) / stepCount
: 0;
// Determine verdict label
let label;
if (qualityScore >= 0.8 && avgReward >= 0.7) {
label = 'Success';
}
else if (qualityScore < 0.4 || avgReward < 0.3) {
label = 'Failure';
}
else {
label = 'Partial';
}
// Collect evidence
const evidence = [];
evidence.push(`Quality score: ${qualityScore.toFixed(2)}`);
evidence.push(`Average reward: ${avgReward.toFixed(2)}`);
evidence.push(`Step count: ${stepCount}`);
if (trajectory.steps.length > 0) {
const lastStep = trajectory.steps[trajectory.steps.length - 1];
evidence.push(`Final action: ${lastStep.action}`);
evidence.push(`Final reward: ${lastStep.reward.toFixed(2)}`);
}
// Generate reasoning
const reasoning = this.generateJudgmentReasoning(trajectory, label, evidence);
return {
label,
score: qualityScore,
evidence,
reasoning,
};
}
// ==========================================================================
// Step 3: DISTILL - Extract strategy memories from trajectories
// ==========================================================================
/**
* Distill memories from a judged trajectory
*/
async distill(trajectory, verdict, options) {
const maxItems = verdict.label === 'Success'
? this.config.maxItemsSuccess
: this.config.maxItemsFailure;
const confidencePrior = verdict.label === 'Success'
? this.config.confidencePriorSuccess
: this.config.confidencePriorFailure;
const memoryIds = [];
// Extract key patterns from trajectory
const patterns = this.extractPatternsFromTrajectory(trajectory, verdict);
for (let i = 0; i < Math.min(patterns.length, maxItems); i++) {
const pattern = patterns[i];
const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
const rbPattern = {
id,
type: 'reasoning_memory',
domain: trajectory.domain,
patternData: {
title: pattern.title,
description: pattern.description,
content: pattern.content,
source: {
taskId: options?.taskId || trajectory.trajectoryId,
agentId: options?.agentId || 'unknown',
outcome: verdict.label,
evidence: verdict.evidence,
},
tags: pattern.tags,
domain: trajectory.domain,
createdAt: new Date().toISOString(),
confidence: confidencePrior,
nUses: 0,
},
confidence: confidencePrior,
usageCount: 0,
embedding: this.computePatternEmbedding(trajectory, i),
createdAt: Date.now(),
lastUsed: Date.now(),
};
this.patterns.set(id, rbPattern);
this.newPatternsSinceConsolidation++;
memoryIds.push(id);
}
// Check if consolidation should run
if (this.shouldConsolidate()) {
await this.consolidate();
}
return memoryIds;
}
// ==========================================================================
// Step 4: CONSOLIDATE - Dedup, detect contradictions, prune old patterns
// ==========================================================================
/**
* Run consolidation: deduplicate, detect contradictions, prune
*/
async consolidate() {
const startTime = Date.now();
const patterns = Array.from(this.patterns.values());
let duplicatesFound = 0;
let contradictionsFound = 0;
let itemsPruned = 0;
// Step 1: Deduplicate similar patterns
const toRemove = new Set();
for (let i = 0; i < patterns.length; i++) {
if (toRemove.has(patterns[i].id))
continue;
for (let j = i + 1; j < patterns.length; j++) {
if (toRemove.has(patterns[j].id))
continue;
const similarity = this.cosineSimilarity(patterns[i].embedding, patterns[j].embedding);
if (similarity >= this.config.duplicateThreshold) {
duplicatesFound++;
// Keep the one with higher usage/confidence
const score1 = patterns[i].usageCount * patterns[i].confidence;
const score2 = patterns[j].usageCount * patterns[j].confidence;
if (score1 >= score2) {
toRemove.add(patterns[j].id);
}
else {
toRemove.add(patterns[i].id);
}
}
}
}
// Step 2: Detect contradictions (similar embeddings, different outcomes)
for (let i = 0; i < patterns.length; i++) {
if (toRemove.has(patterns[i].id))
continue;
for (let j = i + 1; j < patterns.length; j++) {
if (toRemove.has(patterns[j].id))
continue;
const similarity = this.cosineSimilarity(patterns[i].embedding, patterns[j].embedding);
const outcome1 = patterns[i].patternData.source.outcome;
const outcome2 = patterns[j].patternData.source.outcome;
if (similarity >= this.config.contradictionThreshold &&
outcome1 !== outcome2) {
contradictionsFound++;
// Log contradiction for analysis (don't auto-remove)
console.warn(`Contradiction detected: ${patterns[i].id} vs ${patterns[j].id}`);
}
}
}
// Step 3: Prune old, low-confidence patterns
const now = Date.now();
const maxAge = this.config.pruneAgeDays * 24 * 60 * 60 * 1000;
for (const pattern of patterns) {
if (toRemove.has(pattern.id))
continue;
const age = now - pattern.createdAt;
if (age > maxAge &&
pattern.confidence < this.config.minConfidenceKeep &&
pattern.usageCount < 3) {
toRemove.add(pattern.id);
itemsPruned++;
}
}
// Remove marked patterns
for (const id of toRemove) {
this.patterns.delete(id);
}
this.newPatternsSinceConsolidation = 0;
const durationMs = Date.now() - startTime;
return {
itemsProcessed: patterns.length,
duplicatesFound,
contradictionsFound,
itemsPruned,
durationMs,
};
}
// ==========================================================================
// Pattern Management
// ==========================================================================
/**
* Insert a pattern directly
*/
insertPattern(pattern) {
const fullPattern = {
...pattern,
createdAt: Date.now(),
lastUsed: Date.now(),
};
this.patterns.set(pattern.id, fullPattern);
this.newPatternsSinceConsolidation++;
return pattern.id;
}
/**
* Get a pattern by ID
*/
getPattern(id) {
const pattern = this.patterns.get(id);
if (pattern) {
pattern.lastUsed = Date.now();
pattern.usageCount++;
}
return pattern;
}
/**
* Update pattern confidence
*/
updateConfidence(id, delta) {
const pattern = this.patterns.get(id);
if (pattern) {
pattern.confidence = Math.max(0, Math.min(1, pattern.confidence + delta));
pattern.patternData.confidence = pattern.confidence;
}
}
/**
* Get statistics
*/
getStats() {
const patterns = Array.from(this.patterns.values());
const byDomain = {};
const byOutcome = {};
for (const pattern of patterns) {
byDomain[pattern.domain] = (byDomain[pattern.domain] || 0) + 1;
byOutcome[pattern.patternData.source.outcome] =
(byOutcome[pattern.patternData.source.outcome] || 0) + 1;
}
const avgConfidence = patterns.length > 0
? patterns.reduce((sum, p) => sum + p.confidence, 0) / patterns.length
: 0;
return {
totalPatterns: patterns.length,
byDomain,
byOutcome,
avgConfidence,
};
}
// ==========================================================================
// Private Helper Methods
// ==========================================================================
cosineSimilarity(a, b) {
if (a.length !== b.length)
return 0;
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
const denom = Math.sqrt(normA) * Math.sqrt(normB);
return denom > 0 ? dot / denom : 0;
}
mmrSelect(query, candidates, k, lambda) {
const selected = [];
const remaining = [...candidates];
while (selected.length < k && remaining.length > 0) {
let bestScore = -Infinity;
let bestIdx = 0;
for (let i = 0; i < remaining.length; i++) {
const candidate = remaining[i];
const relevance = candidate.similarity;
// Calculate max similarity to already selected patterns
let maxSimilarity = 0;
for (const sel of selected) {
const sim = this.cosineSimilarity(candidate.pattern.embedding, sel.embedding);
maxSimilarity = Math.max(maxSimilarity, sim);
}
// MMR score: lambda * relevance - (1 - lambda) * redundancy
const score = lambda * relevance - (1 - lambda) * maxSimilarity;
if (score > bestScore) {
bestScore = score;
bestIdx = i;
}
}
selected.push(remaining[bestIdx].pattern);
remaining.splice(bestIdx, 1);
}
return selected;
}
shouldConsolidate() {
return this.newPatternsSinceConsolidation >= this.config.consolidateTriggerThreshold;
}
generateJudgmentReasoning(trajectory, label, evidence) {
const parts = [];
parts.push(`Trajectory ${trajectory.trajectoryId} judged as ${label}.`);
parts.push(`Domain: ${trajectory.domain}, Steps: ${trajectory.steps.length}.`);
if (label === 'Success') {
parts.push('The trajectory achieved high quality scores with positive rewards.');
}
else if (label === 'Failure') {
parts.push('The trajectory had low quality scores or negative rewards.');
}
else {
parts.push('The trajectory showed mixed results with room for improvement.');
}
return parts.join(' ');
}
extractPatternsFromTrajectory(trajectory, verdict) {
const patterns = [];
// Extract overall strategy pattern
const actions = trajectory.steps.map(s => s.action);
patterns.push({
title: `${verdict.label}: ${trajectory.context.slice(0, 50)}`,
description: `Strategy for ${trajectory.domain} task with ${verdict.label.toLowerCase()} outcome`,
content: `Actions: ${actions.slice(0, 5).join(' -> ')}${actions.length > 5 ? '...' : ''}`,
tags: [verdict.label.toLowerCase(), trajectory.domain, 'strategy'],
});
// Extract key step patterns for successful trajectories
if (verdict.label === 'Success' && trajectory.steps.length > 0) {
const highRewardSteps = trajectory.steps
.filter(s => s.reward > 0.7)
.slice(0, 3);
for (const step of highRewardSteps) {
patterns.push({
title: `High-reward action: ${step.action.slice(0, 30)}`,
description: `Effective action in ${trajectory.domain} context`,
content: `Action: ${step.action}, Reward: ${step.reward.toFixed(2)}`,
tags: ['high-reward', trajectory.domain, 'action'],
});
}
}
return patterns;
}
computePatternEmbedding(trajectory, index) {
if (trajectory.steps.length === 0) {
return new Float32Array(768);
}
// Use weighted average of step embeddings
const dim = trajectory.steps[0].stateAfter.length;
const embedding = new Float32Array(dim);
let totalWeight = 0;
for (let i = 0; i < trajectory.steps.length; i++) {
const weight = (i + 1 + index) / (trajectory.steps.length + index);
totalWeight += weight;
for (let j = 0; j < dim; j++) {
embedding[j] += trajectory.steps[i].stateAfter[j] * weight;
}
}
for (let j = 0; j < dim; j++) {
embedding[j] /= totalWeight;
}
return embedding;
}
}
// ============================================================================
// Factory Functions
// ============================================================================
/**
* Create ReasoningBank adapter
*/
export function createReasoningBankAdapter(config) {
return new ReasoningBankAdapter(config);
}
/**
* Create default ReasoningBank adapter
*/
export function createDefaultReasoningBankAdapter() {
return new ReasoningBankAdapter({
enableLearning: true,
enableReasoning: true,
sonaMode: 'balanced',
});
}
//# sourceMappingURL=reasoningbank-adapter.js.map