@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
301 lines (262 loc) • 10.4 kB
text/typescript
import { describe, expect, it } from 'vitest';
import {
MODEL_PARAMETER_CONFLICTS,
createParameterResolver,
resolveParameters,
} from './parameterResolver';
describe('resolveParameters', () => {
describe('Basic functionality', () => {
it('should return empty object when no parameters are provided', () => {
const result = resolveParameters({}, {});
expect(result).toEqual({});
});
it('should normalize temperature by dividing by 2 by default', () => {
const result = resolveParameters({ temperature: 1 }, {});
expect(result).toEqual({ temperature: 0.5 });
});
it('should not normalize temperature when normalizeTemperature is false', () => {
const result = resolveParameters({ temperature: 1 }, { normalizeTemperature: false });
expect(result).toEqual({ temperature: 1 });
});
it('should pass through top_p unchanged', () => {
const result = resolveParameters({ top_p: 0.9 }, {});
expect(result).toEqual({ top_p: 0.9 });
});
it('should return both parameters when no conflict', () => {
const result = resolveParameters({ temperature: 1, top_p: 0.9 }, { hasConflict: false });
expect(result).toEqual({ temperature: 0.5, top_p: 0.9 });
});
});
describe('Conflict handling', () => {
it('should prefer temperature over top_p when hasConflict is true', () => {
const result = resolveParameters(
{ temperature: 1, top_p: 0.9 },
{ hasConflict: true, preferTemperature: true },
);
expect(result).toEqual({ temperature: 0.5 });
});
it('should prefer top_p over temperature when preferTemperature is false', () => {
const result = resolveParameters(
{ temperature: 1, top_p: 0.9 },
{ hasConflict: true, preferTemperature: false },
);
expect(result).toEqual({ top_p: 0.9 });
});
it('should return temperature when only temperature is provided with conflict', () => {
const result = resolveParameters({ temperature: 1 }, { hasConflict: true });
expect(result).toEqual({ temperature: 0.5 });
});
it('should return top_p when only top_p is provided with conflict', () => {
const result = resolveParameters({ top_p: 0.9 }, { hasConflict: true });
expect(result).toEqual({ top_p: 0.9 });
});
});
describe('Range constraints', () => {
it('should apply temperature min constraint', () => {
const result = resolveParameters(
{ temperature: 0.02 }, // 0.02 / 2 = 0.01
{ temperatureRange: { min: 0.05 } },
);
expect(result).toEqual({ temperature: 0.05 });
});
it('should apply temperature max constraint', () => {
const result = resolveParameters(
{ temperature: 2 }, // 2 / 2 = 1
{ temperatureRange: { max: 0.99 } },
);
expect(result).toEqual({ temperature: 0.99 });
});
it('should apply top_p min constraint', () => {
const result = resolveParameters({ top_p: 0.005 }, { topPRange: { min: 0.01 } });
expect(result).toEqual({ top_p: 0.01 });
});
it('should apply top_p max constraint', () => {
const result = resolveParameters({ top_p: 1.5 }, { topPRange: { max: 0.99 } });
expect(result).toEqual({ top_p: 0.99 });
});
it('should apply both min and max constraints', () => {
const result = resolveParameters(
{ temperature: 0.02, top_p: 0.005 },
{
temperatureRange: { max: 0.99, min: 0.01 },
topPRange: { max: 0.99, min: 0.01 },
},
);
expect(result).toEqual({ temperature: 0.01, top_p: 0.01 });
});
});
describe('Additional parameters', () => {
it('should handle frequency_penalty', () => {
const result = resolveParameters({ frequency_penalty: 0.5 }, {});
expect(result).toEqual({ frequency_penalty: 0.5 });
});
it('should handle presence_penalty', () => {
const result = resolveParameters({ presence_penalty: 0.5 }, {});
expect(result).toEqual({ presence_penalty: 0.5 });
});
it('should handle max_tokens', () => {
const result = resolveParameters({ max_tokens: 1000 }, {});
expect(result).toEqual({ max_tokens: 1000 });
});
it('should apply frequency_penalty range constraints', () => {
const result = resolveParameters(
{ frequency_penalty: 3 },
{ frequencyPenaltyRange: { max: 2, min: -2 } },
);
expect(result).toEqual({ frequency_penalty: 2 });
});
it('should apply presence_penalty range constraints', () => {
const result = resolveParameters(
{ presence_penalty: -3 },
{ presencePenaltyRange: { max: 2, min: -2 } },
);
expect(result).toEqual({ presence_penalty: -2 });
});
it('should apply max_tokens range constraints', () => {
const result = resolveParameters({ max_tokens: 100_000 }, { maxTokensRange: { max: 8192 } });
expect(result).toEqual({ max_tokens: 8192 });
});
it('should handle all parameters together', () => {
const result = resolveParameters(
{
frequency_penalty: 0.5,
max_tokens: 2000,
presence_penalty: 0.5,
temperature: 1,
top_p: 0.9,
},
{},
);
expect(result).toEqual({
frequency_penalty: 0.5,
max_tokens: 2000,
presence_penalty: 0.5,
temperature: 0.5,
top_p: 0.9,
});
});
});
describe('Real-world scenarios', () => {
it('should handle Claude Opus 4.1 scenario (conflict with normalization)', () => {
const result = resolveParameters(
{ temperature: 1, top_p: 0.9 },
{ hasConflict: true, normalizeTemperature: true, preferTemperature: true },
);
expect(result).toEqual({ temperature: 0.5 });
});
it('should handle Zhipu glm-4-alltools scenario (range constraints)', () => {
const result = resolveParameters(
{ temperature: 1, top_p: 0.5 },
{
normalizeTemperature: true,
temperatureRange: { max: 0.99, min: 0.01 },
topPRange: { max: 0.99, min: 0.01 },
},
);
expect(result).toEqual({ temperature: 0.5, top_p: 0.5 });
});
it('should handle Groq scenario (temperature <= 0 becomes undefined)', () => {
// In Groq's case, they handle this in their own logic, but we can test the parameter resolver
const result = resolveParameters({ temperature: 0 }, { normalizeTemperature: false });
expect(result.temperature).toBe(0);
});
it('should handle Qwen range constraint scenario with multiple parameters', () => {
const result = resolveParameters(
{ presence_penalty: 1.5, temperature: 1.5, top_p: 0.8 },
{
normalizeTemperature: false,
presencePenaltyRange: { max: 2, min: -2 },
temperatureRange: { max: 2, min: 0 },
topPRange: { max: 1, min: 0 },
},
);
expect(result).toEqual({ presence_penalty: 1.5, temperature: 1.5, top_p: 0.8 });
});
});
describe('Edge cases', () => {
it('should handle temperature = 0', () => {
const result = resolveParameters({ temperature: 0 }, {});
expect(result).toEqual({ temperature: 0 });
});
it('should handle top_p = 0', () => {
const result = resolveParameters({ top_p: 0 }, {});
expect(result).toEqual({ top_p: 0 });
});
it('should handle temperature = undefined explicitly', () => {
const result = resolveParameters({ temperature: undefined, top_p: 0.9 }, {});
expect(result).toEqual({ top_p: 0.9 });
});
it('should handle both parameters undefined with conflict', () => {
const result = resolveParameters({}, { hasConflict: true });
expect(result).toEqual({});
});
});
});
describe('createParameterResolver', () => {
it('should create a resolver with predefined options', () => {
const resolver = createParameterResolver({
hasConflict: true,
normalizeTemperature: true,
preferTemperature: true,
});
const result = resolver({ temperature: 1, top_p: 0.9 });
expect(result).toEqual({ temperature: 0.5 });
});
it('should create a resolver with range constraints', () => {
const resolver = createParameterResolver({
normalizeTemperature: true,
temperatureRange: { max: 0.99, min: 0.01 },
topPRange: { max: 0.99, min: 0.01 },
});
const result = resolver({ temperature: 0.02, top_p: 0.005 });
expect(result).toEqual({ temperature: 0.01, top_p: 0.01 });
});
});
describe('MODEL_PARAMETER_CONFLICTS', () => {
describe('ANTHROPIC_CLAUDE_4_PLUS', () => {
it('should contain expected Claude 4+ models', () => {
expect(MODEL_PARAMETER_CONFLICTS.ANTHROPIC_CLAUDE_4_PLUS.has('claude-opus-4-1')).toBe(true);
expect(
MODEL_PARAMETER_CONFLICTS.ANTHROPIC_CLAUDE_4_PLUS.has('claude-opus-4-1-20250805'),
).toBe(true);
expect(
MODEL_PARAMETER_CONFLICTS.ANTHROPIC_CLAUDE_4_PLUS.has('claude-sonnet-4-5-20250929'),
).toBe(true);
});
it('should not contain Claude 3.x models', () => {
expect(MODEL_PARAMETER_CONFLICTS.ANTHROPIC_CLAUDE_4_PLUS.has('claude-3-opus-20240229')).toBe(
false,
);
expect(
MODEL_PARAMETER_CONFLICTS.ANTHROPIC_CLAUDE_4_PLUS.has('claude-3.5-sonnet-20240620'),
).toBe(false);
});
});
describe('BEDROCK_CLAUDE_4_PLUS', () => {
it('should contain both standard and Bedrock-specific model IDs', () => {
expect(MODEL_PARAMETER_CONFLICTS.BEDROCK_CLAUDE_4_PLUS.has('claude-opus-4-1')).toBe(true);
expect(
MODEL_PARAMETER_CONFLICTS.BEDROCK_CLAUDE_4_PLUS.has(
'anthropic.claude-opus-4-1-20250805-v1:0',
),
).toBe(true);
expect(
MODEL_PARAMETER_CONFLICTS.BEDROCK_CLAUDE_4_PLUS.has(
'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
),
).toBe(true);
});
it('should contain all Bedrock regional variants', () => {
expect(
MODEL_PARAMETER_CONFLICTS.BEDROCK_CLAUDE_4_PLUS.has(
'anthropic.claude-opus-4-20250514-v1:0',
),
).toBe(true);
expect(
MODEL_PARAMETER_CONFLICTS.BEDROCK_CLAUDE_4_PLUS.has(
'us.anthropic.claude-opus-4-20250514-v1:0',
),
).toBe(true);
});
});
});