'use strict'; /** * debug_bad_llm_output.js — воспроизводит этап 2 локально для вопросов, * на которых прод вернул 502 bad_llm_output, и печатает СЫРОЙ ответ LLM. * * Использует тот же промпт и тот же индекс (живой), что и прод. * Запуск: node llm_project/tests/debug_bad_llm_output.js */ const fs = require('fs'); const https = require('https'); const http = require('http'); const STAND = 'nubes-test'; const VM = 'http://5.172.178.213'; const KEY_FILE = '/home/naeel/nubes/llm.key'; const BASE_URL = 'https://api.aillm.ru'; const MODEL = 'gpt-oss-120b'; const QUESTIONS = [ 'openbao для секретов в кластере', 'как создать виртуальную машину', 'как развернуть rabbitmq', 'nodejs приложение задеплоить', 'как развернуть gitea', 'pgadmin для управления базами', ]; const REQUIRED_HEADERS = ['Роль', 'Задача', 'Контекст', 'Требования', 'Ограничения']; const STAGE2_SYSTEM_PROMPT = 'Ты — ассистент документации Terraform-провайдера Nubes. ' + 'Составь ответ СТРОГО по шаблону с заголовками-маркерами, каждый на новой строке:\n' + 'Роль:\nЗадача:\nКонтекст:\nТребования:\nОграничения:\n' + 'Используй ТОЛЬКО факты из блока ДОКУМЕНТАЦИЯ ниже. ' + 'Не выдумывай ресурсов, атрибутов и значений, которых нет в документации. ' + 'Отвечай на русском языке.'; function findApiKey() { const lines = fs.readFileSync(KEY_FILE, 'utf8').split(/\r?\n/); for (const l of lines) { const t = l.trim(); if (t.startsWith('sk-')) return t; } return null; } function httpGet(url) { return new Promise((resolve, reject) => { const mod = url.startsWith('https') ? https : http; const req = mod.get(url, (res) => { if (res.statusCode !== 200) { res.resume(); reject(new Error('status ' + res.statusCode)); return; } let d = ''; res.on('data', (c) => { d += c; }); res.on('end', () => resolve(d)); }); req.on('error', reject); req.setTimeout(15000, () => req.destroy(new Error('timeout'))); }); } async function llmRaw(messages, maxTokens, apiKey) { const resp = await fetch(`${BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: MODEL, messages, max_tokens: maxTokens, temperature: 0.2 }), signal: AbortSignal.timeout(60000), }); const j = await resp.json(); const msg = j && j.choices && j.choices[0] && j.choices[0].message; return { content: msg ? msg.content : null, finish: j.choices && j.choices[0] ? j.choices[0].finish_reason : null }; } async function main() { const { loadIndex } = require('../../search.js'); const { mergeSearchResults, buildContext } = require('../../stage2.js'); const { stage1ExtractResourceType } = require('../../stage1.js'); const apiKey = findApiKey(); const raw = await httpGet(`${VM}/${STAND}/search/search_index.json`); const idx = loadIndex(raw); const types = idx.listResourceTypes(); console.log('Индекс docs:', idx.docs.length, '| ресурсов:', types.size, '\n'); for (const q of QUESTIONS) { console.log('='.repeat(80)); console.log('ВОПРОС:', q); const r1 = await stage1ExtractResourceType(q, { resourceTypesSet: types, baseUrl: BASE_URL, apiKey, model: MODEL }); const rt = r1.ok ? r1.resourceType : null; console.log('этап1:', JSON.stringify(r1)); const merged = mergeSearchResults(idx, q, rt, { topK: 5 }); console.log('контекст:', merged.map((m) => m.location).join(' , ')); const ctx = buildContext(merged, 1500); const docsBlock = ctx.map((c, i) => `[${i + 1}] ${c.title}\n${c.text}`).join('\n\n'); const userContent = 'ДОКУМЕНТАЦИЯ:\n' + (docsBlock || '(нет релевантных фрагментов)') + '\n\nВОПРОС ПОЛЬЗОВАТЕЛЯ:\n' + q; const messages = [{ role: 'system', content: STAGE2_SYSTEM_PROMPT }, { role: 'user', content: userContent }]; const out = await llmRaw(messages, 1024, apiKey); console.log('finish_reason:', out.finish, '| content len:', out.content ? out.content.length : 0); const missing = out.content ? REQUIRED_HEADERS.filter((h) => !out.content.includes(h + ':')) : REQUIRED_HEADERS; console.log('НЕТ заголовков:', missing.join(', ') || '(нет, всё ок)'); console.log('--- СЫРОЙ ОТВЕТ (первые 500) ---'); console.log((out.content || '(null)').slice(0, 500)); console.log(''); } } main().catch((e) => { console.error('FATAL', e); process.exit(1); });