@librechat/agents 3.1.73 → 3.1.75-dev.0
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/README.md +66 -0
- package/dist/cjs/agents/AgentContext.cjs +146 -57
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +13 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +145 -52
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +25 -15
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +5 -4
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +468 -647
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +1 -448
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +57 -175
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +5 -3
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +39 -4
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +7 -6
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +7 -6
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +26 -0
- package/dist/cjs/messages/langchain.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +7 -6
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +21 -11
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +37 -10
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -11
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +5 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +147 -58
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +13 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +146 -54
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +25 -15
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +5 -4
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +469 -648
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +4 -449
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +57 -175
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +5 -3
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/cache.mjs +39 -4
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +7 -6
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +7 -6
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +23 -0
- package/dist/esm/messages/langchain.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +7 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +22 -12
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +37 -11
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -12
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +5 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +29 -4
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +46 -0
- package/dist/types/llm/anthropic/index.d.ts +22 -9
- package/dist/types/llm/anthropic/types.d.ts +5 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +21 -24
- package/dist/types/llm/openrouter/index.d.ts +11 -9
- package/dist/types/llm/vertexai/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/messages/langchain.d.ts +27 -0
- package/dist/types/tools/CodeExecutor.d.ts +6 -0
- package/dist/types/types/graph.d.ts +26 -38
- package/dist/types/types/llm.d.ts +3 -3
- package/dist/types/types/run.d.ts +2 -0
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/types/tools.d.ts +9 -0
- package/package.json +17 -16
- package/src/agents/AgentContext.ts +189 -71
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +116 -0
- package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +149 -0
- package/src/agents/__tests__/AgentContext.test.ts +333 -2
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +165 -0
- package/src/graphs/Graph.ts +24 -4
- package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
- package/src/llm/anthropic/index.ts +252 -84
- package/src/llm/anthropic/llm.spec.ts +751 -102
- package/src/llm/anthropic/types.ts +9 -1
- package/src/llm/anthropic/utils/message_inputs.ts +43 -20
- package/src/llm/anthropic/utils/message_outputs.ts +119 -101
- package/src/llm/anthropic/utils/server-tool-inputs.test.ts +77 -0
- package/src/llm/bedrock/index.ts +2 -2
- package/src/llm/bedrock/llm.spec.ts +341 -0
- package/src/llm/bedrock/utils/message_inputs.ts +303 -4
- package/src/llm/bedrock/utils/message_outputs.ts +2 -1
- package/src/llm/custom-chat-models.smoke.test.ts +662 -0
- package/src/llm/google/llm.spec.ts +339 -57
- package/src/llm/google/utils/common.ts +53 -48
- package/src/llm/openai/contentBlocks.test.ts +346 -0
- package/src/llm/openai/index.ts +736 -837
- package/src/llm/openai/utils/index.ts +84 -64
- package/src/llm/openrouter/index.ts +124 -247
- package/src/llm/openrouter/reasoning.test.ts +8 -1
- package/src/llm/vertexai/index.ts +11 -5
- package/src/llm/vertexai/llm.spec.ts +28 -1
- package/src/messages/cache.test.ts +106 -4
- package/src/messages/cache.ts +57 -5
- package/src/messages/core.ts +16 -9
- package/src/messages/format.ts +9 -6
- package/src/messages/langchain.ts +39 -0
- package/src/messages/prune.ts +12 -8
- package/src/scripts/caching.ts +2 -3
- package/src/specs/anthropic.simple.test.ts +61 -0
- package/src/specs/summarization.test.ts +58 -61
- package/src/tools/BashExecutor.ts +37 -13
- package/src/tools/CodeExecutor.ts +55 -11
- package/src/tools/ProgrammaticToolCalling.ts +29 -14
- package/src/tools/ToolNode.ts +5 -1
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +60 -0
- package/src/types/graph.ts +35 -88
- package/src/types/llm.ts +3 -3
- package/src/types/run.ts +2 -0
- package/src/types/stream.ts +1 -1
- package/src/types/tools.ts +9 -0
- package/src/utils/llmConfig.ts +1 -6
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
import { ChatOpenAI } from '@/llm/openai';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
FunctionMessageChunk,
|
|
7
|
-
SystemMessageChunk,
|
|
8
|
-
HumanMessageChunk,
|
|
9
|
-
ToolMessageChunk,
|
|
10
|
-
ChatMessageChunk,
|
|
11
|
-
AIMessageChunk,
|
|
12
|
-
BaseMessage,
|
|
13
|
-
} from '@langchain/core/messages';
|
|
2
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
4
|
+
import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
|
|
14
5
|
import type {
|
|
15
6
|
ChatOpenAICallOptions,
|
|
16
7
|
OpenAIChatInput,
|
|
17
8
|
OpenAIClient,
|
|
18
9
|
} 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';
|
|
31
10
|
|
|
32
11
|
export type OpenRouterReasoningEffort =
|
|
33
12
|
| 'xhigh'
|
|
@@ -52,6 +31,10 @@ export interface ChatOpenRouterCallOptions
|
|
|
52
31
|
modelKwargs?: OpenAIChatInput['modelKwargs'];
|
|
53
32
|
}
|
|
54
33
|
|
|
34
|
+
export type ChatOpenRouterInput = Partial<
|
|
35
|
+
ChatOpenRouterCallOptions & OpenAIChatInput
|
|
36
|
+
>;
|
|
37
|
+
|
|
55
38
|
/** invocationParams return type extended with OpenRouter reasoning */
|
|
56
39
|
export type OpenRouterInvocationParams = Omit<
|
|
57
40
|
OpenAIClient.Chat.ChatCompletionCreateParams,
|
|
@@ -59,12 +42,68 @@ export type OpenRouterInvocationParams = Omit<
|
|
|
59
42
|
> & {
|
|
60
43
|
reasoning?: OpenRouterReasoning;
|
|
61
44
|
};
|
|
45
|
+
|
|
46
|
+
type InvocationParamsExtra = {
|
|
47
|
+
streaming?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
interface OpenRouterReasoningTextDetail {
|
|
51
|
+
type: 'reasoning.text';
|
|
52
|
+
text?: string;
|
|
53
|
+
format?: string;
|
|
54
|
+
index?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface OpenRouterReasoningEncryptedDetail {
|
|
58
|
+
type: 'reasoning.encrypted';
|
|
59
|
+
id?: string;
|
|
60
|
+
data?: string;
|
|
61
|
+
format?: string;
|
|
62
|
+
index?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type OpenRouterReasoningDetail =
|
|
66
|
+
| OpenRouterReasoningTextDetail
|
|
67
|
+
| OpenRouterReasoningEncryptedDetail;
|
|
68
|
+
|
|
69
|
+
function isReasoningTextDetail(
|
|
70
|
+
value: unknown
|
|
71
|
+
): value is OpenRouterReasoningTextDetail {
|
|
72
|
+
return (
|
|
73
|
+
typeof value === 'object' &&
|
|
74
|
+
value !== null &&
|
|
75
|
+
'type' in value &&
|
|
76
|
+
value.type === 'reasoning.text'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isReasoningEncryptedDetail(
|
|
81
|
+
value: unknown
|
|
82
|
+
): value is OpenRouterReasoningEncryptedDetail {
|
|
83
|
+
return (
|
|
84
|
+
typeof value === 'object' &&
|
|
85
|
+
value !== null &&
|
|
86
|
+
'type' in value &&
|
|
87
|
+
value.type === 'reasoning.encrypted'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getReasoningDetails(value: unknown): OpenRouterReasoningDetail[] {
|
|
92
|
+
if (!Array.isArray(value)) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
return value.filter(
|
|
96
|
+
(detail): detail is OpenRouterReasoningDetail =>
|
|
97
|
+
isReasoningTextDetail(detail) || isReasoningEncryptedDetail(detail)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
62
101
|
export class ChatOpenRouter extends ChatOpenAI {
|
|
63
102
|
private openRouterReasoning?: OpenRouterReasoning;
|
|
64
103
|
/** @deprecated Use `reasoning` object instead */
|
|
65
104
|
private includeReasoning?: boolean;
|
|
66
105
|
|
|
67
|
-
constructor(_fields:
|
|
106
|
+
constructor(_fields: ChatOpenRouterInput) {
|
|
68
107
|
const {
|
|
69
108
|
include_reasoning,
|
|
70
109
|
reasoning: openRouterReasoning,
|
|
@@ -80,6 +119,8 @@ export class ChatOpenRouter extends ChatOpenAI {
|
|
|
80
119
|
super({
|
|
81
120
|
...fields,
|
|
82
121
|
modelKwargs: restModelKwargs,
|
|
122
|
+
includeReasoningDetails: true,
|
|
123
|
+
convertReasoningDetailsToContent: true,
|
|
83
124
|
});
|
|
84
125
|
|
|
85
126
|
// Merge reasoning config: modelKwargs.reasoning < constructor reasoning
|
|
@@ -101,21 +142,26 @@ export class ChatOpenRouter extends ChatOpenAI {
|
|
|
101
142
|
// The parent's generic conditional return type cannot be widened in an override.
|
|
102
143
|
override invocationParams(
|
|
103
144
|
options?: this['ParsedCallOptions'],
|
|
104
|
-
extra?:
|
|
145
|
+
extra?: InvocationParamsExtra
|
|
105
146
|
): OpenRouterInvocationParams {
|
|
106
147
|
type MutableParams = Omit<
|
|
107
148
|
OpenAIClient.Chat.ChatCompletionCreateParams,
|
|
108
149
|
'messages'
|
|
109
150
|
> & { reasoning_effort?: string; reasoning?: OpenRouterReasoning };
|
|
110
151
|
|
|
111
|
-
const
|
|
152
|
+
const optionsWithDefaults = this._combineCallOptions(options);
|
|
153
|
+
const params = (
|
|
154
|
+
this._useResponsesApi(options)
|
|
155
|
+
? this.responses.invocationParams(optionsWithDefaults)
|
|
156
|
+
: this.completions.invocationParams(optionsWithDefaults, extra)
|
|
157
|
+
) as MutableParams;
|
|
112
158
|
|
|
113
159
|
// Remove the OpenAI-native reasoning_effort that the parent sets;
|
|
114
160
|
// OpenRouter uses a `reasoning` object instead
|
|
115
161
|
delete params.reasoning_effort;
|
|
116
162
|
|
|
117
163
|
// Build the OpenRouter reasoning config
|
|
118
|
-
const reasoning = this.buildOpenRouterReasoning(
|
|
164
|
+
const reasoning = this.buildOpenRouterReasoning(optionsWithDefaults);
|
|
119
165
|
if (reasoning != null) {
|
|
120
166
|
params.reasoning = reasoning;
|
|
121
167
|
} else {
|
|
@@ -158,245 +204,76 @@ export class ChatOpenRouter extends ChatOpenAI {
|
|
|
158
204
|
|
|
159
205
|
return reasoning;
|
|
160
206
|
}
|
|
161
|
-
protected override _convertOpenAIDeltaToBaseMessageChunk(
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
|
-
delta: Record<string, any>,
|
|
164
|
-
rawResponse: OpenAIClient.ChatCompletionChunk,
|
|
165
|
-
defaultRole?:
|
|
166
|
-
| 'function'
|
|
167
|
-
| 'user'
|
|
168
|
-
| 'system'
|
|
169
|
-
| 'developer'
|
|
170
|
-
| 'assistant'
|
|
171
|
-
| 'tool'
|
|
172
|
-
):
|
|
173
|
-
| AIMessageChunk
|
|
174
|
-
| HumanMessageChunk
|
|
175
|
-
| SystemMessageChunk
|
|
176
|
-
| FunctionMessageChunk
|
|
177
|
-
| ToolMessageChunk
|
|
178
|
-
| ChatMessageChunk {
|
|
179
|
-
const messageChunk = super._convertOpenAIDeltaToBaseMessageChunk(
|
|
180
|
-
delta,
|
|
181
|
-
rawResponse,
|
|
182
|
-
defaultRole
|
|
183
|
-
);
|
|
184
|
-
if (delta.reasoning != null) {
|
|
185
|
-
messageChunk.additional_kwargs.reasoning = delta.reasoning;
|
|
186
|
-
}
|
|
187
|
-
if (delta.reasoning_details != null) {
|
|
188
|
-
messageChunk.additional_kwargs.reasoning_details =
|
|
189
|
-
delta.reasoning_details;
|
|
190
|
-
}
|
|
191
|
-
return messageChunk;
|
|
192
|
-
}
|
|
193
207
|
|
|
194
|
-
async *
|
|
208
|
+
override async *_streamResponseChunks(
|
|
195
209
|
messages: BaseMessage[],
|
|
196
210
|
options: this['ParsedCallOptions'],
|
|
197
211
|
runManager?: CallbackManagerForLLMRun
|
|
198
212
|
): AsyncGenerator<ChatGenerationChunk> {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
messages
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Store reasoning_details keyed by unique identifier to prevent incorrect merging
|
|
218
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
-
const reasoningTextByIndex: Map<number, Record<string, any>> = new Map();
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
-
const reasoningEncryptedById: Map<string, Record<string, any>> = new Map();
|
|
222
|
-
|
|
223
|
-
for await (const data of streamIterable) {
|
|
224
|
-
const choice = data.choices[0] as
|
|
225
|
-
| Partial<OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice>
|
|
226
|
-
| undefined;
|
|
227
|
-
if (data.usage) {
|
|
228
|
-
usage = data.usage;
|
|
229
|
-
}
|
|
230
|
-
if (!choice) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const { delta } = choice;
|
|
235
|
-
if (!delta) {
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
213
|
+
const reasoningTextByIndex = new Map<
|
|
214
|
+
number,
|
|
215
|
+
OpenRouterReasoningTextDetail
|
|
216
|
+
>();
|
|
217
|
+
const reasoningEncryptedById = new Map<
|
|
218
|
+
string,
|
|
219
|
+
OpenRouterReasoningEncryptedDetail
|
|
220
|
+
>();
|
|
221
|
+
|
|
222
|
+
for await (const generationChunk of super._streamResponseChunks(
|
|
223
|
+
messages,
|
|
224
|
+
options,
|
|
225
|
+
runManager
|
|
226
|
+
)) {
|
|
227
|
+
let currentReasoningText = '';
|
|
228
|
+
const reasoningDetails = getReasoningDetails(
|
|
229
|
+
generationChunk.message.additional_kwargs.reasoning_details
|
|
230
|
+
);
|
|
238
231
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
) {
|
|
248
|
-
for (const detail of deltaAny.reasoning_details) {
|
|
249
|
-
// For encrypted reasoning (thought signatures), store by ID - MUST be separate
|
|
250
|
-
if (detail.type === 'reasoning.encrypted' && detail.id) {
|
|
251
|
-
reasoningEncryptedById.set(detail.id, {
|
|
252
|
-
type: detail.type,
|
|
253
|
-
id: detail.id,
|
|
254
|
-
data: detail.data,
|
|
255
|
-
format: detail.format,
|
|
256
|
-
index: detail.index,
|
|
257
|
-
});
|
|
258
|
-
} else if (detail.type === 'reasoning.text') {
|
|
259
|
-
// Extract current chunk's text for streaming
|
|
260
|
-
currentChunkReasoningText += detail.text || '';
|
|
261
|
-
// For text reasoning, accumulate text by index for final message
|
|
262
|
-
const idx = detail.index ?? 0;
|
|
263
|
-
const existing = reasoningTextByIndex.get(idx);
|
|
264
|
-
if (existing) {
|
|
265
|
-
// Only append text, keep other fields from first entry
|
|
266
|
-
existing.text = (existing.text || '') + (detail.text || '');
|
|
267
|
-
} else {
|
|
268
|
-
reasoningTextByIndex.set(idx, {
|
|
269
|
-
type: detail.type,
|
|
270
|
-
text: detail.text || '',
|
|
271
|
-
format: detail.format,
|
|
272
|
-
index: idx,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
232
|
+
for (const detail of reasoningDetails) {
|
|
233
|
+
if (detail.type === 'reasoning.text') {
|
|
234
|
+
currentReasoningText += detail.text ?? '';
|
|
235
|
+
const index = detail.index ?? 0;
|
|
236
|
+
const existing = reasoningTextByIndex.get(index);
|
|
237
|
+
if (existing != null) {
|
|
238
|
+
existing.text = `${existing.text ?? ''}${detail.text ?? ''}`;
|
|
239
|
+
continue;
|
|
275
240
|
}
|
|
241
|
+
reasoningTextByIndex.set(index, {
|
|
242
|
+
...detail,
|
|
243
|
+
text: detail.text ?? '',
|
|
244
|
+
});
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (detail.id != null) {
|
|
248
|
+
reasoningEncryptedById.set(detail.id, { ...detail });
|
|
276
249
|
}
|
|
277
250
|
}
|
|
278
251
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// For models that send reasoning_details (Gemini style) instead of reasoning (DeepSeek style),
|
|
286
|
-
// set the current chunk's reasoning text to additional_kwargs.reasoning for streaming
|
|
287
|
-
if (currentChunkReasoningText && !chunk.additional_kwargs.reasoning) {
|
|
288
|
-
chunk.additional_kwargs.reasoning = currentChunkReasoningText;
|
|
252
|
+
if (
|
|
253
|
+
currentReasoningText.length > 0 &&
|
|
254
|
+
generationChunk.message.additional_kwargs.reasoning == null
|
|
255
|
+
) {
|
|
256
|
+
generationChunk.message.additional_kwargs.reasoning =
|
|
257
|
+
currentReasoningText;
|
|
289
258
|
}
|
|
290
259
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// Check if this is the final chunk (has finish_reason)
|
|
294
|
-
if (choice.finish_reason != null) {
|
|
295
|
-
// Build properly structured reasoning_details array
|
|
296
|
-
// Text entries first (but we only need the encrypted ones for thought signatures)
|
|
297
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
298
|
-
const finalReasoningDetails: Record<string, any>[] = [
|
|
260
|
+
if (generationChunk.generationInfo?.finish_reason != null) {
|
|
261
|
+
const finalReasoningDetails = [
|
|
299
262
|
...reasoningTextByIndex.values(),
|
|
300
263
|
...reasoningEncryptedById.values(),
|
|
301
264
|
];
|
|
302
|
-
|
|
303
265
|
if (finalReasoningDetails.length > 0) {
|
|
304
|
-
|
|
266
|
+
generationChunk.message.additional_kwargs.reasoning_details =
|
|
267
|
+
finalReasoningDetails;
|
|
268
|
+
} else {
|
|
269
|
+
delete generationChunk.message.additional_kwargs.reasoning_details;
|
|
305
270
|
}
|
|
306
|
-
|
|
307
|
-
// Clear reasoning_details from intermediate chunks to prevent concatenation issues
|
|
308
|
-
delete chunk.additional_kwargs.reasoning_details;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
defaultRole = delta.role ?? defaultRole;
|
|
312
|
-
const newTokenIndices = {
|
|
313
|
-
prompt: options.promptIndex ?? 0,
|
|
314
|
-
completion: choice.index ?? 0,
|
|
315
|
-
};
|
|
316
|
-
if (typeof chunk.content !== 'string') {
|
|
317
|
-
// eslint-disable-next-line no-console
|
|
318
|
-
console.log(
|
|
319
|
-
'[WARNING]: Received non-string content from OpenAI. This is currently not supported.'
|
|
320
|
-
);
|
|
271
|
+
yield generationChunk;
|
|
321
272
|
continue;
|
|
322
273
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (choice.finish_reason != null) {
|
|
326
|
-
generationInfo.finish_reason = choice.finish_reason;
|
|
327
|
-
generationInfo.system_fingerprint = data.system_fingerprint;
|
|
328
|
-
generationInfo.model_name = data.model;
|
|
329
|
-
generationInfo.service_tier = data.service_tier;
|
|
330
|
-
}
|
|
331
|
-
if (this.logprobs == true) {
|
|
332
|
-
generationInfo.logprobs = choice.logprobs;
|
|
333
|
-
}
|
|
334
|
-
const generationChunk = new ChatGenerationChunk({
|
|
335
|
-
message: chunk,
|
|
336
|
-
text: chunk.content,
|
|
337
|
-
generationInfo,
|
|
338
|
-
});
|
|
339
|
-
yield generationChunk;
|
|
340
|
-
if (this._lc_stream_delay != null) {
|
|
341
|
-
await new Promise((resolve) =>
|
|
342
|
-
setTimeout(resolve, this._lc_stream_delay)
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
await runManager?.handleLLMNewToken(
|
|
346
|
-
generationChunk.text || '',
|
|
347
|
-
newTokenIndices,
|
|
348
|
-
undefined,
|
|
349
|
-
undefined,
|
|
350
|
-
undefined,
|
|
351
|
-
{ chunk: generationChunk }
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
if (usage) {
|
|
355
|
-
const inputTokenDetails = {
|
|
356
|
-
...(usage.prompt_tokens_details?.audio_tokens != null && {
|
|
357
|
-
audio: usage.prompt_tokens_details.audio_tokens,
|
|
358
|
-
}),
|
|
359
|
-
...(usage.prompt_tokens_details?.cached_tokens != null && {
|
|
360
|
-
cache_read: usage.prompt_tokens_details.cached_tokens,
|
|
361
|
-
}),
|
|
362
|
-
};
|
|
363
|
-
const outputTokenDetails = {
|
|
364
|
-
...(usage.completion_tokens_details?.audio_tokens != null && {
|
|
365
|
-
audio: usage.completion_tokens_details.audio_tokens,
|
|
366
|
-
}),
|
|
367
|
-
...(usage.completion_tokens_details?.reasoning_tokens != null && {
|
|
368
|
-
reasoning: usage.completion_tokens_details.reasoning_tokens,
|
|
369
|
-
}),
|
|
370
|
-
};
|
|
371
|
-
const generationChunk = new ChatGenerationChunk({
|
|
372
|
-
message: new AIMessageChunkClass({
|
|
373
|
-
content: '',
|
|
374
|
-
response_metadata: {
|
|
375
|
-
usage: { ...usage },
|
|
376
|
-
},
|
|
377
|
-
usage_metadata: {
|
|
378
|
-
input_tokens: usage.prompt_tokens,
|
|
379
|
-
output_tokens: usage.completion_tokens,
|
|
380
|
-
total_tokens: usage.total_tokens,
|
|
381
|
-
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
382
|
-
input_token_details: inputTokenDetails,
|
|
383
|
-
}),
|
|
384
|
-
...(Object.keys(outputTokenDetails).length > 0 && {
|
|
385
|
-
output_token_details: outputTokenDetails,
|
|
386
|
-
}),
|
|
387
|
-
},
|
|
388
|
-
}),
|
|
389
|
-
text: '',
|
|
390
|
-
});
|
|
274
|
+
|
|
275
|
+
delete generationChunk.message.additional_kwargs.reasoning_details;
|
|
391
276
|
yield generationChunk;
|
|
392
|
-
if (this._lc_stream_delay != null) {
|
|
393
|
-
await new Promise((resolve) =>
|
|
394
|
-
setTimeout(resolve, this._lc_stream_delay)
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (options.signal?.aborted === true) {
|
|
399
|
-
throw new Error('AbortError');
|
|
400
277
|
}
|
|
401
278
|
}
|
|
402
279
|
}
|
|
@@ -3,7 +3,8 @@ import type { OpenRouterReasoning, ChatOpenRouterCallOptions } from './index';
|
|
|
3
3
|
import type { OpenAIChatInput } from '@langchain/openai';
|
|
4
4
|
|
|
5
5
|
type CreateRouterOptions = Partial<
|
|
6
|
-
ChatOpenRouterCallOptions &
|
|
6
|
+
ChatOpenRouterCallOptions &
|
|
7
|
+
Pick<OpenAIChatInput, 'model' | 'apiKey' | 'streamUsage'>
|
|
7
8
|
>;
|
|
8
9
|
|
|
9
10
|
function createRouter(overrides: CreateRouterOptions = {}): ChatOpenRouter {
|
|
@@ -96,6 +97,12 @@ describe('ChatOpenRouter reasoning handling', () => {
|
|
|
96
97
|
expect(params.reasoning).toBeUndefined();
|
|
97
98
|
expect(params.reasoning_effort).toBeUndefined();
|
|
98
99
|
});
|
|
100
|
+
|
|
101
|
+
it('preserves streaming extras from parent invocation params', () => {
|
|
102
|
+
const router = createRouter({ streamUsage: true });
|
|
103
|
+
const params = router.invocationParams(undefined, { streaming: true });
|
|
104
|
+
expect(params.stream_options).toEqual({ include_usage: true });
|
|
105
|
+
});
|
|
99
106
|
});
|
|
100
107
|
|
|
101
108
|
// ---------------------------------------------------------------
|
|
@@ -97,10 +97,7 @@ class CustomChatConnection extends ChatConnection<VertexAIClientOptions> {
|
|
|
97
97
|
}
|
|
98
98
|
delete formattedData.generationConfig.thinkingConfig.thinkingBudget;
|
|
99
99
|
}
|
|
100
|
-
if (
|
|
101
|
-
this.thinkingConfig?.thinkingLevel != null &&
|
|
102
|
-
this.thinkingConfig.thinkingLevel !== ''
|
|
103
|
-
) {
|
|
100
|
+
if (this.thinkingConfig?.thinkingLevel != null) {
|
|
104
101
|
formattedData.generationConfig ??= {};
|
|
105
102
|
// thinkingLevel and thinkingBudget cannot coexist — the API rejects the request.
|
|
106
103
|
// Remove thinkingBudget when thinkingLevel is set.
|
|
@@ -422,7 +419,16 @@ export class ChatVertexAI extends ChatGoogle {
|
|
|
422
419
|
return 'LibreChatVertexAI';
|
|
423
420
|
}
|
|
424
421
|
|
|
425
|
-
constructor(fields?: VertexAIClientOptions)
|
|
422
|
+
constructor(model: string, fields?: Omit<VertexAIClientOptions, 'model'>);
|
|
423
|
+
constructor(fields?: VertexAIClientOptions);
|
|
424
|
+
constructor(
|
|
425
|
+
modelOrFields?: string | VertexAIClientOptions,
|
|
426
|
+
params?: Omit<VertexAIClientOptions, 'model'>
|
|
427
|
+
) {
|
|
428
|
+
const fields =
|
|
429
|
+
typeof modelOrFields === 'string'
|
|
430
|
+
? { ...(params ?? {}), model: modelOrFields }
|
|
431
|
+
: modelOrFields;
|
|
426
432
|
const dynamicThinkingBudget = fields?.thinkingBudget === -1;
|
|
427
433
|
super({
|
|
428
434
|
...fields,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { config } from 'dotenv';
|
|
2
2
|
config();
|
|
3
|
-
import { test, describe, jest } from '@jest/globals';
|
|
3
|
+
import { expect, test, describe, jest } from '@jest/globals';
|
|
4
4
|
|
|
5
5
|
jest.setTimeout(90000);
|
|
6
6
|
import {
|
|
@@ -26,6 +26,33 @@ const weatherTool = tool(async () => 'The weather is 80 degrees and sunny', {
|
|
|
26
26
|
}),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
describe('ChatVertexAI upstream compatibility', () => {
|
|
30
|
+
test('serialization uses the LibreChat constructor name on the Vertex namespace', () => {
|
|
31
|
+
const model = new ChatVertexAI();
|
|
32
|
+
expect(JSON.stringify(model)).toEqual(
|
|
33
|
+
'{"lc":1,"type":"constructor","id":["langchain","chat_models","vertexai","LibreChatVertexAI"],"kwargs":{"platform_type":"gcp"}}'
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('labels parameter support', () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
const model = new ChatVertexAI({
|
|
40
|
+
labels: {
|
|
41
|
+
team: 'test',
|
|
42
|
+
environment: 'development',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
expect(model.platform).toEqual('gcp');
|
|
46
|
+
}).not.toThrow();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('constructor overload supports model string', () => {
|
|
50
|
+
const model = new ChatVertexAI('gemini-1.5-pro');
|
|
51
|
+
expect(model.model).toEqual('gemini-1.5-pro');
|
|
52
|
+
expect(model.platform).toEqual('gcp');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
29
56
|
describe.each(gemini3Models)(
|
|
30
57
|
'Vertex AI reasoning with thinkingLevel (%s)',
|
|
31
58
|
(modelName) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AIMessage,
|
|
3
3
|
BaseMessage,
|
|
4
|
-
ToolMessage,
|
|
5
4
|
HumanMessage,
|
|
6
|
-
|
|
5
|
+
SystemMessage,
|
|
6
|
+
ToolMessage,
|
|
7
7
|
} from '@langchain/core/messages';
|
|
8
|
+
import type { MessageContentComplex } from '@langchain/core/messages';
|
|
8
9
|
import type Anthropic from '@anthropic-ai/sdk';
|
|
9
10
|
import type { AnthropicMessages } from '@/types/messages';
|
|
10
11
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
addBedrockCacheControl,
|
|
14
15
|
addCacheControl,
|
|
15
16
|
} from './cache';
|
|
17
|
+
import { toLangChainContent } from './langchain';
|
|
16
18
|
import { ContentTypes } from '@/common/enum';
|
|
17
19
|
|
|
18
20
|
describe('addCacheControl', () => {
|
|
@@ -404,7 +406,107 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
|
|
|
404
406
|
expect(first[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
405
407
|
});
|
|
406
408
|
|
|
407
|
-
it('
|
|
409
|
+
it('preserves LangChain system message content unchanged', () => {
|
|
410
|
+
const systemContent = [
|
|
411
|
+
{ type: ContentTypes.TEXT, text: 'Stable system text' },
|
|
412
|
+
{ cachePoint: { type: 'default' } },
|
|
413
|
+
{ type: ContentTypes.TEXT, text: 'Dynamic system text' },
|
|
414
|
+
] as MessageContentComplex[];
|
|
415
|
+
const messages: BaseMessage[] = [
|
|
416
|
+
new SystemMessage({ content: toLangChainContent(systemContent) }),
|
|
417
|
+
new HumanMessage('Hello'),
|
|
418
|
+
new AIMessage('Hi'),
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
const result = addBedrockCacheControl(messages);
|
|
422
|
+
|
|
423
|
+
expect(result[0]).toBe(messages[0]);
|
|
424
|
+
expect(result[0].content).toEqual(systemContent);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('preserves serialized system message content unchanged', () => {
|
|
428
|
+
const systemContent = [
|
|
429
|
+
{ type: ContentTypes.TEXT, text: 'Stable system text' },
|
|
430
|
+
{ cachePoint: { type: 'default' } },
|
|
431
|
+
{ type: ContentTypes.TEXT, text: 'Dynamic system text' },
|
|
432
|
+
] as MessageContentComplex[];
|
|
433
|
+
const messages: TestMsg[] = [
|
|
434
|
+
{ role: 'system', content: systemContent },
|
|
435
|
+
{ role: 'user', content: 'Hello' },
|
|
436
|
+
{ role: 'assistant', content: 'Hi' },
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
const result = addBedrockCacheControl(messages);
|
|
440
|
+
|
|
441
|
+
expect(result[0]).toBe(messages[0]);
|
|
442
|
+
expect(result[0].content).toEqual(systemContent);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('strips Anthropic cache_control from LangChain system messages without moving cache points', () => {
|
|
446
|
+
const systemContent = [
|
|
447
|
+
{
|
|
448
|
+
type: ContentTypes.TEXT,
|
|
449
|
+
text: 'Stable system text',
|
|
450
|
+
cache_control: { type: 'ephemeral' },
|
|
451
|
+
} as MessageContentComplex,
|
|
452
|
+
{ cachePoint: { type: 'default' } },
|
|
453
|
+
{
|
|
454
|
+
type: ContentTypes.TEXT,
|
|
455
|
+
text: 'Dynamic system text',
|
|
456
|
+
cache_control: { type: 'ephemeral' },
|
|
457
|
+
} as MessageContentComplex,
|
|
458
|
+
] as MessageContentComplex[];
|
|
459
|
+
const messages: BaseMessage[] = [
|
|
460
|
+
new SystemMessage({ content: toLangChainContent(systemContent) }),
|
|
461
|
+
new HumanMessage('Hello'),
|
|
462
|
+
new AIMessage('Hi'),
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
const result = addBedrockCacheControl(messages);
|
|
466
|
+
|
|
467
|
+
expect(result[0]).not.toBe(messages[0]);
|
|
468
|
+
expect(result[0].content).toEqual([
|
|
469
|
+
{ type: ContentTypes.TEXT, text: 'Stable system text' },
|
|
470
|
+
{ cachePoint: { type: 'default' } },
|
|
471
|
+
{ type: ContentTypes.TEXT, text: 'Dynamic system text' },
|
|
472
|
+
]);
|
|
473
|
+
expect(systemContent[0]).toHaveProperty('cache_control');
|
|
474
|
+
expect(systemContent[2]).toHaveProperty('cache_control');
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('strips Anthropic cache_control from serialized system messages without moving cache points', () => {
|
|
478
|
+
const systemContent = [
|
|
479
|
+
{
|
|
480
|
+
type: ContentTypes.TEXT,
|
|
481
|
+
text: 'Stable system text',
|
|
482
|
+
cache_control: { type: 'ephemeral' },
|
|
483
|
+
} as MessageContentComplex,
|
|
484
|
+
{ cachePoint: { type: 'default' } },
|
|
485
|
+
{
|
|
486
|
+
type: ContentTypes.TEXT,
|
|
487
|
+
text: 'Dynamic system text',
|
|
488
|
+
cache_control: { type: 'ephemeral' },
|
|
489
|
+
} as MessageContentComplex,
|
|
490
|
+
] as MessageContentComplex[];
|
|
491
|
+
const messages: TestMsg[] = [
|
|
492
|
+
{ role: 'system', content: systemContent },
|
|
493
|
+
{ role: 'user', content: 'Hello' },
|
|
494
|
+
{ role: 'assistant', content: 'Hi' },
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
const result = addBedrockCacheControl(messages);
|
|
498
|
+
|
|
499
|
+
expect(result[0]).not.toBe(messages[0]);
|
|
500
|
+
expect(result[0].content).toEqual([
|
|
501
|
+
{ type: ContentTypes.TEXT, text: 'Stable system text' },
|
|
502
|
+
{ cachePoint: { type: 'default' } },
|
|
503
|
+
{ type: ContentTypes.TEXT, text: 'Dynamic system text' },
|
|
504
|
+
]);
|
|
505
|
+
expect(systemContent[0]).toHaveProperty('cache_control');
|
|
506
|
+
expect(systemContent[2]).toHaveProperty('cache_control');
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('skips serialized system messages while adding cache points to non-system turns', () => {
|
|
408
510
|
const messages: TestMsg[] = [
|
|
409
511
|
{
|
|
410
512
|
role: 'system',
|
|
@@ -429,7 +531,7 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
|
|
|
429
531
|
type: ContentTypes.TEXT,
|
|
430
532
|
text: 'You\'re an advanced AI assistant.',
|
|
431
533
|
});
|
|
432
|
-
expect(system
|
|
534
|
+
expect(system).toHaveLength(1);
|
|
433
535
|
expect(user[0]).toEqual({
|
|
434
536
|
type: ContentTypes.TEXT,
|
|
435
537
|
text: 'What is the capital of France?',
|