Localize pipeline fallback notifications
This commit is contained in:
+1
-1
@@ -99,7 +99,7 @@
|
|||||||
- [x] Для `continue_without_stage` писать короткий debug/audit без user notification.
|
- [x] Для `continue_without_stage` писать короткий debug/audit без user notification.
|
||||||
- [x] Для `use_alternate_target` логировать исходный и alternate target.
|
- [x] Для `use_alternate_target` логировать исходный и alternate target.
|
||||||
- [x] Для `fail_request` завершать request через единый error path.
|
- [x] Для `fail_request` завершать request через единый error path.
|
||||||
- [ ] Добавить локализацию fallback messages.
|
- [x] Добавить локализацию fallback messages.
|
||||||
- [x] Добавить отдельные тексты для RAG failure, STT failure, TTS failure, tool failure.
|
- [x] Добавить отдельные тексты для RAG failure, STT failure, TTS failure, tool failure.
|
||||||
- [x] Не спамить пользователя несколькими fallback notifications за один request.
|
- [x] Не спамить пользователя несколькими fallback notifications за один request.
|
||||||
- [x] Сохранять fallback notification в `request_audit.details`.
|
- [x] Сохранять fallback notification в `request_audit.details`.
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
},
|
},
|
||||||
"providerChoice.default": "Default",
|
"providerChoice.default": "Default",
|
||||||
"errorText": "⚠️ An error occurred.",
|
"errorText": "⚠️ An error occurred.",
|
||||||
|
"pipelineFallback.generic": "⚠️ I had to skip part of the request, but I can continue.",
|
||||||
|
"pipelineFallback.notifyUser": "⚠️ I hit a problem and need to continue with a fallback.",
|
||||||
|
"pipelineFallback.failRequest": "⚠️ I could not finish this request.",
|
||||||
|
"pipelineFallback.documentRag": "⚠️ Document retrieval failed, so I will answer without RAG.",
|
||||||
|
"pipelineFallback.speechToText": "⚠️ Speech transcription failed, so I will continue without the audio transcript.",
|
||||||
|
"pipelineFallback.textToSpeech": "⚠️ Text-to-speech failed, so I will continue without audio output.",
|
||||||
|
"pipelineFallback.toolLoop": "⚠️ Tool execution failed, so I will continue without that tool.",
|
||||||
"waitThinkText": "⏳ Let me think...",
|
"waitThinkText": "⏳ Let me think...",
|
||||||
"analyzingPictureText": "🔍 Analyzing the image...",
|
"analyzingPictureText": "🔍 Analyzing the image...",
|
||||||
"analyzingPicturesText": "🔍 Analyzing the images...",
|
"analyzingPicturesText": "🔍 Analyzing the images...",
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
},
|
},
|
||||||
"providerChoice.default": "По умолчанию",
|
"providerChoice.default": "По умолчанию",
|
||||||
"errorText": "⚠️ Произошла ошибка.",
|
"errorText": "⚠️ Произошла ошибка.",
|
||||||
|
"pipelineFallback.generic": "⚠️ Мне пришлось пропустить часть запроса, но я могу продолжить.",
|
||||||
|
"pipelineFallback.notifyUser": "⚠️ Возникла проблема, и я продолжу с запасным вариантом.",
|
||||||
|
"pipelineFallback.failRequest": "⚠️ Я не смог завершить этот запрос.",
|
||||||
|
"pipelineFallback.documentRag": "⚠️ Не удалось получить документы, поэтому я отвечу без RAG.",
|
||||||
|
"pipelineFallback.speechToText": "⚠️ Не удалось распознать речь, поэтому я продолжу без расшифровки аудио.",
|
||||||
|
"pipelineFallback.textToSpeech": "⚠️ Не удалось выполнить синтез речи, поэтому я продолжу без аудио.",
|
||||||
|
"pipelineFallback.toolLoop": "⚠️ Не удалось выполнить инструменты, поэтому я продолжу без них.",
|
||||||
"waitThinkText": "⏳ Дайте-ка подумать...",
|
"waitThinkText": "⏳ Дайте-ка подумать...",
|
||||||
"analyzingPictureText": "🔍 Анализирую изображение...",
|
"analyzingPictureText": "🔍 Анализирую изображение...",
|
||||||
"analyzingPicturesText": "🔍 Анализирую изображения...",
|
"analyzingPicturesText": "🔍 Анализирую изображения...",
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
},
|
},
|
||||||
"providerChoice.default": "За замовчуванням",
|
"providerChoice.default": "За замовчуванням",
|
||||||
"errorText": "⚠️ Сталася помилка.",
|
"errorText": "⚠️ Сталася помилка.",
|
||||||
|
"pipelineFallback.generic": "⚠️ Мені довелося пропустити частину запиту, але я можу продовжити.",
|
||||||
|
"pipelineFallback.notifyUser": "⚠️ Виникла проблема, і я продовжу із запасним варіантом.",
|
||||||
|
"pipelineFallback.failRequest": "⚠️ Я не зміг завершити цей запит.",
|
||||||
|
"pipelineFallback.documentRag": "⚠️ Не вдалося отримати документи, тому я відповім без RAG.",
|
||||||
|
"pipelineFallback.speechToText": "⚠️ Не вдалося розпізнати мовлення, тому я продовжу без розшифровки аудіо.",
|
||||||
|
"pipelineFallback.textToSpeech": "⚠️ Не вдалося виконати синтез мовлення, тому я продовжу без аудіо.",
|
||||||
|
"pipelineFallback.toolLoop": "⚠️ Не вдалося виконати інструменти, тому я продовжу без них.",
|
||||||
"waitThinkText": "⏳ Дайте-но подумати...",
|
"waitThinkText": "⏳ Дайте-но подумати...",
|
||||||
"analyzingPictureText": "🔍 Аналізую зображення...",
|
"analyzingPictureText": "🔍 Аналізую зображення...",
|
||||||
"analyzingPicturesText": "🔍 Аналізую зображення...",
|
"analyzingPicturesText": "🔍 Аналізую зображення...",
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ export async function prepareUnifiedAiRequestPipeline(params: {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const state = createAiRequestPipelineState(options);
|
const state = createAiRequestPipelineState(options);
|
||||||
const fallbackNotifier = new PipelineFallbackNotifier(options.msg);
|
const fallbackNotifier = new PipelineFallbackNotifier(options.msg, options.responseLanguage);
|
||||||
const pipeline = new UserRequestPipeline({
|
const pipeline = new UserRequestPipeline({
|
||||||
stages,
|
stages,
|
||||||
stageNames: [
|
stageNames: [
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export async function runUnifiedAiResponsePipeline(params: {
|
|||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const {options, config, downloads, prepared, streamMessage, controller} = params;
|
const {options, config, downloads, prepared, streamMessage, controller} = params;
|
||||||
const state = createResponsePipelineState(options);
|
const state = createResponsePipelineState(options);
|
||||||
const fallbackNotifier = new PipelineFallbackNotifier(options.msg);
|
const fallbackNotifier = new PipelineFallbackNotifier(options.msg, options.responseLanguage);
|
||||||
const adapter = getProviderAdapter(options.provider);
|
const adapter = getProviderAdapter(options.provider);
|
||||||
let selectedToolNames: string[] = [];
|
let selectedToolNames: string[] = [];
|
||||||
let filteredTools: unknown[] = [];
|
let filteredTools: unknown[] = [];
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
|
import {Localization} from "../../common/localization.js";
|
||||||
import type {PipelineFallbackAction, PipelineStageName} from "./types.js";
|
import type {PipelineFallbackAction, PipelineStageName} from "./types.js";
|
||||||
|
|
||||||
const DEFAULT_TEXT = "⚠️ I had to skip part of the request, but I can continue.";
|
export function resolvePipelineFallbackText(
|
||||||
const NOTIFY_TEXT = "⚠️ I hit a problem and need to continue with a fallback.";
|
stage: PipelineStageName,
|
||||||
const FAIL_TEXT = "⚠️ I could not finish this request.";
|
action: PipelineFallbackAction,
|
||||||
const RAG_TEXT = "⚠️ Document retrieval failed, so I will answer without RAG.";
|
locale?: string,
|
||||||
const STT_TEXT = "⚠️ Speech transcription failed, so I will continue without the audio transcript.";
|
): string | undefined {
|
||||||
const TTS_TEXT = "⚠️ Text-to-speech failed, so I will continue without audio output.";
|
|
||||||
const TOOL_TEXT = "⚠️ Tool execution failed, so I will continue without that tool.";
|
|
||||||
|
|
||||||
export function resolvePipelineFallbackText(stage: PipelineStageName, action: PipelineFallbackAction): string | undefined {
|
|
||||||
if (action === "continue_without_stage") return undefined;
|
if (action === "continue_without_stage") return undefined;
|
||||||
if (action === "fail_request") return FAIL_TEXT;
|
if (action === "fail_request") return Localization.text("pipelineFallback.failRequest", {}, "⚠️ I could not finish this request.", locale);
|
||||||
|
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
case "speech_to_text":
|
case "speech_to_text":
|
||||||
return STT_TEXT;
|
return Localization.text("pipelineFallback.speechToText", {}, "⚠️ Speech transcription failed, so I will continue without the audio transcript.", locale);
|
||||||
case "document_rag":
|
case "document_rag":
|
||||||
return RAG_TEXT;
|
return Localization.text("pipelineFallback.documentRag", {}, "⚠️ Document retrieval failed, so I will answer without RAG.", locale);
|
||||||
case "tool_loop":
|
case "tool_loop":
|
||||||
return TOOL_TEXT;
|
return Localization.text("pipelineFallback.toolLoop", {}, "⚠️ Tool execution failed, so I will continue without that tool.", locale);
|
||||||
case "text_to_speech":
|
case "text_to_speech":
|
||||||
return TTS_TEXT;
|
return Localization.text("pipelineFallback.textToSpeech", {}, "⚠️ Text-to-speech failed, so I will continue without audio output.", locale);
|
||||||
default:
|
default:
|
||||||
return action === "notify_user" ? NOTIFY_TEXT : DEFAULT_TEXT;
|
return action === "notify_user"
|
||||||
|
? Localization.text("pipelineFallback.notifyUser", {}, "⚠️ I hit a problem and need to continue with a fallback.", locale)
|
||||||
|
: Localization.text("pipelineFallback.generic", {}, "⚠️ I had to skip part of the request, but I can continue.", locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type {Message} from "typescript-telegram-bot-api";
|
import type {Message} from "typescript-telegram-bot-api";
|
||||||
|
import {Localization} from "../../common/localization.js";
|
||||||
import {replyToMessage, logError} from "../../util/utils.js";
|
import {replyToMessage, logError} from "../../util/utils.js";
|
||||||
import type {PipelineFallbackDecision} from "./fallback-executor.js";
|
import type {PipelineFallbackDecision} from "./fallback-executor.js";
|
||||||
import {PipelineFallbackNotificationRegistry} from "./fallback-notifier-registry.js";
|
import {PipelineFallbackNotificationRegistry} from "./fallback-notifier-registry.js";
|
||||||
@@ -9,6 +10,7 @@ export class PipelineFallbackNotifier {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly sourceMessage: Message,
|
private readonly sourceMessage: Message,
|
||||||
|
private readonly responseLanguage?: string,
|
||||||
private readonly sendFallbackMessage: (text: string) => Promise<void> = async text => {
|
private readonly sendFallbackMessage: (text: string) => Promise<void> = async text => {
|
||||||
await replyToMessage({
|
await replyToMessage({
|
||||||
message: this.sourceMessage,
|
message: this.sourceMessage,
|
||||||
@@ -22,7 +24,10 @@ export class PipelineFallbackNotifier {
|
|||||||
return {notified: false};
|
return {notified: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = resolvePipelineFallbackText(decision.stage, decision.action);
|
const locale = this.responseLanguage === "default"
|
||||||
|
? Localization.currentLocale()
|
||||||
|
: Localization.normalizeLocale(this.responseLanguage) ?? Localization.currentLocale();
|
||||||
|
const text = resolvePipelineFallbackText(decision.stage, decision.action, locale);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return {notified: false};
|
return {notified: false};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ test("pipeline fallback text maps notify_user to a user-facing message", () => {
|
|||||||
assert.match(resolvePipelineFallbackText("tool_loop", "notify_user"), /tool/i);
|
assert.match(resolvePipelineFallbackText("tool_loop", "notify_user"), /tool/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("pipeline fallback text is localized when locale is provided", () => {
|
||||||
|
assert.match(resolvePipelineFallbackText("document_rag", "notify_user", "ru"), /RAG|документ/i);
|
||||||
|
assert.match(resolvePipelineFallbackText("text_to_speech", "notify_user", "ua"), /аудіо|мовлення/i);
|
||||||
|
});
|
||||||
|
|
||||||
test("pipeline fallback text stays silent for continue_without_stage", () => {
|
test("pipeline fallback text stays silent for continue_without_stage", () => {
|
||||||
assert.equal(resolvePipelineFallbackText("document_rag", "continue_without_stage"), undefined);
|
assert.equal(resolvePipelineFallbackText("document_rag", "continue_without_stage"), undefined);
|
||||||
assert.equal(resolvePipelineFallbackText("tool_loop", "continue_without_stage"), undefined);
|
assert.equal(resolvePipelineFallbackText("tool_loop", "continue_without_stage"), undefined);
|
||||||
|
|||||||
Reference in New Issue
Block a user