@librechat/agents 3.6.11 → 3.6.13
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/agents/AgentContext.cjs +4 -0
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +45 -116
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/contextPressureMeter.cjs +212 -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/llm/tokenCounterCacheCompatibility.cjs +14 -0
- package/dist/cjs/llm/tokenCounterCacheCompatibility.cjs.map +1 -0
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/messages/content.cjs +5 -4
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +3 -2
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +4 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +48 -119
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/contextPressureMeter.mjs +211 -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/llm/tokenCounterCacheCompatibility.mjs +13 -0
- package/dist/esm/llm/tokenCounterCacheCompatibility.mjs.map +1 -0
- package/dist/esm/main.mjs +3 -1
- package/dist/esm/messages/content.mjs +5 -4
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +3 -2
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +3 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/llm/contextPressureMeter.d.ts +36 -0
- package/dist/types/llm/invoke.d.ts +31 -33
- package/dist/types/llm/prepareProviderRequest.d.ts +55 -0
- package/dist/types/llm/tokenCounterCacheCompatibility.d.ts +7 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +9 -0
- package/src/graphs/Graph.ts +70 -276
- package/src/index.ts +9 -0
- package/src/llm/contextPressureMeter.ts +449 -0
- package/src/llm/invoke.ts +136 -296
- package/src/llm/prepareProviderRequest.ts +357 -0
- package/src/llm/tokenCounterCacheCompatibility.ts +22 -0
- package/src/messages/content.ts +3 -3
- package/src/utils/tokens.ts +3 -2
package/src/llm/invoke.ts
CHANGED
|
@@ -12,30 +12,13 @@ import type { ChatGeneration } from '@langchain/core/outputs';
|
|
|
12
12
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
13
13
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
14
14
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
15
|
+
import type { PreparedProviderRequest } from '@/llm/prepareProviderRequest';
|
|
15
16
|
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
16
17
|
import type { ContextOverflowContext } from '@/utils/errors';
|
|
17
18
|
import type * as t from '@/types';
|
|
18
|
-
import {
|
|
19
|
-
projectCacheControlledToolOutputsToText,
|
|
20
|
-
projectComputerCallOutputsToText,
|
|
21
|
-
projectOpenAIChatToolMessageContent,
|
|
22
|
-
projectOpenAIResponsesToolMessageContent,
|
|
23
|
-
projectOpenRouterToolMessageContent,
|
|
24
|
-
projectSingleTextToolOutputsToText,
|
|
25
|
-
projectStructuredToolOutputsToText,
|
|
26
|
-
projectToolStreamContentForProvider,
|
|
27
|
-
} from '@/messages/core';
|
|
28
19
|
import {
|
|
29
20
|
modifyDeltaProperties,
|
|
30
|
-
coalesceAdjacentUserTurns,
|
|
31
|
-
strictAlternationProviders,
|
|
32
|
-
appendPredecessorHandoffCue,
|
|
33
|
-
removePredecessorHandoffCue,
|
|
34
21
|
} from '@/messages';
|
|
35
|
-
import {
|
|
36
|
-
stripAnthropicCacheControl,
|
|
37
|
-
stripBedrockCacheControl,
|
|
38
|
-
} from '@/messages/cache';
|
|
39
22
|
import { ChatModelStreamHandler, dispatchesChatModelStream } from '@/stream';
|
|
40
23
|
import { Constants, ContentTypes, GraphEvents, Providers } from '@/common';
|
|
41
24
|
import {
|
|
@@ -48,23 +31,38 @@ import {
|
|
|
48
31
|
STREAM_LIMIT_REDISPATCH_KEY,
|
|
49
32
|
STREAM_LIMIT_ATTEMPT_KEY,
|
|
50
33
|
} from '@/llm/streamLimits';
|
|
51
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
assertPreparedProviderRequestFor,
|
|
36
|
+
prepareProviderRequest,
|
|
37
|
+
} from '@/llm/prepareProviderRequest';
|
|
52
38
|
import { assertNotTruncatedToolCall } from '@/llm/truncation';
|
|
53
39
|
import { manualToolStreamProviders } from '@/llm/providers';
|
|
54
|
-
import { isAnthropicLike, isOpenAILike } from '@/utils/llm';
|
|
55
40
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
56
41
|
import { getContextOverflowInfo } from '@/utils/errors';
|
|
57
42
|
import { appendCallbacks } from '@/utils/callbacks';
|
|
58
43
|
import { canSealPreempt } from '@/llm/preempt';
|
|
59
44
|
import { initializeModel } from '@/llm/init';
|
|
60
45
|
|
|
46
|
+
export {
|
|
47
|
+
projectMessagesForProvider,
|
|
48
|
+
resolveServingModelId,
|
|
49
|
+
usesNativeOpenAIResponses,
|
|
50
|
+
} from '@/llm/prepareProviderRequest';
|
|
51
|
+
export type {
|
|
52
|
+
PreparedProviderRequest,
|
|
53
|
+
PrepareProviderRequestParams,
|
|
54
|
+
ProviderMessageProjectionMode,
|
|
55
|
+
ProviderPayloadMeasurement,
|
|
56
|
+
ProviderRequestContext,
|
|
57
|
+
} from '@/llm/prepareProviderRequest';
|
|
58
|
+
|
|
61
59
|
/**
|
|
62
60
|
* Context passed to `attemptInvoke`. Matches the subset of Graph that
|
|
63
61
|
* `ChatModelStreamHandler.handle` needs *plus* the explicit
|
|
64
|
-
* `getOrCreateToolOutputRegistry()` accessor
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
62
|
+
* `getOrCreateToolOutputRegistry()` accessor used while preparing a
|
|
63
|
+
* provider request. Raw callers prepare inside `attemptInvoke`; Graph
|
|
64
|
+
* callers prepare before final payload measurement and pass the exact
|
|
65
|
+
* artifact through.
|
|
68
66
|
*
|
|
69
67
|
* The intersection is intentional: `Parameters<...>[3]` resolves
|
|
70
68
|
* indirectly through the stream handler's signature (which returns
|
|
@@ -107,161 +105,6 @@ export type OnChunk = (
|
|
|
107
105
|
/** Unique per-model-attempt sequence; see the stamp in `attemptInvoke`. */
|
|
108
106
|
let streamLimitAttemptSeq = 0;
|
|
109
107
|
|
|
110
|
-
export function usesNativeOpenAIResponses(
|
|
111
|
-
model: t.ChatModel,
|
|
112
|
-
provider: Providers,
|
|
113
|
-
callOptions?: unknown
|
|
114
|
-
): boolean {
|
|
115
|
-
if (!isOpenAILike(provider)) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
let candidate: unknown = model;
|
|
119
|
-
let effectiveCallOptions = callOptions;
|
|
120
|
-
const seen = new Set<object>();
|
|
121
|
-
for (let depth = 0; depth < 20; depth++) {
|
|
122
|
-
if (candidate == null || typeof candidate !== 'object') {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
if (seen.has(candidate)) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
seen.add(candidate);
|
|
129
|
-
const runnable = candidate as {
|
|
130
|
-
_useResponsesApi?: (options?: unknown) => boolean;
|
|
131
|
-
bound?: unknown;
|
|
132
|
-
defaultOptions?: unknown;
|
|
133
|
-
last?: unknown;
|
|
134
|
-
constructor?: { name?: unknown };
|
|
135
|
-
};
|
|
136
|
-
try {
|
|
137
|
-
if (
|
|
138
|
-
runnable.defaultOptions != null &&
|
|
139
|
-
typeof runnable.defaultOptions === 'object' &&
|
|
140
|
-
!Array.isArray(runnable.defaultOptions) &&
|
|
141
|
-
effectiveCallOptions != null &&
|
|
142
|
-
typeof effectiveCallOptions === 'object' &&
|
|
143
|
-
!Array.isArray(effectiveCallOptions)
|
|
144
|
-
) {
|
|
145
|
-
effectiveCallOptions = {
|
|
146
|
-
...(runnable.defaultOptions as Record<string, unknown>),
|
|
147
|
-
...(effectiveCallOptions as Record<string, unknown>),
|
|
148
|
-
};
|
|
149
|
-
} else if (effectiveCallOptions == null) {
|
|
150
|
-
effectiveCallOptions = runnable.defaultOptions;
|
|
151
|
-
}
|
|
152
|
-
if (
|
|
153
|
-
runnable._useResponsesApi?.(effectiveCallOptions) === true ||
|
|
154
|
-
runnable._useResponsesApi?.(undefined) === true
|
|
155
|
-
) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
} catch {
|
|
159
|
-
// Continue through RunnableSequence/RunnableBinding wrappers.
|
|
160
|
-
}
|
|
161
|
-
if (
|
|
162
|
-
typeof runnable.constructor?.name === 'string' &&
|
|
163
|
-
runnable.constructor.name.includes('Responses')
|
|
164
|
-
) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
if (runnable.last != null && typeof runnable.last === 'object') {
|
|
168
|
-
candidate = runnable.last;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (runnable.bound != null && typeof runnable.bound === 'object') {
|
|
172
|
-
candidate = runnable.bound;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Produces the exact provider-facing message representation before a model
|
|
182
|
-
* adapter serializes it. This is shared by invocation and Graph's final budget
|
|
183
|
-
* guard so structured tool output cannot grow after the payload was measured.
|
|
184
|
-
*/
|
|
185
|
-
export function projectMessagesForProvider({
|
|
186
|
-
model,
|
|
187
|
-
messages,
|
|
188
|
-
provider,
|
|
189
|
-
maxToolResultChars,
|
|
190
|
-
callOptions,
|
|
191
|
-
}: {
|
|
192
|
-
model: t.ChatModel;
|
|
193
|
-
messages: BaseMessage[];
|
|
194
|
-
provider: Providers;
|
|
195
|
-
maxToolResultChars?: number;
|
|
196
|
-
callOptions?: unknown;
|
|
197
|
-
}): BaseMessage[] {
|
|
198
|
-
const nativeOpenAIResponses = usesNativeOpenAIResponses(
|
|
199
|
-
model,
|
|
200
|
-
provider,
|
|
201
|
-
callOptions
|
|
202
|
-
);
|
|
203
|
-
const providerInputMessages = projectToolStreamContentForProvider(
|
|
204
|
-
messages,
|
|
205
|
-
nativeOpenAIResponses ? 'native' : 'fallback',
|
|
206
|
-
maxToolResultChars
|
|
207
|
-
);
|
|
208
|
-
if (nativeOpenAIResponses) {
|
|
209
|
-
return projectOpenAIResponsesToolMessageContent(
|
|
210
|
-
stripAnthropicCacheControl(
|
|
211
|
-
stripBedrockCacheControl(providerInputMessages)
|
|
212
|
-
),
|
|
213
|
-
maxToolResultChars
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
if (provider === Providers.OPENROUTER) {
|
|
217
|
-
return projectComputerCallOutputsToText(
|
|
218
|
-
projectOpenRouterToolMessageContent(
|
|
219
|
-
stripBedrockCacheControl(providerInputMessages),
|
|
220
|
-
maxToolResultChars
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
if (isOpenAILike(provider)) {
|
|
225
|
-
return projectComputerCallOutputsToText(
|
|
226
|
-
projectOpenAIChatToolMessageContent(
|
|
227
|
-
stripAnthropicCacheControl(
|
|
228
|
-
stripBedrockCacheControl(providerInputMessages)
|
|
229
|
-
),
|
|
230
|
-
maxToolResultChars
|
|
231
|
-
)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
if (provider === Providers.ANTHROPIC) {
|
|
235
|
-
return projectComputerCallOutputsToText(
|
|
236
|
-
projectSingleTextToolOutputsToText(
|
|
237
|
-
stripBedrockCacheControl(providerInputMessages),
|
|
238
|
-
maxToolResultChars
|
|
239
|
-
)
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
if (provider === Providers.BEDROCK) {
|
|
243
|
-
return stripAnthropicCacheControl(
|
|
244
|
-
projectComputerCallOutputsToText(
|
|
245
|
-
projectCacheControlledToolOutputsToText(
|
|
246
|
-
providerInputMessages,
|
|
247
|
-
maxToolResultChars
|
|
248
|
-
)
|
|
249
|
-
)
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
return projectComputerCallOutputsToText(
|
|
253
|
-
projectStructuredToolOutputsToText(
|
|
254
|
-
projectSingleTextToolOutputsToText(
|
|
255
|
-
stripAnthropicCacheControl(
|
|
256
|
-
stripBedrockCacheControl(providerInputMessages)
|
|
257
|
-
),
|
|
258
|
-
maxToolResultChars
|
|
259
|
-
),
|
|
260
|
-
maxToolResultChars
|
|
261
|
-
)
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
108
|
/**
|
|
266
109
|
* The registered handler that owns content-part dispatch, if any.
|
|
267
110
|
*
|
|
@@ -554,36 +397,6 @@ function collectModelCallbackSources(model: unknown): Callbacks[] {
|
|
|
554
397
|
return sources;
|
|
555
398
|
}
|
|
556
399
|
|
|
557
|
-
/**
|
|
558
|
-
* The serving model's id, read through the same wrapper stack
|
|
559
|
-
* `collectModelCallbackSources` walks — `bindTools` returns a
|
|
560
|
-
* `RunnableBinding` and a system runnable pipes a `RunnableSequence`, and
|
|
561
|
-
* neither exposes the chat model's `model` at the top level.
|
|
562
|
-
*/
|
|
563
|
-
export function resolveServingModelId(model: unknown): string | undefined {
|
|
564
|
-
const seen = new Set<unknown>();
|
|
565
|
-
let current: unknown = model;
|
|
566
|
-
while (current != null && typeof current === 'object' && !seen.has(current)) {
|
|
567
|
-
seen.add(current);
|
|
568
|
-
const wrapper = current as {
|
|
569
|
-
model?: unknown;
|
|
570
|
-
bound?: unknown;
|
|
571
|
-
last?: unknown;
|
|
572
|
-
steps?: unknown[];
|
|
573
|
-
};
|
|
574
|
-
if (typeof wrapper.model === 'string' && wrapper.model !== '') {
|
|
575
|
-
return wrapper.model;
|
|
576
|
-
}
|
|
577
|
-
current =
|
|
578
|
-
wrapper.bound ??
|
|
579
|
-
wrapper.last ??
|
|
580
|
-
(Array.isArray(wrapper.steps)
|
|
581
|
-
? wrapper.steps[wrapper.steps.length - 1]
|
|
582
|
-
: undefined);
|
|
583
|
-
}
|
|
584
|
-
return undefined;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
400
|
async function endSealedModelRun(
|
|
588
401
|
context: InvokeContext | undefined,
|
|
589
402
|
chunk: AIMessageChunk,
|
|
@@ -680,10 +493,7 @@ function appendStreamChunk({
|
|
|
680
493
|
* Pass an `onChunk` callback to override this with custom chunk processing
|
|
681
494
|
* (e.g. summarization delta events).
|
|
682
495
|
*/
|
|
683
|
-
interface
|
|
684
|
-
model: t.ChatModel;
|
|
685
|
-
messages: BaseMessage[];
|
|
686
|
-
provider: Providers;
|
|
496
|
+
interface AttemptInvokeCommonParams {
|
|
687
497
|
context?: InvokeContext;
|
|
688
498
|
onChunk?: OnChunk;
|
|
689
499
|
/** Accounting owner for callers that deliberately pass no `context`
|
|
@@ -692,6 +502,51 @@ interface AttemptInvokeParams {
|
|
|
692
502
|
streamLimitState?: StreamLimitState;
|
|
693
503
|
}
|
|
694
504
|
|
|
505
|
+
type AttemptInvokeParams = AttemptInvokeCommonParams &
|
|
506
|
+
(
|
|
507
|
+
| {
|
|
508
|
+
request: PreparedProviderRequest;
|
|
509
|
+
model?: never;
|
|
510
|
+
messages?: never;
|
|
511
|
+
provider?: never;
|
|
512
|
+
}
|
|
513
|
+
| {
|
|
514
|
+
request?: never;
|
|
515
|
+
model: t.ChatModel;
|
|
516
|
+
messages: BaseMessage[];
|
|
517
|
+
provider: Providers;
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
function resolveAttemptProvider(params: AttemptInvokeParams): Providers {
|
|
522
|
+
if (params.request != null) {
|
|
523
|
+
return params.request.provider;
|
|
524
|
+
}
|
|
525
|
+
return params.provider;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function resolveAttemptRequest(
|
|
529
|
+
params: AttemptInvokeParams,
|
|
530
|
+
config: RunnableConfig
|
|
531
|
+
): PreparedProviderRequest {
|
|
532
|
+
if (params.request != null) {
|
|
533
|
+
assertPreparedProviderRequestFor(
|
|
534
|
+
params.request,
|
|
535
|
+
params.request.model,
|
|
536
|
+
params.request.provider,
|
|
537
|
+
config
|
|
538
|
+
);
|
|
539
|
+
return params.request;
|
|
540
|
+
}
|
|
541
|
+
return prepareProviderRequest({
|
|
542
|
+
model: params.model,
|
|
543
|
+
messages: params.messages,
|
|
544
|
+
provider: params.provider,
|
|
545
|
+
context: params.context,
|
|
546
|
+
config,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
695
550
|
/**
|
|
696
551
|
* One model attempt. Stamps the attempt identity into callback metadata
|
|
697
552
|
* (see the generation-key notes in `streamLimits.ts`), leases the attempt's
|
|
@@ -703,11 +558,12 @@ export async function attemptInvoke(
|
|
|
703
558
|
params: AttemptInvokeParams,
|
|
704
559
|
config?: RunnableConfig
|
|
705
560
|
): Promise<Partial<t.BaseGraphState>> {
|
|
561
|
+
const provider = resolveAttemptProvider(params);
|
|
706
562
|
const stampedConfig: RunnableConfig = {
|
|
707
563
|
...config,
|
|
708
564
|
metadata: {
|
|
709
565
|
...(config?.metadata ?? {}),
|
|
710
|
-
[Constants.INVOKED_PROVIDER]:
|
|
566
|
+
[Constants.INVOKED_PROVIDER]: provider,
|
|
711
567
|
/**
|
|
712
568
|
* One `attemptInvoke` call is one model attempt; primary, fallback,
|
|
713
569
|
* and retry attempts within a node otherwise share the same langgraph
|
|
@@ -738,7 +594,14 @@ export async function attemptInvoke(
|
|
|
738
594
|
registerActiveStreamLimitGeneration(leaseTarget, generationKey);
|
|
739
595
|
}
|
|
740
596
|
try {
|
|
741
|
-
return await attemptInvokeBody(
|
|
597
|
+
return await attemptInvokeBody(
|
|
598
|
+
{
|
|
599
|
+
request: resolveAttemptRequest(params, stampedConfig),
|
|
600
|
+
context: params.context,
|
|
601
|
+
onChunk: params.onChunk,
|
|
602
|
+
},
|
|
603
|
+
stampedConfig
|
|
604
|
+
);
|
|
742
605
|
} finally {
|
|
743
606
|
if (leaseTarget != null && generationKey != null) {
|
|
744
607
|
releaseStreamLimitGeneration(leaseTarget, generationKey);
|
|
@@ -748,77 +611,15 @@ export async function attemptInvoke(
|
|
|
748
611
|
|
|
749
612
|
async function attemptInvokeBody(
|
|
750
613
|
{
|
|
751
|
-
|
|
752
|
-
messages,
|
|
753
|
-
provider,
|
|
614
|
+
request,
|
|
754
615
|
context,
|
|
755
616
|
onChunk,
|
|
756
|
-
}:
|
|
617
|
+
}: Pick<AttemptInvokeCommonParams, 'context' | 'onChunk'> & {
|
|
618
|
+
request: PreparedProviderRequest;
|
|
619
|
+
},
|
|
757
620
|
config: RunnableConfig
|
|
758
621
|
): Promise<Partial<t.BaseGraphState>> {
|
|
759
|
-
|
|
760
|
-
* Pull the run-scoped tool output registry off the graph (when one
|
|
761
|
-
* exists) and project ToolMessages carrying ref metadata into a
|
|
762
|
-
* transient annotated copy. The original `messages` array stays
|
|
763
|
-
* untouched so the graph state never sees `[ref: …]` / `_ref`
|
|
764
|
-
* payload.
|
|
765
|
-
*/
|
|
766
|
-
const invocationMessages = projectMessagesForProvider({
|
|
767
|
-
model,
|
|
768
|
-
messages,
|
|
769
|
-
provider,
|
|
770
|
-
callOptions: config,
|
|
771
|
-
});
|
|
772
|
-
const registry = context?.getOrCreateToolOutputRegistry();
|
|
773
|
-
const runId = config.configurable?.run_id as string | undefined;
|
|
774
|
-
const annotated = annotateMessagesForLLM(invocationMessages, registry, runId);
|
|
775
|
-
/**
|
|
776
|
-
* Keyed on the provider ACTUALLY serving this call, not the agent's primary.
|
|
777
|
-
* `createCallModel` normalizes for the primary, but `tryFallbackProviders`
|
|
778
|
-
* re-sends the same array — so an OpenAI primary that fails after a boundary
|
|
779
|
-
* injected two human turns would hand a Bedrock or Mistral fallback the
|
|
780
|
-
* consecutive user turns those APIs reject, and the recovery request would
|
|
781
|
-
* fail for a reason unrelated to the original failure.
|
|
782
|
-
*
|
|
783
|
-
* `attemptInvoke` is the single funnel for primary, fallback and
|
|
784
|
-
* summarization calls, so applying it here covers all three. Idempotent, so
|
|
785
|
-
* the primary simply re-runs a no-op over already-coalesced messages.
|
|
786
|
-
*/
|
|
787
|
-
/**
|
|
788
|
-
* Serving-provider re-keying for the predecessor handoff cue (#345). The
|
|
789
|
-
* PRIMARY's cue is baked in createCallModel's measured transform stage —
|
|
790
|
-
* appending after measurement could push a just-fits prompt over budget —
|
|
791
|
-
* so this funnel only corrects for fallbacks crossing provider families:
|
|
792
|
-
* a tolerant primary falling back to a Claude surface gains the cue here,
|
|
793
|
-
* and an Anthropic primary falling back to OpenAI/Mistral/Nova has the
|
|
794
|
-
* Claude-only synthetic turn stripped. Both helpers are identity on their
|
|
795
|
-
* no-op paths, so the primary's own pass re-runs for free.
|
|
796
|
-
*
|
|
797
|
-
* The serving model id is read through the wrapper stack (`bindTools`'
|
|
798
|
-
* binding, a system runnable's sequence) — a wrapper's top-level `.model`
|
|
799
|
-
* is undefined, and `isAnthropicLike` would otherwise default a wrapped
|
|
800
|
-
* Bedrock-Nova model to Claude. The context cast is widened deliberately:
|
|
801
|
-
* the type says every context is a full Graph, but summarization passes
|
|
802
|
-
* none and long-standing tests pass partial stubs.
|
|
803
|
-
*/
|
|
804
|
-
const isRunProduced = (
|
|
805
|
-
context as
|
|
806
|
-
| { isRunProducedMessage?: (message: BaseMessage) => boolean }
|
|
807
|
-
| undefined
|
|
808
|
-
)?.isRunProducedMessage;
|
|
809
|
-
const cued = isAnthropicLike(provider, {
|
|
810
|
-
model: resolveServingModelId(model),
|
|
811
|
-
})
|
|
812
|
-
? appendPredecessorHandoffCue(
|
|
813
|
-
annotated,
|
|
814
|
-
isRunProduced == null
|
|
815
|
-
? undefined
|
|
816
|
-
: (message): boolean => isRunProduced.call(context, message)
|
|
817
|
-
)
|
|
818
|
-
: removePredecessorHandoffCue(annotated);
|
|
819
|
-
const messagesForProvider = strictAlternationProviders.has(provider)
|
|
820
|
-
? coalesceAdjacentUserTurns(cued)
|
|
821
|
-
: cued;
|
|
622
|
+
const { model, messages: messagesForProvider, provider } = request;
|
|
822
623
|
|
|
823
624
|
/**
|
|
824
625
|
* Stamp the provider that is ACTUALLY serving this invocation onto the
|
|
@@ -1140,6 +941,7 @@ export async function tryFallbackProviders({
|
|
|
1140
941
|
onChunk,
|
|
1141
942
|
streamLimitState,
|
|
1142
943
|
overflowContext,
|
|
944
|
+
prepareProviderRequest: prepareFallbackRequest,
|
|
1143
945
|
prepareProviderMessages,
|
|
1144
946
|
}: {
|
|
1145
947
|
fallbacks: t.FallbackConfig[];
|
|
@@ -1160,10 +962,18 @@ export async function tryFallbackProviders({
|
|
|
1160
962
|
*/
|
|
1161
963
|
overflowContext?: ContextOverflowContext;
|
|
1162
964
|
/**
|
|
1163
|
-
* Optional
|
|
1164
|
-
*
|
|
1165
|
-
* the fallback request is measured and sent.
|
|
965
|
+
* Optional exact-payload preparation used by Graph. The returned request is
|
|
966
|
+
* measured and sent without another provider projection.
|
|
1166
967
|
*/
|
|
968
|
+
prepareProviderRequest?: (input: {
|
|
969
|
+
model: t.ChatModel;
|
|
970
|
+
messages: BaseMessage[];
|
|
971
|
+
provider: Providers;
|
|
972
|
+
clientOptions?: t.ClientOptions;
|
|
973
|
+
maxContextTokens?: number;
|
|
974
|
+
config?: RunnableConfig;
|
|
975
|
+
}) => PreparedProviderRequest | Promise<PreparedProviderRequest>;
|
|
976
|
+
/** @deprecated Return a `PreparedProviderRequest` instead. */
|
|
1167
977
|
prepareProviderMessages?: (input: {
|
|
1168
978
|
model: t.ChatModel;
|
|
1169
979
|
messages: BaseMessage[];
|
|
@@ -1213,15 +1023,35 @@ export async function tryFallbackProviders({
|
|
|
1213
1023
|
[Constants.INVOKED_MODEL]: fbModelName,
|
|
1214
1024
|
},
|
|
1215
1025
|
};
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1026
|
+
const preparationInput = {
|
|
1027
|
+
model: fbModel as t.ChatModel,
|
|
1028
|
+
messages,
|
|
1029
|
+
provider: fb.provider,
|
|
1030
|
+
clientOptions: fb.clientOptions,
|
|
1031
|
+
maxContextTokens: fb.maxContextTokens,
|
|
1032
|
+
config: fbConfig,
|
|
1033
|
+
};
|
|
1034
|
+
const preparedRequest = await prepareFallbackRequest?.(preparationInput);
|
|
1035
|
+
if (preparedRequest != null) {
|
|
1036
|
+
assertPreparedProviderRequestFor(
|
|
1037
|
+
preparedRequest,
|
|
1038
|
+
fbModel as t.ChatModel,
|
|
1039
|
+
fb.provider,
|
|
1040
|
+
fbConfig
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
let fallbackMessages = messages;
|
|
1044
|
+
if (preparedRequest == null) {
|
|
1045
|
+
fallbackMessages =
|
|
1046
|
+
(await prepareProviderMessages?.({
|
|
1047
|
+
model: fbModel as t.ChatModel,
|
|
1048
|
+
messages,
|
|
1049
|
+
provider: fb.provider,
|
|
1050
|
+
clientOptions: fb.clientOptions,
|
|
1051
|
+
maxContextTokens: fb.maxContextTokens,
|
|
1052
|
+
config: fbConfig,
|
|
1053
|
+
})) ?? messages;
|
|
1054
|
+
}
|
|
1225
1055
|
/** A sibling can trip the breaker while the preparation above is
|
|
1226
1056
|
* awaited — and the catch below only sees attempts that THROW, so a
|
|
1227
1057
|
* provider that ignores an aborted signal and succeeds would resolve
|
|
@@ -1232,7 +1062,18 @@ export async function tryFallbackProviders({
|
|
|
1232
1062
|
) {
|
|
1233
1063
|
throw config.signal.reason;
|
|
1234
1064
|
}
|
|
1235
|
-
|
|
1065
|
+
if (preparedRequest != null) {
|
|
1066
|
+
return await attemptInvoke(
|
|
1067
|
+
{
|
|
1068
|
+
request: preparedRequest,
|
|
1069
|
+
context,
|
|
1070
|
+
onChunk,
|
|
1071
|
+
streamLimitState,
|
|
1072
|
+
},
|
|
1073
|
+
fbConfig
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
return await attemptInvoke(
|
|
1236
1077
|
{
|
|
1237
1078
|
model: fbModel as t.ChatModel,
|
|
1238
1079
|
messages: fallbackMessages,
|
|
@@ -1243,7 +1084,6 @@ export async function tryFallbackProviders({
|
|
|
1243
1084
|
},
|
|
1244
1085
|
fbConfig
|
|
1245
1086
|
);
|
|
1246
|
-
return result;
|
|
1247
1087
|
} catch (e) {
|
|
1248
1088
|
/**
|
|
1249
1089
|
* A tripped stream circuit breaker is a deliberate abort, not a
|