| const { google } = require('googleapis'); |
| const { Agent, ProxyAgent } = require('undici'); |
| const { ChatVertexAI } = require('@langchain/google-vertexai'); |
| const { ChatGoogleGenerativeAI } = require('@langchain/google-genai'); |
| const { GoogleGenerativeAI: GenAI } = require('@google/generative-ai'); |
| const { GoogleVertexAI } = require('@langchain/community/llms/googlevertexai'); |
| const { ChatGoogleVertexAI } = require('langchain/chat_models/googlevertexai'); |
| const { AIMessage, HumanMessage, SystemMessage } = require('langchain/schema'); |
| const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken'); |
| const { |
| validateVisionModel, |
| getResponseSender, |
| endpointSettings, |
| EModelEndpoint, |
| VisionModes, |
| AuthKeys, |
| } = require('librechat-data-provider'); |
| const { encodeAndFormat } = require('~/server/services/Files/images'); |
| const { getModelMaxTokens } = require('~/utils'); |
| const { logger } = require('~/config'); |
| const { |
| formatMessage, |
| createContextHandlers, |
| titleInstruction, |
| truncateText, |
| } = require('./prompts'); |
| const BaseClient = require('./BaseClient'); |
|
|
| const loc = 'us-central1'; |
| const publisher = 'google'; |
| const endpointPrefix = `https://${loc}-aiplatform.googleapis.com`; |
| |
| const tokenizersCache = {}; |
|
|
| const settings = endpointSettings[EModelEndpoint.google]; |
|
|
| class GoogleClient extends BaseClient { |
| constructor(credentials, options = {}) { |
| super('apiKey', options); |
| let creds = {}; |
|
|
| if (typeof credentials === 'string') { |
| creds = JSON.parse(credentials); |
| } else if (credentials) { |
| creds = credentials; |
| } |
|
|
| const serviceKey = creds[AuthKeys.GOOGLE_SERVICE_KEY] ?? {}; |
| this.serviceKey = |
| serviceKey && typeof serviceKey === 'string' ? JSON.parse(serviceKey) : serviceKey ?? {}; |
| this.client_email = this.serviceKey.client_email; |
| this.private_key = this.serviceKey.private_key; |
| this.project_id = this.serviceKey.project_id; |
| this.access_token = null; |
|
|
| this.apiKey = creds[AuthKeys.GOOGLE_API_KEY]; |
|
|
| if (options.skipSetOptions) { |
| return; |
| } |
| this.setOptions(options); |
| } |
|
|
| |
| constructUrl() { |
| return `${endpointPrefix}/v1/projects/${this.project_id}/locations/${loc}/publishers/${publisher}/models/${this.modelOptions.model}:serverStreamingPredict`; |
| } |
|
|
| async getClient() { |
| const scopes = ['https://www.googleapis.com/auth/cloud-platform']; |
| const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes); |
|
|
| jwtClient.authorize((err) => { |
| if (err) { |
| logger.error('jwtClient failed to authorize', err); |
| throw err; |
| } |
| }); |
|
|
| return jwtClient; |
| } |
|
|
| async getAccessToken() { |
| const scopes = ['https://www.googleapis.com/auth/cloud-platform']; |
| const jwtClient = new google.auth.JWT(this.client_email, null, this.private_key, scopes); |
|
|
| return new Promise((resolve, reject) => { |
| jwtClient.authorize((err, tokens) => { |
| if (err) { |
| logger.error('jwtClient failed to authorize', err); |
| reject(err); |
| } else { |
| resolve(tokens.access_token); |
| } |
| }); |
| }); |
| } |
|
|
| |
| setOptions(options) { |
| if (this.options && !this.options.replaceOptions) { |
| |
| this.options.modelOptions = { |
| ...this.options.modelOptions, |
| ...options.modelOptions, |
| }; |
| delete options.modelOptions; |
| |
| this.options = { |
| ...this.options, |
| ...options, |
| }; |
| } else { |
| this.options = options; |
| } |
|
|
| this.options.examples = (this.options.examples ?? []) |
| .filter((ex) => ex) |
| .filter((obj) => obj.input.content !== '' && obj.output.content !== ''); |
|
|
| const modelOptions = this.options.modelOptions || {}; |
| this.modelOptions = { |
| ...modelOptions, |
| |
| model: modelOptions.model || settings.model.default, |
| temperature: |
| typeof modelOptions.temperature === 'undefined' |
| ? settings.temperature.default |
| : modelOptions.temperature, |
| topP: typeof modelOptions.topP === 'undefined' ? settings.topP.default : modelOptions.topP, |
| topK: typeof modelOptions.topK === 'undefined' ? settings.topK.default : modelOptions.topK, |
| |
| }; |
|
|
| this.options.attachments?.then((attachments) => this.checkVisionRequest(attachments)); |
|
|
| |
| this.isGenerativeModel = this.modelOptions.model.includes('gemini'); |
| const { isGenerativeModel } = this; |
| this.isChatModel = !isGenerativeModel && this.modelOptions.model.includes('chat'); |
| const { isChatModel } = this; |
| this.isTextModel = |
| !isGenerativeModel && !isChatModel && /code|text/.test(this.modelOptions.model); |
| const { isTextModel } = this; |
|
|
| this.maxContextTokens = |
| this.options.maxContextTokens ?? |
| getModelMaxTokens(this.modelOptions.model, EModelEndpoint.google); |
|
|
| |
| |
| this.maxResponseTokens = this.modelOptions.maxOutputTokens || settings.maxOutputTokens.default; |
|
|
| if (this.maxContextTokens > 32000) { |
| this.maxContextTokens = this.maxContextTokens - this.maxResponseTokens; |
| } |
|
|
| this.maxPromptTokens = |
| this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; |
|
|
| if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) { |
| throw new Error( |
| `maxPromptTokens + maxOutputTokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${ |
| this.maxPromptTokens + this.maxResponseTokens |
| }) must be less than or equal to maxContextTokens (${this.maxContextTokens})`, |
| ); |
| } |
|
|
| this.sender = |
| this.options.sender ?? |
| getResponseSender({ |
| model: this.modelOptions.model, |
| endpoint: EModelEndpoint.google, |
| modelLabel: this.options.modelLabel, |
| }); |
|
|
| this.userLabel = this.options.userLabel || 'User'; |
| this.modelLabel = this.options.modelLabel || 'Assistant'; |
|
|
| if (isChatModel || isGenerativeModel) { |
| |
| |
| |
| this.startToken = '||>'; |
| this.endToken = ''; |
| this.gptEncoder = this.constructor.getTokenizer('cl100k_base'); |
| } else if (isTextModel) { |
| this.startToken = '||>'; |
| this.endToken = ''; |
| this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true, { |
| '<|im_start|>': 100264, |
| '<|im_end|>': 100265, |
| }); |
| } else { |
| |
| |
| |
| this.startToken = '||>'; |
| this.endToken = ''; |
| try { |
| this.gptEncoder = this.constructor.getTokenizer(this.modelOptions.model, true); |
| } catch { |
| this.gptEncoder = this.constructor.getTokenizer('text-davinci-003', true); |
| } |
| } |
|
|
| if (!this.modelOptions.stop) { |
| const stopTokens = [this.startToken]; |
| if (this.endToken && this.endToken !== this.startToken) { |
| stopTokens.push(this.endToken); |
| } |
| stopTokens.push(`\n${this.userLabel}:`); |
| stopTokens.push('<|diff_marker|>'); |
| |
| this.modelOptions.stop = stopTokens; |
| } |
|
|
| if (this.options.reverseProxyUrl) { |
| this.completionsUrl = this.options.reverseProxyUrl; |
| } else { |
| this.completionsUrl = this.constructUrl(); |
| } |
|
|
| return this; |
| } |
|
|
| |
| |
| |
| |
| |
| checkVisionRequest(attachments) { |
| |
| this.defaultVisionModel = this.options.visionModel ?? 'gemini-pro-vision'; |
| const availableModels = this.options.modelsConfig?.[EModelEndpoint.google]; |
| this.isVisionModel = validateVisionModel({ model: this.modelOptions.model, availableModels }); |
|
|
| if ( |
| attachments && |
| attachments.some((file) => file?.type && file?.type?.includes('image')) && |
| availableModels?.includes(this.defaultVisionModel) && |
| !this.isVisionModel |
| ) { |
| this.modelOptions.model = this.defaultVisionModel; |
| this.isVisionModel = true; |
| } |
|
|
| if (this.isVisionModel && !attachments && this.modelOptions.model.includes('gemini-pro')) { |
| this.modelOptions.model = 'gemini-pro'; |
| this.isVisionModel = false; |
| } |
| } |
|
|
| formatMessages() { |
| return ((message) => ({ |
| author: message?.author ?? (message.isCreatedByUser ? this.userLabel : this.modelLabel), |
| content: message?.content ?? message.text, |
| })).bind(this); |
| } |
|
|
| |
| |
| |
| |
| |
| async formatGenerativeMessages(messages) { |
| const formattedMessages = []; |
| const attachments = await this.options.attachments; |
| const latestMessage = { ...messages[messages.length - 1] }; |
| const files = await this.addImageURLs(latestMessage, attachments, VisionModes.generative); |
| this.options.attachments = files; |
| messages[messages.length - 1] = latestMessage; |
|
|
| for (const _message of messages) { |
| const role = _message.isCreatedByUser ? this.userLabel : this.modelLabel; |
| const parts = []; |
| parts.push({ text: _message.text }); |
| if (!_message.image_urls?.length) { |
| formattedMessages.push({ role, parts }); |
| continue; |
| } |
|
|
| for (const images of _message.image_urls) { |
| if (images.inlineData) { |
| parts.push({ inlineData: images.inlineData }); |
| } |
| } |
|
|
| formattedMessages.push({ role, parts }); |
| } |
|
|
| return formattedMessages; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async addImageURLs(message, attachments, mode = '') { |
| const { files, image_urls } = await encodeAndFormat( |
| this.options.req, |
| attachments, |
| EModelEndpoint.google, |
| mode, |
| ); |
| message.image_urls = image_urls.length ? image_urls : undefined; |
| return files; |
| } |
|
|
| |
| |
| |
| |
| |
| async buildAugmentedPrompt(messages = []) { |
| const attachments = await this.options.attachments; |
| const latestMessage = { ...messages[messages.length - 1] }; |
| this.contextHandlers = createContextHandlers(this.options.req, latestMessage.text); |
|
|
| if (this.contextHandlers) { |
| for (const file of attachments) { |
| if (file.embedded) { |
| this.contextHandlers?.processFile(file); |
| continue; |
| } |
| } |
|
|
| this.augmentedPrompt = await this.contextHandlers.createContext(); |
| this.options.promptPrefix = this.augmentedPrompt + this.options.promptPrefix; |
| } |
| } |
|
|
| async buildVisionMessages(messages = [], parentMessageId) { |
| const attachments = await this.options.attachments; |
| const latestMessage = { ...messages[messages.length - 1] }; |
| await this.buildAugmentedPrompt(messages); |
|
|
| const { prompt } = await this.buildMessagesPrompt(messages, parentMessageId); |
|
|
| const files = await this.addImageURLs(latestMessage, attachments); |
|
|
| this.options.attachments = files; |
|
|
| latestMessage.text = prompt; |
|
|
| const payload = { |
| instances: [ |
| { |
| messages: [new HumanMessage(formatMessage({ message: latestMessage }))], |
| }, |
| ], |
| parameters: this.modelOptions, |
| }; |
| return { prompt: payload }; |
| } |
|
|
| |
| async buildGenerativeMessages(messages = []) { |
| this.userLabel = 'user'; |
| this.modelLabel = 'model'; |
| const promises = []; |
| promises.push(await this.formatGenerativeMessages(messages)); |
| promises.push(this.buildAugmentedPrompt(messages)); |
| const [formattedMessages] = await Promise.all(promises); |
| return { prompt: formattedMessages }; |
| } |
|
|
| async buildMessages(messages = [], parentMessageId) { |
| if (!this.isGenerativeModel && !this.project_id) { |
| throw new Error( |
| '[GoogleClient] a Service Account JSON Key is required for PaLM 2 and Codey models (Vertex AI)', |
| ); |
| } |
|
|
| if (!this.project_id && this.modelOptions.model.includes('1.5')) { |
| return await this.buildGenerativeMessages(messages); |
| } |
|
|
| if (this.options.attachments && this.isGenerativeModel) { |
| return this.buildVisionMessages(messages, parentMessageId); |
| } |
|
|
| if (this.isTextModel) { |
| return this.buildMessagesPrompt(messages, parentMessageId); |
| } |
|
|
| let payload = { |
| instances: [ |
| { |
| messages: messages |
| .map(this.formatMessages()) |
| .map((msg) => ({ ...msg, role: msg.author === 'User' ? 'user' : 'assistant' })) |
| .map((message) => formatMessage({ message, langChain: true })), |
| }, |
| ], |
| parameters: this.modelOptions, |
| }; |
|
|
| if (this.options.promptPrefix) { |
| payload.instances[0].context = this.options.promptPrefix; |
| } |
|
|
| if (this.options.examples.length > 0) { |
| payload.instances[0].examples = this.options.examples; |
| } |
|
|
| logger.debug('[GoogleClient] buildMessages', payload); |
|
|
| return { prompt: payload }; |
| } |
|
|
| async buildMessagesPrompt(messages, parentMessageId) { |
| const orderedMessages = this.constructor.getMessagesForConversation({ |
| messages, |
| parentMessageId, |
| }); |
|
|
| logger.debug('[GoogleClient]', { |
| orderedMessages, |
| parentMessageId, |
| }); |
|
|
| const formattedMessages = orderedMessages.map((message) => ({ |
| author: message.isCreatedByUser ? this.userLabel : this.modelLabel, |
| content: message?.content ?? message.text, |
| })); |
|
|
| let lastAuthor = ''; |
| let groupedMessages = []; |
|
|
| for (let message of formattedMessages) { |
| |
| if (lastAuthor !== message.author) { |
| groupedMessages.push({ |
| author: message.author, |
| content: [message.content], |
| }); |
| lastAuthor = message.author; |
| |
| } else { |
| groupedMessages[groupedMessages.length - 1].content.push(message.content); |
| } |
| } |
|
|
| let identityPrefix = ''; |
| if (this.options.userLabel) { |
| identityPrefix = `\nHuman's name: ${this.options.userLabel}`; |
| } |
|
|
| if (this.options.modelLabel) { |
| identityPrefix = `${identityPrefix}\nYou are ${this.options.modelLabel}`; |
| } |
|
|
| let promptPrefix = (this.options.promptPrefix || '').trim(); |
| if (promptPrefix) { |
| |
| if (!promptPrefix.endsWith(`${this.endToken}`)) { |
| promptPrefix = `${promptPrefix.trim()}${this.endToken}\n\n`; |
| } |
| promptPrefix = `\nContext:\n${promptPrefix}`; |
| } |
|
|
| if (identityPrefix) { |
| promptPrefix = `${identityPrefix}${promptPrefix}`; |
| } |
|
|
| |
| let isEdited = lastAuthor === this.modelLabel; |
| const promptSuffix = isEdited ? '' : `${promptPrefix}\n\n${this.modelLabel}:\n`; |
| let currentTokenCount = isEdited |
| ? this.getTokenCount(promptPrefix) |
| : this.getTokenCount(promptSuffix); |
|
|
| let promptBody = ''; |
| const maxTokenCount = this.maxPromptTokens; |
|
|
| const context = []; |
|
|
| |
| |
| |
| |
| const nextMessage = { |
| remove: false, |
| tokenCount: 0, |
| messageString: '', |
| }; |
|
|
| const buildPromptBody = async () => { |
| if (currentTokenCount < maxTokenCount && groupedMessages.length > 0) { |
| const message = groupedMessages.pop(); |
| const isCreatedByUser = message.author === this.userLabel; |
| |
| const messagePrefix = |
| isCreatedByUser || !isEdited |
| ? `\n\n${message.author}:` |
| : `${promptPrefix}\n\n${message.author}:`; |
| const messageString = `${messagePrefix}\n${message.content}${this.endToken}\n`; |
| let newPromptBody = `${messageString}${promptBody}`; |
|
|
| context.unshift(message); |
|
|
| const tokenCountForMessage = this.getTokenCount(messageString); |
| const newTokenCount = currentTokenCount + tokenCountForMessage; |
|
|
| if (!isCreatedByUser) { |
| nextMessage.messageString = messageString; |
| nextMessage.tokenCount = tokenCountForMessage; |
| } |
|
|
| if (newTokenCount > maxTokenCount) { |
| if (!promptBody) { |
| |
| throw new Error( |
| `Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`, |
| ); |
| } |
|
|
| |
| |
| if (isCreatedByUser) { |
| nextMessage.remove = true; |
| } |
|
|
| return false; |
| } |
| promptBody = newPromptBody; |
| currentTokenCount = newTokenCount; |
|
|
| |
| if (isEdited) { |
| isEdited = false; |
| } |
|
|
| |
| await new Promise((resolve) => setImmediate(resolve)); |
| return buildPromptBody(); |
| } |
| return true; |
| }; |
|
|
| await buildPromptBody(); |
|
|
| if (nextMessage.remove) { |
| promptBody = promptBody.replace(nextMessage.messageString, ''); |
| currentTokenCount -= nextMessage.tokenCount; |
| context.shift(); |
| } |
|
|
| let prompt = `${promptBody}${promptSuffix}`.trim(); |
|
|
| |
| currentTokenCount += 2; |
|
|
| |
| this.modelOptions.maxOutputTokens = Math.min( |
| this.maxContextTokens - currentTokenCount, |
| this.maxResponseTokens, |
| ); |
|
|
| return { prompt, context }; |
| } |
|
|
| async _getCompletion(payload, abortController = null) { |
| if (!abortController) { |
| abortController = new AbortController(); |
| } |
| const { debug } = this.options; |
| const url = this.completionsUrl; |
| if (debug) { |
| logger.debug('GoogleClient _getCompletion', { url, payload }); |
| } |
| const opts = { |
| method: 'POST', |
| agent: new Agent({ |
| bodyTimeout: 0, |
| headersTimeout: 0, |
| }), |
| signal: abortController.signal, |
| }; |
|
|
| if (this.options.proxy) { |
| opts.agent = new ProxyAgent(this.options.proxy); |
| } |
|
|
| const client = await this.getClient(); |
| const res = await client.request({ url, method: 'POST', data: payload }); |
| logger.debug('GoogleClient _getCompletion', { res }); |
| return res.data; |
| } |
|
|
| createLLM(clientOptions) { |
| const model = clientOptions.modelName ?? clientOptions.model; |
| if (this.project_id && this.isTextModel) { |
| logger.debug('Creating Google VertexAI client'); |
| return new GoogleVertexAI(clientOptions); |
| } else if (this.project_id && this.isChatModel) { |
| logger.debug('Creating Chat Google VertexAI client'); |
| return new ChatGoogleVertexAI(clientOptions); |
| } else if (this.project_id) { |
| logger.debug('Creating VertexAI client'); |
| return new ChatVertexAI(clientOptions); |
| } else if (model.includes('1.5')) { |
| logger.debug('Creating GenAI client'); |
| return new GenAI(this.apiKey).getGenerativeModel( |
| { |
| ...clientOptions, |
| model, |
| }, |
| { apiVersion: 'v1beta' }, |
| ); |
| } |
|
|
| logger.debug('Creating Chat Google Generative AI client'); |
| return new ChatGoogleGenerativeAI({ ...clientOptions, apiKey: this.apiKey }); |
| } |
|
|
| async getCompletion(_payload, options = {}) { |
| const { onProgress, abortController } = options; |
| const { parameters, instances } = _payload; |
| const { messages: _messages, context, examples: _examples } = instances?.[0] ?? {}; |
|
|
| let examples; |
|
|
| let clientOptions = { ...parameters, maxRetries: 2 }; |
|
|
| if (this.project_id) { |
| clientOptions['authOptions'] = { |
| credentials: { |
| ...this.serviceKey, |
| }, |
| projectId: this.project_id, |
| }; |
| } |
|
|
| if (!parameters) { |
| clientOptions = { ...clientOptions, ...this.modelOptions }; |
| } |
|
|
| if (this.isGenerativeModel && !this.project_id) { |
| clientOptions.modelName = clientOptions.model; |
| delete clientOptions.model; |
| } |
|
|
| if (_examples && _examples.length) { |
| examples = _examples |
| .map((ex) => { |
| const { input, output } = ex; |
| if (!input || !output) { |
| return undefined; |
| } |
| return { |
| input: new HumanMessage(input.content), |
| output: new AIMessage(output.content), |
| }; |
| }) |
| .filter((ex) => ex); |
|
|
| clientOptions.examples = examples; |
| } |
|
|
| const model = this.createLLM(clientOptions); |
|
|
| let reply = ''; |
| const messages = this.isTextModel ? _payload.trim() : _messages; |
|
|
| if (!this.isVisionModel && context && messages?.length > 0) { |
| messages.unshift(new SystemMessage(context)); |
| } |
|
|
| const modelName = clientOptions.modelName ?? clientOptions.model ?? ''; |
| if (modelName?.includes('1.5') && !this.project_id) { |
| |
| const client = model; |
| const requestOptions = { |
| contents: _payload, |
| }; |
|
|
| if (this.options?.promptPrefix?.length) { |
| requestOptions.systemInstruction = { |
| parts: [ |
| { |
| text: this.options.promptPrefix, |
| }, |
| ], |
| }; |
| } |
|
|
| const safetySettings = _payload.safetySettings; |
| requestOptions.safetySettings = safetySettings; |
|
|
| const delay = modelName.includes('flash') ? 8 : 14; |
| const result = await client.generateContentStream(requestOptions); |
| for await (const chunk of result.stream) { |
| const chunkText = chunk.text(); |
| await this.generateTextStream(chunkText, onProgress, { |
| delay, |
| }); |
| reply += chunkText; |
| } |
| return reply; |
| } |
|
|
| const safetySettings = _payload.safetySettings; |
| const stream = await model.stream(messages, { |
| signal: abortController.signal, |
| timeout: 7000, |
| safetySettings: safetySettings, |
| }); |
|
|
| let delay = this.isGenerativeModel ? 12 : 8; |
| if (modelName.includes('flash')) { |
| delay = 5; |
| } |
| for await (const chunk of stream) { |
| const chunkText = chunk?.content ?? chunk; |
| await this.generateTextStream(chunkText, onProgress, { |
| delay, |
| }); |
| reply += chunkText; |
| } |
|
|
| return reply; |
| } |
|
|
| |
| |
| |
| async titleChatCompletion(_payload, options = {}) { |
| const { abortController } = options; |
| const { parameters, instances } = _payload; |
| const { messages: _messages, examples: _examples } = instances?.[0] ?? {}; |
|
|
| let clientOptions = { ...parameters, maxRetries: 2 }; |
|
|
| logger.debug('Initialized title client options'); |
|
|
| if (this.project_id) { |
| clientOptions['authOptions'] = { |
| credentials: { |
| ...this.serviceKey, |
| }, |
| projectId: this.project_id, |
| }; |
| } |
|
|
| if (!parameters) { |
| clientOptions = { ...clientOptions, ...this.modelOptions }; |
| } |
|
|
| if (this.isGenerativeModel && !this.project_id) { |
| clientOptions.modelName = clientOptions.model; |
| delete clientOptions.model; |
| } |
|
|
| const model = this.createLLM(clientOptions); |
|
|
| let reply = ''; |
| const messages = this.isTextModel ? _payload.trim() : _messages; |
|
|
| const modelName = clientOptions.modelName ?? clientOptions.model ?? ''; |
| if (modelName?.includes('1.5') && !this.project_id) { |
| logger.debug('Identified titling model as 1.5 version'); |
| |
| const client = model; |
| const requestOptions = { |
| contents: _payload, |
| }; |
|
|
| if (this.options?.promptPrefix?.length) { |
| requestOptions.systemInstruction = { |
| parts: [ |
| { |
| text: this.options.promptPrefix, |
| }, |
| ], |
| }; |
| } |
|
|
| const safetySettings = _payload.safetySettings; |
| requestOptions.safetySettings = safetySettings; |
|
|
| const result = await client.generateContent(requestOptions); |
|
|
| reply = result.response?.text(); |
|
|
| return reply; |
| } else { |
| logger.debug('Beginning titling'); |
| const safetySettings = _payload.safetySettings; |
|
|
| const titleResponse = await model.invoke(messages, { |
| signal: abortController.signal, |
| timeout: 7000, |
| safetySettings: safetySettings, |
| }); |
|
|
| reply = titleResponse.content; |
|
|
| return reply; |
| } |
| } |
|
|
| async titleConvo({ text, responseText = '' }) { |
| let title = 'New Chat'; |
| const convo = `||>User: |
| "${truncateText(text)}" |
| ||>Response: |
| "${JSON.stringify(truncateText(responseText))}"`; |
|
|
| let { prompt: payload } = await this.buildMessages([ |
| { |
| text: `Please generate ${titleInstruction} |
| |
| ${convo} |
| |
| ||>Title:`, |
| isCreatedByUser: true, |
| author: this.userLabel, |
| }, |
| ]); |
|
|
| if (this.isVisionModel) { |
| logger.warn( |
| `Current vision model does not support titling without an attachment; falling back to default model ${settings.model.default}`, |
| ); |
|
|
| payload.parameters = { ...payload.parameters, model: settings.model.default }; |
| } |
|
|
| try { |
| title = await this.titleChatCompletion(payload, { |
| abortController: new AbortController(), |
| onProgress: () => {}, |
| }); |
| } catch (e) { |
| logger.error('[GoogleClient] There was an issue generating the title', e); |
| } |
| logger.debug(`Title response: ${title}`); |
| return title; |
| } |
|
|
| getSaveOptions() { |
| return { |
| promptPrefix: this.options.promptPrefix, |
| modelLabel: this.options.modelLabel, |
| iconURL: this.options.iconURL, |
| greeting: this.options.greeting, |
| spec: this.options.spec, |
| ...this.modelOptions, |
| }; |
| } |
|
|
| getBuildMessagesOptions() { |
| |
| } |
|
|
| async sendCompletion(payload, opts = {}) { |
| const modelName = payload.parameters?.model; |
|
|
| if (modelName && modelName.toLowerCase().includes('gemini')) { |
| const safetySettings = [ |
| { |
| category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', |
| threshold: |
| process.env.GOOGLE_SAFETY_SEXUALLY_EXPLICIT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', |
| }, |
| { |
| category: 'HARM_CATEGORY_HATE_SPEECH', |
| threshold: process.env.GOOGLE_SAFETY_HATE_SPEECH || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', |
| }, |
| { |
| category: 'HARM_CATEGORY_HARASSMENT', |
| threshold: process.env.GOOGLE_SAFETY_HARASSMENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', |
| }, |
| { |
| category: 'HARM_CATEGORY_DANGEROUS_CONTENT', |
| threshold: |
| process.env.GOOGLE_SAFETY_DANGEROUS_CONTENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED', |
| }, |
| ]; |
|
|
| payload.safetySettings = safetySettings; |
| } |
|
|
| let reply = ''; |
| reply = await this.getCompletion(payload, opts); |
| return reply.trim(); |
| } |
|
|
| |
| static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) { |
| if (tokenizersCache[encoding]) { |
| return tokenizersCache[encoding]; |
| } |
| let tokenizer; |
| if (isModelName) { |
| tokenizer = encodingForModel(encoding, extendSpecialTokens); |
| } else { |
| tokenizer = getEncoding(encoding, extendSpecialTokens); |
| } |
| tokenizersCache[encoding] = tokenizer; |
| return tokenizer; |
| } |
|
|
| getTokenCount(text) { |
| return this.gptEncoder.encode(text, 'all').length; |
| } |
| } |
|
|
| module.exports = GoogleClient; |
|
|