mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-08 19:48:51 +01:00
🤖 feat(Anthropic): Claude 3 & Vision Support (#1984)
* chore: bump anthropic SDK * chore: update anthropic config settings (fileSupport, default models) * feat: anthropic multi modal formatting * refactor: update vision models and use endpoint specific max long side resizing * feat(anthropic): multimodal messages, retry logic, and messages payload * chore: add more safety to trimming content due to whitespace error for assistant messages * feat(anthropic): token accounting and resending multiple images in progress * chore: bump data-provider * feat(anthropic): resendImages feature * chore: optimize Edit/Ask controllers, switch model back to req model * fix: false positive of invalid model * refactor(validateVisionModel): use object as arg, pass in additional/available models * refactor(validateModel): use helper function, `getModelsConfig` * feat: add modelsConfig to endpointOption so it gets passed to all clients, use for properly validating vision models * refactor: initialize default vision model and make sure it's available before assigning it * refactor(useSSE): avoid resetting model if user selected a new model between request and response * feat: show rate in transaction logging * fix: return tokenCountMap regardless of payload shape
This commit is contained in:
parent
b023c5683d
commit
8263ddda3f
28 changed files with 599 additions and 115 deletions
|
|
@ -11,12 +11,13 @@ const { logger } = require('~/config');
|
|||
* Converts an image file to the WebP format. The function first resizes the image based on the specified
|
||||
* resolution.
|
||||
*
|
||||
*
|
||||
* @param {Express.Request} req - The request object from Express. It should have a `user` property with an `id`
|
||||
* @param {Object} params - The params object.
|
||||
* @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
||||
* @param {Express.Multer.File} file - The file object, which is part of the request. The file object should
|
||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||
* have a `path` property that points to the location of the uploaded file.
|
||||
* @param {string} [resolution='high'] - Optional. The desired resolution for the image resizing. Default is 'high'.
|
||||
* @param {EModelEndpoint} params.endpoint - The params object.
|
||||
* @param {string} [params.resolution='high'] - Optional. The desired resolution for the image resizing. Default is 'high'.
|
||||
*
|
||||
* @returns {Promise<{ filepath: string, bytes: number, width: number, height: number}>}
|
||||
* A promise that resolves to an object containing:
|
||||
|
|
@ -25,10 +26,14 @@ const { logger } = require('~/config');
|
|||
* - width: The width of the converted image.
|
||||
* - height: The height of the converted image.
|
||||
*/
|
||||
async function uploadImageToFirebase(req, file, resolution = 'high') {
|
||||
async function uploadImageToFirebase({ req, file, endpoint, resolution = 'high' }) {
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const { buffer: resizedBuffer, width, height } = await resizeImageBuffer(inputBuffer, resolution);
|
||||
const {
|
||||
buffer: resizedBuffer,
|
||||
width,
|
||||
height,
|
||||
} = await resizeImageBuffer(inputBuffer, resolution, endpoint);
|
||||
const extension = path.extname(inputFilePath);
|
||||
const userId = req.user.id;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ const { updateFile } = require('~/models/File');
|
|||
* it converts the image to WebP format before saving.
|
||||
*
|
||||
* The original image is deleted after conversion.
|
||||
*
|
||||
* @param {Object} req - The request object from Express. It should have a `user` property with an `id`
|
||||
* @param {Object} params - The params object.
|
||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
||||
* @param {Express.Multer.File} file - The file object, which is part of the request. The file object should
|
||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||
* have a `path` property that points to the location of the uploaded file.
|
||||
* @param {string} [resolution='high'] - Optional. The desired resolution for the image resizing. Default is 'high'.
|
||||
* @param {EModelEndpoint} params.endpoint - The params object.
|
||||
* @param {string} [params.resolution='high'] - Optional. The desired resolution for the image resizing. Default is 'high'.
|
||||
*
|
||||
* @returns {Promise<{ filepath: string, bytes: number, width: number, height: number}>}
|
||||
* A promise that resolves to an object containing:
|
||||
|
|
@ -27,10 +28,14 @@ const { updateFile } = require('~/models/File');
|
|||
* - width: The width of the converted image.
|
||||
* - height: The height of the converted image.
|
||||
*/
|
||||
async function uploadLocalImage(req, file, resolution = 'high') {
|
||||
async function uploadLocalImage({ req, file, endpoint, resolution = 'high' }) {
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const { buffer: resizedBuffer, width, height } = await resizeImageBuffer(inputBuffer, resolution);
|
||||
const {
|
||||
buffer: resizedBuffer,
|
||||
width,
|
||||
height,
|
||||
} = await resizeImageBuffer(inputBuffer, resolution, endpoint);
|
||||
const extension = path.extname(inputFilePath);
|
||||
|
||||
const { imageOutput } = req.app.locals.paths;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ async function fetchImageToBase64(url) {
|
|||
}
|
||||
}
|
||||
|
||||
const base64Only = new Set([EModelEndpoint.google, EModelEndpoint.anthropic]);
|
||||
|
||||
/**
|
||||
* Encodes and formats the given files.
|
||||
* @param {Express.Request} req - The request object.
|
||||
|
|
@ -50,7 +52,7 @@ async function encodeAndFormat(req, files, endpoint) {
|
|||
encodingMethods[source] = prepareImagePayload;
|
||||
|
||||
/* Google doesn't support passing URLs to payload */
|
||||
if (source !== FileSources.local && endpoint === EModelEndpoint.google) {
|
||||
if (source !== FileSources.local && base64Only.has(endpoint)) {
|
||||
const [_file, imageURL] = await prepareImagePayload(req, file);
|
||||
promises.push([_file, await fetchImageToBase64(imageURL)]);
|
||||
continue;
|
||||
|
|
@ -81,6 +83,14 @@ async function encodeAndFormat(req, files, endpoint) {
|
|||
|
||||
if (endpoint && endpoint === EModelEndpoint.google) {
|
||||
imagePart.image_url = imagePart.image_url.url;
|
||||
} else if (endpoint && endpoint === EModelEndpoint.anthropic) {
|
||||
imagePart.type = 'image';
|
||||
imagePart.source = {
|
||||
type: 'base64',
|
||||
media_type: file.type,
|
||||
data: imageContent,
|
||||
};
|
||||
delete imagePart.image_url;
|
||||
}
|
||||
|
||||
result.image_urls.push(imagePart);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const sharp = require('sharp');
|
||||
const { EModelEndpoint } = require('librechat-data-provider');
|
||||
|
||||
/**
|
||||
* Resizes an image from a given buffer based on the specified resolution.
|
||||
|
|
@ -7,13 +8,14 @@ const sharp = require('sharp');
|
|||
* @param {'low' | 'high'} resolution - The resolution to resize the image to.
|
||||
* 'low' for a maximum of 512x512 resolution,
|
||||
* 'high' for a maximum of 768x2000 resolution.
|
||||
* @param {EModelEndpoint} endpoint - Identifier for specific endpoint handling
|
||||
* @returns {Promise<{buffer: Buffer, width: number, height: number}>} An object containing the resized image buffer and its dimensions.
|
||||
* @throws Will throw an error if the resolution parameter is invalid.
|
||||
*/
|
||||
async function resizeImageBuffer(inputBuffer, resolution) {
|
||||
async function resizeImageBuffer(inputBuffer, resolution, endpoint) {
|
||||
const maxLowRes = 512;
|
||||
const maxShortSideHighRes = 768;
|
||||
const maxLongSideHighRes = 2000;
|
||||
const maxLongSideHighRes = endpoint === EModelEndpoint.anthropic ? 1568 : 2000;
|
||||
|
||||
let newWidth, newHeight;
|
||||
let resizeOptions = { fit: 'inside', withoutEnlargement: true };
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ const processFileURL = async ({ fileStrategy, userId, URL, fileName, basePath, c
|
|||
const processImageFile = async ({ req, res, file, metadata }) => {
|
||||
const source = req.app.locals.fileStrategy;
|
||||
const { handleImageUpload } = getStrategyFunctions(source);
|
||||
const { file_id, temp_file_id } = metadata;
|
||||
const { filepath, bytes, width, height } = await handleImageUpload(req, file);
|
||||
const { file_id, temp_file_id, endpoint } = metadata;
|
||||
const { filepath, bytes, width, height } = await handleImageUpload({ req, file, endpoint });
|
||||
const result = await createFile(
|
||||
{
|
||||
user: req.user.id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue