zhizhi/server/app/modules/smart-router.js

208 lines
6.3 KiB
JavaScript
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 · Smart Model Router
* ═══════════════════════════════════════════════════════════
*
* 编号: ZY-MODEL-ROUTER-002
* 守护: 铸渊 · ICE-GL-ZY001
*
* 根据用户说什么来决定调用便宜的模型还是深度推理模型
* 同时追踪API调用成本和次数
*/
'use strict';
// ─── 模型定价表(元/百万token ───
const MODEL_PRICING = {
'deepseek-chat': { input: 1.0, output: 2.0, tier: 'economy', name: 'DeepSeek-V3' },
'deepseek-reasoner': { input: 4.0, output: 16.0, tier: 'premium', name: 'DeepSeek-R1' },
'qwen-turbo': { input: 0.3, output: 0.6, tier: 'economy', name: '通义千问-Turbo' },
'qwen-plus': { input: 0.8, output: 2.0, tier: 'economy', name: '通义千问-Plus' },
'qwen-max': { input: 2.0, output: 6.0, tier: 'standard', name: '通义千问-Max' },
'gpt-4o-mini': { input: 0.15, output: 0.6, tier: 'economy', name: 'GPT-4o-mini' },
'gpt-4o': { input: 2.5, output: 10.0, tier: 'premium', name: 'GPT-4o' },
'claude-3-5-sonnet': { input: 3.0, output: 15.0, tier: 'premium', name: 'Claude-3.5-Sonnet' },
'claude-3-haiku': { input: 0.25, output: 1.25, tier: 'economy', name: 'Claude-3-Haiku' }
};
// ─── 深度推理触发关键词 ───
const DEEP_REASONING_PATTERNS = [
/分析|推理|评估|审查|review|analyze/i,
/架构|设计|重构|方案|strategy/i,
/为什么|原因|解释.*原理|how.*work/i,
/复杂|困难|棘手|tricky|complex/i,
/比较|对比|权衡|trade.?off/i,
/安全|漏洞|vulnerability|security/i,
/调试|debug|排查|诊断|diagnose/i,
/优化|性能|performance|bottleneck/i
];
// ─── 简单对话关键词 ───
const SIMPLE_CHAT_PATTERNS = [
/你好|hi|hello|嗨|在吗/i,
/谢谢|感谢|thank/i,
/是的|好的|对|ok|确认|没问题/i,
/帮我.*写|生成|创建|create|generate/i,
/查询|查看|获取|get|fetch|list/i,
/翻译|translate/i
];
// ─── 代码生成关键词 ───
const CODE_PATTERNS = [
/写代码|编写|实现|implement|code/i,
/函数|function|方法|method|class/i,
/接口|api|路由|route|endpoint/i,
/部署|deploy|发布|build|构建/i,
/修复|fix|bug|报错|error/i
];
// ─── API调用统计 ───
const usageStats = {
totalCalls: 0,
totalInputTokens: 0,
totalOutputTokens: 0,
totalCostCNY: 0,
byModel: {},
byTier: { economy: 0, standard: 0, premium: 0 },
hourlyRate: [],
startTime: Date.now()
};
/**
* 分析用户输入,决定使用哪个模型
* @param {string} userMessage - 用户说的话
* @param {object} context - 上下文信息
* @returns {object} 模型选择结果
*/
function routeModel(userMessage, context = {}) {
const { messageCount = 0, isFirstMessage = false, userId = null } = context;
const msgLen = userMessage.length;
let selectedModel = 'deepseek-chat'; // 默认便宜模型
let reason = '默认对话';
let tier = 'economy';
let temperature = 0.7;
let maxTokens = 2000;
// 1. 检查是否需要深度推理
const needsDeepReasoning = DEEP_REASONING_PATTERNS.some(p => p.test(userMessage));
if (needsDeepReasoning && msgLen > 50) {
selectedModel = 'deepseek-reasoner';
reason = '检测到深度推理需求';
tier = 'premium';
temperature = 0.3;
maxTokens = 4000;
}
// 2. 检查是否是代码相关
else if (CODE_PATTERNS.some(p => p.test(userMessage))) {
selectedModel = 'deepseek-chat';
reason = '代码生成任务';
tier = 'economy';
temperature = 0.3;
maxTokens = 4000;
}
// 3. 简单对话用最便宜的
else if (SIMPLE_CHAT_PATTERNS.some(p => p.test(userMessage)) || msgLen < 20) {
selectedModel = 'qwen-turbo';
reason = '简单对话';
tier = 'economy';
temperature = 0.8;
maxTokens = 1000;
}
// 4. 中等长度的普通对话
else if (msgLen < 200) {
selectedModel = 'deepseek-chat';
reason = '普通对话';
tier = 'economy';
temperature = 0.7;
maxTokens = 2000;
}
// 5. 长消息,可能需要更好的理解
else {
selectedModel = 'deepseek-chat';
reason = '长文本理解';
tier = 'economy';
temperature = 0.5;
maxTokens = 3000;
}
const pricing = MODEL_PRICING[selectedModel] || MODEL_PRICING['deepseek-chat'];
return {
model: selectedModel,
modelName: pricing.name,
reason,
tier,
temperature,
maxTokens,
estimatedCost: {
inputPer1k: (pricing.input / 1000).toFixed(6),
outputPer1k: (pricing.output / 1000).toFixed(6),
currency: 'CNY'
}
};
}
/**
* 记录API调用
*/
function recordUsage(model, inputTokens, outputTokens) {
const pricing = MODEL_PRICING[model] || MODEL_PRICING['deepseek-chat'];
// 价格单位: 元/百万token → cost = tokens * (元/百万token) / 1000000
const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1000000;
usageStats.totalCalls++;
usageStats.totalInputTokens += inputTokens;
usageStats.totalOutputTokens += outputTokens;
usageStats.totalCostCNY += cost;
if (!usageStats.byModel[model]) {
usageStats.byModel[model] = { calls: 0, tokens: 0, cost: 0 };
}
usageStats.byModel[model].calls++;
usageStats.byModel[model].tokens += inputTokens + outputTokens;
usageStats.byModel[model].cost += cost;
usageStats.byTier[pricing.tier] = (usageStats.byTier[pricing.tier] || 0) + 1;
// 记录小时速率
const hour = new Date().getHours();
if (!usageStats.hourlyRate[hour]) usageStats.hourlyRate[hour] = 0;
usageStats.hourlyRate[hour]++;
return { cost, model, inputTokens, outputTokens };
}
/**
* 获取使用统计
*/
function getUsageStats() {
const uptimeHours = (Date.now() - usageStats.startTime) / 3600000;
return {
...usageStats,
uptimeHours: Math.round(uptimeHours * 10) / 10,
avgCallsPerHour: uptimeHours > 0 ? Math.round(usageStats.totalCalls / uptimeHours * 10) / 10 : 0,
totalCostCNY: Math.round(usageStats.totalCostCNY * 10000) / 10000
};
}
/**
* 获取模型定价表
*/
function getPricingTable() {
return MODEL_PRICING;
}
module.exports = {
routeModel,
recordUsage,
getUsageStats,
getPricingTable,
MODEL_PRICING
};