Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useAccessStore,
useChatStore,
} from "../store";
import type { ModelConfig } from "../store/config";
import { ChatGPTApi, DalleRequestPayload } from "./platforms/openai";
import { GeminiProApi } from "./platforms/google";
import { ClaudeApi } from "./platforms/anthropic";
Expand Down Expand Up @@ -397,3 +398,78 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.GPT);
}
}

// Core fields that must not be overwritten by extraParams
const PROTECTED_PAYLOAD_KEYS = new Set([
"messages",
"model",
"stream",
"temperature",
"top_p",
"presence_penalty",
"frequency_penalty",
]);

/**
* Apply model-config extras to a request payload (called after requestPayload
* has been constructed in each provider):
* 1. Remove parameters flagged as "do not send" so the provider defaults
* are used instead.
* 2. Parse modelConfig.extraParams (a JSON string) and merge it into the
* payload.
* 3. Protect core fields from being overwritten by extraParams.
*
* Note: temperature/top_p/presence_penalty/frequency_penalty are in the
* protected set because they can be removed by the disable flags above;
* adding them back via extraParams would defeat that intent.
*/
export function applyModelConfigExtras(
modelConfig: ModelConfig,
payload: Record<string, any>,
): Record<string, any> {
if (!payload || typeof payload !== "object") {
return payload;
}

// 1. Remove parameters the user chose not to send
if (modelConfig.disableTemperature) {
delete payload.temperature;
}
if (modelConfig.disableTopP) {
delete payload.top_p;
}
if (modelConfig.disablePresencePenalty) {
delete payload.presence_penalty;
}
if (modelConfig.disableFrequencyPenalty) {
delete payload.frequency_penalty;
}

// 2. Merge custom request parameters
const extraParamsStr = modelConfig.extraParams;
if (extraParamsStr && extraParamsStr.trim().length > 0) {
try {
const extra = JSON.parse(extraParamsStr);
if (extra && typeof extra === "object" && !Array.isArray(extra)) {
Object.keys(extra).forEach((key) => {
if (!PROTECTED_PAYLOAD_KEYS.has(key)) {
payload[key] = extra[key];
} else {
console.warn(
`[applyModelConfigExtras] skip protected key "${key}"`,
);
}
});
} else {
console.warn(
"[applyModelConfigExtras] extraParams must be a JSON object, got:",
extra,
);
}
} catch (e) {
console.warn("[applyModelConfigExtras] invalid extraParams JSON:", e);
}
}

return payload;
}
3 changes: 3 additions & 0 deletions app/client/platforms/ai302.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import {
Expand Down Expand Up @@ -117,6 +118,8 @@ export class Ai302Api implements LLMApi {
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] openai payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
13 changes: 13 additions & 0 deletions app/client/platforms/alibaba.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SpeechOptions,
MultimodalContent,
MultimodalContentForAlibaba,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import {
Expand Down Expand Up @@ -132,6 +133,18 @@ export class QwenApi implements LLMApi {
},
};

// Alibaba nests temperature/top_p inside parameters, so handle the
// disable flags specifically for those nested fields
if (modelConfig.disableTemperature) {
delete (requestPayload.parameters as any).temperature;
}
if (modelConfig.disableTopP) {
delete (requestPayload.parameters as any).top_p;
}
// Merge custom extraParams at the top level; top-level disable flags
// are no-ops here because Alibaba does not use those top-level names
applyModelConfigExtras(modelConfig, requestPayload as any);

const controller = new AbortController();
options.onController?.(controller);

Expand Down
7 changes: 6 additions & 1 deletion app/client/platforms/anthropic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Anthropic, ApiPath } from "@/app/constant";
import { ChatOptions, getHeaders, LLMApi, SpeechOptions } from "../api";
import { ChatOptions, getHeaders, LLMApi, SpeechOptions, applyModelConfigExtras } from "../api";
import {
useAccessStore,
useAppConfig,
Expand Down Expand Up @@ -191,6 +191,11 @@ export class ClaudeApi implements LLMApi {
top_k: 5,
};

// Apply disable flags (temperature/top_p) + merge custom extraParams
// presence_penalty / frequency_penalty are not supported by Anthropic,
// so those disable flags are effectively no-ops here
applyModelConfigExtras(modelConfig, requestBody as any);

const path = this.path(Anthropic.ChatPath);

const controller = new AbortController();
Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/baidu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
LLMModel,
MultimodalContent,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import Locale from "../../locales";
import {
Expand Down Expand Up @@ -117,6 +118,8 @@ export class ErnieApi implements LLMApi {
top_p: modelConfig.top_p,
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] Baidu payload: ", requestPayload);

const controller = new AbortController();
Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/bytedance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LLMModel,
MultimodalContent,
SpeechOptions,
applyModelConfigExtras,
} from "../api";

import { streamWithThink } from "@/app/utils/chat";
Expand Down Expand Up @@ -113,6 +114,8 @@ export class DoubaoApi implements LLMApi {
top_p: modelConfig.top_p,
};

applyModelConfigExtras(modelConfig, requestPayload);

const controller = new AbortController();
options.onController?.(controller);

Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/deepseek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import {
Expand Down Expand Up @@ -115,6 +116,8 @@ export class DeepSeekApi implements LLMApi {
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] openai payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
6 changes: 6 additions & 0 deletions app/client/platforms/glm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import {
Expand Down Expand Up @@ -175,6 +176,11 @@ export class ChatGLMApi implements LLMApi {
const requestPayload = this.createPayload(messages, modelConfig, options);
const path = this.path(this.getModelPath(modelType));

// Apply disable flags + merge custom extraParams (chat only; skip for image/video)
if (modelType !== "image" && modelType !== "video") {
applyModelConfigExtras(modelConfig, requestPayload as any);
}

console.log(`[Request] glm ${modelType} payload: `, requestPayload);

const controller = new AbortController();
Expand Down
14 changes: 14 additions & 0 deletions app/client/platforms/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
LLMModel,
LLMUsage,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import {
useAccessStore,
Expand Down Expand Up @@ -181,6 +182,19 @@ export class GeminiProApi implements LLMApi {
],
};

// Google nests temperature/topP inside generationConfig, so handle the
// disable flags specifically for those nested fields
if (modelConfig.disableTemperature) {
delete requestPayload.generationConfig.temperature;
}
if (modelConfig.disableTopP) {
delete requestPayload.generationConfig.topP;
}
// Merge custom extraParams at the top level; top-level disable flags
// (temperature/top_p/presence_penalty/frequency_penalty) are no-ops here
// because Google does not use those top-level field names
applyModelConfigExtras(modelConfig, requestPayload);

let shouldStream = !!options.config.stream;
const controller = new AbortController();
options.onController?.(controller);
Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/iflytek.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import Locale from "../../locales";
import {
Expand Down Expand Up @@ -92,6 +93,8 @@ export class SparkApi implements LLMApi {
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] Spark payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/moonshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import { getMessageTextContent } from "@/app/utils";
Expand Down Expand Up @@ -92,6 +93,8 @@ export class MoonshotApi implements LLMApi {
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] openai payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
4 changes: 4 additions & 0 deletions app/client/platforms/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
LLMUsage,
MultimodalContent,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import Locale from "../../locales";
import { getClientConfig } from "@/app/config/client";
Expand Down Expand Up @@ -263,6 +264,9 @@ export class ChatGPTApi implements LLMApi {
if (visionModel && !isO1OrO3 && ! isGpt5) {
requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000);
}

// Apply disable flags + merge custom extraParams (chat only; skip for Dalle3)
requestPayload = applyModelConfigExtras(modelConfig, requestPayload as any) as any;
}

console.log("[Request] openai payload: ", requestPayload);
Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/siliconflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import {
Expand Down Expand Up @@ -117,6 +118,8 @@ export class SiliconflowApi implements LLMApi {
// Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore.
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] openai payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
20 changes: 16 additions & 4 deletions app/client/platforms/tencent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
LLMModel,
MultimodalContent,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import Locale from "../../locales";
import {
Expand Down Expand Up @@ -113,13 +114,24 @@ export class HunyuanApi implements LLMApi {
},
};

const requestPayload: RequestPayload = capitalizeKeys({
// Tencent payload keys get uppercased by capitalizeKeys, so construct the
// raw object conditionally based on disable flags before capitalizing
const basePayload: Record<string, any> = {
model: modelConfig.model,
messages,
temperature: modelConfig.temperature,
top_p: modelConfig.top_p,
stream: options.config.stream,
});
};
if (!modelConfig.disableTemperature) {
basePayload.temperature = modelConfig.temperature;
}
if (!modelConfig.disableTopP) {
basePayload.top_p = modelConfig.top_p;
}
const requestPayload: RequestPayload = capitalizeKeys(basePayload);

// Merge custom extraParams (top-level). Top-level disable flags are no-ops
// here because all keys have been capitalized already
applyModelConfigExtras(modelConfig, requestPayload as any);

console.log("[Request] Tencent payload: ", requestPayload);

Expand Down
3 changes: 3 additions & 0 deletions app/client/platforms/xai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LLMApi,
LLMModel,
SpeechOptions,
applyModelConfigExtras,
} from "../api";
import { getClientConfig } from "@/app/config/client";
import { getTimeoutMSByModel } from "@/app/utils";
Expand Down Expand Up @@ -86,6 +87,8 @@ export class XAIApi implements LLMApi {
top_p: modelConfig.top_p,
};

applyModelConfigExtras(modelConfig, requestPayload);

console.log("[Request] xai payload: ", requestPayload);

const shouldStream = !!options.config.stream;
Expand Down
3 changes: 3 additions & 0 deletions app/components/input-range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface InputRangeProps {
max: string;
step: string;
aria: string;
disabled?: boolean;
}

export function InputRange({
Expand All @@ -22,6 +23,7 @@ export function InputRange({
max,
step,
aria,
disabled,
}: InputRangeProps) {
return (
<div className={clsx(styles["input-range"], className)}>
Expand All @@ -35,6 +37,7 @@ export function InputRange({
max={max}
step={step}
onChange={onChange}
disabled={disabled}
></input>
</div>
);
Expand Down
Loading
Loading