268 lines
13 KiB
JavaScript
268 lines
13 KiB
JavaScript
// src/brain/model-router.js
|
||
// 任务型模型路由器 — 核心脑升级
|
||
// 职责:根据任务类型、上下文长度、成本、可用性选择最优模型
|
||
//
|
||
// 适配 PoloAPI (poloai.top) 可用模型 — 2026-04-23 更新
|
||
// 分组:Deepseek(0.8x) | Claude-官(5x) | Gemini-优质临时(3x) | gpt-openai(5x) | qwen千问(0.8x) | kimi(0.6x) | glm(0.8x)
|
||
|
||
'use strict';
|
||
|
||
/**
|
||
* PoloAPI 可用模型完整注册表
|
||
* 从 poloai.top/pricing 模型广场实际确认 (2026-04-23)
|
||
*/
|
||
const MODEL_REGISTRY = {
|
||
// ——— DeepSeek 系列 (Deepseek 分组 0.8x) ———
|
||
'deepseek-v3': { provider: 'deepseek', type: 'chat', cost: 'low', ctx: 64000, note: '通用对话,性价比最高' },
|
||
'deepseek-v3.1': { provider: 'deepseek', type: 'chat', cost: 'low', ctx: 64000, note: '通用对话升级版' },
|
||
'deepseek-v3.2': { provider: 'deepseek', type: 'chat', cost: 'low', ctx: 64000, note: '最新通用对话' },
|
||
'deepseek-v3.2-exp': { provider: 'deepseek', type: 'chat', cost: 'low', ctx: 64000, note: '实验版' },
|
||
'deepseek-r1': { provider: 'deepseek', type: 'reasoning', cost: 'medium', ctx: 64000, note: '推理型,逻辑分析强' },
|
||
'deepseek-r1-0528': { provider: 'deepseek', type: 'reasoning', cost: 'medium', ctx: 64000, note: '推理型指定版本' },
|
||
'deepseek-r1-distill-qwen-32b': { provider: 'deepseek', type: 'reasoning', cost: 'low', ctx: 32000, note: '轻量推理' },
|
||
'deepseek-r1-distill-qwen-14b': { provider: 'deepseek', type: 'reasoning', cost: 'low', ctx: 32000, note: '轻量推理' },
|
||
'deepseek-r1-distill-qwen-7b': { provider: 'deepseek', type: 'reasoning', cost: 'vlow', ctx: 32000, note: '超轻量推理' },
|
||
'deepseek-r1-distill-qwen-1.5b': { provider: 'deepseek', type: 'reasoning', cost: 'vlow', ctx: 16000, note: '极轻量推理' },
|
||
'deepseek-r1-distill-llama-8b': { provider: 'deepseek', type: 'reasoning', cost: 'low', ctx: 32000, note: 'Llama架构推理' },
|
||
|
||
// ——— Claude 系列 (Claude-官 分组 5x) ———
|
||
'claude-3.7-sonnet': { provider: 'anthropic', type: 'chat', cost: 'medium', ctx: 200000, note: '均衡型,代码+文案都强' },
|
||
'claude-3.7-sonnet-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'medium', ctx: 200000, note: '带思考链' },
|
||
'claude-3.5-haiku': { provider: 'anthropic', type: 'chat', cost: 'high', ctx: 200000, note: '快速响应' },
|
||
'claude-3.5-haiku-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'high', ctx: 200000, note: '快速+思考链' },
|
||
'claude-3.5-sonnet-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'high', ctx: 200000, note: 'Sonnet思考链' },
|
||
'claude-opus-4': { provider: 'anthropic', type: 'reasoning', cost: 'vhigh', ctx: 200000, note: '最强推理,复杂任务' },
|
||
'claude-opus-4-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'vhigh', ctx: 200000, note: 'Opus思考链' },
|
||
'claude-opus-4-6': { provider: 'anthropic', type: 'reasoning', cost: 'high', ctx: 200000, note: 'Opus 4.6' },
|
||
'claude-opus-4-6-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'high', ctx: 200000, note: 'Opus 4.6思考链' },
|
||
'claude-opus-4-7': { provider: 'anthropic', type: 'reasoning', cost: 'high', ctx: 200000, note: 'Opus 4.7 最新' },
|
||
'claude-haiku-4-5-20251001': { provider: 'anthropic', type: 'chat', cost: 'medium', ctx: 200000, note: 'Haiku 4.5' },
|
||
'claude-haiku-4-5-20251001-thinking': { provider: 'anthropic', type: 'reasoning', cost: 'medium', ctx: 200000, note: 'Haiku 4.5思考链' },
|
||
'claude-3-haiku-20240307': { provider: 'anthropic', type: 'chat', cost: 'low', ctx: 200000, note: '老版Haiku,便宜' },
|
||
|
||
// ——— Gemini 系列 (Gemini-优质临时 分组 3x) ———
|
||
'gemini-2.5-flash': { provider: 'google', type: 'chat', cost: 'vlow', ctx: 1000000, note: '超快超便宜' },
|
||
'gemini-2.5-flash-thinking': { provider: 'google', type: 'reasoning', cost: 'vlow', ctx: 1000000, note: 'Flash思考链' },
|
||
'gemini-2.5-flash-preview-09-2025': { provider: 'google', type: 'chat', cost: 'vlow', ctx: 1000000, note: 'Flash预览版' },
|
||
'gemini-2.5-pro': { provider: 'google', type: 'chat', cost: 'low', ctx: 1000000, note: '长上下文之王' },
|
||
'gemini-2.5-pro-thinking': { provider: 'google', type: 'reasoning', cost: 'low', ctx: 1000000, note: 'Pro思考链' },
|
||
'gemini-2.5-pro-nothinking': { provider: 'google', type: 'chat', cost: 'low', ctx: 1000000, note: 'Pro无思考链' },
|
||
'gemini-2.5-pro-thinking-512': { provider: 'google', type: 'reasoning', cost: 'high', ctx: 1000000, note: 'Pro 512K思考' },
|
||
'gemini-3-flash': { provider: 'google', type: 'chat', cost: 'vlow', ctx: 1000000, note: 'Gemini 3 Flash' },
|
||
'gemini-3-flash-preview': { provider: 'google', type: 'chat', cost: 'vlow', ctx: 1000000, note: 'Gemini 3 Flash预览' },
|
||
'gemini-3-flash-preview-thinking':{ provider: 'google', type: 'reasoning', cost: 'vlow', ctx: 1000000, note: 'Gemini 3 Flash思考链' },
|
||
'gemini-3-pro': { provider: 'google', type: 'chat', cost: 'medium', ctx: 1000000, note: 'Gemini 3 Pro' },
|
||
'gemini-3-pro-preview': { provider: 'google', type: 'chat', cost: 'medium', ctx: 1000000, note: 'Gemini 3 Pro预览' },
|
||
'gemini-3-pro-preview-thinking': { provider: 'google', type: 'reasoning', cost: 'medium', ctx: 1000000, note: 'Gemini 3 Pro思考链' },
|
||
|
||
// ——— GPT 系列 (gpt-openai 分组 5x) ———
|
||
'gpt-4o-mini': { provider: 'openai', type: 'chat', cost: 'low', ctx: 128000, note: '性价比极高' },
|
||
'gpt-4o': { provider: 'openai', type: 'chat', cost: 'medium', ctx: 128000, note: '全能型' },
|
||
'gpt-4-turbo': { provider: 'openai', type: 'chat', cost: 'medium', ctx: 128000, note: '快速版GPT-4' },
|
||
'gpt-4-turbo-2024-04-09': { provider: 'openai', type: 'chat', cost: 'medium', ctx: 128000, note: 'GPT-4 Turbo指定版' },
|
||
'gpt-4': { provider: 'openai', type: 'chat', cost: 'high', ctx: 8192, note: '经典GPT-4' },
|
||
'gpt-4-all': { provider: 'openai', type: 'chat', cost: 'high', ctx: 128000, note: 'GPT-4全能版' },
|
||
'gpt-4-32k': { provider: 'openai', type: 'chat', cost: 'vhigh', ctx: 32768, note: 'GPT-4 32K上下文' },
|
||
'gpt-3.5-turbo': { provider: 'openai', type: 'chat', cost: 'vlow', ctx: 16384, note: '最便宜GPT' },
|
||
'gpt-3.5-turbo-16k': { provider: 'openai', type: 'chat', cost: 'vlow', ctx: 16384, note: 'GPT-3.5长文本' },
|
||
'gpt-4-dalle': { provider: 'openai', type: 'image', cost: 'medium', ctx: 0, note: 'DALL-E绘图' },
|
||
};
|
||
|
||
/**
|
||
* 任务-模型路由映射表
|
||
* 所有模型名已适配 PoloAPI 实际可用名称
|
||
*
|
||
* 成本优先级说明(含分组倍率):
|
||
* Deepseek 0.8x → 实际最便宜
|
||
* Gemini 3x → 基础价低但倍率中
|
||
* GPT 5x → 倍率最高
|
||
* Claude 5x → 倍率最高
|
||
*/
|
||
const ROUTING_TABLE = {
|
||
chat: {
|
||
description: '普通对话 — 均衡性价比',
|
||
preferred: { provider: 'poloai', model: 'deepseek-v3', temperature: 0.8, max_tokens: 2000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'gemini-2.5-flash', temperature: 0.8, max_tokens: 2000 },
|
||
{ provider: 'poloai', model: 'gpt-4o-mini', temperature: 0.8, max_tokens: 2000 },
|
||
{ provider: 'poloai', model: 'gpt-3.5-turbo', temperature: 0.8, max_tokens: 2000 },
|
||
],
|
||
context_budget: 64000,
|
||
},
|
||
build: {
|
||
description: '写代码 / 构建 — 代码能力优先',
|
||
preferred: { provider: 'poloai', model: 'deepseek-v3.2', temperature: 0.3, max_tokens: 4000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'claude-3.7-sonnet', temperature: 0.3, max_tokens: 4000 },
|
||
{ provider: 'poloai', model: 'deepseek-v3', temperature: 0.3, max_tokens: 4000 },
|
||
{ provider: 'poloai', model: 'gpt-4-turbo', temperature: 0.3, max_tokens: 4000 },
|
||
],
|
||
context_budget: 64000,
|
||
},
|
||
review: {
|
||
description: '审查 / 分析 — 推理能力优先',
|
||
preferred: { provider: 'poloai', model: 'deepseek-r1', temperature: 0.5, max_tokens: 3000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'claude-3.7-sonnet-thinking', temperature: 0.5, max_tokens: 3000 },
|
||
{ provider: 'poloai', model: 'gemini-2.5-pro-thinking', temperature: 0.5, max_tokens: 3000 },
|
||
{ provider: 'poloai', model: 'deepseek-r1-0528', temperature: 0.5, max_tokens: 3000 },
|
||
],
|
||
context_budget: 64000,
|
||
},
|
||
brain: {
|
||
description: '脑记忆整理 — 低温稳定输出',
|
||
preferred: { provider: 'poloai', model: 'deepseek-v3', temperature: 0.2, max_tokens: 2000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'gemini-2.5-flash', temperature: 0.2, max_tokens: 2000 },
|
||
{ provider: 'poloai', model: 'gpt-4o-mini', temperature: 0.2, max_tokens: 2000 },
|
||
],
|
||
context_budget: 32000,
|
||
},
|
||
long: {
|
||
description: '长文档 / 系统总结 — 长上下文优先',
|
||
preferred: { provider: 'poloai', model: 'gemini-2.5-pro', temperature: 0.5, max_tokens: 4000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'gemini-2.5-flash', temperature: 0.5, max_tokens: 4000 },
|
||
{ provider: 'poloai', model: 'deepseek-v3', temperature: 0.5, max_tokens: 4000 },
|
||
{ provider: 'poloai', model: 'claude-3.7-sonnet', temperature: 0.5, max_tokens: 4000 },
|
||
],
|
||
context_budget: 1000000,
|
||
},
|
||
creative: {
|
||
description: '创作 / 文案 — 创意能力优先',
|
||
preferred: { provider: 'poloai', model: 'claude-3.7-sonnet', temperature: 0.9, max_tokens: 4000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'deepseek-v3.2', temperature: 0.9, max_tokens: 4000 },
|
||
{ provider: 'poloai', model: 'gpt-4o-mini', temperature: 0.9, max_tokens: 4000 },
|
||
],
|
||
context_budget: 64000,
|
||
},
|
||
deep_research: {
|
||
description: '深度研究 — 复杂问题深度分析',
|
||
preferred: { provider: 'poloai', model: 'o4-mini-deep-research', temperature: 0.5, max_tokens: 8000 },
|
||
fallbacks: [
|
||
{ provider: 'poloai', model: 'deepseek-r1', temperature: 0.5, max_tokens: 8000 },
|
||
{ provider: 'poloai', model: 'claude-opus-4', temperature: 0.5, max_tokens: 8000 },
|
||
],
|
||
context_budget: 128000,
|
||
},
|
||
};
|
||
|
||
// 模型失败记录 — 最近失败的模型暂时降低优先级
|
||
const failureLog = new Map();
|
||
const FAILURE_COOLDOWN_MS = 5 * 60 * 1000; // 5分钟冷却
|
||
|
||
function recordFailure(provider, model) {
|
||
const key = provider + '/' + model;
|
||
const entry = failureLog.get(key) || { count: 0, lastFail: 0 };
|
||
entry.count++;
|
||
entry.lastFail = Date.now();
|
||
failureLog.set(key, entry);
|
||
}
|
||
|
||
function isInCooldown(provider, model) {
|
||
const key = provider + '/' + model;
|
||
const entry = failureLog.get(key);
|
||
if (!entry) return false;
|
||
if (Date.now() - entry.lastFail > FAILURE_COOLDOWN_MS) {
|
||
failureLog.delete(key);
|
||
return false;
|
||
}
|
||
return entry.count >= 3;
|
||
}
|
||
|
||
function selectModel(mode, opts = {}) {
|
||
const { contextLength = 0, isGuest = false, preferModel = null } = opts;
|
||
|
||
// 如果指定了模型,直接使用
|
||
if (preferModel && MODEL_REGISTRY[preferModel]) {
|
||
const reg = MODEL_REGISTRY[preferModel];
|
||
return {
|
||
provider: 'poloai',
|
||
model: preferModel,
|
||
temperature: 0.7,
|
||
max_tokens: 2000,
|
||
context_budget: reg.ctx,
|
||
via: 'user-specified',
|
||
};
|
||
}
|
||
|
||
// 访客强制使用最低成本配置
|
||
if (isGuest) {
|
||
return {
|
||
provider: 'poloai',
|
||
model: 'deepseek-v3',
|
||
temperature: 0.8,
|
||
max_tokens: 1500,
|
||
context_budget: 64000,
|
||
via: 'guest-fixed',
|
||
};
|
||
}
|
||
|
||
const effectiveMode = (contextLength > 60000 && mode !== 'long') ? 'long' : mode;
|
||
const route = ROUTING_TABLE[effectiveMode] || ROUTING_TABLE.chat;
|
||
|
||
if (!isInCooldown(route.preferred.provider, route.preferred.model)) {
|
||
return {
|
||
...route.preferred,
|
||
context_budget: route.context_budget,
|
||
via: 'preferred',
|
||
};
|
||
}
|
||
|
||
for (const fb of route.fallbacks) {
|
||
if (!isInCooldown(fb.provider, fb.model)) {
|
||
return {
|
||
...fb,
|
||
context_budget: route.context_budget,
|
||
via: 'fallback',
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
...route.preferred,
|
||
context_budget: route.context_budget,
|
||
via: 'forced-retry',
|
||
};
|
||
}
|
||
|
||
function getRoutingTable() {
|
||
return ROUTING_TABLE;
|
||
}
|
||
|
||
function getModelRegistry() {
|
||
return MODEL_REGISTRY;
|
||
}
|
||
|
||
function getModelsByType(type) {
|
||
return Object.entries(MODEL_REGISTRY)
|
||
.filter(([, info]) => info.type === type)
|
||
.map(([name, info]) => ({ name, ...info }));
|
||
}
|
||
|
||
function getFailureStatus() {
|
||
const status = {};
|
||
for (const [key, entry] of failureLog.entries()) {
|
||
const inCooldown = Date.now() - entry.lastFail < FAILURE_COOLDOWN_MS && entry.count >= 3;
|
||
status[key] = {
|
||
failures: entry.count,
|
||
lastFail: new Date(entry.lastFail).toISOString(),
|
||
inCooldown,
|
||
};
|
||
}
|
||
return status;
|
||
}
|
||
|
||
module.exports = {
|
||
selectModel,
|
||
recordFailure,
|
||
isInCooldown,
|
||
getRoutingTable,
|
||
getModelRegistry,
|
||
getModelsByType,
|
||
getFailureStatus,
|
||
ROUTING_TABLE,
|
||
MODEL_REGISTRY,
|
||
};
|