/** * ActiveLearningStore - 主动学习状态管理 * * 猡久学习事件和学习模式,学习建议的状态。 */ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { type LearningEvent, type LearningPattern, type LearningSuggestion, type LearningEventType, type LearningConfig, } from '../types/active-learning'; // === Types === interface ActiveLearningState { events: LearningEvent[]; patterns: LearningPattern[]; suggestions: LearningSuggestion[]; config: LearningConfig; isLoading: boolean; error: string | null; } interface ActiveLearningActions { recordEvent: (event: Omit) => Promise; recordFeedback: (agentId: string, messageId: string, feedback: string, context?: string) => Promise; acknowledgeEvent: (eventId: string) => void; getPatterns: (agentId: string) => LearningPattern[]; getSuggestions: (agentId: string) => LearningSuggestion[]; applySuggestion: (suggestionId: string) => void; dismissSuggestion: (suggestionId: string) => void; getStats: (agentId: string) => ActiveLearningStats; setConfig: (config: Partial) => void; clearEvents: (agentId: string) => void; exportLearningData: (agentId: string) => Promise; importLearningData: (agentId: string, data: string) => Promise; } interface ActiveLearningStats { totalEvents: number; eventsByType: Record; totalPatterns: number; avgConfidence: number; } export type ActiveLearningStore = ActiveLearningState & ActiveLearningActions; const STORAGE_KEY = 'zclaw-active-learning'; const MAX_EVENTS = 1000; // === Helper Functions === function generateEventId(): string { return `le-${Date.now()}-${Math.random().toString(36).slice(2)}`; } function analyzeSentiment(text: string): 'positive' | 'negative' | 'neutral' { const positive = ['好的', '很棒', '谢谢', '完美', 'excellent', '喜欢', '爱了', 'good', 'great', 'nice', '满意']; const negative = ['不好', '差', '糟糕', '错误', 'wrong', 'bad', '不喜欢', '讨厌', '问题', '失败', 'fail', 'error']; const lowerText = text.toLowerCase(); if (positive.some(w => lowerText.includes(w))) return 'positive'; if (negative.some(w => lowerText.includes(w))) return 'negative'; return 'neutral'; } 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 'feedback'; } function inferPreference(feedback: string, sentiment: string): 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 '用户反馈中性'; } // === Store === export const useActiveLearningStore = create()( persist( (set, get) => ({ events: [], patterns: [], suggestions: [], config: { enabled: true, minConfidence: 0.5, maxEvents: MAX_EVENTS, suggestionCooldown: 2, }, isLoading: false, error: null, recordEvent: async (event) => { const { events, config } = get(); if (!config.enabled) throw new Error('Learning is disabled'); // 检查重复事件 const existing = events.find(e => e.agentId === event.agentId && e.messageId === event.messageId && e.type === event.type ); if (existing) { // 更新现有事件 const updated = events.map(e => e.id === existing.id ? { ...e, observation: e.observation + ' | ' + event.observation, confidence: (e.confidence + event.confidence) / 2, appliedCount: e.appliedCount + 1, } : e ); set({ events: updated }); return existing; } // 创建新事件 const newEvent: LearningEvent = { ...event, id: generateEventId(), timestamp: Date.now(), acknowledged: false, appliedCount: 0, }; // 提取模式 const newPatterns = extractPatterns(newEvent, get().patterns); const newSuggestions = generateSuggestions(newEvent, newPatterns); // 保持事件数量限制 const updatedEvents = [newEvent, ...events].slice(0, config.maxEvents); set({ events: updatedEvents, patterns: [...get().patterns, ...newPatterns], suggestions: [...get().suggestions, ...newSuggestions], }); return newEvent; }, recordFeedback: async (agentId, messageId, feedback, context) => { const { config } = get(); if (!config.enabled) return null; const sentiment = analyzeSentiment(feedback); const type = analyzeEventType(feedback); return get().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, appliedCount: 0, }); }, acknowledgeEvent: (eventId) => { const { events } = get(); set({ events: events.map(e => e.id === eventId ? { ...e, acknowledged: true } : e ), }); }, getPatterns: (agentId) => { return get().patterns.filter(p => p.agentId === agentId); }, getSuggestions: (agentId) => { const now = Date.now(); return get().suggestions.filter(s => s.agentId === agentId && !s.dismissed && (!s.expiresAt || s.expiresAt.getTime() > now) ); }, applySuggestion: (suggestionId) => { const { suggestions, patterns } = get(); const suggestion = suggestions.find(s => s.id === suggestionId); if (suggestion) { // 更新模式置信度 const updatedPatterns = patterns.map(p => p.pattern === suggestion.pattern ? { ...p, confidence: Math.min(1, p.confidence + 0.1) } : p ); set({ suggestions: suggestions.map(s => s.id === suggestionId ? { ...s, dismissed: false } : s ), patterns: updatedPatterns, }); } }, dismissSuggestion: (suggestionId) => { const { suggestions } = get(); set({ suggestions: suggestions.map(s => s.id === suggestionId ? { ...s, dismissed: true } : s ), }); }, getStats: (agentId) => { const { events, patterns } = get(); const agentEvents = events.filter(e => e.agentId === agentId); const agentPatterns = patterns.filter(p => p.agentId === agentId); const eventsByType: Record = { 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, }; }, setConfig: (config) => { set(state => ({ config: { ...state.config, ...config }, })); }, clearEvents: (agentId) => { const { events, patterns, suggestions } = get(); set({ events: events.filter(e => e.agentId !== agentId), patterns: patterns.filter(p => p.agentId !== agentId), suggestions: suggestions.filter(s => s.agentId !== agentId), }); }, exportLearningData: async (agentId) => { const { events, patterns, config } = get(); const data = { events: events.filter(e => e.agentId === agentId), patterns: patterns.filter(p => p.agentId === agentId), config, exportedAt: new Date().toISOString(), }; return JSON.stringify(data, null, 2); }, importLearningData: async (agentId, data) => { try { const parsed = JSON.parse(data); const { events, patterns } = get(); // 合并导入的数据 const mergedEvents = [ ...events, ...parsed.events.map((e: LearningEvent) => ({ ...e, id: generateEventId(), agentId, })), ].slice(0, MAX_EVENTS); const mergedPatterns = [ ...patterns, ...parsed.patterns.map((p: LearningPattern) => ({ ...p, agentId, })), ]; set({ events: mergedEvents, patterns: mergedPatterns, }); } catch (err) { throw new Error(`Failed to import learning data: ${err}`); } }, }), { name: STORAGE_KEY, } ) ); // === Pattern Extraction === function extractPatterns( event: LearningEvent, existingPatterns: LearningPattern[] ): LearningPattern[] { const patterns: LearningPattern[] = []; // 偏好模式 if (event.observation.includes('谢谢') || event.observation.includes('好的')) { patterns.push({ type: 'preference', pattern: 'positive_response_preference', description: '用户偏好正面回复风格', examples: [event.observation], confidence: 0.8, agentId: event.agentId, }); } // 精确性模式 if (event.type === 'correction') { patterns.push({ type: 'rule', pattern: 'precision_preference', description: '用户对精确性有更高要求', examples: [event.observation], confidence: 0.9, agentId: event.agentId, }); } // 上下文模式 if (event.context) { patterns.push({ type: 'context', pattern: 'context_aware', description: 'Agent 需要关注上下文', examples: [event.context], confidence: 0.6, agentId: event.agentId, }); } return patterns.filter(p => !existingPatterns.some(ep => ep.pattern === p.pattern && ep.agentId === p.agentId) ); } // === Suggestion Generation === function generateSuggestions( event: LearningEvent, patterns: LearningPattern[] ): LearningSuggestion[] { const suggestions: LearningSuggestion[] = []; const now = Date.now(); for (const pattern of patterns) { const template = SUGGESTION_TEMPLATES[pattern.pattern]; if (template) { suggestions.push({ id: `sug-${Date.now()}-${Math.random().toString(36).slice(2)}`, agentId: event.agentId, type: pattern.type, pattern: pattern.pattern, suggestion: template, confidence: pattern.confidence, createdAt: now, expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000), dismissed: false, }); } } return suggestions; } const SUGGESTION_TEMPLATES: Record = { positive_response_preference: '用户似乎偏好正面回复。建议在回复时保持积极和确认的语气。', precision_preference: '用户对精确性有更高要求。建议在提供信息时更加详细和准确。', context_aware: 'Agent 需要关注上下文。建议在回复时考虑对话的背景和历史。', };