Files
NetMesh/infrastructure/ai/cattyReasoning.test.ts

340 lines
14 KiB
TypeScript
Raw Permalink Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import {
applyResponsesApiStatelessStoreOption,
buildCattyReasoningProviderOptions,
cattyReasoningLevelsForSelection,
estimateReasoningOutputReserve,
openaiModelLikelySupportsReasoning,
openaiModelSupportsNoneReasoning,
resolveVisibleCattyThinkingLevel,
} from './cattyReasoning';
test('buildCattyReasoningProviderOptions is omitted when effort is off', () => {
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'off'),
undefined,
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, undefined),
undefined,
);
});
test('buildCattyReasoningProviderOptions maps OpenAI-compatible effort', () => {
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'deepseek' }, 'high'),
{ openai: { reasoningEffort: 'high' } },
);
});
test('buildCattyReasoningProviderOptions omits reasoningEffort for non-reasoning OpenAI models', () => {
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'high', 'gpt-4o'),
undefined,
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'high', 'gpt-5.5'),
{ openai: { reasoningEffort: 'high' } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'off', 'o3-mini'),
{ openai: { reasoningEffort: 'low' } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'off', 'gpt-5'),
{ openai: { reasoningEffort: 'minimal' } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'off', 'gpt-5.5'),
{ openai: { reasoningEffort: 'none' } },
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, undefined, 'gpt-5.5'),
undefined,
);
assert.equal(openaiModelSupportsNoneReasoning('o4-mini'), false);
assert.equal(openaiModelSupportsNoneReasoning('gpt-5.1-codex'), true);
assert.equal(openaiModelSupportsNoneReasoning('gpt-5.1-chat-latest'), false);
});
test('cattyReasoningLevelsForSelection hides the chip unless the model can take effort', () => {
assert.equal(openaiModelLikelySupportsReasoning('gpt-4o'), false);
assert.equal(openaiModelLikelySupportsReasoning('gpt-5-chat-latest'), false);
assert.equal(openaiModelLikelySupportsReasoning('gpt-5.1-chat-latest'), false);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'high', 'gpt-5-chat-latest'),
undefined,
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'off', 'gpt-5.1-chat-latest'),
undefined,
);
assert.equal(openaiModelLikelySupportsReasoning('gpt-5.5'), true);
assert.deepEqual(cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-4o'), []);
assert.ok(cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-5.5').includes('high'));
assert.ok(cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-5.5').includes('off'));
assert.equal(
cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-5.5'),
cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-5.6'),
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'gpt-5'),
['minimal', 'low', 'medium', 'high'],
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'openai' }, 'o3-mini'),
['low', 'medium', 'high'],
);
assert.deepEqual(cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-1.5-flash'), []);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3-flash'),
['minimal', 'low', 'medium', 'high'],
);
assert.ok(cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3-flash').includes('minimal'));
assert.ok(!cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3-flash').includes('off'));
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3.7-flash'),
['low', 'medium', 'high'],
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3.1-flash-lite-image'),
['minimal', 'high'],
);
assert.ok(cattyReasoningLevelsForSelection({ providerId: 'anthropic' }, 'claude-opus-4-6').includes('high'));
assert.ok(cattyReasoningLevelsForSelection({ providerId: 'anthropic' }, 'claude-sonnet-5').includes('high'));
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'anthropic' }, 'claude-fable-5'),
['low', 'medium', 'high'],
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'anthropic' }, 'claude-3-haiku-20240307'),
[],
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'high', 'claude-3-haiku-20240307'),
undefined,
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3-pro'),
['low', 'high'],
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-3.1-pro-preview'),
['low', 'medium', 'high'],
);
assert.deepEqual(
cattyReasoningLevelsForSelection({ providerId: 'google' }, 'gemini-2.5-pro'),
['low', 'medium', 'high'],
);
});
test('resolveVisibleCattyThinkingLevel drops stale levels after a model switch', () => {
assert.equal(
resolveVisibleCattyThinkingLevel(['low', 'medium', 'high'], 'minimal'),
'low',
);
assert.equal(
resolveVisibleCattyThinkingLevel(['minimal', 'low', 'medium', 'high'], 'off'),
'minimal',
);
assert.equal(
resolveVisibleCattyThinkingLevel(['off', 'low', 'medium', 'high'], 'high'),
'high',
);
assert.equal(
resolveVisibleCattyThinkingLevel(['low', 'high'], 'medium'),
'high',
);
assert.equal(
resolveVisibleCattyThinkingLevel(['low', 'medium', 'high'], 'off'),
'low',
);
});
test('estimateReasoningOutputReserve folds thinking budgets into the output reserve', () => {
assert.equal(estimateReasoningOutputReserve(undefined), 0);
assert.equal(
estimateReasoningOutputReserve(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'high', 'claude-sonnet-4-5'),
),
20_000,
);
assert.equal(
estimateReasoningOutputReserve(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'medium', 'claude-3-7-sonnet-20250219'),
),
10_000,
);
assert.equal(
estimateReasoningOutputReserve(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'high', 'claude-opus-4-6'),
),
0,
);
assert.equal(
estimateReasoningOutputReserve(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'high', 'gemini-2.5-pro'),
),
16_384,
);
assert.equal(
estimateReasoningOutputReserve(
buildCattyReasoningProviderOptions({ providerId: 'openai' }, 'high', 'gpt-5.5'),
),
0,
);
});
test('buildCattyReasoningProviderOptions maps Anthropic thinking budgets', () => {
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'medium'),
{ anthropic: { thinking: { type: 'enabled', budgetTokens: 10_000 } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'medium', 'claude-sonnet-4-5'),
{ anthropic: { thinking: { type: 'enabled', budgetTokens: 10_000 } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'high', 'claude-opus-4-20250514'),
{ anthropic: { thinking: { type: 'enabled', budgetTokens: 20_000 } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'high', 'claude-opus-4-6'),
{ anthropic: { thinking: { type: 'adaptive' }, effort: 'high' } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'low', 'claude-sonnet-5'),
{ anthropic: { thinking: { type: 'adaptive' }, effort: 'low' } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'off', 'claude-sonnet-5'),
{ anthropic: { thinking: { type: 'disabled' } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'off', 'claude-fable-5'),
{ anthropic: { thinking: { type: 'adaptive' }, effort: 'low' } },
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'anthropic' }, 'off', 'claude-sonnet-4-5'),
undefined,
);
});
test('buildCattyReasoningProviderOptions respects an explicit style override', () => {
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'custom', style: 'openai' }, 'low'),
{ openai: { reasoningEffort: 'low' } },
);
});
test('buildCattyReasoningProviderOptions maps Gemini thinking levels', () => {
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'high', 'gemini-3-pro'),
{ google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'off', 'gemini-3-pro'),
{ google: { thinkingConfig: { thinkingLevel: 'low', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'medium', 'gemini-3-pro'),
{ google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'medium', 'gemini-3.1-pro-preview'),
{ google: { thinkingConfig: { thinkingLevel: 'medium', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'off', 'gemini-3-flash'),
{ google: { thinkingConfig: { thinkingLevel: 'minimal', includeThoughts: false } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'minimal', 'gemini-3.7-flash'),
{ google: { thinkingConfig: { thinkingLevel: 'low', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'off', 'gemini-3.7-flash'),
{ google: { thinkingConfig: { thinkingLevel: 'low', includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'low', 'gemini-3.1-flash-lite-image'),
{ google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true } } },
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'google' }, undefined, 'gemini-3-flash'),
undefined,
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'high', 'gemini-2.5-pro'),
{ google: { thinkingConfig: { thinkingBudget: 16_384, includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'off', 'gemini-2.5-pro'),
{ google: { thinkingConfig: { thinkingBudget: 1_024, includeThoughts: true } } },
);
assert.deepEqual(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'off', 'gemini-2.5-flash'),
{ google: { thinkingConfig: { thinkingBudget: 0, includeThoughts: false } } },
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'google' }, undefined, 'gemini-2.5-flash'),
undefined,
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'high'),
undefined,
);
assert.equal(
buildCattyReasoningProviderOptions({ providerId: 'google' }, 'high', 'gemini-1.5-flash'),
undefined,
);
});
test('applyResponsesApiStatelessStoreOption sets store:false for OpenAI Responses providers', () => {
const responsesProvider = { providerId: 'openai' as const, openaiApi: 'responses' as const };
assert.deepEqual(
applyResponsesApiStatelessStoreOption(responsesProvider, { openai: { reasoningEffort: 'high' } }),
{ openai: { reasoningEffort: 'high', store: false, include: ['reasoning.encrypted_content'] } },
);
// Even with no reasoning options, Responses turns stay stateless
// and request replayable (encrypted) reasoning.
assert.deepEqual(
applyResponsesApiStatelessStoreOption(responsesProvider, undefined),
{ openai: { store: false, include: ['reasoning.encrypted_content'] } },
);
});
test('applyResponsesApiStatelessStoreOption is a no-op for Chat Completions and other styles', () => {
assert.deepEqual(
applyResponsesApiStatelessStoreOption({ providerId: 'openai' as const }, { openai: { reasoningEffort: 'high' } }),
{ openai: { reasoningEffort: 'high' } },
);
const chatProvider = { providerId: 'openai' as const, openaiApi: 'chat' as const };
assert.deepEqual(
applyResponsesApiStatelessStoreOption(chatProvider, { openai: { reasoningEffort: 'low' } }),
{ openai: { reasoningEffort: 'low' } },
);
const anthropicProvider = { providerId: 'openai' as const, openaiApi: 'responses' as const, style: 'anthropic' as const };
assert.deepEqual(
applyResponsesApiStatelessStoreOption(anthropicProvider, { anthropic: { thinking: { type: 'adaptive' } } }),
{ anthropic: { thinking: { type: 'adaptive' } } },
);
assert.equal(applyResponsesApiStatelessStoreOption(undefined, undefined), undefined);
});
test('applyResponsesApiStatelessStoreOption always requests encrypted reasoning on stateless Responses turns', () => {
const responsesProvider = { providerId: 'custom' as const, openaiApi: 'responses' as const };
// Every stateless Responses turn gets the include, covering known reasoner
// IDs (`deepseek-r1`, `gpt-oss-120b`, `grok-4`, `o3`, `gpt-5.1`) as before,
// always-thinking relay models whose IDs match no classifier (e.g.
// DeepSeek's default `deepseek-v4-flash`), and plain chat models — for the
// latter the include is a no-op (no reasoning items to encrypt).
assert.deepEqual(
applyResponsesApiStatelessStoreOption(responsesProvider, undefined),
{ openai: { store: false, include: ['reasoning.encrypted_content'] } },
'deepseek-v4-flash',
);
});