fix(autoCombo): use longest pattern match in static fitness table (#8603) (#8664)

Sort static fitness table patterns by descending length before matching. This prevents shorter substrings like 'gpt-4o' from incorrectly matching longer model IDs like 'gpt-4o-mini' when 'gpt-4o' happens to be listed earlier in JavaScript Object key iteration order.

Co-authored-by: Austin Liu <austinliu@Austins-MacBook-Air-3.local>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
Austin Liu
2026-07-28 05:54:18 +09:30
committed by GitHub
parent 6ca33ef58a
commit 809e6d1880
2 changed files with 22 additions and 18 deletions

View File

@@ -277,19 +277,10 @@ export function getModelsDevTierFitness(model: string, taskType: string): number
const dbScore = queryModelIntelligence(normalizedModel, normalizedTask, "models_dev_tier");
if (dbScore !== null) return dbScore;
const baseModel = normalizedModel.endsWith(FREE_SUFFIX)
? normalizedModel.slice(0, -FREE_SUFFIX.length)
: normalizedModel;
if (baseModel !== normalizedModel) {
const baseDbScore = queryModelIntelligence(baseModel, normalizedTask, "models_dev_tier");
if (baseDbScore !== null) return baseDbScore;
}
const caps = loadModelCapabilities();
if (!caps) return null;
const capRow = caps[normalizedModel] || caps[baseModel];
const capRow = caps[normalizedModel];
if (!capRow) return null;
const tier = deriveTierFromCapabilities(capRow);
@@ -303,8 +294,8 @@ export function getModelsDevTierFitness(model: string, taskType: string): number
function lookupStaticFitnessTable(normalizedModel: string, normalizedTask: string): number | null {
const table = FITNESS_TABLE[normalizedTask] || FITNESS_TABLE.default;
const entries = Object.entries(table).sort((a, b) => b[0].length - a[0].length);
for (const [pattern, score] of entries) {
const sortedEntries = Object.entries(table).sort((a, b) => b[0].length - a[0].length);
for (const [pattern, score] of sortedEntries) {
if (normalizedModel.includes(pattern)) return score;
}
return null;
@@ -357,16 +348,12 @@ export function getTaskFitnessWithSource(
return { score: tierScore, source: "models_dev_tier" };
}
const baseModel = normalizedModel.endsWith(FREE_SUFFIX)
? normalizedModel.slice(0, -FREE_SUFFIX.length)
: normalizedModel;
const staticScore = lookupStaticFitnessTable(baseModel, normalizedTask);
const staticScore = lookupStaticFitnessTable(normalizedModel, normalizedTask);
if (staticScore !== null) {
return { score: staticScore, source: "fitness_table" };
}
return { score: lookupWildcardBoosts(baseModel, normalizedTask), source: "wildcard_boost" };
return { score: lookupWildcardBoosts(normalizedModel, normalizedTask), source: "wildcard_boost" };
}
/** Suffix used to mark free-tier model variants (e.g. "mimo-v2.5-free"). */

View File

@@ -0,0 +1,17 @@
import { describe, expect, it } from "vitest";
import { getTaskFitnessWithSource } from "../../../open-sse/services/autoCombo/taskFitness.js";
describe("lookupStaticFitnessTable longest-match (#8603)", () => {
it("should match gpt-4o-mini to its specific score (0.8) rather than gpt-4o (0.9)", () => {
// Using a model name that falls through to static table (e.g. unknown-provider/gpt-4o-mini)
const result = getTaskFitnessWithSource("unknown-provider-xyz/gpt-4o-mini", "coding");
expect(result.source).toBe("fitness_table");
expect(result.score).toBe(0.8);
});
it("should match gpt-4o to gpt-4o score (0.9)", () => {
const result = getTaskFitnessWithSource("unknown-provider-xyz/gpt-4o", "coding");
expect(result.source).toBe("fitness_table");
expect(result.score).toBe(0.9);
});
});