154 lines
3.9 KiB
JavaScript
154 lines
3.9 KiB
JavaScript
|
|
/**
|
|||
|
|
* LLM Router · 模块入口(完整实现)
|
|||
|
|
* GH-GMP-005 · M2 · Agent搬迁工程
|
|||
|
|
*
|
|||
|
|
* 大模型路由调度 · 通义千问API · 模型降级
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const QwenClient = require('./qwen-client');
|
|||
|
|
|
|||
|
|
const MODULE_NAME = 'llm-router';
|
|||
|
|
const MODULE_VERSION = '1.0.0';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 模型路由配置
|
|||
|
|
*/
|
|||
|
|
const MODEL_ROUTES = {
|
|||
|
|
reasoning: {
|
|||
|
|
model: 'qwen-plus',
|
|||
|
|
maxTokens: 4000,
|
|||
|
|
temperature: 0.7,
|
|||
|
|
description: '通用推理:工单分析、回执生成、讨论回复',
|
|||
|
|
},
|
|||
|
|
coding: {
|
|||
|
|
model: 'qwen-coder-turbo',
|
|||
|
|
maxTokens: 8000,
|
|||
|
|
temperature: 0.3,
|
|||
|
|
description: '代码生成:生成代码文件、代码审查',
|
|||
|
|
},
|
|||
|
|
simple: {
|
|||
|
|
model: 'qwen-turbo',
|
|||
|
|
maxTokens: 1000,
|
|||
|
|
temperature: 0.5,
|
|||
|
|
description: '简单任务:状态更新、格式化、短回复',
|
|||
|
|
},
|
|||
|
|
thinking: {
|
|||
|
|
model: 'qwen-plus',
|
|||
|
|
maxTokens: 8000,
|
|||
|
|
temperature: 0.8,
|
|||
|
|
description: '深度思考:架构设计、复杂决策',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 模型降级顺序
|
|||
|
|
*/
|
|||
|
|
const FALLBACK_ORDER = ['qwen-plus', 'qwen-turbo'];
|
|||
|
|
|
|||
|
|
let qwenClient = null;
|
|||
|
|
let logger = console;
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
name: MODULE_NAME,
|
|||
|
|
version: MODULE_VERSION,
|
|||
|
|
type: 'service',
|
|||
|
|
MODEL_ROUTES,
|
|||
|
|
|
|||
|
|
async init(context) {
|
|||
|
|
logger = (context && context.logger) || console;
|
|||
|
|
logger.info(`[${MODULE_NAME}] 初始化中 v${MODULE_VERSION}...`);
|
|||
|
|
|
|||
|
|
qwenClient = new QwenClient({ logger });
|
|||
|
|
|
|||
|
|
const hasKey = !!process.env.GH_LLM_API_KEY;
|
|||
|
|
logger.info(`[${MODULE_NAME}] API Key: ${hasKey ? '已配置 ✅' : '❌ 未配置'}`);
|
|||
|
|
|
|||
|
|
if (!hasKey) {
|
|||
|
|
logger.warn(`[${MODULE_NAME}] 无API Key,LLM功能将不可用`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { status: hasKey ? 'ok' : 'warning', hasApiKey: hasKey };
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async start(context) {
|
|||
|
|
logger.info(`[${MODULE_NAME}] LLM路由就绪 · ${Object.keys(MODEL_ROUTES).length} 条路由`);
|
|||
|
|
return { status: 'running' };
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async stop() {
|
|||
|
|
logger.info(`[${MODULE_NAME}] LLM路由停止`);
|
|||
|
|
return { status: 'stopped' };
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
async healthCheck() {
|
|||
|
|
return {
|
|||
|
|
status: process.env.GH_LLM_API_KEY ? 'ok' : 'warning',
|
|||
|
|
module: MODULE_NAME,
|
|||
|
|
version: MODULE_VERSION,
|
|||
|
|
routes: Object.keys(MODEL_ROUTES),
|
|||
|
|
stats: qwenClient ? qwenClient.stats : null,
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// ─── 对外接口 ───
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 按路由类型调用LLM
|
|||
|
|
* @param {string} routeType - 'reasoning'|'coding'|'simple'|'thinking'
|
|||
|
|
* @param {Array} messages - [{role, content}]
|
|||
|
|
* @returns {Promise<{content: string, usage: object, model: string}>}
|
|||
|
|
*/
|
|||
|
|
async chat(routeType, messages) {
|
|||
|
|
const route = MODEL_ROUTES[routeType];
|
|||
|
|
if (!route) {
|
|||
|
|
throw new Error(`[${MODULE_NAME}] 未知路由类型: ${routeType}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试首选模型
|
|||
|
|
try {
|
|||
|
|
return await qwenClient.chat({
|
|||
|
|
model: route.model,
|
|||
|
|
messages,
|
|||
|
|
maxTokens: route.maxTokens,
|
|||
|
|
temperature: route.temperature,
|
|||
|
|
});
|
|||
|
|
} catch (err) {
|
|||
|
|
logger.warn(
|
|||
|
|
`[${MODULE_NAME}] ${route.model} 调用失败 · 尝试降级 · ${err.message}`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 降级到备选模型
|
|||
|
|
for (const fallback of FALLBACK_ORDER) {
|
|||
|
|
if (fallback === route.model) continue;
|
|||
|
|
try {
|
|||
|
|
logger.info(`[${MODULE_NAME}] 降级到 ${fallback}`);
|
|||
|
|
return await qwenClient.chat({
|
|||
|
|
model: fallback,
|
|||
|
|
messages,
|
|||
|
|
maxTokens: route.maxTokens,
|
|||
|
|
temperature: route.temperature,
|
|||
|
|
});
|
|||
|
|
} catch (fallbackErr) {
|
|||
|
|
logger.warn(`[${MODULE_NAME}] ${fallback} 也失败 · ${fallbackErr.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new Error(`[${MODULE_NAME}] 所有模型均不可用`);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 直接指定模型调用(不走路由)
|
|||
|
|
*/
|
|||
|
|
async chatDirect({ model, messages, maxTokens, temperature }) {
|
|||
|
|
return qwenClient.chat({ model, messages, maxTokens, temperature });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取QwenClient实例
|
|||
|
|
*/
|
|||
|
|
getClient: () => qwenClient,
|
|||
|
|
};
|