@librechat/agents 3.1.97 → 3.1.99

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.
Files changed (49) hide show
  1. package/dist/cjs/graphs/Graph.cjs +6 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/langfuseToolOutputTracing.cjs +16 -5
  4. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +10 -0
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/llm/bedrock/toolCache.cjs +125 -0
  8. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -0
  9. package/dist/cjs/messages/cache.cjs +17 -9
  10. package/dist/cjs/messages/cache.cjs.map +1 -1
  11. package/dist/cjs/messages/prune.cjs +45 -8
  12. package/dist/cjs/messages/prune.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +6 -1
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/esm/graphs/Graph.mjs +6 -0
  16. package/dist/esm/graphs/Graph.mjs.map +1 -1
  17. package/dist/esm/langfuseToolOutputTracing.mjs +16 -5
  18. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  19. package/dist/esm/llm/bedrock/index.mjs +10 -0
  20. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  21. package/dist/esm/llm/bedrock/toolCache.mjs +122 -0
  22. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -0
  23. package/dist/esm/messages/cache.mjs +17 -9
  24. package/dist/esm/messages/cache.mjs.map +1 -1
  25. package/dist/esm/messages/prune.mjs +45 -8
  26. package/dist/esm/messages/prune.mjs.map +1 -1
  27. package/dist/esm/tools/ToolNode.mjs +6 -1
  28. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  29. package/dist/types/llm/bedrock/index.d.ts +16 -0
  30. package/dist/types/llm/bedrock/toolCache.d.ts +4 -0
  31. package/dist/types/messages/cache.d.ts +2 -2
  32. package/dist/types/types/llm.d.ts +2 -2
  33. package/package.json +1 -1
  34. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +332 -0
  35. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +504 -0
  36. package/src/graphs/Graph.ts +14 -0
  37. package/src/langfuseToolOutputTracing.ts +26 -7
  38. package/src/llm/bedrock/index.ts +32 -1
  39. package/src/llm/bedrock/llm.spec.ts +154 -1
  40. package/src/llm/bedrock/toolCache.test.ts +131 -0
  41. package/src/llm/bedrock/toolCache.ts +191 -0
  42. package/src/messages/cache.test.ts +97 -38
  43. package/src/messages/cache.ts +18 -10
  44. package/src/messages/prune.ts +55 -17
  45. package/src/specs/langfuse-tool-output-tracing.test.ts +28 -0
  46. package/src/specs/prune.test.ts +193 -0
  47. package/src/tools/ToolNode.ts +7 -1
  48. package/src/tools/__tests__/ToolNode.langfuse.test.ts +6 -0
  49. package/src/types/llm.ts +2 -2
@@ -18,14 +18,16 @@ import {
18
18
  ConverseCommand,
19
19
  ConverseStreamCommand,
20
20
  } from '@aws-sdk/client-bedrock-runtime';
21
- import type { ConverseResponse } from '@aws-sdk/client-bedrock-runtime';
21
+ import type { ConverseResponse, Tool } from '@aws-sdk/client-bedrock-runtime';
22
22
  import {
23
23
  convertConverseMessageToLangChainMessage,
24
24
  handleConverseStreamMetadata,
25
25
  convertToConverseMessages,
26
26
  } from './utils';
27
+ import type { GraphTools } from '@/types';
27
28
  import { toLangChainContent } from '@/messages/langchain';
28
29
  import { CustomChatBedrockConverse, ServiceTierType } from './index';
30
+ import { partitionAndMarkBedrockToolCache } from './toolCache';
29
31
 
30
32
  jest.setTimeout(120000);
31
33
 
@@ -85,6 +87,17 @@ function humanMessageWithContent(
85
87
  return new HumanMessage({ content: toLangChainContent(content) });
86
88
  }
87
89
 
90
+ function getBedrockToolName(entry: Tool): string {
91
+ if ('cachePoint' in entry) {
92
+ return 'cachePoint';
93
+ }
94
+ return entry.toolSpec?.name ?? 'missing';
95
+ }
96
+
97
+ function getBedrockToolNames(tools: Tool[] | undefined): string[] {
98
+ return (tools ?? []).map(getBedrockToolName);
99
+ }
100
+
88
101
  describe('CustomChatBedrockConverse', () => {
89
102
  describe('applicationInferenceProfile parameter', () => {
90
103
  test('should initialize applicationInferenceProfile from constructor', () => {
@@ -240,6 +253,51 @@ describe('CustomChatBedrockConverse', () => {
240
253
  });
241
254
  });
242
255
 
256
+ describe('guardrailConfig configuration', () => {
257
+ test('should pass guardrailConfig through to ConverseStreamCommand', async () => {
258
+ const guardrailConfig = {
259
+ guardrailIdentifier: 'test-guardrail-id',
260
+ guardrailVersion: 'DRAFT',
261
+ trace: 'enabled_full' as const,
262
+ streamProcessingMode: 'sync' as const,
263
+ };
264
+ const mockSend = jest.fn<any>().mockResolvedValue({
265
+ stream: (async function* streamChunks() {
266
+ yield {
267
+ contentBlockDelta: {
268
+ contentBlockIndex: 0,
269
+ delta: { text: 'Guardrail response' },
270
+ },
271
+ };
272
+ })(),
273
+ });
274
+
275
+ const mockClient = {
276
+ send: mockSend,
277
+ } as unknown as BedrockRuntimeClient;
278
+
279
+ const model = new CustomChatBedrockConverse({
280
+ ...baseConstructorArgs,
281
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
282
+ guardrailConfig,
283
+ client: mockClient,
284
+ });
285
+
286
+ let chunks = 0;
287
+ for await (const _chunk of await model.stream([
288
+ new HumanMessage('Hello'),
289
+ ])) {
290
+ chunks += 1;
291
+ }
292
+
293
+ expect(mockSend).toHaveBeenCalledTimes(1);
294
+ expect(chunks).toBe(1);
295
+ const commandArg = mockSend.mock.calls[0][0] as ConverseStreamCommand;
296
+ expect(commandArg).toBeInstanceOf(ConverseStreamCommand);
297
+ expect(commandArg.input.guardrailConfig).toEqual(guardrailConfig);
298
+ });
299
+ });
300
+
243
301
  describe('serviceTier configuration', () => {
244
302
  test('should set serviceTier in constructor', () => {
245
303
  const model = new CustomChatBedrockConverse({
@@ -313,6 +371,101 @@ describe('CustomChatBedrockConverse', () => {
313
371
  });
314
372
  });
315
373
 
374
+ describe('promptCache tool configuration', () => {
375
+ test('adds a Bedrock cache point for directly-bound tools', () => {
376
+ const model = new CustomChatBedrockConverse({
377
+ ...baseConstructorArgs,
378
+ promptCache: true,
379
+ });
380
+
381
+ const params = model.invocationParams({
382
+ tools: [
383
+ {
384
+ type: 'function',
385
+ function: {
386
+ name: 'direct_tool',
387
+ description: 'Direct tool',
388
+ parameters: { type: 'object', properties: {} },
389
+ },
390
+ },
391
+ ],
392
+ });
393
+
394
+ expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
395
+ 'direct_tool',
396
+ 'cachePoint',
397
+ ]);
398
+ });
399
+
400
+ test('adds the Bedrock cache point before deferred tools', () => {
401
+ const model = new CustomChatBedrockConverse({
402
+ ...baseConstructorArgs,
403
+ promptCache: true,
404
+ });
405
+ const tools = partitionAndMarkBedrockToolCache(
406
+ [
407
+ {
408
+ type: 'function',
409
+ function: {
410
+ name: 'static_tool',
411
+ description: 'Static tool',
412
+ parameters: { type: 'object', properties: {} },
413
+ },
414
+ },
415
+ {
416
+ type: 'function',
417
+ function: {
418
+ name: 'deferred_tool',
419
+ description: 'Deferred tool',
420
+ parameters: { type: 'object', properties: {} },
421
+ },
422
+ },
423
+ ] as GraphTools,
424
+ (name) => name === 'deferred_tool'
425
+ );
426
+
427
+ const params = model.invocationParams({ tools });
428
+
429
+ expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
430
+ 'static_tool',
431
+ 'cachePoint',
432
+ 'deferred_tool',
433
+ ]);
434
+ expect(JSON.stringify(params.toolConfig?.tools)).not.toContain(
435
+ '__lc_bedrock_cache_point_after'
436
+ );
437
+ });
438
+
439
+ test('does not fall back to caching when Graph marks all tools deferred', () => {
440
+ const model = new CustomChatBedrockConverse({
441
+ ...baseConstructorArgs,
442
+ promptCache: true,
443
+ });
444
+ const tools = partitionAndMarkBedrockToolCache(
445
+ [
446
+ {
447
+ type: 'function',
448
+ function: {
449
+ name: 'deferred_tool',
450
+ description: 'Deferred tool',
451
+ parameters: { type: 'object', properties: {} },
452
+ },
453
+ },
454
+ ] as GraphTools,
455
+ () => true
456
+ );
457
+
458
+ const params = model.invocationParams({ tools });
459
+
460
+ expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
461
+ 'deferred_tool',
462
+ ]);
463
+ expect(JSON.stringify(params.toolConfig?.tools)).not.toContain(
464
+ '__lc_bedrock_skip_tool_cache'
465
+ );
466
+ });
467
+ });
468
+
316
469
  describe('contentBlockIndex cleanup', () => {
317
470
  // Access private methods for testing via any cast
318
471
  function getModelWithCleanMethods() {
@@ -0,0 +1,131 @@
1
+ import { tool } from '@langchain/core/tools';
2
+ import type { Tool } from '@aws-sdk/client-bedrock-runtime';
3
+ import type { GraphTools } from '@/types';
4
+ import {
5
+ insertBedrockToolCachePoint,
6
+ partitionAndMarkBedrockToolCache,
7
+ } from './toolCache';
8
+
9
+ type OpenAITool = {
10
+ type: 'function';
11
+ function: {
12
+ name: string;
13
+ description?: string;
14
+ parameters?: object;
15
+ };
16
+ };
17
+
18
+ function createOpenAITool(name: string): OpenAITool {
19
+ return {
20
+ type: 'function',
21
+ function: {
22
+ name,
23
+ description: `${name} description`,
24
+ parameters: {
25
+ type: 'object',
26
+ properties: {},
27
+ },
28
+ },
29
+ };
30
+ }
31
+
32
+ function toolName(entry: Tool): string {
33
+ if ('cachePoint' in entry) {
34
+ return 'cachePoint';
35
+ }
36
+ return entry.toolSpec?.name ?? 'missing';
37
+ }
38
+
39
+ function toolNames(tools: Tool[] | undefined): string[] {
40
+ return (tools ?? []).map(toolName);
41
+ }
42
+
43
+ describe('partitionAndMarkBedrockToolCache', () => {
44
+ it('inserts the Bedrock cache point after the last static tool', () => {
45
+ const tools = [
46
+ createOpenAITool('static_one'),
47
+ createOpenAITool('static_two'),
48
+ createOpenAITool('dynamic_one'),
49
+ ] as GraphTools;
50
+
51
+ const marked = partitionAndMarkBedrockToolCache(
52
+ tools,
53
+ (name) => name === 'dynamic_one'
54
+ ) as Tool[];
55
+ const result = insertBedrockToolCachePoint({ tools: marked }, false);
56
+
57
+ expect(toolNames(result?.tools)).toEqual([
58
+ 'static_one',
59
+ 'static_two',
60
+ 'cachePoint',
61
+ 'dynamic_one',
62
+ ]);
63
+ expect(JSON.stringify(result?.tools)).not.toContain(
64
+ '__lc_bedrock_cache_point_after'
65
+ );
66
+ });
67
+
68
+ it('converts LangChain tools to Bedrock tool specs before marking', () => {
69
+ const staticTool = tool(async () => 'static', {
70
+ name: 'static_tool',
71
+ description: 'Static tool',
72
+ schema: {
73
+ type: 'object',
74
+ properties: {},
75
+ },
76
+ });
77
+ const dynamicTool = tool(async () => 'dynamic', {
78
+ name: 'dynamic_tool',
79
+ description: 'Dynamic tool',
80
+ schema: {
81
+ type: 'object',
82
+ properties: {},
83
+ },
84
+ });
85
+
86
+ const marked = partitionAndMarkBedrockToolCache(
87
+ [dynamicTool, staticTool] as GraphTools,
88
+ (name) => name === 'dynamic_tool'
89
+ ) as Tool[];
90
+ const result = insertBedrockToolCachePoint({ tools: marked }, false);
91
+
92
+ expect(toolNames(result?.tools)).toEqual([
93
+ 'static_tool',
94
+ 'cachePoint',
95
+ 'dynamic_tool',
96
+ ]);
97
+ });
98
+
99
+ it('does not add a cache point when every tool is deferred', () => {
100
+ const tools = [createOpenAITool('dynamic_one')] as GraphTools;
101
+ const marked = partitionAndMarkBedrockToolCache(
102
+ tools,
103
+ () => true
104
+ ) as Tool[];
105
+ const result = insertBedrockToolCachePoint({ tools: marked }, false);
106
+
107
+ expect(toolNames(result?.tools)).toEqual(['dynamic_one']);
108
+ expect(JSON.stringify(result?.tools)).not.toContain(
109
+ '__lc_bedrock_skip_tool_cache'
110
+ );
111
+ });
112
+
113
+ it('can fall back to caching all directly-bound tools', () => {
114
+ const result = insertBedrockToolCachePoint(
115
+ {
116
+ tools: [
117
+ {
118
+ toolSpec: {
119
+ name: 'direct_tool',
120
+ description: 'Direct tool',
121
+ inputSchema: { json: { type: 'object', properties: {} } },
122
+ },
123
+ },
124
+ ],
125
+ },
126
+ true
127
+ );
128
+
129
+ expect(toolNames(result?.tools)).toEqual(['direct_tool', 'cachePoint']);
130
+ });
131
+ });
@@ -0,0 +1,191 @@
1
+ import type { Tool, ToolConfiguration } from '@aws-sdk/client-bedrock-runtime';
2
+ import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
3
+ import type { OpenAIClient } from '@langchain/openai';
4
+ import type { DocumentType } from '@smithy/types';
5
+ import type { GraphTools } from '@/types';
6
+ import { _convertToOpenAITool } from '@/llm/openai';
7
+
8
+ const CACHE_POINT: Tool.CachePointMember = {
9
+ cachePoint: { type: 'default' },
10
+ };
11
+
12
+ const BEDROCK_TOOL_CACHE_MARKER = '__lc_bedrock_cache_point_after';
13
+ const BEDROCK_TOOL_CACHE_DISABLED_MARKER = '__lc_bedrock_skip_tool_cache';
14
+
15
+ type BedrockToolWithCacheMarker = Tool & {
16
+ [BEDROCK_TOOL_CACHE_MARKER]?: true;
17
+ [BEDROCK_TOOL_CACHE_DISABLED_MARKER]?: true;
18
+ };
19
+
20
+ type OpenAIFunctionTool = Extract<
21
+ OpenAIClient.ChatCompletionTool,
22
+ { type: 'function' }
23
+ >;
24
+
25
+ type ToolNameCandidate = {
26
+ name?: unknown;
27
+ function?: {
28
+ name?: unknown;
29
+ };
30
+ toolSpec?: {
31
+ name?: unknown;
32
+ };
33
+ };
34
+
35
+ function isBedrockToolSpec(tool: unknown): tool is Tool.ToolSpecMember {
36
+ return (
37
+ typeof tool === 'object' &&
38
+ tool != null &&
39
+ 'toolSpec' in tool &&
40
+ typeof (tool as ToolNameCandidate).toolSpec?.name === 'string'
41
+ );
42
+ }
43
+
44
+ function isBedrockCachePoint(tool: Tool): tool is Tool.CachePointMember {
45
+ return 'cachePoint' in tool && tool.cachePoint != null;
46
+ }
47
+
48
+ function getToolName(tool: unknown): string | undefined {
49
+ const candidate = tool as ToolNameCandidate;
50
+ if (typeof candidate.toolSpec?.name === 'string') {
51
+ return candidate.toolSpec.name;
52
+ }
53
+ if (typeof candidate.name === 'string') {
54
+ return candidate.name;
55
+ }
56
+ if (typeof candidate.function?.name === 'string') {
57
+ return candidate.function.name;
58
+ }
59
+ return undefined;
60
+ }
61
+
62
+ function openAIToBedrockTool(tool: OpenAIFunctionTool): Tool.ToolSpecMember {
63
+ return {
64
+ toolSpec: {
65
+ name: tool.function.name,
66
+ description: tool.function.description,
67
+ inputSchema: { json: tool.function.parameters as DocumentType },
68
+ },
69
+ };
70
+ }
71
+
72
+ function toBedrockTool(tool: unknown): BedrockToolWithCacheMarker {
73
+ if (isBedrockToolSpec(tool)) {
74
+ return { ...tool };
75
+ }
76
+
77
+ return openAIToBedrockTool(
78
+ _convertToOpenAITool(tool as BindToolsInput) as OpenAIFunctionTool
79
+ ) as BedrockToolWithCacheMarker;
80
+ }
81
+
82
+ function markCachePointAfter(
83
+ tool: BedrockToolWithCacheMarker
84
+ ): BedrockToolWithCacheMarker {
85
+ return {
86
+ ...tool,
87
+ [BEDROCK_TOOL_CACHE_MARKER]: true,
88
+ };
89
+ }
90
+
91
+ function markToolCacheDisabled(
92
+ tool: BedrockToolWithCacheMarker
93
+ ): BedrockToolWithCacheMarker {
94
+ return {
95
+ ...tool,
96
+ [BEDROCK_TOOL_CACHE_DISABLED_MARKER]: true,
97
+ };
98
+ }
99
+
100
+ function stripCachePointMarker(tool: BedrockToolWithCacheMarker): Tool {
101
+ const {
102
+ [BEDROCK_TOOL_CACHE_MARKER]: _marker,
103
+ [BEDROCK_TOOL_CACHE_DISABLED_MARKER]: _disabled,
104
+ ...rest
105
+ } = tool;
106
+ return rest as Tool;
107
+ }
108
+
109
+ export function partitionAndMarkBedrockToolCache(
110
+ tools: GraphTools | undefined,
111
+ isDeferred: (toolName: string) => boolean
112
+ ): GraphTools | undefined {
113
+ if (tools == null || tools.length === 0) {
114
+ return tools;
115
+ }
116
+
117
+ const staticTools: BedrockToolWithCacheMarker[] = [];
118
+ const deferredTools: BedrockToolWithCacheMarker[] = [];
119
+
120
+ for (const tool of tools as readonly unknown[]) {
121
+ const converted = toBedrockTool(tool);
122
+ const name = getToolName(converted) ?? getToolName(tool);
123
+
124
+ if (name != null && isDeferred(name)) {
125
+ deferredTools.push(converted);
126
+ continue;
127
+ }
128
+
129
+ staticTools.push(converted);
130
+ }
131
+
132
+ if (staticTools.length === 0) {
133
+ deferredTools[0] = markToolCacheDisabled(deferredTools[0]);
134
+ return [...deferredTools] as GraphTools;
135
+ }
136
+
137
+ staticTools[staticTools.length - 1] = markCachePointAfter(
138
+ staticTools[staticTools.length - 1]
139
+ );
140
+
141
+ return [...staticTools, ...deferredTools] as GraphTools;
142
+ }
143
+
144
+ export function insertBedrockToolCachePoint(
145
+ toolConfig: ToolConfiguration | undefined,
146
+ fallbackToEnd: boolean
147
+ ): ToolConfiguration | undefined {
148
+ const tools = toolConfig?.tools as BedrockToolWithCacheMarker[] | undefined;
149
+ if (tools == null || tools.length === 0) {
150
+ return toolConfig;
151
+ }
152
+
153
+ let markerIndex = -1;
154
+ let hasCachePoint = false;
155
+ let hasDisabledMarker = false;
156
+ const cleanedTools: Tool[] = [];
157
+
158
+ for (let i = 0; i < tools.length; i++) {
159
+ const tool = tools[i];
160
+ if (isBedrockCachePoint(tool)) {
161
+ hasCachePoint = true;
162
+ cleanedTools.push(tool);
163
+ continue;
164
+ }
165
+ if (tool[BEDROCK_TOOL_CACHE_MARKER] === true) {
166
+ markerIndex = cleanedTools.length;
167
+ }
168
+ if (tool[BEDROCK_TOOL_CACHE_DISABLED_MARKER] === true) {
169
+ hasDisabledMarker = true;
170
+ }
171
+ cleanedTools.push(stripCachePointMarker(tool));
172
+ }
173
+
174
+ if (hasCachePoint || hasDisabledMarker) {
175
+ return { ...toolConfig, tools: cleanedTools };
176
+ }
177
+
178
+ const insertionIndex = markerIndex >= 0 ? markerIndex : tools.length - 1;
179
+ if (markerIndex < 0 && !fallbackToEnd) {
180
+ return { ...toolConfig, tools: cleanedTools };
181
+ }
182
+
183
+ return {
184
+ ...toolConfig,
185
+ tools: [
186
+ ...cleanedTools.slice(0, insertionIndex + 1),
187
+ CACHE_POINT,
188
+ ...cleanedTools.slice(insertionIndex + 1),
189
+ ],
190
+ };
191
+ }