💄 style: optimize nana banana pro error message (#10378)

This commit is contained in:
YuTengjing
2025-11-23 21:51:00 +08:00
committed by GitHub
parent cdc71b26c6
commit cb34757743
5 changed files with 54 additions and 23 deletions

View File

@@ -8,6 +8,7 @@ import { createGoogleImage } from './createImage';
const provider = 'google';
const bizErrorType = 'ProviderBizError';
const noImageErrorType = 'ProviderNoImageGenerated';
const invalidErrorType = 'InvalidProviderAPIKey';
// Mock the console.error to avoid polluting test output
@@ -201,7 +202,7 @@ describe('createGoogleImage', () => {
// Act & Assert - Test error behavior rather than specific text
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
expect.objectContaining({
errorType: bizErrorType,
errorType: noImageErrorType,
provider,
}),
);
@@ -224,7 +225,7 @@ describe('createGoogleImage', () => {
// Act & Assert
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
expect.objectContaining({
errorType: bizErrorType,
errorType: noImageErrorType,
provider,
}),
);
@@ -251,7 +252,7 @@ describe('createGoogleImage', () => {
// Act & Assert
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
expect.objectContaining({
errorType: bizErrorType,
errorType: noImageErrorType,
provider,
}),
);
@@ -602,7 +603,7 @@ describe('createGoogleImage', () => {
// Act & Assert
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
expect.objectContaining({
errorType: bizErrorType,
errorType: noImageErrorType,
provider,
}),
);
@@ -627,7 +628,7 @@ describe('createGoogleImage', () => {
// Act & Assert
await expect(createGoogleImage(mockClient, provider, payload)).rejects.toEqual(
expect.objectContaining({
errorType: bizErrorType,
errorType: noImageErrorType,
provider,
}),
);

View File

@@ -47,7 +47,11 @@ async function processImageForParts(imageUrl: string): Promise<Part> {
*/
function extractImageFromResponse(response: any): CreateImageResponse {
const candidate = response.candidates?.[0];
if (candidate?.finishReason === 'NO_IMAGE') {
throw new Error('No image generated');
}
if (!candidate?.content?.parts) {
// Handle cases where Google returns 200 but omits image parts (often moderation)
throw new Error('No image generated');
}
@@ -58,6 +62,7 @@ function extractImageFromResponse(response: any): CreateImageResponse {
}
}
// Fallback when no inlineData is present (commonly moderation or policy blocks)
throw new Error('No image data found in response');
}
@@ -79,16 +84,11 @@ async function generateByImageModel(
prompt: params.prompt,
});
if (!response.generatedImages || response.generatedImages.length === 0) {
throw new Error('No images generated');
const imageBytes = response.generatedImages?.[0]?.image?.imageBytes;
if (!imageBytes) {
throw new Error('No image generated');
}
const generatedImage = response.generatedImages[0];
if (!generatedImage.image || !generatedImage.image.imageBytes) {
throw new Error('Invalid image data');
}
const { imageBytes } = generatedImage.image;
// 1. official doc use png as example
// 2. no responseType param support like openai now.
// I think we can just hard code png now
@@ -189,6 +189,10 @@ export async function createGoogleImage(
} catch (error) {
const err = error as Error;
if ((err as any)?.errorType) {
throw err;
}
const { errorType, error: parsedError } = parseGoogleErrorMessage(err.message);
throw AgentRuntimeError.createImage({
error: parsedError,

View File

@@ -19,14 +19,6 @@ export const AgentRuntimeErrorType = {
OllamaBizError: 'OllamaBizError',
OllamaServiceUnavailable: 'OllamaServiceUnavailable',
InvalidComfyUIArgs: 'InvalidComfyUIArgs',
ComfyUIBizError: 'ComfyUIBizError',
ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
ComfyUIEmptyResult: 'ComfyUIEmptyResult',
ComfyUIUploadFailed: 'ComfyUIUploadFailed',
ComfyUIWorkflowError: 'ComfyUIWorkflowError',
ComfyUIModelError: 'ComfyUIModelError',
InvalidBedrockCredentials: 'InvalidBedrockCredentials',
InvalidVertexCredentials: 'InvalidVertexCredentials',
StreamChunkError: 'StreamChunkError',
@@ -35,6 +27,17 @@ export const AgentRuntimeErrorType = {
ConnectionCheckFailed: 'ConnectionCheckFailed',
// ******* Image Generation Error ******* //
ProviderNoImageGenerated: 'ProviderNoImageGenerated',
InvalidComfyUIArgs: 'InvalidComfyUIArgs',
ComfyUIBizError: 'ComfyUIBizError',
ComfyUIServiceUnavailable: 'ComfyUIServiceUnavailable',
ComfyUIEmptyResult: 'ComfyUIEmptyResult',
ComfyUIUploadFailed: 'ComfyUIUploadFailed',
ComfyUIWorkflowError: 'ComfyUIWorkflowError',
ComfyUIModelError: 'ComfyUIModelError',
/**
* @deprecated
*/

View File

@@ -104,6 +104,11 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
}
const lowerMessage = message.toLowerCase();
if (lowerMessage.includes('no image generated') || lowerMessage.includes('no image data')) {
return { error: { message }, errorType: AgentRuntimeErrorType.ProviderNoImageGenerated };
}
// Unified error type determination function
const getErrorType = (code: number | null, message: string): ILobeAgentRuntimeErrorType => {
if (code === 400 && message.includes('API key not valid')) {

View File

@@ -65,6 +65,7 @@ const checkAbortSignal = (signal: AbortSignal) => {
const categorizeError = (
error: any,
isAborted: boolean,
isEditingImage: boolean,
): { errorMessage: string; errorType: AsyncTaskErrorType } => {
log('🔥🔥🔥 [ASYNC] categorizeError called:', {
errorMessage: error?.message,
@@ -73,6 +74,7 @@ const categorizeError = (
errorType: error?.errorType,
fullError: JSON.stringify(error, null, 2),
isAborted,
isEditingImage,
});
// Handle Comfy UI errors
if (error.errorType === AgentRuntimeErrorType.ComfyUIServiceUnavailable) {
@@ -127,6 +129,15 @@ const categorizeError = (
};
}
if (error.errorType === AgentRuntimeErrorType.ProviderNoImageGenerated) {
return {
errorMessage: isEditingImage
? 'Provider returned no image (maybe content review). Try a safer source image or milder prompt.'
: 'Provider returned no image (maybe content review). Try a milder prompt or another model.',
errorType: AsyncTaskErrorType.ServerError,
};
}
// FIXME: 401 的问题应该放到 agentRuntime 中处理会更好
if (error.errorType === AgentRuntimeErrorType.InvalidProviderAPIKey || error?.status === 401) {
return {
@@ -195,11 +206,14 @@ export const imageRouter = router({
const abortController = new AbortController();
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const isEditingImage =
Boolean((params as any).imageUrl) || Boolean(params.imageUrls && params.imageUrls.length > 0);
try {
const imageGenerationPromise = async (signal: AbortSignal) => {
log('Initializing agent runtime for provider: %s', provider);
const agentRuntime = await initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
const agentRuntime = initModelRuntimeWithUserPayload(provider, ctx.jwtPayload);
// Check if operation has been cancelled
checkAbortSignal(signal);
@@ -328,7 +342,11 @@ export const imageRouter = router({
});
// Improved error categorization logic
const { errorType, errorMessage } = categorizeError(error, abortController.signal.aborted);
const { errorType, errorMessage } = categorizeError(
error,
abortController.signal.aborted,
isEditingImage,
);
await ctx.asyncTaskModel.update(taskId, {
error: new AsyncTaskError(errorType, errorMessage),