feat: use managed model capabilities for reasoning and image input
This commit is contained in:
1 parent
e7701d1f2c
commit
26cbb29aa9
29 files changed
+652
-271
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { normalizeManagedModelCatalog } from '../../shared/managed-model-capabilities';
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
@@ -107,23 +108,10 @@ describe('Pi Provider catalog', () => {
|
||||
'X-Works-Square-AI-Token',
|
||||
]);
|
||||
expect(descriptor.models[0]).toMatchObject({
|
||||
id: 'qwen3.6-plus',
|
||||
input: ['text', 'image'],
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxOutputTokens: 65_536,
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: 'high',
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'qwen',
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: false,
|
||||
supportsStore: false,
|
||||
},
|
||||
id: 'qwen3.6-plus', input: ['text'], reasoning: false,
|
||||
contextWindow: 1_000_000, maxOutputTokens: 65_536,
|
||||
managedCapability: { resolutionStatus: 'unknown' },
|
||||
compat: { supportsDeveloperRole: false, supportsReasoningEffort: false, supportsStore: false },
|
||||
});
|
||||
expect(catalog.modelsFile.providers[descriptor.runtimeProviderId]?.models[0]?.compat)
|
||||
.toMatchObject({ supportsDeveloperRole: false });
|
||||
@@ -170,48 +158,6 @@ describe('Pi Provider catalog', () => {
|
||||
expect(descriptor.models[0]?.compat?.supportsDeveloperRole).toBe(false);
|
||||
});
|
||||
|
||||
it('projects Qwen3.8 Max reasoning effort levels into Pi models.json', () => {
|
||||
const provider = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
apiProtocol: 'openai-completions',
|
||||
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
|
||||
model: 'qwen3.8-max',
|
||||
metadata: {
|
||||
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
|
||||
customModels: ['qwen3.8-max'],
|
||||
},
|
||||
});
|
||||
|
||||
const catalog = buildPiProviderCatalog({ accounts: [provider] });
|
||||
const descriptor = catalog.descriptors[0]!.models[0]!;
|
||||
const written = catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0]!;
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
id: 'qwen3.8-max',
|
||||
input: ['text', 'image'],
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxOutputTokens: 131_072,
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'xhigh',
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'qwen',
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: true,
|
||||
supportsStore: false,
|
||||
},
|
||||
});
|
||||
expect(written).toMatchObject({
|
||||
reasoning: true,
|
||||
thinkingLevelMap: descriptor.thinkingLevelMap,
|
||||
compat: descriptor.compat,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses account-scoped model capability metadata and rejects unavailable models', () => {
|
||||
const provider = account({ id: 'account-with-vision', model: 'vision-model' });
|
||||
@@ -244,165 +190,40 @@ describe('Pi Provider catalog', () => {
|
||||
})).toThrowError(PiProviderConfigError);
|
||||
});
|
||||
|
||||
it('projects the managed DeepSeek capability contract into Pi models.json', () => {
|
||||
|
||||
|
||||
|
||||
|
||||
it.each(['qwen', 'deepseek'] as const)('uses v2 %s capabilities for unfamiliar models and native effort values', format => {
|
||||
const provider = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
apiProtocol: 'openai-completions',
|
||||
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
|
||||
model: 'deepseek/deepseek-v4-pro',
|
||||
id: 'niancode-user-models', vendorId: 'custom', apiProtocol: 'openai-completions',
|
||||
model: 'future-model', baseUrl: 'https://gateway.test/v1',
|
||||
metadata: {
|
||||
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
|
||||
customModels: ['deepseek/deepseek-v4-pro'],
|
||||
worksSquareModelCapabilitiesV2: normalizeManagedModelCatalog({
|
||||
schema_version: 2, models: { 'future-model': {
|
||||
input_modalities: ['text', 'image'], output_modalities: ['text'],
|
||||
reasoning: { supported: true, can_disable: true, default_enabled: true,
|
||||
control_format: format, effort_values: ['low', 'medium', 'xhigh', 'max', 'future'] },
|
||||
limits: { context_window: 123456, max_output_tokens: 1234 }, resolution_status: 'ready',
|
||||
} },
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const catalog = buildPiProviderCatalog({ accounts: [provider] });
|
||||
const descriptor = catalog.descriptors[0]!.models[0]!;
|
||||
const written = catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0]!;
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
id: 'deepseek-v4-pro',
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxOutputTokens: 384_000,
|
||||
compat: {
|
||||
thinkingFormat: 'deepseek',
|
||||
requiresReasoningContentOnAssistantMessages: true,
|
||||
},
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: 'high',
|
||||
max: 'max',
|
||||
},
|
||||
});
|
||||
expect(written).toMatchObject({
|
||||
reasoning: true,
|
||||
thinkingLevelMap: descriptor.thinkingLevelMap,
|
||||
compat: descriptor.compat,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses server reasoning capabilities while retaining local model metadata', () => {
|
||||
const provider = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
apiProtocol: 'openai-completions',
|
||||
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
|
||||
model: 'qwen3.8-max',
|
||||
metadata: {
|
||||
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
|
||||
customModels: ['qwen3.8-max'],
|
||||
worksSquareModelCapabilities: {
|
||||
'qwen3.8-max': {
|
||||
reasoningEfforts: ['low'],
|
||||
reasoningCanDisable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
id: 'qwen3.8-max',
|
||||
input: ['text', 'image'],
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxOutputTokens: 131_072,
|
||||
thinkingLevelMap: {
|
||||
off: null,
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: null,
|
||||
max: null,
|
||||
},
|
||||
compat: {
|
||||
thinkingFormat: 'qwen',
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: true,
|
||||
supportsStore: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('enables reasoning-effort serialization for a server model without a local profile', () => {
|
||||
const provider = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
apiProtocol: 'openai-completions',
|
||||
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
|
||||
model: 'future-reasoning-model',
|
||||
metadata: {
|
||||
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
|
||||
customModels: ['future-reasoning-model'],
|
||||
worksSquareModelCapabilities: {
|
||||
'future-reasoning-model': {
|
||||
reasoningEfforts: ['low', 'high', 'max'],
|
||||
reasoningCanDisable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
|
||||
|
||||
expect(descriptor).toMatchObject({
|
||||
id: 'future-reasoning-model',
|
||||
reasoning: true,
|
||||
compat: {
|
||||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: true,
|
||||
},
|
||||
thinkingLevelMap: {
|
||||
minimal: null,
|
||||
low: 'low',
|
||||
medium: null,
|
||||
high: 'high',
|
||||
max: 'max',
|
||||
},
|
||||
});
|
||||
expect(descriptor.thinkingLevelMap).not.toHaveProperty('off');
|
||||
});
|
||||
|
||||
it('lets an explicit empty server effort list override a locally reasoning model', () => {
|
||||
const provider = account({
|
||||
id: 'niancode-user-models',
|
||||
vendorId: 'custom',
|
||||
apiProtocol: 'openai-completions',
|
||||
baseUrl: 'http://127.0.0.1:54321/api/ai-proxy/v1',
|
||||
model: 'deepseek-v4-pro',
|
||||
metadata: {
|
||||
worksSquareCredentialMode: 'works_square_ai_gateway_proxy',
|
||||
customModels: ['deepseek-v4-pro'],
|
||||
worksSquareModelCapabilities: {
|
||||
'deepseek-v4-pro': {
|
||||
reasoningEfforts: [],
|
||||
reasoningCanDisable: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const descriptor = buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0]!;
|
||||
|
||||
expect(descriptor.reasoning).toBe(false);
|
||||
expect(descriptor.thinkingLevelMap).toEqual({
|
||||
off: null,
|
||||
minimal: null,
|
||||
low: null,
|
||||
medium: null,
|
||||
high: null,
|
||||
max: null,
|
||||
});
|
||||
expect(descriptor.compat).toMatchObject({
|
||||
thinkingFormat: 'deepseek',
|
||||
supportsReasoningEffort: true,
|
||||
requiresReasoningContentOnAssistantMessages: true,
|
||||
input: ['text', 'image'], reasoning: true, contextWindow: 123456, maxOutputTokens: 1234,
|
||||
compat: { thinkingFormat: format, supportsReasoningEffort: false, requiresReasoningContentOnAssistantMessages: true },
|
||||
managedCapability: { reasoning: { effortValues: ['low', 'medium', 'xhigh', 'max', 'future'] } },
|
||||
});
|
||||
expect(descriptor.thinkingLevelMap).toBeUndefined();
|
||||
expect(catalog.modelsFile.providers[resolvePiRuntimeProviderId(provider.id)]!.models[0])
|
||||
.not.toHaveProperty('managedCapability');
|
||||
const profile = provider.metadata!.worksSquareModelCapabilitiesV2!.models['future-model']!;
|
||||
profile.inputModalities = ['text'];
|
||||
profile.reasoning.supported = false;
|
||||
expect(buildPiProviderCatalog({ accounts: [provider] }).descriptors[0]!.models[0])
|
||||
.toMatchObject({ input: ['text'], reasoning: false });
|
||||
});
|
||||
|
||||
it('serializes DeepSeek off without sending a reasoning effort', async () => {
|
||||
|
||||
Reference in new issue
Block a user