Files
zclaw_openfang/desktop/src/lib/active-learning.ts
2026-03-17 23:26:16 +08:00

366 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 主动学习引擎 - 从用户交互中学习并改进 Agent 行为
*
* 提供学习事件记录、模式提取和建议生成功能。
* Phase 1: 内存存储Zustand 持久化
* Phase 2: SQLite + 向量化存储
*/
import {
type LearningEvent,
type LearningPattern,
type LearningSuggestion,
type LearningEventType,
type FeedbackSentiment,
} from '../types/active-learning';
// === 常量 ===
const MAX_EVENTS = 1000;
const PATTERN_CONFIDENCE_THRESHOLD = 0.7;
const SUGGESTION_COOLDOWN_HOURS = 2;
// === 生成 ID ===
function generateEventId(): string {
return `le-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
// === 分析反馈情感 ===
export function analyzeSentiment(text: string): FeedbackSentiment {
const positive = ['好的', '很棒', '谢谢', '完美', 'excellent', '喜欢', '爱了', 'good', 'great', 'nice', '满意'];
const negative = ['不好', '差', '糟糕', '错误', 'wrong', 'bad', '不喜欢', '讨厌', '问题', '失败', 'fail', 'error'];
const lowerText = text.toLowerCase();
if (positive.some(w => lowerText.includes(w.toLowerCase()))) return 'positive';
if (negative.some(w => lowerText.includes(w.toLowerCase()))) return 'negative';
return 'neutral';
}
// === 分析学习类型 ===
export function analyzeEventType(text: string): LearningEventType {
const lowerText = text.toLowerCase();
if (lowerText.includes('纠正') || lowerText.includes('不对') || lowerText.includes('修改')) {
return 'correction';
}
if (lowerText.includes('喜欢') || lowerText.includes('偏好') || lowerText.includes('风格')) {
return 'preference';
}
if (lowerText.includes('场景') || lowerText.includes('上下文') || lowerText.includes('情况')) {
return 'context';
}
if (lowerText.includes('总是') || lowerText.includes('经常') || lowerText.includes('习惯')) {
return 'behavior';
}
return 'implicit';
}
// === 推断偏好 ===
export function inferPreference(feedback: string, sentiment: FeedbackSentiment): string {
if (sentiment === 'positive') {
if (feedback.includes('简洁')) return '用户偏好简洁的回复';
if (feedback.includes('详细')) return '用户偏好详细的回复';
if (feedback.includes('快速')) return '用户偏好快速响应';
return '用户对当前回复风格满意';
}
if (sentiment === 'negative') {
if (feedback.includes('太长')) return '用户偏好更短的回复';
if (feedback.includes('太短')) return '用户偏好更详细的回复';
if (feedback.includes('不准确')) return '用户偏好更准确的信息';
return '用户对当前回复风格不满意';
}
return '用户反馈中性';
}
// === 学习引擎类 ===
export class ActiveLearningEngine {
private events: LearningEvent[] = [];
private patterns: LearningPattern[] = [];
// Reserved for future learning suggestions feature
private suggestions: LearningSuggestion[] = [];
private initialized: boolean = false;
constructor() {
this.initialized = true;
}
/** Get current suggestions (reserved for future use) */
getSuggestions(): LearningSuggestion[] {
return this.suggestions;
}
/** Check if engine is initialized */
isInitialized(): boolean {
return this.initialized;
}
/**
* 记录学习事件
*/
recordEvent(
event: Omit<LearningEvent, 'id' | 'timestamp' | 'acknowledged' | 'appliedCount'>
): LearningEvent {
// 检查重复事件
const existing = this.events.find(e =>
e.agentId === event.agentId &&
e.messageId === event.messageId &&
e.type === event.type
);
if (existing) {
// 更新现有事件
existing.observation += ' | ' + event.observation;
existing.confidence = (existing.confidence + event.confidence) / 2;
existing.appliedCount++;
return existing;
}
// 创建新事件
const newEvent: LearningEvent = {
...event,
id: generateEventId(),
timestamp: Date.now(),
acknowledged: false,
appliedCount: 0,
};
this.events.push(newEvent);
this.extractPatterns(newEvent);
// 保持事件数量限制
if (this.events.length > MAX_EVENTS) {
this.events = this.events.slice(-MAX_EVENTS);
}
return newEvent;
}
/**
* 从反馈中学习
*/
learnFromFeedback(
agentId: string,
messageId: string,
feedback: string,
context?: string
): LearningEvent {
const sentiment = analyzeSentiment(feedback);
const type = analyzeEventType(feedback);
return this.recordEvent({
type,
agentId,
messageId,
trigger: context || 'User feedback',
observation: feedback,
context,
inferredPreference: inferPreference(feedback, sentiment),
confidence: sentiment === 'positive' ? 0.8 : sentiment === 'negative' ? 0.5 : 0.3,
});
}
/**
* 提取学习模式
*/
private extractPatterns(event: LearningEvent): void {
// 1. 正面反馈 -> 偏好正面回复
if (event.observation.includes('谢谢') || event.observation.includes('好的')) {
this.addPattern({
type: 'preference',
pattern: 'positive_response_preference',
description: '用户偏好正面回复风格',
examples: [event.observation],
confidence: 0.8,
agentId: event.agentId,
});
}
// 2. 纠正 -> 需要更精确
if (event.type === 'correction') {
this.addPattern({
type: 'rule',
pattern: 'precision_preference',
description: '用户对精确性有更高要求',
examples: [event.observation],
confidence: 0.9,
agentId: event.agentId,
});
}
// 3. 上下文相关 -> 场景偏好
if (event.context) {
this.addPattern({
type: 'context',
pattern: 'context_aware',
description: 'Agent 需要关注上下文',
examples: [event.context],
confidence: 0.6,
agentId: event.agentId,
});
}
}
/**
* 添加学习模式
*/
private addPattern(pattern: Omit<LearningPattern, 'updatedAt'>): void {
const existing = this.patterns.find(p =>
p.type === pattern.type &&
p.pattern === pattern.pattern &&
p.agentId === pattern.agentId
);
if (existing) {
// 增强置信度
existing.confidence = Math.min(1, existing.confidence + pattern.confidence * 0.1);
existing.examples.push(pattern.examples[0]);
existing.updatedAt = Date.now();
} else {
this.patterns.push({
...pattern,
updatedAt: Date.now(),
});
}
}
/**
* 生成学习建议
*/
generateSuggestions(agentId: string): LearningSuggestion[] {
const suggestions: LearningSuggestion[] = [];
const now = Date.now();
// 获取该 Agent 的模式
const agentPatterns = this.patterns.filter(p => p.agentId === agentId);
for (const pattern of agentPatterns) {
// 检查冷却时间
const hoursSinceUpdate = (now - (pattern.updatedAt || now)) / (1000 * 60 * 60);
if (hoursSinceUpdate < SUGGESTION_COOLDOWN_HOURS) continue;
// 检查置信度阈值
if (pattern.confidence < PATTERN_CONFIDENCE_THRESHOLD) continue;
// 生成建议
suggestions.push({
id: `sug-${Date.now()}-${Math.random().toString(36).slice(2)}`,
agentId,
type: pattern.type,
pattern: pattern.pattern,
suggestion: this.generateSuggestionContent(pattern),
confidence: pattern.confidence,
createdAt: now,
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
dismissed: false,
});
}
return suggestions;
}
/**
* 生成建议内容
*/
private generateSuggestionContent(pattern: LearningPattern): string {
const templates: Record<string, string> = {
positive_response_preference:
'用户似乎偏好正面回复。建议在回复时保持积极和确认的语气。',
precision_preference:
'用户对精确性有更高要求。建议在提供信息时更加详细和准确。',
context_aware:
'Agent 需要关注上下文。建议在回复时考虑对话的背景和历史。',
};
return templates[pattern.pattern] || `观察到模式: ${pattern.pattern}`;
}
/**
* 获取统计信息
*/
getStats(agentId: string) {
const agentEvents = this.events.filter(e => e.agentId === agentId);
const agentPatterns = this.patterns.filter(p => p.agentId === agentId);
const eventsByType: Record<LearningEventType, number> = {
preference: 0,
correction: 0,
context: 0,
feedback: 0,
behavior: 0,
implicit: 0,
};
for (const event of agentEvents) {
eventsByType[event.type]++;
}
return {
totalEvents: agentEvents.length,
eventsByType,
totalPatterns: agentPatterns.length,
avgConfidence: agentPatterns.length > 0
? agentPatterns.reduce((sum, p) => sum + p.confidence, 0) / agentPatterns.length
: 0,
};
}
/**
* 获取所有事件
*/
getEvents(agentId?: string): LearningEvent[] {
if (agentId) {
return this.events.filter(e => e.agentId === agentId);
}
return [...this.events];
}
/**
* 获取所有模式
*/
getPatterns(agentId?: string): LearningPattern[] {
if (agentId) {
return this.patterns.filter(p => p.agentId === agentId);
}
return [...this.patterns];
}
/**
* 确认事件
*/
acknowledgeEvent(eventId: string): void {
const event = this.events.find(e => e.id === eventId);
if (event) {
event.acknowledged = true;
}
}
/**
* 清除事件
*/
clearEvents(agentId: string): void {
this.events = this.events.filter(e => e.agentId !== agentId);
this.patterns = this.patterns.filter(p => p.agentId !== agentId);
}
}
// === 单例实例 ===
let engineInstance: ActiveLearningEngine | null = null;
export function getActiveLearningEngine(): ActiveLearningEngine {
if (!engineInstance) {
engineInstance = new ActiveLearningEngine();
}
return engineInstance;
}
export function resetActiveLearningEngine(): void {
engineInstance = null;
}