317 lines
9.4 KiB
JavaScript
Raw Normal View History

// exe-engine/src/router/age-router.js
// EXE-Engine · AGE-Router 路由网关
// 接收所有 AI 执行请求,进行鉴权、分类、路由
// PRJ-EXE-001 · Phase 0 · ZY-EXE-P0-001
// 版权:国作登字-2026-A-00037559
'use strict';
const { randomUUID } = require('crypto');
// HLI 501 存根路由Phase 0 占位,后续 Phase 实现)
const { personaStubs } = require('./stubs/persona-stub');
const { userStubs } = require('./stubs/user-stub');
const { ticketStubs } = require('./stubs/ticket-stub');
const { dialogueStubs } = require('./stubs/dialogue-stub');
const { storageStubs } = require('./stubs/storage-stub');
const { dashboardStubs } = require('./stubs/dashboard-stub');
/**
* AGE-Router 路由网关
*
* 本体论锚定路由网关 = 笔的分流阀
* 所有墨水需求都从这里进入网关决定用哪种墨水多少量
*
* 职责
* 1. 请求鉴权验证用户身份 + 资源池额度
* 2. 任务分类代码生成 / 文本处理 / 数据分析 / Agent 指令
* 3. 模型选择通过 LoadBalancer 选择最优模型
* 4. 限流降级资源池耗尽时的优雅降级策略
*/
class AGERouter {
/**
* @param {object} deps
* @param {LoadBalancer} deps.loadBalancer 负载均衡器
* @param {ResourceMeter} deps.resourceMeter 资源计量器
* @param {ContextCache} deps.contextCache 上下文缓存
* @param {AgentController} deps.agentController Agent 调度器
* @param {object} [deps.poolConfig] 资源池配置
*/
constructor(deps) {
this._loadBalancer = deps.loadBalancer;
this._resourceMeter = deps.resourceMeter;
this._contextCache = deps.contextCache;
this._agentController = deps.agentController;
this._poolConfig = deps.poolConfig || {};
// 限流计数器 { agentId: { count, resetAt } }
this._rateLimits = new Map();
// HLI 存根路由注册表(天眼路由扫描识别用)
this._hliStubs = [
...personaStubs,
...userStubs,
...ticketStubs,
...dialogueStubs,
...storageStubs,
...dashboardStubs
];
}
/**
* 执行 AI 请求统一入口
*
* 对应 API: POST /api/exe/v1/execute
*
* @param {object} request
* @param {string} request.agentId Agent ID
* @param {string} request.taskType 任务类型
* @param {string} request.prompt Prompt
* @param {string} [request.systemPrompt] 系统 Prompt
* @param {object} [request.context] 上下文
* @param {object} [request.preferences] 偏好 { model, priority, maxTokens }
* @param {string} [request.resourcePool] 资源池 ID
* @param {string} [request.apiKey] BYOK 模式的用户 Key
* @returns {Promise<object>} EXE 标准响应
*/
async execute(request) {
const requestId = `exe-${randomUUID().slice(0, 12)}`;
const startTime = Date.now();
try {
// 1. 限流检查
this._checkRateLimit(request.agentId, request.resourcePool);
// 2. 尝试加载缓存上下文
const cacheKey = `${request.agentId}:${request.taskType}`;
const cachedContext = this._contextCache.get(cacheKey);
if (cachedContext && !request.context) {
request.context = cachedContext;
}
// 3. 通过 LoadBalancer 选择适配器
const adapter = this._loadBalancer.select({
taskType: request.taskType,
model: request.preferences?.model || 'auto',
priority: request.preferences?.priority || 'balanced'
});
if (!adapter) {
return this._buildErrorResponse(requestId, startTime, 'NO_AVAILABLE_MODEL',
'没有可用的模型适配器,所有模型可能处于冷却期');
}
// 4. 执行推理
const result = await adapter.execute({
prompt: request.prompt,
systemPrompt: request.systemPrompt,
taskType: request.taskType,
maxTokens: request.preferences?.maxTokens || 4096,
temperature: request.preferences?.temperature
});
// 5. 记录计量数据
const mode = request.apiKey ? 'byok' : 'pool';
this._resourceMeter.record({
requestId,
agentId: request.agentId,
model: result.model,
taskType: request.taskType,
resourcePool: request.resourcePool || 'default',
mode,
inputTokens: result.usage.inputTokens,
outputTokens: result.usage.outputTokens,
cost: result.usage.cost,
costUnit: result.usage.unit,
latency: result.latency,
status: 'success'
});
// 6. 更新上下文缓存
if (request.context) {
this._contextCache.set(cacheKey, request.context);
}
// 7. 返回标准响应
return {
requestId,
model: result.model,
output: result.output,
usage: result.usage,
latency: result.latency,
status: 'success'
};
} catch (err) {
// 尝试故障转移
const failoverResult = await this._tryFailover(request, requestId, startTime, err);
if (failoverResult) return failoverResult;
// 记录失败
this._resourceMeter.record({
requestId,
agentId: request.agentId,
model: 'unknown',
taskType: request.taskType,
resourcePool: request.resourcePool || 'default',
mode: request.apiKey ? 'byok' : 'pool',
inputTokens: 0,
outputTokens: 0,
cost: 0,
costUnit: 'CNY',
latency: Date.now() - startTime,
status: 'error'
});
return this._buildErrorResponse(requestId, startTime, 'EXECUTION_FAILED', err.message);
}
}
/**
* 获取系统状态
* @returns {object}
*/
getStatus() {
return {
router: 'AGE-Router v1.0.0',
project: 'PRJ-EXE-001',
phase: 'Phase 0',
adapters: this._loadBalancer.getStatus(),
meter: this._resourceMeter.getSummary(),
cache: this._contextCache.getStatus(),
agents: this._agentController.getRegisteredAgents(),
hliStubs: this._hliStubs.map(s => ({
routeId: s.routeId,
path: s.path,
phase: s.phase,
status: 'stub_501'
}))
};
}
/**
* 处理 HLI 存根路由请求
* @param {string} routePath 路由路径 /hli/persona/load
* @returns {object|null} 501 响应不匹配返回 null
*/
async handleStubRoute(routePath) {
const stub = this._hliStubs.find(s => s.path === routePath);
if (!stub) return null;
return stub.handler({});
}
/**
* 获取所有已注册的 HLI 存根路由
* @returns {Array}
*/
getHLIStubs() {
return this._hliStubs;
}
// ── 内部方法 ──
/**
* 限流检查
* @param {string} agentId
* @param {string} poolId
*/
_checkRateLimit(agentId, poolId) {
const key = agentId || 'anonymous';
const now = Date.now();
let limiter = this._rateLimits.get(key);
if (!limiter || now >= limiter.resetAt) {
limiter = { count: 0, resetAt: now + 60000 }; // 每分钟窗口
this._rateLimits.set(key, limiter);
}
limiter.count++;
// 获取资源池的速率限制
const pool = this._poolConfig.pools?.[poolId || 'default'];
const limit = pool?.rateLimitPerMinute || 30;
if (limiter.count > limit) {
throw new Error(`[限流] Agent ${key} 每分钟请求数超限 (${limiter.count}/${limit})`);
}
}
/**
* 故障转移
* @param {object} request
* @param {string} requestId
* @param {number} startTime
* @param {Error} originalError
* @returns {Promise<object|null>}
*/
async _tryFailover(request, requestId, startTime, originalError) {
try {
const failoverAdapter = this._loadBalancer.failover(
request.preferences?.model || 'unknown',
request.taskType
);
if (!failoverAdapter) return null;
const result = await failoverAdapter.execute({
prompt: request.prompt,
systemPrompt: request.systemPrompt,
taskType: request.taskType,
maxTokens: request.preferences?.maxTokens || 4096
});
this._resourceMeter.record({
requestId,
agentId: request.agentId,
model: result.model,
taskType: request.taskType,
resourcePool: request.resourcePool || 'default',
mode: request.apiKey ? 'byok' : 'pool',
inputTokens: result.usage.inputTokens,
outputTokens: result.usage.outputTokens,
cost: result.usage.cost,
costUnit: result.usage.unit,
latency: result.latency,
status: 'success'
});
return {
requestId,
model: result.model,
output: result.output,
usage: result.usage,
latency: result.latency,
status: 'success',
_failover: true,
_originalError: originalError.message
};
} catch (failoverErr) {
// Failover also failed - return null to trigger error response
this._lastFailoverError = failoverErr.message;
return null;
}
}
/**
* 构建错误响应
* @param {string} requestId
* @param {number} startTime
* @param {string} code
* @param {string} message
* @returns {object}
*/
_buildErrorResponse(requestId, startTime, code, message) {
return {
requestId,
model: null,
output: null,
usage: { inputTokens: 0, outputTokens: 0, cost: 0, unit: 'CNY' },
latency: Date.now() - startTime,
status: 'error',
error: { code, message }
};
}
}
module.exports = AGERouter;