@librechat/agents 2.4.59 → 2.4.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/llm/google/index.cjs +1 -5
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +2 -1
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +146 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +1 -5
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +2 -1
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +146 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/types/llm/openai/index.d.ts +27 -5
- package/dist/types/llm/openai/types.d.ts +10 -0
- package/package.json +1 -1
- package/src/llm/google/utils/common.ts +2 -1
- package/src/llm/openai/index.ts +209 -20
- package/src/llm/openai/types.ts +24 -0
- package/src/scripts/simple.ts +9 -3
package/package.json
CHANGED
|
@@ -557,7 +557,8 @@ export function convertResponseContentToChatGenerationChunk(
|
|
|
557
557
|
...functionCalls.map((fc) => ({
|
|
558
558
|
...fc,
|
|
559
559
|
args: JSON.stringify(fc.args),
|
|
560
|
-
|
|
560
|
+
// Un-commenting this causes LangChain to incorrectly merge tool calls together
|
|
561
|
+
// index: extra.index,
|
|
561
562
|
type: 'tool_call_chunk' as const,
|
|
562
563
|
id: 'id' in fc && typeof fc.id === 'string' ? fc.id : uuidv4(),
|
|
563
564
|
}))
|
package/src/llm/openai/index.ts
CHANGED
|
@@ -13,8 +13,15 @@ import {
|
|
|
13
13
|
ChatOpenAI as OriginalChatOpenAI,
|
|
14
14
|
AzureChatOpenAI as OriginalAzureChatOpenAI,
|
|
15
15
|
} from '@langchain/openai';
|
|
16
|
+
import type {
|
|
17
|
+
OpenAIChatCallOptions,
|
|
18
|
+
OpenAIRoleEnum,
|
|
19
|
+
HeaderValue,
|
|
20
|
+
HeadersLike,
|
|
21
|
+
} from './types';
|
|
16
22
|
import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
|
|
17
|
-
import type { BaseMessage } from '@langchain/core/messages';
|
|
23
|
+
import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
|
|
24
|
+
import type { ChatXAIInput } from '@langchain/xai';
|
|
18
25
|
import type * as t from '@langchain/openai';
|
|
19
26
|
import {
|
|
20
27
|
_convertMessagesToOpenAIParams,
|
|
@@ -23,25 +30,6 @@ import {
|
|
|
23
30
|
type ResponseReturnStreamEvents,
|
|
24
31
|
} from './utils';
|
|
25
32
|
|
|
26
|
-
// TODO import from SDK when available
|
|
27
|
-
type OpenAIRoleEnum =
|
|
28
|
-
| 'system'
|
|
29
|
-
| 'developer'
|
|
30
|
-
| 'assistant'
|
|
31
|
-
| 'user'
|
|
32
|
-
| 'function'
|
|
33
|
-
| 'tool';
|
|
34
|
-
|
|
35
|
-
type HeaderValue = string | undefined | null;
|
|
36
|
-
export type HeadersLike =
|
|
37
|
-
| Headers
|
|
38
|
-
| readonly HeaderValue[][]
|
|
39
|
-
| Record<string, HeaderValue | readonly HeaderValue[]>
|
|
40
|
-
| undefined
|
|
41
|
-
| null
|
|
42
|
-
// NullableHeaders
|
|
43
|
-
| { values: Headers; [key: string]: unknown };
|
|
44
|
-
|
|
45
33
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
46
34
|
const iife = <T>(fn: () => T) => fn();
|
|
47
35
|
|
|
@@ -542,10 +530,49 @@ export class ChatDeepSeek extends OriginalChatDeepSeek {
|
|
|
542
530
|
}
|
|
543
531
|
}
|
|
544
532
|
|
|
533
|
+
/** xAI-specific usage metadata type */
|
|
534
|
+
export interface XAIUsageMetadata
|
|
535
|
+
extends OpenAIClient.Completions.CompletionUsage {
|
|
536
|
+
prompt_tokens_details?: {
|
|
537
|
+
audio_tokens?: number;
|
|
538
|
+
cached_tokens?: number;
|
|
539
|
+
text_tokens?: number;
|
|
540
|
+
image_tokens?: number;
|
|
541
|
+
};
|
|
542
|
+
completion_tokens_details?: {
|
|
543
|
+
audio_tokens?: number;
|
|
544
|
+
reasoning_tokens?: number;
|
|
545
|
+
accepted_prediction_tokens?: number;
|
|
546
|
+
rejected_prediction_tokens?: number;
|
|
547
|
+
};
|
|
548
|
+
num_sources_used?: number;
|
|
549
|
+
}
|
|
550
|
+
|
|
545
551
|
export class ChatXAI extends OriginalChatXAI {
|
|
552
|
+
constructor(
|
|
553
|
+
fields?: Partial<ChatXAIInput> & {
|
|
554
|
+
configuration?: { baseURL?: string };
|
|
555
|
+
clientConfig?: { baseURL?: string };
|
|
556
|
+
}
|
|
557
|
+
) {
|
|
558
|
+
super(fields);
|
|
559
|
+
const customBaseURL =
|
|
560
|
+
fields?.configuration?.baseURL ?? fields?.clientConfig?.baseURL;
|
|
561
|
+
if (customBaseURL != null && customBaseURL) {
|
|
562
|
+
this.clientConfig = {
|
|
563
|
+
...this.clientConfig,
|
|
564
|
+
baseURL: customBaseURL,
|
|
565
|
+
};
|
|
566
|
+
// Reset the client to force recreation with new config
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
568
|
+
this.client = undefined as any;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
546
572
|
public get exposedClient(): CustomOpenAIClient {
|
|
547
573
|
return this.client;
|
|
548
574
|
}
|
|
575
|
+
|
|
549
576
|
protected _getClientOptions(
|
|
550
577
|
options?: OpenAICoreRequestOptions
|
|
551
578
|
): OpenAICoreRequestOptions {
|
|
@@ -573,4 +600,166 @@ export class ChatXAI extends OriginalChatXAI {
|
|
|
573
600
|
} as OpenAICoreRequestOptions;
|
|
574
601
|
return requestOptions;
|
|
575
602
|
}
|
|
603
|
+
|
|
604
|
+
async *_streamResponseChunks(
|
|
605
|
+
messages: BaseMessage[],
|
|
606
|
+
options: this['ParsedCallOptions'],
|
|
607
|
+
runManager?: CallbackManagerForLLMRun
|
|
608
|
+
): AsyncGenerator<ChatGenerationChunk> {
|
|
609
|
+
const messagesMapped: OpenAICompletionParam[] =
|
|
610
|
+
_convertMessagesToOpenAIParams(messages, this.model);
|
|
611
|
+
|
|
612
|
+
const params = {
|
|
613
|
+
...this.invocationParams(options, {
|
|
614
|
+
streaming: true,
|
|
615
|
+
}),
|
|
616
|
+
messages: messagesMapped,
|
|
617
|
+
stream: true as const,
|
|
618
|
+
};
|
|
619
|
+
let defaultRole: OpenAIRoleEnum | undefined;
|
|
620
|
+
|
|
621
|
+
const streamIterable = await this.completionWithRetry(params, options);
|
|
622
|
+
let usage: OpenAIClient.Completions.CompletionUsage | undefined;
|
|
623
|
+
for await (const data of streamIterable) {
|
|
624
|
+
const choice = data.choices[0] as
|
|
625
|
+
| Partial<OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice>
|
|
626
|
+
| undefined;
|
|
627
|
+
if (data.usage) {
|
|
628
|
+
usage = data.usage;
|
|
629
|
+
}
|
|
630
|
+
if (!choice) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const { delta } = choice;
|
|
635
|
+
if (!delta) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const chunk = this._convertOpenAIDeltaToBaseMessageChunk(
|
|
639
|
+
delta,
|
|
640
|
+
data,
|
|
641
|
+
defaultRole
|
|
642
|
+
);
|
|
643
|
+
if (chunk.usage_metadata != null) {
|
|
644
|
+
chunk.usage_metadata = {
|
|
645
|
+
input_tokens:
|
|
646
|
+
(chunk.usage_metadata as Partial<UsageMetadata>).input_tokens ?? 0,
|
|
647
|
+
output_tokens:
|
|
648
|
+
(chunk.usage_metadata as Partial<UsageMetadata>).output_tokens ?? 0,
|
|
649
|
+
total_tokens:
|
|
650
|
+
(chunk.usage_metadata as Partial<UsageMetadata>).total_tokens ?? 0,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
if ('reasoning_content' in delta) {
|
|
654
|
+
chunk.additional_kwargs.reasoning_content = delta.reasoning_content;
|
|
655
|
+
}
|
|
656
|
+
defaultRole = delta.role ?? defaultRole;
|
|
657
|
+
const newTokenIndices = {
|
|
658
|
+
prompt: (options as OpenAIChatCallOptions).promptIndex ?? 0,
|
|
659
|
+
completion: choice.index ?? 0,
|
|
660
|
+
};
|
|
661
|
+
if (typeof chunk.content !== 'string') {
|
|
662
|
+
// eslint-disable-next-line no-console
|
|
663
|
+
console.log(
|
|
664
|
+
'[WARNING]: Received non-string content from OpenAI. This is currently not supported.'
|
|
665
|
+
);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
669
|
+
const generationInfo: Record<string, any> = { ...newTokenIndices };
|
|
670
|
+
if (choice.finish_reason != null) {
|
|
671
|
+
generationInfo.finish_reason = choice.finish_reason;
|
|
672
|
+
// Only include system fingerprint in the last chunk for now
|
|
673
|
+
// to avoid concatenation issues
|
|
674
|
+
generationInfo.system_fingerprint = data.system_fingerprint;
|
|
675
|
+
generationInfo.model_name = data.model;
|
|
676
|
+
generationInfo.service_tier = data.service_tier;
|
|
677
|
+
}
|
|
678
|
+
if (this.logprobs == true) {
|
|
679
|
+
generationInfo.logprobs = choice.logprobs;
|
|
680
|
+
}
|
|
681
|
+
const generationChunk = new ChatGenerationChunk({
|
|
682
|
+
message: chunk,
|
|
683
|
+
text: chunk.content,
|
|
684
|
+
generationInfo,
|
|
685
|
+
});
|
|
686
|
+
yield generationChunk;
|
|
687
|
+
await runManager?.handleLLMNewToken(
|
|
688
|
+
generationChunk.text || '',
|
|
689
|
+
newTokenIndices,
|
|
690
|
+
undefined,
|
|
691
|
+
undefined,
|
|
692
|
+
undefined,
|
|
693
|
+
{ chunk: generationChunk }
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (usage) {
|
|
697
|
+
// Type assertion for xAI-specific usage structure
|
|
698
|
+
const xaiUsage = usage as XAIUsageMetadata;
|
|
699
|
+
const inputTokenDetails = {
|
|
700
|
+
// Standard OpenAI fields
|
|
701
|
+
...(usage.prompt_tokens_details?.audio_tokens != null && {
|
|
702
|
+
audio: usage.prompt_tokens_details.audio_tokens,
|
|
703
|
+
}),
|
|
704
|
+
...(usage.prompt_tokens_details?.cached_tokens != null && {
|
|
705
|
+
cache_read: usage.prompt_tokens_details.cached_tokens,
|
|
706
|
+
}),
|
|
707
|
+
// Add xAI-specific prompt token details if they exist
|
|
708
|
+
...(xaiUsage.prompt_tokens_details?.text_tokens != null && {
|
|
709
|
+
text: xaiUsage.prompt_tokens_details.text_tokens,
|
|
710
|
+
}),
|
|
711
|
+
...(xaiUsage.prompt_tokens_details?.image_tokens != null && {
|
|
712
|
+
image: xaiUsage.prompt_tokens_details.image_tokens,
|
|
713
|
+
}),
|
|
714
|
+
};
|
|
715
|
+
const outputTokenDetails = {
|
|
716
|
+
// Standard OpenAI fields
|
|
717
|
+
...(usage.completion_tokens_details?.audio_tokens != null && {
|
|
718
|
+
audio: usage.completion_tokens_details.audio_tokens,
|
|
719
|
+
}),
|
|
720
|
+
...(usage.completion_tokens_details?.reasoning_tokens != null && {
|
|
721
|
+
reasoning: usage.completion_tokens_details.reasoning_tokens,
|
|
722
|
+
}),
|
|
723
|
+
// Add xAI-specific completion token details if they exist
|
|
724
|
+
...(xaiUsage.completion_tokens_details?.accepted_prediction_tokens !=
|
|
725
|
+
null && {
|
|
726
|
+
accepted_prediction:
|
|
727
|
+
xaiUsage.completion_tokens_details.accepted_prediction_tokens,
|
|
728
|
+
}),
|
|
729
|
+
...(xaiUsage.completion_tokens_details?.rejected_prediction_tokens !=
|
|
730
|
+
null && {
|
|
731
|
+
rejected_prediction:
|
|
732
|
+
xaiUsage.completion_tokens_details.rejected_prediction_tokens,
|
|
733
|
+
}),
|
|
734
|
+
};
|
|
735
|
+
const generationChunk = new ChatGenerationChunk({
|
|
736
|
+
message: new AIMessageChunk({
|
|
737
|
+
content: '',
|
|
738
|
+
response_metadata: {
|
|
739
|
+
usage: { ...usage },
|
|
740
|
+
// Include xAI-specific metadata if it exists
|
|
741
|
+
...(xaiUsage.num_sources_used != null && {
|
|
742
|
+
num_sources_used: xaiUsage.num_sources_used,
|
|
743
|
+
}),
|
|
744
|
+
},
|
|
745
|
+
usage_metadata: {
|
|
746
|
+
input_tokens: usage.prompt_tokens,
|
|
747
|
+
output_tokens: usage.completion_tokens,
|
|
748
|
+
total_tokens: usage.total_tokens,
|
|
749
|
+
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
750
|
+
input_token_details: inputTokenDetails,
|
|
751
|
+
}),
|
|
752
|
+
...(Object.keys(outputTokenDetails).length > 0 && {
|
|
753
|
+
output_token_details: outputTokenDetails,
|
|
754
|
+
}),
|
|
755
|
+
},
|
|
756
|
+
}),
|
|
757
|
+
text: '',
|
|
758
|
+
});
|
|
759
|
+
yield generationChunk;
|
|
760
|
+
}
|
|
761
|
+
if (options.signal?.aborted === true) {
|
|
762
|
+
throw new Error('AbortError');
|
|
763
|
+
}
|
|
764
|
+
}
|
|
576
765
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { OpenAICallOptions } from '@langchain/openai';
|
|
2
|
+
|
|
3
|
+
export interface OpenAIChatCallOptions extends OpenAICallOptions {
|
|
4
|
+
promptIndex?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// TODO import from SDK when available
|
|
8
|
+
export type OpenAIRoleEnum =
|
|
9
|
+
| 'system'
|
|
10
|
+
| 'developer'
|
|
11
|
+
| 'assistant'
|
|
12
|
+
| 'user'
|
|
13
|
+
| 'function'
|
|
14
|
+
| 'tool';
|
|
15
|
+
|
|
16
|
+
export type HeaderValue = string | undefined | null;
|
|
17
|
+
export type HeadersLike =
|
|
18
|
+
| Headers
|
|
19
|
+
| readonly HeaderValue[][]
|
|
20
|
+
| Record<string, HeaderValue | readonly HeaderValue[]>
|
|
21
|
+
| undefined
|
|
22
|
+
| null
|
|
23
|
+
// NullableHeaders
|
|
24
|
+
| { values: Headers; [key: string]: unknown };
|
package/src/scripts/simple.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// src/scripts/cli.ts
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
config();
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
HumanMessage,
|
|
6
|
+
BaseMessage,
|
|
7
|
+
UsageMetadata,
|
|
8
|
+
} from '@langchain/core/messages';
|
|
5
9
|
import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
|
|
6
10
|
import type * as t from '@/types';
|
|
7
11
|
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
@@ -17,6 +21,7 @@ import { Run } from '@/run';
|
|
|
17
21
|
|
|
18
22
|
const conversationHistory: BaseMessage[] = [];
|
|
19
23
|
let _contentParts: t.MessageContentComplex[] = [];
|
|
24
|
+
let collectedUsage: UsageMetadata[] = [];
|
|
20
25
|
|
|
21
26
|
async function testStandardStreaming(): Promise<void> {
|
|
22
27
|
const { userName, location, provider, currentDate } = await getArgs();
|
|
@@ -24,7 +29,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
24
29
|
_contentParts = contentParts as t.MessageContentComplex[];
|
|
25
30
|
const customHandlers = {
|
|
26
31
|
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
27
|
-
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
32
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
28
33
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
29
34
|
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
30
35
|
handle: (
|
|
@@ -177,8 +182,9 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
177
182
|
};
|
|
178
183
|
}
|
|
179
184
|
const titleResult = await run.generateTitle(titleOptions);
|
|
185
|
+
console.log('Collected usage metadata:', collectedUsage);
|
|
180
186
|
console.log('Generated Title:', titleResult);
|
|
181
|
-
console.log('Collected metadata:', collected);
|
|
187
|
+
console.log('Collected title usage metadata:', collected);
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
process.on('unhandledRejection', (reason, promise) => {
|