feat: implement database self-evolution + 200k context + memory injection
- Add profile-learner.js for auto user profile learning - Add knowledge-extractor.js for knowledge auto-extraction - Add pattern-analyzer.js for pattern recognition - Add evolution-logger.js for evolution event logging - Add memory-injector.js with 5-layer system prompt - Update model-config.json with context_window_target: 200000 - Update model-router.js to prioritize 200k+ models - Update persona-engine.js with knowledge/pattern queries - Update code-generator.js with post-build evolution triggers - Update memory-manager.js with updateProfile/autoExtractKnowledge - Update chat route with auto profile update after each conversation - Add brain JSON structures for knowledge-base, pattern-library, evolution-log, quality-scores Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
0429e7f58b
commit
f6ad45745b
|
|
@ -6,6 +6,9 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const modelRouter = require('./model-router');
|
||||
const knowledgeExtractor = require('./knowledge-extractor');
|
||||
const patternAnalyzer = require('./pattern-analyzer');
|
||||
const evolutionLogger = require('./evolution-logger');
|
||||
|
||||
const WORKSPACE_DIR = path.join(__dirname, '..', '..', 'workspace');
|
||||
|
||||
|
|
@ -31,6 +34,7 @@ async function generate({ dev_id, conversation }) {
|
|||
const requirements = extractRequirements(conversation);
|
||||
const projectName = 'project-' + Date.now();
|
||||
const projectDir = path.join(WORKSPACE_DIR, dev_id, projectName);
|
||||
const startTime = Date.now();
|
||||
|
||||
// 确保工作目录存在
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
|
|
@ -39,7 +43,9 @@ async function generate({ dev_id, conversation }) {
|
|||
|
||||
if (!apiKey) {
|
||||
// 无 API 密钥时生成模板项目
|
||||
return generateTemplate(projectDir, projectName, requirements);
|
||||
const result = generateTemplate(projectDir, projectName, requirements);
|
||||
triggerPostBuildEvolution(dev_id, conversation, result, startTime, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -84,14 +90,47 @@ async function generate({ dev_id, conversation }) {
|
|||
files.push('README.md');
|
||||
}
|
||||
|
||||
return {
|
||||
const result = {
|
||||
projectName,
|
||||
files,
|
||||
summary: `项目 ${projectName} 已生成,包含 ${files.length} 个文件。`
|
||||
};
|
||||
triggerPostBuildEvolution(dev_id, conversation, result, startTime, true);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Code generation failed:', err.message);
|
||||
return generateTemplate(projectDir, projectName, requirements);
|
||||
const result = generateTemplate(projectDir, projectName, requirements);
|
||||
triggerPostBuildEvolution(dev_id, conversation, result, startTime, false);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目完成后触发自进化:知识提取 + 模式识别
|
||||
*/
|
||||
function triggerPostBuildEvolution(devId, conversation, result, startTime, success) {
|
||||
try {
|
||||
const buildTimeMs = Date.now() - startTime;
|
||||
|
||||
// 知识提取
|
||||
knowledgeExtractor.autoExtractKnowledge(devId, conversation, result.projectName);
|
||||
|
||||
// 模式识别
|
||||
const patterns = patternAnalyzer.analyzeAndUpdatePatterns(
|
||||
devId, conversation, result.files, buildTimeMs, success
|
||||
);
|
||||
|
||||
// 进化日志
|
||||
evolutionLogger.logEvent('build_complete', '项目构建完成: ' + result.projectName, {
|
||||
dev_id: devId,
|
||||
project: result.projectName,
|
||||
files_count: (result.files || []).length,
|
||||
build_time_ms: buildTimeMs,
|
||||
success: success,
|
||||
patterns_detected: patterns
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Post-build evolution error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* persona-studio · 进化日志记录器
|
||||
*
|
||||
* 功能:记录系统每一次自进化事件
|
||||
* 事件类型:profile_update / knowledge_extract / pattern_update /
|
||||
* model_benchmark / quality_score / system_init
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const EVOLUTION_LOG_PATH = path.join(BRAIN_DIR, 'evolution-log.json');
|
||||
|
||||
/**
|
||||
* 加载进化日志
|
||||
*/
|
||||
function loadEvolutionLog() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(EVOLUTION_LOG_PATH, 'utf-8'));
|
||||
} catch {
|
||||
return {
|
||||
schema_version: '1.0',
|
||||
description: '系统进化日志',
|
||||
last_updated: null,
|
||||
total_events: 0,
|
||||
events: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存进化日志
|
||||
*/
|
||||
function saveEvolutionLog(log) {
|
||||
log.last_updated = new Date().toISOString();
|
||||
log.total_events = log.events.length;
|
||||
fs.writeFileSync(EVOLUTION_LOG_PATH, JSON.stringify(log, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录进化事件
|
||||
* @param {string} type - 事件类型
|
||||
* @param {string} description - 事件描述
|
||||
* @param {object} metadata - 事件元数据
|
||||
*/
|
||||
function logEvent(type, description, metadata) {
|
||||
const log = loadEvolutionLog();
|
||||
|
||||
const event = {
|
||||
id: 'EVT-' + Date.now(),
|
||||
type: type,
|
||||
description: description,
|
||||
metadata: metadata || {},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
log.events.push(event);
|
||||
|
||||
// 保留最近 1000 条
|
||||
if (log.events.length > 1000) {
|
||||
log.events = log.events.slice(-1000);
|
||||
}
|
||||
|
||||
saveEvolutionLog(log);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录画像更新事件
|
||||
*/
|
||||
function logProfileUpdate(devId, fieldsUpdated) {
|
||||
return logEvent('profile_update', '用户画像更新: ' + devId, {
|
||||
dev_id: devId,
|
||||
fields_updated: fieldsUpdated || []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录知识提取事件
|
||||
*/
|
||||
function logKnowledgeExtract(devId, entriesCount, projectName) {
|
||||
return logEvent('knowledge_extract', '知识提取: ' + entriesCount + ' 条新知识', {
|
||||
dev_id: devId,
|
||||
entries_count: entriesCount,
|
||||
project: projectName || null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录模式更新事件
|
||||
*/
|
||||
function logPatternUpdate(patternNames, devId) {
|
||||
return logEvent('pattern_update', '模式识别: ' + (patternNames || []).join(', '), {
|
||||
dev_id: devId,
|
||||
patterns: patternNames || []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录质量评分事件
|
||||
*/
|
||||
function logQualityScore(devId, projectName, score) {
|
||||
return logEvent('quality_score', '质量评分: ' + projectName + ' = ' + score, {
|
||||
dev_id: devId,
|
||||
project: projectName,
|
||||
score: score
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录模型评测事件
|
||||
*/
|
||||
function logModelBenchmark(modelsCount, routingChanges) {
|
||||
return logEvent('model_benchmark', '模型评测完成: ' + modelsCount + ' 个模型', {
|
||||
models_count: modelsCount,
|
||||
routing_changes: routingChanges || []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近事件
|
||||
* @param {number} limit - 数量限制
|
||||
* @param {string} type - 可选,按类型过滤
|
||||
* @returns {Array} 事件列表
|
||||
*/
|
||||
function getRecentEvents(limit, type) {
|
||||
const log = loadEvolutionLog();
|
||||
let events = log.events;
|
||||
|
||||
if (type) {
|
||||
events = events.filter(function (e) { return e.type === type; });
|
||||
}
|
||||
|
||||
return events.slice(-(limit || 20)).reverse();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logEvent,
|
||||
logProfileUpdate,
|
||||
logKnowledgeExtract,
|
||||
logPatternUpdate,
|
||||
logQualityScore,
|
||||
logModelBenchmark,
|
||||
getRecentEvents,
|
||||
loadEvolutionLog
|
||||
};
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* persona-studio · 知识提取引擎
|
||||
*
|
||||
* 触发:代码生成完成时 / 用户确认方案时 / 解决技术问题时
|
||||
* 功能:从对话中提取有价值的知识条目存入 knowledge-base.json
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const KB_PATH = path.join(BRAIN_DIR, 'knowledge-base.json');
|
||||
|
||||
/**
|
||||
* 加载知识库
|
||||
*/
|
||||
function loadKnowledgeBase() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(KB_PATH, 'utf-8'));
|
||||
} catch {
|
||||
return {
|
||||
schema_version: '1.0',
|
||||
description: '系统知识库 · 自动积累',
|
||||
last_updated: null,
|
||||
total_entries: 0,
|
||||
entries: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存知识库
|
||||
*/
|
||||
function saveKnowledgeBase(kb) {
|
||||
kb.last_updated = new Date().toISOString();
|
||||
kb.total_entries = kb.entries.length;
|
||||
fs.writeFileSync(KB_PATH, JSON.stringify(kb, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从对话中提取技术方案
|
||||
*/
|
||||
function extractSolutions(conversation, sourceExp, sourceProject) {
|
||||
const entries = [];
|
||||
const messages = conversation || [];
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i];
|
||||
if (msg.role !== 'assistant') continue;
|
||||
const content = msg.content || '';
|
||||
|
||||
// 检测方案类关键词
|
||||
if (/技术方案|解决方案|实现方式|推荐.*方案|建议使用/i.test(content)) {
|
||||
entries.push({
|
||||
id: 'KB-' + Date.now() + '-' + i,
|
||||
type: 'solution',
|
||||
category: detectCategory(content),
|
||||
tags: extractTags(content),
|
||||
title: extractTitle(content),
|
||||
content: content.substring(0, 1000),
|
||||
source_exp: sourceExp,
|
||||
source_project: sourceProject || null,
|
||||
usage_count: 0,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测最佳实践
|
||||
if (/最佳实践|best practice|推荐做法|正确.*方式/i.test(content)) {
|
||||
entries.push({
|
||||
id: 'KB-' + Date.now() + '-bp-' + i,
|
||||
type: 'best_practice',
|
||||
category: detectCategory(content),
|
||||
tags: extractTags(content),
|
||||
title: extractTitle(content),
|
||||
content: content.substring(0, 1000),
|
||||
source_exp: sourceExp,
|
||||
source_project: sourceProject || null,
|
||||
usage_count: 0,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// 检测常见陷阱
|
||||
if (/注意|陷阱|坑|避免|不要.*这样|常见错误|pitfall/i.test(content)) {
|
||||
entries.push({
|
||||
id: 'KB-' + Date.now() + '-pit-' + i,
|
||||
type: 'pitfall',
|
||||
category: detectCategory(content),
|
||||
tags: extractTags(content),
|
||||
title: extractTitle(content),
|
||||
content: content.substring(0, 1000),
|
||||
source_exp: sourceExp,
|
||||
source_project: sourceProject || null,
|
||||
usage_count: 0,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测技术类别
|
||||
*/
|
||||
function detectCategory(content) {
|
||||
const lower = content.toLowerCase();
|
||||
if (/react|vue|angular|前端|frontend|html|css|ui/i.test(lower)) return 'frontend';
|
||||
if (/node|express|api|后端|backend|server|数据库|sql/i.test(lower)) return 'backend';
|
||||
if (/docker|部署|deploy|ci|cd|运维|devops|nginx/i.test(lower)) return 'devops';
|
||||
if (/设计|design|ux|ui|交互|布局|layout/i.test(lower)) return 'design';
|
||||
if (/安全|security|auth|鉴权|加密|token/i.test(lower)) return 'security';
|
||||
if (/性能|performance|优化|缓存|cache/i.test(lower)) return 'performance';
|
||||
return 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取标签
|
||||
*/
|
||||
function extractTags(content) {
|
||||
const tags = [];
|
||||
const techKeywords = [
|
||||
'javascript', 'typescript', 'python', 'react', 'vue', 'node',
|
||||
'express', 'html', 'css', 'sql', 'mongodb', 'redis', 'docker',
|
||||
'nginx', 'git', 'api', 'rest', 'graphql', 'websocket'
|
||||
];
|
||||
const lower = content.toLowerCase();
|
||||
techKeywords.forEach(function (kw) {
|
||||
if (lower.includes(kw)) tags.push(kw);
|
||||
});
|
||||
return tags.slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取标题(取第一行非空文本的前50字符)
|
||||
*/
|
||||
function extractTitle(content) {
|
||||
const lines = content.split('\n').filter(function (l) { return l.trim().length > 0; });
|
||||
if (lines.length === 0) return '未命名知识条目';
|
||||
const first = lines[0].replace(/^[#*\-\s]+/, '').trim();
|
||||
return first.length > 50 ? first.substring(0, 50) + '…' : first;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将提取的知识添加到知识库(自动去重)
|
||||
*/
|
||||
function addToKnowledgeBase(entries) {
|
||||
if (!entries || entries.length === 0) return;
|
||||
|
||||
const kb = loadKnowledgeBase();
|
||||
|
||||
entries.forEach(function (entry) {
|
||||
// 简单去重:同标题+同类型 = 已存在
|
||||
const exists = kb.entries.some(function (existing) {
|
||||
return existing.title === entry.title && existing.type === entry.type;
|
||||
});
|
||||
if (!exists) {
|
||||
kb.entries.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
// 保留最近 500 条
|
||||
if (kb.entries.length > 500) {
|
||||
kb.entries = kb.entries.slice(-500);
|
||||
}
|
||||
|
||||
saveKnowledgeBase(kb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询知识库(供 persona-engine 调用)
|
||||
* @param {string} query - 搜索关键词
|
||||
* @param {number} limit - 最大返回数量
|
||||
* @returns {Array} 匹配的知识条目
|
||||
*/
|
||||
function queryKnowledge(query, limit) {
|
||||
const kb = loadKnowledgeBase();
|
||||
if (!query || kb.entries.length === 0) return [];
|
||||
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const keywords = lowerQuery.split(/[\s,,。.!??!]+/).filter(Boolean);
|
||||
|
||||
const scored = kb.entries.map(function (entry) {
|
||||
let score = 0;
|
||||
const entryText = ((entry.title || '') + ' ' + (entry.content || '') + ' ' + (entry.tags || []).join(' ')).toLowerCase();
|
||||
|
||||
keywords.forEach(function (kw) {
|
||||
if (entryText.includes(kw)) score += 1;
|
||||
});
|
||||
|
||||
// 使用次数加权
|
||||
score += (entry.usage_count || 0) * 0.1;
|
||||
|
||||
return { entry: entry, score: score };
|
||||
});
|
||||
|
||||
return scored
|
||||
.filter(function (s) { return s.score > 0; })
|
||||
.sort(function (a, b) { return b.score - a.score; })
|
||||
.slice(0, limit || 5)
|
||||
.map(function (s) {
|
||||
// 增加使用计数
|
||||
s.entry.usage_count = (s.entry.usage_count || 0) + 1;
|
||||
return s.entry;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:自动提取知识
|
||||
*/
|
||||
function autoExtractKnowledge(devId, conversation, projectName) {
|
||||
const entries = extractSolutions(conversation, devId, projectName);
|
||||
if (entries.length > 0) {
|
||||
addToKnowledgeBase(entries);
|
||||
}
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
autoExtractKnowledge,
|
||||
queryKnowledge,
|
||||
loadKnowledgeBase,
|
||||
addToKnowledgeBase,
|
||||
extractSolutions
|
||||
};
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
/**
|
||||
* persona-studio · 记忆注入Agent
|
||||
*
|
||||
* 三步走机制:
|
||||
* Step 1 压缩:把长对话压缩成结构化摘要
|
||||
* Step 2 注入:每次调模型前,摘要注入 system prompt
|
||||
* Step 3 刷新:每10轮或token>50k时重新压缩
|
||||
*
|
||||
* 五层 system prompt 结构:
|
||||
* 第1层:人格体身份(固定·来自 persona-config.json)
|
||||
* 第2层:通感语言风格(固定)
|
||||
* 第3层:用户画像(来自 profile.json)
|
||||
* 第4层:记忆摘要(来自 compressed.json · ≤8k token)
|
||||
* 第5层:最近10轮原始对话(滑动窗口)
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
|
||||
const CONFIG_PATH = path.join(__dirname, 'model-config.json');
|
||||
|
||||
/**
|
||||
* 加载注入配置
|
||||
*/
|
||||
function loadInjectionConfig() {
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
||||
return config.memory_injection || getDefaultConfig();
|
||||
} catch {
|
||||
return getDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultConfig() {
|
||||
return {
|
||||
enabled: true,
|
||||
compression_model: 'quick_reply',
|
||||
compression_trigger: {
|
||||
every_n_rounds: 10,
|
||||
token_threshold: 50000,
|
||||
force_on_session_start: true
|
||||
},
|
||||
injection_strategy: {
|
||||
system_prompt_max_tokens: 8000,
|
||||
sliding_window_rounds: 10,
|
||||
priority: ['confirmed_decisions', 'requirements', 'open_questions', 'user_preferences', 'emotional_signals']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取滑动窗口大小
|
||||
*/
|
||||
function getSlidingWindowSize() {
|
||||
const config = loadInjectionConfig();
|
||||
return (config.injection_strategy && config.injection_strategy.sliding_window_rounds) || 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载压缩摘要
|
||||
*/
|
||||
function loadCompressed(devId) {
|
||||
const file = path.join(MEMORY_DIR, devId, 'compressed.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存压缩摘要
|
||||
*/
|
||||
function saveCompressed(devId, compressed) {
|
||||
const dir = path.join(MEMORY_DIR, devId);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'compressed.json'),
|
||||
JSON.stringify(compressed, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载注入日志
|
||||
*/
|
||||
function loadInjectionLog(devId) {
|
||||
const file = path.join(MEMORY_DIR, devId, 'injection-log.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
} catch {
|
||||
return { entries: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存注入日志
|
||||
*/
|
||||
function saveInjectionLog(devId, log) {
|
||||
const dir = path.join(MEMORY_DIR, devId);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
const entries = log.entries || [];
|
||||
// 保留最近 100 条日志
|
||||
if (entries.length > 100) {
|
||||
log.entries = entries.slice(-100);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'injection-log.json'),
|
||||
JSON.stringify(log, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载用户画像
|
||||
*/
|
||||
function loadProfile(devId) {
|
||||
const file = path.join(MEMORY_DIR, devId, 'profile.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 粗略估算 token 数量(中文约 1.5 token/字,英文约 0.25 token/word)
|
||||
*/
|
||||
function estimateTokens(text) {
|
||||
if (!text) return 0;
|
||||
const chineseChars = (text.match(/[\u4e00-\u9fff]/g) || []).length;
|
||||
const otherChars = text.length - chineseChars;
|
||||
return Math.ceil(chineseChars * 1.5 + otherChars * 0.4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要重新压缩
|
||||
*/
|
||||
function needsRecompression(devId, history) {
|
||||
const config = loadInjectionConfig();
|
||||
if (!config.enabled) return false;
|
||||
|
||||
const trigger = config.compression_trigger || {};
|
||||
const compressed = loadCompressed(devId);
|
||||
const totalRounds = Math.floor((history || []).length / 2);
|
||||
|
||||
// 首次对话强制压缩
|
||||
if (!compressed && trigger.force_on_session_start) return true;
|
||||
|
||||
// 每 N 轮压缩一次
|
||||
if (compressed && trigger.every_n_rounds) {
|
||||
const processedRounds = compressed.total_rounds_processed || 0;
|
||||
if (totalRounds - processedRounds >= trigger.every_n_rounds) return true;
|
||||
}
|
||||
|
||||
// token 阈值
|
||||
if (trigger.token_threshold) {
|
||||
const totalText = (history || []).map(function (m) { return m.content || ''; }).join('');
|
||||
if (estimateTokens(totalText) > trigger.token_threshold) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地压缩对话历史(不依赖 AI 模型的规则式压缩)
|
||||
* 当模型不可用时的降级方案
|
||||
*/
|
||||
function localCompress(devId, history) {
|
||||
const messages = history || [];
|
||||
const userMessages = messages.filter(function (m) { return m.role === 'user'; });
|
||||
const assistantMessages = messages.filter(function (m) { return m.role === 'assistant'; });
|
||||
|
||||
// 提取需求
|
||||
const requirements = [];
|
||||
const decisions = [];
|
||||
const openQuestions = [];
|
||||
|
||||
userMessages.forEach(function (msg) {
|
||||
const content = msg.content || '';
|
||||
if (/想做|需要|功能|要求|做一个/i.test(content)) {
|
||||
requirements.push(content.substring(0, 100));
|
||||
}
|
||||
if (/[??]/.test(content)) {
|
||||
openQuestions.push(content.substring(0, 100));
|
||||
}
|
||||
});
|
||||
|
||||
assistantMessages.forEach(function (msg) {
|
||||
const content = msg.content || '';
|
||||
if (/方案已确认|确认|可以开始|建议使用|推荐/i.test(content)) {
|
||||
decisions.push(content.substring(0, 100));
|
||||
}
|
||||
});
|
||||
|
||||
const compressed = {
|
||||
version: 1,
|
||||
last_compressed_at: new Date().toISOString(),
|
||||
total_rounds_processed: Math.floor(messages.length / 2),
|
||||
summary: {
|
||||
requirements: requirements.slice(-10),
|
||||
confirmed_decisions: decisions.slice(-10),
|
||||
user_preferences: {},
|
||||
open_questions: openQuestions.slice(-5),
|
||||
emotional_signals: {
|
||||
overall: 'neutral',
|
||||
last_mood: 'neutral'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 融入画像信息
|
||||
const profile = loadProfile(devId);
|
||||
if (profile) {
|
||||
if (profile.communication_style) {
|
||||
compressed.summary.user_preferences.communication_style =
|
||||
profile.communication_style.verbosity || 'normal';
|
||||
}
|
||||
if (profile.design_preferences) {
|
||||
compressed.summary.user_preferences.design_preference =
|
||||
profile.design_preferences.color_scheme || null;
|
||||
}
|
||||
if (profile.tech_assessment) {
|
||||
compressed.summary.user_preferences.tech_level =
|
||||
profile.tech_assessment.level || null;
|
||||
}
|
||||
if (profile.emotional_profile) {
|
||||
compressed.summary.emotional_signals.overall =
|
||||
profile.emotional_profile.current_mood || 'neutral';
|
||||
}
|
||||
}
|
||||
|
||||
saveCompressed(devId, compressed);
|
||||
return compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建注入的五层 system prompt
|
||||
* @param {string} basePrompt - 基础 system prompt(第1-2层)
|
||||
* @param {string} devId - 开发者编号
|
||||
* @param {object} memory - 记忆对象
|
||||
* @param {Array} history - 对话历史
|
||||
* @returns {string} 注入后的完整 system prompt
|
||||
*/
|
||||
function buildInjectedSystemPrompt(basePrompt, devId, memory, history) {
|
||||
const config = loadInjectionConfig();
|
||||
if (!config.enabled || !devId || devId === 'GUEST') {
|
||||
return basePrompt;
|
||||
}
|
||||
|
||||
const parts = [basePrompt]; // 第1-2层已包含在 basePrompt 中
|
||||
|
||||
// 第3层:用户画像
|
||||
const profile = loadProfile(devId);
|
||||
if (profile) {
|
||||
const profileParts = [];
|
||||
if (profile.tech_assessment && profile.tech_assessment.level) {
|
||||
profileParts.push('技术水平:' + profile.tech_assessment.level);
|
||||
}
|
||||
if (profile.tech_assessment && profile.tech_assessment.known_skills && profile.tech_assessment.known_skills.length > 0) {
|
||||
profileParts.push('已知技能:' + profile.tech_assessment.known_skills.join(', '));
|
||||
}
|
||||
if (profile.communication_style && profile.communication_style.verbosity) {
|
||||
profileParts.push('沟通偏好:' + profile.communication_style.verbosity);
|
||||
}
|
||||
if (profile.design_preferences && profile.design_preferences.color_scheme) {
|
||||
profileParts.push('设计偏好:' + profile.design_preferences.color_scheme);
|
||||
}
|
||||
if (profile.emotional_profile && profile.emotional_profile.current_mood) {
|
||||
profileParts.push('当前情绪:' + profile.emotional_profile.current_mood);
|
||||
}
|
||||
|
||||
if (profileParts.length > 0) {
|
||||
parts.push('\n## 用户画像\n' + profileParts.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
// 第4层:记忆摘要
|
||||
if (needsRecompression(devId, history)) {
|
||||
localCompress(devId, history);
|
||||
}
|
||||
|
||||
const compressed = loadCompressed(devId);
|
||||
if (compressed && compressed.summary) {
|
||||
const summaryParts = [];
|
||||
const priority = (config.injection_strategy && config.injection_strategy.priority) || [];
|
||||
const summary = compressed.summary;
|
||||
|
||||
priority.forEach(function (key) {
|
||||
if (key === 'confirmed_decisions' && summary.confirmed_decisions && summary.confirmed_decisions.length > 0) {
|
||||
summaryParts.push('已确认的决策:\n- ' + summary.confirmed_decisions.slice(-5).join('\n- '));
|
||||
}
|
||||
if (key === 'requirements' && summary.requirements && summary.requirements.length > 0) {
|
||||
summaryParts.push('用户需求要点:\n- ' + summary.requirements.slice(-5).join('\n- '));
|
||||
}
|
||||
if (key === 'open_questions' && summary.open_questions && summary.open_questions.length > 0) {
|
||||
summaryParts.push('待解决问题:\n- ' + summary.open_questions.slice(-3).join('\n- '));
|
||||
}
|
||||
if (key === 'user_preferences' && summary.user_preferences) {
|
||||
const prefs = Object.entries(summary.user_preferences)
|
||||
.filter(function (pair) { return pair[1] != null; })
|
||||
.map(function (pair) { return pair[0] + ': ' + pair[1]; });
|
||||
if (prefs.length > 0) {
|
||||
summaryParts.push('用户偏好:' + prefs.join(', '));
|
||||
}
|
||||
}
|
||||
if (key === 'emotional_signals' && summary.emotional_signals) {
|
||||
if (summary.emotional_signals.overall && summary.emotional_signals.overall !== 'neutral') {
|
||||
summaryParts.push('情感信号:整体 ' + summary.emotional_signals.overall);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (summaryParts.length > 0) {
|
||||
parts.push('\n## 记忆摘要(远期记忆)\n' + summaryParts.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
// 记录注入日志
|
||||
try {
|
||||
const log = loadInjectionLog(devId);
|
||||
log.entries.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
layers_injected: parts.length,
|
||||
has_profile: !!profile,
|
||||
has_compressed: !!compressed,
|
||||
history_rounds: Math.floor((history || []).length / 2)
|
||||
});
|
||||
saveInjectionLog(devId, log);
|
||||
} catch (_e) { /* log failed silently */ }
|
||||
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildInjectedSystemPrompt,
|
||||
getSlidingWindowSize,
|
||||
needsRecompression,
|
||||
localCompress,
|
||||
loadCompressed,
|
||||
saveCompressed,
|
||||
loadProfile,
|
||||
estimateTokens,
|
||||
loadInjectionConfig
|
||||
};
|
||||
|
|
@ -4,6 +4,9 @@
|
|||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const profileLearner = require('./profile-learner');
|
||||
const knowledgeExtractor = require('./knowledge-extractor');
|
||||
const evolutionLogger = require('./evolution-logger');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
|
||||
|
|
@ -125,6 +128,43 @@ function loadProfile(devId) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户画像(调用 profile-learner)
|
||||
* @param {string} devId - 开发编号
|
||||
* @param {string} userMessage - 用户消息
|
||||
* @param {string} assistantReply - 助手回复
|
||||
*/
|
||||
function updateProfile(devId, userMessage, assistantReply) {
|
||||
if (!devId || devId === 'GUEST') return;
|
||||
try {
|
||||
const result = profileLearner.updateProfile(devId, userMessage, assistantReply);
|
||||
if (result) {
|
||||
evolutionLogger.logProfileUpdate(devId, Object.keys(result));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Profile update error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动提取知识(调用 knowledge-extractor)
|
||||
* @param {string} devId - 开发编号
|
||||
* @param {Array} conversation - 对话历史
|
||||
* @param {string} projectName - 项目名
|
||||
*/
|
||||
function autoExtractKnowledge(devId, conversation, projectName) {
|
||||
try {
|
||||
const count = knowledgeExtractor.autoExtractKnowledge(devId, conversation, projectName);
|
||||
if (count > 0) {
|
||||
evolutionLogger.logKnowledgeExtract(devId, count, projectName);
|
||||
}
|
||||
return count;
|
||||
} catch (err) {
|
||||
console.error('Knowledge extraction error:', err.message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadMemory,
|
||||
saveMemory,
|
||||
|
|
@ -132,5 +172,7 @@ module.exports = {
|
|||
updateLastTopic,
|
||||
loadProjects,
|
||||
addProject,
|
||||
loadProfile
|
||||
loadProfile,
|
||||
updateProfile,
|
||||
autoExtractKnowledge
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"api_source": "third_party_combined",
|
||||
"api_key_env": "MODEL_API_KEY",
|
||||
"base_url": "https://api.yunwu.ai/v1",
|
||||
"context_window_target": 200000,
|
||||
"auto_detect": {
|
||||
"enabled": true,
|
||||
"schedule": "daily_0300",
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
},
|
||||
"routing_rules": {
|
||||
"chat": {
|
||||
"priority": ["chinese_ability", "conversation_quality", "speed"],
|
||||
"priority": ["chinese_ability", "conversation_quality", "context_window", "speed"],
|
||||
"max_latency_ms": 5000
|
||||
},
|
||||
"code_generation": {
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
"max_latency_ms": 30000
|
||||
},
|
||||
"code_review": {
|
||||
"priority": ["reasoning", "code_quality"],
|
||||
"priority": ["reasoning", "code_quality", "context_window"],
|
||||
"max_latency_ms": 15000
|
||||
},
|
||||
"quick_reply": {
|
||||
|
|
@ -29,6 +30,20 @@
|
|||
"max_latency_ms": 2000
|
||||
}
|
||||
},
|
||||
"memory_injection": {
|
||||
"enabled": true,
|
||||
"compression_model": "quick_reply",
|
||||
"compression_trigger": {
|
||||
"every_n_rounds": 10,
|
||||
"token_threshold": 50000,
|
||||
"force_on_session_start": true
|
||||
},
|
||||
"injection_strategy": {
|
||||
"system_prompt_max_tokens": 8000,
|
||||
"sliding_window_rounds": 10,
|
||||
"priority": ["confirmed_decisions", "requirements", "open_questions", "user_preferences", "emotional_signals"]
|
||||
}
|
||||
},
|
||||
"fallback": {
|
||||
"max_retries": 3,
|
||||
"timeout_ms": 30000,
|
||||
|
|
|
|||
|
|
@ -53,9 +53,26 @@ function selectModel(taskType) {
|
|||
const benchmark = loadBenchmark();
|
||||
const apiKey = process.env.MODEL_API_KEY || '';
|
||||
const baseUrl = config.base_url || 'https://api.yunwu.ai/v1';
|
||||
const contextTarget = config.context_window_target || 200000;
|
||||
|
||||
// 如果有 benchmark 且有路由表,使用路由表
|
||||
if (benchmark && benchmark.routing_table && benchmark.routing_table[taskType]) {
|
||||
// 优先选择 context_window >= contextTarget 的模型
|
||||
if (benchmark.benchmark) {
|
||||
const preferred = benchmark.benchmark.find(function (m) {
|
||||
return m.available &&
|
||||
m.scores &&
|
||||
m.scores.context_window >= contextTarget;
|
||||
});
|
||||
if (preferred) {
|
||||
return {
|
||||
model: preferred.model_id,
|
||||
baseUrl,
|
||||
apiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
model: benchmark.routing_table[taskType],
|
||||
baseUrl,
|
||||
|
|
@ -92,6 +109,7 @@ function selectModel(taskType) {
|
|||
async function callModel({ model, baseUrl, apiKey, messages, maxTokens = 2000, temperature = 0.8 }) {
|
||||
const config = loadConfig();
|
||||
const fallbackConfig = config.fallback || { max_retries: 3, timeout_ms: 30000 };
|
||||
const contextWindowTarget = config.context_window_target || 200000;
|
||||
|
||||
// 尝试调用,支持降级
|
||||
const benchmark = loadBenchmark();
|
||||
|
|
@ -227,10 +245,13 @@ async function autoDetect() {
|
|||
});
|
||||
|
||||
// 生成基准测试结果
|
||||
const contextTarget = config.context_window_target || 200000;
|
||||
const benchmarkData = {
|
||||
last_updated: new Date().toISOString(),
|
||||
models_detected: modelsList.length,
|
||||
context_window_target: contextTarget,
|
||||
benchmark: modelsList.slice(0, 10).map(function (m) {
|
||||
const ctxWindow = m.context_window || m.context_length || 32000;
|
||||
return {
|
||||
model_id: m.id,
|
||||
available: true,
|
||||
|
|
@ -240,9 +261,10 @@ async function autoDetect() {
|
|||
code_quality: 80,
|
||||
reasoning: 80,
|
||||
speed_ms: 2000,
|
||||
context_window: m.context_window || 32000,
|
||||
context_window: ctxWindow,
|
||||
cost_per_1k_tokens: 0.002
|
||||
},
|
||||
meets_context_target: ctxWindow >= contextTarget,
|
||||
best_for: ['chat']
|
||||
};
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
* persona-studio · 模式识别引擎
|
||||
*
|
||||
* 触发:每完成一个项目 + 每日凌晨聚合
|
||||
* 功能:从已完成项目中识别高频模式,建立模式库
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const PATTERN_PATH = path.join(BRAIN_DIR, 'pattern-library.json');
|
||||
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
|
||||
|
||||
/**
|
||||
* 加载模式库
|
||||
*/
|
||||
function loadPatternLibrary() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(PATTERN_PATH, 'utf-8'));
|
||||
} catch {
|
||||
return {
|
||||
schema_version: '1.0',
|
||||
description: '系统模式库 · 自动识别',
|
||||
last_updated: null,
|
||||
total_patterns: 0,
|
||||
patterns: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存模式库
|
||||
*/
|
||||
function savePatternLibrary(lib) {
|
||||
lib.last_updated = new Date().toISOString();
|
||||
lib.total_patterns = lib.patterns.length;
|
||||
fs.writeFileSync(PATTERN_PATH, JSON.stringify(lib, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从项目中检测模式类型
|
||||
*/
|
||||
function detectPatternType(conversation, projectFiles) {
|
||||
const allText = (conversation || [])
|
||||
.map(function (m) { return m.content || ''; })
|
||||
.join('\n')
|
||||
.toLowerCase();
|
||||
|
||||
const patterns = [];
|
||||
|
||||
const patternDefs = [
|
||||
{ name: '个人博客', keywords: ['博客', 'blog', '文章', '发布', '个人主页'] },
|
||||
{ name: '登录注册', keywords: ['登录', '注册', 'login', 'register', '用户认证', '用户系统'] },
|
||||
{ name: '管理后台', keywords: ['管理', '后台', 'dashboard', '管理面板', 'admin'] },
|
||||
{ name: '电商页面', keywords: ['商城', '购物', '商品', '购买', '电商', 'shop'] },
|
||||
{ name: '展示页面', keywords: ['展示', '介绍', '落地页', 'landing', '产品页'] },
|
||||
{ name: '数据表格', keywords: ['表格', '数据', '列表', '筛选', 'table', '搜索'] },
|
||||
{ name: '表单系统', keywords: ['表单', '提交', '验证', 'form', '输入'] },
|
||||
{ name: '聊天应用', keywords: ['聊天', 'chat', '即时通讯', '消息', '对话'] },
|
||||
{ name: '小工具', keywords: ['工具', '计算器', '转换器', '生成器', 'tool', 'utility'] },
|
||||
{ name: 'API服务', keywords: ['api', '接口', '后端服务', 'restful', 'server'] }
|
||||
];
|
||||
|
||||
patternDefs.forEach(function (def) {
|
||||
const matchCount = def.keywords.filter(function (kw) {
|
||||
return allText.includes(kw);
|
||||
}).length;
|
||||
|
||||
if (matchCount >= 2) {
|
||||
patterns.push(def.name);
|
||||
}
|
||||
});
|
||||
|
||||
return patterns.length > 0 ? patterns : ['通用项目'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测技术栈
|
||||
*/
|
||||
function detectTechStack(conversation, projectFiles) {
|
||||
const allText = (conversation || [])
|
||||
.map(function (m) { return m.content || ''; })
|
||||
.join('\n')
|
||||
.toLowerCase();
|
||||
|
||||
const stack = [];
|
||||
const techMap = {
|
||||
'HTML/CSS/JS': ['html', 'css', 'javascript'],
|
||||
'React': ['react', 'jsx', 'tsx'],
|
||||
'Vue': ['vue', 'vuex'],
|
||||
'Node.js': ['node', 'express', 'koa'],
|
||||
'Python': ['python', 'flask', 'django'],
|
||||
'TypeScript': ['typescript', 'ts'],
|
||||
'Tailwind': ['tailwind'],
|
||||
'Bootstrap': ['bootstrap']
|
||||
};
|
||||
|
||||
for (const [tech, keywords] of Object.entries(techMap)) {
|
||||
if (keywords.some(function (kw) { return allText.includes(kw); })) {
|
||||
stack.push(tech);
|
||||
}
|
||||
}
|
||||
|
||||
// 从文件扩展名推断
|
||||
if (projectFiles && projectFiles.length > 0) {
|
||||
const exts = projectFiles.map(function (f) {
|
||||
return path.extname(f).toLowerCase();
|
||||
});
|
||||
|
||||
if (exts.includes('.html')) stack.push('HTML/CSS/JS');
|
||||
if (exts.includes('.jsx') || exts.includes('.tsx')) stack.push('React');
|
||||
if (exts.includes('.vue')) stack.push('Vue');
|
||||
if (exts.includes('.py')) stack.push('Python');
|
||||
}
|
||||
|
||||
return [...new Set(stack)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:分析项目并更新模式库
|
||||
* @param {string} devId - 开发者编号
|
||||
* @param {Array} conversation - 对话历史
|
||||
* @param {Array} projectFiles - 项目文件列表
|
||||
* @param {number} buildTimeMs - 构建耗时(毫秒)
|
||||
* @param {boolean} success - 是否成功
|
||||
*/
|
||||
function analyzeAndUpdatePatterns(devId, conversation, projectFiles, buildTimeMs, success) {
|
||||
const lib = loadPatternLibrary();
|
||||
const patternNames = detectPatternType(conversation, projectFiles);
|
||||
const techStack = detectTechStack(conversation, projectFiles);
|
||||
|
||||
patternNames.forEach(function (patternName) {
|
||||
// 查找已有模式
|
||||
const existing = lib.patterns.find(function (p) { return p.name === patternName; });
|
||||
|
||||
if (existing) {
|
||||
// 更新已有模式
|
||||
existing.frequency = (existing.frequency || 0) + 1;
|
||||
existing.common_tech_stack = mergeArrays(existing.common_tech_stack, techStack);
|
||||
if (buildTimeMs) {
|
||||
existing.avg_build_time = existing.avg_build_time
|
||||
? Math.round((existing.avg_build_time + buildTimeMs) / 2)
|
||||
: buildTimeMs;
|
||||
}
|
||||
if (success !== undefined) {
|
||||
const totalAttempts = existing.frequency;
|
||||
const prevSuccesses = Math.round((existing.success_rate || 100) / 100 * (totalAttempts - 1));
|
||||
existing.success_rate = Math.round((prevSuccesses + (success ? 1 : 0)) / totalAttempts * 100);
|
||||
}
|
||||
existing.last_seen = new Date().toISOString();
|
||||
} else {
|
||||
// 添加新模式
|
||||
lib.patterns.push({
|
||||
name: patternName,
|
||||
frequency: 1,
|
||||
common_features: [],
|
||||
common_tech_stack: techStack,
|
||||
avg_build_time: buildTimeMs || null,
|
||||
success_rate: success !== false ? 100 : 0,
|
||||
first_seen: new Date().toISOString(),
|
||||
last_seen: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
savePatternLibrary(lib);
|
||||
return patternNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询模式库(供 persona-engine 调用)
|
||||
* @param {string} query - 搜索关键词
|
||||
* @returns {Array} 匹配的模式
|
||||
*/
|
||||
function queryPatterns(query) {
|
||||
const lib = loadPatternLibrary();
|
||||
if (!query || lib.patterns.length === 0) return [];
|
||||
|
||||
const lower = query.toLowerCase();
|
||||
|
||||
return lib.patterns
|
||||
.filter(function (p) {
|
||||
return p.name.toLowerCase().includes(lower) ||
|
||||
(p.common_tech_stack || []).some(function (t) { return t.toLowerCase().includes(lower); });
|
||||
})
|
||||
.sort(function (a, b) { return (b.frequency || 0) - (a.frequency || 0); });
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具:合并去重数组
|
||||
*/
|
||||
function mergeArrays(arr1, arr2) {
|
||||
return [...new Set((arr1 || []).concat(arr2 || []))];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
analyzeAndUpdatePatterns,
|
||||
queryPatterns,
|
||||
loadPatternLibrary,
|
||||
detectPatternType,
|
||||
detectTechStack
|
||||
};
|
||||
|
|
@ -6,6 +6,9 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const modelRouter = require('./model-router');
|
||||
const knowledgeExtractor = require('./knowledge-extractor');
|
||||
const patternAnalyzer = require('./pattern-analyzer');
|
||||
const memoryInjector = require('./memory-injector');
|
||||
|
||||
const PERSONA_CONFIG_PATH = path.join(__dirname, '..', '..', 'brain', 'persona-config.json');
|
||||
|
||||
|
|
@ -154,15 +157,41 @@ async function respond({ dev_id, message, history, memory, isGreeting }) {
|
|||
return getLocalReply(message, memory, config, isGuest);
|
||||
}
|
||||
|
||||
const systemPrompt = buildSystemPrompt(config, memory, devInfo);
|
||||
// 查询知识库和模式库,为回复提供参考
|
||||
let knowledgeContext = '';
|
||||
try {
|
||||
const relevantKnowledge = knowledgeExtractor.queryKnowledge(message, 3);
|
||||
if (relevantKnowledge.length > 0) {
|
||||
knowledgeContext += '\n\n## 系统知识库参考(不要直接暴露给用户,作为回复参考)\n';
|
||||
relevantKnowledge.forEach(function (k) {
|
||||
knowledgeContext += '- [' + k.type + '] ' + k.title + '\n';
|
||||
});
|
||||
}
|
||||
|
||||
const relevantPatterns = patternAnalyzer.queryPatterns(message);
|
||||
if (relevantPatterns.length > 0) {
|
||||
knowledgeContext += '\n## 已知高频模式\n';
|
||||
relevantPatterns.slice(0, 3).forEach(function (p) {
|
||||
knowledgeContext += '- ' + p.name + '(使用 ' + p.frequency + ' 次,成功率 ' + p.success_rate + '%,常用技术栈:' + (p.common_tech_stack || []).join(', ') + ')\n';
|
||||
});
|
||||
}
|
||||
} catch (_e) { /* knowledge/pattern query failed silently */ }
|
||||
|
||||
const systemPrompt = buildSystemPrompt(config, memory, devInfo) + knowledgeContext;
|
||||
|
||||
// 使用记忆注入构建五层 system prompt
|
||||
const injectedPrompt = memoryInjector.buildInjectedSystemPrompt(
|
||||
systemPrompt, dev_id, memory, history
|
||||
);
|
||||
|
||||
// 构建消息列表
|
||||
const messages = [
|
||||
{ role: 'system', content: systemPrompt }
|
||||
{ role: 'system', content: injectedPrompt }
|
||||
];
|
||||
|
||||
// 加入最近历史(最多 20 条)
|
||||
const recentHistory = (history || []).slice(-20);
|
||||
// 使用滑动窗口加入最近历史
|
||||
const windowSize = memoryInjector.getSlidingWindowSize();
|
||||
const recentHistory = (history || []).slice(-windowSize);
|
||||
recentHistory.forEach(function (msg) {
|
||||
messages.push({
|
||||
role: msg.role === 'user' ? 'user' : 'assistant',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* persona-studio · 用户画像自动学习引擎
|
||||
*
|
||||
* 触发:每轮对话结束后自动调用 updateProfile()
|
||||
* 功能:从对话内容中推断用户技术水平、沟通风格、设计偏好等
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||||
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
|
||||
|
||||
/**
|
||||
* 确保用户目录存在
|
||||
*/
|
||||
function ensureDevDir(devId) {
|
||||
const dir = path.join(MEMORY_DIR, devId);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载用户画像
|
||||
*/
|
||||
function loadProfile(devId) {
|
||||
const dir = ensureDevDir(devId);
|
||||
const file = path.join(dir, 'profile.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
} catch {
|
||||
return createInitialProfile(devId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户画像
|
||||
*/
|
||||
function saveProfile(devId, profile) {
|
||||
const dir = ensureDevDir(devId);
|
||||
const file = path.join(dir, 'profile.json');
|
||||
profile.updated_at = new Date().toISOString();
|
||||
fs.writeFileSync(file, JSON.stringify(profile, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建初始画像
|
||||
*/
|
||||
function createInitialProfile(devId) {
|
||||
return {
|
||||
dev_id: devId,
|
||||
version: 1,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: null,
|
||||
tech_assessment: {
|
||||
level: null,
|
||||
known_skills: [],
|
||||
learning_skills: [],
|
||||
weakness: [],
|
||||
growth_velocity: null,
|
||||
assessment_confidence: 0
|
||||
},
|
||||
communication_style: {
|
||||
verbosity: null,
|
||||
prefers_examples: null,
|
||||
question_frequency: null,
|
||||
patience_level: null
|
||||
},
|
||||
design_preferences: {
|
||||
color_scheme: null,
|
||||
style_keywords: [],
|
||||
layout_preference: null
|
||||
},
|
||||
project_patterns: {
|
||||
avg_requirements_rounds: 0,
|
||||
decision_speed: null,
|
||||
change_frequency: 0,
|
||||
total_projects: 0
|
||||
},
|
||||
emotional_profile: {
|
||||
current_mood: 'neutral',
|
||||
satisfaction_trend: [],
|
||||
frustration_triggers: [],
|
||||
delight_triggers: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户消息中推断技术技能
|
||||
*/
|
||||
function inferSkills(message) {
|
||||
const skills = [];
|
||||
const skillMap = {
|
||||
'javascript': ['javascript', 'js', 'node', 'nodejs', 'npm'],
|
||||
'typescript': ['typescript', 'ts'],
|
||||
'python': ['python', 'pip', 'django', 'flask', 'fastapi'],
|
||||
'react': ['react', 'jsx', 'tsx', 'hooks', 'useState', 'useEffect'],
|
||||
'vue': ['vue', 'vuex', 'pinia', 'nuxt'],
|
||||
'html': ['html', 'dom', 'div', 'css'],
|
||||
'css': ['css', 'scss', 'sass', 'tailwind', 'styled'],
|
||||
'sql': ['sql', 'mysql', 'postgres', 'sqlite', '数据库', 'database'],
|
||||
'git': ['git', 'github', 'gitlab', 'commit', 'branch'],
|
||||
'docker': ['docker', 'container', '容器', 'k8s', 'kubernetes'],
|
||||
'api': ['api', 'rest', 'graphql', 'grpc', '接口'],
|
||||
'mobile': ['android', 'ios', 'flutter', 'react native', '移动端', '小程序']
|
||||
};
|
||||
|
||||
const lowerMsg = message.toLowerCase();
|
||||
for (const [skill, keywords] of Object.entries(skillMap)) {
|
||||
if (keywords.some(function (kw) { return lowerMsg.includes(kw); })) {
|
||||
skills.push(skill);
|
||||
}
|
||||
}
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断沟通风格
|
||||
*/
|
||||
function inferCommunicationStyle(message) {
|
||||
const style = {};
|
||||
const msgLen = message.length;
|
||||
|
||||
// 简洁度
|
||||
if (msgLen < 20) {
|
||||
style.verbosity = 'concise';
|
||||
} else if (msgLen > 200) {
|
||||
style.verbosity = 'detailed';
|
||||
}
|
||||
|
||||
// 偏好示例
|
||||
if (/给.*例子|示例|比如|example|举个/i.test(message)) {
|
||||
style.prefers_examples = true;
|
||||
}
|
||||
|
||||
// 提问频率
|
||||
if ((message.match(/[??]/g) || []).length >= 2) {
|
||||
style.question_frequency = 'high';
|
||||
}
|
||||
|
||||
// 直接命令式 = patience_level low
|
||||
if (/直接告诉|直接给|快速|尽快|赶紧/i.test(message)) {
|
||||
style.patience_level = 'low';
|
||||
style.verbosity = 'concise';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断技术水平
|
||||
*/
|
||||
function inferTechLevel(message) {
|
||||
const advancedKeywords = ['架构', '微服务', '分布式', '高并发', 'CI/CD', 'kubernetes', '设计模式', 'design pattern', '重构', '性能优化', 'SSR', 'ISR', 'WebSocket', 'gRPC'];
|
||||
const intermediateKeywords = ['组件', '路由', '中间件', 'middleware', '接口', 'API', '数据库', '前后端', '部署', '框架'];
|
||||
const beginnerKeywords = ['怎么开始', '入门', '新手', '不太懂', '什么是', '帮我做', '教我'];
|
||||
|
||||
const lower = message.toLowerCase();
|
||||
|
||||
if (advancedKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
|
||||
return 'advanced';
|
||||
}
|
||||
if (intermediateKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
|
||||
return 'intermediate';
|
||||
}
|
||||
if (beginnerKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
|
||||
return 'beginner';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断设计偏好
|
||||
*/
|
||||
function inferDesignPreferences(message) {
|
||||
const prefs = { style_keywords: [] };
|
||||
|
||||
if (/暗色|深色|dark|黑色/i.test(message)) {
|
||||
prefs.color_scheme = 'dark';
|
||||
} else if (/亮色|浅色|light|白色/i.test(message)) {
|
||||
prefs.color_scheme = 'light';
|
||||
}
|
||||
|
||||
const styleKeywords = ['简约', '科技感', '可爱', '商务', '极简', 'minimal', '现代', 'modern', '复古', '扁平', 'flat', '渐变', '毛玻璃', '赛博朋克'];
|
||||
styleKeywords.forEach(function (kw) {
|
||||
if (message.toLowerCase().includes(kw.toLowerCase())) {
|
||||
prefs.style_keywords.push(kw);
|
||||
}
|
||||
});
|
||||
|
||||
return prefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断情感信号
|
||||
*/
|
||||
function inferEmotionalSignals(message) {
|
||||
const signals = {};
|
||||
|
||||
if (/太好了|完美|厉害|赞|棒|不错|满意|感谢|谢谢|开心|👍|🎉|✅/i.test(message)) {
|
||||
signals.current_mood = 'positive';
|
||||
} else if (/不行|不对|错了|差|不满|失望|难用|麻烦|头疼|崩溃|😤|😡|❌/i.test(message)) {
|
||||
signals.current_mood = 'negative';
|
||||
} else {
|
||||
signals.current_mood = 'neutral';
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:更新用户画像
|
||||
* @param {string} devId - 开发编号
|
||||
* @param {string} userMessage - 用户消息
|
||||
* @param {string} assistantReply - 助手回复
|
||||
*/
|
||||
function updateProfile(devId, userMessage, assistantReply) {
|
||||
if (!devId || devId === 'GUEST') return null;
|
||||
|
||||
const profile = loadProfile(devId);
|
||||
|
||||
// 1. 推断技术技能
|
||||
const skills = inferSkills(userMessage);
|
||||
if (skills.length > 0) {
|
||||
const known = new Set(profile.tech_assessment.known_skills || []);
|
||||
skills.forEach(function (s) { known.add(s); });
|
||||
profile.tech_assessment.known_skills = Array.from(known);
|
||||
profile.tech_assessment.assessment_confidence = Math.min(
|
||||
(profile.tech_assessment.assessment_confidence || 0) + 5, 100
|
||||
);
|
||||
}
|
||||
|
||||
// 2. 推断技术水平
|
||||
const techLevel = inferTechLevel(userMessage);
|
||||
if (techLevel) {
|
||||
profile.tech_assessment.level = techLevel;
|
||||
}
|
||||
|
||||
// 3. 推断沟通风格
|
||||
const commStyle = inferCommunicationStyle(userMessage);
|
||||
Object.keys(commStyle).forEach(function (key) {
|
||||
if (commStyle[key] != null) {
|
||||
profile.communication_style[key] = commStyle[key];
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 推断设计偏好
|
||||
const designPrefs = inferDesignPreferences(userMessage);
|
||||
if (designPrefs.color_scheme) {
|
||||
profile.design_preferences.color_scheme = designPrefs.color_scheme;
|
||||
}
|
||||
if (designPrefs.style_keywords.length > 0) {
|
||||
const existing = new Set(profile.design_preferences.style_keywords || []);
|
||||
designPrefs.style_keywords.forEach(function (kw) { existing.add(kw); });
|
||||
profile.design_preferences.style_keywords = Array.from(existing);
|
||||
}
|
||||
|
||||
// 5. 推断情感信号
|
||||
const emotions = inferEmotionalSignals(userMessage);
|
||||
profile.emotional_profile.current_mood = emotions.current_mood;
|
||||
const trend = profile.emotional_profile.satisfaction_trend || [];
|
||||
trend.push({ mood: emotions.current_mood, at: new Date().toISOString() });
|
||||
if (trend.length > 50) trend.splice(0, trend.length - 50);
|
||||
profile.emotional_profile.satisfaction_trend = trend;
|
||||
|
||||
// 6. 更新版本
|
||||
profile.version = (profile.version || 0) + 1;
|
||||
|
||||
saveProfile(devId, profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateProfile,
|
||||
loadProfile,
|
||||
saveProfile,
|
||||
createInitialProfile,
|
||||
inferSkills,
|
||||
inferTechLevel,
|
||||
inferCommunicationStyle,
|
||||
inferDesignPreferences,
|
||||
inferEmotionalSignals
|
||||
};
|
||||
|
|
@ -50,6 +50,9 @@ router.post('/message', async (req, res) => {
|
|||
|
||||
// 更新最后话题
|
||||
memoryManager.updateLastTopic(dev_id, message);
|
||||
|
||||
// 自动更新用户画像
|
||||
memoryManager.updateProfile(dev_id, message, result.reply);
|
||||
}
|
||||
|
||||
res.json({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"schema_version": "1.0",
|
||||
"description": "系统进化日志 · 记录每次自进化事件",
|
||||
"last_updated": null,
|
||||
"total_events": 0,
|
||||
"events": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"schema_version": "1.0",
|
||||
"description": "系统知识库 · 自动积累 · 每次代码生成/方案确认/技术问题解决时更新",
|
||||
"last_updated": null,
|
||||
"total_entries": 0,
|
||||
"entries": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"schema_version": "1.0",
|
||||
"description": "系统模式库 · 自动识别 · 每完成一个项目+每日聚合更新",
|
||||
"last_updated": null,
|
||||
"total_patterns": 0,
|
||||
"patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"schema_version": "1.0",
|
||||
"description": "质量评分 · 反馈驱动 · 项目完成后自动评分",
|
||||
"last_updated": null,
|
||||
"total_scores": 0,
|
||||
"scores": []
|
||||
}
|
||||
Loading…
Reference in New Issue