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 };
|
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 = `Ты — опытный редактор. Передо мной черновик статьи. Перепиши его так, чтобы он звучал максимально по-человечески — как будто писал живой думающий автор для русской аудитории.
|
const editorPrompt = `Ты — опытный редактор. Передо мной черновик статьи. Перепиши его так, чтобы он звучал максимально по-человечески — как будто писал живой думающий автор для русской аудитории.
|
||||||
|
|
||||||
@@ -280,8 +287,16 @@ async function generateArticle(channel, opts = {}) {
|
|||||||
{ maxTokens: 3000, temperature: 0.75 }
|
{ 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 {
|
return {
|
||||||
content: edited.text,
|
content: useDraft ? draft.text : edited.text,
|
||||||
usage: {
|
usage: {
|
||||||
prompt_tokens: (draft.usage?.prompt_tokens || 0) + (edited.usage?.prompt_tokens || 0),
|
prompt_tokens: (draft.usage?.prompt_tokens || 0) + (edited.usage?.prompt_tokens || 0),
|
||||||
completion_tokens: (draft.usage?.completion_tokens || 0) + (edited.usage?.completion_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 {object} [opts]
|
||||||
* @param {boolean} [opts.dryRun] — не сохранять, вернуть только собранные данные
|
* @param {boolean} [opts.dryRun] — не сохранять, вернуть только собранные данные
|
||||||
*/
|
*/
|
||||||
|
// Сколько разных запросов пробуем за утро, прежде чем сдаться. Раньше одна
|
||||||
|
// неудача (поиск упал, источники пустые, модель ушла в отказ) = день без новости.
|
||||||
|
const MAX_ATTEMPTS = 3;
|
||||||
|
const ATTEMPT_QUERY_STEP = 3; // gatherWithFallback сам заглядывает на +1, не пересекаемся
|
||||||
|
|
||||||
async function generateMorningNews(opts = {}) {
|
async function generateMorningNews(opts = {}) {
|
||||||
const { dryRun = false } = opts;
|
const { dryRun = false } = opts;
|
||||||
|
|
||||||
@@ -106,7 +111,22 @@ async function generateMorningNews(opts = {}) {
|
|||||||
return { ok: false, skipped: true };
|
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);
|
const gathered = await gatherWithFallback(baseQuery);
|
||||||
|
|
||||||
if (!gathered.sources.length) {
|
if (!gathered.sources.length) {
|
||||||
|
|||||||
Reference in New Issue
Block a user