179 lines
5.5 KiB
JavaScript
179 lines
5.5 KiB
JavaScript
// 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
|
||
};
|