1
1
const OpenAI = require ( 'openai' ) ;
2
2
const { HttpsProxyAgent } = require ( 'https-proxy-agent' ) ;
3
- const { getResponseSender, EModelEndpoint } = require ( 'librechat-data-provider' ) ;
3
+ const { getResponseSender } = require ( 'librechat-data-provider' ) ;
4
4
const { encoding_for_model : encodingForModel , get_encoding : getEncoding } = require ( 'tiktoken' ) ;
5
5
const { encodeAndFormat, validateVisionModel } = require ( '~/server/services/Files/images' ) ;
6
6
const { getModelMaxTokens, genAzureChatCompletion, extractBaseURL } = require ( '~/utils' ) ;
@@ -94,10 +94,23 @@ class OpenAIClient extends BaseClient {
94
94
}
95
95
96
96
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
+
97
106
this . FORCE_PROMPT =
98
107
isEnabled ( OPENAI_FORCE_PROMPT ) ||
99
108
( reverseProxy && reverseProxy . includes ( 'completions' ) && ! reverseProxy . includes ( 'chat' ) ) ;
100
109
110
+ if ( typeof this . options . forcePrompt === 'boolean' ) {
111
+ this . FORCE_PROMPT = this . options . forcePrompt ;
112
+ }
113
+
101
114
if ( this . azure && process . env . AZURE_OPENAI_DEFAULT_MODEL ) {
102
115
this . azureEndpoint = genAzureChatCompletion ( this . azure , this . modelOptions . model ) ;
103
116
this . modelOptions . model = process . env . AZURE_OPENAI_DEFAULT_MODEL ;
@@ -146,8 +159,10 @@ class OpenAIClient extends BaseClient {
146
159
this . options . sender ??
147
160
getResponseSender ( {
148
161
model : this . modelOptions . model ,
149
- endpoint : EModelEndpoint . openAI ,
162
+ endpoint : this . options . endpoint ,
163
+ endpointType : this . options . endpointType ,
150
164
chatGptLabel : this . options . chatGptLabel ,
165
+ modelDisplayLabel : this . options . modelDisplayLabel ,
151
166
} ) ;
152
167
153
168
this . userLabel = this . options . userLabel || 'User' ;
@@ -434,7 +449,7 @@ class OpenAIClient extends BaseClient {
434
449
} ,
435
450
opts . abortController || new AbortController ( ) ,
436
451
) ;
437
- } else if ( typeof opts . onProgress === 'function' ) {
452
+ } else if ( typeof opts . onProgress === 'function' || this . options . useChatCompletion ) {
438
453
reply = await this . chatCompletion ( {
439
454
payload,
440
455
clientOptions : opts ,
@@ -530,6 +545,19 @@ class OpenAIClient extends BaseClient {
530
545
return llm ;
531
546
}
532
547
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
+ */
533
561
async titleConvo ( { text, responseText = '' } ) {
534
562
let title = 'New Chat' ;
535
563
const convo = `||>User:
@@ -539,32 +567,25 @@ class OpenAIClient extends BaseClient {
539
567
540
568
const { OPENAI_TITLE_MODEL } = process . env ?? { } ;
541
569
570
+ const model = this . options . titleModel ?? OPENAI_TITLE_MODEL ?? 'gpt-3.5-turbo' ;
571
+
542
572
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,
544
575
temperature : 0.2 ,
545
576
presence_penalty : 0 ,
546
577
frequency_penalty : 0 ,
547
578
max_tokens : 16 ,
548
579
} ;
549
580
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
+
564
584
if ( this . azure ) {
565
585
modelOptions . model = process . env . AZURE_OPENAI_DEFAULT_MODEL ?? modelOptions . model ;
566
586
this . azureEndpoint = genAzureChatCompletion ( this . azure , modelOptions . model ) ;
567
587
}
588
+
568
589
const instructionsPayload = [
569
590
{
570
591
role : 'system' ,
@@ -578,10 +599,38 @@ ${convo}
578
599
] ;
579
600
580
601
try {
581
- title = ( await this . sendPayload ( instructionsPayload , { modelOptions } ) ) . replaceAll ( '"' , '' ) ;
602
+ title = (
603
+ await this . sendPayload ( instructionsPayload , { modelOptions, useChatCompletion : true } )
604
+ ) . replaceAll ( '"' , '' ) ;
582
605
} 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
+ ) ;
584
610
}
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 ( ) ;
585
634
}
586
635
587
636
logger . debug ( '[OpenAIClient] Convo Title: ' + title ) ;
@@ -593,8 +642,11 @@ ${convo}
593
642
let context = messagesToRefine ;
594
643
let prompt ;
595
644
645
+ // TODO: remove the gpt fallback and make it specific to endpoint
596
646
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
+
598
650
// 3 tokens for the assistant label, and 98 for the summarizer prompt (101)
599
651
let promptBuffer = 101 ;
600
652
@@ -644,7 +696,7 @@ ${convo}
644
696
logger . debug ( '[OpenAIClient] initialPromptTokens' , initialPromptTokens ) ;
645
697
646
698
const llm = this . initializeLLM ( {
647
- model : OPENAI_SUMMARY_MODEL ,
699
+ model,
648
700
temperature : 0.2 ,
649
701
context : 'summary' ,
650
702
tokenBuffer : initialPromptTokens ,
@@ -719,7 +771,9 @@ ${convo}
719
771
if ( ! abortController ) {
720
772
abortController = new AbortController ( ) ;
721
773
}
722
- const modelOptions = { ...this . modelOptions } ;
774
+
775
+ let modelOptions = { ...this . modelOptions } ;
776
+
723
777
if ( typeof onProgress === 'function' ) {
724
778
modelOptions . stream = true ;
725
779
}
@@ -779,6 +833,27 @@ ${convo}
779
833
...opts ,
780
834
} ) ;
781
835
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
+
782
857
let UnexpectedRoleError = false ;
783
858
if ( modelOptions . stream ) {
784
859
const stream = await openai . beta . chat . completions
@@ -847,7 +922,7 @@ ${convo}
847
922
err ?. message ?. includes ( 'abort' ) ||
848
923
( err instanceof OpenAI . APIError && err ?. message ?. includes ( 'abort' ) )
849
924
) {
850
- return '' ;
925
+ return intermediateReply ;
851
926
}
852
927
if (
853
928
err ?. message ?. includes (
@@ -859,7 +934,6 @@ ${convo}
859
934
( err instanceof OpenAI . OpenAIError && err ?. message ?. includes ( 'missing finish_reason' ) )
860
935
) {
861
936
logger . error ( '[OpenAIClient] Known OpenAI error:' , err ) ;
862
- await abortController . abortCompletion ( ) ;
863
937
return intermediateReply ;
864
938
} else if ( err instanceof OpenAI . APIError ) {
865
939
if ( intermediateReply ) {
0 commit comments