Skip to content

Commit 9d9340c

Browse files
committed
feat: improve multi-model evaluation reliability
- Refactor generation evaluation to try models sequentially until success - Enhance AI provider setup to support multiple models with index selection - Simplify pre-commit hook to use local lint-staged - Bump version to 2.0.2
1 parent 63c96d3 commit 9d9340c

File tree

4 files changed

+145
-43
lines changed

4 files changed

+145
-43
lines changed

.husky/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
npx lint-staged
1+
lint-staged

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ai-pp3",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "CLI tool combining multimodal AI analysis with RawTherapee's engine to generate optimized PP3 profiles for RAW photography",
55
"engines": {
66
"node": ">=18"

src/ai-generation/ai-processor.ts

Lines changed: 131 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -269,44 +269,44 @@ export function parseBestGenerationIndex(
269269
}
270270

271271
/**
272-
* Evaluates multiple generations and selects the best one
272+
* Handles the case when there's only one successful generation
273273
*/
274-
export async function evaluateGenerations(
274+
function handleSingleGeneration(
275+
successfulResults: GenerationResult[],
275276
generationResults: GenerationResult[],
277+
): { bestIndex: number; evaluationReason: string } {
278+
const originalIndex = generationResults.indexOf(successfulResults[0]);
279+
return {
280+
bestIndex: originalIndex,
281+
evaluationReason: "Only one successful generation available",
282+
};
283+
}
284+
285+
/**
286+
* Attempts to evaluate generations with a specific model
287+
*/
288+
async function attemptEvaluationWithModel(
289+
model: string,
290+
modelIndex: number,
291+
models: string[],
276292
providerName: string,
277-
visionModel: string | string[],
293+
imageContents: (
294+
| { type: "text"; text: string }
295+
| { type: "image"; image: Buffer }
296+
)[],
278297
maxRetries: number,
298+
successfulResults: GenerationResult[],
299+
generationResults: GenerationResult[],
279300
verbose: boolean,
280-
): Promise<{ bestIndex: number; evaluationReason: string }> {
281-
// Filter out failed generations
282-
const successfulResults = generationResults.filter(
283-
(result) => result.success,
284-
);
285-
286-
if (successfulResults.length === 0) {
287-
throw new Error("No successful generations to evaluate");
288-
}
289-
290-
if (successfulResults.length === 1) {
291-
// Find the index of this successful result in the original array
292-
const originalIndex = generationResults.indexOf(successfulResults[0]);
293-
return {
294-
bestIndex: originalIndex,
295-
evaluationReason: "Only one successful generation available",
296-
};
297-
}
298-
299-
const aiProvider = handleProviderSetup(providerName, visionModel);
300-
// Only pass successful generations to prepareImageContents
301-
const imageContents = await prepareImageContents(successfulResults, verbose);
302-
303-
if (verbose) {
301+
): Promise<{ bestIndex: number; evaluationReason: string } | null> {
302+
if (verbose && models.length > 1) {
304303
console.log(
305-
`Evaluating ${String(successfulResults.length)} successful generations with AI...`,
304+
`Attempting evaluation with model ${model} (${String(modelIndex + 1)}/${String(models.length)})...`,
306305
);
307306
}
308307

309308
try {
309+
const aiProvider = handleProviderSetup(providerName, model);
310310
const response = await generateText({
311311
model: aiProvider,
312312
messages: [
@@ -343,18 +343,110 @@ export async function evaluateGenerations(
343343
bestIndex: finalIndex,
344344
evaluationReason: responseText,
345345
};
346-
} catch (error) {
347-
if (verbose) {
348-
console.warn("AI evaluation failed, using first generation:", error);
349-
}
350-
// Find the index of the first successful generation in the original array
351-
const firstSuccessfulIndex = generationResults.findIndex(
352-
(result) => result.success,
346+
} catch {
347+
// Handle error in the calling function
348+
return null;
349+
}
350+
}
351+
352+
/**
353+
* Handles the fallback case when all models fail
354+
*/
355+
function handleAllModelsFailed(
356+
generationResults: GenerationResult[],
357+
lastError: unknown,
358+
verbose: boolean,
359+
): { bestIndex: number; evaluationReason: string } {
360+
if (verbose) {
361+
console.warn(
362+
"All AI evaluation models failed, using first generation as fallback:",
363+
lastError,
353364
);
365+
}
354366

355-
return {
356-
bestIndex: Math.max(firstSuccessfulIndex, 0),
357-
evaluationReason: `AI evaluation failed: ${error instanceof Error ? error.message : "Unknown error"}. Using first successful generation as fallback.`,
358-
};
367+
// Find the index of the first successful generation in the original array
368+
const firstSuccessfulIndex = generationResults.findIndex(
369+
(result) => result.success,
370+
);
371+
372+
return {
373+
bestIndex: Math.max(firstSuccessfulIndex, 0),
374+
evaluationReason: `AI evaluation failed: ${lastError instanceof Error ? lastError.message : "Unknown error"}. Using first successful generation as fallback.`,
375+
};
376+
}
377+
378+
/**
379+
* Evaluates multiple generations and selects the best one
380+
* If multiple models are specified, it will try each one sequentially until successful
381+
*/
382+
export async function evaluateGenerations(
383+
generationResults: GenerationResult[],
384+
providerName: string,
385+
visionModel: string | string[],
386+
maxRetries: number,
387+
verbose: boolean,
388+
): Promise<{ bestIndex: number; evaluationReason: string }> {
389+
// Filter out failed generations
390+
const successfulResults = generationResults.filter(
391+
(result) => result.success,
392+
);
393+
394+
if (successfulResults.length === 0) {
395+
throw new Error("No successful generations to evaluate");
396+
}
397+
398+
if (successfulResults.length === 1) {
399+
return handleSingleGeneration(successfulResults, generationResults);
359400
}
401+
402+
// Only pass successful generations to prepareImageContents
403+
const imageContents = await prepareImageContents(successfulResults, verbose);
404+
405+
if (verbose) {
406+
console.log(
407+
`Evaluating ${String(successfulResults.length)} successful generations with AI...`,
408+
);
409+
}
410+
411+
// Convert visionModel to array for sequential attempts
412+
const models = Array.isArray(visionModel) ? visionModel : [visionModel];
413+
let lastError: unknown = null;
414+
415+
// Try each model sequentially until one succeeds
416+
for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {
417+
const currentModel = models[modelIndex];
418+
419+
const result = await attemptEvaluationWithModel(
420+
currentModel,
421+
modelIndex,
422+
models,
423+
providerName,
424+
imageContents,
425+
maxRetries,
426+
successfulResults,
427+
generationResults,
428+
verbose,
429+
);
430+
431+
if (result) {
432+
return result;
433+
} else {
434+
// Handle error and try next model if available
435+
const error = new Error(`Evaluation with model ${currentModel} failed`);
436+
lastError = error;
437+
438+
if (verbose) {
439+
console.warn(
440+
`AI evaluation with model ${currentModel} failed: ${error.message}`,
441+
);
442+
}
443+
444+
if (modelIndex < models.length - 1 && verbose) {
445+
console.log(`Trying next model: ${models[modelIndex + 1]}...`);
446+
}
447+
}
448+
}
449+
450+
// If we get here, all models failed
451+
return handleAllModelsFailed(generationResults, lastError, verbose);
360452
}

src/utils/ai-provider.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,23 @@
44

55
import { provider } from "../provider.js";
66

7+
/**
8+
* Sets up the AI provider with the specified model
9+
*
10+
* @param providerName The name of the AI provider to use
11+
* @param model A single model name or an array of model names
12+
* @param modelIndex Optional index to use a specific model from the array (defaults to 0)
13+
* @returns The configured AI provider
14+
*/
715
export function handleProviderSetup(
816
providerName: string,
917
model: string | string[],
18+
modelIndex?: number,
1019
) {
1120
try {
12-
// If model is an array, use the first model in the array
13-
const modelToUse = Array.isArray(model) ? model[0] : model;
21+
// If model is an array, use the specified model index or default to the first model
22+
const modelToUse = Array.isArray(model) ? model[modelIndex ?? 0] : model;
23+
1424
return provider(providerName)(modelToUse);
1525
} catch (error: unknown) {
1626
throw new Error(

0 commit comments

Comments
 (0)