chore: форматирование модулей + план по шагам

This commit is contained in:
Repinoid
2026-10-07 14:11:35 +03:00
parent 2136536b0d
commit c59f0245b5
6 changed files with 808 additions and 479 deletions
+134 -134
View File
@@ -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); });