Add fallback target logging and unified failures
This commit is contained in:
+2
-2
@@ -97,8 +97,8 @@
|
|||||||
- [x] Добавить `PipelineFallbackNotifier`.
|
- [x] Добавить `PipelineFallbackNotifier`.
|
||||||
- [x] Для `notify_user` отправлять пользователю понятное сообщение.
|
- [x] Для `notify_user` отправлять пользователю понятное сообщение.
|
||||||
- [x] Для `continue_without_stage` писать короткий debug/audit без user notification.
|
- [x] Для `continue_without_stage` писать короткий debug/audit без user notification.
|
||||||
- [ ] Для `use_alternate_target` логировать исходный и alternate target.
|
- [x] Для `use_alternate_target` логировать исходный и alternate target.
|
||||||
- [ ] Для `fail_request` завершать request через единый error path.
|
- [x] Для `fail_request` завершать request через единый error path.
|
||||||
- [ ] Добавить локализацию fallback messages.
|
- [ ] Добавить локализацию 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.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {AI_VOICE_MODE_TRANSCRIPT, DEFAULT_AI_RESPONSE_LANGUAGE} from "../common/
|
|||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {UserRequestPipeline, type UserRequestPipelineState, type UserRequestPipelineStage} from "./user-request-pipeline";
|
import {UserRequestPipeline, type UserRequestPipelineState, type UserRequestPipelineStage} from "./user-request-pipeline";
|
||||||
import {PipelineFallbackNotifier} from "./user-request-pipeline/fallback-notifier";
|
import {PipelineFallbackNotifier} from "./user-request-pipeline/fallback-notifier";
|
||||||
|
import {buildToolRankFallbackTargetDetails} from "./user-request-pipeline/fallback-target-details";
|
||||||
import type {AiDownloadedFile} from "./telegram-attachments";
|
import type {AiDownloadedFile} from "./telegram-attachments";
|
||||||
import type {TelegramStreamMessage} from "./telegram-stream-message";
|
import type {TelegramStreamMessage} from "./telegram-stream-message";
|
||||||
import type {ChatMessage} from "./chat-messages-types";
|
import type {ChatMessage} from "./chat-messages-types";
|
||||||
@@ -304,6 +305,23 @@ export async function prepareUnifiedAiRequestPipeline(params: {
|
|||||||
"audit_finish",
|
"audit_finish",
|
||||||
],
|
],
|
||||||
onFallback: async decision => {
|
onFallback: async decision => {
|
||||||
|
if (decision.action === "use_alternate_target") {
|
||||||
|
aiLog("warn", "request.fallback.use_alternate_target", {
|
||||||
|
provider: options.provider,
|
||||||
|
stage: decision.stage,
|
||||||
|
reason: decision.reason,
|
||||||
|
...buildToolRankFallbackTargetDetails(options.provider, config),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision.action === "fail_request") {
|
||||||
|
aiLog("error", "request.fallback.fail_request", {
|
||||||
|
provider: options.provider,
|
||||||
|
stage: decision.stage,
|
||||||
|
reason: decision.reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const notification = await fallbackNotifier.notify(state.requestId, decision);
|
const notification = await fallbackNotifier.notify(state.requestId, decision);
|
||||||
state.audit.push({
|
state.audit.push({
|
||||||
stage: decision.stage,
|
stage: decision.stage,
|
||||||
@@ -315,6 +333,9 @@ export async function prepareUnifiedAiRequestPipeline(params: {
|
|||||||
fallbackNotification: notification.text,
|
fallbackNotification: notification.text,
|
||||||
fallbackNotified: notification.notified,
|
fallbackNotified: notification.notified,
|
||||||
reason: decision.reason,
|
reason: decision.reason,
|
||||||
|
...(decision.action === "use_alternate_target"
|
||||||
|
? buildToolRankFallbackTargetDetails(options.provider, config)
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {summarizeModelOutput} from "./response-model-output";
|
|||||||
import {summarizeToolLoop} from "./tool-loop-summary";
|
import {summarizeToolLoop} from "./tool-loop-summary";
|
||||||
import {persistToolLoopSummaryArtifactAttachment} from "./tool-loop-artifact-store";
|
import {persistToolLoopSummaryArtifactAttachment} from "./tool-loop-artifact-store";
|
||||||
import {PipelineFallbackNotifier} from "./user-request-pipeline/fallback-notifier";
|
import {PipelineFallbackNotifier} from "./user-request-pipeline/fallback-notifier";
|
||||||
|
import {buildToolRankFallbackTargetDetails} from "./user-request-pipeline/fallback-target-details";
|
||||||
import {
|
import {
|
||||||
resolveTextToSpeechProviderForUser,
|
resolveTextToSpeechProviderForUser,
|
||||||
sendSynthesizedSpeech,
|
sendSynthesizedSpeech,
|
||||||
@@ -395,6 +396,23 @@ export async function runUnifiedAiResponsePipeline(params: {
|
|||||||
"audit_finish",
|
"audit_finish",
|
||||||
],
|
],
|
||||||
onFallback: async decision => {
|
onFallback: async decision => {
|
||||||
|
if (decision.action === "use_alternate_target") {
|
||||||
|
aiLog("warn", "response.fallback.use_alternate_target", {
|
||||||
|
provider: options.provider,
|
||||||
|
stage: decision.stage,
|
||||||
|
reason: decision.reason,
|
||||||
|
...buildToolRankFallbackTargetDetails(options.provider, config),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision.action === "fail_request") {
|
||||||
|
aiLog("error", "response.fallback.fail_request", {
|
||||||
|
provider: options.provider,
|
||||||
|
stage: decision.stage,
|
||||||
|
reason: decision.reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const notification = await fallbackNotifier.notify(state.requestId, decision);
|
const notification = await fallbackNotifier.notify(state.requestId, decision);
|
||||||
state.audit.push({
|
state.audit.push({
|
||||||
stage: decision.stage,
|
stage: decision.stage,
|
||||||
@@ -406,6 +424,9 @@ export async function runUnifiedAiResponsePipeline(params: {
|
|||||||
fallbackNotification: notification.text,
|
fallbackNotification: notification.text,
|
||||||
fallbackNotified: notification.notified,
|
fallbackNotified: notification.notified,
|
||||||
reason: decision.reason,
|
reason: decision.reason,
|
||||||
|
...(decision.action === "use_alternate_target"
|
||||||
|
? buildToolRankFallbackTargetDetails(options.provider, config)
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type {PipelineFallbackDecision} from "./fallback-executor.js";
|
||||||
|
|
||||||
|
export class PipelineRequestFailure extends Error {
|
||||||
|
constructor(public readonly decision: PipelineFallbackDecision, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "PipelineRequestFailure";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function raisePipelineRequestFailure(decision: PipelineFallbackDecision, stageName: string): never {
|
||||||
|
throw new PipelineRequestFailure(decision, `Pipeline send failed at stage ${stageName} with fallback action ${decision.action}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import {AiProvider} from "../../model/ai-provider.js";
|
||||||
|
import type {RuntimeConfigSnapshot} from "../unified-ai-runner.shared.js";
|
||||||
|
import {aiLogProviderTarget} from "../../logging/ai-logger.js";
|
||||||
|
import {buildRankerTarget} from "../tool-ranker-pipeline.js";
|
||||||
|
import {providerChatTarget} from "../unified-ai-runner.shared.js";
|
||||||
|
|
||||||
|
export function buildToolRankFallbackTargetDetails(provider: AiProvider, config: RuntimeConfigSnapshot) {
|
||||||
|
const sourceTarget = buildRankerTarget(config, provider);
|
||||||
|
const alternateTarget = providerChatTarget(provider, config);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceTarget: aiLogProviderTarget(sourceTarget),
|
||||||
|
alternateTarget: aiLogProviderTarget(alternateTarget),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {DEFAULT_PIPELINE_FALLBACK_POLICIES, USER_REQUEST_PIPELINE_STAGES} from "./blueprint.js";
|
import {DEFAULT_PIPELINE_FALLBACK_POLICIES, USER_REQUEST_PIPELINE_STAGES} from "./blueprint.js";
|
||||||
import {decidePipelineFallback, type PipelineFallbackDecision} from "./fallback-executor.js";
|
import {decidePipelineFallback, type PipelineFallbackDecision} from "./fallback-executor.js";
|
||||||
|
import {raisePipelineRequestFailure} from "./fallback-failure.js";
|
||||||
import type {
|
import type {
|
||||||
PipelineAuditEvent,
|
PipelineAuditEvent,
|
||||||
PipelineFallbackPolicy,
|
PipelineFallbackPolicy,
|
||||||
@@ -66,7 +67,7 @@ export class UserRequestPipeline {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
if (decision.shouldFailRequest) {
|
if (decision.shouldFailRequest) {
|
||||||
throw new Error(`Required pipeline stage is not registered: ${stageName}`);
|
raisePipelineRequestFailure(decision, stageName);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ export class UserRequestPipeline {
|
|||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
}));
|
}));
|
||||||
if (decision.shouldFailRequest) {
|
if (decision.shouldFailRequest) {
|
||||||
throw error;
|
raisePipelineRequestFailure(decision, stageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user