guanghulab/llm-engine.js

179 lines
5.5 KiB
JavaScript
Raw Normal View History

// LLM自动检测引擎·llm-engine.js·v1.0
// HoloLake·M-DINGTALK Phase 7
// DEV-004 之之 × 秋秋
var axios = require('axios');
var LLM_API_KEY = process.env.LLM_API_KEY || '';
var LLM_BASE_URL = (process.env.LLM_BASE_URL || 'https://api.anthropic.com/v1').replace(/\/$/, '');
// ===== 模型优先级队列 =====
var PREFERRED_MODELS = [
'gpt-4o',
'claude-3-5-sonnet',
'claude-3-5-sonnet-20241022',
'anthropic/claude-3.5-sonnet',
'claude-3-sonnet',
'claude-3-haiku',
'deepseek-chat',
'deepseek-v3',
'gpt-4o-mini'
];
async function discoverModels() {
try {
var res = await axios.get(LLM_BASE_URL + '/models', {
headers: { 'Authorization': 'Bearer ' + LLM_API_KEY },
timeout: 10000
});
var models = (res.data && res.data.data) || [];
console.log('[LLM] 发现 ' + models.length + ' 个可用模型');
return models;
} catch (err) {
console.log('[LLM] △ 模型发现失败: ' + err.message + ',使用默认模型');
return [];
}
}
function selectBestModel(models) {
if (models.length === 0) return 'gpt-4o';
var available = models.map(function(m) { return m.id.toLowerCase(); });
for (var i = 0; i < PREFERRED_MODELS.length; i++) {
var preferred = PREFERRED_MODELS[i].toLowerCase();
var match = available.find(function(id) { return id.includes(preferred); });
if (match) {
var original = models.find(function(m) { return m.id.toLowerCase() === match; });
return original ? original.id : match;
}
}
var anyClaude = available.find(function(id) { return id.includes('claude'); });
if (anyClaude) {
var orig = models.find(function(m) { return m.id.toLowerCase() === anyClaude; });
return orig ? orig.id : anyClaude;
}
return models[0] ? models[0].id : 'gpt-4o';
}
async function detectApiFormat() {
try {
var res = await axios.post(LLM_BASE_URL + '/chat/completions', {
model: 'gpt-4o',
messages: [{ role: 'user', content: 'ping' }],
max_tokens: 5
}, {
headers: {
'Authorization': 'Bearer ' + LLM_API_KEY,
'Content-Type': 'application/json'
},
timeout: 15000,
validateStatus: function(s) { return s < 500; }
});
if (res.status < 500) return 'openai-compat';
} catch (e) {}
try {
var res2 = await axios.post(LLM_BASE_URL + '/messages', {
model: 'gpt-4o',
messages: [{ role: 'user', content: 'ping' }],
max_tokens: 5
}, {
headers: {
'x-api-key': LLM_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json'
},
timeout: 15000,
validateStatus: function(s) { return s < 500; }
});
if (res2.status < 500) return 'anthropic-native';
} catch (e) {}
return 'openai-compat';
}
async function callLLM(systemPrompt, userMessage, options) {
options = options || {};
var maxTokens = options.maxTokens || 4000;
var models = await discoverModels();
var model = options.model || selectBestModel(models);
var format = await detectApiFormat();
console.log('[LLM] 调用: model=' + model + ' format=' + format + ' platform=' + LLM_BASE_URL);
var response;
if (format === 'openai-compat') {
response = await axios.post(LLM_BASE_URL + '/chat/completions', {
model: model,
max_tokens: maxTokens,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
]
}, {
headers: {
'Authorization': 'Bearer ' + LLM_API_KEY,
'Content-Type': 'application/json'
},
timeout: 120000
});
var text = response.data.choices &&
response.data.choices[0] &&
response.data.choices[0].message &&
response.data.choices[0].message.content;
return { text: text || '', model: model, format: format };
} else {
response = await axios.post(LLM_BASE_URL + '/messages', {
model: model,
max_tokens: maxTokens,
system: systemPrompt,
messages: [
{ role: 'user', content: userMessage }
]
}, {
headers: {
'x-api-key': LLM_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json'
},
timeout: 120000
});
var text2 = response.data.content &&
response.data.content[0] &&
response.data.content[0].text;
return { text: text2 || '', model: model, format: format };
}
}
async function healthCheck() {
try {
var models = await discoverModels();
var best = selectBestModel(models);
var format = await detectApiFormat();
return {
status: 'ok',
model_count: models.length,
selected_model: best,
api_format: format,
base_url: LLM_BASE_URL,
has_key: !!LLM_API_KEY
};
} catch (err) {
return { status: 'error', error: err.message };
}
}
module.exports = {
callLLM: callLLM,
discoverModels: discoverModels,
selectBestModel: selectBestModel,
detectApiFormat: detectApiFormat,
healthCheck: healthCheck
};