239 lines
10 KiB
JavaScript
239 lines
10 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* 批量生成真实路由反馈数据 (v5.3)
|
|||
|
|
*
|
|||
|
|
* 基于 50 个技能的典型查询场景,使用 route-analyzer 实际评分,
|
|||
|
|
* 生成路由反馈记录,验证并训练自适应学习系统。
|
|||
|
|
*
|
|||
|
|
* 用法:
|
|||
|
|
* node generate-routing-feedback.js 生成反馈 + 学习
|
|||
|
|
* node generate-routing-feedback.js --dry-run 仅预览不写入
|
|||
|
|
* node generate-routing-feedback.js --json JSON 输出
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
let PATHS;
|
|||
|
|
try {
|
|||
|
|
PATHS = require('./paths.config.js').PATHS;
|
|||
|
|
} catch {
|
|||
|
|
const ROOT = __dirname.replace(/[/\\]scripts$/, '');
|
|||
|
|
PATHS = {
|
|||
|
|
root: ROOT,
|
|||
|
|
debugDir: path.join(ROOT, 'debug'),
|
|||
|
|
skillsIndexJson: path.join(ROOT, 'skills-index.json'),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const FEEDBACK_FILE = path.join(PATHS.debugDir, 'route-feedback.jsonl');
|
|||
|
|
const WEIGHTS_FILE = path.join(PATHS.debugDir, 'route-weights.json');
|
|||
|
|
|
|||
|
|
// ─── 典型查询场景 (每个技能 1-2 条) ────────────────────
|
|||
|
|
const SCENARIOS = [
|
|||
|
|
// AI / 数据
|
|||
|
|
{ query: 'PyTorch 训练 BERT 模型微调', expected: 'ai-ml-expert' },
|
|||
|
|
{ query: 'LLM RAG 检索增强生成', expected: 'ai-ml-expert' },
|
|||
|
|
{ query: 'pandas 数据清洗 A/B 测试分析', expected: 'data-analyst-expert' },
|
|||
|
|
{ query: 'Spark ETL 数据管道 Kafka 消费', expected: 'data-engineer-expert' },
|
|||
|
|
|
|||
|
|
// 开发
|
|||
|
|
{ query: 'React 组件 Hooks useMemo 性能', expected: 'frontend-expert' },
|
|||
|
|
{ query: 'Vue3 Composition API Pinia 状态管理', expected: 'frontend-expert' },
|
|||
|
|
{ query: 'Node.js Express REST API 中间件', expected: 'backend-builder' },
|
|||
|
|
{ query: 'React Native 移动端适配 iOS Android', expected: 'mobile-expert' },
|
|||
|
|
{ query: '微信小程序 Taro 跨端开发', expected: 'miniprogram-expert' },
|
|||
|
|
{ query: '通用编程 算法 数据结构 代码解释', expected: 'developer-expert' },
|
|||
|
|
{ query: 'Bug 排查 错误诊断 堆栈分析', expected: 'debugger-expert' },
|
|||
|
|
{ query: 'OAuth 2.0 Webhook 第三方 API 集成', expected: 'api-integration-specialist' },
|
|||
|
|
{ query: '正则表达式 Shell 脚本 批量重命名', expected: 'regex-shell-wizard' },
|
|||
|
|
{ query: '双重审查 生产级代码 代码质量', expected: 'ultimate-code-expert' },
|
|||
|
|
{ query: 'Playwright 浏览器自动化 网页抓取', expected: 'browser-automation-expert' },
|
|||
|
|
|
|||
|
|
// 架构
|
|||
|
|
{ query: '系统架构设计 DDD 领域驱动 ADR', expected: 'architect-expert' },
|
|||
|
|
{ query: 'SQL 索引优化 慢查询 explain 分析', expected: 'database-tuning-expert' },
|
|||
|
|
{ query: 'Kubernetes Istio 服务网格 GitOps', expected: 'cloud-native-expert' },
|
|||
|
|
{ query: 'Cloudflare Workers 边缘函数 Deno Deploy', expected: 'edge-computing-expert' },
|
|||
|
|
{ query: 'Core Web Vitals LCP FCP 首屏优化', expected: 'performance-expert' },
|
|||
|
|
{ query: '变更影响分析 依赖图 爆炸半径', expected: 'impact-analyst' },
|
|||
|
|
{ query: 'Mermaid 流程图 PlantUML 架构图', expected: 'diagram-as-code-expert' },
|
|||
|
|
{ query: '零缺陷守护 Pinning Test 安全重构', expected: 'zero-defect-guardian' },
|
|||
|
|
|
|||
|
|
// DevOps
|
|||
|
|
{ query: 'CI/CD 流水线 Docker 镜像构建', expected: 'devops-expert' },
|
|||
|
|
{ query: 'SAST DAST 容器安全扫描 SBOM', expected: 'devsecops-expert' },
|
|||
|
|
{ query: 'Git rebase 分支管理 merge 冲突', expected: 'git-operation-master' },
|
|||
|
|
{ query: 'SLI SLO 监控告警 事故响应 Postmortem', expected: 'sre-expert' },
|
|||
|
|
|
|||
|
|
// 安全
|
|||
|
|
{ query: 'OWASP XSS SQL注入 JWT 安全', expected: 'security-expert' },
|
|||
|
|
|
|||
|
|
// 质量
|
|||
|
|
{ query: 'Jest 单元测试 Vitest mock TDD', expected: 'tester-expert' },
|
|||
|
|
{ query: 'Code Review 代码审查 技术债评估', expected: 'reviewer-expert' },
|
|||
|
|
{ query: '项目全栈审计 上线前检查清单', expected: 'project-audit-expert' },
|
|||
|
|
|
|||
|
|
// 产品 / 设计
|
|||
|
|
{ query: 'PRD 需求文档 RICE KANO 路线图', expected: 'product-manager-expert' },
|
|||
|
|
{ query: 'UI UX 设计系统 Figma WCAG 无障碍', expected: 'designer-expert' },
|
|||
|
|
{ query: '用户访谈 可用性测试 Persona', expected: 'ux-researcher' },
|
|||
|
|
{ query: '甘特图 Sprint 计划 里程碑 风险管理', expected: 'project-coordinator' },
|
|||
|
|
|
|||
|
|
// 商业 / 研究
|
|||
|
|
{ query: '商业计划书 融资材料 商业模式画布', expected: 'business-plan-skill' },
|
|||
|
|
{ query: '记账 税务 现金流 报价单', expected: 'finance-advisor' },
|
|||
|
|
{ query: '销售漏斗 CRM 客户谈判技巧', expected: 'sales-consultant' },
|
|||
|
|
{ query: 'SaaS 定价策略 免费增值 提价', expected: 'pricing-strategist' },
|
|||
|
|
{ query: 'SLA 客户成功 Onboarding 流失预警', expected: 'customer-success-expert' },
|
|||
|
|
{ query: 'AARRR 增长模型 A/B 测试 裂变营销', expected: 'growth-hacker' },
|
|||
|
|
{ query: '投资评估 尽职调查 估值分析', expected: 'investor-review-guide' },
|
|||
|
|
{ query: '行业调研 市场规模 竞品分析', expected: 'industry-research-cn' },
|
|||
|
|
{ query: '合同审查 合规 知识产权 数据保护', expected: 'legal-review-skill' },
|
|||
|
|
|
|||
|
|
// 内容 / 传播
|
|||
|
|
{ query: 'API 文档 README 用户手册', expected: 'tech-writer-expert' },
|
|||
|
|
{ query: '营销文案 落地页 CTA 转化率', expected: 'copywriter-expert' },
|
|||
|
|
{ query: '商务邮件 冷邮件 催款邮件', expected: 'email-communicator' },
|
|||
|
|
{ query: '新媒体运营 内容日历 KOL 合作', expected: 'social-media-manager' },
|
|||
|
|
{ query: 'sitemap robots.txt JSON-LD SEO', expected: 'technical-seo-expert' },
|
|||
|
|
|
|||
|
|
// 元技能
|
|||
|
|
{ query: '从零开发新项目 全生命周期', expected: 'genesis-engine' },
|
|||
|
|
{ query: '提示词优化 结构化 Prompt', expected: 'prompt-optimizer' },
|
|||
|
|
{ query: '团队管理 1on1 晋升 招聘面试', expected: 'tech-lead-mentor' },
|
|||
|
|
|
|||
|
|
// 额外混合场景 (测试歧义处理)
|
|||
|
|
{ query: 'Go 微服务 gRPC protobuf', expected: 'backend-builder' },
|
|||
|
|
{ query: 'Tailwind CSS 响应式布局', expected: 'frontend-expert' },
|
|||
|
|
{ query: 'PostgreSQL 分区表 索引优化', expected: 'database-tuning-expert' },
|
|||
|
|
{ query: '渗透测试 漏洞利用 安全审计', expected: 'security-expert' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// ─── 主流程 ──────────────────────────────────────────
|
|||
|
|
function main() {
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
const isDryRun = args.includes('--dry-run');
|
|||
|
|
const jsonMode = args.includes('--json');
|
|||
|
|
|
|||
|
|
// 加载 route-analyzer
|
|||
|
|
let routeAnalyzer;
|
|||
|
|
try {
|
|||
|
|
routeAnalyzer = require('./route-analyzer.js');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('无法加载 route-analyzer.js:', e.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const indexPath = PATHS.skillsIndexJson || path.join(PATHS.root, 'skills-index.json');
|
|||
|
|
if (!fs.existsSync(indexPath)) {
|
|||
|
|
console.error('skills-index.json 未找到');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|||
|
|
const bm25Params = routeAnalyzer.buildBM25Params(index);
|
|||
|
|
|
|||
|
|
// 评估每个场景
|
|||
|
|
const results = [];
|
|||
|
|
let correct = 0;
|
|||
|
|
let incorrect = 0;
|
|||
|
|
|
|||
|
|
for (const scenario of SCENARIOS) {
|
|||
|
|
const tokens = routeAnalyzer.tokenize(scenario.query);
|
|||
|
|
const scores = index.skills.map(skill => {
|
|||
|
|
const { totalScore } = routeAnalyzer.scoreSkill(skill, tokens, bm25Params);
|
|||
|
|
return { name: skill.name, score: totalScore };
|
|||
|
|
}).sort((a, b) => b.score - a.score);
|
|||
|
|
|
|||
|
|
const normalized = routeAnalyzer.normalizeScores(scores).slice(0, 5);
|
|||
|
|
const topSkill = normalized[0]?.name || 'none';
|
|||
|
|
const topConfidence = normalized[0]?.confidence || 0;
|
|||
|
|
const isCorrect = topSkill === scenario.expected;
|
|||
|
|
|
|||
|
|
if (isCorrect) correct++;
|
|||
|
|
else incorrect++;
|
|||
|
|
|
|||
|
|
results.push({
|
|||
|
|
query: scenario.query,
|
|||
|
|
expected: scenario.expected,
|
|||
|
|
routedTo: topSkill,
|
|||
|
|
confidence: topConfidence,
|
|||
|
|
correct: isCorrect,
|
|||
|
|
candidates: normalized.slice(0, 3).map(r => `${r.name}(${(r.confidence * 100).toFixed(0)}%)`),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const accuracy = (correct / SCENARIOS.length * 100).toFixed(1);
|
|||
|
|
|
|||
|
|
if (!isDryRun) {
|
|||
|
|
// 写入反馈记录
|
|||
|
|
if (!fs.existsSync(PATHS.debugDir)) fs.mkdirSync(PATHS.debugDir, { recursive: true });
|
|||
|
|
|
|||
|
|
// 清空旧种子数据,写入真实反馈
|
|||
|
|
const feedbackEntries = [];
|
|||
|
|
for (const r of results) {
|
|||
|
|
const entry = {
|
|||
|
|
ts: new Date().toISOString(),
|
|||
|
|
query: r.query,
|
|||
|
|
routedTo: r.routedTo,
|
|||
|
|
correctedTo: r.expected,
|
|||
|
|
topConfidence: r.confidence,
|
|||
|
|
queryTokens: Array.from(routeAnalyzer.tokenize(r.query)),
|
|||
|
|
type: r.correct ? 'confirm' : 'correction',
|
|||
|
|
};
|
|||
|
|
feedbackEntries.push(JSON.stringify(entry));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fs.writeFileSync(FEEDBACK_FILE, feedbackEntries.join('\n') + '\n');
|
|||
|
|
|
|||
|
|
// 触发权重学习
|
|||
|
|
try {
|
|||
|
|
const routeFeedback = require('./route-feedback.js');
|
|||
|
|
routeFeedback.learnWeights();
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 输出
|
|||
|
|
if (jsonMode) {
|
|||
|
|
console.log(JSON.stringify({
|
|||
|
|
total: SCENARIOS.length,
|
|||
|
|
correct,
|
|||
|
|
incorrect,
|
|||
|
|
accuracy: accuracy + '%',
|
|||
|
|
misroutes: results.filter(r => !r.correct).map(r => ({
|
|||
|
|
query: r.query,
|
|||
|
|
expected: r.expected,
|
|||
|
|
got: r.routedTo,
|
|||
|
|
candidates: r.candidates,
|
|||
|
|
})),
|
|||
|
|
}, null, 2));
|
|||
|
|
} else {
|
|||
|
|
console.log(`\n=== 路由反馈生成 (${isDryRun ? '预览' : '已写入'}) ===\n`);
|
|||
|
|
console.log(`总场景: ${SCENARIOS.length}`);
|
|||
|
|
console.log(`正确: ${correct} (${accuracy}%)`);
|
|||
|
|
console.log(`错误: ${incorrect}`);
|
|||
|
|
|
|||
|
|
if (incorrect > 0) {
|
|||
|
|
console.log('\n误路由:');
|
|||
|
|
for (const r of results.filter(r => !r.correct)) {
|
|||
|
|
console.log(` "${r.query}"`);
|
|||
|
|
console.log(` 期望: ${r.expected}, 实际: ${r.routedTo} (${(r.confidence * 100).toFixed(0)}%)`);
|
|||
|
|
console.log(` 候选: ${r.candidates.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isDryRun) {
|
|||
|
|
console.log(`\n反馈已写入: ${FEEDBACK_FILE}`);
|
|||
|
|
console.log(`权重已学习: ${WEIGHTS_FILE}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (typeof module !== 'undefined') {
|
|||
|
|
module.exports = { SCENARIOS, main };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (require.main === module) {
|
|||
|
|
main();
|
|||
|
|
}
|