chore: форматирование модулей + план по шагам
This commit is contained in:
@@ -16,12 +16,12 @@
|
||||
const { llmChat } = require('./assist.js');
|
||||
|
||||
const SYSTEM_PROMPT =
|
||||
'Ты — помощник по документации Terraform-провайдера Nubes. ' +
|
||||
'Определи, к какому типу ресурса относится вопрос пользователя. ' +
|
||||
'Отвечай строго JSON вида {"resource_type":"nubes_<имя>"}. ' +
|
||||
'Используй ТОЛЬКО типы из списка ниже. ' +
|
||||
'Если вопрос не относится ни к одному типу или тип не из списка — верни {"resource_type":""}. ' +
|
||||
'Не выдумывай типы.';
|
||||
'Ты — помощник по документации Terraform-провайдера Nubes. ' +
|
||||
'Определи, к какому типу ресурса относится вопрос пользователя. ' +
|
||||
'Отвечай строго JSON вида {"resource_type":"nubes_<имя>"}. ' +
|
||||
'Используй ТОЛЬКО типы из списка ниже. ' +
|
||||
'Если вопрос не относится ни к одному типу или тип не из списка — верни {"resource_type":""}. ' +
|
||||
'Не выдумывай типы.';
|
||||
|
||||
const DEFAULT_MAX_TOKENS = 256;
|
||||
const DEFAULT_MODEL = 'gpt-oss-120b';
|
||||
@@ -36,19 +36,19 @@ const DEFAULT_MODEL = 'gpt-oss-120b';
|
||||
* @returns {Array<{role:string, content:string}>}
|
||||
*/
|
||||
function buildStage1Messages(question, resourceTypesSet) {
|
||||
const types = [...new Set(resourceTypesSet || [])].map((t) => String(t)).sort();
|
||||
const list = types.length > 0 ? types.join('\n') : '(пусто)';
|
||||
const types = [...new Set(resourceTypesSet || [])].map((t) => String(t)).sort();
|
||||
const list = types.length > 0 ? types.join('\n') : '(пусто)';
|
||||
|
||||
const userContent =
|
||||
'Доступные типы ресурсов (используй ТОЛЬКО из этого списка):\n' +
|
||||
list +
|
||||
'\n\nВопрос пользователя:\n' +
|
||||
String(question ?? '');
|
||||
const userContent =
|
||||
'Доступные типы ресурсов (используй ТОЛЬКО из этого списка):\n' +
|
||||
list +
|
||||
'\n\nВопрос пользователя:\n' +
|
||||
String(question ?? '');
|
||||
|
||||
return [
|
||||
{ role: 'system', content: SYSTEM_PROMPT },
|
||||
{ role: 'user', content: userContent },
|
||||
];
|
||||
return [
|
||||
{ role: 'system', content: SYSTEM_PROMPT },
|
||||
{ role: 'user', content: userContent },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,12 +59,12 @@ function buildStage1Messages(question, resourceTypesSet) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function extractJsonString(raw) {
|
||||
const text = String(raw ?? '').trim();
|
||||
const stripped = text.replace(/```(?:json)?\s*/gi, '').replace(/```/g, '');
|
||||
const start = stripped.indexOf('{');
|
||||
const end = stripped.lastIndexOf('}');
|
||||
if (start === -1 || end === -1 || end < start) return '';
|
||||
return stripped.slice(start, end + 1);
|
||||
const text = String(raw ?? '').trim();
|
||||
const stripped = text.replace(/```(?:json)?\s*/gi, '').replace(/```/g, '');
|
||||
const start = stripped.indexOf('{');
|
||||
const end = stripped.lastIndexOf('}');
|
||||
if (start === -1 || end === -1 || end < start) return '';
|
||||
return stripped.slice(start, end + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,18 +75,18 @@ function extractJsonString(raw) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseResourceType(content) {
|
||||
const jsonStr = extractJsonString(content);
|
||||
if (!jsonStr) return '';
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(jsonStr);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
if (!obj || typeof obj !== 'object') return '';
|
||||
const raw = obj.resource_type;
|
||||
if (typeof raw !== 'string') return '';
|
||||
return raw.toLowerCase().trim();
|
||||
const jsonStr = extractJsonString(content);
|
||||
if (!jsonStr) return '';
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(jsonStr);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
if (!obj || typeof obj !== 'object') return '';
|
||||
const raw = obj.resource_type;
|
||||
if (typeof raw !== 'string') return '';
|
||||
return raw.toLowerCase().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,80 +112,80 @@ function parseResourceType(content) {
|
||||
* @returns {Promise<{ok:boolean, resourceType?:string|null, error?:string}>}
|
||||
*/
|
||||
async function stage1ExtractResourceType(question, {
|
||||
resourceTypesSet,
|
||||
baseUrl = process.env.LLM_BASE_URL || '',
|
||||
apiKey = process.env.LLM_API_KEY || '',
|
||||
model = process.env.LLM_MODEL || DEFAULT_MODEL,
|
||||
timeoutMs,
|
||||
controller,
|
||||
resourceTypesSet,
|
||||
baseUrl = process.env.LLM_BASE_URL || '',
|
||||
apiKey = process.env.LLM_API_KEY || '',
|
||||
model = process.env.LLM_MODEL || DEFAULT_MODEL,
|
||||
timeoutMs,
|
||||
controller,
|
||||
}) {
|
||||
const types = new Set(
|
||||
[...new Set(resourceTypesSet || [])].map((t) => String(t).toLowerCase().trim())
|
||||
);
|
||||
const types = new Set(
|
||||
[...new Set(resourceTypesSet || [])].map((t) => String(t).toLowerCase().trim())
|
||||
);
|
||||
|
||||
if (types.size === 0) {
|
||||
return { ok: false, error: 'no_resource_types' };
|
||||
}
|
||||
if (types.size === 0) {
|
||||
return { ok: false, error: 'no_resource_types' };
|
||||
}
|
||||
|
||||
const messages = buildStage1Messages(question, types);
|
||||
const messages = buildStage1Messages(question, types);
|
||||
|
||||
const attempt = async (maxTokens) => {
|
||||
const params = {
|
||||
messages,
|
||||
maxTokens,
|
||||
temperature: 0,
|
||||
responseFormat: { type: 'json_object' },
|
||||
controller,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
const attempt = async (maxTokens) => {
|
||||
const params = {
|
||||
messages,
|
||||
maxTokens,
|
||||
temperature: 0,
|
||||
responseFormat: { type: 'json_object' },
|
||||
controller,
|
||||
baseUrl,
|
||||
apiKey,
|
||||
model,
|
||||
};
|
||||
let timer = null;
|
||||
let ctrl = controller;
|
||||
if (timeoutMs && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
||||
ctrl = new AbortController();
|
||||
timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
const forward = () => ctrl.abort();
|
||||
if (controller) {
|
||||
if (controller.signal.aborted) ctrl.abort();
|
||||
else controller.signal.addEventListener('abort', forward, { once: true });
|
||||
}
|
||||
params.controller = ctrl;
|
||||
try {
|
||||
return await llmChat(params);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
if (controller) controller.signal.removeEventListener('abort', forward);
|
||||
}
|
||||
}
|
||||
return llmChat(params);
|
||||
};
|
||||
let timer = null;
|
||||
let ctrl = controller;
|
||||
if (timeoutMs && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
||||
ctrl = new AbortController();
|
||||
timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
const forward = () => ctrl.abort();
|
||||
if (controller) {
|
||||
if (controller.signal.aborted) ctrl.abort();
|
||||
else controller.signal.addEventListener('abort', forward, { once: true });
|
||||
}
|
||||
params.controller = ctrl;
|
||||
try {
|
||||
return await llmChat(params);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
if (controller) controller.signal.removeEventListener('abort', forward);
|
||||
}
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = await attempt(DEFAULT_MAX_TOKENS);
|
||||
} catch (err) {
|
||||
if (err && err.code === 'empty_content') {
|
||||
try {
|
||||
content = await attempt(DEFAULT_MAX_TOKENS * 2);
|
||||
} catch (err2) {
|
||||
return { ok: false, error: err2 && err2.code ? err2.code : 'stage1_retry_failed' };
|
||||
}
|
||||
} else {
|
||||
return { ok: false, error: err && err.code ? err.code : 'stage1_failed' };
|
||||
}
|
||||
}
|
||||
return llmChat(params);
|
||||
};
|
||||
|
||||
let content;
|
||||
try {
|
||||
content = await attempt(DEFAULT_MAX_TOKENS);
|
||||
} catch (err) {
|
||||
if (err && err.code === 'empty_content') {
|
||||
try {
|
||||
content = await attempt(DEFAULT_MAX_TOKENS * 2);
|
||||
} catch (err2) {
|
||||
return { ok: false, error: err2 && err2.code ? err2.code : 'stage1_retry_failed' };
|
||||
}
|
||||
} else {
|
||||
return { ok: false, error: err && err.code ? err.code : 'stage1_failed' };
|
||||
const resourceType = parseResourceType(content);
|
||||
|
||||
if (resourceType && types.has(resourceType)) {
|
||||
return { ok: true, resourceType };
|
||||
}
|
||||
}
|
||||
|
||||
const resourceType = parseResourceType(content);
|
||||
|
||||
if (resourceType && types.has(resourceType)) {
|
||||
return { ok: true, resourceType };
|
||||
}
|
||||
|
||||
return { ok: true, resourceType: null };
|
||||
return { ok: true, resourceType: null };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stage1ExtractResourceType,
|
||||
buildStage1Messages,
|
||||
stage1ExtractResourceType,
|
||||
buildStage1Messages,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user