guanghulab/exe-engine/src/balancer/load-balancer.js

176 lines
4.5 KiB
JavaScript
Raw Normal View History

// exe-engine/src/balancer/load-balancer.js
// EXE-Engine · 负载均衡器
// 根据策略选择最优模型适配器
// PRJ-EXE-001 · Phase 0
// 版权:国作登字-2026-A-00037559
'use strict';
/**
* 负载均衡器
*
* 本体论锚定均衡器 = 选墨水的智慧
* 不同的字需要不同的墨水均衡器知道哪瓶墨水最合适
*
* 策略
* cost 成本优先满足质量阈值前提下选最便宜的
* quality 质量优先选能力最强的模型
* balanced 均衡成本权重 0.6 + 质量权重 0.4
*/
class LoadBalancer {
/**
* @param {Map<string, BaseAdapter>} adapters 已注册的适配器 Map
* @param {object} modelConfig 模型配置 taskModelMapping
*/
constructor(adapters, modelConfig) {
this._adapters = adapters;
this._modelConfig = modelConfig;
}
/**
* 根据请求选择最优适配器
*
* @param {object} request
* @param {string} request.taskType 任务类型
* @param {string} [request.model] 指定模型 ('auto' 则自动选择)
* @param {string} [request.priority] 优先策略 (cost | balanced | quality)
* @returns {BaseAdapter|null}
*/
select(request) {
const { taskType, model, priority = 'balanced' } = request;
// 用户指定了具体模型
if (model && model !== 'auto') {
const adapter = this._adapters.get(model);
if (adapter && !adapter.isInCooldown()) {
return adapter;
}
// 指定模型不可用,尝试 fallback
}
// 根据任务类型获取候选模型列表
const candidates = this._getCandidates(taskType);
if (candidates.length === 0) {
return null;
}
// 按优先策略排序
return this._selectByPriority(candidates, priority);
}
/**
* 故障转移获取指定模型的下一个备选
*
* @param {string} failedModel 失败的模型名
* @param {string} taskType 任务类型
* @returns {BaseAdapter|null}
*/
failover(failedModel, taskType) {
const candidates = this._getCandidates(taskType)
.filter(a => a.name !== failedModel);
if (candidates.length === 0) return null;
return candidates[0];
}
/**
* 获取所有适配器状态
* @returns {object[]}
*/
getStatus() {
const status = [];
for (const [, adapter] of this._adapters) {
status.push(adapter.getStatus());
}
return status;
}
// ── 内部方法 ──
/**
* 获取可用候选适配器
* @param {string} taskType
* @returns {BaseAdapter[]}
*/
_getCandidates(taskType) {
const mapping = this._modelConfig.taskModelMapping || {};
const modelNames = mapping[taskType] || Object.keys(this._modelConfig.models || {});
return modelNames
.map(name => this._adapters.get(name))
.filter(a => a && !a.isInCooldown());
}
/**
* 按优先策略选择
* @param {BaseAdapter[]} candidates
* @param {string} priority
* @returns {BaseAdapter}
*/
_selectByPriority(candidates, priority) {
if (priority === 'cost') {
return this._selectLowestCost(candidates);
}
if (priority === 'quality') {
// 质量优先 = 列表中第一个(配置中按质量排序)
return candidates[0];
}
// balanced: 加权评分
return this._selectBalanced(candidates);
}
/**
* 选择成本最低的适配器
* @param {BaseAdapter[]} candidates
* @returns {BaseAdapter}
*/
_selectLowestCost(candidates) {
let best = candidates[0];
let bestCost = this._avgCost(best);
for (let i = 1; i < candidates.length; i++) {
const cost = this._avgCost(candidates[i]);
if (cost < bestCost) {
best = candidates[i];
bestCost = cost;
}
}
return best;
}
/**
* 均衡选择成本 0.6 + 位置 0.4
* @param {BaseAdapter[]} candidates
* @returns {BaseAdapter}
*/
_selectBalanced(candidates) {
let best = null;
let bestScore = Infinity;
for (let i = 0; i < candidates.length; i++) {
const costScore = this._avgCost(candidates[i]);
const positionScore = i; // 位置越前,质量越高
const score = costScore * 0.6 + positionScore * 0.4;
if (score < bestScore) {
best = candidates[i];
bestScore = score;
}
}
return best;
}
/**
* 计算平均每百万 token 成本
* @param {BaseAdapter} adapter
* @returns {number}
*/
_avgCost(adapter) {
const c = adapter.costPerToken;
return ((c.input || 0) + (c.output || 0)) / 2;
}
}
module.exports = LoadBalancer;