Skip to content

Commit a2636bf

Browse files
authored
Merge pull request #1 from danny-avila/main
Catchup with upstream
2 parents 51050cc + 79c1783 commit a2636bf

File tree

271 files changed

+8105
-3588
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

271 files changed

+8105
-3588
lines changed

.devcontainer/docker-compose.yml

-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ services:
5757
# ports:
5858
# - 7700:7700 # if exposing these ports, make sure your master key is not the default value
5959
environment:
60-
- MEILI_HTTP_ADDR=meilisearch:7700
6160
- MEILI_NO_ANALYTICS=true
6261
- MEILI_MASTER_KEY=5c71cf56d672d009e36070b5bc5e47b743535ae55c818ae3b735bb6ebfb4ba63
6362
volumes:

.dockerignore

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
**/.circleci
2+
**/.editorconfig
3+
**/.dockerignore
4+
**/.git
5+
**/.DS_Store
6+
**/.vscode
17
**/node_modules
2-
client/dist/images
8+
9+
# Specific patterns to ignore
310
data-node
4-
.env
5-
**/.env
11+
meili_data*
12+
librechat*
13+
Dockerfile*
14+
docs
15+
16+
# Ignore all hidden files
17+
.*

.env.example

+18-1
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ MONGO_URI=mongodb://127.0.0.1:27017/LibreChat
2424
DOMAIN_CLIENT=http://localhost:3080
2525
DOMAIN_SERVER=http://localhost:3080
2626

27+
NO_INDEX=true
28+
2729
#===============#
2830
# Debug Logging #
2931
#===============#
32+
3033
DEBUG_LOGGING=true
3134
DEBUG_CONSOLE=false
3235

@@ -174,7 +177,6 @@ ZAPIER_NLA_API_KEY=
174177
SEARCH=true
175178
MEILI_NO_ANALYTICS=true
176179
MEILI_HOST=http://0.0.0.0:7700
177-
MEILI_HTTP_ADDR=0.0.0.0:7700
178180
MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
179181

180182
#===================================================#
@@ -185,6 +187,10 @@ MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
185187
# Moderation #
186188
#========================#
187189

190+
OPENAI_MODERATION=false
191+
OPENAI_MODERATION_API_KEY=
192+
# OPENAI_MODERATION_REVERSE_PROXY=not working with some reverse proxys
193+
188194
BAN_VIOLATIONS=true
189195
BAN_DURATION=1000 * 60 * 60 * 2
190196
BAN_INTERVAL=20
@@ -278,6 +284,17 @@ EMAIL_PASSWORD=
278284
EMAIL_FROM_NAME=
279285
EMAIL_FROM=noreply@librechat.ai
280286

287+
#========================#
288+
# Firebase CDN #
289+
#========================#
290+
291+
FIREBASE_API_KEY=
292+
FIREBASE_AUTH_DOMAIN=
293+
FIREBASE_PROJECT_ID=
294+
FIREBASE_STORAGE_BUCKET=
295+
FIREBASE_MESSAGING_SENDER_ID=
296+
FIREBASE_APP_ID=
297+
281298
#==================================================#
282299
# Others #
283300
#==================================================#

.github/pull_request_template.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Please delete any irrelevant options.
1515
- [ ] New feature (non-breaking change which adds functionality)
1616
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
1717
- [ ] This change requires a documentation update
18-
- [ ] Documentation update
18+
- [ ] Documentation update
19+
- [ ] Translation update
1920

2021
## Testing
2122

.github/workflows/mkdocs.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ jobs:
2222
mkdocs-material-
2323
- run: pip install mkdocs-material
2424
- run: pip install mkdocs-nav-weight
25+
- run: pip install mkdocs-publisher
26+
- run: pip install mkdocs-exclude
2527
- run: mkdocs gh-deploy --force

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ bower_components/
4848
.floo
4949
.flooignore
5050

51+
#config file
52+
librechat.yaml
53+
5154
# Environment
5255
.npmrc
5356
.env*
@@ -82,4 +85,6 @@ data.ms/*
8285
auth.json
8386

8487
/packages/ux-shared/
85-
/images
88+
/images
89+
90+
!client/src/components/Nav/SettingsTabs/Data/

Dockerfile

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# Base node image
2-
FROM node:19-alpine AS node
2+
FROM node:18-alpine AS node
33

44
COPY . /app
55
WORKDIR /app
66

7+
# Allow mounting of these files, which have no default
8+
# values.
9+
RUN touch .env
710
# Install call deps - Install curl for health check
811
RUN apk --no-cache add curl && \
9-
# We want to inherit env from the container, not the file
10-
# This will preserve any existing env file if it's already in source
11-
# otherwise it will create a new one
12-
touch .env && \
13-
# Build deps in seperate
1412
npm ci
1513

1614
# React client build

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 LibreChat
3+
Copyright (c) 2024 LibreChat
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

api/app/clients/BaseClient.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,11 @@ class BaseClient {
516516
}
517517

518518
async saveMessageToDatabase(message, endpointOptions, user = null) {
519-
await saveMessage({ ...message, user, unfinished: false, cancelled: false });
519+
await saveMessage({ ...message, endpoint: this.options.endpoint, user, unfinished: false });
520520
await saveConvo(user, {
521521
conversationId: message.conversationId,
522522
endpoint: this.options.endpoint,
523+
endpointType: this.options.endpointType,
523524
...endpointOptions,
524525
});
525526
}

api/app/clients/OpenAIClient.js

+99-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const OpenAI = require('openai');
22
const { HttpsProxyAgent } = require('https-proxy-agent');
3-
const { getResponseSender, EModelEndpoint } = require('librechat-data-provider');
3+
const { getResponseSender } = require('librechat-data-provider');
44
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('tiktoken');
55
const { encodeAndFormat, validateVisionModel } = require('~/server/services/Files/images');
66
const { getModelMaxTokens, genAzureChatCompletion, extractBaseURL } = require('~/utils');
@@ -94,10 +94,23 @@ class OpenAIClient extends BaseClient {
9494
}
9595

9696
const { reverseProxyUrl: reverseProxy } = this.options;
97+
98+
if (
99+
!this.useOpenRouter &&
100+
reverseProxy &&
101+
reverseProxy.includes('https://openrouter.ai/api/v1')
102+
) {
103+
this.useOpenRouter = true;
104+
}
105+
97106
this.FORCE_PROMPT =
98107
isEnabled(OPENAI_FORCE_PROMPT) ||
99108
(reverseProxy && reverseProxy.includes('completions') && !reverseProxy.includes('chat'));
100109

110+
if (typeof this.options.forcePrompt === 'boolean') {
111+
this.FORCE_PROMPT = this.options.forcePrompt;
112+
}
113+
101114
if (this.azure && process.env.AZURE_OPENAI_DEFAULT_MODEL) {
102115
this.azureEndpoint = genAzureChatCompletion(this.azure, this.modelOptions.model);
103116
this.modelOptions.model = process.env.AZURE_OPENAI_DEFAULT_MODEL;
@@ -146,8 +159,10 @@ class OpenAIClient extends BaseClient {
146159
this.options.sender ??
147160
getResponseSender({
148161
model: this.modelOptions.model,
149-
endpoint: EModelEndpoint.openAI,
162+
endpoint: this.options.endpoint,
163+
endpointType: this.options.endpointType,
150164
chatGptLabel: this.options.chatGptLabel,
165+
modelDisplayLabel: this.options.modelDisplayLabel,
151166
});
152167

153168
this.userLabel = this.options.userLabel || 'User';
@@ -434,7 +449,7 @@ class OpenAIClient extends BaseClient {
434449
},
435450
opts.abortController || new AbortController(),
436451
);
437-
} else if (typeof opts.onProgress === 'function') {
452+
} else if (typeof opts.onProgress === 'function' || this.options.useChatCompletion) {
438453
reply = await this.chatCompletion({
439454
payload,
440455
clientOptions: opts,
@@ -530,6 +545,19 @@ class OpenAIClient extends BaseClient {
530545
return llm;
531546
}
532547

548+
/**
549+
* Generates a concise title for a conversation based on the user's input text and response.
550+
* Uses either specified method or starts with the OpenAI `functions` method (using LangChain).
551+
* If the `functions` method fails, it falls back to the `completion` method,
552+
* which involves sending a chat completion request with specific instructions for title generation.
553+
*
554+
* @param {Object} params - The parameters for the conversation title generation.
555+
* @param {string} params.text - The user's input.
556+
* @param {string} [params.responseText=''] - The AI's immediate response to the user.
557+
*
558+
* @returns {Promise<string | 'New Chat'>} A promise that resolves to the generated conversation title.
559+
* In case of failure, it will return the default title, "New Chat".
560+
*/
533561
async titleConvo({ text, responseText = '' }) {
534562
let title = 'New Chat';
535563
const convo = `||>User:
@@ -539,32 +567,25 @@ class OpenAIClient extends BaseClient {
539567

540568
const { OPENAI_TITLE_MODEL } = process.env ?? {};
541569

570+
const model = this.options.titleModel ?? OPENAI_TITLE_MODEL ?? 'gpt-3.5-turbo';
571+
542572
const modelOptions = {
543-
model: OPENAI_TITLE_MODEL ?? 'gpt-3.5-turbo',
573+
// TODO: remove the gpt fallback and make it specific to endpoint
574+
model,
544575
temperature: 0.2,
545576
presence_penalty: 0,
546577
frequency_penalty: 0,
547578
max_tokens: 16,
548579
};
549580

550-
try {
551-
this.abortController = new AbortController();
552-
const llm = this.initializeLLM({ ...modelOptions, context: 'title', tokenBuffer: 150 });
553-
title = await runTitleChain({ llm, text, convo, signal: this.abortController.signal });
554-
} catch (e) {
555-
if (e?.message?.toLowerCase()?.includes('abort')) {
556-
logger.debug('[OpenAIClient] Aborted title generation');
557-
return;
558-
}
559-
logger.error(
560-
'[OpenAIClient] There was an issue generating title with LangChain, trying the old method...',
561-
e,
562-
);
563-
modelOptions.model = OPENAI_TITLE_MODEL ?? 'gpt-3.5-turbo';
581+
const titleChatCompletion = async () => {
582+
modelOptions.model = model;
583+
564584
if (this.azure) {
565585
modelOptions.model = process.env.AZURE_OPENAI_DEFAULT_MODEL ?? modelOptions.model;
566586
this.azureEndpoint = genAzureChatCompletion(this.azure, modelOptions.model);
567587
}
588+
568589
const instructionsPayload = [
569590
{
570591
role: 'system',
@@ -578,10 +599,38 @@ ${convo}
578599
];
579600

580601
try {
581-
title = (await this.sendPayload(instructionsPayload, { modelOptions })).replaceAll('"', '');
602+
title = (
603+
await this.sendPayload(instructionsPayload, { modelOptions, useChatCompletion: true })
604+
).replaceAll('"', '');
582605
} catch (e) {
583-
logger.error('[OpenAIClient] There was another issue generating the title', e);
606+
logger.error(
607+
'[OpenAIClient] There was an issue generating the title with the completion method',
608+
e,
609+
);
584610
}
611+
};
612+
613+
if (this.options.titleMethod === 'completion') {
614+
await titleChatCompletion();
615+
logger.debug('[OpenAIClient] Convo Title: ' + title);
616+
return title;
617+
}
618+
619+
try {
620+
this.abortController = new AbortController();
621+
const llm = this.initializeLLM({ ...modelOptions, context: 'title', tokenBuffer: 150 });
622+
title = await runTitleChain({ llm, text, convo, signal: this.abortController.signal });
623+
} catch (e) {
624+
if (e?.message?.toLowerCase()?.includes('abort')) {
625+
logger.debug('[OpenAIClient] Aborted title generation');
626+
return;
627+
}
628+
logger.error(
629+
'[OpenAIClient] There was an issue generating title with LangChain, trying completion method...',
630+
e,
631+
);
632+
633+
await titleChatCompletion();
585634
}
586635

587636
logger.debug('[OpenAIClient] Convo Title: ' + title);
@@ -593,8 +642,11 @@ ${convo}
593642
let context = messagesToRefine;
594643
let prompt;
595644

645+
// TODO: remove the gpt fallback and make it specific to endpoint
596646
const { OPENAI_SUMMARY_MODEL = 'gpt-3.5-turbo' } = process.env ?? {};
597-
const maxContextTokens = getModelMaxTokens(OPENAI_SUMMARY_MODEL) ?? 4095;
647+
const model = this.options.summaryModel ?? OPENAI_SUMMARY_MODEL;
648+
const maxContextTokens = getModelMaxTokens(model) ?? 4095;
649+
598650
// 3 tokens for the assistant label, and 98 for the summarizer prompt (101)
599651
let promptBuffer = 101;
600652

@@ -644,7 +696,7 @@ ${convo}
644696
logger.debug('[OpenAIClient] initialPromptTokens', initialPromptTokens);
645697

646698
const llm = this.initializeLLM({
647-
model: OPENAI_SUMMARY_MODEL,
699+
model,
648700
temperature: 0.2,
649701
context: 'summary',
650702
tokenBuffer: initialPromptTokens,
@@ -719,7 +771,9 @@ ${convo}
719771
if (!abortController) {
720772
abortController = new AbortController();
721773
}
722-
const modelOptions = { ...this.modelOptions };
774+
775+
let modelOptions = { ...this.modelOptions };
776+
723777
if (typeof onProgress === 'function') {
724778
modelOptions.stream = true;
725779
}
@@ -779,6 +833,27 @@ ${convo}
779833
...opts,
780834
});
781835

836+
/* hacky fix for Mistral AI API not allowing a singular system message in payload */
837+
if (opts.baseURL.includes('https://api.mistral.ai/v1') && modelOptions.messages) {
838+
const { messages } = modelOptions;
839+
if (messages.length === 1 && messages[0].role === 'system') {
840+
modelOptions.messages[0].role = 'user';
841+
}
842+
}
843+
844+
if (this.options.addParams && typeof this.options.addParams === 'object') {
845+
modelOptions = {
846+
...modelOptions,
847+
...this.options.addParams,
848+
};
849+
}
850+
851+
if (this.options.dropParams && Array.isArray(this.options.dropParams)) {
852+
this.options.dropParams.forEach((param) => {
853+
delete modelOptions[param];
854+
});
855+
}
856+
782857
let UnexpectedRoleError = false;
783858
if (modelOptions.stream) {
784859
const stream = await openai.beta.chat.completions
@@ -847,7 +922,7 @@ ${convo}
847922
err?.message?.includes('abort') ||
848923
(err instanceof OpenAI.APIError && err?.message?.includes('abort'))
849924
) {
850-
return '';
925+
return intermediateReply;
851926
}
852927
if (
853928
err?.message?.includes(
@@ -859,7 +934,6 @@ ${convo}
859934
(err instanceof OpenAI.OpenAIError && err?.message?.includes('missing finish_reason'))
860935
) {
861936
logger.error('[OpenAIClient] Known OpenAI error:', err);
862-
await abortController.abortCompletion();
863937
return intermediateReply;
864938
} else if (err instanceof OpenAI.APIError) {
865939
if (intermediateReply) {

0 commit comments

Comments
 (0)