@librechat/agents 3.7.6 → 3.7.8
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 +23 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +10 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/eventActor/EventActorExecutor.cjs +305 -11
- package/dist/cjs/eventActor/EventActorExecutor.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +16 -5
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +75 -0
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
- package/dist/cjs/llm/fake.cjs +9 -5
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
- package/dist/cjs/llm/providers.cjs +1 -1
- package/dist/cjs/llm/truncation.cjs +1 -0
- package/dist/cjs/main.cjs +3 -2
- package/dist/cjs/messages/format.cjs +298 -3
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +25 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/node.cjs +43 -9
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/summarization/semanticIndex.cjs +362 -0
- package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +23 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +10 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/eventActor/EventActorExecutor.mjs +305 -11
- package/dist/esm/eventActor/EventActorExecutor.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +16 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +75 -0
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
- package/dist/esm/llm/fake.mjs +9 -5
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
- package/dist/esm/llm/providers.mjs +1 -1
- package/dist/esm/llm/truncation.mjs +1 -1
- package/dist/esm/main.mjs +4 -4
- package/dist/esm/messages/format.mjs +298 -3
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +25 -15
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/node.mjs +43 -9
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/summarization/semanticIndex.mjs +360 -0
- package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
- package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/types/agents/AgentContext.d.ts +27 -1
- package/dist/types/common/constants.d.ts +10 -0
- package/dist/types/eventActor/EventActorExecutor.d.ts +3 -1
- package/dist/types/eventActor/index.d.ts +1 -1
- package/dist/types/eventActor/types.d.ts +149 -5
- package/dist/types/graphs/Graph.d.ts +15 -0
- package/dist/types/llm/fake.d.ts +12 -2
- package/dist/types/messages/format.d.ts +10 -1
- package/dist/types/run.d.ts +17 -5
- package/dist/types/summarization/semanticIndex.d.ts +19 -0
- package/dist/types/types/graph.d.ts +9 -1
- package/dist/types/types/summarize.d.ts +38 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +61 -1
- package/src/common/constants.ts +11 -0
- package/src/eventActor/EventActorExecutor.ts +545 -16
- package/src/eventActor/index.ts +17 -0
- package/src/eventActor/types.ts +182 -3
- package/src/graphs/Graph.ts +41 -8
- package/src/langfuseTraceShaping.ts +94 -0
- package/src/llm/fake.ts +35 -4
- package/src/messages/format.ts +652 -4
- package/src/run.ts +36 -10
- package/src/summarization/node.ts +90 -33
- package/src/summarization/semanticIndex.ts +653 -0
- package/src/tools/subagent/childGraphConfig.ts +3 -0
- package/src/types/graph.ts +9 -0
- package/src/types/summarize.ts +50 -0
package/src/eventActor/index.ts
CHANGED
|
@@ -5,7 +5,15 @@ export {
|
|
|
5
5
|
export type {
|
|
6
6
|
EventActorAdapterPrepareRequest,
|
|
7
7
|
EventActorAdapterPreparation,
|
|
8
|
+
EventActorAdapterInvocationResult,
|
|
9
|
+
EventActorAdapterResumeRequest,
|
|
10
|
+
EventActorAdapterResumeResult,
|
|
11
|
+
EventActorAdapterSuspendedResult,
|
|
8
12
|
EventActorAppliedResult,
|
|
13
|
+
EventActorAdapterCancelSuspensionRequest,
|
|
14
|
+
EventActorAdapterCancelSuspensionResult,
|
|
15
|
+
EventActorCancelSuspensionRequest,
|
|
16
|
+
EventActorCancelSuspensionResult,
|
|
9
17
|
EventActorCheckpointFork,
|
|
10
18
|
EventActorCheckpointReference,
|
|
11
19
|
EventActorCommitRequest,
|
|
@@ -21,12 +29,21 @@ export type {
|
|
|
21
29
|
EventActorInvocation,
|
|
22
30
|
EventActorInvocationContext,
|
|
23
31
|
EventActorIndeterminateResult,
|
|
32
|
+
EventActorInterrupt,
|
|
24
33
|
EventActorInvocationReference,
|
|
25
34
|
EventActorInvocationResult,
|
|
26
35
|
EventActorPreparation,
|
|
27
36
|
EventActorPreparationContext,
|
|
28
37
|
EventActorPreparedInvocation,
|
|
29
38
|
EventActorPrepareRequest,
|
|
39
|
+
EventActorResumeRequest,
|
|
40
|
+
EventActorSettleSuspensionRequest,
|
|
41
|
+
EventActorSettleSuspensionResult,
|
|
42
|
+
EventActorSettlementAuthority,
|
|
30
43
|
EventActorSettlementResult,
|
|
44
|
+
EventActorSuspendRequest,
|
|
45
|
+
EventActorSuspendResult,
|
|
46
|
+
EventActorSuspendedResult,
|
|
47
|
+
EventActorSuspension,
|
|
31
48
|
EventActorTerminalResult,
|
|
32
49
|
} from './types';
|
package/src/eventActor/types.ts
CHANGED
|
@@ -74,14 +74,156 @@ export type EventActorTerminalResult<TResult extends EventActorEvent> =
|
|
|
74
74
|
}
|
|
75
75
|
| { status: 'completed_no_action'; result?: TResult };
|
|
76
76
|
|
|
77
|
+
/** JSON-safe interrupt descriptor retained with a suspended invocation fork. */
|
|
78
|
+
export interface EventActorInterrupt<
|
|
79
|
+
TPayload extends EventActorEvent = EventActorEvent,
|
|
80
|
+
> {
|
|
81
|
+
id: string;
|
|
82
|
+
payload: TPayload;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Nonterminal adapter outcome. The checkpoint must already contain the pause;
|
|
87
|
+
* the SDK publishes its authority through `suspend` before exposing it.
|
|
88
|
+
*/
|
|
89
|
+
export interface EventActorAdapterSuspendedResult<
|
|
90
|
+
TPayload extends EventActorEvent = EventActorEvent,
|
|
91
|
+
> {
|
|
92
|
+
status: 'suspended';
|
|
93
|
+
checkpoint: EventActorCheckpointFork;
|
|
94
|
+
interrupt: EventActorInterrupt<TPayload>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type EventActorAdapterInvocationResult<
|
|
98
|
+
TResult extends EventActorEvent,
|
|
99
|
+
TPayload extends EventActorEvent = EventActorEvent,
|
|
100
|
+
> =
|
|
101
|
+
| EventActorTerminalResult<TResult>
|
|
102
|
+
| EventActorAdapterSuspendedResult<TPayload>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Authenticated, versioned, JSON-safe evidence for one nonterminal invocation.
|
|
106
|
+
* Integrity does not make this evidence one-shot; the host's durable current
|
|
107
|
+
* suspension record is the replay and ownership fence. Hosts must capability-
|
|
108
|
+
* route each exact version during rolling deploys and drain or migrate current
|
|
109
|
+
* evidence before rotating away from its signing key.
|
|
110
|
+
*/
|
|
111
|
+
export interface EventActorSuspension<
|
|
112
|
+
TPayload extends EventActorEvent = EventActorEvent,
|
|
113
|
+
> {
|
|
114
|
+
version: 1;
|
|
115
|
+
suspensionId: string;
|
|
116
|
+
attempt: number;
|
|
117
|
+
issuedAt: number;
|
|
118
|
+
expiresAt: number;
|
|
119
|
+
invocation: EventActorInvocationReference;
|
|
120
|
+
checkpoint: EventActorCheckpointFork;
|
|
121
|
+
interrupt: EventActorInterrupt<TPayload>;
|
|
122
|
+
suspensionDigest: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface EventActorSuspendedResult<
|
|
126
|
+
TPayload extends EventActorEvent = EventActorEvent,
|
|
127
|
+
> {
|
|
128
|
+
status: 'suspended';
|
|
129
|
+
suspension: EventActorSuspension<TPayload>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface EventActorSuspendRequest {
|
|
133
|
+
suspension: EventActorSuspension;
|
|
134
|
+
/** CAS predecessor required when a claimed resume pauses again. */
|
|
135
|
+
previous?: {
|
|
136
|
+
suspensionId: string;
|
|
137
|
+
attempt: number;
|
|
138
|
+
resumeAttemptId: string;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export type EventActorSuspendResult =
|
|
143
|
+
| { status: 'stored' }
|
|
144
|
+
| { status: 'stale' };
|
|
145
|
+
|
|
146
|
+
export interface EventActorAdapterResumeRequest {
|
|
147
|
+
suspension: EventActorSuspension;
|
|
148
|
+
resumeAttemptId: string;
|
|
149
|
+
value: EventActorEvent;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export type EventActorAdapterResumeResult<TResult extends EventActorEvent> =
|
|
153
|
+
| {
|
|
154
|
+
status: 'claimed';
|
|
155
|
+
result: EventActorAdapterInvocationResult<TResult>;
|
|
156
|
+
}
|
|
157
|
+
| {
|
|
158
|
+
/** The host proved no action before returning this claimed failure. */
|
|
159
|
+
status: 'claimed_failed';
|
|
160
|
+
error: Error;
|
|
161
|
+
}
|
|
162
|
+
| { status: 'stale' };
|
|
163
|
+
|
|
164
|
+
export interface EventActorResumeRequest {
|
|
165
|
+
suspension: EventActorSuspension;
|
|
166
|
+
resumeAttemptId: string;
|
|
167
|
+
value: EventActorEvent;
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface EventActorCancelSuspensionRequest {
|
|
172
|
+
suspension: EventActorSuspension;
|
|
173
|
+
cancelAttemptId: string;
|
|
174
|
+
reason?: 'cancelled' | 'expired';
|
|
175
|
+
signal?: AbortSignal;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface EventActorAdapterCancelSuspensionRequest {
|
|
179
|
+
suspension: EventActorSuspension;
|
|
180
|
+
cancelAttemptId: string;
|
|
181
|
+
reason: 'cancelled' | 'expired';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type EventActorCancelSuspensionResult =
|
|
185
|
+
| { status: 'cancelled' }
|
|
186
|
+
| EventActorIndeterminateResult<EventActorEvent>;
|
|
187
|
+
|
|
188
|
+
export type EventActorAdapterCancelSuspensionResult =
|
|
189
|
+
| { status: 'cancelled' }
|
|
190
|
+
| { status: 'stale' };
|
|
191
|
+
|
|
192
|
+
export interface EventActorSettleSuspensionRequest {
|
|
193
|
+
suspensionId: string;
|
|
194
|
+
attempt: number;
|
|
195
|
+
resumeAttemptId: string;
|
|
196
|
+
status: 'completed_no_action' | 'failed';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type EventActorSettleSuspensionResult =
|
|
200
|
+
| { status: 'settled' }
|
|
201
|
+
| { status: 'stale' };
|
|
202
|
+
|
|
77
203
|
export type EventActorAppliedResult<TResult extends EventActorEvent> = Extract<
|
|
78
204
|
EventActorTerminalResult<TResult>,
|
|
79
205
|
{ status: 'applied' }
|
|
80
206
|
> & {
|
|
81
|
-
/** Executor-issued
|
|
207
|
+
/** Executor-issued settlement for the invocation that produced this action. */
|
|
82
208
|
invocation: EventActorInvocationReference;
|
|
209
|
+
/** Authenticated cross-executor authority for a resumed terminal action. */
|
|
210
|
+
settlementAuthority?: EventActorSettlementAuthority;
|
|
83
211
|
};
|
|
84
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Authenticated fence that binds a resumed terminal result to its claimed
|
|
215
|
+
* suspension. The host must consume it atomically with the actor-head CAS.
|
|
216
|
+
*/
|
|
217
|
+
export interface EventActorSettlementAuthority {
|
|
218
|
+
version: 1;
|
|
219
|
+
suspensionId: string;
|
|
220
|
+
attempt: number;
|
|
221
|
+
resumeAttemptId: string;
|
|
222
|
+
issuedAt: number;
|
|
223
|
+
expiresAt: number;
|
|
224
|
+
settlementDigest: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
85
227
|
export interface EventActorIndeterminateResult<
|
|
86
228
|
TResult extends EventActorEvent,
|
|
87
229
|
> {
|
|
@@ -95,6 +237,7 @@ export interface EventActorIndeterminateResult<
|
|
|
95
237
|
export type EventActorInvocationResult<TResult extends EventActorEvent> =
|
|
96
238
|
| EventActorAppliedResult<TResult>
|
|
97
239
|
| EventActorIndeterminateResult<TResult>
|
|
240
|
+
| EventActorSuspendedResult
|
|
98
241
|
| Extract<
|
|
99
242
|
EventActorTerminalResult<TResult>,
|
|
100
243
|
{ status: 'completed_no_action' }
|
|
@@ -128,6 +271,8 @@ export interface EventActorCommitRequest<TResult extends EventActorEvent> {
|
|
|
128
271
|
expectedHead: EventActorHead;
|
|
129
272
|
checkpoint: EventActorCheckpointFork;
|
|
130
273
|
result: TResult;
|
|
274
|
+
/** Host must consume this suspension fence atomically with the head CAS. */
|
|
275
|
+
settlementAuthority?: EventActorSettlementAuthority;
|
|
131
276
|
retention: {
|
|
132
277
|
committedCheckpoints: 2;
|
|
133
278
|
dormantCheckpointTtlMs: number;
|
|
@@ -156,7 +301,11 @@ export interface EventActorDiscardRequest {
|
|
|
156
301
|
/**
|
|
157
302
|
* Host adapter for durable actor state and the concrete agent invocation.
|
|
158
303
|
* `commit` must compare both the expected generation and checkpoint identity
|
|
159
|
-
* atomically before advancing the logical actor head.
|
|
304
|
+
* atomically before advancing the logical actor head. When settlement authority
|
|
305
|
+
* is present, that same transaction must also verify and close the exact
|
|
306
|
+
* suspension/resume-attempt fence, including stale-head outcomes. Retrying an
|
|
307
|
+
* ambiguous acknowledgement must return the durable outcome rather than apply
|
|
308
|
+
* the same terminal transition again. The host mailbox
|
|
160
309
|
* deduplicates the logical `invocationId` before entering this seam, while each
|
|
161
310
|
* SDK execution attempt receives a distinct checkpoint namespace. Preparation
|
|
162
311
|
* methods own rollback until they return a ready invocation and must treat the
|
|
@@ -170,6 +319,18 @@ export interface EventActorDiscardRequest {
|
|
|
170
319
|
* fork: the SDK retains and surfaces it as `commit_conflict` for host
|
|
171
320
|
* reconciliation. `discard` must be idempotent for the same invocation because
|
|
172
321
|
* an ambiguous cleanup failure can be retried through the public lifecycle.
|
|
322
|
+
*
|
|
323
|
+
* Suspension-capable hosts implement all optional suspension methods. `suspend`
|
|
324
|
+
* publishes an initial suspension only while its logical invocation is current;
|
|
325
|
+
* with `previous`, it atomically replaces only the exact claimed predecessor.
|
|
326
|
+
* `resume` atomically claims the current suspension before applying its value to
|
|
327
|
+
* the declared interrupt and rejects duplicate or competing claims as `stale`.
|
|
328
|
+
* It must return `claimed_failed` only when it can prove no qualifying action;
|
|
329
|
+
* any failure after an action returns `applied`. `settleSuspension` atomically
|
|
330
|
+
* discards the claimed fork and closes its fence for definite no-action
|
|
331
|
+
* outcomes. `cancelSuspension`
|
|
332
|
+
* atomically claims, discards, and closes current state, including expired
|
|
333
|
+
* evidence; expiration alone never implies a safe action outcome.
|
|
173
334
|
*/
|
|
174
335
|
export interface EventActorHostAdapter<
|
|
175
336
|
TEvent extends EventActorEvent,
|
|
@@ -187,7 +348,20 @@ export interface EventActorHostAdapter<
|
|
|
187
348
|
invoke(
|
|
188
349
|
invocation: EventActorInvocation<TEvent>,
|
|
189
350
|
context: EventActorInvocationContext
|
|
190
|
-
): Promise<
|
|
351
|
+
): Promise<EventActorAdapterInvocationResult<TResult>>;
|
|
352
|
+
suspend?(request: EventActorSuspendRequest): Promise<EventActorSuspendResult>;
|
|
353
|
+
resume?(
|
|
354
|
+
request: EventActorAdapterResumeRequest,
|
|
355
|
+
context: EventActorInvocationContext
|
|
356
|
+
): Promise<EventActorAdapterResumeResult<TResult>>;
|
|
357
|
+
/** Atomically claims, discards, and closes the current suspension. */
|
|
358
|
+
cancelSuspension?(
|
|
359
|
+
request: EventActorAdapterCancelSuspensionRequest,
|
|
360
|
+
context: EventActorPreparationContext
|
|
361
|
+
): Promise<EventActorAdapterCancelSuspensionResult>;
|
|
362
|
+
settleSuspension?(
|
|
363
|
+
request: EventActorSettleSuspensionRequest
|
|
364
|
+
): Promise<EventActorSettleSuspensionResult>;
|
|
191
365
|
commit(
|
|
192
366
|
request: EventActorCommitRequest<TResult>
|
|
193
367
|
): Promise<EventActorCommitResult>;
|
|
@@ -219,6 +393,9 @@ export type EventActorExecutionResult<TResult extends EventActorEvent> =
|
|
|
219
393
|
status: 'cancelled';
|
|
220
394
|
continuation: 'warm' | 'cold';
|
|
221
395
|
}
|
|
396
|
+
| (EventActorSuspendedResult & {
|
|
397
|
+
continuation: 'warm' | 'cold';
|
|
398
|
+
})
|
|
222
399
|
| {
|
|
223
400
|
/** The action happened, but another head won the CAS. Reconcile; do not retry. */
|
|
224
401
|
status: 'commit_conflict';
|
|
@@ -247,4 +424,6 @@ export interface EventActorExecutorOptions {
|
|
|
247
424
|
dormantCheckpointTtlMs?: number;
|
|
248
425
|
/** Stable private key of at least 32 bytes for cross-lifetime handoffs. */
|
|
249
426
|
preparationSigningKey?: string | Uint8Array;
|
|
427
|
+
/** Maximum UTF-8 byte size of canonical suspension evidence. */
|
|
428
|
+
maxSuspensionPayloadBytes?: number;
|
|
250
429
|
}
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -153,10 +153,10 @@ import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
|
153
153
|
import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
|
|
154
154
|
import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
|
|
155
155
|
import { createContextPressureMeter } from '@/llm/contextPressureMeter';
|
|
156
|
-
import { prepareToolsForPromptCache } from '@/llm/promptCacheTools';
|
|
157
156
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
158
157
|
import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
|
|
159
158
|
import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
|
|
159
|
+
import { prepareToolsForPromptCache } from '@/llm/promptCacheTools';
|
|
160
160
|
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
161
161
|
import { buildSubagentToolParams } from '@/tools/SubagentTool';
|
|
162
162
|
import { initializeLangfuseTracing } from '@/instrumentation';
|
|
@@ -164,6 +164,7 @@ import { shouldTriggerSummarization } from '@/summarization';
|
|
|
164
164
|
import { isRunStepResumeState } from '@/tools/runStepResume';
|
|
165
165
|
import { resolveLocalToolsForBinding } from '@/tools/local';
|
|
166
166
|
import { createSummarizeNode } from '@/summarization/node';
|
|
167
|
+
import { getTruncationStopReason } from '@/llm/truncation';
|
|
167
168
|
import { messagesStateReducer } from '@/messages/reducer';
|
|
168
169
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
169
170
|
import { AgentContext } from '@/agents/AgentContext';
|
|
@@ -1425,6 +1426,21 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1425
1426
|
* hosts persist it as unfinished rather than complete.
|
|
1426
1427
|
*/
|
|
1427
1428
|
preemptIncomplete = false;
|
|
1429
|
+
/**
|
|
1430
|
+
* True when `routeMessage` sent a turn to `END` because the last AI
|
|
1431
|
+
* message carries no tool call, AND the provider reports it stopped for
|
|
1432
|
+
* hitting the output token ceiling (`getTruncationStopReason`). Plain-text
|
|
1433
|
+
* and reasoning turns cut off this way carry no tool call for
|
|
1434
|
+
* `assertNotTruncatedToolCall` to catch, so `toolsCondition` reads them as
|
|
1435
|
+
* an ordinary finished turn otherwise — hosts read this flag to persist
|
|
1436
|
+
* the turn as unfinished instead of a silently truncated "complete" answer.
|
|
1437
|
+
*
|
|
1438
|
+
* Deliberately separate from `preemptIncomplete`/`preemptHaltReason`: this
|
|
1439
|
+
* has no interaction with the preempt/seal machinery (in particular the
|
|
1440
|
+
* `preemptHaltReason` check at each model node's entry), so setting it
|
|
1441
|
+
* cannot suppress an unrelated agent's turn in a multi-agent graph.
|
|
1442
|
+
*/
|
|
1443
|
+
outputTruncatedIncomplete = false;
|
|
1428
1444
|
/**
|
|
1429
1445
|
* `stopReason` from a `PreemptBoundary` hook that halted the turn.
|
|
1430
1446
|
*
|
|
@@ -1702,6 +1718,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1702
1718
|
this.preemptEmptyBoundaries = 0;
|
|
1703
1719
|
this.preemptIncomplete = false;
|
|
1704
1720
|
this.preemptHaltReason = undefined;
|
|
1721
|
+
this.outputTruncatedIncomplete = false;
|
|
1705
1722
|
}
|
|
1706
1723
|
|
|
1707
1724
|
/**
|
|
@@ -2801,9 +2818,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2801
2818
|
provider,
|
|
2802
2819
|
clientOptions,
|
|
2803
2820
|
tools,
|
|
2804
|
-
isDeferred: makeIsDeferred(
|
|
2805
|
-
agentContext.getEffectiveToolDefinitions()
|
|
2806
|
-
),
|
|
2821
|
+
isDeferred: makeIsDeferred(agentContext.getEffectiveToolDefinitions()),
|
|
2807
2822
|
});
|
|
2808
2823
|
}
|
|
2809
2824
|
|
|
@@ -3030,9 +3045,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3030
3045
|
messagesToRefine.length > 0;
|
|
3031
3046
|
|
|
3032
3047
|
if (hasPrunedMessages) {
|
|
3033
|
-
const shouldSkip =
|
|
3034
|
-
|
|
3035
|
-
|
|
3048
|
+
const shouldSkip =
|
|
3049
|
+
agentContext.summarizationExhausted ||
|
|
3050
|
+
agentContext.shouldSkipSummarization(messages.length);
|
|
3036
3051
|
const triggerResult =
|
|
3037
3052
|
!shouldSkip &&
|
|
3038
3053
|
shouldTriggerSummarization({
|
|
@@ -3089,6 +3104,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
3089
3104
|
messageCount: messages.length,
|
|
3090
3105
|
messagesToRefineCount: messagesToRefine.length,
|
|
3091
3106
|
contextLength: context.length,
|
|
3107
|
+
summarizationFailures: agentContext.summarizationFailures,
|
|
3108
|
+
summarizationExhausted: agentContext.summarizationExhausted,
|
|
3092
3109
|
},
|
|
3093
3110
|
{ runId: this.runId, agentId }
|
|
3094
3111
|
);
|
|
@@ -4818,11 +4835,27 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4818
4835
|
if (state.summarizationRequest != null) {
|
|
4819
4836
|
return summarizeNode;
|
|
4820
4837
|
}
|
|
4821
|
-
|
|
4838
|
+
const decision = toolsCondition(
|
|
4822
4839
|
state as t.BaseGraphState,
|
|
4823
4840
|
toolNode,
|
|
4824
4841
|
this.invokedToolIds
|
|
4825
4842
|
);
|
|
4843
|
+
/**
|
|
4844
|
+
* `toolsCondition` only looks at `tool_calls` — a plain-text/reasoning
|
|
4845
|
+
* turn cut off by the output token ceiling has none, so it reads as an
|
|
4846
|
+
* ordinary finished turn and routes here to END. Flag it so hosts can
|
|
4847
|
+
* tell a genuinely finished answer from one the model never got to
|
|
4848
|
+
* complete. See `outputTruncatedIncomplete` for why this stays clear
|
|
4849
|
+
* of the preempt/seal halt fields.
|
|
4850
|
+
*/
|
|
4851
|
+
if (decision === END) {
|
|
4852
|
+
const { messages } = state as t.BaseGraphState;
|
|
4853
|
+
const lastMessage = messages[messages.length - 1];
|
|
4854
|
+
if (getTruncationStopReason(lastMessage) != null) {
|
|
4855
|
+
this.outputTruncatedIncomplete = true;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
return decision;
|
|
4826
4859
|
};
|
|
4827
4860
|
|
|
4828
4861
|
const StateAnnotation = Annotation.Root({
|
|
@@ -35,6 +35,11 @@ const DEPRECATED_TRACE_INPUT_ATTRIBUTE = 'langfuse.trace.input';
|
|
|
35
35
|
const DEPRECATED_TRACE_OUTPUT_ATTRIBUTE = 'langfuse.trace.output';
|
|
36
36
|
const OBSERVATION_METADATA_LANGGRAPH_NODE = `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.langgraph_node`;
|
|
37
37
|
const OBSERVATION_METADATA_OPERATION = `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.${LANGFUSE_OPERATION_METADATA_KEY}`;
|
|
38
|
+
const OBSERVATION_METADATA_COMPACTION_SEMANTIC_INDEX_ENTRIES = `${LangfuseOtelSpanAttributes.OBSERVATION_METADATA}.compaction_semantic_index_entries`;
|
|
39
|
+
const COMPACTION_SEMANTIC_INDEX_OPEN = '<compaction-semantic-index>';
|
|
40
|
+
const COMPACTION_SEMANTIC_INDEX_CLOSE = '</compaction-semantic-index>';
|
|
41
|
+
const REDACTED_COMPACTION_SEMANTIC_INDEX =
|
|
42
|
+
'<compaction-semantic-index redacted="true" />\n\n';
|
|
38
43
|
|
|
39
44
|
type MutableSpan = ReadableSpan & {
|
|
40
45
|
name: string;
|
|
@@ -66,6 +71,94 @@ function parseAttributeValue(value: unknown): unknown {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
type CompactionSemanticIndexRedaction = {
|
|
75
|
+
value: unknown;
|
|
76
|
+
redacted: boolean;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function redactCompactionSemanticIndexText(
|
|
80
|
+
value: string
|
|
81
|
+
): CompactionSemanticIndexRedaction {
|
|
82
|
+
if (!value.startsWith(COMPACTION_SEMANTIC_INDEX_OPEN)) {
|
|
83
|
+
return { value, redacted: false };
|
|
84
|
+
}
|
|
85
|
+
const start = 0;
|
|
86
|
+
const close = value.indexOf(COMPACTION_SEMANTIC_INDEX_CLOSE, start);
|
|
87
|
+
if (close < 0) {
|
|
88
|
+
return { value, redacted: false };
|
|
89
|
+
}
|
|
90
|
+
let end = close + COMPACTION_SEMANTIC_INDEX_CLOSE.length;
|
|
91
|
+
while (end < value.length && /\s/.test(value[end])) {
|
|
92
|
+
end++;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
value:
|
|
96
|
+
value.slice(0, start) +
|
|
97
|
+
REDACTED_COMPACTION_SEMANTIC_INDEX +
|
|
98
|
+
value.slice(end),
|
|
99
|
+
redacted: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function redactCompactionSemanticIndexValue(
|
|
104
|
+
value: unknown
|
|
105
|
+
): CompactionSemanticIndexRedaction {
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
return redactCompactionSemanticIndexText(value);
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
for (let index = value.length - 1; index >= 0; index--) {
|
|
111
|
+
const nested = redactCompactionSemanticIndexValue(value[index]);
|
|
112
|
+
if (nested.redacted) {
|
|
113
|
+
const result = [...value];
|
|
114
|
+
result[index] = nested.value;
|
|
115
|
+
return { value: result, redacted: true };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { value, redacted: false };
|
|
119
|
+
}
|
|
120
|
+
if (!isRecord(value)) {
|
|
121
|
+
return { value, redacted: false };
|
|
122
|
+
}
|
|
123
|
+
const entries = Object.entries(value);
|
|
124
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
125
|
+
const [key, nestedValue] = entries[index];
|
|
126
|
+
const nested = redactCompactionSemanticIndexValue(nestedValue);
|
|
127
|
+
if (nested.redacted) {
|
|
128
|
+
return {
|
|
129
|
+
value: { ...value, [key]: nested.value },
|
|
130
|
+
redacted: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { value, redacted: false };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function redactCompactionSemanticIndexInput(span: MutableSpan): void {
|
|
138
|
+
const entryCount =
|
|
139
|
+
span.attributes[
|
|
140
|
+
OBSERVATION_METADATA_COMPACTION_SEMANTIC_INDEX_ENTRIES
|
|
141
|
+
];
|
|
142
|
+
const numericEntryCount = Number(entryCount);
|
|
143
|
+
if (!Number.isFinite(numericEntryCount) || numericEntryCount <= 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const inputKey = LangfuseOtelSpanAttributes.OBSERVATION_INPUT;
|
|
147
|
+
const input = span.attributes[inputKey];
|
|
148
|
+
if (input == null) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const parsed = parseAttributeValue(input);
|
|
152
|
+
const redaction = redactCompactionSemanticIndexValue(parsed);
|
|
153
|
+
if (!redaction.redacted) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
span.attributes[inputKey] =
|
|
157
|
+
typeof redaction.value === 'string' && parsed === input
|
|
158
|
+
? redaction.value
|
|
159
|
+
: JSON.stringify(redaction.value);
|
|
160
|
+
}
|
|
161
|
+
|
|
69
162
|
function getMessageArray(
|
|
70
163
|
value: unknown
|
|
71
164
|
): Record<string, unknown>[] | undefined {
|
|
@@ -594,6 +687,7 @@ export function shapeLangfuseSpan(span: ReadableSpan): void {
|
|
|
594
687
|
const mutable = span as MutableSpan;
|
|
595
688
|
delete mutable.attributes[DEPRECATED_TRACE_INPUT_ATTRIBUTE];
|
|
596
689
|
delete mutable.attributes[DEPRECATED_TRACE_OUTPUT_ATTRIBUTE];
|
|
690
|
+
redactCompactionSemanticIndexInput(mutable);
|
|
597
691
|
const isGraphObservation = isGraphSpan(mutable);
|
|
598
692
|
if (mutable.name.startsWith(LANGGRAPH_AGENT_NODE_PREFIX)) {
|
|
599
693
|
shapeAgentNodeSpan(mutable);
|
package/src/llm/fake.ts
CHANGED
|
@@ -14,6 +14,15 @@ export class FakeChatModel extends FakeListChatModel {
|
|
|
14
14
|
private splitStrategy: SplitStrategy;
|
|
15
15
|
private toolCalls: ToolCall[] = [];
|
|
16
16
|
private addedToolCalls: boolean = false;
|
|
17
|
+
/**
|
|
18
|
+
* Attached to the last streamed chunk of every response, mirroring a real
|
|
19
|
+
* provider's terminal `finish_reason`/`stop_reason` on `generationInfo`.
|
|
20
|
+
* Lets truncation-path tests (`getTruncationStopReason`) drive a genuine
|
|
21
|
+
* `Run`/`StandardGraph` without a live provider. `undefined` (the
|
|
22
|
+
* default) reproduces the previous behavior exactly: no metadata on any
|
|
23
|
+
* chunk.
|
|
24
|
+
*/
|
|
25
|
+
private finalChunkGenerationInfo?: Record<string, unknown>;
|
|
17
26
|
|
|
18
27
|
constructor({
|
|
19
28
|
responses,
|
|
@@ -21,16 +30,19 @@ export class FakeChatModel extends FakeListChatModel {
|
|
|
21
30
|
emitCustomEvent,
|
|
22
31
|
splitStrategy = { type: 'regex', value: /(?<=\s+)|(?=\s+)/ },
|
|
23
32
|
toolCalls = [],
|
|
33
|
+
finalChunkGenerationInfo,
|
|
24
34
|
}: {
|
|
25
35
|
responses: string[];
|
|
26
36
|
sleep?: number;
|
|
27
37
|
emitCustomEvent?: boolean;
|
|
28
38
|
splitStrategy?: SplitStrategy;
|
|
29
39
|
toolCalls?: ToolCall[];
|
|
40
|
+
finalChunkGenerationInfo?: Record<string, unknown>;
|
|
30
41
|
}) {
|
|
31
42
|
super({ responses, sleep, emitCustomEvent });
|
|
32
43
|
this.splitStrategy = splitStrategy;
|
|
33
44
|
this.toolCalls = toolCalls;
|
|
45
|
+
this.finalChunkGenerationInfo = finalChunkGenerationInfo;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
private splitText(text: string): string[] {
|
|
@@ -47,14 +59,16 @@ export class FakeChatModel extends FakeListChatModel {
|
|
|
47
59
|
}
|
|
48
60
|
_createResponseChunk(
|
|
49
61
|
text: string,
|
|
50
|
-
tool_call_chunks?: ToolCallChunk[]
|
|
62
|
+
tool_call_chunks?: ToolCallChunk[],
|
|
63
|
+
responseMetadata?: Record<string, unknown>
|
|
51
64
|
): ChatGenerationChunk {
|
|
52
65
|
return new ChatGenerationChunk({
|
|
53
66
|
text,
|
|
54
|
-
generationInfo: {},
|
|
67
|
+
generationInfo: responseMetadata ?? {},
|
|
55
68
|
message: new AIMessageChunk({
|
|
56
69
|
content: text,
|
|
57
70
|
tool_call_chunks,
|
|
71
|
+
response_metadata: responseMetadata,
|
|
58
72
|
additional_kwargs: tool_call_chunks
|
|
59
73
|
? {
|
|
60
74
|
tool_calls: tool_call_chunks.map((toolCall) => ({
|
|
@@ -87,14 +101,31 @@ export class FakeChatModel extends FakeListChatModel {
|
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
const chunks = this.splitText(response);
|
|
90
|
-
for
|
|
104
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
105
|
+
const chunk = chunks[i];
|
|
91
106
|
await this._sleepIfRequested();
|
|
92
107
|
|
|
93
108
|
if (options.thrownErrorString != null && options.thrownErrorString) {
|
|
94
109
|
throw new Error(options.thrownErrorString);
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Only the true terminal chunk of the response gets the metadata: when
|
|
114
|
+
* this response also appends a trailing tool-call chunk below, THAT
|
|
115
|
+
* chunk is terminal instead (not currently exercised by any test —
|
|
116
|
+
* a caller wanting truncated-tool-call metadata should attach it
|
|
117
|
+
* there).
|
|
118
|
+
*/
|
|
119
|
+
const isTerminalChunk =
|
|
120
|
+
i === chunks.length - 1 && this.toolCalls.length === 0;
|
|
121
|
+
const responseChunk =
|
|
122
|
+
isTerminalChunk && this.finalChunkGenerationInfo != null
|
|
123
|
+
? this._createResponseChunk(
|
|
124
|
+
chunk,
|
|
125
|
+
undefined,
|
|
126
|
+
this.finalChunkGenerationInfo
|
|
127
|
+
)
|
|
128
|
+
: super._createResponseChunk(chunk);
|
|
98
129
|
yield responseChunk;
|
|
99
130
|
void runManager?.handleLLMNewToken(chunk);
|
|
100
131
|
}
|