diff --git a/.env.example b/.env.example index a17cb64ed1f9..02a571b8391e 100644 --- a/.env.example +++ b/.env.example @@ -87,7 +87,6 @@ ANTHROPIC_API_KEY=user_provided # Azure # #============# - # Note: these variables are DEPRECATED # Use the `librechat.yaml` configuration for `azureOpenAI` instead # You may also continue to use them if you opt out of using the `librechat.yaml` configuration @@ -117,7 +116,7 @@ BINGAI_TOKEN=user_provided GOOGLE_KEY=user_provided # GOOGLE_REVERSE_PROXY= -# Gemini API +# Gemini API (AI Studio) # GOOGLE_MODELS=gemini-1.5-flash-latest,gemini-1.0-pro,gemini-1.0-pro-001,gemini-1.0-pro-latest,gemini-1.0-pro-vision-latest,gemini-1.5-pro-latest,gemini-pro,gemini-pro-vision # Vertex AI @@ -125,26 +124,30 @@ GOOGLE_KEY=user_provided # GOOGLE_TITLE_MODEL=gemini-pro -# Google Gemini Safety Settings -# NOTE (Vertex AI): You do not have access to the BLOCK_NONE setting by default. -# To use this restricted HarmBlockThreshold setting, you will need to either: +# Google Safety Settings +# NOTE: These settings apply to both Vertex AI and Gemini API (AI Studio) +# +# For Vertex AI: +# To use the BLOCK_NONE setting, you need either: +# (a) Access through an allowlist via your Google account team, or +# (b) Switch to monthly invoiced billing: https://cloud.google.com/billing/docs/how-to/invoiced-billing +# +# For Gemini API (AI Studio): +# BLOCK_NONE is available by default, no special account requirements. # -# (a) Get access through an allowlist via your Google account team -# (b) Switch your account type to monthly invoiced billing following this instruction: -# https://cloud.google.com/billing/docs/how-to/invoiced-billing +# Available options: BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE # # GOOGLE_SAFETY_SEXUALLY_EXPLICIT=BLOCK_ONLY_HIGH # GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH # GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH # GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH - #============# # OpenAI # #============# OPENAI_API_KEY=user_provided -# OPENAI_MODELS=gpt-4o,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k +# OPENAI_MODELS=gpt-4o,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-0301,gpt-3.5-turbo,gpt-4,gpt-4-0613,gpt-4-vision-preview,gpt-3.5-turbo-0613,gpt-3.5-turbo-16k-0613,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview,gpt-3.5-turbo-1106,gpt-3.5-turbo-instruct,gpt-3.5-turbo-instruct-0914,gpt-3.5-turbo-16k DEBUG_OPENAI=false @@ -166,7 +169,7 @@ DEBUG_OPENAI=false ASSISTANTS_API_KEY=user_provided # ASSISTANTS_BASE_URL= -# ASSISTANTS_MODELS=gpt-4o,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview +# ASSISTANTS_MODELS=gpt-4o,gpt-4o-mini,gpt-3.5-turbo-0125,gpt-3.5-turbo-16k-0613,gpt-3.5-turbo-16k,gpt-3.5-turbo,gpt-4,gpt-4-0314,gpt-4-32k-0314,gpt-4-0613,gpt-3.5-turbo-0613,gpt-3.5-turbo-1106,gpt-4-0125-preview,gpt-4-turbo-preview,gpt-4-1106-preview #==========================# # Azure Assistants API # @@ -188,7 +191,7 @@ ASSISTANTS_API_KEY=user_provided # Plugins # #============# -# PLUGIN_MODELS=gpt-4o,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613 +# PLUGIN_MODELS=gpt-4o,gpt-4o-mini,gpt-4,gpt-4-turbo-preview,gpt-4-0125-preview,gpt-4-1106-preview,gpt-4-0613,gpt-3.5-turbo,gpt-3.5-turbo-0125,gpt-3.5-turbo-1106,gpt-3.5-turbo-0613 DEBUG_PLUGINS=true @@ -261,7 +264,6 @@ MEILI_NO_ANALYTICS=true MEILI_HOST=http://0.0.0.0:7700 MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt - #==================================================# # Speech to Text & Text to Speech # #==================================================# @@ -269,6 +271,16 @@ MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt STT_API_KEY= TTS_API_KEY= +#==================================================# +# RAG # +#==================================================# +# More info: https://www.librechat.ai/docs/configuration/rag_api + +# RAG_OPENAI_BASEURL= +# RAG_OPENAI_API_KEY= +# EMBEDDINGS_PROVIDER=openai +# EMBEDDINGS_MODEL=text-embedding-3-small + #===================================================# # User System # #===================================================# @@ -374,6 +386,8 @@ LDAP_BIND_CREDENTIALS= LDAP_USER_SEARCH_BASE= LDAP_SEARCH_FILTER=mail={{username}} LDAP_CA_CERT_PATH= +# LDAP_TLS_REJECT_UNAUTHORIZED= +# LDAP_LOGIN_USES_USERNAME=true # LDAP_ID= # LDAP_USERNAME= # LDAP_FULL_NAME= @@ -411,6 +425,18 @@ FIREBASE_APP_ID= ALLOW_SHARED_LINKS=true ALLOW_SHARED_LINKS_PUBLIC=true +#==============================# +# Static File Cache Control # +#==============================# + +# Leave commented out to use default of 1 month for max-age and 1 week for s-maxage +# NODE_ENV must be set to production for these to take effect +# STATIC_CACHE_MAX_AGE=604800 +# STATIC_CACHE_S_MAX_AGE=259200 + +# If you have another service in front of your LibreChat doing compression, disable express based compression here +# DISABLE_COMPRESSION=true + #===================================================# # UI # #===================================================# diff --git a/.eslintrc.js b/.eslintrc.js index 58ee6d20a234..cbb34c74f245 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,7 @@ module.exports = { 'plugin:react-hooks/recommended', 'plugin:jest/recommended', 'prettier', + 'plugin:jsx-a11y/recommended', ], ignorePatterns: [ 'client/dist/**/*', @@ -32,7 +33,7 @@ module.exports = { jsx: true, }, }, - plugins: ['react', 'react-hooks', '@typescript-eslint', 'import'], + plugins: ['react', 'react-hooks', '@typescript-eslint', 'import', 'jsx-a11y'], rules: { 'react/react-in-jsx-scope': 'off', '@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow' }], @@ -65,6 +66,7 @@ module.exports = { 'no-restricted-syntax': 'off', 'react/prop-types': ['off'], 'react/display-name': ['off'], + 'no-nested-ternary': 'error', 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }], quotes: ['error', 'single'], }, @@ -118,6 +120,8 @@ module.exports = { ], rules: { '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unnecessary-condition': 'warn', + '@typescript-eslint/strict-boolean-expressions': 'warn', }, }, { diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml new file mode 100644 index 000000000000..e5a3be108ddd --- /dev/null +++ b/.github/workflows/a11y.yml @@ -0,0 +1,17 @@ +name: Lint for accessibility issues + +on: + pull_request: + paths: + - 'client/src/**' + +jobs: + axe-linter: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: dequelabs/axe-linter-action@v1 + with: + api_key: ${{ secrets.AXE_LINTER_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 000000000000..fc1c02db69f3 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,41 @@ +name: Update Test Server + +on: + workflow_run: + workflows: ["Docker Dev Images Build"] + types: + - completed + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + if: | + github.repository == 'danny-avila/LibreChat' && + (github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install SSH Key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.DO_SSH_PRIVATE_KEY }} + known_hosts: ${{ secrets.DO_KNOWN_HOSTS }} + + - name: Run update script on DigitalOcean Droplet + env: + DO_HOST: ${{ secrets.DO_HOST }} + DO_USER: ${{ secrets.DO_USER }} + run: | + ssh -o StrictHostKeyChecking=no ${DO_USER}@${DO_HOST} << EOF + sudo -i -u danny bash << EEOF + cd ~/LibreChat && \ + git fetch origin main && \ + npm run update:deployed && \ + git checkout do-deploy && \ + git rebase main && \ + npm run start:deployed && \ + echo "Update completed. Application should be running now." + EEOF + EOF diff --git a/.github/workflows/helmcharts.yml b/.github/workflows/helmcharts.yml new file mode 100644 index 000000000000..fcd8bc7df644 --- /dev/null +++ b/.github/workflows/helmcharts.yml @@ -0,0 +1,35 @@ +name: Build Helm Charts on Tag + +# The workflow is triggered when a tag is pushed +on: + push: + tags: + - "*" + +jobs: + release: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v4 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.6.0 + with: + charts_dir: helmchart + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1ace3200d51c..3a8f3484be3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# v0.7.3 +# v0.7.4 # Base node image FROM node:20-alpine AS node diff --git a/Dockerfile.multi b/Dockerfile.multi index aba396bd46fb..b4596daa0027 100644 --- a/Dockerfile.multi +++ b/Dockerfile.multi @@ -1,4 +1,4 @@ -# v0.7.3 +# v0.7.4 # Build API, Client and Data Provider FROM node:20-alpine AS base @@ -38,6 +38,6 @@ ENV HOST=0.0.0.0 CMD ["node", "server/index.js"] # Nginx setup -FROM nginx:1.21.1-alpine AS prod-stage +FROM nginx:1.27.0-alpine AS prod-stage COPY ./client/nginx.conf /etc/nginx/conf.d/default.conf CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 85a80e3ca4cf..93f80444ae13 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@
-
+
@@ -50,7 +50,7 @@
- 🔄 Edit, Resubmit, and Continue Messages with Conversation branching
- 🌿 Fork Messages & Conversations for Advanced Context control
- 💬 Multimodal Chat:
- - Upload and analyze images with Claude 3, GPT-4 (including `gpt-4o`), and Gemini Vision 📸
+ - Upload and analyze images with Claude 3, GPT-4 (including `gpt-4o` and `gpt-4o-mini`), and Gemini Vision 📸
- Chat with Files using Custom Endpoints, OpenAI, Azure, Anthropic, & Google. 🗃️
- Advanced Agents with Files, Code Interpreter, Tools, and API Actions 🔦
- Available through the [OpenAI Assistants API](https://platform.openai.com/docs/assistants/overview) 🌤️
diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js
index 2373a321f59c..3bc33af39819 100644
--- a/api/app/clients/AnthropicClient.js
+++ b/api/app/clients/AnthropicClient.js
@@ -2,8 +2,10 @@ const Anthropic = require('@anthropic-ai/sdk');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
const {
- getResponseSender,
+ Constants,
EModelEndpoint,
+ anthropicSettings,
+ getResponseSender,
validateVisionModel,
} = require('librechat-data-provider');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
@@ -16,6 +18,7 @@ const {
} = require('./prompts');
const spendTokens = require('~/models/spendTokens');
const { getModelMaxTokens } = require('~/utils');
+const { sleep } = require('~/server/utils');
const BaseClient = require('./BaseClient');
const { logger } = require('~/config');
@@ -29,6 +32,8 @@ function delayBeforeRetry(attempts, baseDelay = 1000) {
return new Promise((resolve) => setTimeout(resolve, baseDelay * attempts));
}
+const { legacy } = anthropicSettings;
+
class AnthropicClient extends BaseClient {
constructor(apiKey, options = {}) {
super(apiKey, options);
@@ -61,15 +66,20 @@ class AnthropicClient extends BaseClient {
const modelOptions = this.options.modelOptions || {};
this.modelOptions = {
...modelOptions,
- // set some good defaults (check for undefined in some cases because they may be 0)
- model: modelOptions.model || 'claude-1',
- temperature: typeof modelOptions.temperature === 'undefined' ? 1 : modelOptions.temperature, // 0 - 1, 1 is default
- topP: typeof modelOptions.topP === 'undefined' ? 0.7 : modelOptions.topP, // 0 - 1, default: 0.7
- topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40
- stop: modelOptions.stop, // no stop method for now
+ model: modelOptions.model || anthropicSettings.model.default,
};
this.isClaude3 = this.modelOptions.model.includes('claude-3');
+ this.isLegacyOutput = !this.modelOptions.model.includes('claude-3-5-sonnet');
+
+ if (
+ this.isLegacyOutput &&
+ this.modelOptions.maxOutputTokens &&
+ this.modelOptions.maxOutputTokens > legacy.maxOutputTokens.default
+ ) {
+ this.modelOptions.maxOutputTokens = legacy.maxOutputTokens.default;
+ }
+
this.useMessages = this.isClaude3 || !!this.options.attachments;
this.defaultVisionModel = this.options.visionModel ?? 'claude-3-sonnet-20240229';
@@ -119,10 +129,11 @@ class AnthropicClient extends BaseClient {
/**
* Get the initialized Anthropic client.
+ * @param {Partial