@librechat/agents 3.0.33 → 3.0.35
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/common/enum.cjs +0 -1
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +102 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +87 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +175 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +0 -3
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +20 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +0 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +0 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +103 -1
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +88 -2
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +175 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +0 -3
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/stream.mjs +20 -0
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +0 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +0 -1
- package/dist/types/llm/openai/index.d.ts +1 -0
- package/dist/types/llm/openai/utils/index.d.ts +10 -1
- package/dist/types/llm/openrouter/index.d.ts +4 -1
- package/dist/types/types/llm.d.ts +1 -6
- package/package.json +2 -3
- package/src/common/enum.ts +0 -1
- package/src/llm/openai/index.ts +126 -0
- package/src/llm/openai/utils/index.ts +116 -1
- package/src/llm/openrouter/index.ts +222 -1
- package/src/llm/providers.ts +0 -3
- package/src/stream.ts +26 -0
- package/src/types/llm.ts +0 -6
- package/src/utils/llm.ts +0 -1
- package/src/utils/llmConfig.ts +13 -5
- package/dist/cjs/llm/ollama/index.cjs +0 -70
- package/dist/cjs/llm/ollama/index.cjs.map +0 -1
- package/dist/cjs/llm/ollama/utils.cjs +0 -158
- package/dist/cjs/llm/ollama/utils.cjs.map +0 -1
- package/dist/esm/llm/ollama/index.mjs +0 -68
- package/dist/esm/llm/ollama/index.mjs.map +0 -1
- package/dist/esm/llm/ollama/utils.mjs +0 -155
- package/dist/esm/llm/ollama/utils.mjs.map +0 -1
- package/dist/types/llm/ollama/index.d.ts +0 -8
- package/dist/types/llm/ollama/utils.d.ts +0 -7
- package/src/llm/ollama/index.ts +0 -92
- package/src/llm/ollama/utils.ts +0 -193
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.35",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"caching": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/caching.ts --name 'Jo' --location 'New York, NY'",
|
|
52
52
|
"thinking": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/thinking.ts --name 'Jo' --location 'New York, NY'",
|
|
53
53
|
"memory": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/memory.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
|
54
|
-
"tool": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider '
|
|
54
|
+
"tool": "node --trace-warnings -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'openrouter' --name 'Jo' --location 'New York, NY'",
|
|
55
55
|
"search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/search.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
56
56
|
"ant_web_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/ant_web_search.ts --name 'Jo' --location 'New York, NY'",
|
|
57
57
|
"ant_web_search_edge_case": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/ant_web_search_edge_case.ts --name 'Jo' --location 'New York, NY'",
|
|
@@ -106,7 +106,6 @@
|
|
|
106
106
|
"@langchain/google-vertexai": "^0.2.18",
|
|
107
107
|
"@langchain/langgraph": "^0.4.9",
|
|
108
108
|
"@langchain/mistralai": "^0.2.1",
|
|
109
|
-
"@langchain/ollama": "^0.2.3",
|
|
110
109
|
"@langchain/openai": "0.5.18",
|
|
111
110
|
"@langchain/textsplitters": "^0.1.0",
|
|
112
111
|
"@langchain/xai": "^0.0.3",
|
package/src/common/enum.ts
CHANGED
package/src/llm/openai/index.ts
CHANGED
|
@@ -643,6 +643,132 @@ export class ChatDeepSeek extends OriginalChatDeepSeek {
|
|
|
643
643
|
} as OpenAICoreRequestOptions;
|
|
644
644
|
return requestOptions;
|
|
645
645
|
}
|
|
646
|
+
|
|
647
|
+
async *_streamResponseChunks(
|
|
648
|
+
messages: BaseMessage[],
|
|
649
|
+
options: this['ParsedCallOptions'],
|
|
650
|
+
runManager?: CallbackManagerForLLMRun
|
|
651
|
+
): AsyncGenerator<ChatGenerationChunk> {
|
|
652
|
+
const messagesMapped: OpenAICompletionParam[] =
|
|
653
|
+
_convertMessagesToOpenAIParams(messages, this.model, {
|
|
654
|
+
includeReasoningContent: true,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const params = {
|
|
658
|
+
...this.invocationParams(options, {
|
|
659
|
+
streaming: true,
|
|
660
|
+
}),
|
|
661
|
+
messages: messagesMapped,
|
|
662
|
+
stream: true as const,
|
|
663
|
+
};
|
|
664
|
+
let defaultRole: OpenAIRoleEnum | undefined;
|
|
665
|
+
|
|
666
|
+
const streamIterable = await this.completionWithRetry(params, options);
|
|
667
|
+
let usage: OpenAIClient.Completions.CompletionUsage | undefined;
|
|
668
|
+
for await (const data of streamIterable) {
|
|
669
|
+
const choice = data.choices[0] as
|
|
670
|
+
| Partial<OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice>
|
|
671
|
+
| undefined;
|
|
672
|
+
if (data.usage) {
|
|
673
|
+
usage = data.usage;
|
|
674
|
+
}
|
|
675
|
+
if (!choice) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const { delta } = choice;
|
|
680
|
+
if (!delta) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
const chunk = this._convertOpenAIDeltaToBaseMessageChunk(
|
|
684
|
+
delta,
|
|
685
|
+
data,
|
|
686
|
+
defaultRole
|
|
687
|
+
);
|
|
688
|
+
if ('reasoning_content' in delta) {
|
|
689
|
+
chunk.additional_kwargs.reasoning_content = delta.reasoning_content;
|
|
690
|
+
}
|
|
691
|
+
defaultRole = delta.role ?? defaultRole;
|
|
692
|
+
const newTokenIndices = {
|
|
693
|
+
prompt: (options as OpenAIChatCallOptions).promptIndex ?? 0,
|
|
694
|
+
completion: choice.index ?? 0,
|
|
695
|
+
};
|
|
696
|
+
if (typeof chunk.content !== 'string') {
|
|
697
|
+
// eslint-disable-next-line no-console
|
|
698
|
+
console.log(
|
|
699
|
+
'[WARNING]: Received non-string content from OpenAI. This is currently not supported.'
|
|
700
|
+
);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
704
|
+
const generationInfo: Record<string, any> = { ...newTokenIndices };
|
|
705
|
+
if (choice.finish_reason != null) {
|
|
706
|
+
generationInfo.finish_reason = choice.finish_reason;
|
|
707
|
+
generationInfo.system_fingerprint = data.system_fingerprint;
|
|
708
|
+
generationInfo.model_name = data.model;
|
|
709
|
+
generationInfo.service_tier = data.service_tier;
|
|
710
|
+
}
|
|
711
|
+
if (this.logprobs == true) {
|
|
712
|
+
generationInfo.logprobs = choice.logprobs;
|
|
713
|
+
}
|
|
714
|
+
const generationChunk = new ChatGenerationChunk({
|
|
715
|
+
message: chunk,
|
|
716
|
+
text: chunk.content,
|
|
717
|
+
generationInfo,
|
|
718
|
+
});
|
|
719
|
+
yield generationChunk;
|
|
720
|
+
await runManager?.handleLLMNewToken(
|
|
721
|
+
generationChunk.text || '',
|
|
722
|
+
newTokenIndices,
|
|
723
|
+
undefined,
|
|
724
|
+
undefined,
|
|
725
|
+
undefined,
|
|
726
|
+
{ chunk: generationChunk }
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (usage) {
|
|
730
|
+
const inputTokenDetails = {
|
|
731
|
+
...(usage.prompt_tokens_details?.audio_tokens != null && {
|
|
732
|
+
audio: usage.prompt_tokens_details.audio_tokens,
|
|
733
|
+
}),
|
|
734
|
+
...(usage.prompt_tokens_details?.cached_tokens != null && {
|
|
735
|
+
cache_read: usage.prompt_tokens_details.cached_tokens,
|
|
736
|
+
}),
|
|
737
|
+
};
|
|
738
|
+
const outputTokenDetails = {
|
|
739
|
+
...(usage.completion_tokens_details?.audio_tokens != null && {
|
|
740
|
+
audio: usage.completion_tokens_details.audio_tokens,
|
|
741
|
+
}),
|
|
742
|
+
...(usage.completion_tokens_details?.reasoning_tokens != null && {
|
|
743
|
+
reasoning: usage.completion_tokens_details.reasoning_tokens,
|
|
744
|
+
}),
|
|
745
|
+
};
|
|
746
|
+
const generationChunk = new ChatGenerationChunk({
|
|
747
|
+
message: new AIMessageChunk({
|
|
748
|
+
content: '',
|
|
749
|
+
response_metadata: {
|
|
750
|
+
usage: { ...usage },
|
|
751
|
+
},
|
|
752
|
+
usage_metadata: {
|
|
753
|
+
input_tokens: usage.prompt_tokens,
|
|
754
|
+
output_tokens: usage.completion_tokens,
|
|
755
|
+
total_tokens: usage.total_tokens,
|
|
756
|
+
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
757
|
+
input_token_details: inputTokenDetails,
|
|
758
|
+
}),
|
|
759
|
+
...(Object.keys(outputTokenDetails).length > 0 && {
|
|
760
|
+
output_token_details: outputTokenDetails,
|
|
761
|
+
}),
|
|
762
|
+
},
|
|
763
|
+
}),
|
|
764
|
+
text: '',
|
|
765
|
+
});
|
|
766
|
+
yield generationChunk;
|
|
767
|
+
}
|
|
768
|
+
if (options.signal?.aborted === true) {
|
|
769
|
+
throw new Error('AbortError');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
646
772
|
}
|
|
647
773
|
|
|
648
774
|
/** xAI-specific usage metadata type */
|
|
@@ -286,10 +286,21 @@ const completionsApiContentBlockConverter: StandardContentBlockConverter<{
|
|
|
286
286
|
},
|
|
287
287
|
};
|
|
288
288
|
|
|
289
|
+
/** Options for converting messages to OpenAI params */
|
|
290
|
+
export interface ConvertMessagesOptions {
|
|
291
|
+
/** Include reasoning_content field for DeepSeek thinking mode with tool calls */
|
|
292
|
+
includeReasoningContent?: boolean;
|
|
293
|
+
/** Include reasoning_details field for OpenRouter/Gemini thinking mode with tool calls */
|
|
294
|
+
includeReasoningDetails?: boolean;
|
|
295
|
+
/** Convert reasoning_details to content blocks for Claude (requires content array format) */
|
|
296
|
+
convertReasoningDetailsToContent?: boolean;
|
|
297
|
+
}
|
|
298
|
+
|
|
289
299
|
// Used in LangSmith, export is important here
|
|
290
300
|
export function _convertMessagesToOpenAIParams(
|
|
291
301
|
messages: BaseMessage[],
|
|
292
|
-
model?: string
|
|
302
|
+
model?: string,
|
|
303
|
+
options?: ConvertMessagesOptions
|
|
293
304
|
): OpenAICompletionParam[] {
|
|
294
305
|
// TODO: Function messages do not support array content, fix cast
|
|
295
306
|
return messages.flatMap((message) => {
|
|
@@ -333,9 +344,113 @@ export function _convertMessagesToOpenAIParams(
|
|
|
333
344
|
convertLangChainToolCallToOpenAI
|
|
334
345
|
);
|
|
335
346
|
completionParam.content = hasAnthropicThinkingBlock ? content : '';
|
|
347
|
+
if (
|
|
348
|
+
options?.includeReasoningContent === true &&
|
|
349
|
+
message.additional_kwargs.reasoning_content != null
|
|
350
|
+
) {
|
|
351
|
+
completionParam.reasoning_content =
|
|
352
|
+
message.additional_kwargs.reasoning_content;
|
|
353
|
+
}
|
|
354
|
+
if (
|
|
355
|
+
options?.includeReasoningDetails === true &&
|
|
356
|
+
message.additional_kwargs.reasoning_details != null
|
|
357
|
+
) {
|
|
358
|
+
// For Claude via OpenRouter, convert reasoning_details to content blocks
|
|
359
|
+
const isClaudeModel =
|
|
360
|
+
model?.includes('claude') === true ||
|
|
361
|
+
model?.includes('anthropic') === true;
|
|
362
|
+
if (
|
|
363
|
+
options.convertReasoningDetailsToContent === true &&
|
|
364
|
+
isClaudeModel
|
|
365
|
+
) {
|
|
366
|
+
const reasoningDetails = message.additional_kwargs
|
|
367
|
+
.reasoning_details as Record<string, unknown>[];
|
|
368
|
+
const contentBlocks = [];
|
|
369
|
+
|
|
370
|
+
// Add thinking blocks from reasoning_details
|
|
371
|
+
for (const detail of reasoningDetails) {
|
|
372
|
+
if (detail.type === 'reasoning.text' && detail.text != null) {
|
|
373
|
+
contentBlocks.push({
|
|
374
|
+
type: 'thinking',
|
|
375
|
+
thinking: detail.text,
|
|
376
|
+
});
|
|
377
|
+
} else if (
|
|
378
|
+
detail.type === 'reasoning.encrypted' &&
|
|
379
|
+
detail.data != null
|
|
380
|
+
) {
|
|
381
|
+
contentBlocks.push({
|
|
382
|
+
type: 'redacted_thinking',
|
|
383
|
+
data: detail.data,
|
|
384
|
+
id: detail.id,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Set content to array with thinking blocks
|
|
390
|
+
if (contentBlocks.length > 0) {
|
|
391
|
+
completionParam.content = contentBlocks;
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// For non-Claude models, pass as separate field
|
|
395
|
+
completionParam.reasoning_details =
|
|
396
|
+
message.additional_kwargs.reasoning_details;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
336
399
|
} else {
|
|
337
400
|
if (message.additional_kwargs.tool_calls != null) {
|
|
338
401
|
completionParam.tool_calls = message.additional_kwargs.tool_calls;
|
|
402
|
+
if (
|
|
403
|
+
options?.includeReasoningContent === true &&
|
|
404
|
+
message.additional_kwargs.reasoning_content != null
|
|
405
|
+
) {
|
|
406
|
+
completionParam.reasoning_content =
|
|
407
|
+
message.additional_kwargs.reasoning_content;
|
|
408
|
+
}
|
|
409
|
+
if (
|
|
410
|
+
options?.includeReasoningDetails === true &&
|
|
411
|
+
message.additional_kwargs.reasoning_details != null
|
|
412
|
+
) {
|
|
413
|
+
// For Claude via OpenRouter, convert reasoning_details to content blocks
|
|
414
|
+
const isClaudeModel =
|
|
415
|
+
model?.includes('claude') === true ||
|
|
416
|
+
model?.includes('anthropic') === true;
|
|
417
|
+
if (
|
|
418
|
+
options.convertReasoningDetailsToContent === true &&
|
|
419
|
+
isClaudeModel
|
|
420
|
+
) {
|
|
421
|
+
const reasoningDetails = message.additional_kwargs
|
|
422
|
+
.reasoning_details as Record<string, unknown>[];
|
|
423
|
+
const contentBlocks = [];
|
|
424
|
+
|
|
425
|
+
// Add thinking blocks from reasoning_details
|
|
426
|
+
for (const detail of reasoningDetails) {
|
|
427
|
+
if (detail.type === 'reasoning.text' && detail.text != null) {
|
|
428
|
+
contentBlocks.push({
|
|
429
|
+
type: 'thinking',
|
|
430
|
+
thinking: detail.text,
|
|
431
|
+
});
|
|
432
|
+
} else if (
|
|
433
|
+
detail.type === 'reasoning.encrypted' &&
|
|
434
|
+
detail.data != null
|
|
435
|
+
) {
|
|
436
|
+
contentBlocks.push({
|
|
437
|
+
type: 'redacted_thinking',
|
|
438
|
+
data: detail.data,
|
|
439
|
+
id: detail.id,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Set content to array with thinking blocks
|
|
445
|
+
if (contentBlocks.length > 0) {
|
|
446
|
+
completionParam.content = contentBlocks;
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
// For non-Claude models, pass as separate field
|
|
450
|
+
completionParam.reasoning_details =
|
|
451
|
+
message.additional_kwargs.reasoning_details;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
339
454
|
}
|
|
340
455
|
if ((message as ToolMessage).tool_call_id != null) {
|
|
341
456
|
completionParam.tool_call_id = (message as ToolMessage).tool_call_id;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { ChatOpenAI } from '@/llm/openai';
|
|
2
|
+
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
3
|
+
import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
|
|
4
|
+
import { AIMessageChunk as AIMessageChunkClass } from '@langchain/core/messages';
|
|
2
5
|
import type {
|
|
3
6
|
FunctionMessageChunk,
|
|
4
7
|
SystemMessageChunk,
|
|
@@ -6,12 +9,25 @@ import type {
|
|
|
6
9
|
ToolMessageChunk,
|
|
7
10
|
ChatMessageChunk,
|
|
8
11
|
AIMessageChunk,
|
|
12
|
+
BaseMessage,
|
|
9
13
|
} from '@langchain/core/messages';
|
|
10
14
|
import type {
|
|
11
15
|
ChatOpenAICallOptions,
|
|
12
16
|
OpenAIChatInput,
|
|
13
17
|
OpenAIClient,
|
|
14
18
|
} from '@langchain/openai';
|
|
19
|
+
import { _convertMessagesToOpenAIParams } from '@/llm/openai/utils';
|
|
20
|
+
|
|
21
|
+
type OpenAICompletionParam =
|
|
22
|
+
OpenAIClient.Chat.Completions.ChatCompletionMessageParam;
|
|
23
|
+
|
|
24
|
+
type OpenAIRoleEnum =
|
|
25
|
+
| 'system'
|
|
26
|
+
| 'developer'
|
|
27
|
+
| 'assistant'
|
|
28
|
+
| 'user'
|
|
29
|
+
| 'function'
|
|
30
|
+
| 'tool';
|
|
15
31
|
|
|
16
32
|
export interface ChatOpenRouterCallOptions extends ChatOpenAICallOptions {
|
|
17
33
|
include_reasoning?: boolean;
|
|
@@ -54,7 +70,212 @@ export class ChatOpenRouter extends ChatOpenAI {
|
|
|
54
70
|
rawResponse,
|
|
55
71
|
defaultRole
|
|
56
72
|
);
|
|
57
|
-
|
|
73
|
+
if (delta.reasoning != null) {
|
|
74
|
+
messageChunk.additional_kwargs.reasoning = delta.reasoning;
|
|
75
|
+
}
|
|
76
|
+
if (delta.reasoning_details != null) {
|
|
77
|
+
messageChunk.additional_kwargs.reasoning_details =
|
|
78
|
+
delta.reasoning_details;
|
|
79
|
+
}
|
|
58
80
|
return messageChunk;
|
|
59
81
|
}
|
|
82
|
+
|
|
83
|
+
async *_streamResponseChunks2(
|
|
84
|
+
messages: BaseMessage[],
|
|
85
|
+
options: this['ParsedCallOptions'],
|
|
86
|
+
runManager?: CallbackManagerForLLMRun
|
|
87
|
+
): AsyncGenerator<ChatGenerationChunk> {
|
|
88
|
+
const messagesMapped: OpenAICompletionParam[] =
|
|
89
|
+
_convertMessagesToOpenAIParams(messages, this.model, {
|
|
90
|
+
includeReasoningDetails: true,
|
|
91
|
+
convertReasoningDetailsToContent: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const params = {
|
|
95
|
+
...this.invocationParams(options, {
|
|
96
|
+
streaming: true,
|
|
97
|
+
}),
|
|
98
|
+
messages: messagesMapped,
|
|
99
|
+
stream: true as const,
|
|
100
|
+
};
|
|
101
|
+
let defaultRole: OpenAIRoleEnum | undefined;
|
|
102
|
+
|
|
103
|
+
const streamIterable = await this.completionWithRetry(params, options);
|
|
104
|
+
let usage: OpenAIClient.Completions.CompletionUsage | undefined;
|
|
105
|
+
|
|
106
|
+
// Store reasoning_details keyed by unique identifier to prevent incorrect merging
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
const reasoningTextByIndex: Map<number, Record<string, any>> = new Map();
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
const reasoningEncryptedById: Map<string, Record<string, any>> = new Map();
|
|
111
|
+
|
|
112
|
+
for await (const data of streamIterable) {
|
|
113
|
+
const choice = data.choices[0] as
|
|
114
|
+
| Partial<OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice>
|
|
115
|
+
| undefined;
|
|
116
|
+
if (data.usage) {
|
|
117
|
+
usage = data.usage;
|
|
118
|
+
}
|
|
119
|
+
if (!choice) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { delta } = choice;
|
|
124
|
+
if (!delta) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Accumulate reasoning_details from each delta
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
const deltaAny = delta as Record<string, any>;
|
|
131
|
+
if (
|
|
132
|
+
deltaAny.reasoning_details != null &&
|
|
133
|
+
Array.isArray(deltaAny.reasoning_details)
|
|
134
|
+
) {
|
|
135
|
+
for (const detail of deltaAny.reasoning_details) {
|
|
136
|
+
// For encrypted reasoning (thought signatures), store by ID - MUST be separate
|
|
137
|
+
if (detail.type === 'reasoning.encrypted' && detail.id) {
|
|
138
|
+
reasoningEncryptedById.set(detail.id, {
|
|
139
|
+
type: detail.type,
|
|
140
|
+
id: detail.id,
|
|
141
|
+
data: detail.data,
|
|
142
|
+
format: detail.format,
|
|
143
|
+
index: detail.index,
|
|
144
|
+
});
|
|
145
|
+
} else if (detail.type === 'reasoning.text') {
|
|
146
|
+
// For text reasoning, accumulate text by index
|
|
147
|
+
const idx = detail.index ?? 0;
|
|
148
|
+
const existing = reasoningTextByIndex.get(idx);
|
|
149
|
+
if (existing) {
|
|
150
|
+
// Only append text, keep other fields from first entry
|
|
151
|
+
existing.text = (existing.text || '') + (detail.text || '');
|
|
152
|
+
} else {
|
|
153
|
+
reasoningTextByIndex.set(idx, {
|
|
154
|
+
type: detail.type,
|
|
155
|
+
text: detail.text || '',
|
|
156
|
+
format: detail.format,
|
|
157
|
+
index: idx,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const chunk = this._convertOpenAIDeltaToBaseMessageChunk(
|
|
165
|
+
delta,
|
|
166
|
+
data,
|
|
167
|
+
defaultRole
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// IMPORTANT: Only set reasoning_details on the FINAL chunk to prevent
|
|
171
|
+
// LangChain's chunk concatenation from corrupting the array
|
|
172
|
+
// Check if this is the final chunk (has finish_reason)
|
|
173
|
+
if (choice.finish_reason != null) {
|
|
174
|
+
// Build properly structured reasoning_details array
|
|
175
|
+
// Text entries first (but we only need the encrypted ones for thought signatures)
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
const finalReasoningDetails: Record<string, any>[] = [
|
|
178
|
+
...reasoningTextByIndex.values(),
|
|
179
|
+
...reasoningEncryptedById.values(),
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
if (finalReasoningDetails.length > 0) {
|
|
183
|
+
chunk.additional_kwargs.reasoning_details = finalReasoningDetails;
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
// Clear reasoning_details from intermediate chunks to prevent concatenation issues
|
|
187
|
+
delete chunk.additional_kwargs.reasoning_details;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
defaultRole = delta.role ?? defaultRole;
|
|
191
|
+
const newTokenIndices = {
|
|
192
|
+
prompt: options.promptIndex ?? 0,
|
|
193
|
+
completion: choice.index ?? 0,
|
|
194
|
+
};
|
|
195
|
+
if (typeof chunk.content !== 'string') {
|
|
196
|
+
// eslint-disable-next-line no-console
|
|
197
|
+
console.log(
|
|
198
|
+
'[WARNING]: Received non-string content from OpenAI. This is currently not supported.'
|
|
199
|
+
);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
const generationInfo: Record<string, any> = { ...newTokenIndices };
|
|
204
|
+
if (choice.finish_reason != null) {
|
|
205
|
+
generationInfo.finish_reason = choice.finish_reason;
|
|
206
|
+
generationInfo.system_fingerprint = data.system_fingerprint;
|
|
207
|
+
generationInfo.model_name = data.model;
|
|
208
|
+
generationInfo.service_tier = data.service_tier;
|
|
209
|
+
}
|
|
210
|
+
if (this.logprobs == true) {
|
|
211
|
+
generationInfo.logprobs = choice.logprobs;
|
|
212
|
+
}
|
|
213
|
+
const generationChunk = new ChatGenerationChunk({
|
|
214
|
+
message: chunk,
|
|
215
|
+
text: chunk.content,
|
|
216
|
+
generationInfo,
|
|
217
|
+
});
|
|
218
|
+
yield generationChunk;
|
|
219
|
+
if (this._lc_stream_delay != null) {
|
|
220
|
+
await new Promise((resolve) =>
|
|
221
|
+
setTimeout(resolve, this._lc_stream_delay)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
await runManager?.handleLLMNewToken(
|
|
225
|
+
generationChunk.text || '',
|
|
226
|
+
newTokenIndices,
|
|
227
|
+
undefined,
|
|
228
|
+
undefined,
|
|
229
|
+
undefined,
|
|
230
|
+
{ chunk: generationChunk }
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (usage) {
|
|
234
|
+
const inputTokenDetails = {
|
|
235
|
+
...(usage.prompt_tokens_details?.audio_tokens != null && {
|
|
236
|
+
audio: usage.prompt_tokens_details.audio_tokens,
|
|
237
|
+
}),
|
|
238
|
+
...(usage.prompt_tokens_details?.cached_tokens != null && {
|
|
239
|
+
cache_read: usage.prompt_tokens_details.cached_tokens,
|
|
240
|
+
}),
|
|
241
|
+
};
|
|
242
|
+
const outputTokenDetails = {
|
|
243
|
+
...(usage.completion_tokens_details?.audio_tokens != null && {
|
|
244
|
+
audio: usage.completion_tokens_details.audio_tokens,
|
|
245
|
+
}),
|
|
246
|
+
...(usage.completion_tokens_details?.reasoning_tokens != null && {
|
|
247
|
+
reasoning: usage.completion_tokens_details.reasoning_tokens,
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
const generationChunk = new ChatGenerationChunk({
|
|
251
|
+
message: new AIMessageChunkClass({
|
|
252
|
+
content: '',
|
|
253
|
+
response_metadata: {
|
|
254
|
+
usage: { ...usage },
|
|
255
|
+
},
|
|
256
|
+
usage_metadata: {
|
|
257
|
+
input_tokens: usage.prompt_tokens,
|
|
258
|
+
output_tokens: usage.completion_tokens,
|
|
259
|
+
total_tokens: usage.total_tokens,
|
|
260
|
+
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
261
|
+
input_token_details: inputTokenDetails,
|
|
262
|
+
}),
|
|
263
|
+
...(Object.keys(outputTokenDetails).length > 0 && {
|
|
264
|
+
output_token_details: outputTokenDetails,
|
|
265
|
+
}),
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
text: '',
|
|
269
|
+
});
|
|
270
|
+
yield generationChunk;
|
|
271
|
+
if (this._lc_stream_delay != null) {
|
|
272
|
+
await new Promise((resolve) =>
|
|
273
|
+
setTimeout(resolve, this._lc_stream_delay)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (options.signal?.aborted === true) {
|
|
278
|
+
throw new Error('AbortError');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
60
281
|
}
|
package/src/llm/providers.ts
CHANGED
|
@@ -16,13 +16,11 @@ import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
|
16
16
|
import { CustomAnthropic } from '@/llm/anthropic';
|
|
17
17
|
import { ChatOpenRouter } from '@/llm/openrouter';
|
|
18
18
|
import { ChatVertexAI } from '@/llm/vertexai';
|
|
19
|
-
import { ChatOllama } from '@/llm/ollama';
|
|
20
19
|
import { Providers } from '@/common';
|
|
21
20
|
|
|
22
21
|
export const llmProviders: Partial<ChatModelConstructorMap> = {
|
|
23
22
|
[Providers.XAI]: ChatXAI,
|
|
24
23
|
[Providers.OPENAI]: ChatOpenAI,
|
|
25
|
-
[Providers.OLLAMA]: ChatOllama,
|
|
26
24
|
[Providers.AZURE]: AzureChatOpenAI,
|
|
27
25
|
[Providers.VERTEXAI]: ChatVertexAI,
|
|
28
26
|
[Providers.DEEPSEEK]: ChatDeepSeek,
|
|
@@ -38,7 +36,6 @@ export const llmProviders: Partial<ChatModelConstructorMap> = {
|
|
|
38
36
|
export const manualToolStreamProviders = new Set<Providers | string>([
|
|
39
37
|
Providers.ANTHROPIC,
|
|
40
38
|
Providers.BEDROCK,
|
|
41
|
-
Providers.OLLAMA,
|
|
42
39
|
]);
|
|
43
40
|
|
|
44
41
|
export const getChatModelClass = <P extends Providers>(
|
package/src/stream.ts
CHANGED
|
@@ -107,6 +107,25 @@ export function getChunkContent({
|
|
|
107
107
|
| undefined
|
|
108
108
|
)?.summary?.[0]?.text;
|
|
109
109
|
}
|
|
110
|
+
if (
|
|
111
|
+
provider === Providers.OPENROUTER &&
|
|
112
|
+
chunk?.additional_kwargs?.reasoning_details != null &&
|
|
113
|
+
Array.isArray(chunk.additional_kwargs.reasoning_details)
|
|
114
|
+
) {
|
|
115
|
+
// Extract text from reasoning_details array (for Gemini, DeepSeek, etc.)
|
|
116
|
+
const textEntries = chunk.additional_kwargs.reasoning_details
|
|
117
|
+
.filter(
|
|
118
|
+
(detail) =>
|
|
119
|
+
detail.type === 'reasoning.text' &&
|
|
120
|
+
detail.text != null &&
|
|
121
|
+
detail.text !== ''
|
|
122
|
+
)
|
|
123
|
+
.map((detail) => detail.text)
|
|
124
|
+
.join('');
|
|
125
|
+
if (textEntries) {
|
|
126
|
+
return textEntries;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
110
129
|
return (
|
|
111
130
|
((chunk?.additional_kwargs?.[reasoningKey] as string | undefined) ?? '') ||
|
|
112
131
|
chunk?.content
|
|
@@ -355,6 +374,13 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
355
374
|
reasoning_content.summary[0].text
|
|
356
375
|
) {
|
|
357
376
|
reasoning_content = 'valid';
|
|
377
|
+
} else if (
|
|
378
|
+
agentContext.provider === Providers.OPENROUTER &&
|
|
379
|
+
chunk.additional_kwargs?.reasoning_details != null &&
|
|
380
|
+
Array.isArray(chunk.additional_kwargs.reasoning_details) &&
|
|
381
|
+
chunk.additional_kwargs.reasoning_details.length > 0
|
|
382
|
+
) {
|
|
383
|
+
reasoning_content = 'valid';
|
|
358
384
|
}
|
|
359
385
|
if (
|
|
360
386
|
reasoning_content != null &&
|
package/src/types/llm.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/types/llm.ts
|
|
2
|
-
import { ChatOllama } from '@langchain/ollama';
|
|
3
2
|
import { ChatMistralAI } from '@langchain/mistralai';
|
|
4
3
|
import type {
|
|
5
4
|
BindToolsInput,
|
|
@@ -22,7 +21,6 @@ import type { RequestOptions } from '@google/generative-ai';
|
|
|
22
21
|
import type { StructuredTool } from '@langchain/core/tools';
|
|
23
22
|
import type { AnthropicInput } from '@langchain/anthropic';
|
|
24
23
|
import type { Runnable } from '@langchain/core/runnables';
|
|
25
|
-
import type { ChatOllamaInput } from '@langchain/ollama';
|
|
26
24
|
import type { OpenAI as OpenAIClient } from 'openai';
|
|
27
25
|
import type { ChatXAIInput } from '@langchain/xai';
|
|
28
26
|
import {
|
|
@@ -57,7 +55,6 @@ export type AnthropicReasoning = {
|
|
|
57
55
|
thinkingBudget?: number;
|
|
58
56
|
};
|
|
59
57
|
export type OpenAIClientOptions = ChatOpenAIFields;
|
|
60
|
-
export type OllamaClientOptions = ChatOllamaInput;
|
|
61
58
|
export type AnthropicClientOptions = AnthropicInput;
|
|
62
59
|
export type MistralAIClientOptions = ChatMistralAIInput;
|
|
63
60
|
export type VertexAIClientOptions = ChatVertexAIInput & {
|
|
@@ -80,7 +77,6 @@ export type XAIClientOptions = ChatXAIInput;
|
|
|
80
77
|
export type ClientOptions =
|
|
81
78
|
| OpenAIClientOptions
|
|
82
79
|
| AzureClientOptions
|
|
83
|
-
| OllamaClientOptions
|
|
84
80
|
| AnthropicClientOptions
|
|
85
81
|
| MistralAIClientOptions
|
|
86
82
|
| VertexAIClientOptions
|
|
@@ -103,7 +99,6 @@ export type LLMConfig = SharedLLMConfig &
|
|
|
103
99
|
export type ProviderOptionsMap = {
|
|
104
100
|
[Providers.AZURE]: AzureClientOptions;
|
|
105
101
|
[Providers.OPENAI]: OpenAIClientOptions;
|
|
106
|
-
[Providers.OLLAMA]: OllamaClientOptions;
|
|
107
102
|
[Providers.GOOGLE]: GoogleClientOptions;
|
|
108
103
|
[Providers.VERTEXAI]: VertexAIClientOptions;
|
|
109
104
|
[Providers.DEEPSEEK]: DeepSeekClientOptions;
|
|
@@ -118,7 +113,6 @@ export type ProviderOptionsMap = {
|
|
|
118
113
|
export type ChatModelMap = {
|
|
119
114
|
[Providers.XAI]: ChatXAI;
|
|
120
115
|
[Providers.OPENAI]: ChatOpenAI;
|
|
121
|
-
[Providers.OLLAMA]: ChatOllama;
|
|
122
116
|
[Providers.AZURE]: AzureChatOpenAI;
|
|
123
117
|
[Providers.DEEPSEEK]: ChatDeepSeek;
|
|
124
118
|
[Providers.VERTEXAI]: ChatVertexAI;
|