Skip to content

Commit af78a90

Browse files
committed
feat: update error management in AI generation processes
1 parent 05f699c commit af78a90

File tree

3 files changed

+91
-49
lines changed

3 files changed

+91
-49
lines changed

custom/VisionAction.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ function fillCarouselSaveImages() {
155155
const tempItemIndex: any = {};
156156
for (const [key, value] of Object.entries(item)) {
157157
if (props.meta.outputImageFields?.includes(key)) {
158-
tempItem[key] = "";
158+
tempItem[key] = [];
159159
tempItemIndex[key] = 0;
160160
}
161161
}
@@ -496,6 +496,10 @@ async function runAiAction({
496496
isAtLeastOneInProgress = true;
497497
// if job is failed - set error
498498
} else if (jobStatus === 'failed') {
499+
const index = selected.value.findIndex(item => String(item[primaryKey]) === String(recordId));
500+
if (actionType !== 'analyze_no_images' || !props.meta.isFieldsForAnalizeFromImages) {
501+
responseFlag.value[index] = true;
502+
}
499503
adminforth.alert({
500504
message: `Generation action "${actionType.replace('_', ' ')}" failed for record: ${recordId}. Error: ${jobResponse.job?.error || 'Unknown error'}`,
501505
variant: 'danger',

custom/VisionTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</template>
1111
<!-- CHECKBOX CELL TEMPLATE -->
1212
<template #cell:checkboxes="{ item }">
13-
<div class="flex items-center justify-center">
13+
<div class="max-w-[100px] flex items-center justify-center">
1414
<Checkbox
1515
v-model="selected[tableColumnsIndexes.findIndex(el => el[primaryKey] === item[primaryKey])].isChecked"
1616
/>

index.ts

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { PluginOptions } from './types.js';
44
import Handlebars from 'handlebars';
55
import { RateLimiter } from "adminforth";
66
import { randomUUID } from "crypto";
7-
import { ref } from "process";
87

98
const STUB_MODE = false;
109
const jobs = new Map();
@@ -70,6 +69,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
7069

7170
private async analyze_image(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
7271
const selectedId = recordId;
72+
let isError = false;
7373
if (typeof(this.options.rateLimits?.fillFieldsFromImages) === 'string'){
7474
if (this.checkRateLimit("fillFieldsFromImages" ,this.options.rateLimits.fillFieldsFromImages, headers)) {
7575
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
@@ -96,39 +96,46 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
9696
Image URLs:`;
9797

9898
//send prompt to OpenAI and get response
99-
const chatResponse = await this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
100-
101-
const resp: any = (chatResponse as any).response;
102-
const topLevelError = (chatResponse as any).error;
103-
if (topLevelError || resp?.error) {
104-
jobs.set(jobId, { status: 'failed', error: topLevelError || resp?.error });
105-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
99+
let chatResponse;
100+
try {
101+
chatResponse = await this.options.visionAdapter.generate({ prompt, inputFileUrls: attachmentFiles });
102+
} catch (e) {
103+
isError = true;
104+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to analize images' });
105+
return { ok: false, error: 'AI provider refused to analize images' };
106106
}
107+
if (!isError) {
108+
const resp: any = (chatResponse as any).response;
109+
const topLevelError = (chatResponse as any).error;
110+
if (topLevelError || resp?.error) {
111+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError || resp?.error)}` });
112+
}
107113

108-
const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
109-
if (!textOutput || typeof textOutput !== 'string') {
110-
jobs.set(jobId, { status: 'failed', error: 'Unexpected AI response format' });
111-
throw new Error('Unexpected AI response format');
112-
}
114+
const textOutput = resp?.output?.[0]?.content?.[0]?.text ?? resp?.output_text ?? resp?.choices?.[0]?.message?.content;
115+
if (!textOutput || typeof textOutput !== 'string') {
116+
jobs.set(jobId, { status: 'failed', error: 'Unexpected AI response format' });
117+
}
113118

114-
//parse response and update record
115-
const resData = JSON.parse(textOutput);
116-
const result = resData;
117-
jobs.set(jobId, { status: 'completed', result });
118-
return { ok: true };
119+
//parse response and update record
120+
const resData = JSON.parse(textOutput);
121+
const result = resData;
122+
jobs.set(jobId, { status: 'completed', result });
123+
return { ok: true };
124+
}
119125
};
120126
}
121127

122128
private async analyzeNoImages(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
123129
const selectedId = recordId;
130+
let isError = false;
124131
if (typeof(this.options.rateLimits?.fillPlainFields) === 'string'){
125132
if (this.checkRateLimit("fillPlainFields", this.options.rateLimits.fillPlainFields, headers)) {
126133
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
127134
return { error: "Rate limit exceeded" };
128135
}
129136
}
130137
if (STUB_MODE) {
131-
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 2000) + 1000));
138+
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
132139
jobs.set(jobId, { status: 'completed', result: {} });
133140
return {};
134141
} else {
@@ -142,15 +149,22 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
142149
If it's number field - return only number.`;
143150
//send prompt to OpenAI and get response
144151
const numberOfTokens = this.options.fillPlainFieldsMaxTokens ? this.options.fillPlainFieldsMaxTokens : 1000;
145-
const { content: chatResponse } = await this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
146-
147-
const resp: any = (chatResponse as any).response;
148-
const topLevelError = (chatResponse as any).error;
149-
if (topLevelError || resp?.error) {
150-
jobs.set(jobId, { status: 'failed', error: topLevelError || resp?.error });
151-
throw new Error(`ERROR: ${JSON.stringify(topLevelError || resp?.error)}`);
152+
let resp: any;
153+
try {
154+
const { content: chatResponse } = await this.options.textCompleteAdapter.complete(prompt, [], numberOfTokens);
155+
resp = (chatResponse as any).response;
156+
const topLevelError = (chatResponse as any).error;
157+
if (topLevelError || resp?.error) {
158+
isError = true;
159+
jobs.set(jobId, { status: 'failed', error: `ERROR: ${JSON.stringify(topLevelError || resp?.error)}` });
160+
}
161+
resp = chatResponse
162+
} catch (e) {
163+
isError = true;
164+
jobs.set(jobId, { status: 'failed', error: 'AI provider refused to fill fields' });
165+
return { ok: false, error: 'AI provider refused to fill fields' };
152166
}
153-
const resData = JSON.parse(chatResponse);
167+
const resData = JSON.parse(resp);
154168

155169
//return resData;
156170
const result = resData;
@@ -161,6 +175,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
161175

162176
private async initialImageGenerate(jobId: string, recordId: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
163177
const selectedId = recordId;
178+
let isError = false;
164179
if (typeof(this.options.rateLimits?.generateImages) === 'string'){
165180
if (this.checkRateLimit("generateImages", this.options.rateLimits.generateImages, headers)) {
166181
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
@@ -184,7 +199,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
184199
return { key, images: [] };
185200
} else {
186201
if (STUB_MODE) {
187-
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 8000) + 1000));
202+
await new Promise((resolve) => setTimeout(resolve, Math.floor(Math.random() * 20000) + 1000));
188203
images = `https://pic.re/image`;
189204
} else {
190205
let generationAdapter;
@@ -193,14 +208,21 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
193208
} else {
194209
generationAdapter = this.options.imageGenerationAdapter;
195210
}
196-
const resp = await generationAdapter.generate(
197-
{
198-
prompt,
199-
inputFiles: attachmentFiles,
200-
n: 1,
201-
size: this.options.generateImages[key].outputSize,
202-
}
203-
)
211+
let resp;
212+
try {
213+
resp = await generationAdapter.generate(
214+
{
215+
prompt,
216+
inputFiles: attachmentFiles,
217+
n: 1,
218+
size: this.options.generateImages[key].outputSize,
219+
}
220+
)
221+
} catch (e) {
222+
jobs.set(jobId, { status: 'failed', error: "AI provider refused to generate image" });
223+
isError = true;
224+
return { key, images: [] };
225+
}
204226
images = resp.imageURLs[0];
205227
}
206228
return { key, images };
@@ -219,12 +241,17 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
219241

220242
this.totalCalls++;
221243
this.totalDuration += (+new Date() - start) / 1000;
244+
if (!isError) {
222245
jobs.set(jobId, { status: 'completed', result });
223-
return { ok: true };
246+
return { ok: true }
247+
} else {
248+
return { ok: false, error: 'Error during image generation' };
249+
}
224250
}
225251

226252
private async regenerateImage(jobId: string, recordId: string, fieldName: string, prompt: string, adminUser: any, headers: Record<string, string | string[] | undefined>) {
227253
const Id = recordId;
254+
let isError = false;
228255
if (this.checkRateLimit(fieldName, this.options.generateImages[fieldName].rateLimit, headers)) {
229256
jobs.set(jobId, { status: 'failed', error: "Rate limit exceeded" });
230257
return { error: "Rate limit exceeded" };
@@ -255,21 +282,32 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
255282
} else {
256283
generationAdapter = this.options.imageGenerationAdapter;
257284
}
258-
const resp = await generationAdapter.generate(
259-
{
260-
prompt,
261-
inputFiles: attachmentFiles,
262-
n: 1,
263-
size: this.options.generateImages[fieldName].outputSize,
264-
}
265-
)
285+
let resp;
286+
try {
287+
resp = await generationAdapter.generate(
288+
{
289+
prompt,
290+
inputFiles: attachmentFiles,
291+
n: 1,
292+
size: this.options.generateImages[fieldName].outputSize,
293+
}
294+
)
295+
} catch (e) {
296+
jobs.set(jobId, { status: 'failed', error: "AI provider refused to generate image" });
297+
isError = true;
298+
return [];
299+
}
266300
return resp.imageURLs[0]
267301
})
268302
);
269303
this.totalCalls++;
270304
this.totalDuration += (+new Date() - start) / 1000;
271-
jobs.set(jobId, { status: 'completed', result: { [fieldName]: images } });
272-
return { ok: true };
305+
if (!isError) {
306+
jobs.set(jobId, { status: 'completed', result: { [fieldName]: images } });
307+
return { ok: true };
308+
} else {
309+
return { ok: false, error: 'Error during image generation' };
310+
}
273311
}
274312

275313
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {

0 commit comments

Comments
 (0)