feat(l4): upgrade engines with LLM-powered capabilities (Phase 2)

Phase 2 LLM Engine Upgrades:
- ReflectionEngine: Add LLM semantic analysis for pattern detection
- ContextCompactor: Add LLM summarization for high-quality compaction
- MemoryExtractor: Add LLM importance scoring for memory extraction
- Add unified LLM service adapter (OpenAI, Volcengine, Gateway, Mock)
- Add MemorySource 'llm-reflection' for LLM-generated memories
- Add 13 integration tests for LLM-powered features

Config options added:
- useLLM: Enable LLM mode for each engine
- llmProvider: Preferred LLM provider
- llmFallbackToRules: Fallback to rules if LLM fails

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-16 10:41:03 +08:00
parent ef3315db69
commit 0b89329e19
5 changed files with 599 additions and 16 deletions

View File

@@ -9,11 +9,20 @@
*
* Also handles auto-updating USER.md with discovered preferences.
*
* Phase 1: Rule-based extraction (pattern matching).
* Phase 4: LLM-powered semantic extraction with importance scoring.
*
* Reference: ZCLAW_AGENT_INTELLIGENCE_EVOLUTION.md §6.2.2
*/
import { getMemoryManager, type MemoryType } from './agent-memory';
import { getAgentIdentityManager } from './agent-identity';
import {
getLLMAdapter,
llmExtract,
type LLMServiceAdapter,
type LLMProvider,
} from './llm-service';
// === Types ===
@@ -36,6 +45,15 @@ export interface ConversationMessage {
content: string;
}
export interface ExtractionConfig {
useLLM: boolean; // Use LLM for semantic extraction (Phase 4)
llmProvider?: LLMProvider; // Preferred LLM provider
llmFallbackToRules: boolean; // Fall back to rules if LLM fails
minMessagesForExtraction: number; // Minimum messages before extraction
extractionCooldownMs: number; // Cooldown between extractions
minImportanceThreshold: number; // Only save items with importance >= this
}
// === Extraction Prompt ===
const EXTRACTION_PROMPT = `请从以下对话中提取值得长期记住的信息。
@@ -59,38 +77,80 @@ const EXTRACTION_PROMPT = `请从以下对话中提取值得长期记住的信
对话内容:
`;
// === Default Config ===
export const DEFAULT_EXTRACTION_CONFIG: ExtractionConfig = {
useLLM: false,
llmFallbackToRules: true,
minMessagesForExtraction: 4,
extractionCooldownMs: 30_000,
minImportanceThreshold: 3,
};
// === Memory Extractor ===
export class MemoryExtractor {
private minMessagesForExtraction = 4;
private extractionCooldownMs = 30_000; // 30 seconds between extractions
private config: ExtractionConfig;
private lastExtractionTime = 0;
private llmAdapter: LLMServiceAdapter | null = null;
constructor(config?: Partial<ExtractionConfig>) {
this.config = { ...DEFAULT_EXTRACTION_CONFIG, ...config };
// Initialize LLM adapter if configured
if (this.config.useLLM) {
try {
this.llmAdapter = getLLMAdapter();
} catch (error) {
console.warn('[MemoryExtractor] Failed to initialize LLM adapter:', error);
}
}
}
/**
* Extract memories from a conversation using rule-based heuristics.
* This is the Phase 1 approach — no LLM call needed.
* Phase 2 will add LLM-based extraction using EXTRACTION_PROMPT.
* Extract memories from a conversation.
* Uses LLM if configured, falls back to rule-based extraction.
*/
async extractFromConversation(
messages: ConversationMessage[],
agentId: string,
conversationId?: string
conversationId?: string,
options?: { forceLLM?: boolean }
): Promise<ExtractionResult> {
// Cooldown check
if (Date.now() - this.lastExtractionTime < this.extractionCooldownMs) {
if (Date.now() - this.lastExtractionTime < this.config.extractionCooldownMs) {
return { items: [], saved: 0, skipped: 0, userProfileUpdated: false };
}
// Minimum message threshold
const chatMessages = messages.filter(m => m.role === 'user' || m.role === 'assistant');
if (chatMessages.length < this.minMessagesForExtraction) {
if (chatMessages.length < this.config.minMessagesForExtraction) {
return { items: [], saved: 0, skipped: 0, userProfileUpdated: false };
}
this.lastExtractionTime = Date.now();
// Phase 1: Rule-based extraction (pattern matching)
const extracted = this.ruleBasedExtraction(chatMessages);
// Try LLM extraction if enabled
let extracted: ExtractedItem[];
if ((this.config.useLLM || options?.forceLLM) && this.llmAdapter?.isAvailable()) {
try {
console.log('[MemoryExtractor] Using LLM-powered semantic extraction');
extracted = await this.llmBasedExtraction(chatMessages);
} catch (error) {
console.error('[MemoryExtractor] LLM extraction failed:', error);
if (!this.config.llmFallbackToRules) {
throw error;
}
console.log('[MemoryExtractor] Falling back to rule-based extraction');
extracted = this.ruleBasedExtraction(chatMessages);
}
} else {
// Rule-based extraction
extracted = this.ruleBasedExtraction(chatMessages);
}
// Filter by importance threshold
extracted = extracted.filter(item => item.importance >= this.config.minImportanceThreshold);
// Save to memory
const memoryManager = getMemoryManager();
@@ -135,6 +195,23 @@ export class MemoryExtractor {
return { items: extracted, saved, skipped, userProfileUpdated };
}
/**
* LLM-powered semantic extraction.
* Uses LLM to understand context and score importance semantically.
*/
private async llmBasedExtraction(messages: ConversationMessage[]): Promise<ExtractedItem[]> {
const conversationText = messages
.filter(m => m.role === 'user' || m.role === 'assistant')
.map(m => `[${m.role === 'user' ? '用户' : '助手'}]: ${m.content}`)
.join('\n\n');
// Use llmExtract helper from llm-service
const llmResponse = await llmExtract(conversationText, this.llmAdapter!);
// Parse the JSON response
return this.parseExtractionResponse(llmResponse);
}
/**
* Phase 1: Rule-based extraction using pattern matching.
* Extracts common patterns from user messages.