guanghulab/exe-engine/src/balancer/load-balancer.js
2026-05-10 13:12:44 +08:00

176 lines
4.5 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.

// 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;