mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-09 17:31:19 +00:00
💭 fix: Preserve Custom Endpoint Reasoning Params (#13447)
* fix: Preserve custom endpoint reasoning params * fix: Address custom reasoning review cases * fix: Format configured reasoning defaults * fix: Honor dropped reasoning params * fix: Configure custom reasoning response key
This commit is contained in:
parent
730878bc5a
commit
2ab432bd0a
14 changed files with 635 additions and 66 deletions
|
|
@ -177,7 +177,8 @@ https://www.librechat.ai/docs/configuration/stt_tts`);
|
|||
|
||||
// Validate and fill out missing values for custom parameters
|
||||
function parseCustomParams(endpointName, customParams) {
|
||||
const paramEndpoint = customParams.defaultParamsEndpoint;
|
||||
const paramEndpoint = customParams.defaultParamsEndpoint ?? 'custom';
|
||||
customParams.defaultParamsEndpoint = paramEndpoint;
|
||||
customParams.paramDefinitions = customParams.paramDefinitions || [];
|
||||
|
||||
// Checks if `defaultParamsEndpoint` is a key in `paramSettings`.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ jest.mock('librechat-data-provider', () => {
|
|||
paramSettings: {
|
||||
foo: {},
|
||||
bar: {},
|
||||
custom: {},
|
||||
custom: [],
|
||||
openrouter: [
|
||||
{
|
||||
key: 'promptCache',
|
||||
|
|
@ -59,6 +59,7 @@ jest.mock('@librechat/data-schemas', () => {
|
|||
const axios = require('axios');
|
||||
const { loadYaml } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { ReasoningParameterFormat, ReasoningResponseKey } = require('librechat-data-provider');
|
||||
const loadCustomConfig = require('./loadCustomConfig');
|
||||
|
||||
describe('loadCustomConfig', () => {
|
||||
|
|
@ -307,11 +308,28 @@ describe('loadCustomConfig', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('throws an error when defaultParamsEndpoint is not provided', async () => {
|
||||
const malformedCustomParams = { defaultParamsEndpoint: undefined };
|
||||
await expect(loadCustomParams(malformedCustomParams)).rejects.toThrow(
|
||||
'defaultParamsEndpoint of "Google" endpoint is invalid. Valid options are foo, bar, custom, openrouter, google',
|
||||
);
|
||||
it('defaults defaultParamsEndpoint when only reasoningFormat is provided', async () => {
|
||||
const parsedConfig = await loadCustomParams({
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
});
|
||||
|
||||
expect(parsedConfig.endpoints.custom[0].customParams).toEqual({
|
||||
defaultParamsEndpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
paramDefinitions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults defaultParamsEndpoint when only reasoningKey is provided', async () => {
|
||||
const parsedConfig = await loadCustomParams({
|
||||
reasoningKey: ReasoningResponseKey.reasoning,
|
||||
});
|
||||
|
||||
expect(parsedConfig.endpoints.custom[0].customParams).toEqual({
|
||||
defaultParamsEndpoint: 'custom',
|
||||
reasoningKey: ReasoningResponseKey.reasoning,
|
||||
paramDefinitions: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('fills the paramDefinitions with missing values', async () => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type {
|
|||
AgentToolResources,
|
||||
AgentToolOptions,
|
||||
TEndpointOption,
|
||||
ReasoningResponseKey,
|
||||
TFile,
|
||||
Agent,
|
||||
TUser,
|
||||
|
|
@ -169,6 +170,8 @@ export type InitializedAgent = Agent & {
|
|||
actionsEnabled?: boolean;
|
||||
/** Maximum characters allowed in a single tool result before truncation. */
|
||||
maxToolResultChars?: number;
|
||||
/** Response field to read model reasoning from for custom OpenAI-compatible endpoints. */
|
||||
reasoningKey?: ReasoningResponseKey;
|
||||
/**
|
||||
* Whether the code-execution environment is available *for this agent*.
|
||||
* Narrower than the incoming `params.codeEnvAvailable` admin flag — this
|
||||
|
|
@ -1035,6 +1038,7 @@ export async function initializeAgent(
|
|||
actionsEnabled,
|
||||
baseContextTokens,
|
||||
codeEnvAvailable: effectiveCodeEnvAvailable,
|
||||
reasoningKey: customEndpointConfig?.customParams?.reasoningKey,
|
||||
skillCount,
|
||||
accessibleSkillIds: executableSkillIds,
|
||||
activeSkillNames,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Providers } from '@librechat/agents';
|
||||
import { ToolMessage, AIMessage, HumanMessage } from '@librechat/agents/langchain/messages';
|
||||
import { ReasoningResponseKey } from 'librechat-data-provider';
|
||||
|
||||
import {
|
||||
extractDiscoveredToolsFromHistory,
|
||||
|
|
@ -150,6 +151,56 @@ describe('getReasoningKey', () => {
|
|||
|
||||
expect(reasoningKey).toBe('reasoning');
|
||||
});
|
||||
|
||||
it('keeps Vercel AI Gateway on ChatOpenAI normalized reasoning_content', () => {
|
||||
const llmConfig = {
|
||||
configuration: {
|
||||
baseURL: 'https://ai-gateway.vercel.sh/v1',
|
||||
},
|
||||
} as Parameters<typeof getReasoningKey>[1];
|
||||
|
||||
const reasoningKey = getReasoningKey(Providers.OPENAI, llmConfig);
|
||||
|
||||
expect(reasoningKey).toBe('reasoning_content');
|
||||
});
|
||||
|
||||
it('keeps Vercel custom endpoint names on ChatOpenAI normalized reasoning_content', () => {
|
||||
const llmConfig = {} as Parameters<typeof getReasoningKey>[1];
|
||||
|
||||
const reasoningKey = getReasoningKey(Providers.OPENAI, llmConfig, 'Vercel');
|
||||
|
||||
expect(reasoningKey).toBe('reasoning_content');
|
||||
});
|
||||
|
||||
it('uses explicit reasoning response keys for Vercel when configured', () => {
|
||||
const llmConfig = {
|
||||
configuration: {
|
||||
baseURL: 'https://ai-gateway.vercel.sh/v1',
|
||||
},
|
||||
} as Parameters<typeof getReasoningKey>[1];
|
||||
|
||||
const reasoningKey = getReasoningKey(
|
||||
Providers.OPENAI,
|
||||
llmConfig,
|
||||
'Vercel',
|
||||
ReasoningResponseKey.reasoning,
|
||||
);
|
||||
|
||||
expect(reasoningKey).toBe('reasoning');
|
||||
});
|
||||
|
||||
it('uses explicit reasoning response keys for otherwise default OpenAI-compatible endpoints', () => {
|
||||
const llmConfig = {} as Parameters<typeof getReasoningKey>[1];
|
||||
|
||||
const reasoningKey = getReasoningKey(
|
||||
Providers.OPENAI,
|
||||
llmConfig,
|
||||
'Company Gateway',
|
||||
ReasoningResponseKey.reasoning,
|
||||
);
|
||||
|
||||
expect(reasoningKey).toBe('reasoning');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDeepSeekReasoningProvider', () => {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import type {
|
|||
Agent,
|
||||
AgentModelParameters,
|
||||
AgentSubagentsConfig,
|
||||
ReasoningResponseKey,
|
||||
SummarizationConfig,
|
||||
} from 'librechat-data-provider';
|
||||
import type { BaseMessage } from '@librechat/agents/langchain/messages';
|
||||
|
|
@ -212,6 +213,8 @@ const customProviders = new Set([
|
|||
KnownEndpoints.ollama,
|
||||
]);
|
||||
|
||||
type AgentReasoningKey = 'reasoning_content' | 'reasoning';
|
||||
|
||||
function includesOpenRouter(value?: string | null): boolean {
|
||||
return typeof value === 'string' && value.toLowerCase().includes(KnownEndpoints.openrouter);
|
||||
}
|
||||
|
|
@ -220,8 +223,13 @@ export function getReasoningKey(
|
|||
provider: Providers,
|
||||
llmConfig: t.RunLLMConfig,
|
||||
agentEndpoint?: string | null,
|
||||
): 'reasoning_content' | 'reasoning' {
|
||||
let reasoningKey: 'reasoning_content' | 'reasoning' = 'reasoning_content';
|
||||
customReasoningKey?: ReasoningResponseKey,
|
||||
): AgentReasoningKey {
|
||||
if (customReasoningKey) {
|
||||
return customReasoningKey as AgentReasoningKey;
|
||||
}
|
||||
|
||||
let reasoningKey: AgentReasoningKey = 'reasoning_content';
|
||||
if (provider === Providers.GOOGLE) {
|
||||
reasoningKey = 'reasoning';
|
||||
} else if (
|
||||
|
|
@ -293,6 +301,8 @@ type RunAgent = Omit<Agent, 'tools'> & {
|
|||
codeEnvAvailable?: boolean;
|
||||
/** Optional per-agent summarization overrides */
|
||||
summarization?: SummarizationConfig;
|
||||
/** Response field to read model reasoning from for custom OpenAI-compatible endpoints. */
|
||||
reasoningKey?: ReasoningResponseKey;
|
||||
/**
|
||||
* Maximum characters allowed in a single tool result before truncation.
|
||||
* Overrides the default computed from maxContextTokens.
|
||||
|
|
@ -946,7 +956,7 @@ export async function createRun({
|
|||
agent.maxContextTokens,
|
||||
);
|
||||
|
||||
const reasoningKey = getReasoningKey(provider, llmConfig, agent.endpoint);
|
||||
const reasoningKey = getReasoningKey(provider, llmConfig, agent.endpoint, agent.reasoningKey);
|
||||
return {
|
||||
provider,
|
||||
reasoningKey,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ describe('getOpenAIConfig - Backward Compatibility', () => {
|
|||
describe('OpenAI endpoint', () => {
|
||||
it('should handle GPT-5 model with reasoning and web search', () => {
|
||||
const apiKey = 'sk-proj-somekey';
|
||||
const endpoint = undefined;
|
||||
const endpoint = EModelEndpoint.openAI;
|
||||
const options = {
|
||||
modelOptions: {
|
||||
model: 'gpt-5-nano',
|
||||
|
|
@ -138,7 +138,7 @@ describe('getOpenAIConfig - Backward Compatibility', () => {
|
|||
|
||||
it('should handle Azure OpenAI with Responses API and reasoning', () => {
|
||||
const apiKey = 'some_azure_key';
|
||||
const endpoint = undefined;
|
||||
const endpoint = EModelEndpoint.azureOpenAI;
|
||||
const options = {
|
||||
modelOptions: {
|
||||
model: 'gpt-5',
|
||||
|
|
@ -395,6 +395,7 @@ describe('getOpenAIConfig - Backward Compatibility', () => {
|
|||
modelOptions: {
|
||||
model: '@cf/deepseek-ai/deepseek-r1-distill-qwen-32b',
|
||||
user: 'some-user',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
reverseProxyUrl:
|
||||
'https://gateway.ai.cloudflare.com/v1/${CF_ACCOUNT_ID}/${CF_GATEWAY_ID}/workers-ai/v1',
|
||||
|
|
@ -419,6 +420,9 @@ describe('getOpenAIConfig - Backward Compatibility', () => {
|
|||
user: 'some-user',
|
||||
disableStreaming: true,
|
||||
apiKey: 'someKey',
|
||||
modelKwargs: {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
},
|
||||
configOptions: {
|
||||
baseURL:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
EModelEndpoint,
|
||||
ReasoningEffort,
|
||||
ReasoningSummary,
|
||||
ReasoningParameterFormat,
|
||||
} from 'librechat-data-provider';
|
||||
import type { RequestInit } from 'undici';
|
||||
import type { OpenAIParameters, AzureOptions } from '~/types';
|
||||
|
|
@ -79,7 +80,7 @@ describe('getOpenAIConfig', () => {
|
|||
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle reasoning params for `useResponsesApi`', () => {
|
||||
it('should pass custom endpoint reasoning object through modelKwargs for `useResponsesApi`', () => {
|
||||
const modelOptions = {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.detailed,
|
||||
|
|
@ -89,27 +90,29 @@ describe('getOpenAIConfig', () => {
|
|||
modelOptions: { ...modelOptions, useResponsesApi: true },
|
||||
});
|
||||
|
||||
expect(result.llmConfig.reasoning).toEqual({
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.detailed,
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning: {
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.detailed,
|
||||
},
|
||||
});
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_summary).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle reasoning params without `useResponsesApi`', () => {
|
||||
it('should pass custom endpoint reasoning through modelKwargs without `useResponsesApi`', () => {
|
||||
const modelOptions = {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.detailed,
|
||||
};
|
||||
|
||||
const result = getOpenAIConfig(mockApiKey, { modelOptions });
|
||||
const result = getOpenAIConfig(mockApiKey, { modelOptions }, 'custom-endpoint');
|
||||
|
||||
/** When no endpoint is specified, it's treated as non-openAI/azureOpenAI, so uses reasoning object */
|
||||
expect(result.llmConfig.reasoning).toEqual({
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.detailed,
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
});
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -173,7 +176,7 @@ describe('getOpenAIConfig', () => {
|
|||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use reasoning object for non-openAI/azureOpenAI endpoints', () => {
|
||||
it('should pass reasoning_effort through modelKwargs for non-openAI/azureOpenAI endpoints', () => {
|
||||
const modelOptions = {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.detailed,
|
||||
|
|
@ -181,13 +184,102 @@ describe('getOpenAIConfig', () => {
|
|||
|
||||
const result = getOpenAIConfig(mockApiKey, { modelOptions }, 'custom-endpoint');
|
||||
|
||||
expect(result.llmConfig.reasoning).toEqual({
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.detailed,
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
});
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support custom endpoint reasoning object format', () => {
|
||||
const result = getOpenAIConfig(
|
||||
mockApiKey,
|
||||
{
|
||||
customParams: {
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
},
|
||||
modelOptions: {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.detailed,
|
||||
},
|
||||
},
|
||||
'custom-endpoint',
|
||||
);
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning: {
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.detailed,
|
||||
},
|
||||
});
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should default Vercel custom endpoints to reasoning object format', () => {
|
||||
const result = getOpenAIConfig(
|
||||
mockApiKey,
|
||||
{
|
||||
reverseProxyUrl: 'https://ai-gateway.vercel.sh/v1',
|
||||
modelOptions: {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
},
|
||||
'Vercel',
|
||||
);
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning: {
|
||||
effort: ReasoningEffort.high,
|
||||
},
|
||||
});
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should apply Vercel reasoning format to custom default params', () => {
|
||||
const result = getOpenAIConfig(
|
||||
mockApiKey,
|
||||
{
|
||||
reverseProxyUrl: 'https://ai-gateway.vercel.sh/v1',
|
||||
customParams: {
|
||||
paramDefinitions: [{ key: 'reasoning_effort', default: ReasoningEffort.low }],
|
||||
},
|
||||
modelOptions: {
|
||||
model: 'openai/gpt-5-mini',
|
||||
},
|
||||
},
|
||||
'Vercel',
|
||||
);
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning: {
|
||||
effort: ReasoningEffort.low,
|
||||
},
|
||||
});
|
||||
expect((result.llmConfig as Record<string, unknown>).reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should allow Vercel reasoning format override', () => {
|
||||
const result = getOpenAIConfig(
|
||||
mockApiKey,
|
||||
{
|
||||
reverseProxyUrl: 'https://ai-gateway.vercel.sh/v1',
|
||||
customParams: {
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningEffort,
|
||||
},
|
||||
modelOptions: {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
},
|
||||
'Vercel',
|
||||
);
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle OpenRouter configuration', () => {
|
||||
const reverseProxyUrl = 'https://openrouter.ai/api/v1';
|
||||
|
||||
|
|
@ -1006,11 +1098,12 @@ describe('getOpenAIConfig', () => {
|
|||
const result = getOpenAIConfig(mockApiKey, {
|
||||
modelOptions: { ...modelOptions, useResponsesApi: true } as Partial<OpenAIParameters>,
|
||||
});
|
||||
const reasoning = result.llmConfig?.reasoning ?? result.llmConfig?.modelKwargs?.reasoning;
|
||||
|
||||
if (shouldHaveReasoning) {
|
||||
expect(result.llmConfig?.reasoning).toBeDefined();
|
||||
expect(reasoning).toBeDefined();
|
||||
} else {
|
||||
expect(result.llmConfig?.reasoning).toBeUndefined();
|
||||
expect(reasoning).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1088,6 +1181,7 @@ describe('getOpenAIConfig', () => {
|
|||
frequency_penalty: 0.5,
|
||||
presence_penalty: 0.6,
|
||||
max_tokens: 1000,
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
custom_param: 'should-remain',
|
||||
};
|
||||
|
||||
|
|
@ -1102,6 +1196,7 @@ describe('getOpenAIConfig', () => {
|
|||
/** `presence_penalty` is converted to `presencePenalty` */
|
||||
expect(result.llmConfig.maxTokens).toBe(1000); // max_tokens is allowed
|
||||
expect((result.llmConfig as Record<string, unknown>).custom_param).toBe('should-remain');
|
||||
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1209,9 +1304,11 @@ describe('getOpenAIConfig', () => {
|
|||
streaming: false,
|
||||
useResponsesApi: true, // From web_search
|
||||
});
|
||||
expect(result.llmConfig.reasoning).toBeUndefined();
|
||||
expect(result.llmConfig.maxTokens).toBe(2000);
|
||||
expect(result.llmConfig.modelKwargs).toEqual({
|
||||
text: { verbosity: Verbosity.medium },
|
||||
reasoning: { effort: ReasoningEffort.high },
|
||||
customParam: 'custom-value',
|
||||
});
|
||||
expect(result.tools).toEqual([{ type: 'web_search' }]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ProxyAgent } from 'undici';
|
||||
import { Providers } from '@librechat/agents';
|
||||
import { KnownEndpoints, EModelEndpoint } from 'librechat-data-provider';
|
||||
import { KnownEndpoints, EModelEndpoint, ReasoningParameterFormat } from 'librechat-data-provider';
|
||||
import type * as t from '~/types';
|
||||
import { getLLMConfig as getAnthropicLLMConfig } from '~/endpoints/anthropic/llm';
|
||||
import { getOpenAILLMConfig, extractDefaultParams } from './llm';
|
||||
|
|
@ -34,6 +34,22 @@ function getDefaultParams({
|
|||
};
|
||||
}
|
||||
|
||||
function getReasoningFormat({
|
||||
customFormat,
|
||||
isVercel,
|
||||
}: {
|
||||
customFormat?: ReasoningParameterFormat;
|
||||
isVercel: boolean;
|
||||
}): ReasoningParameterFormat | undefined {
|
||||
if (customFormat) {
|
||||
return customFormat;
|
||||
}
|
||||
if (isVercel) {
|
||||
return ReasoningParameterFormat.reasoningObject;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function mergeHeadersPreservingAnthropicBeta(
|
||||
headers: Record<string, string> | undefined,
|
||||
defaultHeaders: Record<string, string>,
|
||||
|
|
@ -159,6 +175,10 @@ export function getOpenAIConfig(
|
|||
defaultParams,
|
||||
modelOptions,
|
||||
useOpenRouter,
|
||||
reasoningFormat: getReasoningFormat({
|
||||
customFormat: options.customParams?.reasoningFormat,
|
||||
isVercel: Boolean(isVercel),
|
||||
}),
|
||||
});
|
||||
llmConfig = openaiResult.llmConfig;
|
||||
azure = openaiResult.azure;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
EModelEndpoint,
|
||||
ReasoningEffort,
|
||||
ReasoningSummary,
|
||||
ReasoningParameterFormat,
|
||||
} from 'librechat-data-provider';
|
||||
import { getOpenAILLMConfig, extractDefaultParams, applyDefaultParams } from './llm';
|
||||
import type * as t from '~/types';
|
||||
|
|
@ -463,23 +464,159 @@ describe('getOpenAILLMConfig', () => {
|
|||
expect(result.llmConfig).toHaveProperty('reasoning_effort', ReasoningEffort.high);
|
||||
});
|
||||
|
||||
it('should use reasoning object for non-OpenAI endpoints', () => {
|
||||
it('should pass reasoning_effort through modelKwargs for custom endpoints', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
modelOptions: {
|
||||
model: 'o1',
|
||||
model: 'provider/reasoning-model',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toHaveProperty('reasoning_effort', ReasoningEffort.high);
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||
});
|
||||
|
||||
it('should support reasoning object passthrough for custom endpoints', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.concise,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).toHaveProperty('reasoning');
|
||||
expect(result.llmConfig.reasoning).toEqual({
|
||||
expect(result.llmConfig.modelKwargs).toHaveProperty('reasoning', {
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.concise,
|
||||
});
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||
});
|
||||
|
||||
it('should apply reasoning format to default reasoning params', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
defaultParams: {
|
||||
reasoning_effort: ReasoningEffort.low,
|
||||
reasoning_summary: ReasoningSummary.concise,
|
||||
},
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toHaveProperty('reasoning', {
|
||||
effort: ReasoningEffort.low,
|
||||
summary: ReasoningSummary.concise,
|
||||
});
|
||||
expect(result.llmConfig.modelKwargs).not.toHaveProperty('reasoning_effort');
|
||||
});
|
||||
|
||||
it('should let addParams reasoning override default reasoning params before formatting', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
defaultParams: {
|
||||
reasoning_effort: ReasoningEffort.low,
|
||||
},
|
||||
addParams: {
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig.modelKwargs).toHaveProperty('reasoning', {
|
||||
effort: ReasoningEffort.high,
|
||||
});
|
||||
expect(result.llmConfig.modelKwargs).not.toHaveProperty('reasoning_effort');
|
||||
});
|
||||
|
||||
it('should allow custom endpoints to disable reasoning passthrough', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.disabled,
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use Responses API reasoning when web_search enables Responses API', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
reasoning_summary: ReasoningSummary.concise,
|
||||
web_search: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).toHaveProperty('useResponsesApi', true);
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig.modelKwargs).toHaveProperty('reasoning', {
|
||||
effort: ReasoningEffort.high,
|
||||
summary: ReasoningSummary.concise,
|
||||
});
|
||||
expect(result.tools).toContainEqual({ type: 'web_search' });
|
||||
});
|
||||
|
||||
it('should remove reasoning kwargs for GPT-4o search models', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
modelOptions: {
|
||||
model: 'gpt-4o-search',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should honor dropParams after reasoning object conversion', () => {
|
||||
const result = getOpenAILLMConfig({
|
||||
apiKey: 'test-api-key',
|
||||
streaming: true,
|
||||
endpoint: 'custom',
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
dropParams: ['reasoning_effort'],
|
||||
modelOptions: {
|
||||
model: 'provider/reasoning-model',
|
||||
reasoning_effort: ReasoningEffort.high,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning');
|
||||
expect(result.llmConfig).not.toHaveProperty('reasoning_effort');
|
||||
expect(result.llmConfig.modelKwargs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use reasoning object when useResponsesApi is true', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
EModelEndpoint,
|
||||
ReasoningParameterFormat,
|
||||
removeNullishValues,
|
||||
supportsAdaptiveThinking,
|
||||
} from 'librechat-data-provider';
|
||||
|
|
@ -86,6 +87,78 @@ function hasReasoningParams({
|
|||
);
|
||||
}
|
||||
|
||||
function getReasoningObject({
|
||||
reasoningEffort,
|
||||
reasoningSummary,
|
||||
}: {
|
||||
reasoningEffort?: OpenAILLMConfig['reasoning_effort'];
|
||||
reasoningSummary?: OpenAILLMConfig['reasoning_summary'];
|
||||
}): OpenAI.Reasoning {
|
||||
return removeNullishValues(
|
||||
{
|
||||
effort: reasoningEffort,
|
||||
summary: reasoningSummary,
|
||||
},
|
||||
true,
|
||||
) as OpenAI.Reasoning;
|
||||
}
|
||||
|
||||
function isOpenAIEndpoint(endpoint?: EModelEndpoint | string | null): boolean {
|
||||
return endpoint === EModelEndpoint.openAI || endpoint === EModelEndpoint.azureOpenAI;
|
||||
}
|
||||
|
||||
function removeReasoningSummary(target: Record<string, unknown>) {
|
||||
const { reasoning } = target;
|
||||
if (reasoning == null || typeof reasoning !== 'object' || Array.isArray(reasoning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rest = { ...(reasoning as Record<string, unknown>) };
|
||||
delete rest.summary;
|
||||
if (Object.keys(rest).length === 0) {
|
||||
delete target.reasoning;
|
||||
return;
|
||||
}
|
||||
|
||||
target.reasoning = rest;
|
||||
}
|
||||
|
||||
function removeReasoningPayload(target: Record<string, unknown>) {
|
||||
delete target.reasoning;
|
||||
delete target.reasoning_effort;
|
||||
}
|
||||
|
||||
function deleteConfigParam({
|
||||
param,
|
||||
llmConfig,
|
||||
modelKwargs,
|
||||
}: {
|
||||
param: string;
|
||||
llmConfig: OpenAILLMConfig;
|
||||
modelKwargs: Record<string, unknown>;
|
||||
}) {
|
||||
if (param === 'reasoning_effort') {
|
||||
removeReasoningPayload(llmConfig as Record<string, unknown>);
|
||||
removeReasoningPayload(modelKwargs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (param === 'reasoning_summary') {
|
||||
delete (llmConfig as Record<string, unknown>).reasoning_summary;
|
||||
delete modelKwargs.reasoning_summary;
|
||||
removeReasoningSummary(llmConfig as Record<string, unknown>);
|
||||
removeReasoningSummary(modelKwargs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (param in llmConfig) {
|
||||
delete llmConfig[param as keyof t.OAIClientOptions];
|
||||
}
|
||||
if (param in modelKwargs) {
|
||||
delete modelKwargs[param];
|
||||
}
|
||||
}
|
||||
|
||||
const openRouterAnthropicVerbosityByEffort: Record<
|
||||
string,
|
||||
NonNullable<OpenAILLMConfig['verbosity']>
|
||||
|
|
@ -204,6 +277,64 @@ function applyOpenRouterReasoningConfig({
|
|||
return true;
|
||||
}
|
||||
|
||||
function applyReasoningConfig({
|
||||
endpoint,
|
||||
llmConfig,
|
||||
modelKwargs,
|
||||
reasoningEffort,
|
||||
reasoningFormat,
|
||||
reasoningSummary,
|
||||
}: {
|
||||
endpoint?: EModelEndpoint | string | null;
|
||||
llmConfig: OpenAILLMConfig;
|
||||
modelKwargs: Record<string, unknown>;
|
||||
reasoningEffort?: OpenAILLMConfig['reasoning_effort'];
|
||||
reasoningFormat?: ReasoningParameterFormat;
|
||||
reasoningSummary?: OpenAILLMConfig['reasoning_summary'];
|
||||
}): boolean {
|
||||
if (
|
||||
!hasReasoningParams({
|
||||
reasoning_effort: reasoningEffort,
|
||||
reasoning_summary: reasoningSummary,
|
||||
})
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reasoning = getReasoningObject({ reasoningEffort, reasoningSummary });
|
||||
if (reasoningFormat === ReasoningParameterFormat.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isOpenAIEndpoint(endpoint)) {
|
||||
if (llmConfig.useResponsesApi === true) {
|
||||
llmConfig.reasoning = reasoning;
|
||||
return false;
|
||||
}
|
||||
if (reasoningEffort) {
|
||||
llmConfig.reasoning_effort = reasoningEffort;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (llmConfig.useResponsesApi === true) {
|
||||
modelKwargs.reasoning = reasoning;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (reasoningFormat === ReasoningParameterFormat.reasoningObject) {
|
||||
modelKwargs.reasoning = reasoning;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (reasoningEffort) {
|
||||
modelKwargs.reasoning_effort = reasoningEffort;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getModelKwargsText(modelKwargs: Record<string, unknown>): Record<string, unknown> {
|
||||
const { text } = modelKwargs;
|
||||
if (text == null || typeof text !== 'object' || Array.isArray(text)) {
|
||||
|
|
@ -294,6 +425,7 @@ export function getOpenAILLMConfig({
|
|||
dropParams,
|
||||
defaultParams,
|
||||
useOpenRouter,
|
||||
reasoningFormat = ReasoningParameterFormat.reasoningEffort,
|
||||
modelOptions: _modelOptions,
|
||||
}: {
|
||||
apiKey: string;
|
||||
|
|
@ -305,6 +437,7 @@ export function getOpenAILLMConfig({
|
|||
dropParams?: string[];
|
||||
defaultParams?: Record<string, unknown>;
|
||||
useOpenRouter?: boolean;
|
||||
reasoningFormat?: ReasoningParameterFormat;
|
||||
azure?: false | t.AzureOptions;
|
||||
}): Pick<t.LLMConfigResult, 'llmConfig' | 'tools'> & {
|
||||
azure?: t.AzureOptions;
|
||||
|
|
@ -343,6 +476,8 @@ export function getOpenAILLMConfig({
|
|||
|
||||
const modelKwargs: Record<string, unknown> = {};
|
||||
let hasModelKwargs = false;
|
||||
let reasoningEffort = reasoning_effort;
|
||||
let reasoningSummary = reasoning_summary;
|
||||
|
||||
if (verbosity != null && verbosity !== '' && useOpenRouter) {
|
||||
llmConfig.verbosity = verbosity;
|
||||
|
|
@ -369,6 +504,18 @@ export function getOpenAILLMConfig({
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'reasoning_effort') {
|
||||
if (!reasoningEffort && typeof value === 'string') {
|
||||
reasoningEffort = value as OpenAILLMConfig['reasoning_effort'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'reasoning_summary') {
|
||||
if (!reasoningSummary && typeof value === 'string') {
|
||||
reasoningSummary = value as OpenAILLMConfig['reasoning_summary'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'verbosity') {
|
||||
hasModelKwargs =
|
||||
applyVerbosityParam({
|
||||
|
|
@ -408,6 +555,18 @@ export function getOpenAILLMConfig({
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'reasoning_effort') {
|
||||
if (typeof value === 'string' || value == null) {
|
||||
reasoningEffort = value as OpenAILLMConfig['reasoning_effort'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'reasoning_summary') {
|
||||
if (typeof value === 'string' || value == null) {
|
||||
reasoningSummary = value as OpenAILLMConfig['reasoning_summary'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === 'verbosity') {
|
||||
hasModelKwargs =
|
||||
applyVerbosityParam({
|
||||
|
|
@ -437,25 +596,11 @@ export function getOpenAILLMConfig({
|
|||
*/
|
||||
hasModelKwargs =
|
||||
applyOpenRouterReasoningConfig({
|
||||
reasoningEffort: reasoning_effort,
|
||||
reasoningEffort,
|
||||
model: modelOptions.model,
|
||||
modelKwargs,
|
||||
llmConfig,
|
||||
}) || hasModelKwargs;
|
||||
} else if (
|
||||
hasReasoningParams({ reasoning_effort, reasoning_summary }) &&
|
||||
(llmConfig.useResponsesApi === true ||
|
||||
(endpoint !== EModelEndpoint.openAI && endpoint !== EModelEndpoint.azureOpenAI))
|
||||
) {
|
||||
llmConfig.reasoning = removeNullishValues(
|
||||
{
|
||||
effort: reasoning_effort,
|
||||
summary: reasoning_summary,
|
||||
},
|
||||
true,
|
||||
) as OpenAI.Reasoning;
|
||||
} else if (hasReasoningParams({ reasoning_effort })) {
|
||||
llmConfig.reasoning_effort = reasoning_effort;
|
||||
}
|
||||
|
||||
if (llmConfig.max_tokens != null) {
|
||||
|
|
@ -486,6 +631,18 @@ export function getOpenAILLMConfig({
|
|||
llmConfig.promptCache = true;
|
||||
}
|
||||
|
||||
if (!useOpenRouter) {
|
||||
hasModelKwargs =
|
||||
applyReasoningConfig({
|
||||
endpoint,
|
||||
llmConfig,
|
||||
modelKwargs,
|
||||
reasoningFormat,
|
||||
reasoningEffort,
|
||||
reasoningSummary,
|
||||
}) || hasModelKwargs;
|
||||
}
|
||||
|
||||
/** DeepSeek thinking-mode requires `reasoning_content` replay on tool turns (#13366). */
|
||||
if (
|
||||
typeof modelOptions.model === 'string' &&
|
||||
|
|
@ -515,11 +672,7 @@ export function getOpenAILLMConfig({
|
|||
const updatedDropParams = dropParams || [];
|
||||
const combinedDropParams = [...new Set([...updatedDropParams, ...reasoningExcludeParams])];
|
||||
|
||||
combinedDropParams.forEach((param) => {
|
||||
if (param in llmConfig) {
|
||||
delete llmConfig[param as keyof t.OAIClientOptions];
|
||||
}
|
||||
});
|
||||
combinedDropParams.forEach((param) => deleteConfigParam({ param, llmConfig, modelKwargs }));
|
||||
} else if (modelOptions.model && /gpt-4o.*search/.test(modelOptions.model as string)) {
|
||||
/**
|
||||
* Note: OpenAI Web Search models do not support any known parameters besides `max_tokens`
|
||||
|
|
@ -544,17 +697,9 @@ export function getOpenAILLMConfig({
|
|||
const updatedDropParams = dropParams || [];
|
||||
const combinedDropParams = [...new Set([...updatedDropParams, ...searchExcludeParams])];
|
||||
|
||||
combinedDropParams.forEach((param) => {
|
||||
if (param in llmConfig) {
|
||||
delete llmConfig[param as keyof t.OAIClientOptions];
|
||||
}
|
||||
});
|
||||
combinedDropParams.forEach((param) => deleteConfigParam({ param, llmConfig, modelKwargs }));
|
||||
} else if (dropParams && Array.isArray(dropParams)) {
|
||||
dropParams.forEach((param) => {
|
||||
if (param in llmConfig) {
|
||||
delete llmConfig[param as keyof t.OAIClientOptions];
|
||||
}
|
||||
});
|
||||
dropParams.forEach((param) => deleteConfigParam({ param, llmConfig, modelKwargs }));
|
||||
}
|
||||
|
||||
hasModelKwargs =
|
||||
|
|
@ -576,7 +721,7 @@ export function getOpenAILLMConfig({
|
|||
hasModelKwargs = true;
|
||||
}
|
||||
|
||||
if (hasModelKwargs) {
|
||||
if (hasModelKwargs && Object.keys(modelKwargs).length > 0) {
|
||||
llmConfig.modelKwargs = modelKwargs;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ import {
|
|||
summarizationTriggerSchema,
|
||||
summarizationConfigSchema,
|
||||
} from '../src/config';
|
||||
import { tModelSpecPresetSchema, EModelEndpoint } from '../src/schemas';
|
||||
import {
|
||||
tModelSpecPresetSchema,
|
||||
EModelEndpoint,
|
||||
ReasoningParameterFormat,
|
||||
ReasoningResponseKey,
|
||||
} from '../src/schemas';
|
||||
import { specsConfigSchema } from '../src/models';
|
||||
import { FileSources } from '../src/types/files';
|
||||
|
||||
|
|
@ -305,6 +310,58 @@ describe('endpointSchema addParams validation', () => {
|
|||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts custom reasoning format config', () => {
|
||||
const result = endpointSchema.safeParse({
|
||||
...validEndpoint,
|
||||
customParams: {
|
||||
reasoningFormat: ReasoningParameterFormat.reasoningObject,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.customParams?.reasoningFormat).toBe(
|
||||
ReasoningParameterFormat.reasoningObject,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid custom reasoning format config', () => {
|
||||
const result = endpointSchema.safeParse({
|
||||
...validEndpoint,
|
||||
customParams: {
|
||||
reasoningFormat: 'provider_magic',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts custom reasoning response key config', () => {
|
||||
const result = endpointSchema.safeParse({
|
||||
...validEndpoint,
|
||||
customParams: {
|
||||
reasoningKey: ReasoningResponseKey.reasoning,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.customParams?.reasoningKey).toBe(ReasoningResponseKey.reasoning);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects invalid custom reasoning response key config', () => {
|
||||
const result = endpointSchema.safeParse({
|
||||
...validEndpoint,
|
||||
customParams: {
|
||||
reasoningKey: 'reasoning_text',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects non-boolean web_search objects in addParams', () => {
|
||||
const result = endpointSchema.safeParse({
|
||||
...validEndpoint,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { z } from 'zod';
|
||||
import type { ZodError } from 'zod';
|
||||
import type { TEndpointsConfig, TModelsConfig, TConfig } from './types';
|
||||
import { EModelEndpoint, eModelEndpointSchema, isAgentsEndpoint } from './schemas';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
eModelEndpointSchema,
|
||||
isAgentsEndpoint,
|
||||
eReasoningParameterFormatSchema,
|
||||
eReasoningResponseKeySchema,
|
||||
} from './schemas';
|
||||
import { ComponentTypes, SettingTypes, OptionTypes } from './generate';
|
||||
import { specsConfigSchema, TSpecsConfig } from './models';
|
||||
import { fileConfigSchema } from './file-config';
|
||||
|
|
@ -630,6 +636,8 @@ export const endpointSchema = baseEndpointSchema.merge(
|
|||
customParams: z
|
||||
.object({
|
||||
defaultParamsEndpoint: z.string().default('custom'),
|
||||
reasoningFormat: eReasoningParameterFormatSchema.optional(),
|
||||
reasoningKey: eReasoningResponseKeySchema.optional(),
|
||||
paramDefinitions: z.array(paramDefinitionSchema).optional(),
|
||||
})
|
||||
.strict()
|
||||
|
|
|
|||
|
|
@ -177,6 +177,17 @@ export enum ReasoningEffort {
|
|||
xhigh = 'xhigh',
|
||||
}
|
||||
|
||||
export enum ReasoningParameterFormat {
|
||||
disabled = 'disabled',
|
||||
reasoningEffort = 'reasoning_effort',
|
||||
reasoningObject = 'reasoning_object',
|
||||
}
|
||||
|
||||
export enum ReasoningResponseKey {
|
||||
reasoning = 'reasoning',
|
||||
reasoningContent = 'reasoning_content',
|
||||
}
|
||||
|
||||
export enum AnthropicEffort {
|
||||
unset = '',
|
||||
low = 'low',
|
||||
|
|
@ -250,6 +261,8 @@ export const imageDetailValue = {
|
|||
|
||||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
export const eReasoningEffortSchema = z.nativeEnum(ReasoningEffort);
|
||||
export const eReasoningParameterFormatSchema = z.nativeEnum(ReasoningParameterFormat);
|
||||
export const eReasoningResponseKeySchema = z.nativeEnum(ReasoningResponseKey);
|
||||
export const eAnthropicEffortSchema = z.nativeEnum(AnthropicEffort);
|
||||
export const eThinkingDisplaySchema = z.nativeEnum(ThinkingDisplay);
|
||||
export const eReasoningSummarySchema = z.nativeEnum(ReasoningSummary);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import type {
|
|||
TAttachment,
|
||||
TMessage,
|
||||
TBanner,
|
||||
ReasoningResponseKey,
|
||||
ReasoningParameterFormat,
|
||||
} from './schemas';
|
||||
import type { RefillIntervalUnit } from './balance';
|
||||
import type { SettingDefinition } from './generate';
|
||||
|
|
@ -398,6 +400,8 @@ export type TConfig = {
|
|||
capabilities?: string[];
|
||||
customParams?: {
|
||||
defaultParamsEndpoint?: string;
|
||||
reasoningFormat?: ReasoningParameterFormat;
|
||||
reasoningKey?: ReasoningResponseKey;
|
||||
paramDefinitions?: Partial<SettingDefinition>[];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue