fix(news): до 3 попыток с разными запросами за утро; редактор не ужимает драфт и не трогает отказ
- ai.generateArticle: драфт короче 800 знаков (отказ/пустышка) не отдаём редактору; если редактор ужал текст сильнее чем до 70% драфта — оставляем драфт (10/14/15/17.10 новости срезались ниже порога и терялись) - newsGenerator: при сбое поиска/пустых источниках/отказе модели — следующий запрос из банка, до 3 попыток (13 и 19.10 день терялся после одной ошибки поиска) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BdBKDZjaZDhtZwcQvP4qis
This commit is contained in:
+16
-1
@@ -257,6 +257,13 @@ async function generateArticle(channel, opts = {}) {
|
||||
return { content: draft.text, usage: draft.usage };
|
||||
}
|
||||
|
||||
// Короткий драфт — это отказ («НЕТ_НОВОСТИ») или пустышка. Редактору его не
|
||||
// отдаём: он не видит исходных инструкций и способен «дописать» из отказа пост.
|
||||
const draftLen = String(draft.text || '').trim().length;
|
||||
if (draftLen < 800) {
|
||||
return { content: draft.text, usage: draft.usage, draft: draft.text };
|
||||
}
|
||||
|
||||
// === Второй проход — редактура ===
|
||||
const editorPrompt = `Ты — опытный редактор. Передо мной черновик статьи. Перепиши его так, чтобы он звучал максимально по-человечески — как будто писал живой думающий автор для русской аудитории.
|
||||
|
||||
@@ -280,8 +287,16 @@ async function generateArticle(channel, opts = {}) {
|
||||
{ maxTokens: 3000, temperature: 0.75 }
|
||||
);
|
||||
|
||||
// Редактор иногда ужимает текст вдвое (10/14/15/17.10 новости срезались
|
||||
// ниже 800 знаков и выкидывались). Если сильно короче драфта — берём драфт.
|
||||
const editedLen = String(edited.text || '').trim().length;
|
||||
const useDraft = editedLen < draftLen * 0.7;
|
||||
if (useDraft) {
|
||||
console.warn(`[ai] редактор ужал текст ${draftLen} → ${editedLen} знаков, оставляю драфт`);
|
||||
}
|
||||
|
||||
return {
|
||||
content: edited.text,
|
||||
content: useDraft ? draft.text : edited.text,
|
||||
usage: {
|
||||
prompt_tokens: (draft.usage?.prompt_tokens || 0) + (edited.usage?.prompt_tokens || 0),
|
||||
completion_tokens: (draft.usage?.completion_tokens || 0) + (edited.usage?.completion_tokens || 0),
|
||||
|
||||
@@ -98,6 +98,11 @@ async function gatherWithFallback(baseQuery) {
|
||||
* @param {object} [opts]
|
||||
* @param {boolean} [opts.dryRun] — не сохранять, вернуть только собранные данные
|
||||
*/
|
||||
// Сколько разных запросов пробуем за утро, прежде чем сдаться. Раньше одна
|
||||
// неудача (поиск упал, источники пустые, модель ушла в отказ) = день без новости.
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const ATTEMPT_QUERY_STEP = 3; // gatherWithFallback сам заглядывает на +1, не пересекаемся
|
||||
|
||||
async function generateMorningNews(opts = {}) {
|
||||
const { dryRun = false } = opts;
|
||||
|
||||
@@ -106,7 +111,22 @@ async function generateMorningNews(opts = {}) {
|
||||
return { ok: false, skipped: true };
|
||||
}
|
||||
|
||||
const baseQuery = pickQuery();
|
||||
let last = null;
|
||||
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
||||
const baseQuery = pickQuery(i * ATTEMPT_QUERY_STEP);
|
||||
try {
|
||||
last = await attemptNews(baseQuery, dryRun);
|
||||
} catch (err) {
|
||||
last = { ok: false, error: err.message, query: baseQuery };
|
||||
}
|
||||
if (last.ok) return last;
|
||||
console.warn(`[news] попытка ${i + 1}/${MAX_ATTEMPTS} не удалась (запрос="${baseQuery}"): ${last.error}`);
|
||||
if (String(last.error || '').includes('DAILY_LIMIT') || /limit reached/i.test(last.error || '')) break;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
async function attemptNews(baseQuery, dryRun) {
|
||||
const gathered = await gatherWithFallback(baseQuery);
|
||||
|
||||
if (!gathered.sources.length) {
|
||||
|
||||
Reference in New Issue
Block a user