mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 14:20:35 +08:00
feat: auto-detect dedicated memory llm providers
This commit is contained in:
@@ -2,6 +2,206 @@ function normalizeLlmConfigValue(value) {
|
||||
return String(value || "").trim();
|
||||
}
|
||||
|
||||
const OPENAI_COMPATIBLE_PROVIDER_LABELS = {
|
||||
openai: "OpenAI",
|
||||
openrouter: "OpenRouter",
|
||||
deepseek: "DeepSeek",
|
||||
xai: "xAI",
|
||||
mistral: "Mistral",
|
||||
moonshot: "Moonshot",
|
||||
zai: "Z.AI",
|
||||
groq: "Groq",
|
||||
siliconflow: "SiliconFlow",
|
||||
aimlapi: "AI/ML API",
|
||||
fireworks: "Fireworks",
|
||||
nanogpt: "NanoGPT",
|
||||
chutes: "Chutes",
|
||||
electronhub: "ElectronHub",
|
||||
"volcengine-ark": "火山方舟 Ark",
|
||||
"custom-openai-compatible": "自定义 OpenAI 兼容渠道",
|
||||
};
|
||||
|
||||
function tryParseLlmUrl(value) {
|
||||
const normalized = normalizeLlmConfigValue(value);
|
||||
if (!normalized) return null;
|
||||
|
||||
try {
|
||||
return new URL(normalized);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeParsedUrlString(parsedUrl) {
|
||||
if (!parsedUrl) return "";
|
||||
const cloned = new URL(parsedUrl.toString());
|
||||
cloned.search = "";
|
||||
cloned.hash = "";
|
||||
return String(cloned.toString()).replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function stripOpenAiCompatibleEndpointSuffix(value) {
|
||||
return String(value || "")
|
||||
.replace(/\/+((chat|text)\/completions|completions|embeddings|models)$/i, "")
|
||||
.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function stripAnthropicEndpointSuffix(value) {
|
||||
return String(value || "")
|
||||
.replace(/\/+messages$/i, "")
|
||||
.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function stripGoogleAiStudioEndpointSuffix(value) {
|
||||
return String(value || "")
|
||||
.replace(
|
||||
/\/+v\d+(?:beta)?\/models(?:\/[^/:?#]+:(?:streamGenerateContent|generateContent))?$/i,
|
||||
"",
|
||||
)
|
||||
.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function resolveKnownOpenAiCompatibleProviderId(parsedUrl) {
|
||||
const hostname = String(parsedUrl?.hostname || "").trim().toLowerCase();
|
||||
const pathname = String(parsedUrl?.pathname || "").trim().toLowerCase();
|
||||
|
||||
if (!hostname) {
|
||||
return "custom-openai-compatible";
|
||||
}
|
||||
|
||||
if (hostname.includes("openai.com")) return "openai";
|
||||
if (hostname.includes("openrouter.ai")) return "openrouter";
|
||||
if (hostname.includes("deepseek.com")) return "deepseek";
|
||||
if (hostname === "x.ai" || hostname === "api.x.ai" || hostname.endsWith(".x.ai")) {
|
||||
return "xai";
|
||||
}
|
||||
if (hostname.includes("mistral.ai")) return "mistral";
|
||||
if (hostname.includes("moonshot.ai")) return "moonshot";
|
||||
if (hostname === "api.z.ai" || hostname.endsWith(".z.ai")) return "zai";
|
||||
if (hostname.includes("groq.com")) return "groq";
|
||||
if (hostname.includes("siliconflow.com")) return "siliconflow";
|
||||
if (hostname.includes("aimlapi.com")) return "aimlapi";
|
||||
if (hostname.includes("fireworks.ai")) return "fireworks";
|
||||
if (hostname.includes("nano-gpt.com")) return "nanogpt";
|
||||
if (hostname.includes("chutes.ai")) return "chutes";
|
||||
if (hostname.includes("electronhub.ai")) return "electronhub";
|
||||
if (
|
||||
hostname.includes("volces.com") ||
|
||||
hostname.startsWith("ark.") ||
|
||||
pathname.includes("/api/coding/v3")
|
||||
) {
|
||||
return "volcengine-ark";
|
||||
}
|
||||
|
||||
return "custom-openai-compatible";
|
||||
}
|
||||
|
||||
function createResolvedDedicatedProviderConfig(overrides = {}) {
|
||||
return {
|
||||
inputUrl: "",
|
||||
apiUrl: "",
|
||||
providerId: "",
|
||||
providerLabel: "",
|
||||
transportId: "",
|
||||
transportLabel: "",
|
||||
hostSource: "",
|
||||
hostSourceConst: "",
|
||||
routeMode: "",
|
||||
supportsModelFetch: false,
|
||||
statusStrategies: [],
|
||||
isKnownProvider: false,
|
||||
isOpenAiCompatible: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveDedicatedLlmProviderConfig(value = "") {
|
||||
const normalizedInput = normalizeLlmConfigValue(value);
|
||||
if (!normalizedInput) {
|
||||
return createResolvedDedicatedProviderConfig();
|
||||
}
|
||||
|
||||
const parsedUrl = tryParseLlmUrl(normalizedInput);
|
||||
if (!parsedUrl) {
|
||||
return createResolvedDedicatedProviderConfig({
|
||||
inputUrl: normalizedInput,
|
||||
apiUrl: normalizedInput.replace(/\/+$/, ""),
|
||||
providerId: "custom-openai-compatible",
|
||||
providerLabel: OPENAI_COMPATIBLE_PROVIDER_LABELS["custom-openai-compatible"],
|
||||
transportId: "dedicated-openai-compatible",
|
||||
transportLabel: "专用 OpenAI 兼容接口",
|
||||
hostSource: "custom",
|
||||
hostSourceConst: "CUSTOM",
|
||||
routeMode: "custom",
|
||||
supportsModelFetch: true,
|
||||
statusStrategies: ["custom", "openai-reverse-proxy"],
|
||||
isKnownProvider: false,
|
||||
isOpenAiCompatible: true,
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedUrl = normalizeParsedUrlString(parsedUrl);
|
||||
const hostname = String(parsedUrl.hostname || "").trim().toLowerCase();
|
||||
|
||||
if (hostname.includes("anthropic.com")) {
|
||||
const apiUrl = stripAnthropicEndpointSuffix(normalizedUrl) || normalizedUrl;
|
||||
return createResolvedDedicatedProviderConfig({
|
||||
inputUrl: normalizedInput,
|
||||
apiUrl,
|
||||
providerId: "anthropic-claude",
|
||||
providerLabel: "Anthropic Claude",
|
||||
transportId: "dedicated-anthropic-claude",
|
||||
transportLabel: "Anthropic Claude 接口",
|
||||
hostSource: "claude",
|
||||
hostSourceConst: "CLAUDE",
|
||||
routeMode: "reverse-proxy",
|
||||
supportsModelFetch: false,
|
||||
statusStrategies: [],
|
||||
isKnownProvider: true,
|
||||
isOpenAiCompatible: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (hostname.includes("generativelanguage.googleapis.com")) {
|
||||
const apiUrl = stripGoogleAiStudioEndpointSuffix(normalizedUrl) || normalizedUrl;
|
||||
return createResolvedDedicatedProviderConfig({
|
||||
inputUrl: normalizedInput,
|
||||
apiUrl,
|
||||
providerId: "google-ai-studio",
|
||||
providerLabel: "Google AI Studio / Gemini",
|
||||
transportId: "dedicated-google-ai-studio",
|
||||
transportLabel: "Google AI Studio / Gemini 接口",
|
||||
hostSource: "makersuite",
|
||||
hostSourceConst: "MAKERSUITE",
|
||||
routeMode: "reverse-proxy",
|
||||
supportsModelFetch: true,
|
||||
statusStrategies: ["makersuite-reverse-proxy"],
|
||||
isKnownProvider: true,
|
||||
isOpenAiCompatible: false,
|
||||
});
|
||||
}
|
||||
|
||||
const providerId = resolveKnownOpenAiCompatibleProviderId(parsedUrl);
|
||||
const apiUrl = stripOpenAiCompatibleEndpointSuffix(normalizedUrl) || normalizedUrl;
|
||||
return createResolvedDedicatedProviderConfig({
|
||||
inputUrl: normalizedInput,
|
||||
apiUrl,
|
||||
providerId,
|
||||
providerLabel:
|
||||
OPENAI_COMPATIBLE_PROVIDER_LABELS[providerId] ||
|
||||
OPENAI_COMPATIBLE_PROVIDER_LABELS["custom-openai-compatible"],
|
||||
transportId: "dedicated-openai-compatible",
|
||||
transportLabel: "专用 OpenAI 兼容接口",
|
||||
hostSource: "custom",
|
||||
hostSourceConst: "CUSTOM",
|
||||
routeMode: "custom",
|
||||
supportsModelFetch: true,
|
||||
statusStrategies: ["custom", "openai-reverse-proxy"],
|
||||
isKnownProvider: providerId !== "custom-openai-compatible",
|
||||
isOpenAiCompatible: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function createLlmConfigSnapshot(source = {}) {
|
||||
return {
|
||||
llmApiUrl: normalizeLlmConfigValue(source?.llmApiUrl),
|
||||
|
||||
256
llm/llm.js
256
llm/llm.js
@@ -6,7 +6,10 @@ import { extension_settings } from "../../../../extensions.js";
|
||||
import { chat_completion_sources, sendOpenAIRequest } from "../../../../openai.js";
|
||||
import { debugLog, debugWarn } from "../runtime/debug-logging.js";
|
||||
import { resolveTaskGenerationOptions } from "../runtime/generation-options.js";
|
||||
import { resolveLlmConfigSelection } from "./llm-preset-utils.js";
|
||||
import {
|
||||
resolveDedicatedLlmProviderConfig,
|
||||
resolveLlmConfigSelection,
|
||||
} from "./llm-preset-utils.js";
|
||||
import { getActiveTaskProfile } from "../prompting/prompt-profiles.js";
|
||||
import { resolveConfiguredTimeoutMs } from "../runtime/request-timeout.js";
|
||||
import { applyTaskRegex } from "../prompting/task-regex.js";
|
||||
@@ -206,11 +209,27 @@ function getMemoryLLMConfig(taskType = "") {
|
||||
? activeProfile.generation.llm_preset
|
||||
: "";
|
||||
const selection = resolveLlmConfigSelection(settings, selectedPresetName);
|
||||
const resolvedProvider = resolveDedicatedLlmProviderConfig(
|
||||
selection.config?.llmApiUrl,
|
||||
);
|
||||
return {
|
||||
apiUrl: normalizeOpenAICompatibleBaseUrl(selection.config?.llmApiUrl),
|
||||
inputApiUrl: resolvedProvider.inputUrl || "",
|
||||
apiUrl: resolvedProvider.apiUrl || "",
|
||||
apiKey: String(selection.config?.llmApiKey || "").trim(),
|
||||
model: String(selection.config?.llmModel || "").trim(),
|
||||
timeoutMs: getConfiguredTimeoutMs(settings),
|
||||
llmProvider: resolvedProvider.providerId || "",
|
||||
llmProviderLabel: resolvedProvider.providerLabel || "",
|
||||
llmTransport: resolvedProvider.transportId || "",
|
||||
llmTransportLabel: resolvedProvider.transportLabel || "",
|
||||
llmRouteMode: resolvedProvider.routeMode || "",
|
||||
llmHostSource: resolvedProvider.hostSource || "",
|
||||
llmHostSourceConst: resolvedProvider.hostSourceConst || "",
|
||||
llmSupportsModelFetch: resolvedProvider.supportsModelFetch === true,
|
||||
llmStatusStrategies: Array.isArray(resolvedProvider.statusStrategies)
|
||||
? [...resolvedProvider.statusStrategies]
|
||||
: [],
|
||||
llmChannel: resolvedProvider,
|
||||
llmConfigSource: selection.source || "global",
|
||||
llmConfigSourceLabel: formatLlmConfigSourceLabel(selection.source),
|
||||
llmPresetName: selection.presetName || "",
|
||||
@@ -418,6 +437,7 @@ function buildEffectiveLlmRoute(
|
||||
hasDedicatedConfig,
|
||||
privateRequestSource,
|
||||
taskType = "",
|
||||
config = null,
|
||||
) {
|
||||
const dedicated = Boolean(hasDedicatedConfig);
|
||||
return {
|
||||
@@ -425,8 +445,18 @@ function buildEffectiveLlmRoute(
|
||||
requestSource: String(privateRequestSource || "").trim(),
|
||||
llm: dedicated ? "dedicated-memory-llm" : "sillytavern-current-model",
|
||||
transport: dedicated
|
||||
? "dedicated-openai-compatible"
|
||||
? String(config?.llmTransport || "dedicated-openai-compatible")
|
||||
: "sillytavern-current-model",
|
||||
transportLabel: dedicated
|
||||
? String(
|
||||
config?.llmTransportLabel || config?.llmProviderLabel || "专用记忆模型",
|
||||
)
|
||||
: "酒馆当前模型",
|
||||
provider: dedicated ? String(config?.llmProvider || "") : "",
|
||||
providerLabel: dedicated ? String(config?.llmProviderLabel || "") : "",
|
||||
routeMode: dedicated ? String(config?.llmRouteMode || "") : "",
|
||||
inputApiUrl: dedicated ? String(config?.inputApiUrl || "") : "",
|
||||
apiUrl: dedicated ? String(config?.apiUrl || "") : "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -665,10 +695,8 @@ function buildResponseErrorMessage(response, responseText = "") {
|
||||
}
|
||||
|
||||
function normalizeOpenAICompatibleBaseUrl(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.replace(/\/+(chat\/completions|embeddings)$/i, "")
|
||||
.replace(/\/+$/, "");
|
||||
const resolved = resolveDedicatedLlmProviderConfig(value);
|
||||
return resolved.apiUrl || String(value || "").trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function hasDedicatedLLMConfig(config = getMemoryLLMConfig()) {
|
||||
@@ -739,28 +767,87 @@ function buildDedicatedAuthHeaderString(apiKey = "") {
|
||||
return normalized ? `Authorization: Bearer ${normalized}` : "";
|
||||
}
|
||||
|
||||
function buildDedicatedStatusRequestVariants(config = getMemoryLLMConfig()) {
|
||||
const customVariant = {
|
||||
function resolveChatCompletionSourceValue(sourceConst = "", fallback = "") {
|
||||
const normalizedConst = String(sourceConst || "").trim();
|
||||
if (
|
||||
normalizedConst &&
|
||||
chat_completion_sources &&
|
||||
typeof chat_completion_sources === "object" &&
|
||||
chat_completion_sources[normalizedConst]
|
||||
) {
|
||||
return String(chat_completion_sources[normalizedConst]).trim();
|
||||
}
|
||||
return String(fallback || "").trim();
|
||||
}
|
||||
|
||||
function buildDedicatedCustomStatusVariant(config = getMemoryLLMConfig()) {
|
||||
return {
|
||||
mode: "custom",
|
||||
body: {
|
||||
chat_completion_source: chat_completion_sources.CUSTOM,
|
||||
chat_completion_source: resolveChatCompletionSourceValue("CUSTOM", "custom"),
|
||||
custom_url: config.apiUrl,
|
||||
custom_include_headers: buildDedicatedAuthHeaderString(config.apiKey),
|
||||
reverse_proxy: config.apiUrl,
|
||||
proxy_password: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const legacyOpenAiVariant = {
|
||||
mode: "openai-reverse-proxy",
|
||||
function buildDedicatedReverseProxyStatusVariant(
|
||||
mode,
|
||||
sourceConst,
|
||||
fallbackSource,
|
||||
config = getMemoryLLMConfig(),
|
||||
) {
|
||||
return {
|
||||
mode,
|
||||
body: {
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
chat_completion_source: resolveChatCompletionSourceValue(
|
||||
sourceConst,
|
||||
fallbackSource,
|
||||
),
|
||||
reverse_proxy: config.apiUrl,
|
||||
proxy_password: config.apiKey || "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return [customVariant, legacyOpenAiVariant];
|
||||
function buildDedicatedStatusRequestVariants(config = getMemoryLLMConfig()) {
|
||||
const strategies = Array.isArray(config.llmStatusStrategies)
|
||||
? config.llmStatusStrategies
|
||||
: ["custom", "openai-reverse-proxy"];
|
||||
const variants = [];
|
||||
const seenModes = new Set();
|
||||
|
||||
for (const strategy of strategies) {
|
||||
let variant = null;
|
||||
if (strategy === "custom") {
|
||||
variant = buildDedicatedCustomStatusVariant(config);
|
||||
} else if (strategy === "openai-reverse-proxy") {
|
||||
variant = buildDedicatedReverseProxyStatusVariant(
|
||||
"openai-reverse-proxy",
|
||||
"OPENAI",
|
||||
"openai",
|
||||
config,
|
||||
);
|
||||
} else if (strategy === "makersuite-reverse-proxy") {
|
||||
variant = buildDedicatedReverseProxyStatusVariant(
|
||||
"makersuite-reverse-proxy",
|
||||
"MAKERSUITE",
|
||||
"makersuite",
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
if (!variant?.mode || seenModes.has(variant.mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenModes.add(variant.mode);
|
||||
variants.push(variant);
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
async function requestDedicatedStatusModels(
|
||||
@@ -1444,6 +1531,64 @@ async function executeDedicatedRequest(
|
||||
}
|
||||
}
|
||||
|
||||
function shouldForceDedicatedNonStream(config = getMemoryLLMConfig()) {
|
||||
return (
|
||||
String(config.llmRouteMode || "").trim() === "reverse-proxy" &&
|
||||
["claude", "makersuite"].includes(
|
||||
String(config.llmHostSource || "").trim().toLowerCase(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function buildDedicatedRequestBody(
|
||||
config,
|
||||
transportMessages,
|
||||
filteredGeneration,
|
||||
resolvedCompletionTokens,
|
||||
{ jsonMode = false } = {},
|
||||
) {
|
||||
const routeMode = String(config?.llmRouteMode || "custom").trim() || "custom";
|
||||
const body = {
|
||||
model: config.model,
|
||||
messages: transportMessages,
|
||||
temperature: filteredGeneration.temperature ?? 1,
|
||||
max_tokens: resolvedCompletionTokens,
|
||||
stream: filteredGeneration.stream ?? false,
|
||||
frequency_penalty: filteredGeneration.frequency_penalty ?? 0,
|
||||
presence_penalty: filteredGeneration.presence_penalty ?? 0,
|
||||
top_p: filteredGeneration.top_p ?? 1,
|
||||
};
|
||||
|
||||
if (routeMode === "reverse-proxy") {
|
||||
body.chat_completion_source = resolveChatCompletionSourceValue(
|
||||
config.llmHostSourceConst,
|
||||
config.llmHostSource || "custom",
|
||||
);
|
||||
body.reverse_proxy = config.apiUrl;
|
||||
body.proxy_password = config.apiKey || "";
|
||||
if (jsonMode) {
|
||||
body.json_schema = createGenericJsonSchema();
|
||||
}
|
||||
} else {
|
||||
body.chat_completion_source = resolveChatCompletionSourceValue("CUSTOM", "custom");
|
||||
body.custom_url = config.apiUrl;
|
||||
body.custom_include_headers = config.apiKey
|
||||
? buildYamlObject({
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
})
|
||||
: "";
|
||||
if (jsonMode && _jsonModeSupported) {
|
||||
body.custom_include_body = buildYamlObject({
|
||||
response_format: {
|
||||
type: "json_object",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
async function callDedicatedOpenAICompatible(
|
||||
messages,
|
||||
{
|
||||
@@ -1487,8 +1632,15 @@ async function callDedicatedOpenAICompatible(
|
||||
};
|
||||
const taskKey = taskType || privateRequestSource;
|
||||
const initialFilteredGeneration = generationResolved.filtered || {};
|
||||
const filteredGeneration = {
|
||||
...initialFilteredGeneration,
|
||||
};
|
||||
const forceNonStream = hasDedicatedConfig && shouldForceDedicatedNonStream(config);
|
||||
if (forceNonStream && filteredGeneration.stream === true) {
|
||||
filteredGeneration.stream = false;
|
||||
}
|
||||
const streamRequested =
|
||||
hasDedicatedConfig && initialFilteredGeneration.stream === true;
|
||||
hasDedicatedConfig && filteredGeneration.stream === true;
|
||||
const streamState = createStreamDebugState({
|
||||
requested: streamRequested,
|
||||
});
|
||||
@@ -1499,10 +1651,17 @@ async function callDedicatedOpenAICompatible(
|
||||
jsonMode,
|
||||
dedicatedConfig: hasDedicatedConfig,
|
||||
route: hasDedicatedConfig
|
||||
? "dedicated-openai-compatible"
|
||||
? config.llmTransport || "dedicated-openai-compatible"
|
||||
: "sillytavern-current-model",
|
||||
routeLabel: hasDedicatedConfig ? config.llmTransportLabel || "" : "酒馆当前模型",
|
||||
model: hasDedicatedConfig ? config.model : "sillytavern-current-model",
|
||||
inputApiUrl: hasDedicatedConfig ? config.inputApiUrl || "" : "",
|
||||
apiUrl: hasDedicatedConfig ? config.apiUrl : "",
|
||||
llmProvider: config.llmProvider || "",
|
||||
llmProviderLabel: config.llmProviderLabel || "",
|
||||
llmTransport: config.llmTransport || "",
|
||||
llmTransportLabel: config.llmTransportLabel || "",
|
||||
llmRouteMode: config.llmRouteMode || "",
|
||||
llmConfigSource: config.llmConfigSource || "global",
|
||||
llmConfigSourceLabel: config.llmConfigSourceLabel || "",
|
||||
llmPresetName: config.llmPresetName || "",
|
||||
@@ -1511,13 +1670,15 @@ async function callDedicatedOpenAICompatible(
|
||||
messages,
|
||||
transportMessages,
|
||||
generation: generationResolved.generation || {},
|
||||
filteredGeneration: generationResolved.filtered || {},
|
||||
filteredGeneration,
|
||||
removedGeneration: generationResolved.removed || [],
|
||||
capabilityMode: generationResolved.capabilityMode || "",
|
||||
streamForceDisabled: forceNonStream,
|
||||
effectiveRoute: buildEffectiveLlmRoute(
|
||||
hasDedicatedConfig,
|
||||
privateRequestSource,
|
||||
taskType,
|
||||
config,
|
||||
),
|
||||
maxCompletionTokens,
|
||||
...buildStreamDebugSnapshot(streamState),
|
||||
@@ -1546,30 +1707,19 @@ async function callDedicatedOpenAICompatible(
|
||||
: jsonMode
|
||||
? DEFAULT_JSON_COMPLETION_TOKENS
|
||||
: DEFAULT_TEXT_COMPLETION_TOKENS;
|
||||
const filteredGeneration = generationResolved.filtered || {};
|
||||
const resolvedCompletionTokens = Number.isFinite(
|
||||
filteredGeneration.max_completion_tokens,
|
||||
)
|
||||
? filteredGeneration.max_completion_tokens
|
||||
: completionTokens;
|
||||
|
||||
const body = {
|
||||
chat_completion_source: chat_completion_sources.CUSTOM,
|
||||
custom_url: config.apiUrl,
|
||||
custom_include_headers: config.apiKey
|
||||
? buildYamlObject({
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
})
|
||||
: "",
|
||||
model: config.model,
|
||||
messages: transportMessages,
|
||||
temperature: filteredGeneration.temperature ?? 1,
|
||||
max_tokens: resolvedCompletionTokens,
|
||||
stream: filteredGeneration.stream ?? false,
|
||||
frequency_penalty: filteredGeneration.frequency_penalty ?? 0,
|
||||
presence_penalty: filteredGeneration.presence_penalty ?? 0,
|
||||
top_p: filteredGeneration.top_p ?? 1,
|
||||
};
|
||||
const body = buildDedicatedRequestBody(
|
||||
config,
|
||||
transportMessages,
|
||||
filteredGeneration,
|
||||
resolvedCompletionTokens,
|
||||
{ jsonMode },
|
||||
);
|
||||
|
||||
const optionalGenerationFields = [
|
||||
"top_p",
|
||||
@@ -1596,12 +1746,8 @@ async function callDedicatedOpenAICompatible(
|
||||
body[field] = filteredGeneration[field];
|
||||
}
|
||||
|
||||
if (jsonMode && _jsonModeSupported) {
|
||||
body.custom_include_body = buildYamlObject({
|
||||
response_format: {
|
||||
type: "json_object",
|
||||
},
|
||||
});
|
||||
if (Object.prototype.hasOwnProperty.call(filteredGeneration, "request_thoughts")) {
|
||||
body.include_reasoning = Boolean(filteredGeneration.request_thoughts);
|
||||
}
|
||||
|
||||
recordTaskLlmRequest(taskKey, {
|
||||
@@ -1609,9 +1755,16 @@ async function callDedicatedOpenAICompatible(
|
||||
taskType: String(taskType || "").trim(),
|
||||
jsonMode,
|
||||
dedicatedConfig: true,
|
||||
route: "dedicated-openai-compatible",
|
||||
route: config.llmTransport || "dedicated-openai-compatible",
|
||||
routeLabel: config.llmTransportLabel || "",
|
||||
model: config.model,
|
||||
inputApiUrl: config.inputApiUrl || "",
|
||||
apiUrl: config.apiUrl,
|
||||
llmProvider: config.llmProvider || "",
|
||||
llmProviderLabel: config.llmProviderLabel || "",
|
||||
llmTransport: config.llmTransport || "",
|
||||
llmTransportLabel: config.llmTransportLabel || "",
|
||||
llmRouteMode: config.llmRouteMode || "",
|
||||
llmConfigSource: config.llmConfigSource || "global",
|
||||
llmConfigSourceLabel: config.llmConfigSourceLabel || "",
|
||||
llmPresetName: config.llmPresetName || "",
|
||||
@@ -1624,10 +1777,12 @@ async function callDedicatedOpenAICompatible(
|
||||
removedGeneration: generationResolved.removed || [],
|
||||
capabilityMode: generationResolved.capabilityMode || "",
|
||||
resolvedCompletionTokens,
|
||||
streamForceDisabled: forceNonStream,
|
||||
effectiveRoute: buildEffectiveLlmRoute(
|
||||
true,
|
||||
privateRequestSource,
|
||||
taskType,
|
||||
config,
|
||||
),
|
||||
requestBody: body,
|
||||
...buildStreamDebugSnapshot(streamState),
|
||||
@@ -1983,7 +2138,7 @@ export async function callLLM(systemPrompt, userPrompt, options = {}) {
|
||||
export async function testLLMConnection() {
|
||||
const config = getMemoryLLMConfig();
|
||||
const mode = hasDedicatedLLMConfig(config)
|
||||
? `dedicated:${config.model}`
|
||||
? `dedicated:${config.llmProviderLabel || config.llmTransportLabel || config.model}:${config.model}`
|
||||
: "sillytavern-current-model";
|
||||
|
||||
try {
|
||||
@@ -2013,7 +2168,22 @@ export async function fetchMemoryLLMModels() {
|
||||
};
|
||||
}
|
||||
|
||||
if (config.llmSupportsModelFetch !== true) {
|
||||
return {
|
||||
success: false,
|
||||
models: [],
|
||||
error: `${config.llmProviderLabel || "当前渠道"} 暂不支持自动拉取模型,请手动填写模型名`,
|
||||
};
|
||||
}
|
||||
|
||||
const variants = buildDedicatedStatusRequestVariants(config);
|
||||
if (!variants.length) {
|
||||
return {
|
||||
success: false,
|
||||
models: [],
|
||||
error: `${config.llmProviderLabel || "当前渠道"} 暂无可用的模型探测策略,请手动填写模型名`,
|
||||
};
|
||||
}
|
||||
const errors = [];
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user