🏆 fix: Longest-or-Exact-Key Match in findMatchingPattern, Remove Deprecated Models (#12073)

* 🔧 fix: Use longest-match in findMatchingPattern, remove deprecated PaLM2/Codey models

findMatchingPattern now selects the longest matching key instead of the
first reverse-order match, preventing cross-provider substring collisions
(e.g., "gpt-5.2-chat-2025-12-11" incorrectly matching Google's "chat-"
pattern instead of OpenAI's "gpt-5.2"). Adds early exit when key length
equals model name length. Reorders aggregateModels spreads so OpenAI is
last (preferred on same-length ties). Removes deprecated PaLM2/Codey
entries from googleModels.

* refactor: re-order models based on more likely usage

* refactor: Improve key matching logic in findMatchingPattern

Updated the findMatchingPattern function to enhance key matching by ensuring case-insensitive comparisons and maintaining the longest match priority. Clarified comments regarding key ordering and performance implications, emphasizing the importance of defining older models first for efficiency and the handling of same-length ties. This refactor aims to improve code clarity and maintainability.

* test: Enhance findMatchingPattern tests for edge cases and performance

Added new test cases to the findMatchingPattern function, covering scenarios such as empty model names, case-insensitive matching, and performance optimizations. Included checks for longest match priority and ensured deprecated PaLM2/Codey models are no longer present in token entries. This update aims to improve test coverage and validate the function's behavior under various conditions.

* test: Update findMatchingPattern test to use last key for exact match validation

Modified the test for findMatchingPattern to utilize the last key from the openAIMap for exact match checks, ensuring the test accurately reflects the expected behavior of the function. This change enhances the clarity and reliability of the test case.
This commit is contained in:
Danny Avila 2026-03-04 19:34:13 -05:00 committed by GitHub
parent c6dba9f0a1
commit 956f8fb6f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 311 additions and 189 deletions

View file

@ -251,16 +251,6 @@ describe('getModelMaxTokens', () => {
});
});
// Tests for Google models
test('should return correct tokens for exact match - Google models', () => {
expect(getModelMaxTokens('text-bison-32k', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['text-bison-32k'],
);
expect(getModelMaxTokens('codechat-bison-32k', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['codechat-bison-32k'],
);
});
test('should return undefined for no match - Google models', () => {
expect(getModelMaxTokens('unknown-google-model', EModelEndpoint.google)).toBeUndefined();
});
@ -317,12 +307,6 @@ describe('getModelMaxTokens', () => {
expect(getModelMaxTokens('gemini-pro', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['gemini'],
);
expect(getModelMaxTokens('code-', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['code-'],
);
expect(getModelMaxTokens('chat-', EModelEndpoint.google)).toBe(
maxTokensMap[EModelEndpoint.google]['chat-'],
);
});
test('should return correct tokens for partial match - Cohere models', () => {
@ -541,6 +525,184 @@ describe('getModelMaxTokens', () => {
});
});
describe('findMatchingPattern - longest match wins', () => {
test('should prefer longer matching key over shorter cross-provider pattern', () => {
const result = findMatchingPattern(
'gpt-5.2-chat-2025-12-11',
maxTokensMap[EModelEndpoint.openAI],
);
expect(result).toBe('gpt-5.2');
});
test('should match gpt-5.2 tokens for date-suffixed chat variant', () => {
expect(getModelMaxTokens('gpt-5.2-chat-2025-12-11')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-5.2'],
);
});
test('should match gpt-5.2-pro over shorter patterns', () => {
expect(getModelMaxTokens('gpt-5.2-pro-chat-2025-12-11')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-5.2-pro'],
);
});
test('should match gpt-5-mini over gpt-5 for mini variants', () => {
expect(getModelMaxTokens('gpt-5-mini-chat-2025-01-01')).toBe(
maxTokensMap[EModelEndpoint.openAI]['gpt-5-mini'],
);
});
test('should prefer gpt-4-1106 over gpt-4 for versioned model names', () => {
const result = findMatchingPattern('gpt-4-1106-preview', maxTokensMap[EModelEndpoint.openAI]);
expect(result).toBe('gpt-4-1106');
});
test('should prefer gpt-4-32k-0613 over gpt-4-32k for exact versioned names', () => {
const result = findMatchingPattern('gpt-4-32k-0613', maxTokensMap[EModelEndpoint.openAI]);
expect(result).toBe('gpt-4-32k-0613');
});
test('should prefer claude-3-5-sonnet over claude-3', () => {
const result = findMatchingPattern(
'claude-3-5-sonnet-20241022',
maxTokensMap[EModelEndpoint.anthropic],
);
expect(result).toBe('claude-3-5-sonnet');
});
test('should prefer gemini-2.0-flash-lite over gemini-2.0-flash', () => {
const result = findMatchingPattern(
'gemini-2.0-flash-lite-preview',
maxTokensMap[EModelEndpoint.google],
);
expect(result).toBe('gemini-2.0-flash-lite');
});
});
describe('findMatchingPattern - bestLength selection', () => {
test('should return the longest matching key when multiple keys match', () => {
const tokensMap = { short: 100, 'short-med': 200, 'short-med-long': 300 };
expect(findMatchingPattern('short-med-long-extra', tokensMap)).toBe('short-med-long');
});
test('should return the longest match regardless of key insertion order', () => {
const tokensMap = { 'a-b-c': 300, a: 100, 'a-b': 200 };
expect(findMatchingPattern('a-b-c-d', tokensMap)).toBe('a-b-c');
});
test('should return null when no key matches', () => {
const tokensMap = { alpha: 100, beta: 200 };
expect(findMatchingPattern('gamma-delta', tokensMap)).toBeNull();
});
test('should return the single matching key when only one matches', () => {
const tokensMap = { alpha: 100, beta: 200, gamma: 300 };
expect(findMatchingPattern('beta-extended', tokensMap)).toBe('beta');
});
test('should match case-insensitively against model name', () => {
const tokensMap = { 'gpt-5': 400000 };
expect(findMatchingPattern('GPT-5-turbo', tokensMap)).toBe('gpt-5');
});
test('should select the longest key among overlapping substring matches', () => {
const tokensMap = { 'gpt-': 100, 'gpt-5': 200, 'gpt-5.2': 300, 'gpt-5.2-pro': 400 };
expect(findMatchingPattern('gpt-5.2-pro-2025-01-01', tokensMap)).toBe('gpt-5.2-pro');
expect(findMatchingPattern('gpt-5.2-chat-2025-01-01', tokensMap)).toBe('gpt-5.2');
expect(findMatchingPattern('gpt-5.1-preview', tokensMap)).toBe('gpt-5');
expect(findMatchingPattern('gpt-unknown', tokensMap)).toBe('gpt-');
});
test('should not be confused by a short key that appears later in the model name', () => {
const tokensMap = { 'model-v2': 200, v2: 100 };
expect(findMatchingPattern('model-v2-extended', tokensMap)).toBe('model-v2');
});
test('should handle exact-length match as the best match', () => {
const tokensMap = { 'exact-model': 500, exact: 100 };
expect(findMatchingPattern('exact-model', tokensMap)).toBe('exact-model');
});
test('should return null for empty model name', () => {
expect(findMatchingPattern('', { 'gpt-5': 400000 })).toBeNull();
});
test('should prefer last-defined key on same-length ties', () => {
const tokensMap = { 'aa-bb': 100, 'cc-dd': 200 };
// model name contains both 5-char keys; last-defined wins in reverse iteration
expect(findMatchingPattern('aa-bb-cc-dd', tokensMap)).toBe('cc-dd');
});
test('longest match beats short cross-provider pattern even when both present', () => {
const tokensMap = { 'gpt-5.2': 400000, 'chat-': 8187 };
expect(findMatchingPattern('gpt-5.2-chat-2025-12-11', tokensMap)).toBe('gpt-5.2');
});
test('should match case-insensitively against keys', () => {
const tokensMap = { 'GPT-5': 400000 };
expect(findMatchingPattern('gpt-5-turbo', tokensMap)).toBe('GPT-5');
});
});
describe('findMatchingPattern - iteration performance', () => {
let includesSpy;
beforeEach(() => {
includesSpy = jest.spyOn(String.prototype, 'includes');
});
afterEach(() => {
includesSpy.mockRestore();
});
test('exact match early-exits with minimal includes() checks', () => {
const openAIMap = maxTokensMap[EModelEndpoint.openAI];
const keys = Object.keys(openAIMap);
const lastKey = keys[keys.length - 1];
includesSpy.mockClear();
const result = findMatchingPattern(lastKey, openAIMap);
const exactCalls = includesSpy.mock.calls.length;
expect(result).toBe(lastKey);
expect(exactCalls).toBe(1);
});
test('bestLength check skips includes() for shorter keys after a long match', () => {
const openAIMap = maxTokensMap[EModelEndpoint.openAI];
includesSpy.mockClear();
findMatchingPattern('gpt-3.5-turbo-0301-test', openAIMap);
const longKeyCalls = includesSpy.mock.calls.length;
includesSpy.mockClear();
findMatchingPattern('gpt-5.3-chat-latest', openAIMap);
const shortKeyCalls = includesSpy.mock.calls.length;
// gpt-3.5-turbo-0301 (20 chars) matches early, then bestLength prunes most keys
// gpt-5.3 (7 chars) is short, so fewer keys are pruned by the length check
expect(longKeyCalls).toBeLessThan(shortKeyCalls);
});
test('last-defined keys are checked first in reverse iteration', () => {
const tokensMap = { first: 100, second: 200, third: 300 };
includesSpy.mockClear();
const result = findMatchingPattern('third', tokensMap);
const calls = includesSpy.mock.calls.length;
// 'third' is last key, found on first reverse check, exact match exits immediately
expect(result).toBe('third');
expect(calls).toBe(1);
});
});
describe('deprecated PaLM2/Codey model removal', () => {
test('deprecated PaLM2/Codey models no longer have token entries', () => {
expect(getModelMaxTokens('text-bison-32k', EModelEndpoint.google)).toBeUndefined();
expect(getModelMaxTokens('codechat-bison-32k', EModelEndpoint.google)).toBeUndefined();
expect(getModelMaxTokens('code-bison', EModelEndpoint.google)).toBeUndefined();
expect(getModelMaxTokens('chat-bison', EModelEndpoint.google)).toBeUndefined();
});
});
describe('matchModelName', () => {
it('should return the exact model name if it exists in maxTokensMap', () => {
expect(matchModelName('gpt-4-32k-0613')).toBe('gpt-4-32k-0613');
@ -642,22 +804,11 @@ describe('matchModelName', () => {
expect(matchModelName('gpt-5.3-2025-03-01')).toBe('gpt-5.3');
});
// Tests for Google models
it('should return the exact model name if it exists in maxTokensMap - Google models', () => {
expect(matchModelName('text-bison-32k', EModelEndpoint.google)).toBe('text-bison-32k');
expect(matchModelName('codechat-bison-32k', EModelEndpoint.google)).toBe('codechat-bison-32k');
});
it('should return the input model name if no match is found - Google models', () => {
expect(matchModelName('unknown-google-model', EModelEndpoint.google)).toBe(
'unknown-google-model',
);
});
it('should return the closest matching key for partial matches - Google models', () => {
expect(matchModelName('code-', EModelEndpoint.google)).toBe('code-');
expect(matchModelName('chat-', EModelEndpoint.google)).toBe('chat-');
});
});
describe('Meta Models Tests', () => {