| 1 | import { ChatAnthropic } from '@langchain/anthropic' |
| 2 | import type { BaseLanguageModel } from '@langchain/core/language_models/base' |
| 3 | import { ChatGoogleGenerativeAI } from '@langchain/google-genai' |
| 4 | import { ChatOpenAICompletions } from '@langchain/openai' |
| 5 | import log from 'electron-log/main.js' |
| 6 | import { |
| 7 | buildOpenAIModelOptions, |
| 8 | isOpenAIResponsesProvider, |
| 9 | normalizeOpenAIBaseUrl, |
| 10 | resolveOpenAIThinkingModelKwargs |
| 11 | } from './options' |
| 12 | import { CompatibleChatOpenAIResponses } from './responses-compat' |
| 13 | import { |
| 14 | getCurrentModelTemperatureControl, |
| 15 | isCurrentModelTemperatureEnabled, |
| 16 | resolveCurrentModelThinkingParameterMode, |
| 17 | resolveCurrentModelTemperatureOptions |
| 18 | } from './runtime' |
| 19 | import { ModelUsageCallbackHandler } from './usage' |
| 20 | import type { ModelRuntimeConfig } from './usage' |
| 21 | |
| 22 | export function resolveModel( |
| 23 | provider: string, |
| 24 | apiKey: string, |
| 25 | model: string, |
| 26 | baseUrl?: string, |
| 27 | temperature?: number, |
| 28 | maxTokens?: number, |
| 29 | runtime?: Pick<ModelRuntimeConfig, 'recorder'> |
| 30 | ): BaseLanguageModel { |
| 31 | const resolvedModel = model.trim() |
| 32 | if (!resolvedModel) { |
| 33 | throw new Error('model 不能为空,请先在系统设置中配置模型。') |
| 34 | } |
| 35 | const temperatureOptions = resolveCurrentModelTemperatureOptions(temperature) |
| 36 | const resolvedTemperature = temperatureOptions.temperature |
| 37 | const temperatureControl = getCurrentModelTemperatureControl() |
| 38 | const thinkingParameterMode = resolveCurrentModelThinkingParameterMode() |
| 39 | const resolvedBaseUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '' |
| 40 | const resolvedMaxTokens = maxTokens && maxTokens > 0 ? maxTokens : 4096 |
| 41 | const useOpenAIResponsesApi = isOpenAIResponsesProvider(provider) |
| 42 | const openAIProtocol = |
| 43 | provider === 'openai' ? 'chat-completions' : useOpenAIResponsesApi ? 'responses' : undefined |
| 44 | const openAIThinkingModelKwargs = |
| 45 | provider === 'openai' || provider === 'openai-responses' |
| 46 | ? resolveOpenAIThinkingModelKwargs({ |
| 47 | baseUrl: normalizeOpenAIBaseUrl(resolvedBaseUrl, useOpenAIResponsesApi), |
| 48 | useResponsesApi: useOpenAIResponsesApi, |
| 49 | thinkingParameterMode |
| 50 | }) |
| 51 | : {} |
| 52 | const usageCallback = new ModelUsageCallbackHandler({ |
| 53 | provider, |
| 54 | model: resolvedModel, |
| 55 | modelConfigId: temperatureControl?.modelConfigId |
| 56 | }, runtime?.recorder ?? null) |
| 57 | |
| 58 | log.info('[llm] resolveModel', { |
| 59 | provider, |
| 60 | model: resolvedModel, |
| 61 | baseUrl: resolvedBaseUrl, |
| 62 | temperature: resolvedTemperature ?? null, |
| 63 | temperatureEnabled: isCurrentModelTemperatureEnabled(), |
| 64 | temperatureControlBound: temperatureControl !== undefined, |
| 65 | modelConfigId: temperatureControl?.modelConfigId ?? null, |
| 66 | thinkingParameterMode, |
| 67 | maxTokens: resolvedMaxTokens, |
| 68 | openAIProtocol, |
| 69 | openAICompatibility: 'thinking' in openAIThinkingModelKwargs ? ['thinking.type=disabled'] : [] |
| 70 | }) |
| 71 | |
| 72 | switch (provider) { |
| 73 | case 'openai': |
| 74 | return new ChatOpenAICompletions({ |
| 75 | ...buildOpenAIModelOptions({ |
| 76 | model: resolvedModel, |
| 77 | apiKey, |
| 78 | baseUrl: resolvedBaseUrl, |
| 79 | temperatureOptions, |
| 80 | maxTokens: resolvedMaxTokens, |
| 81 | thinkingParameterMode |
| 82 | }), |
| 83 | callbacks: [usageCallback] |
| 84 | }) |
| 85 | case 'openai-responses': |
| 86 | return new CompatibleChatOpenAIResponses({ |
| 87 | ...buildOpenAIModelOptions({ |
| 88 | model: resolvedModel, |
| 89 | apiKey, |
| 90 | baseUrl: resolvedBaseUrl, |
| 91 | temperatureOptions, |
| 92 | maxTokens: resolvedMaxTokens, |
| 93 | useResponsesApi: true, |
| 94 | thinkingParameterMode |
| 95 | }), |
| 96 | callbacks: [usageCallback] |
| 97 | }) |
| 98 | case 'anthropic': |
| 99 | return new ChatAnthropic({ |
| 100 | model: resolvedModel, |
| 101 | apiKey, |
| 102 | ...temperatureOptions, |
| 103 | maxTokens: resolvedMaxTokens, |
| 104 | anthropicApiUrl: resolvedBaseUrl || undefined, |
| 105 | callbacks: [usageCallback] |
| 106 | }) |
| 107 | case 'google': |
| 108 | return new ChatGoogleGenerativeAI({ |
| 109 | model: resolvedModel, |
| 110 | apiKey, |
| 111 | ...temperatureOptions, |
| 112 | maxOutputTokens: resolvedMaxTokens, |
| 113 | baseUrl: resolvedBaseUrl || undefined, |
| 114 | callbacks: [usageCallback] |
| 115 | }) |
| 116 | default: |
| 117 | throw new Error(`Unknown provider: ${provider}`) |
| 118 | } |
| 119 | } |
| 120 |