mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 08:20:14 +01:00
* refactor: move endpoint initialization methods to typescript * refactor: move agent init to packages/api - Introduced `initialize.ts` for agent initialization, including file processing and tool loading. - Updated `resources.ts` to allow optional appConfig parameter. - Enhanced endpoint configuration handling in various initialization files to support model parameters. - Added new artifacts and prompts for React component generation. - Refactored existing code to improve type safety and maintainability. * refactor: streamline endpoint initialization and enhance type safety - Updated initialization functions across various endpoints to use a consistent request structure, replacing `unknown` types with `ServerResponse`. - Simplified request handling by directly extracting keys from the request body. - Improved type safety by ensuring user IDs are safely accessed with optional chaining. - Removed unnecessary parameters and streamlined model options handling for better clarity and maintainability. * refactor: moved ModelService and extractBaseURL to packages/api - Added comprehensive tests for the models fetching functionality, covering scenarios for OpenAI, Anthropic, Google, and Ollama models. - Updated existing endpoint index to include the new models module. - Enhanced utility functions for URL extraction and model data processing. - Improved type safety and error handling across the models fetching logic. * refactor: consolidate utility functions and remove unused files - Merged `deriveBaseURL` and `extractBaseURL` into the `@librechat/api` module for better organization. - Removed redundant utility files and their associated tests to streamline the codebase. - Updated imports across various client files to utilize the new consolidated functions. - Enhanced overall maintainability by reducing the number of utility modules. * refactor: replace ModelService references with direct imports from @librechat/api and remove ModelService file * refactor: move encrypt/decrypt methods and key db methods to data-schemas, use `getProviderConfig` from `@librechat/api` * chore: remove unused 'res' from options in AgentClient * refactor: file model imports and methods - Updated imports in various controllers and services to use the unified file model from '~/models' instead of '~/models/File'. - Consolidated file-related methods into a new file methods module in the data-schemas package. - Added comprehensive tests for file methods including creation, retrieval, updating, and deletion. - Enhanced the initializeAgent function to accept dependency injection for file-related methods. - Improved error handling and logging in file methods. * refactor: streamline database method references in agent initialization * refactor: enhance file method tests and update type references to IMongoFile * refactor: consolidate database method imports in agent client and initialization * chore: remove redundant import of initializeAgent from @librechat/api * refactor: move checkUserKeyExpiry utility to @librechat/api and update references across endpoints * refactor: move updateUserPlugins logic to user.ts and simplify UserController * refactor: update imports for user key management and remove UserService * refactor: remove unused Anthropics and Bedrock endpoint files and clean up imports * refactor: consolidate and update encryption imports across various files to use @librechat/data-schemas * chore: update file model mock to use unified import from '~/models' * chore: import order * refactor: remove migrated to TS agent.js file and its associated logic from the endpoints * chore: add reusable function to extract imports from source code in unused-packages workflow * chore: enhance unused-packages workflow to include @librechat/api dependencies and improve dependency extraction * chore: improve dependency extraction in unused-packages workflow with enhanced error handling and debugging output * chore: add detailed debugging output to unused-packages workflow for better visibility into unused dependencies and exclusion lists * chore: refine subpath handling in unused-packages workflow to correctly process scoped and non-scoped package imports * chore: clean up unused debug output in unused-packages workflow and reorganize type imports in initialize.ts
167 lines
4.6 KiB
JavaScript
167 lines
4.6 KiB
JavaScript
const { z } = require('zod');
|
|
const axios = require('axios');
|
|
const { Ollama } = require('ollama');
|
|
const { sleep } = require('@librechat/agents');
|
|
const { logger } = require('@librechat/data-schemas');
|
|
const { Constants } = require('librechat-data-provider');
|
|
const { resolveHeaders, deriveBaseURL } = require('@librechat/api');
|
|
|
|
const ollamaPayloadSchema = z.object({
|
|
mirostat: z.number().optional(),
|
|
mirostat_eta: z.number().optional(),
|
|
mirostat_tau: z.number().optional(),
|
|
num_ctx: z.number().optional(),
|
|
repeat_last_n: z.number().optional(),
|
|
repeat_penalty: z.number().optional(),
|
|
temperature: z.number().optional(),
|
|
seed: z.number().nullable().optional(),
|
|
stop: z.array(z.string()).optional(),
|
|
tfs_z: z.number().optional(),
|
|
num_predict: z.number().optional(),
|
|
top_k: z.number().optional(),
|
|
top_p: z.number().optional(),
|
|
stream: z.optional(z.boolean()),
|
|
model: z.string(),
|
|
});
|
|
|
|
/**
|
|
* @param {string} imageUrl
|
|
* @returns {string}
|
|
* @throws {Error}
|
|
*/
|
|
const getValidBase64 = (imageUrl) => {
|
|
const parts = imageUrl.split(';base64,');
|
|
|
|
if (parts.length === 2) {
|
|
return parts[1];
|
|
} else {
|
|
logger.error('Invalid or no Base64 string found in URL.');
|
|
}
|
|
};
|
|
|
|
class OllamaClient {
|
|
constructor(options = {}) {
|
|
const host = deriveBaseURL(options.baseURL ?? 'http://localhost:11434');
|
|
this.streamRate = options.streamRate ?? Constants.DEFAULT_STREAM_RATE;
|
|
this.headers = options.headers ?? {};
|
|
/** @type {Ollama} */
|
|
this.client = new Ollama({ host });
|
|
}
|
|
|
|
/**
|
|
* Fetches Ollama models from the specified base API path.
|
|
* @param {string} baseURL
|
|
* @param {Object} [options] - Optional configuration
|
|
* @param {Partial<IUser>} [options.user] - User object for header resolution
|
|
* @param {Record<string, string>} [options.headers] - Headers to include in the request
|
|
* @returns {Promise<string[]>} The Ollama models.
|
|
* @throws {Error} Throws if the Ollama API request fails
|
|
*/
|
|
static async fetchModels(baseURL, options = {}) {
|
|
if (!baseURL) {
|
|
return [];
|
|
}
|
|
|
|
const ollamaEndpoint = deriveBaseURL(baseURL);
|
|
|
|
const resolvedHeaders = resolveHeaders({
|
|
headers: options.headers,
|
|
user: options.user,
|
|
});
|
|
|
|
/** @type {Promise<AxiosResponse<OllamaListResponse>>} */
|
|
const response = await axios.get(`${ollamaEndpoint}/api/tags`, {
|
|
headers: resolvedHeaders,
|
|
timeout: 5000,
|
|
});
|
|
|
|
const models = response.data.models.map((tag) => tag.name);
|
|
return models;
|
|
}
|
|
|
|
/**
|
|
* @param {ChatCompletionMessage[]} messages
|
|
* @returns {OllamaMessage[]}
|
|
*/
|
|
static formatOpenAIMessages(messages) {
|
|
const ollamaMessages = [];
|
|
|
|
for (const message of messages) {
|
|
if (typeof message.content === 'string') {
|
|
ollamaMessages.push({
|
|
role: message.role,
|
|
content: message.content,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
let aggregatedText = '';
|
|
let imageUrls = [];
|
|
|
|
for (const content of message.content) {
|
|
if (content.type === 'text') {
|
|
aggregatedText += content.text + ' ';
|
|
} else if (content.type === 'image_url') {
|
|
imageUrls.push(getValidBase64(content.image_url.url));
|
|
}
|
|
}
|
|
|
|
const ollamaMessage = {
|
|
role: message.role,
|
|
content: aggregatedText.trim(),
|
|
};
|
|
|
|
if (imageUrls.length > 0) {
|
|
ollamaMessage.images = imageUrls;
|
|
}
|
|
|
|
ollamaMessages.push(ollamaMessage);
|
|
}
|
|
|
|
return ollamaMessages;
|
|
}
|
|
|
|
/***
|
|
* @param {Object} params
|
|
* @param {ChatCompletionPayload} params.payload
|
|
* @param {onTokenProgress} params.onProgress
|
|
* @param {AbortController} params.abortController
|
|
*/
|
|
async chatCompletion({ payload, onProgress, abortController = null }) {
|
|
let intermediateReply = '';
|
|
|
|
const parameters = ollamaPayloadSchema.parse(payload);
|
|
const messages = OllamaClient.formatOpenAIMessages(payload.messages);
|
|
|
|
if (parameters.stream) {
|
|
const stream = await this.client.chat({
|
|
messages,
|
|
...parameters,
|
|
});
|
|
|
|
for await (const chunk of stream) {
|
|
const token = chunk.message.content;
|
|
intermediateReply += token;
|
|
onProgress(token);
|
|
if (abortController.signal.aborted) {
|
|
stream.controller.abort();
|
|
break;
|
|
}
|
|
|
|
await sleep(this.streamRate);
|
|
}
|
|
}
|
|
// TODO: regular completion
|
|
else {
|
|
// const generation = await this.client.generate(payload);
|
|
}
|
|
|
|
return intermediateReply;
|
|
}
|
|
catch(err) {
|
|
logger.error('[OllamaClient.chatCompletion]', err);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
module.exports = { OllamaClient, ollamaPayloadSchema };
|