guanghulab/llm-engine.js
2026-05-10 13:12:44 +08:00

179 lines
5.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.

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