@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,30 @@
|
|
|
1
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { ProviderPayloadMeasurement } from '@/llm/prepareProviderRequest';
|
|
3
|
+
import type * as t from '@/types';
|
|
4
|
+
interface ContextPressureUsage {
|
|
5
|
+
contextBudget?: number;
|
|
6
|
+
effectiveInstructionTokens?: number;
|
|
7
|
+
remainingContextTokens?: number;
|
|
8
|
+
calibrationRatio?: number;
|
|
9
|
+
}
|
|
10
|
+
interface ContextPressureMeterParams {
|
|
11
|
+
tokenCounter?: t.TokenCounter;
|
|
12
|
+
sourceMessages: BaseMessage[];
|
|
13
|
+
retainedMessages: BaseMessage[];
|
|
14
|
+
indexTokenCountMap: Record<string, number | undefined>;
|
|
15
|
+
contextUsage?: ContextPressureUsage | null;
|
|
16
|
+
instructionTokens: number;
|
|
17
|
+
calibrationRatio: number;
|
|
18
|
+
}
|
|
19
|
+
interface ProviderPayloadMeasureOptions {
|
|
20
|
+
contextBudget?: number;
|
|
21
|
+
forceRawRecount?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ContextPressureMeter {
|
|
24
|
+
trackProjection(before: BaseMessage[], after: BaseMessage[]): BaseMessage[];
|
|
25
|
+
trackClone(source: BaseMessage, clone: BaseMessage): void;
|
|
26
|
+
measure(messages: BaseMessage[], options?: ProviderPayloadMeasureOptions): ProviderPayloadMeasurement;
|
|
27
|
+
}
|
|
28
|
+
/** Measures repeated provider projections while tokenizing each message object once. */
|
|
29
|
+
export declare function createContextPressureMeter({ tokenCounter, sourceMessages, retainedMessages, indexTokenCountMap, contextUsage, instructionTokens, calibrationRatio, }: ContextPressureMeterParams): ContextPressureMeter;
|
|
30
|
+
export {};
|
|
@@ -2,18 +2,21 @@ import { AIMessageChunk } from '@langchain/core/messages';
|
|
|
2
2
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
3
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
4
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
5
|
+
import type { PreparedProviderRequest } from '@/llm/prepareProviderRequest';
|
|
5
6
|
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
6
7
|
import type { ContextOverflowContext } from '@/utils/errors';
|
|
7
8
|
import type * as t from '@/types';
|
|
8
9
|
import { ChatModelStreamHandler } from '@/stream';
|
|
9
10
|
import { Providers } from '@/common';
|
|
11
|
+
export { projectMessagesForProvider, resolveServingModelId, usesNativeOpenAIResponses, } from '@/llm/prepareProviderRequest';
|
|
12
|
+
export type { PreparedProviderRequest, PrepareProviderRequestParams, ProviderMessageProjectionMode, ProviderPayloadMeasurement, ProviderRequestContext, } from '@/llm/prepareProviderRequest';
|
|
10
13
|
/**
|
|
11
14
|
* Context passed to `attemptInvoke`. Matches the subset of Graph that
|
|
12
15
|
* `ChatModelStreamHandler.handle` needs *plus* the explicit
|
|
13
|
-
* `getOrCreateToolOutputRegistry()` accessor
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* `getOrCreateToolOutputRegistry()` accessor used while preparing a
|
|
17
|
+
* provider request. Raw callers prepare inside `attemptInvoke`; Graph
|
|
18
|
+
* callers prepare before final payload measurement and pass the exact
|
|
19
|
+
* artifact through.
|
|
17
20
|
*
|
|
18
21
|
* The intersection is intentional: `Parameters<...>[3]` resolves
|
|
19
22
|
* indirectly through the stream handler's signature (which returns
|
|
@@ -46,26 +49,6 @@ export type InvokeContext = NonNullable<Parameters<ChatModelStreamHandler['handl
|
|
|
46
49
|
* limits key each model attempt separately.
|
|
47
50
|
*/
|
|
48
51
|
export type OnChunk = (chunk: AIMessageChunk, metadata?: Record<string, unknown>) => void | Promise<void>;
|
|
49
|
-
export declare function usesNativeOpenAIResponses(model: t.ChatModel, provider: Providers, callOptions?: unknown): boolean;
|
|
50
|
-
/**
|
|
51
|
-
* Produces the exact provider-facing message representation before a model
|
|
52
|
-
* adapter serializes it. This is shared by invocation and Graph's final budget
|
|
53
|
-
* guard so structured tool output cannot grow after the payload was measured.
|
|
54
|
-
*/
|
|
55
|
-
export declare function projectMessagesForProvider({ model, messages, provider, maxToolResultChars, callOptions, }: {
|
|
56
|
-
model: t.ChatModel;
|
|
57
|
-
messages: BaseMessage[];
|
|
58
|
-
provider: Providers;
|
|
59
|
-
maxToolResultChars?: number;
|
|
60
|
-
callOptions?: unknown;
|
|
61
|
-
}): BaseMessage[];
|
|
62
|
-
/**
|
|
63
|
-
* The serving model's id, read through the same wrapper stack
|
|
64
|
-
* `collectModelCallbackSources` walks — `bindTools` returns a
|
|
65
|
-
* `RunnableBinding` and a system runnable pipes a `RunnableSequence`, and
|
|
66
|
-
* neither exposes the chat model's `model` at the top level.
|
|
67
|
-
*/
|
|
68
|
-
export declare function resolveServingModelId(model: unknown): string | undefined;
|
|
69
52
|
/**
|
|
70
53
|
* Invokes a chat model with the given messages, handling both streaming and
|
|
71
54
|
* non-streaming paths.
|
|
@@ -75,10 +58,7 @@ export declare function resolveServingModelId(model: unknown): string | undefine
|
|
|
75
58
|
* Pass an `onChunk` callback to override this with custom chunk processing
|
|
76
59
|
* (e.g. summarization delta events).
|
|
77
60
|
*/
|
|
78
|
-
interface
|
|
79
|
-
model: t.ChatModel;
|
|
80
|
-
messages: BaseMessage[];
|
|
81
|
-
provider: Providers;
|
|
61
|
+
interface AttemptInvokeCommonParams {
|
|
82
62
|
context?: InvokeContext;
|
|
83
63
|
onChunk?: OnChunk;
|
|
84
64
|
/** Accounting owner for callers that deliberately pass no `context`
|
|
@@ -86,6 +66,17 @@ interface AttemptInvokeParams {
|
|
|
86
66
|
* for charge claims. */
|
|
87
67
|
streamLimitState?: StreamLimitState;
|
|
88
68
|
}
|
|
69
|
+
type AttemptInvokeParams = AttemptInvokeCommonParams & ({
|
|
70
|
+
request: PreparedProviderRequest;
|
|
71
|
+
model?: never;
|
|
72
|
+
messages?: never;
|
|
73
|
+
provider?: never;
|
|
74
|
+
} | {
|
|
75
|
+
request?: never;
|
|
76
|
+
model: t.ChatModel;
|
|
77
|
+
messages: BaseMessage[];
|
|
78
|
+
provider: Providers;
|
|
79
|
+
});
|
|
89
80
|
/**
|
|
90
81
|
* One model attempt. Stamps the attempt identity into callback metadata
|
|
91
82
|
* (see the generation-key notes in `streamLimits.ts`), leases the attempt's
|
|
@@ -122,7 +113,7 @@ export declare function getFallbackOverflowCandidates(error: unknown): FallbackO
|
|
|
122
113
|
* behind a later unrelated error would surface a dead end instead. Ordinary
|
|
123
114
|
* failures still throw last-error-wins.
|
|
124
115
|
*/
|
|
125
|
-
export declare function tryFallbackProviders({ fallbacks, tools, messages, config, primaryError, context, onChunk, streamLimitState, overflowContext, prepareProviderMessages, }: {
|
|
116
|
+
export declare function tryFallbackProviders({ fallbacks, tools, messages, config, primaryError, context, onChunk, streamLimitState, overflowContext, prepareProviderRequest: prepareFallbackRequest, prepareProviderMessages, }: {
|
|
126
117
|
fallbacks: t.FallbackConfig[];
|
|
127
118
|
tools?: t.GraphTools;
|
|
128
119
|
messages: BaseMessage[];
|
|
@@ -141,10 +132,18 @@ export declare function tryFallbackProviders({ fallbacks, tools, messages, confi
|
|
|
141
132
|
*/
|
|
142
133
|
overflowContext?: ContextOverflowContext;
|
|
143
134
|
/**
|
|
144
|
-
* Optional
|
|
145
|
-
*
|
|
146
|
-
* the fallback request is measured and sent.
|
|
135
|
+
* Optional exact-payload preparation used by Graph. The returned request is
|
|
136
|
+
* measured and sent without another provider projection.
|
|
147
137
|
*/
|
|
138
|
+
prepareProviderRequest?: (input: {
|
|
139
|
+
model: t.ChatModel;
|
|
140
|
+
messages: BaseMessage[];
|
|
141
|
+
provider: Providers;
|
|
142
|
+
clientOptions?: t.ClientOptions;
|
|
143
|
+
maxContextTokens?: number;
|
|
144
|
+
config?: RunnableConfig;
|
|
145
|
+
}) => PreparedProviderRequest | Promise<PreparedProviderRequest>;
|
|
146
|
+
/** @deprecated Return a `PreparedProviderRequest` instead. */
|
|
148
147
|
prepareProviderMessages?: (input: {
|
|
149
148
|
model: t.ChatModel;
|
|
150
149
|
messages: BaseMessage[];
|
|
@@ -154,4 +153,3 @@ export declare function tryFallbackProviders({ fallbacks, tools, messages, confi
|
|
|
154
153
|
config?: RunnableConfig;
|
|
155
154
|
}) => BaseMessage[] | Promise<BaseMessage[]>;
|
|
156
155
|
}): Promise<Partial<t.BaseGraphState> | undefined>;
|
|
157
|
-
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { Providers } from '@/common';
|
|
6
|
+
declare const preparedProviderRequestBrand: unique symbol;
|
|
7
|
+
export type ProviderMessageProjectionMode = 'chat-messages' | 'openai-responses';
|
|
8
|
+
export interface ProviderPayloadMeasurement {
|
|
9
|
+
readonly fits: boolean;
|
|
10
|
+
readonly projectedMessageTokens?: number;
|
|
11
|
+
readonly availableMessageTokens?: number;
|
|
12
|
+
readonly contextBudget?: number;
|
|
13
|
+
readonly effectiveInstructionTokens?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PreparedProviderRequest {
|
|
16
|
+
readonly model: t.ChatModel;
|
|
17
|
+
readonly modelId?: string;
|
|
18
|
+
readonly provider: Providers;
|
|
19
|
+
readonly projectionMode: ProviderMessageProjectionMode;
|
|
20
|
+
readonly messages: BaseMessage[];
|
|
21
|
+
readonly measurement?: ProviderPayloadMeasurement;
|
|
22
|
+
readonly [preparedProviderRequestBrand]: true;
|
|
23
|
+
}
|
|
24
|
+
export interface ProviderRequestContext {
|
|
25
|
+
getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;
|
|
26
|
+
isRunProducedMessage?(message: BaseMessage): boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface PrepareProviderRequestParams {
|
|
29
|
+
model: t.ChatModel;
|
|
30
|
+
messages: BaseMessage[];
|
|
31
|
+
provider: Providers;
|
|
32
|
+
context?: ProviderRequestContext;
|
|
33
|
+
config?: RunnableConfig;
|
|
34
|
+
maxToolResultChars?: number;
|
|
35
|
+
measure?: (messages: BaseMessage[]) => ProviderPayloadMeasurement;
|
|
36
|
+
}
|
|
37
|
+
export declare function usesNativeOpenAIResponses(model: t.ChatModel, provider: Providers, callOptions?: unknown): boolean;
|
|
38
|
+
interface ProjectMessagesForProviderParams {
|
|
39
|
+
model: t.ChatModel;
|
|
40
|
+
messages: BaseMessage[];
|
|
41
|
+
provider: Providers;
|
|
42
|
+
maxToolResultChars?: number;
|
|
43
|
+
callOptions?: unknown;
|
|
44
|
+
}
|
|
45
|
+
/** Produces the provider-facing representation before adapter serialization. */
|
|
46
|
+
export declare function projectMessagesForProvider(params: ProjectMessagesForProviderParams): BaseMessage[];
|
|
47
|
+
/** Reads the serving model id through LangChain binding/sequence wrappers. */
|
|
48
|
+
export declare function resolveServingModelId(model: unknown): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Finalizes one provider request and measures the exact message array that
|
|
51
|
+
* will be passed to LangChain. Source messages remain untouched.
|
|
52
|
+
*/
|
|
53
|
+
export declare function prepareProviderRequest({ model, messages, provider, context, config, maxToolResultChars, measure, }: PrepareProviderRequestParams): PreparedProviderRequest;
|
|
54
|
+
export declare function assertPreparedProviderRequestFor(request: PreparedProviderRequest, model: t.ChatModel, provider: Providers, config?: RunnableConfig): void;
|
|
55
|
+
export {};
|
package/package.json
CHANGED
package/src/graphs/Graph.ts
CHANGED
|
@@ -50,8 +50,8 @@ import {
|
|
|
50
50
|
addBedrockTailCacheControl,
|
|
51
51
|
projectArtifactPayload,
|
|
52
52
|
formatContentStrings,
|
|
53
|
+
cloneMessage,
|
|
53
54
|
CALIBRATION_RATIO_MAX,
|
|
54
|
-
REPLY_PRIMER_TOKENS,
|
|
55
55
|
createPruneMessages,
|
|
56
56
|
projectToolCallInputs,
|
|
57
57
|
calculateMaxToolCallInputChars,
|
|
@@ -74,7 +74,6 @@ import {
|
|
|
74
74
|
coalesceAdjacentUserTurns,
|
|
75
75
|
strictAlternationProviders,
|
|
76
76
|
appendPredecessorHandoffCue,
|
|
77
|
-
removePredecessorHandoffCue,
|
|
78
77
|
stampSyntheticProviderMessage,
|
|
79
78
|
} from '@/messages';
|
|
80
79
|
import {
|
|
@@ -104,9 +103,9 @@ import {
|
|
|
104
103
|
tryFallbackProviders,
|
|
105
104
|
getFallbackErrorContext,
|
|
106
105
|
getFallbackOverflowCandidates,
|
|
107
|
-
projectMessagesForProvider,
|
|
108
|
-
resolveServingModelId,
|
|
109
106
|
} from '@/llm/invoke';
|
|
107
|
+
import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
|
|
108
|
+
import { createContextPressureMeter } from '@/llm/contextPressureMeter';
|
|
110
109
|
import {
|
|
111
110
|
resolveStreamLimits,
|
|
112
111
|
StreamLimitExceededError,
|
|
@@ -3154,105 +3153,18 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3154
3153
|
* baseline, then attribute it across retained messages. Provider
|
|
3155
3154
|
* transforms can shrink one message while expanding or adding another;
|
|
3156
3155
|
* per-origin accounting prevents that unrelated shrink from canceling
|
|
3157
|
-
* the expansion.
|
|
3156
|
+
* the expansion. Exact counts are memoized across repeated projections.
|
|
3158
3157
|
*/
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
const rawTokens = agentContext.tokenCounter!(message);
|
|
3170
|
-
const sourceIndex = sourceIndices.get(message);
|
|
3171
|
-
const indexedTokens =
|
|
3172
|
-
sourceIndex != null
|
|
3173
|
-
? agentContext.indexTokenCountMap[sourceIndex]
|
|
3174
|
-
: undefined;
|
|
3175
|
-
const accountingWeight =
|
|
3176
|
-
indexedTokens != null &&
|
|
3177
|
-
Number.isFinite(indexedTokens) &&
|
|
3178
|
-
indexedTokens >= 0
|
|
3179
|
-
? indexedTokens
|
|
3180
|
-
: rawTokens;
|
|
3181
|
-
if (!providerMessageOrigins.has(message)) {
|
|
3182
|
-
providerMessageOrigins.set(message, index);
|
|
3183
|
-
}
|
|
3184
|
-
return { rawTokens, accountingWeight };
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
|
|
3188
|
-
const getProviderMessageOriginKey = (
|
|
3189
|
-
message: BaseMessage
|
|
3190
|
-
): string | undefined => {
|
|
3191
|
-
const type = message.getType();
|
|
3192
|
-
if (
|
|
3193
|
-
message instanceof ToolMessage &&
|
|
3194
|
-
typeof message.tool_call_id === 'string' &&
|
|
3195
|
-
message.tool_call_id.length > 0
|
|
3196
|
-
) {
|
|
3197
|
-
return `tool:call:${message.tool_call_id}`;
|
|
3198
|
-
}
|
|
3199
|
-
if (typeof message.id === 'string' && message.id.length > 0) {
|
|
3200
|
-
return `${type}:id:${message.id}`;
|
|
3201
|
-
}
|
|
3202
|
-
return undefined;
|
|
3203
|
-
};
|
|
3204
|
-
|
|
3205
|
-
/**
|
|
3206
|
-
* Provider projections clone messages. Preserve their baseline origin
|
|
3207
|
-
* without writing tracking metadata onto the wire. Synthetic fold
|
|
3208
|
-
* messages intentionally remain unattributed and are charged in full.
|
|
3209
|
-
*/
|
|
3210
|
-
const trackProviderMessageOrigins = (
|
|
3211
|
-
before: BaseMessage[],
|
|
3212
|
-
after: BaseMessage[]
|
|
3213
|
-
): BaseMessage[] => {
|
|
3214
|
-
if (providerMessageBaseline == null || before === after) {
|
|
3215
|
-
return after;
|
|
3216
|
-
}
|
|
3217
|
-
if (before.length === after.length) {
|
|
3218
|
-
for (let i = 0; i < after.length; i++) {
|
|
3219
|
-
const origin = providerMessageOrigins.get(before[i]);
|
|
3220
|
-
if (
|
|
3221
|
-
origin != null &&
|
|
3222
|
-
!providerMessageOrigins.has(after[i]) &&
|
|
3223
|
-
before[i].getType() === after[i].getType() &&
|
|
3224
|
-
!isSyntheticProviderContextMessage(after[i])
|
|
3225
|
-
) {
|
|
3226
|
-
providerMessageOrigins.set(after[i], origin);
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
return after;
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
const keyedOrigins = new Map<string, number | null>();
|
|
3233
|
-
for (const message of before) {
|
|
3234
|
-
const origin = providerMessageOrigins.get(message);
|
|
3235
|
-
const key = getProviderMessageOriginKey(message);
|
|
3236
|
-
if (origin == null || key == null) {
|
|
3237
|
-
continue;
|
|
3238
|
-
}
|
|
3239
|
-
keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
|
|
3240
|
-
}
|
|
3241
|
-
for (const message of after) {
|
|
3242
|
-
if (
|
|
3243
|
-
providerMessageOrigins.has(message) ||
|
|
3244
|
-
isSyntheticProviderContextMessage(message)
|
|
3245
|
-
) {
|
|
3246
|
-
continue;
|
|
3247
|
-
}
|
|
3248
|
-
const key = getProviderMessageOriginKey(message);
|
|
3249
|
-
const origin = key != null ? keyedOrigins.get(key) : undefined;
|
|
3250
|
-
if (origin != null) {
|
|
3251
|
-
providerMessageOrigins.set(message, origin);
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
return after;
|
|
3255
|
-
};
|
|
3158
|
+
const contextPressure = createContextPressureMeter({
|
|
3159
|
+
tokenCounter: agentContext.tokenCounter,
|
|
3160
|
+
sourceMessages: messages,
|
|
3161
|
+
retainedMessages: messagesToUse,
|
|
3162
|
+
indexTokenCountMap: agentContext.indexTokenCountMap,
|
|
3163
|
+
contextUsage,
|
|
3164
|
+
instructionTokens: agentContext.instructionTokens,
|
|
3165
|
+
calibrationRatio: agentContext.calibrationRatio,
|
|
3166
|
+
});
|
|
3167
|
+
const trackProviderMessageOrigins = contextPressure.trackProjection;
|
|
3256
3168
|
|
|
3257
3169
|
if (agentContext.useLegacyContent) {
|
|
3258
3170
|
const before = finalMessages;
|
|
@@ -3300,8 +3212,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3300
3212
|
typeof lastMessageX.content === 'string'
|
|
3301
3213
|
) {
|
|
3302
3214
|
const trimmed = lastMessageX.content.trim();
|
|
3303
|
-
|
|
3304
|
-
|
|
3215
|
+
const before = finalMessages;
|
|
3216
|
+
finalMessages = [...before];
|
|
3217
|
+
finalMessages[finalMessages.length - 2] = cloneMessage(
|
|
3218
|
+
lastMessageX,
|
|
3219
|
+
trimmed.length > 0 ? [{ type: 'text' as const, text: trimmed }] : ''
|
|
3220
|
+
);
|
|
3221
|
+
finalMessages = trackProviderMessageOrigins(before, finalMessages);
|
|
3305
3222
|
}
|
|
3306
3223
|
|
|
3307
3224
|
const localProviderOverflowMeasurements = new WeakMap<
|
|
@@ -3311,123 +3228,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3311
3228
|
estimatedPromptTokens: number;
|
|
3312
3229
|
}
|
|
3313
3230
|
>();
|
|
3314
|
-
const measureProviderPayload =
|
|
3315
|
-
candidate: BaseMessage[],
|
|
3316
|
-
contextBudgetOverride?: number,
|
|
3317
|
-
forceRawRecount = false
|
|
3318
|
-
): {
|
|
3319
|
-
fits: boolean;
|
|
3320
|
-
projectedMessageTokens?: number;
|
|
3321
|
-
availableMessageTokens?: number;
|
|
3322
|
-
contextBudget?: number;
|
|
3323
|
-
effectiveInstructionTokens?: number;
|
|
3324
|
-
} => {
|
|
3325
|
-
const contextBudget =
|
|
3326
|
-
contextBudgetOverride ?? contextUsage?.contextBudget;
|
|
3327
|
-
const effectiveInstructionTokens =
|
|
3328
|
-
contextUsage?.effectiveInstructionTokens ??
|
|
3329
|
-
(forceRawRecount ? agentContext.instructionTokens : undefined);
|
|
3330
|
-
if (
|
|
3331
|
-
agentContext.tokenCounter == null ||
|
|
3332
|
-
contextBudget == null ||
|
|
3333
|
-
effectiveInstructionTokens == null
|
|
3334
|
-
) {
|
|
3335
|
-
return { fits: true };
|
|
3336
|
-
}
|
|
3337
|
-
const availableMessageTokens = Math.max(
|
|
3338
|
-
0,
|
|
3339
|
-
contextBudget - effectiveInstructionTokens
|
|
3340
|
-
);
|
|
3341
|
-
let usageRatio =
|
|
3342
|
-
agentContext.calibrationRatio > 0 ? agentContext.calibrationRatio : 1;
|
|
3343
|
-
if (
|
|
3344
|
-
contextUsage?.calibrationRatio != null &&
|
|
3345
|
-
contextUsage.calibrationRatio > 0
|
|
3346
|
-
) {
|
|
3347
|
-
usageRatio = contextUsage.calibrationRatio;
|
|
3348
|
-
}
|
|
3349
|
-
if (forceRawRecount) {
|
|
3350
|
-
usageRatio = Math.max(1, usageRatio);
|
|
3351
|
-
}
|
|
3352
|
-
const baselineRemaining = contextUsage?.remainingContextTokens;
|
|
3353
|
-
const accountedMessageTokens =
|
|
3354
|
-
!forceRawRecount &&
|
|
3355
|
-
providerMessageBaseline != null &&
|
|
3356
|
-
baselineRemaining != null &&
|
|
3357
|
-
Number.isFinite(baselineRemaining)
|
|
3358
|
-
? availableMessageTokens -
|
|
3359
|
-
Math.min(availableMessageTokens, Math.max(0, baselineRemaining))
|
|
3360
|
-
: undefined;
|
|
3361
|
-
|
|
3362
|
-
let projectedMessageTokens: number;
|
|
3363
|
-
if (accountedMessageTokens != null && providerMessageBaseline != null) {
|
|
3364
|
-
const replyPrimerTokens = Math.round(
|
|
3365
|
-
REPLY_PRIMER_TOKENS * usageRatio
|
|
3366
|
-
);
|
|
3367
|
-
const rawWeights: Record<string, number> = {};
|
|
3368
|
-
let totalWeight = 0;
|
|
3369
|
-
for (let i = 0; i < providerMessageBaseline.length; i++) {
|
|
3370
|
-
const weight = providerMessageBaseline[i].accountingWeight;
|
|
3371
|
-
rawWeights[i] = weight;
|
|
3372
|
-
totalWeight += weight;
|
|
3373
|
-
}
|
|
3374
|
-
const attributableTokens =
|
|
3375
|
-
totalWeight > 0
|
|
3376
|
-
? Math.min(
|
|
3377
|
-
Math.max(0, accountedMessageTokens - replyPrimerTokens),
|
|
3378
|
-
Math.round(totalWeight * usageRatio)
|
|
3379
|
-
)
|
|
3380
|
-
: 0;
|
|
3381
|
-
const apportionedTokens =
|
|
3382
|
-
totalWeight > 0
|
|
3383
|
-
? apportionTokenCounts(
|
|
3384
|
-
rawWeights,
|
|
3385
|
-
attributableTokens / totalWeight,
|
|
3386
|
-
attributableTokens
|
|
3387
|
-
)
|
|
3388
|
-
: {};
|
|
3389
|
-
const attributedByOrigin = providerMessageBaseline.map(
|
|
3390
|
-
(_, origin) => apportionedTokens[origin] || 0
|
|
3391
|
-
);
|
|
3392
|
-
projectedMessageTokens = Math.max(
|
|
3393
|
-
replyPrimerTokens,
|
|
3394
|
-
accountedMessageTokens - attributableTokens
|
|
3395
|
-
);
|
|
3396
|
-
let newRawTokens = 0;
|
|
3397
|
-
const usedOrigins = new Set<number>();
|
|
3398
|
-
for (const message of candidate) {
|
|
3399
|
-
const rawTokens = agentContext.tokenCounter(message);
|
|
3400
|
-
const origin = providerMessageOrigins.get(message);
|
|
3401
|
-
if (origin == null || usedOrigins.has(origin)) {
|
|
3402
|
-
newRawTokens += rawTokens;
|
|
3403
|
-
continue;
|
|
3404
|
-
}
|
|
3405
|
-
usedOrigins.add(origin);
|
|
3406
|
-
projectedMessageTokens += Math.max(
|
|
3407
|
-
0,
|
|
3408
|
-
attributedByOrigin[origin] +
|
|
3409
|
-
Math.round(
|
|
3410
|
-
(rawTokens - providerMessageBaseline[origin].rawTokens) *
|
|
3411
|
-
usageRatio
|
|
3412
|
-
)
|
|
3413
|
-
);
|
|
3414
|
-
}
|
|
3415
|
-
projectedMessageTokens += Math.round(newRawTokens * usageRatio);
|
|
3416
|
-
} else {
|
|
3417
|
-
let rawTokens = REPLY_PRIMER_TOKENS;
|
|
3418
|
-
for (const message of candidate) {
|
|
3419
|
-
rawTokens += agentContext.tokenCounter(message);
|
|
3420
|
-
}
|
|
3421
|
-
projectedMessageTokens = Math.round(rawTokens * usageRatio);
|
|
3422
|
-
}
|
|
3423
|
-
return {
|
|
3424
|
-
fits: projectedMessageTokens <= availableMessageTokens,
|
|
3425
|
-
projectedMessageTokens,
|
|
3426
|
-
availableMessageTokens,
|
|
3427
|
-
contextBudget,
|
|
3428
|
-
effectiveInstructionTokens,
|
|
3429
|
-
};
|
|
3430
|
-
};
|
|
3231
|
+
const measureProviderPayload = contextPressure.measure;
|
|
3431
3232
|
|
|
3432
3233
|
const createProviderPayloadOverflowError = ({
|
|
3433
3234
|
projection,
|
|
@@ -3782,10 +3583,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3782
3583
|
finalMessages = trackProviderMessageOrigins(
|
|
3783
3584
|
beforeSanitizeMessages,
|
|
3784
3585
|
sanitizeOrphanToolBlocks(beforeSanitizeMessages, (source, clone) => {
|
|
3785
|
-
|
|
3786
|
-
if (origin != null) {
|
|
3787
|
-
providerMessageOrigins.set(clone, origin);
|
|
3788
|
-
}
|
|
3586
|
+
contextPressure.trackClone(source, clone);
|
|
3789
3587
|
})
|
|
3790
3588
|
);
|
|
3791
3589
|
if (finalMessages.length !== beforeSanitize) {
|
|
@@ -3858,23 +3656,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3858
3656
|
|
|
3859
3657
|
const fallbackBaseMessages = finalMessages;
|
|
3860
3658
|
const beforeFinalProviderProjection = fallbackBaseMessages;
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3659
|
+
const preparedRequest = prepareProviderRequest({
|
|
3660
|
+
model: (this.overrideModel ?? model) as t.ChatModel,
|
|
3661
|
+
messages: beforeFinalProviderProjection,
|
|
3662
|
+
provider: agentContext.provider,
|
|
3663
|
+
context: this,
|
|
3664
|
+
config,
|
|
3665
|
+
maxToolResultChars: maxProviderToolResultChars,
|
|
3666
|
+
measure: (preparedMessages) =>
|
|
3667
|
+
measureProviderPayload(
|
|
3668
|
+
trackProviderMessageOrigins(
|
|
3669
|
+
beforeFinalProviderProjection,
|
|
3670
|
+
preparedMessages
|
|
3671
|
+
)
|
|
3672
|
+
),
|
|
3673
|
+
});
|
|
3674
|
+
finalMessages = preparedRequest.messages;
|
|
3871
3675
|
|
|
3872
3676
|
/**
|
|
3873
3677
|
* Prompt-cache placement and orphan sanitization are provider-wire
|
|
3874
3678
|
* transforms too. Re-measure after both so no content added after the
|
|
3875
3679
|
* earlier artifact/synthetic compaction decision can bypass the guard.
|
|
3876
3680
|
*/
|
|
3877
|
-
finalProjection =
|
|
3681
|
+
finalProjection =
|
|
3682
|
+
preparedRequest.measurement ?? measureProviderPayload(finalMessages);
|
|
3878
3683
|
const preInvokeContextOverflowError = !finalProjection.fits
|
|
3879
3684
|
? createProviderPayloadOverflowError({
|
|
3880
3685
|
projection: finalProjection,
|
|
@@ -4083,9 +3888,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4083
3888
|
() =>
|
|
4084
3889
|
attemptInvoke(
|
|
4085
3890
|
{
|
|
4086
|
-
|
|
4087
|
-
messages: finalMessages,
|
|
4088
|
-
provider: agentContext.provider,
|
|
3891
|
+
request: preparedRequest,
|
|
4089
3892
|
context: this,
|
|
4090
3893
|
},
|
|
4091
3894
|
invokeConfig
|
|
@@ -4288,7 +4091,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4288
4091
|
estimatedPromptTokens: getEstimatedPromptTokens(contextUsage),
|
|
4289
4092
|
maxContextTokens: agentContext.maxContextTokens,
|
|
4290
4093
|
},
|
|
4291
|
-
|
|
4094
|
+
prepareProviderRequest: ({
|
|
4292
4095
|
model: fallbackModel,
|
|
4293
4096
|
messages: fallbackMessages,
|
|
4294
4097
|
provider: fallbackProvider,
|
|
@@ -4300,36 +4103,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4300
4103
|
calculateMaxToolResultChars(
|
|
4301
4104
|
fallbackMaxContextTokens ?? agentContext.maxContextTokens
|
|
4302
4105
|
);
|
|
4303
|
-
/**
|
|
4304
|
-
* Serving-provider cue shaping BEFORE the fallback payload
|
|
4305
|
-
* is measured: a Claude fallback behind a tolerant primary
|
|
4306
|
-
* gains the cue inside the guarded projection (a prompt
|
|
4307
|
-
* within the cue's cost of the fallback budget must take
|
|
4308
|
-
* the recovery path, not ship oversized), and a tolerant
|
|
4309
|
-
* fallback behind an Anthropic primary sheds the baked cue
|
|
4310
|
-
* before it is measured against the tighter budget. The
|
|
4311
|
-
* attemptInvoke funnel pass then finds nothing to change.
|
|
4312
|
-
*/
|
|
4313
|
-
const cueShapedFallbackMessages = trackProviderMessageOrigins(
|
|
4314
|
-
fallbackMessages,
|
|
4315
|
-
isAnthropicLike(fallbackProvider, {
|
|
4316
|
-
model: resolveServingModelId(fallbackModel),
|
|
4317
|
-
})
|
|
4318
|
-
? appendPredecessorHandoffCue(fallbackMessages, (m) =>
|
|
4319
|
-
this.isRunProducedMessage(m)
|
|
4320
|
-
)
|
|
4321
|
-
: removePredecessorHandoffCue(fallbackMessages)
|
|
4322
|
-
);
|
|
4323
|
-
const projectedFallbackMessages = trackProviderMessageOrigins(
|
|
4324
|
-
cueShapedFallbackMessages,
|
|
4325
|
-
projectMessagesForProvider({
|
|
4326
|
-
model: fallbackModel,
|
|
4327
|
-
messages: cueShapedFallbackMessages,
|
|
4328
|
-
provider: fallbackProvider,
|
|
4329
|
-
maxToolResultChars: fallbackToolResultChars,
|
|
4330
|
-
callOptions: fallbackConfig,
|
|
4331
|
-
})
|
|
4332
|
-
);
|
|
4333
4106
|
const primaryContextBudget = contextUsage?.contextBudget;
|
|
4334
4107
|
const fallbackContextBudget =
|
|
4335
4108
|
fallbackMaxContextTokens == null
|
|
@@ -4338,11 +4111,31 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4338
4111
|
primaryContextBudget ?? fallbackMaxContextTokens,
|
|
4339
4112
|
fallbackMaxContextTokens
|
|
4340
4113
|
);
|
|
4341
|
-
const
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4114
|
+
const preparedFallbackRequest = prepareProviderRequest({
|
|
4115
|
+
model: fallbackModel,
|
|
4116
|
+
messages: fallbackMessages,
|
|
4117
|
+
provider: fallbackProvider,
|
|
4118
|
+
context: this,
|
|
4119
|
+
config: fallbackConfig,
|
|
4120
|
+
maxToolResultChars: fallbackToolResultChars,
|
|
4121
|
+
measure: (preparedMessages) =>
|
|
4122
|
+
measureProviderPayload(
|
|
4123
|
+
trackProviderMessageOrigins(
|
|
4124
|
+
fallbackMessages,
|
|
4125
|
+
preparedMessages
|
|
4126
|
+
),
|
|
4127
|
+
{
|
|
4128
|
+
contextBudget: fallbackContextBudget,
|
|
4129
|
+
forceRawRecount: true,
|
|
4130
|
+
}
|
|
4131
|
+
),
|
|
4132
|
+
});
|
|
4133
|
+
const projection =
|
|
4134
|
+
preparedFallbackRequest.measurement ??
|
|
4135
|
+
measureProviderPayload(preparedFallbackRequest.messages, {
|
|
4136
|
+
contextBudget: fallbackContextBudget,
|
|
4137
|
+
forceRawRecount: true,
|
|
4138
|
+
});
|
|
4346
4139
|
if (!projection.fits) {
|
|
4347
4140
|
throw createProviderPayloadOverflowError({
|
|
4348
4141
|
projection,
|
|
@@ -4350,7 +4143,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4350
4143
|
info: 'Fallback provider message formatting exceeded the context budget before invocation.',
|
|
4351
4144
|
});
|
|
4352
4145
|
}
|
|
4353
|
-
return
|
|
4146
|
+
return preparedFallbackRequest;
|
|
4354
4147
|
},
|
|
4355
4148
|
})
|
|
4356
4149
|
);
|
package/src/index.ts
CHANGED
|
@@ -88,6 +88,14 @@ export type { SmoothItem, SmoothPiece } from './llm/stream/smoother';
|
|
|
88
88
|
export { FakeChatModel, createFakeStreamingLLM } from './llm/fake';
|
|
89
89
|
export { initializeModel } from './llm/init';
|
|
90
90
|
export { attemptInvoke, tryFallbackProviders } from './llm/invoke';
|
|
91
|
+
export { prepareProviderRequest } from './llm/prepareProviderRequest';
|
|
92
|
+
export type {
|
|
93
|
+
PreparedProviderRequest,
|
|
94
|
+
PrepareProviderRequestParams,
|
|
95
|
+
ProviderMessageProjectionMode,
|
|
96
|
+
ProviderPayloadMeasurement,
|
|
97
|
+
ProviderRequestContext,
|
|
98
|
+
} from './llm/prepareProviderRequest';
|
|
91
99
|
export { canSealPreempt } from './llm/preempt';
|
|
92
100
|
export { isThinkingEnabled, getMaxOutputTokensKey } from './llm/request';
|
|
93
101
|
export {
|