@librechat/agents 3.6.11 → 3.6.12
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/graphs/Graph.cjs +44 -116
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/contextPressureMeter.cjs +132 -0
- package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -0
- package/dist/cjs/llm/invoke.cjs +46 -75
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +108 -0
- package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -0
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/messages/content.cjs +5 -4
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +47 -119
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/contextPressureMeter.mjs +132 -0
- package/dist/esm/llm/contextPressureMeter.mjs.map +1 -0
- package/dist/esm/llm/invoke.mjs +48 -75
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +105 -0
- package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -0
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/messages/content.mjs +5 -4
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/llm/contextPressureMeter.d.ts +30 -0
- package/dist/types/llm/invoke.d.ts +31 -33
- package/dist/types/llm/prepareProviderRequest.d.ts +55 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +69 -276
- package/src/index.ts +8 -0
- package/src/llm/contextPressureMeter.ts +284 -0
- package/src/llm/invoke.ts +136 -296
- package/src/llm/prepareProviderRequest.ts +357 -0
- package/src/messages/content.ts +3 -3
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
4
|
+
import type * as t from '@/types';
|
|
5
|
+
import {
|
|
6
|
+
projectCacheControlledToolOutputsToText,
|
|
7
|
+
projectComputerCallOutputsToText,
|
|
8
|
+
projectOpenAIChatToolMessageContent,
|
|
9
|
+
projectOpenAIResponsesToolMessageContent,
|
|
10
|
+
projectOpenRouterToolMessageContent,
|
|
11
|
+
projectSingleTextToolOutputsToText,
|
|
12
|
+
projectStructuredToolOutputsToText,
|
|
13
|
+
projectToolStreamContentForProvider,
|
|
14
|
+
} from '@/messages/core';
|
|
15
|
+
import {
|
|
16
|
+
coalesceAdjacentUserTurns,
|
|
17
|
+
strictAlternationProviders,
|
|
18
|
+
appendPredecessorHandoffCue,
|
|
19
|
+
removePredecessorHandoffCue,
|
|
20
|
+
} from '@/messages';
|
|
21
|
+
import {
|
|
22
|
+
stripAnthropicCacheControl,
|
|
23
|
+
stripBedrockCacheControl,
|
|
24
|
+
} from '@/messages/cache';
|
|
25
|
+
import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
|
|
26
|
+
import { Providers } from '@/common';
|
|
27
|
+
import { isAnthropicLike, isOpenAILike } from '@/utils/llm';
|
|
28
|
+
|
|
29
|
+
const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
|
|
30
|
+
|
|
31
|
+
export type ProviderMessageProjectionMode =
|
|
32
|
+
| 'chat-messages'
|
|
33
|
+
| 'openai-responses';
|
|
34
|
+
|
|
35
|
+
export interface ProviderPayloadMeasurement {
|
|
36
|
+
readonly fits: boolean;
|
|
37
|
+
readonly projectedMessageTokens?: number;
|
|
38
|
+
readonly availableMessageTokens?: number;
|
|
39
|
+
readonly contextBudget?: number;
|
|
40
|
+
readonly effectiveInstructionTokens?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PreparedProviderRequest {
|
|
44
|
+
readonly model: t.ChatModel;
|
|
45
|
+
readonly modelId?: string;
|
|
46
|
+
readonly provider: Providers;
|
|
47
|
+
readonly projectionMode: ProviderMessageProjectionMode;
|
|
48
|
+
readonly messages: BaseMessage[];
|
|
49
|
+
readonly measurement?: ProviderPayloadMeasurement;
|
|
50
|
+
readonly [preparedProviderRequestBrand]: true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type PreparedProviderRequestData = Omit<
|
|
54
|
+
PreparedProviderRequest,
|
|
55
|
+
typeof preparedProviderRequestBrand
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
export interface ProviderRequestContext {
|
|
59
|
+
getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;
|
|
60
|
+
isRunProducedMessage?(message: BaseMessage): boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PrepareProviderRequestParams {
|
|
64
|
+
model: t.ChatModel;
|
|
65
|
+
messages: BaseMessage[];
|
|
66
|
+
provider: Providers;
|
|
67
|
+
context?: ProviderRequestContext;
|
|
68
|
+
config?: RunnableConfig;
|
|
69
|
+
maxToolResultChars?: number;
|
|
70
|
+
measure?: (messages: BaseMessage[]) => ProviderPayloadMeasurement;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function usesNativeOpenAIResponses(
|
|
74
|
+
model: t.ChatModel,
|
|
75
|
+
provider: Providers,
|
|
76
|
+
callOptions?: unknown
|
|
77
|
+
): boolean {
|
|
78
|
+
if (!isOpenAILike(provider)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
let candidate: unknown = model;
|
|
82
|
+
let effectiveCallOptions = callOptions;
|
|
83
|
+
const seen = new Set<object>();
|
|
84
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
85
|
+
if (candidate == null || typeof candidate !== 'object') {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (seen.has(candidate)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
seen.add(candidate);
|
|
92
|
+
const runnable = candidate as {
|
|
93
|
+
_useResponsesApi?: (options?: unknown) => boolean;
|
|
94
|
+
bound?: unknown;
|
|
95
|
+
defaultOptions?: unknown;
|
|
96
|
+
last?: unknown;
|
|
97
|
+
constructor?: { name?: unknown };
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
if (
|
|
101
|
+
runnable.defaultOptions != null &&
|
|
102
|
+
typeof runnable.defaultOptions === 'object' &&
|
|
103
|
+
!Array.isArray(runnable.defaultOptions) &&
|
|
104
|
+
effectiveCallOptions != null &&
|
|
105
|
+
typeof effectiveCallOptions === 'object' &&
|
|
106
|
+
!Array.isArray(effectiveCallOptions)
|
|
107
|
+
) {
|
|
108
|
+
effectiveCallOptions = {
|
|
109
|
+
...(runnable.defaultOptions as Record<string, unknown>),
|
|
110
|
+
...(effectiveCallOptions as Record<string, unknown>),
|
|
111
|
+
};
|
|
112
|
+
} else if (effectiveCallOptions == null) {
|
|
113
|
+
effectiveCallOptions = runnable.defaultOptions;
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
runnable._useResponsesApi?.(effectiveCallOptions) === true ||
|
|
117
|
+
runnable._useResponsesApi?.(undefined) === true
|
|
118
|
+
) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Continue through RunnableSequence/RunnableBinding wrappers.
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
typeof runnable.constructor?.name === 'string' &&
|
|
126
|
+
runnable.constructor.name.includes('Responses')
|
|
127
|
+
) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (runnable.last != null && typeof runnable.last === 'object') {
|
|
131
|
+
candidate = runnable.last;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (runnable.bound != null && typeof runnable.bound === 'object') {
|
|
135
|
+
candidate = runnable.bound;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveProviderMessageProjectionMode(
|
|
144
|
+
model: t.ChatModel,
|
|
145
|
+
provider: Providers,
|
|
146
|
+
callOptions?: unknown
|
|
147
|
+
): ProviderMessageProjectionMode {
|
|
148
|
+
return usesNativeOpenAIResponses(model, provider, callOptions)
|
|
149
|
+
? 'openai-responses'
|
|
150
|
+
: 'chat-messages';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface ProjectMessagesForProviderParams {
|
|
154
|
+
model: t.ChatModel;
|
|
155
|
+
messages: BaseMessage[];
|
|
156
|
+
provider: Providers;
|
|
157
|
+
maxToolResultChars?: number;
|
|
158
|
+
callOptions?: unknown;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function projectMessagesForProviderMode({
|
|
162
|
+
messages,
|
|
163
|
+
provider,
|
|
164
|
+
maxToolResultChars,
|
|
165
|
+
}: ProjectMessagesForProviderParams,
|
|
166
|
+
projectionMode: ProviderMessageProjectionMode
|
|
167
|
+
): BaseMessage[] {
|
|
168
|
+
const nativeOpenAIResponses = projectionMode === 'openai-responses';
|
|
169
|
+
const providerInputMessages = projectToolStreamContentForProvider(
|
|
170
|
+
messages,
|
|
171
|
+
nativeOpenAIResponses ? 'native' : 'fallback',
|
|
172
|
+
maxToolResultChars
|
|
173
|
+
);
|
|
174
|
+
if (nativeOpenAIResponses) {
|
|
175
|
+
return projectOpenAIResponsesToolMessageContent(
|
|
176
|
+
stripAnthropicCacheControl(
|
|
177
|
+
stripBedrockCacheControl(providerInputMessages)
|
|
178
|
+
),
|
|
179
|
+
maxToolResultChars
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (provider === Providers.OPENROUTER) {
|
|
183
|
+
return projectComputerCallOutputsToText(
|
|
184
|
+
projectOpenRouterToolMessageContent(
|
|
185
|
+
stripBedrockCacheControl(providerInputMessages),
|
|
186
|
+
maxToolResultChars
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (isOpenAILike(provider)) {
|
|
191
|
+
return projectComputerCallOutputsToText(
|
|
192
|
+
projectOpenAIChatToolMessageContent(
|
|
193
|
+
stripAnthropicCacheControl(
|
|
194
|
+
stripBedrockCacheControl(providerInputMessages)
|
|
195
|
+
),
|
|
196
|
+
maxToolResultChars
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (provider === Providers.ANTHROPIC) {
|
|
201
|
+
return projectComputerCallOutputsToText(
|
|
202
|
+
projectSingleTextToolOutputsToText(
|
|
203
|
+
stripBedrockCacheControl(providerInputMessages),
|
|
204
|
+
maxToolResultChars
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (provider === Providers.BEDROCK) {
|
|
209
|
+
return stripAnthropicCacheControl(
|
|
210
|
+
projectComputerCallOutputsToText(
|
|
211
|
+
projectCacheControlledToolOutputsToText(
|
|
212
|
+
providerInputMessages,
|
|
213
|
+
maxToolResultChars
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return projectComputerCallOutputsToText(
|
|
219
|
+
projectStructuredToolOutputsToText(
|
|
220
|
+
projectSingleTextToolOutputsToText(
|
|
221
|
+
stripAnthropicCacheControl(
|
|
222
|
+
stripBedrockCacheControl(providerInputMessages)
|
|
223
|
+
),
|
|
224
|
+
maxToolResultChars
|
|
225
|
+
),
|
|
226
|
+
maxToolResultChars
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Produces the provider-facing representation before adapter serialization. */
|
|
232
|
+
export function projectMessagesForProvider(
|
|
233
|
+
params: ProjectMessagesForProviderParams
|
|
234
|
+
): BaseMessage[] {
|
|
235
|
+
return projectMessagesForProviderMode(
|
|
236
|
+
params,
|
|
237
|
+
resolveProviderMessageProjectionMode(
|
|
238
|
+
params.model,
|
|
239
|
+
params.provider,
|
|
240
|
+
params.callOptions
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Reads the serving model id through LangChain binding/sequence wrappers. */
|
|
246
|
+
export function resolveServingModelId(model: unknown): string | undefined {
|
|
247
|
+
const seen = new Set<unknown>();
|
|
248
|
+
let current: unknown = model;
|
|
249
|
+
while (current != null && typeof current === 'object' && !seen.has(current)) {
|
|
250
|
+
seen.add(current);
|
|
251
|
+
const wrapper = current as {
|
|
252
|
+
model?: unknown;
|
|
253
|
+
bound?: unknown;
|
|
254
|
+
last?: unknown;
|
|
255
|
+
steps?: unknown[];
|
|
256
|
+
};
|
|
257
|
+
if (typeof wrapper.model === 'string' && wrapper.model !== '') {
|
|
258
|
+
return wrapper.model;
|
|
259
|
+
}
|
|
260
|
+
current =
|
|
261
|
+
wrapper.bound ??
|
|
262
|
+
wrapper.last ??
|
|
263
|
+
(Array.isArray(wrapper.steps)
|
|
264
|
+
? wrapper.steps[wrapper.steps.length - 1]
|
|
265
|
+
: undefined);
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Finalizes one provider request and measures the exact message array that
|
|
272
|
+
* will be passed to LangChain. Source messages remain untouched.
|
|
273
|
+
*/
|
|
274
|
+
export function prepareProviderRequest({
|
|
275
|
+
model,
|
|
276
|
+
messages,
|
|
277
|
+
provider,
|
|
278
|
+
context,
|
|
279
|
+
config,
|
|
280
|
+
maxToolResultChars,
|
|
281
|
+
measure,
|
|
282
|
+
}: PrepareProviderRequestParams): PreparedProviderRequest {
|
|
283
|
+
const projectionMode = resolveProviderMessageProjectionMode(
|
|
284
|
+
model,
|
|
285
|
+
provider,
|
|
286
|
+
config
|
|
287
|
+
);
|
|
288
|
+
const projected = projectMessagesForProviderMode(
|
|
289
|
+
{
|
|
290
|
+
model,
|
|
291
|
+
messages,
|
|
292
|
+
provider,
|
|
293
|
+
maxToolResultChars,
|
|
294
|
+
callOptions: config,
|
|
295
|
+
},
|
|
296
|
+
projectionMode
|
|
297
|
+
);
|
|
298
|
+
const registry = context?.getOrCreateToolOutputRegistry?.();
|
|
299
|
+
const runId = config?.configurable?.run_id as string | undefined;
|
|
300
|
+
const annotated = annotateMessagesForLLM(projected, registry, runId);
|
|
301
|
+
const isRunProduced = context?.isRunProducedMessage;
|
|
302
|
+
const modelId = resolveServingModelId(model);
|
|
303
|
+
const cued = isAnthropicLike(provider, {
|
|
304
|
+
model: modelId,
|
|
305
|
+
})
|
|
306
|
+
? appendPredecessorHandoffCue(
|
|
307
|
+
annotated,
|
|
308
|
+
isRunProduced == null
|
|
309
|
+
? undefined
|
|
310
|
+
: (message): boolean => isRunProduced.call(context, message)
|
|
311
|
+
)
|
|
312
|
+
: removePredecessorHandoffCue(annotated);
|
|
313
|
+
const preparedMessages = strictAlternationProviders.has(provider)
|
|
314
|
+
? coalesceAdjacentUserTurns(cued)
|
|
315
|
+
: cued;
|
|
316
|
+
|
|
317
|
+
const request: PreparedProviderRequestData = {
|
|
318
|
+
model,
|
|
319
|
+
modelId,
|
|
320
|
+
provider,
|
|
321
|
+
projectionMode,
|
|
322
|
+
messages: preparedMessages,
|
|
323
|
+
measurement: measure?.(preparedMessages),
|
|
324
|
+
};
|
|
325
|
+
Object.defineProperty(request, preparedProviderRequestBrand, {
|
|
326
|
+
value: true,
|
|
327
|
+
enumerable: false,
|
|
328
|
+
});
|
|
329
|
+
return Object.freeze(request) as PreparedProviderRequest;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function assertPreparedProviderRequestFor(
|
|
333
|
+
request: PreparedProviderRequest,
|
|
334
|
+
model: t.ChatModel,
|
|
335
|
+
provider: Providers,
|
|
336
|
+
config?: RunnableConfig
|
|
337
|
+
): void {
|
|
338
|
+
if (
|
|
339
|
+
!Object.prototype.hasOwnProperty.call(request, preparedProviderRequestBrand)
|
|
340
|
+
) {
|
|
341
|
+
throw new Error('Invalid prepared provider request');
|
|
342
|
+
}
|
|
343
|
+
if (request.model !== model) {
|
|
344
|
+
throw new Error('Prepared provider request does not match serving model');
|
|
345
|
+
}
|
|
346
|
+
if (request.provider !== provider) {
|
|
347
|
+
throw new Error('Prepared provider request does not match serving provider');
|
|
348
|
+
}
|
|
349
|
+
if (
|
|
350
|
+
request.projectionMode !==
|
|
351
|
+
resolveProviderMessageProjectionMode(model, provider, config)
|
|
352
|
+
) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
'Prepared provider request does not match invocation options'
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
package/src/messages/content.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
MessageContentComplex,
|
|
4
4
|
} from '@langchain/core/messages';
|
|
5
5
|
import { ContentTypes } from '@/common';
|
|
6
|
+
import { cloneMessage } from './cache';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Whether {@link formatContentStrings} will flatten this message's content:
|
|
@@ -42,13 +43,12 @@ export const formatContentStrings = (
|
|
|
42
43
|
const blocks = message.content as MessageContentComplex[];
|
|
43
44
|
const content = blocks.reduce((acc, curr) => {
|
|
44
45
|
if (curr.type === ContentTypes.TEXT) {
|
|
45
|
-
return `${acc}${curr[ContentTypes.TEXT]
|
|
46
|
+
return `${acc}${curr[ContentTypes.TEXT] ?? ''}\n`;
|
|
46
47
|
}
|
|
47
48
|
return acc;
|
|
48
49
|
}, '');
|
|
49
50
|
|
|
50
|
-
message
|
|
51
|
-
result.push(message);
|
|
51
|
+
result.push(cloneMessage(message, content.trim()));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return result;
|