diff --git a/llm/llm.js b/llm/llm.js index 4506cd4..d4c892d 100644 --- a/llm/llm.js +++ b/llm/llm.js @@ -1962,10 +1962,20 @@ async function callDedicatedOpenAICompatible( const transportMessages = buildTransportMessages(messages); const config = getMemoryLLMConfig(taskType); const hostRouting = resolveHostChatCompletionRouting(taskType, { - profileName: "", + profileName: config.requestedLlmPresetName || "", }); const settings = extension_settings[MODULE_NAME] || {}; - const hasDedicatedConfig = hasDedicatedLLMConfig(config); + const shouldPreferLukerHostRoute = + hostRouting.hostProfile === "luker" && + ( + config.llmConfigSource === "global" || + ( + String(config.llmConfigSource || "").startsWith("global-fallback-") && + hostRouting.routeApplied === true + ) + ); + const hasDedicatedConfig = + hasDedicatedLLMConfig(config) && !shouldPreferLukerHostRoute; if (taskType && config.llmPresetFallbackReason) { debugWarn( `[ST-BME] 任务 ${taskType} 指定的 API 模板不可用,已回退当前 API: ` + @@ -2042,6 +2052,7 @@ async function callDedicatedOpenAICompatible( hostRequestApi: hostRouting.requestApi, hostRouteApplied: hostRouting.routeApplied, hostRouteReason: hostRouting.routeReason, + preferHostRoute: shouldPreferLukerHostRoute, apiSettingsOverride: hostRouting.apiSettingsOverride, maxCompletionTokens, ...buildStreamDebugSnapshot(streamState), diff --git a/tests/luker-llm-routing.mjs b/tests/luker-llm-routing.mjs index 4a327be..808e462 100644 --- a/tests/luker-llm-routing.mjs +++ b/tests/luker-llm-routing.mjs @@ -63,6 +63,7 @@ globalThis.__lukerLlmRoutingExtensionSettings = { globalThis.require = require; const llm = await import("../llm/llm.js"); +const { createDefaultTaskProfiles } = await import("../prompting/prompt-profiles.js"); const extensionsApi = await import("../../../../extensions.js"); if (originalRequire === undefined) { @@ -124,7 +125,9 @@ globalThis.__lukerLlmRoutingSendOpenAIRequest = async ( }; }; -extensionsApi.extension_settings.st_bme = {}; +extensionsApi.extension_settings.st_bme = { + taskProfiles: createDefaultTaskProfiles(), +}; try { const result = await llm.callLLMForJSON({ @@ -145,6 +148,81 @@ try { proxy_password: "sk-luker-route", secret_id: "luker-secret-1", }); + + capturedOptions = null; + capturedMessages = null; + extensionsApi.extension_settings.st_bme = { + llmApiUrl: "https://stale-generic-config.invalid/v1", + llmApiKey: "sk-stale-generic", + llmModel: "stale-model", + taskProfiles: createDefaultTaskProfiles(), + }; + + const routedResult = await llm.callLLMForJSON({ + systemPrompt: "system", + userPrompt: "user", + maxRetries: 0, + taskType: "extract", + requestSource: "test:luker-global-stale", + }); + + assert.deepEqual(routedResult, { operations: [] }); + assert.deepEqual(capturedOptions?.apiSettingsOverride, { + chat_completion_source: "openai", + reverse_proxy: "https://example-luker-route.test/v1", + proxy_password: "sk-luker-route", + secret_id: "luker-secret-1", + }); + + capturedOptions = null; + capturedMessages = null; + const taskProfiles = createDefaultTaskProfiles(); + taskProfiles.extract.profiles[0].generation.llm_preset = "luker-profile-alpha"; + extensionsApi.extension_settings.st_bme = { + llmApiUrl: "https://stale-generic-config.invalid/v1", + llmApiKey: "sk-stale-generic", + llmModel: "stale-model", + taskProfiles, + }; + globalThis.Luker = { + getContext() { + return { + mainApi: "openai", + chatCompletionSettings: { + chat_completion_source: "openai", + }, + getChatState() {}, + updateChatState() {}, + getChatStateBatch() {}, + resolveChatCompletionRequestProfile({ profileName }) { + assert.equal(profileName, "luker-profile-alpha"); + return { + requestApi: "openai", + apiSettingsOverride: { + chat_completion_source: "openai", + reverse_proxy: "https://example-luker-profile.test/v1", + proxy_password: "sk-luker-profile", + }, + }; + }, + }; + }, + }; + + const profileRoutedResult = await llm.callLLMForJSON({ + systemPrompt: "system", + userPrompt: "user", + maxRetries: 0, + taskType: "extract", + requestSource: "test:luker-profile-route", + }); + + assert.deepEqual(profileRoutedResult, { operations: [] }); + assert.deepEqual(capturedOptions?.apiSettingsOverride, { + chat_completion_source: "openai", + reverse_proxy: "https://example-luker-profile.test/v1", + proxy_password: "sk-luker-profile", + }); } finally { if (originalSendOpenAIRequest === undefined) { delete globalThis.__lukerLlmRoutingSendOpenAIRequest;