mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
🐛 fix: pause input completion after errors (#15692)
This commit is contained in:
@@ -1032,6 +1032,56 @@ describe('aiChatRouter', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('marks input completion runtime 4xx errors to skip tRPC handler logging', async () => {
|
||||
const { initModelRuntimeFromDB } = await import('@/server/modules/ModelRuntime');
|
||||
const runtimeError = {
|
||||
error: { message: 'rate limited' },
|
||||
errorType: AgentRuntimeErrorType.RateLimitExceeded,
|
||||
};
|
||||
|
||||
vi.mocked(initModelRuntimeFromDB).mockRejectedValueOnce(runtimeError);
|
||||
|
||||
const caller = aiChatRouter.createCaller({ ...mockCtx, serverDB: {} } as any);
|
||||
|
||||
try {
|
||||
await caller.outputJSON({
|
||||
messages: [{ content: 'test', role: 'user' }],
|
||||
model: 'gpt-4o',
|
||||
provider: 'openai',
|
||||
tracing: { scenario: 'input_completion' },
|
||||
});
|
||||
throw new Error('Expected outputJSON to throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TRPCError);
|
||||
expect((runtimeError as any).__lobeSilentTRPCErrorLog).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('does not mark non-input-completion runtime errors as silent', async () => {
|
||||
const { initModelRuntimeFromDB } = await import('@/server/modules/ModelRuntime');
|
||||
const runtimeError = {
|
||||
error: { message: 'rate limited' },
|
||||
errorType: AgentRuntimeErrorType.RateLimitExceeded,
|
||||
};
|
||||
|
||||
vi.mocked(initModelRuntimeFromDB).mockRejectedValueOnce(runtimeError);
|
||||
|
||||
const caller = aiChatRouter.createCaller({ ...mockCtx, serverDB: {} } as any);
|
||||
|
||||
try {
|
||||
await caller.outputJSON({
|
||||
messages: [{ content: 'test', role: 'user' }],
|
||||
model: 'gpt-4o',
|
||||
provider: 'openai',
|
||||
tracing: { scenario: 'topic_title' },
|
||||
});
|
||||
throw new Error('Expected outputJSON to throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(TRPCError);
|
||||
expect((runtimeError as any).__lobeSilentTRPCErrorLog).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('maps raw provider 4xx errors to BAD_REQUEST instead of internal errors', async () => {
|
||||
const { initModelRuntimeFromDB } = await import('@/server/modules/ModelRuntime');
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { TRACING_SCENARIOS } from '@lobechat/const';
|
||||
import { getErrorCodeSpec } from '@lobechat/model-runtime';
|
||||
import type { CreateMessageParams, SendMessageServerResponse } from '@lobechat/types';
|
||||
import { AiSendMessageServerSchema, RequestTrigger, StructureOutputSchema } from '@lobechat/types';
|
||||
@@ -28,6 +29,7 @@ const log = debug('lobe-lambda-router:ai-chat');
|
||||
const { createPrefixedTimingContext, logTiming, runTimedStage } = createTimingHelpers(
|
||||
'lobe-server:chat:lobehub:timing',
|
||||
);
|
||||
const SILENT_TRPC_ERROR_LOG_KEY = '__lobeSilentTRPCErrorLog';
|
||||
|
||||
type TRPCErrorCode = ConstructorParameters<typeof TRPCError>[0]['code'];
|
||||
type TRPCStatusCode = Parameters<typeof getStatusKeyFromCode>[0];
|
||||
@@ -49,10 +51,28 @@ const getTRPCErrorCodeFromStatus = (status: number): TRPCErrorCode => {
|
||||
return 'INTERNAL_SERVER_ERROR';
|
||||
};
|
||||
|
||||
const createRuntimeTRPCError = (error: unknown): TRPCError | undefined => {
|
||||
const markSilentTRPCErrorLog = (error: unknown) => {
|
||||
if (!error || typeof error !== 'object') return;
|
||||
|
||||
try {
|
||||
Object.defineProperty(error, SILENT_TRPC_ERROR_LOG_KEY, {
|
||||
configurable: true,
|
||||
value: true,
|
||||
});
|
||||
} catch {
|
||||
// Best-effort logging hint; never let it mask the original runtime error.
|
||||
}
|
||||
};
|
||||
|
||||
const createRuntimeTRPCError = (
|
||||
error: unknown,
|
||||
options?: { silentHandlerLog?: boolean },
|
||||
): TRPCError | undefined => {
|
||||
const errorType = getRuntimeErrorType(error);
|
||||
const spec = getErrorCodeSpec(errorType);
|
||||
if (errorType && spec) {
|
||||
if (options?.silentHandlerLog && spec.httpStatus < 500) markSilentTRPCErrorLog(error);
|
||||
|
||||
return new TRPCError({
|
||||
cause: error,
|
||||
code: getTRPCErrorCodeFromStatus(spec.httpStatus),
|
||||
@@ -67,6 +87,8 @@ const createRuntimeTRPCError = (error: unknown): TRPCError | undefined => {
|
||||
// rejecting the request) pollutes server 500 monitoring.
|
||||
const status = (error as { status?: unknown } | undefined)?.status;
|
||||
if (typeof status === 'number' && status >= 400 && status < 500) {
|
||||
if (options?.silentHandlerLog) markSilentTRPCErrorLog(error);
|
||||
|
||||
return new TRPCError({
|
||||
cause: error,
|
||||
code: getTRPCErrorCodeFromStatus(status),
|
||||
@@ -129,7 +151,9 @@ export const aiChatRouter = router({
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
const runtimeTRPCError = createRuntimeTRPCError(error);
|
||||
const runtimeTRPCError = createRuntimeTRPCError(error, {
|
||||
silentHandlerLog: input.tracing?.scenario === TRACING_SCENARIOS.InputCompletion,
|
||||
});
|
||||
if (runtimeTRPCError) throw runtimeTRPCError;
|
||||
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user