@librechat/agents 3.1.95 → 3.1.97
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 +54 -21
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +120 -9
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +30 -226
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +465 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -0
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/run.cjs +142 -69
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +29 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +20 -8
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +10 -6
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +56 -23
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +118 -9
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +28 -224
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +457 -0
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -0
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/run.mjs +144 -71
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +29 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +20 -8
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +10 -6
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +5 -1
- package/dist/types/instrumentation.d.ts +5 -1
- package/dist/types/langfuse.d.ts +6 -28
- package/dist/types/langfuseToolOutputTracing.d.ts +20 -0
- package/dist/types/run.d.ts +5 -1
- package/dist/types/tools/BashProgrammaticToolCalling.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +4 -1
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +2 -0
- package/dist/types/types/graph.d.ts +30 -0
- package/dist/types/types/run.d.ts +6 -0
- package/dist/types/types/tools.d.ts +7 -0
- package/package.json +2 -1
- package/src/graphs/Graph.ts +90 -34
- package/src/instrumentation.ts +172 -11
- package/src/langfuse.ts +59 -324
- package/src/langfuseToolOutputTracing.ts +683 -0
- package/src/run.ts +190 -87
- package/src/specs/langfuse-callbacks.test.ts +178 -1
- package/src/specs/langfuse-config.test.ts +112 -76
- package/src/specs/langfuse-instrumentation.test.ts +283 -0
- package/src/specs/langfuse-metadata.test.ts +54 -1
- package/src/specs/langfuse-tool-output-tracing.test.ts +588 -0
- package/src/tools/BashProgrammaticToolCalling.ts +39 -5
- package/src/tools/ToolNode.ts +28 -7
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +54 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +72 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +32 -0
- package/src/tools/__tests__/ToolNode.langfuse.test.ts +41 -0
- package/src/tools/subagent/SubagentExecutor.ts +11 -6
- package/src/types/graph.ts +32 -0
- package/src/types/run.ts +6 -0
- package/src/types/tools.ts +7 -0
package/src/tools/ToolNode.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
42
42
|
import { executeHooks } from '@/hooks';
|
|
43
43
|
import { toLangChainContent } from '@/messages/langchain';
|
|
44
|
+
import { withLangfuseToolOutputTracingConfig } from '@/langfuseToolOutputTracing';
|
|
44
45
|
import { Constants, GraphEvents, CODE_EXECUTION_TOOLS } from '@/common';
|
|
45
46
|
import {
|
|
46
47
|
buildReferenceKey,
|
|
@@ -394,6 +395,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
394
395
|
private loadRuntimeTools?: t.ToolRefGenerator;
|
|
395
396
|
handleToolErrors = true;
|
|
396
397
|
trace = false;
|
|
398
|
+
private runLangfuse?: t.LangfuseConfig;
|
|
399
|
+
private agentLangfuse?: t.LangfuseConfig;
|
|
397
400
|
toolCallStepIds?: Map<string, string>;
|
|
398
401
|
errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
|
|
399
402
|
private toolUsageCount: Map<string, number>;
|
|
@@ -473,6 +476,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
473
476
|
toolMap,
|
|
474
477
|
name,
|
|
475
478
|
tags,
|
|
479
|
+
trace,
|
|
480
|
+
runLangfuse,
|
|
481
|
+
agentLangfuse,
|
|
476
482
|
errorHandler,
|
|
477
483
|
toolCallStepIds,
|
|
478
484
|
handleToolErrors,
|
|
@@ -495,6 +501,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
495
501
|
fileCheckpointer,
|
|
496
502
|
}: t.ToolNodeConstructorParams) {
|
|
497
503
|
super({ name, tags, func: (input, config) => this.run(input, config) });
|
|
504
|
+
this.trace = trace ?? this.trace;
|
|
505
|
+
this.runLangfuse = runLangfuse;
|
|
506
|
+
this.agentLangfuse = agentLangfuse;
|
|
498
507
|
this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
499
508
|
this.toolCallStepIds = toolCallStepIds;
|
|
500
509
|
this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
|
|
@@ -545,6 +554,19 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
545
554
|
}
|
|
546
555
|
}
|
|
547
556
|
|
|
557
|
+
override async invoke(
|
|
558
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
559
|
+
input: any,
|
|
560
|
+
options?: Partial<RunnableConfig>
|
|
561
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
562
|
+
): Promise<any> {
|
|
563
|
+
return withLangfuseToolOutputTracingConfig(
|
|
564
|
+
this.runLangfuse,
|
|
565
|
+
() => super.invoke(input, options),
|
|
566
|
+
this.agentLangfuse
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
548
570
|
/**
|
|
549
571
|
* Returns the run-scoped tool output registry, or `undefined` when
|
|
550
572
|
* the feature is disabled.
|
|
@@ -2140,13 +2162,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2140
2162
|
|
|
2141
2163
|
/**
|
|
2142
2164
|
* `interrupt()` reads the current `RunnableConfig` from
|
|
2143
|
-
* AsyncLocalStorage
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
* `
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
2149
|
-
* `interrupt()` needs to suspend and resume.
|
|
2165
|
+
* AsyncLocalStorage. ToolNode usually runs with tracing disabled
|
|
2166
|
+
* (unless Langfuse explicitly enables it), so the upstream
|
|
2167
|
+
* `runWithConfig` frame may not exist. Re-anchor here using the
|
|
2168
|
+
* node's own `config` — Pregel hands us a config that already
|
|
2169
|
+
* carries every checkpoint/scratchpad key `interrupt()` needs to
|
|
2170
|
+
* suspend and resume.
|
|
2150
2171
|
*/
|
|
2151
2172
|
const resumeValue = AsyncLocalStorageProviderSingleton.runWithConfig(
|
|
2152
2173
|
config,
|
|
@@ -34,6 +34,8 @@ type FetchMock = jest.MockedFunction<
|
|
|
34
34
|
|
|
35
35
|
type CodeApiRequestBody = {
|
|
36
36
|
timeout?: number;
|
|
37
|
+
continuation_token?: string;
|
|
38
|
+
tool_results?: t.PTCToolResult[];
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
type TimeoutSchemaForTest = {
|
|
@@ -387,6 +389,58 @@ describe('CodeAPI auth header injection', () => {
|
|
|
387
389
|
);
|
|
388
390
|
});
|
|
389
391
|
|
|
392
|
+
it('normalizes JSON-looking bash programmatic tool results before continuation', async () => {
|
|
393
|
+
fetchMock
|
|
394
|
+
.mockResolvedValueOnce(
|
|
395
|
+
jsonResponse({
|
|
396
|
+
status: 'tool_call_required',
|
|
397
|
+
continuation_token: 'continue_123',
|
|
398
|
+
tool_calls: [{ id: 'call_001', name: 'lookup_user', input: {} }],
|
|
399
|
+
})
|
|
400
|
+
)
|
|
401
|
+
.mockResolvedValueOnce(completedResponse('done'));
|
|
402
|
+
const tool = createBashProgrammaticToolCallingTool();
|
|
403
|
+
const customToolMap = new Map([
|
|
404
|
+
[
|
|
405
|
+
'lookup_user',
|
|
406
|
+
{
|
|
407
|
+
name: 'lookup_user',
|
|
408
|
+
invoke: jest.fn(async () =>
|
|
409
|
+
JSON.stringify({
|
|
410
|
+
result: {
|
|
411
|
+
data: [{ id: 'user_123', name: 'Ada' }],
|
|
412
|
+
},
|
|
413
|
+
})
|
|
414
|
+
),
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
]) as unknown as t.ToolMap;
|
|
418
|
+
|
|
419
|
+
await tool.invoke(
|
|
420
|
+
{ code: 'lookup_user "{}"' },
|
|
421
|
+
{
|
|
422
|
+
toolCall: {
|
|
423
|
+
name: 'bash_programmatic_code_execution',
|
|
424
|
+
args: {},
|
|
425
|
+
toolMap: customToolMap,
|
|
426
|
+
toolDefs,
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
expect(requestBodyAt(1).tool_results).toEqual([
|
|
432
|
+
{
|
|
433
|
+
call_id: 'call_001',
|
|
434
|
+
result: {
|
|
435
|
+
result: {
|
|
436
|
+
data: [{ id: 'user_123', name: 'Ada' }],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
is_error: false,
|
|
440
|
+
},
|
|
441
|
+
]);
|
|
442
|
+
});
|
|
443
|
+
|
|
390
444
|
it('reminds that failed bash programmatic executions do not register new files', async () => {
|
|
391
445
|
fetchMock.mockResolvedValueOnce(
|
|
392
446
|
jsonResponse({
|
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
normalizeToPythonIdentifier,
|
|
17
17
|
unwrapToolResponse,
|
|
18
18
|
} from '../ProgrammaticToolCalling';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createBashProgrammaticToolCallingSchema,
|
|
21
|
+
normalizeBashToolResultsForReplay,
|
|
22
|
+
} from '../BashProgrammaticToolCalling';
|
|
20
23
|
import {
|
|
21
24
|
createProgrammaticToolRegistry,
|
|
22
25
|
createGetTeamMembersTool,
|
|
@@ -40,9 +43,11 @@ describe('ProgrammaticToolCalling', () => {
|
|
|
40
43
|
const schema = createBashProgrammaticToolCallingSchema();
|
|
41
44
|
const description = schema.properties.code.description;
|
|
42
45
|
|
|
43
|
-
expect(description).toContain(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
expect(description).toContain(
|
|
47
|
+
'Tool stdout is normalized to one compact JSON value'
|
|
48
|
+
);
|
|
49
|
+
expect(description).toContain('use fromjson? // .');
|
|
50
|
+
expect(description).toContain('only for JSON-string fields');
|
|
46
51
|
expect(description).toContain('raw=$(tool');
|
|
47
52
|
expect(description).toContain('direct tool > file may be empty');
|
|
48
53
|
expect(description).toContain('/mnt/data/sf.json');
|
|
@@ -53,6 +58,69 @@ describe('ProgrammaticToolCalling', () => {
|
|
|
53
58
|
});
|
|
54
59
|
});
|
|
55
60
|
|
|
61
|
+
describe('normalizeBashToolResultsForReplay', () => {
|
|
62
|
+
it('decodes JSON-looking string results before bash replay', () => {
|
|
63
|
+
const results = normalizeBashToolResultsForReplay([
|
|
64
|
+
{
|
|
65
|
+
call_id: 'call_001',
|
|
66
|
+
result: JSON.stringify({
|
|
67
|
+
result: {
|
|
68
|
+
data: [{ station_id: 'USW00094725', winter_days: 91 }],
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
is_error: false,
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
expect(results[0].result).toEqual({
|
|
76
|
+
result: {
|
|
77
|
+
data: [{ station_id: 'USW00094725', winter_days: 91 }],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('decodes JSON-looking array string results before bash replay', () => {
|
|
83
|
+
const results = normalizeBashToolResultsForReplay([
|
|
84
|
+
{
|
|
85
|
+
call_id: 'call_001',
|
|
86
|
+
result: '[{"name":"tempAvg"},{"name":"snowDepth"}]',
|
|
87
|
+
is_error: false,
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
expect(results[0].result).toEqual([
|
|
92
|
+
{ name: 'tempAvg' },
|
|
93
|
+
{ name: 'snowDepth' },
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('preserves plain strings, invalid JSON strings, and error results', () => {
|
|
98
|
+
const errorJson = '{"message":"tool failed"}';
|
|
99
|
+
const results = normalizeBashToolResultsForReplay([
|
|
100
|
+
{
|
|
101
|
+
call_id: 'call_001',
|
|
102
|
+
result: 'plain text',
|
|
103
|
+
is_error: false,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
call_id: 'call_002',
|
|
107
|
+
result: '{not json}',
|
|
108
|
+
is_error: false,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
call_id: 'call_003',
|
|
112
|
+
result: errorJson,
|
|
113
|
+
is_error: true,
|
|
114
|
+
error_message: 'tool failed',
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
expect(results[0].result).toBe('plain text');
|
|
119
|
+
expect(results[1].result).toBe('{not json}');
|
|
120
|
+
expect(results[2].result).toBe(errorJson);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
56
124
|
describe('executeTools', () => {
|
|
57
125
|
let toolMap: t.ToolMap;
|
|
58
126
|
|
|
@@ -424,6 +424,38 @@ describe('SubagentExecutor', () => {
|
|
|
424
424
|
expect(clearHeavyState).toHaveBeenCalled();
|
|
425
425
|
});
|
|
426
426
|
|
|
427
|
+
it('passes parent Langfuse config to the child graph', async () => {
|
|
428
|
+
const langfuse = {
|
|
429
|
+
enabled: true,
|
|
430
|
+
publicKey: 'pk-run',
|
|
431
|
+
secretKey: 'sk-run',
|
|
432
|
+
baseUrl: 'https://langfuse.test',
|
|
433
|
+
toolOutputTracing: { enabled: false },
|
|
434
|
+
};
|
|
435
|
+
let observedLangfuse: typeof langfuse | undefined;
|
|
436
|
+
const executor = createExecutor({
|
|
437
|
+
langfuse,
|
|
438
|
+
createChildGraph: (input): StandardGraph => {
|
|
439
|
+
observedLangfuse = input.langfuse as typeof langfuse;
|
|
440
|
+
return {
|
|
441
|
+
createWorkflow: (): { invoke: jest.Mock } => ({
|
|
442
|
+
invoke: jest.fn().mockResolvedValue({
|
|
443
|
+
messages: [new AIMessage('child done')],
|
|
444
|
+
}),
|
|
445
|
+
}),
|
|
446
|
+
clearHeavyState: jest.fn(),
|
|
447
|
+
} as unknown as StandardGraph;
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await executor.execute({
|
|
452
|
+
description: 'Research this topic',
|
|
453
|
+
subagentType: 'researcher',
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
expect(observedLangfuse).toBe(langfuse);
|
|
457
|
+
});
|
|
458
|
+
|
|
427
459
|
it('returns error message when child graph throws', async () => {
|
|
428
460
|
const executor = createExecutor({
|
|
429
461
|
createChildGraph: makeThrowingGraphFactory(
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const mockWithLangfuseToolOutputTracingConfig = jest.fn(
|
|
2
|
+
(_runLangfuse: unknown, action: () => unknown, _agentLangfuse: unknown) =>
|
|
3
|
+
action()
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
jest.mock('@/langfuseToolOutputTracing', () => ({
|
|
7
|
+
...jest.requireActual('@/langfuseToolOutputTracing'),
|
|
8
|
+
withLangfuseToolOutputTracingConfig: mockWithLangfuseToolOutputTracingConfig,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { ToolNode } from '../ToolNode';
|
|
12
|
+
|
|
13
|
+
describe('ToolNode Langfuse redaction context', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockWithLangfuseToolOutputTracingConfig.mockClear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('scopes ToolNode invocation with run and agent Langfuse config', async () => {
|
|
19
|
+
const runLangfuse = {
|
|
20
|
+
toolOutputTracing: { enabled: true },
|
|
21
|
+
};
|
|
22
|
+
const agentLangfuse = {
|
|
23
|
+
toolOutputTracing: { enabled: false },
|
|
24
|
+
};
|
|
25
|
+
const node = new ToolNode({
|
|
26
|
+
tools: [],
|
|
27
|
+
runLangfuse,
|
|
28
|
+
agentLangfuse,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await expect(node.invoke([])).rejects.toThrow(
|
|
32
|
+
'ToolNode only accepts AIMessages'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(mockWithLangfuseToolOutputTracingConfig).toHaveBeenCalledWith(
|
|
36
|
+
runLangfuse,
|
|
37
|
+
expect.any(Function),
|
|
38
|
+
agentLangfuse
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -213,6 +213,7 @@ export type SubagentExecutorOptions = {
|
|
|
213
213
|
hookRegistry?: HookRegistry;
|
|
214
214
|
parentRunId: string;
|
|
215
215
|
parentAgentId?: string;
|
|
216
|
+
langfuse?: StandardGraphInput['langfuse'];
|
|
216
217
|
tokenCounter?: TokenCounter;
|
|
217
218
|
/** Remaining nesting budget. 0 or negative blocks execution. */
|
|
218
219
|
maxDepth?: number;
|
|
@@ -243,6 +244,7 @@ export class SubagentExecutor {
|
|
|
243
244
|
private readonly hookRegistry?: HookRegistry;
|
|
244
245
|
private readonly parentRunId: string;
|
|
245
246
|
private readonly parentAgentId?: string;
|
|
247
|
+
private readonly langfuse?: StandardGraphInput['langfuse'];
|
|
246
248
|
private readonly tokenCounter?: TokenCounter;
|
|
247
249
|
private readonly maxDepth: number;
|
|
248
250
|
private readonly createChildGraph: ChildGraphFactory;
|
|
@@ -256,6 +258,7 @@ export class SubagentExecutor {
|
|
|
256
258
|
this.hookRegistry = options.hookRegistry;
|
|
257
259
|
this.parentRunId = options.parentRunId;
|
|
258
260
|
this.parentAgentId = options.parentAgentId;
|
|
261
|
+
this.langfuse = options.langfuse;
|
|
259
262
|
this.tokenCounter = options.tokenCounter;
|
|
260
263
|
this.maxDepth = options.maxDepth ?? 1;
|
|
261
264
|
this.createChildGraph = options.createChildGraph;
|
|
@@ -352,18 +355,20 @@ export class SubagentExecutor {
|
|
|
352
355
|
runId: childRunId,
|
|
353
356
|
signal: this.parentSignal,
|
|
354
357
|
agents: [childInputs],
|
|
358
|
+
langfuse: this.langfuse,
|
|
355
359
|
tokenCounter: this.tokenCounter,
|
|
356
360
|
});
|
|
357
361
|
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
let forwarding: ForwarderCallback | undefined;
|
|
363
|
+
if (forwardingEnabled) {
|
|
364
|
+
forwarding = this.createForwarderCallback({
|
|
360
365
|
parentRegistry: parentRegistry!,
|
|
361
366
|
subagentType,
|
|
362
367
|
subagentAgentId: childAgentId,
|
|
363
368
|
childRunId,
|
|
364
369
|
parentToolCallId,
|
|
365
|
-
})
|
|
366
|
-
|
|
370
|
+
});
|
|
371
|
+
}
|
|
367
372
|
const forwarder = forwarding?.handler;
|
|
368
373
|
|
|
369
374
|
if (forwarder) {
|
|
@@ -444,7 +449,7 @@ export class SubagentExecutor {
|
|
|
444
449
|
);
|
|
445
450
|
} catch (error) {
|
|
446
451
|
const errorMessage = truncateErrorMessage(error);
|
|
447
|
-
if (
|
|
452
|
+
if (forwarding) {
|
|
448
453
|
await forwarding.drain();
|
|
449
454
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
450
455
|
childRunId,
|
|
@@ -491,7 +496,7 @@ export class SubagentExecutor {
|
|
|
491
496
|
});
|
|
492
497
|
}
|
|
493
498
|
|
|
494
|
-
if (
|
|
499
|
+
if (forwarding) {
|
|
495
500
|
await forwarding.drain();
|
|
496
501
|
await this.emitSubagentUpdate(parentRegistry!, {
|
|
497
502
|
childRunId,
|
package/src/types/graph.ts
CHANGED
|
@@ -295,6 +295,7 @@ export type StandardGraphInput = {
|
|
|
295
295
|
runId?: string;
|
|
296
296
|
signal?: AbortSignal;
|
|
297
297
|
agents: AgentInputs[];
|
|
298
|
+
langfuse?: LangfuseConfig;
|
|
298
299
|
tokenCounter?: TokenCounter;
|
|
299
300
|
indexTokenCountMap?: Record<string, number>;
|
|
300
301
|
calibrationRatio?: number;
|
|
@@ -408,11 +409,42 @@ export interface SubagentUpdateEvent {
|
|
|
408
409
|
timestamp: string;
|
|
409
410
|
}
|
|
410
411
|
|
|
412
|
+
export type LangfuseToolOutputTracingConfig = {
|
|
413
|
+
/**
|
|
414
|
+
* Whether tool outputs should be exported to Langfuse. Defaults to
|
|
415
|
+
* `true`. Set to `false` to keep tool spans and redact their output.
|
|
416
|
+
*/
|
|
417
|
+
enabled?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Optional allowlist of tool names whose outputs should be redacted even
|
|
420
|
+
* when `enabled` is true.
|
|
421
|
+
*/
|
|
422
|
+
redactedToolNames?: string[];
|
|
423
|
+
/**
|
|
424
|
+
* Match strategy for `redactedToolNames`. Defaults to `exact`; use
|
|
425
|
+
* `partial` to redact tools whose names contain a configured value.
|
|
426
|
+
*/
|
|
427
|
+
redactedToolNameMatchMode?: 'exact' | 'partial';
|
|
428
|
+
/** Replacement text used for redacted tool outputs. */
|
|
429
|
+
redactionText?: string;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
export type LangfuseToolNodeTracingConfig = {
|
|
433
|
+
/**
|
|
434
|
+
* Overrides ToolNode callback tracing. ToolNode spans are exported by the
|
|
435
|
+
* env-backed Langfuse callback, so this only enables tracing when that
|
|
436
|
+
* callback is configured.
|
|
437
|
+
*/
|
|
438
|
+
enabled?: boolean;
|
|
439
|
+
};
|
|
440
|
+
|
|
411
441
|
export interface LangfuseConfig {
|
|
412
442
|
enabled?: boolean;
|
|
413
443
|
publicKey?: string;
|
|
414
444
|
secretKey?: string;
|
|
415
445
|
baseUrl?: string;
|
|
446
|
+
toolNodeTracing?: LangfuseToolNodeTracingConfig;
|
|
447
|
+
toolOutputTracing?: LangfuseToolOutputTracingConfig;
|
|
416
448
|
}
|
|
417
449
|
|
|
418
450
|
export interface AgentInputs {
|
package/src/types/run.ts
CHANGED
|
@@ -118,6 +118,12 @@ export type StandardGraphConfig = Omit<
|
|
|
118
118
|
export type RunConfig = {
|
|
119
119
|
runId: string;
|
|
120
120
|
graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Run-scoped Langfuse configuration. Per-agent `AgentInputs.langfuse`
|
|
123
|
+
* takes precedence for agent-specific spans; this object supplies defaults
|
|
124
|
+
* for run-wide tracing controls such as tool-output redaction.
|
|
125
|
+
*/
|
|
126
|
+
langfuse?: g.LangfuseConfig;
|
|
121
127
|
customHandlers?: Record<string, g.EventHandler>;
|
|
122
128
|
/**
|
|
123
129
|
* Pre-constructed hook registry for this run. Hooks fire at lifecycle
|
package/src/types/tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { HookRegistry } from '@/hooks';
|
|
|
6
6
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
7
7
|
import type { MessageContentComplex, ToolErrorData } from './stream';
|
|
8
8
|
import type { HumanInTheLoopConfig } from './hitl';
|
|
9
|
+
import type { LangfuseConfig } from './graph';
|
|
9
10
|
|
|
10
11
|
/** Replacement type for `import type { ToolCall } from '@langchain/core/messages/tool'` in order to have stringified args typed */
|
|
11
12
|
export type CustomToolCall = {
|
|
@@ -69,6 +70,12 @@ export type EagerEventToolCallChunkState = {
|
|
|
69
70
|
export type ToolNodeOptions = {
|
|
70
71
|
name?: string;
|
|
71
72
|
tags?: string[];
|
|
73
|
+
/** Enables LangChain/LangGraph tracing for this ToolNode. Defaults to false. */
|
|
74
|
+
trace?: boolean;
|
|
75
|
+
/** Run-level Langfuse config used to scope ToolNode trace redaction. */
|
|
76
|
+
runLangfuse?: LangfuseConfig;
|
|
77
|
+
/** Agent-level Langfuse config used to scope ToolNode trace redaction. */
|
|
78
|
+
agentLangfuse?: LangfuseConfig;
|
|
72
79
|
handleToolErrors?: boolean;
|
|
73
80
|
loadRuntimeTools?: ToolRefGenerator;
|
|
74
81
|
toolCallStepIds?: Map<string, string>;
|