chore: форматирование модулей + план по шагам
This commit is contained in:
+134
-134
@@ -24,163 +24,163 @@ const MODEL = process.env.LLM_MODEL || 'gpt-oss-120b';
|
||||
|
||||
// ── поиск ключа: первая строка, начинающаяся с sk-, после строки, содержащей 'https' ──
|
||||
function findApiKey() {
|
||||
const lines = fs.readFileSync(KEY_FILE, 'utf8').split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const t = lines[i].trim();
|
||||
if (t.toLowerCase().includes('https') || t.toLowerCase().includes('aillm')) {
|
||||
// дальше ищем ближайший sk-... в следующих 5 строках
|
||||
for (let j = i + 1; j < Math.min(lines.length, i + 6); j++) {
|
||||
const k = lines[j].trim();
|
||||
if (k.startsWith('sk-')) return k;
|
||||
}
|
||||
const lines = fs.readFileSync(KEY_FILE, 'utf8').split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const t = lines[i].trim();
|
||||
if (t.toLowerCase().includes('https') || t.toLowerCase().includes('aillm')) {
|
||||
// дальше ищем ближайший sk-... в следующих 5 строках
|
||||
for (let j = i + 1; j < Math.min(lines.length, i + 6); j++) {
|
||||
const k = lines[j].trim();
|
||||
if (k.startsWith('sk-')) return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback: первый sk-* в файле
|
||||
for (const l of lines) {
|
||||
const t = l.trim();
|
||||
if (t.startsWith('sk-')) return t;
|
||||
}
|
||||
return null;
|
||||
// fallback: первый sk-* в файле
|
||||
for (const l of lines) {
|
||||
const t = l.trim();
|
||||
if (t.startsWith('sk-')) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const API_KEY = findApiKey();
|
||||
|
||||
function log(label, obj) {
|
||||
console.log(`\n=== ${label} ===`);
|
||||
console.log(typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
||||
console.log(`\n=== ${label} ===`);
|
||||
console.log(typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
async function chat(body, { timeoutMs = 60000 } = {}) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
const text = await resp.text();
|
||||
let json = null;
|
||||
try { json = JSON.parse(text); } catch { json = { _raw: text.slice(0, 500) }; }
|
||||
return { status: resp.status, headers: Object.fromEntries(resp.headers), json };
|
||||
} catch (e) {
|
||||
return { status: 'ERR', error: e.name + ': ' + e.message };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
const text = await resp.text();
|
||||
let json = null;
|
||||
try { json = JSON.parse(text); } catch { json = { _raw: text.slice(0, 500) }; }
|
||||
return { status: resp.status, headers: Object.fromEntries(resp.headers), json };
|
||||
} catch (e) {
|
||||
return { status: 'ERR', error: e.name + ': ' + e.message };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function t() {
|
||||
const s = Date.now();
|
||||
return () => Date.now() - s;
|
||||
const s = Date.now();
|
||||
return () => Date.now() - s;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!API_KEY) {
|
||||
console.error('Ключ не найден в ' + KEY_FILE);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('API_KEY: ' + API_KEY.slice(0, 6) + '*** (маска)');
|
||||
console.log('BASE_URL: ' + BASE_URL);
|
||||
console.log('MODEL: ' + MODEL);
|
||||
if (!API_KEY) {
|
||||
console.error('Ключ не найден в ' + KEY_FILE);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('API_KEY: ' + API_KEY.slice(0, 6) + '*** (маска)');
|
||||
console.log('BASE_URL: ' + BASE_URL);
|
||||
console.log('MODEL: ' + MODEL);
|
||||
|
||||
// 1. response_format json_object
|
||||
log('1. response_format json_object', await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: 'Верни JSON {"resource_type":"nubes_vc_vdc"} и ничего больше.' }],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: 0,
|
||||
}));
|
||||
|
||||
// 3. reasoning_effort low
|
||||
log('3. reasoning_effort low', await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: 'Одним словом: что такое vDC?' }],
|
||||
reasoning_effort: 'low',
|
||||
temperature: 0,
|
||||
}));
|
||||
|
||||
// 7. задержка этапа 1 (3 замера)
|
||||
log('7. задержка (3 замера этап-1-подобного)', await (async () => {
|
||||
const results = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const d = t();
|
||||
await chat({
|
||||
// 1. response_format json_object
|
||||
log('1. response_format json_object', await chat({
|
||||
model: MODEL,
|
||||
messages: [
|
||||
{ role: 'system', content: 'Ты ассистент документации. Ответь строгим JSON: {"resource_type":"..."}.' },
|
||||
{ role: 'user', content: 'как создать виртуальный датацентр' },
|
||||
],
|
||||
messages: [{ role: 'user', content: 'Верни JSON {"resource_type":"nubes_vc_vdc"} и ничего больше.' }],
|
||||
response_format: { type: 'json_object' },
|
||||
temperature: 0,
|
||||
});
|
||||
results.push(d());
|
||||
}
|
||||
return results;
|
||||
})());
|
||||
}));
|
||||
|
||||
// 5. размер контекста — увеличиваем промпт
|
||||
log('5. контекст (пробуем ~8k и ~16k токенов)', await (async () => {
|
||||
const mk = (words) => Array(words).fill('слово').join(' ');
|
||||
const r = {};
|
||||
for (const words of [2000, 8000, 16000]) {
|
||||
const d = t();
|
||||
const res = await chat({
|
||||
// 3. reasoning_effort low
|
||||
log('3. reasoning_effort low', await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: `Повтори одно слово "ок". ${mk(words)}` }],
|
||||
max_tokens: 10,
|
||||
});
|
||||
r[words] = { ms: d(), status: res.status, err: res.error || (res.json && res.json.error ? res.json.error.message : null) };
|
||||
}
|
||||
return r;
|
||||
})());
|
||||
messages: [{ role: 'user', content: 'Одним словом: что такое vDC?' }],
|
||||
reasoning_effort: 'low',
|
||||
temperature: 0,
|
||||
}));
|
||||
|
||||
// 6. пустой content (маленький max_tokens + reasoning)
|
||||
log('6. маленький max_tokens (пустой content?)', await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: 'Распиши подробно архитектуру Terraform провайдера Nubes.' }],
|
||||
max_tokens: 1,
|
||||
temperature: 0,
|
||||
}));
|
||||
// 7. задержка этапа 1 (3 замера)
|
||||
log('7. задержка (3 замера этап-1-подобного)', await (async () => {
|
||||
const results = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const d = t();
|
||||
await chat({
|
||||
model: MODEL,
|
||||
messages: [
|
||||
{ role: 'system', content: 'Ты ассистент документации. Ответь строгим JSON: {"resource_type":"..."}.' },
|
||||
{ role: 'user', content: 'как создать виртуальный датацентр' },
|
||||
],
|
||||
temperature: 0,
|
||||
});
|
||||
results.push(d());
|
||||
}
|
||||
return results;
|
||||
})());
|
||||
|
||||
// 2. stream
|
||||
log('2. stream:true (первые байты)', await (async () => {
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model: MODEL, messages: [{ role: 'user', content: 'Скажи "привет"' }], stream: true }),
|
||||
});
|
||||
const reader = resp.body.getReader();
|
||||
const dec = new TextDecoder();
|
||||
let buf = '';
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
buf += dec.decode(value, { stream: true });
|
||||
}
|
||||
return { status: resp.status, firstChunks: buf.slice(0, 800) };
|
||||
} catch (e) {
|
||||
return { error: e.name + ': ' + e.message };
|
||||
}
|
||||
})());
|
||||
// 5. размер контекста — увеличиваем промпт
|
||||
log('5. контекст (пробуем ~8k и ~16k токенов)', await (async () => {
|
||||
const mk = (words) => Array(words).fill('слово').join(' ');
|
||||
const r = {};
|
||||
for (const words of [2000, 8000, 16000]) {
|
||||
const d = t();
|
||||
const res = await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: `Повтори одно слово "ок". ${mk(words)}` }],
|
||||
max_tokens: 10,
|
||||
});
|
||||
r[words] = { ms: d(), status: res.status, err: res.error || (res.json && res.json.error ? res.json.error.message : null) };
|
||||
}
|
||||
return r;
|
||||
})());
|
||||
|
||||
// 4. 429 — пытаемся получить, посылая запрос с заведомо неверным (но тем же) ключом не будем; отдельно проверим /v1/models
|
||||
log('4. /v1/models (проверка доступности)', await (async () => {
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/models`, {
|
||||
headers: { Authorization: `Bearer ${API_KEY}` },
|
||||
});
|
||||
const text = await resp.text().catch(() => '');
|
||||
let m = null; try { m = JSON.parse(text).data.map(x => x.id); } catch { m = text.slice(0, 500); }
|
||||
return { status: resp.status, models: m };
|
||||
} catch (e) {
|
||||
return { error: e.name + ': ' + e.message };
|
||||
}
|
||||
})());
|
||||
// 6. пустой content (маленький max_tokens + reasoning)
|
||||
log('6. маленький max_tokens (пустой content?)', await chat({
|
||||
model: MODEL,
|
||||
messages: [{ role: 'user', content: 'Распиши подробно архитектуру Terraform провайдера Nubes.' }],
|
||||
max_tokens: 1,
|
||||
temperature: 0,
|
||||
}));
|
||||
|
||||
// 2. stream
|
||||
log('2. stream:true (первые байты)', await (async () => {
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model: MODEL, messages: [{ role: 'user', content: 'Скажи "привет"' }], stream: true }),
|
||||
});
|
||||
const reader = resp.body.getReader();
|
||||
const dec = new TextDecoder();
|
||||
let buf = '';
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
buf += dec.decode(value, { stream: true });
|
||||
}
|
||||
return { status: resp.status, firstChunks: buf.slice(0, 800) };
|
||||
} catch (e) {
|
||||
return { error: e.name + ': ' + e.message };
|
||||
}
|
||||
})());
|
||||
|
||||
// 4. 429 — пытаемся получить, посылая запрос с заведомо неверным (но тем же) ключом не будем; отдельно проверим /v1/models
|
||||
log('4. /v1/models (проверка доступности)', await (async () => {
|
||||
try {
|
||||
const resp = await fetch(`${BASE_URL}/v1/models`, {
|
||||
headers: { Authorization: `Bearer ${API_KEY}` },
|
||||
});
|
||||
const text = await resp.text().catch(() => '');
|
||||
let m = null; try { m = JSON.parse(text).data.map(x => x.id); } catch { m = text.slice(0, 500); }
|
||||
return { status: resp.status, models: m };
|
||||
} catch (e) {
|
||||
return { error: e.name + ': ' + e.message };
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error('FATAL', e); process.exit(1); });
|
||||
|
||||
Reference in New Issue
Block a user