@librechat/agents 3.2.60 → 3.2.62
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/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +32 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +6 -0
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +62 -1
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +266 -43
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +274 -7
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/streamMetadata.cjs +69 -0
- package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -0
- package/dist/cjs/llm/openrouter/index.cjs +5 -6
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -1
- package/dist/cjs/messages/format.cjs +61 -0
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +31 -19
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +13 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +75 -10
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +111 -0
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +32 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +6 -0
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +62 -1
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +266 -43
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +274 -7
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/streamMetadata.mjs +69 -0
- package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -0
- package/dist/esm/llm/openrouter/index.mjs +5 -6
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/format.mjs +61 -0
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +31 -19
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +13 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +75 -10
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +108 -1
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +3 -1
- package/dist/types/hooks/HookRegistry.d.ts +10 -0
- package/dist/types/hooks/index.d.ts +7 -0
- package/dist/types/hooks/types.d.ts +18 -1
- package/dist/types/llm/bedrock/index.d.ts +5 -0
- package/dist/types/llm/openai/index.d.ts +19 -0
- package/dist/types/llm/openai/streamMetadata.d.ts +16 -0
- package/dist/types/messages/prune.d.ts +4 -3
- package/dist/types/tools/ToolNode.d.ts +1 -0
- package/dist/types/types/llm.d.ts +12 -2
- package/dist/types/types/tools.d.ts +1 -1
- package/dist/types/utils/tokens.d.ts +30 -0
- package/package.json +9 -9
- package/src/__tests__/stream.eagerEventExecution.test.ts +100 -2
- package/src/common/enum.ts +2 -0
- package/src/hooks/HookRegistry.ts +45 -0
- package/src/hooks/__tests__/HookRegistry.test.ts +48 -0
- package/src/hooks/__tests__/executeHooks.test.ts +85 -2
- package/src/hooks/executeHooks.ts +15 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/types.ts +18 -1
- package/src/langfuse.ts +134 -1
- package/src/llm/bedrock/index.ts +434 -83
- package/src/llm/bedrock/streamSealDispatch.test.ts +97 -0
- package/src/llm/custom-chat-models.smoke.test.ts +7 -0
- package/src/llm/openai/index.ts +604 -6
- package/src/llm/openai/managedRequests.test.ts +182 -0
- package/src/llm/openai/streamMetadata.spec.ts +86 -0
- package/src/llm/openai/streamMetadata.ts +95 -0
- package/src/llm/openai/streamMetadataDedup.spec.ts +166 -0
- package/src/llm/openrouter/index.ts +9 -5
- package/src/messages/format.ts +96 -3
- package/src/messages/formatAgentMessages.steer.test.ts +326 -0
- package/src/messages/labelContentByAgent.test.ts +75 -0
- package/src/messages/prune.ts +56 -30
- package/src/specs/anthropic.simple.test.ts +4 -2
- package/src/specs/cache.simple.test.ts +17 -4
- package/src/specs/langfuse-callbacks.test.ts +61 -0
- package/src/specs/openai.simple.test.ts +4 -2
- package/src/specs/spec.utils.ts +12 -0
- package/src/specs/summarization.test.ts +9 -13
- package/src/specs/token-accounting-pipeline.test.ts +130 -4
- package/src/specs/tokens.test.ts +214 -0
- package/src/stream.ts +22 -3
- package/src/tools/ToolNode.ts +107 -12
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +554 -0
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +49 -2
- package/src/tools/__tests__/hitl.test.ts +112 -0
- package/src/tools/subagent/SubagentExecutor.ts +2 -0
- package/src/types/llm.ts +12 -2
- package/src/types/tools.ts +1 -1
- package/src/utils/tokens.ts +181 -3
|
@@ -155,7 +155,11 @@ describe('executeHooks', () => {
|
|
|
155
155
|
input: preToolUseInput('Bash'),
|
|
156
156
|
matchQuery: 'Bash',
|
|
157
157
|
});
|
|
158
|
-
expect(result).toEqual({
|
|
158
|
+
expect(result).toEqual({
|
|
159
|
+
additionalContexts: [],
|
|
160
|
+
injectedMessages: [],
|
|
161
|
+
errors: [],
|
|
162
|
+
});
|
|
159
163
|
});
|
|
160
164
|
|
|
161
165
|
it('returns an empty result when no matcher pattern matches the query', async () => {
|
|
@@ -176,7 +180,11 @@ describe('executeHooks', () => {
|
|
|
176
180
|
matchQuery: 'Bash',
|
|
177
181
|
});
|
|
178
182
|
expect(called).toBe(false);
|
|
179
|
-
expect(result).toEqual({
|
|
183
|
+
expect(result).toEqual({
|
|
184
|
+
additionalContexts: [],
|
|
185
|
+
injectedMessages: [],
|
|
186
|
+
errors: [],
|
|
187
|
+
});
|
|
180
188
|
});
|
|
181
189
|
});
|
|
182
190
|
|
|
@@ -439,6 +447,81 @@ describe('executeHooks', () => {
|
|
|
439
447
|
});
|
|
440
448
|
});
|
|
441
449
|
|
|
450
|
+
describe('injectedMessages accumulation', () => {
|
|
451
|
+
it('accumulates injectedMessages from every hook in registration order', async () => {
|
|
452
|
+
const registry = new HookRegistry();
|
|
453
|
+
registry.register('PreToolUse', {
|
|
454
|
+
hooks: [
|
|
455
|
+
preToolHook(
|
|
456
|
+
async (): Promise<PreToolUseHookOutput> => ({
|
|
457
|
+
injectedMessages: [
|
|
458
|
+
{ role: 'user', content: 'steer one', source: 'steer' },
|
|
459
|
+
],
|
|
460
|
+
})
|
|
461
|
+
),
|
|
462
|
+
preToolHook(
|
|
463
|
+
async (): Promise<PreToolUseHookOutput> => emptyPreOutput
|
|
464
|
+
),
|
|
465
|
+
],
|
|
466
|
+
});
|
|
467
|
+
registry.register('PreToolUse', {
|
|
468
|
+
hooks: [
|
|
469
|
+
preToolHook(
|
|
470
|
+
async (): Promise<PreToolUseHookOutput> => ({
|
|
471
|
+
injectedMessages: [
|
|
472
|
+
{ role: 'user', content: 'steer two', source: 'steer' },
|
|
473
|
+
{ role: 'system', content: 'hint', source: 'hook' },
|
|
474
|
+
],
|
|
475
|
+
})
|
|
476
|
+
),
|
|
477
|
+
],
|
|
478
|
+
});
|
|
479
|
+
const result = await executeHooks({
|
|
480
|
+
registry,
|
|
481
|
+
input: preToolUseInput('Bash'),
|
|
482
|
+
matchQuery: 'Bash',
|
|
483
|
+
});
|
|
484
|
+
expect(result.injectedMessages).toEqual([
|
|
485
|
+
{ role: 'user', content: 'steer one', source: 'steer' },
|
|
486
|
+
{ role: 'user', content: 'steer two', source: 'steer' },
|
|
487
|
+
{ role: 'system', content: 'hint', source: 'hook' },
|
|
488
|
+
]);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('returns an empty array when no hook sets injectedMessages', async () => {
|
|
492
|
+
const registry = new HookRegistry();
|
|
493
|
+
registry.register('PreToolUse', { hooks: [noopPreHook] });
|
|
494
|
+
const result = await executeHooks({
|
|
495
|
+
registry,
|
|
496
|
+
input: preToolUseInput('Bash'),
|
|
497
|
+
matchQuery: 'Bash',
|
|
498
|
+
});
|
|
499
|
+
expect(result.injectedMessages).toEqual([]);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('skips injectedMessages from fire-and-forget (async) outputs', async () => {
|
|
503
|
+
const registry = new HookRegistry();
|
|
504
|
+
registry.register('PreToolUse', {
|
|
505
|
+
hooks: [
|
|
506
|
+
preToolHook(
|
|
507
|
+
async (): Promise<PreToolUseHookOutput> => ({
|
|
508
|
+
async: true,
|
|
509
|
+
injectedMessages: [
|
|
510
|
+
{ role: 'user', content: 'ignored', source: 'steer' },
|
|
511
|
+
],
|
|
512
|
+
})
|
|
513
|
+
),
|
|
514
|
+
],
|
|
515
|
+
});
|
|
516
|
+
const result = await executeHooks({
|
|
517
|
+
registry,
|
|
518
|
+
input: preToolUseInput('Bash'),
|
|
519
|
+
matchQuery: 'Bash',
|
|
520
|
+
});
|
|
521
|
+
expect(result.injectedMessages).toEqual([]);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
442
525
|
describe('updatedInput handling', () => {
|
|
443
526
|
it('last-writer-wins on updatedInput follows registration order', async () => {
|
|
444
527
|
const registry = new HookRegistry();
|
|
@@ -54,6 +54,7 @@ interface AbortRace {
|
|
|
54
54
|
function freshResult(): AggregatedHookResult {
|
|
55
55
|
return {
|
|
56
56
|
additionalContexts: [],
|
|
57
|
+
injectedMessages: [],
|
|
57
58
|
errors: [],
|
|
58
59
|
};
|
|
59
60
|
}
|
|
@@ -244,6 +245,19 @@ function applyContext(agg: AggregatedHookResult, output: HookOutput): void {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
function applyInjectedMessages(
|
|
249
|
+
agg: AggregatedHookResult,
|
|
250
|
+
output: HookOutput
|
|
251
|
+
): void {
|
|
252
|
+
if (
|
|
253
|
+
output.injectedMessages === undefined ||
|
|
254
|
+
output.injectedMessages.length === 0
|
|
255
|
+
) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
agg.injectedMessages.push(...output.injectedMessages);
|
|
259
|
+
}
|
|
260
|
+
|
|
247
261
|
function applyStopFlag(agg: AggregatedHookResult, output: HookOutput): void {
|
|
248
262
|
if (output.preventContinuation !== true) {
|
|
249
263
|
return;
|
|
@@ -310,6 +324,7 @@ function fold(outcomes: readonly HookOutcome[]): AggregatedHookResult {
|
|
|
310
324
|
continue;
|
|
311
325
|
}
|
|
312
326
|
applyContext(agg, output);
|
|
327
|
+
applyInjectedMessages(agg, output);
|
|
313
328
|
applyStopFlag(agg, output);
|
|
314
329
|
applyDecision(agg, output);
|
|
315
330
|
applyUpdatedInput(agg, output);
|
package/src/hooks/index.ts
CHANGED
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
export { HookRegistry } from './HookRegistry';
|
|
10
10
|
export type { HookHaltSignal } from './HookRegistry';
|
|
11
11
|
export { executeHooks, DEFAULT_HOOK_TIMEOUT_MS } from './executeHooks';
|
|
12
|
+
/**
|
|
13
|
+
* Feature probe for hosts: hook outputs support `injectedMessages`
|
|
14
|
+
* (per-message graph-state injection at the `PostToolBatch` boundary).
|
|
15
|
+
* Hosts must gate drain-style hooks on this so a queued message can never
|
|
16
|
+
* be consumed by an SDK version that would silently drop it.
|
|
17
|
+
*/
|
|
18
|
+
export const HOOK_INJECTED_MESSAGES_CAPABLE = true;
|
|
12
19
|
export {
|
|
13
20
|
matchesQuery,
|
|
14
21
|
hasNestedQuantifier,
|
package/src/hooks/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/hooks/types.ts
|
|
2
2
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { InjectedMessage } from '@/types/tools';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Closed set of hook lifecycle events supported by the hooks system.
|
|
@@ -134,7 +135,9 @@ export interface PostToolBatchEntry {
|
|
|
134
135
|
*
|
|
135
136
|
* Order: fires AFTER all per-tool PostToolUse / PostToolUseFailure hooks
|
|
136
137
|
* for the same batch have completed, BEFORE the next model call. Pass an
|
|
137
|
-
* `additionalContext` to inject context for that next model turn
|
|
138
|
+
* `additionalContext` to inject context for that next model turn, or
|
|
139
|
+
* `injectedMessages` to inject standalone per-message user speech (e.g.
|
|
140
|
+
* mid-run steering) that must not be consolidated with hook context.
|
|
138
141
|
*/
|
|
139
142
|
export interface PostToolBatchHookInput extends BaseHookInput {
|
|
140
143
|
hook_event_name: 'PostToolBatch';
|
|
@@ -246,6 +249,18 @@ export type HookInputByEvent = {
|
|
|
246
249
|
export interface BaseHookOutput {
|
|
247
250
|
/** Context string to inject into the conversation. Accumulated across hooks. */
|
|
248
251
|
additionalContext?: string;
|
|
252
|
+
/**
|
|
253
|
+
* Messages to inject into graph state, one `HumanMessage` per entry
|
|
254
|
+
* (converted via `ToolNode.convertInjectedMessages`, which preserves
|
|
255
|
+
* `role`/`source`/`isMeta` in `additional_kwargs`). Unlike
|
|
256
|
+
* `additionalContext` — which is consolidated across hooks into a single
|
|
257
|
+
* system-flavored message — each entry keeps its own identity and role,
|
|
258
|
+
* making this the channel for injecting verbatim user speech (e.g. a
|
|
259
|
+
* mid-run steering message). Accumulated across hooks in registration
|
|
260
|
+
* order. Currently consumed only at the `PostToolBatch` dispatch site;
|
|
261
|
+
* other events ignore the field.
|
|
262
|
+
*/
|
|
263
|
+
injectedMessages?: InjectedMessage[];
|
|
249
264
|
/** True to prevent the next model turn. Any hook can set this. */
|
|
250
265
|
preventContinuation?: boolean;
|
|
251
266
|
/** Reason reported alongside `preventContinuation`. */
|
|
@@ -508,6 +523,8 @@ export interface AggregatedHookResult {
|
|
|
508
523
|
updatedOutput?: unknown;
|
|
509
524
|
/** Accumulated `additionalContext` strings from every hook, in order. */
|
|
510
525
|
additionalContexts: string[];
|
|
526
|
+
/** Accumulated `injectedMessages` from every hook, in registration order. */
|
|
527
|
+
injectedMessages: InjectedMessage[];
|
|
511
528
|
/** True if any hook returned `preventContinuation`. */
|
|
512
529
|
preventContinuation?: boolean;
|
|
513
530
|
/**
|
package/src/langfuse.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { CallbackHandler } from '@langfuse/langchain';
|
|
2
2
|
import { context as otelContext } from '@opentelemetry/api';
|
|
3
|
+
import { AIMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
3
4
|
import {
|
|
4
5
|
getLangfuseTracerProvider,
|
|
5
6
|
propagateAttributes,
|
|
6
7
|
} from '@langfuse/tracing';
|
|
8
|
+
import type {
|
|
9
|
+
AIMessageChunkFields,
|
|
10
|
+
AIMessageFields,
|
|
11
|
+
UsageMetadata,
|
|
12
|
+
} from '@langchain/core/messages';
|
|
13
|
+
import type {
|
|
14
|
+
ChatGeneration,
|
|
15
|
+
Generation,
|
|
16
|
+
LLMResult,
|
|
17
|
+
} from '@langchain/core/outputs';
|
|
7
18
|
import type { PropagateAttributesParams } from '@langfuse/tracing';
|
|
8
19
|
import type * as t from '@/types';
|
|
9
20
|
import {
|
|
@@ -43,6 +54,116 @@ type FlushableTracerProvider = {
|
|
|
43
54
|
forceFlush?: () => Promise<void> | void;
|
|
44
55
|
};
|
|
45
56
|
|
|
57
|
+
type BedrockResponseUsage = {
|
|
58
|
+
inputTokens?: number;
|
|
59
|
+
cacheReadInputTokens?: number;
|
|
60
|
+
cacheWriteInputTokens?: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type BedrockResponseMetadata = {
|
|
64
|
+
metadata?: {
|
|
65
|
+
usage?: BedrockResponseUsage;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function getLangfuseBedrockUsage(
|
|
70
|
+
message: AIMessage | AIMessageChunk
|
|
71
|
+
): UsageMetadata | undefined {
|
|
72
|
+
const usageMetadata = message.usage_metadata;
|
|
73
|
+
const bedrockUsage = (message.response_metadata as BedrockResponseMetadata)
|
|
74
|
+
.metadata?.usage;
|
|
75
|
+
if (
|
|
76
|
+
usageMetadata == null ||
|
|
77
|
+
bedrockUsage == null ||
|
|
78
|
+
usageMetadata.input_tokens !== bedrockUsage.inputTokens
|
|
79
|
+
) {
|
|
80
|
+
return usageMetadata;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const cacheRead = bedrockUsage.cacheReadInputTokens ?? 0;
|
|
84
|
+
const cacheCreation = bedrockUsage.cacheWriteInputTokens ?? 0;
|
|
85
|
+
if (cacheRead === 0 && cacheCreation === 0) {
|
|
86
|
+
return usageMetadata;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
...usageMetadata,
|
|
91
|
+
input_tokens: usageMetadata.input_tokens + cacheRead + cacheCreation,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function cloneMessageWithUsage(
|
|
96
|
+
message: AIMessage | AIMessageChunk,
|
|
97
|
+
usageMetadata: UsageMetadata
|
|
98
|
+
): AIMessage | AIMessageChunk {
|
|
99
|
+
const fields: AIMessageFields = {
|
|
100
|
+
content: message.content,
|
|
101
|
+
additional_kwargs: message.additional_kwargs,
|
|
102
|
+
response_metadata: message.response_metadata,
|
|
103
|
+
id: message.id,
|
|
104
|
+
name: message.name,
|
|
105
|
+
tool_calls: message.tool_calls,
|
|
106
|
+
invalid_tool_calls: message.invalid_tool_calls,
|
|
107
|
+
usage_metadata: usageMetadata,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (message instanceof AIMessageChunk) {
|
|
111
|
+
const chunkFields: AIMessageChunkFields = {
|
|
112
|
+
...fields,
|
|
113
|
+
tool_call_chunks: message.tool_call_chunks,
|
|
114
|
+
};
|
|
115
|
+
return new AIMessageChunk(chunkFields);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return new AIMessage(fields);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeGenerationForLangfuse(generation: Generation): Generation {
|
|
122
|
+
if (!('message' in generation)) {
|
|
123
|
+
return generation;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const message = (generation as ChatGeneration).message;
|
|
127
|
+
if (!(message instanceof AIMessage || message instanceof AIMessageChunk)) {
|
|
128
|
+
return generation;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const usageMetadata = getLangfuseBedrockUsage(message);
|
|
132
|
+
if (usageMetadata == null || usageMetadata === message.usage_metadata) {
|
|
133
|
+
return generation;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const chatGeneration: ChatGeneration = {
|
|
137
|
+
...(generation as ChatGeneration),
|
|
138
|
+
message: cloneMessageWithUsage(message, usageMetadata),
|
|
139
|
+
};
|
|
140
|
+
return chatGeneration;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeBedrockUsageForLangfuse(output: LLMResult): LLMResult {
|
|
144
|
+
if (output.generations.length === 0) {
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const listIndex = output.generations.length - 1;
|
|
149
|
+
const generationList = output.generations[listIndex];
|
|
150
|
+
if (generationList.length === 0) {
|
|
151
|
+
return output;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const generationIndex = generationList.length - 1;
|
|
155
|
+
const generation = generationList[generationIndex];
|
|
156
|
+
const normalized = normalizeGenerationForLangfuse(generation);
|
|
157
|
+
if (normalized === generation) {
|
|
158
|
+
return output;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const generations = [...output.generations];
|
|
162
|
+
generations[listIndex] = [...generationList];
|
|
163
|
+
generations[listIndex][generationIndex] = normalized;
|
|
164
|
+
return { ...output, generations };
|
|
165
|
+
}
|
|
166
|
+
|
|
46
167
|
class ScopedLangfuseCallbackHandler extends CallbackHandler {
|
|
47
168
|
private readonly langfuse?: t.LangfuseConfig;
|
|
48
169
|
private readonly traceIdSeed?: string;
|
|
@@ -107,6 +228,18 @@ class ScopedLangfuseCallbackHandler extends CallbackHandler {
|
|
|
107
228
|
return this.withRuntimeContext(() => super.handleLLMStart(...args));
|
|
108
229
|
}
|
|
109
230
|
|
|
231
|
+
override handleLLMEnd(
|
|
232
|
+
output: LLMResult,
|
|
233
|
+
runId: string,
|
|
234
|
+
parentRunId?: string
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
return super.handleLLMEnd(
|
|
237
|
+
normalizeBedrockUsageForLangfuse(output),
|
|
238
|
+
runId,
|
|
239
|
+
parentRunId
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
110
243
|
override handleToolStart(
|
|
111
244
|
...args: Parameters<CallbackHandler['handleToolStart']>
|
|
112
245
|
): ReturnType<CallbackHandler['handleToolStart']> {
|
|
@@ -351,7 +484,7 @@ export function isLangfuseCallbackHandler(value: unknown): boolean {
|
|
|
351
484
|
export async function disposeLangfuseHandler(value: unknown): Promise<void> {
|
|
352
485
|
if (
|
|
353
486
|
value == null ||
|
|
354
|
-
|
|
487
|
+
parseBooleanEnv(process.env[LANGFUSE_FORCE_FLUSH_ON_DISPOSE]) !== true
|
|
355
488
|
) {
|
|
356
489
|
return;
|
|
357
490
|
}
|