@librechat/agents 3.1.73 → 3.1.75-dev.0
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/README.md +66 -0
- package/dist/cjs/agents/AgentContext.cjs +146 -57
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +13 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +145 -52
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +25 -15
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +5 -4
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +468 -647
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +1 -448
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +57 -175
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +5 -3
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +39 -4
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +7 -6
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +7 -6
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +26 -0
- package/dist/cjs/messages/langchain.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +7 -6
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +21 -11
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +37 -10
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -11
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +5 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +147 -58
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +13 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +146 -54
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +25 -15
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +5 -4
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +469 -648
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +4 -449
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +57 -175
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +5 -3
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/cache.mjs +39 -4
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +7 -6
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +7 -6
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +23 -0
- package/dist/esm/messages/langchain.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +7 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +22 -12
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +37 -11
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -12
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +5 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +29 -4
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +46 -0
- package/dist/types/llm/anthropic/index.d.ts +22 -9
- package/dist/types/llm/anthropic/types.d.ts +5 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +21 -24
- package/dist/types/llm/openrouter/index.d.ts +11 -9
- package/dist/types/llm/vertexai/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/messages/langchain.d.ts +27 -0
- package/dist/types/tools/CodeExecutor.d.ts +6 -0
- package/dist/types/types/graph.d.ts +26 -38
- package/dist/types/types/llm.d.ts +3 -3
- package/dist/types/types/run.d.ts +2 -0
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/types/tools.d.ts +9 -0
- package/package.json +17 -16
- package/src/agents/AgentContext.ts +189 -71
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +116 -0
- package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +149 -0
- package/src/agents/__tests__/AgentContext.test.ts +333 -2
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +165 -0
- package/src/graphs/Graph.ts +24 -4
- package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
- package/src/llm/anthropic/index.ts +252 -84
- package/src/llm/anthropic/llm.spec.ts +751 -102
- package/src/llm/anthropic/types.ts +9 -1
- package/src/llm/anthropic/utils/message_inputs.ts +43 -20
- package/src/llm/anthropic/utils/message_outputs.ts +119 -101
- package/src/llm/anthropic/utils/server-tool-inputs.test.ts +77 -0
- package/src/llm/bedrock/index.ts +2 -2
- package/src/llm/bedrock/llm.spec.ts +341 -0
- package/src/llm/bedrock/utils/message_inputs.ts +303 -4
- package/src/llm/bedrock/utils/message_outputs.ts +2 -1
- package/src/llm/custom-chat-models.smoke.test.ts +662 -0
- package/src/llm/google/llm.spec.ts +339 -57
- package/src/llm/google/utils/common.ts +53 -48
- package/src/llm/openai/contentBlocks.test.ts +346 -0
- package/src/llm/openai/index.ts +736 -837
- package/src/llm/openai/utils/index.ts +84 -64
- package/src/llm/openrouter/index.ts +124 -247
- package/src/llm/openrouter/reasoning.test.ts +8 -1
- package/src/llm/vertexai/index.ts +11 -5
- package/src/llm/vertexai/llm.spec.ts +28 -1
- package/src/messages/cache.test.ts +106 -4
- package/src/messages/cache.ts +57 -5
- package/src/messages/core.ts +16 -9
- package/src/messages/format.ts +9 -6
- package/src/messages/langchain.ts +39 -0
- package/src/messages/prune.ts +12 -8
- package/src/scripts/caching.ts +2 -3
- package/src/specs/anthropic.simple.test.ts +61 -0
- package/src/specs/summarization.test.ts +58 -61
- package/src/tools/BashExecutor.ts +37 -13
- package/src/tools/CodeExecutor.ts +55 -11
- package/src/tools/ProgrammaticToolCalling.ts +29 -14
- package/src/tools/ToolNode.ts +5 -1
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +60 -0
- package/src/types/graph.ts +35 -88
- package/src/types/llm.ts +3 -3
- package/src/types/run.ts +2 -0
- package/src/types/stream.ts +1 -1
- package/src/types/tools.ts +9 -0
- package/src/utils/llmConfig.ts +1 -6
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// src/agents/__tests__/AgentContext.test.ts
|
|
2
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
2
3
|
import { AgentContext } from '../AgentContext';
|
|
3
4
|
import { Providers } from '@/common';
|
|
5
|
+
import { addBedrockCacheControl } from '@/messages/cache';
|
|
4
6
|
import type * as t from '@/types';
|
|
5
7
|
|
|
6
8
|
describe('AgentContext', () => {
|
|
9
|
+
type TestSystemContentBlock =
|
|
10
|
+
| { type: 'text'; text: string; cache_control?: { type: 'ephemeral' } }
|
|
11
|
+
| { cachePoint: { type: 'default' } };
|
|
12
|
+
|
|
7
13
|
type ContextOptions = {
|
|
8
14
|
agentConfig?: Partial<t.AgentInputs>;
|
|
9
15
|
tokenCounter?: t.TokenCounter;
|
|
@@ -59,14 +65,161 @@ describe('AgentContext', () => {
|
|
|
59
65
|
expect(ctx.systemRunnable).toBeUndefined();
|
|
60
66
|
});
|
|
61
67
|
|
|
62
|
-
it('
|
|
68
|
+
it('keeps additional_instructions after stable instructions', async () => {
|
|
63
69
|
const ctx = createBasicContext({
|
|
64
70
|
agentConfig: {
|
|
65
71
|
instructions: 'Base instructions',
|
|
66
72
|
additional_instructions: 'Additional instructions',
|
|
67
73
|
},
|
|
68
74
|
});
|
|
69
|
-
|
|
75
|
+
|
|
76
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
77
|
+
expect(result[0].content).toBe(
|
|
78
|
+
'Base instructions\n\nAdditional instructions'
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('marks only stable system text for Anthropic prompt caching', async () => {
|
|
83
|
+
const ctx = createBasicContext({
|
|
84
|
+
agentConfig: {
|
|
85
|
+
provider: Providers.ANTHROPIC,
|
|
86
|
+
clientOptions: { model: 'claude-3-5-sonnet', promptCache: true },
|
|
87
|
+
instructions: 'Stable instructions',
|
|
88
|
+
additional_instructions: 'Dynamic instructions',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
93
|
+
const content = result[0].content as TestSystemContentBlock[];
|
|
94
|
+
expect(content).toHaveLength(2);
|
|
95
|
+
expect(content[0]).toMatchObject({
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: 'Stable instructions',
|
|
98
|
+
cache_control: { type: 'ephemeral' },
|
|
99
|
+
});
|
|
100
|
+
expect(content[1]).toEqual({
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: 'Dynamic instructions',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('omits Anthropic cache control when only dynamic system text exists', async () => {
|
|
107
|
+
const ctx = createBasicContext({
|
|
108
|
+
agentConfig: {
|
|
109
|
+
provider: Providers.ANTHROPIC,
|
|
110
|
+
clientOptions: { model: 'claude-3-5-sonnet', promptCache: true },
|
|
111
|
+
instructions: undefined,
|
|
112
|
+
additional_instructions: 'Dynamic only',
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
117
|
+
const content = result[0].content as TestSystemContentBlock[];
|
|
118
|
+
expect(content).toEqual([{ type: 'text', text: 'Dynamic only' }]);
|
|
119
|
+
expect(content[0]).not.toHaveProperty('cache_control');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('keeps cross-run summaries in the dynamic Anthropic system tail', async () => {
|
|
123
|
+
const ctx = createBasicContext({
|
|
124
|
+
agentConfig: {
|
|
125
|
+
provider: Providers.ANTHROPIC,
|
|
126
|
+
clientOptions: { model: 'claude-3-5-sonnet', promptCache: true },
|
|
127
|
+
instructions: 'Stable instructions',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
ctx.setInitialSummary('Prior summary', 13);
|
|
131
|
+
|
|
132
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
133
|
+
const content = result[0].content as TestSystemContentBlock[];
|
|
134
|
+
expect(content).toHaveLength(2);
|
|
135
|
+
expect(content[0]).toHaveProperty('cache_control');
|
|
136
|
+
expect(content[1]).toEqual({
|
|
137
|
+
type: 'text',
|
|
138
|
+
text: '## Conversation Summary\n\nPrior summary',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('places the Bedrock cache point before dynamic system text', async () => {
|
|
143
|
+
const ctx = createBasicContext({
|
|
144
|
+
agentConfig: {
|
|
145
|
+
provider: Providers.BEDROCK,
|
|
146
|
+
clientOptions: {
|
|
147
|
+
model: 'anthropic.claude-3-5-sonnet',
|
|
148
|
+
promptCache: true,
|
|
149
|
+
},
|
|
150
|
+
instructions: 'Stable instructions',
|
|
151
|
+
additional_instructions: 'Dynamic instructions',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
156
|
+
const content = result[0].content as TestSystemContentBlock[];
|
|
157
|
+
expect(content).toEqual([
|
|
158
|
+
{ type: 'text', text: 'Stable instructions' },
|
|
159
|
+
{ cachePoint: { type: 'default' } },
|
|
160
|
+
{ type: 'text', text: 'Dynamic instructions' },
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('uses plain Bedrock system text when only dynamic system text exists', async () => {
|
|
165
|
+
const ctx = createBasicContext({
|
|
166
|
+
agentConfig: {
|
|
167
|
+
provider: Providers.BEDROCK,
|
|
168
|
+
clientOptions: {
|
|
169
|
+
model: 'anthropic.claude-3-5-sonnet',
|
|
170
|
+
promptCache: true,
|
|
171
|
+
},
|
|
172
|
+
instructions: undefined,
|
|
173
|
+
additional_instructions: 'Dynamic only',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
178
|
+
expect(result[0].content).toBe('Dynamic only');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('keeps non-cache providers as plain system text with promptCache-like options', async () => {
|
|
182
|
+
const clientOptions: t.OpenAIClientOptions & { promptCache: true } = {
|
|
183
|
+
modelName: 'gpt-4o-mini',
|
|
184
|
+
promptCache: true,
|
|
185
|
+
};
|
|
186
|
+
const ctx = createBasicContext({
|
|
187
|
+
agentConfig: {
|
|
188
|
+
provider: Providers.OPENAI,
|
|
189
|
+
clientOptions,
|
|
190
|
+
instructions: 'Stable instructions',
|
|
191
|
+
additional_instructions: 'Dynamic instructions',
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result = await ctx.systemRunnable!.invoke([]);
|
|
196
|
+
expect(result[0].content).toBe(
|
|
197
|
+
'Stable instructions\n\nDynamic instructions'
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('preserves the Bedrock system cache point through message cache-control pass', async () => {
|
|
202
|
+
const ctx = createBasicContext({
|
|
203
|
+
agentConfig: {
|
|
204
|
+
provider: Providers.BEDROCK,
|
|
205
|
+
clientOptions: {
|
|
206
|
+
model: 'anthropic.claude-3-5-sonnet',
|
|
207
|
+
promptCache: true,
|
|
208
|
+
},
|
|
209
|
+
instructions: 'Stable instructions',
|
|
210
|
+
additional_instructions: 'Dynamic instructions',
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const result = await ctx.systemRunnable!.invoke([
|
|
215
|
+
new HumanMessage('Hello'),
|
|
216
|
+
]);
|
|
217
|
+
const finalMessages = addBedrockCacheControl(result);
|
|
218
|
+
expect(finalMessages[0].content).toEqual([
|
|
219
|
+
{ type: 'text', text: 'Stable instructions' },
|
|
220
|
+
{ cachePoint: { type: 'default' } },
|
|
221
|
+
{ type: 'text', text: 'Dynamic instructions' },
|
|
222
|
+
]);
|
|
70
223
|
});
|
|
71
224
|
});
|
|
72
225
|
|
|
@@ -404,6 +557,141 @@ describe('AgentContext', () => {
|
|
|
404
557
|
expect(ctxWithDeferred.toolSchemaTokens).toBe(ctxBase.toolSchemaTokens);
|
|
405
558
|
});
|
|
406
559
|
|
|
560
|
+
it('excludes programmatic-only toolDefinitions from toolSchemaTokens', async () => {
|
|
561
|
+
// getEventDrivenToolsForBinding excludes definitions whose
|
|
562
|
+
// allowed_callers omit 'direct'. Accounting must mirror that — a
|
|
563
|
+
// programmatic-only definition is never bound to the model and
|
|
564
|
+
// shouldn't inflate toolSchemaTokens.
|
|
565
|
+
const activeDef: t.LCTool = {
|
|
566
|
+
name: 'active_tool',
|
|
567
|
+
description: 'Always loaded',
|
|
568
|
+
parameters: { type: 'object', properties: {} },
|
|
569
|
+
};
|
|
570
|
+
const programmaticDef: t.LCTool = {
|
|
571
|
+
name: 'programmatic_tool',
|
|
572
|
+
description: 'Only callable via code execution',
|
|
573
|
+
parameters: { type: 'object', properties: {} },
|
|
574
|
+
allowed_callers: ['code_execution'],
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const ctxBase = createBasicContext({
|
|
578
|
+
agentConfig: { toolDefinitions: [activeDef] },
|
|
579
|
+
tokenCounter: mockTokenCounter,
|
|
580
|
+
});
|
|
581
|
+
const ctxWithProgrammatic = createBasicContext({
|
|
582
|
+
agentConfig: { toolDefinitions: [activeDef, programmaticDef] },
|
|
583
|
+
tokenCounter: mockTokenCounter,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
await ctxBase.tokenCalculationPromise;
|
|
587
|
+
await ctxWithProgrammatic.tokenCalculationPromise;
|
|
588
|
+
|
|
589
|
+
expect(ctxWithProgrammatic.toolSchemaTokens).toBe(
|
|
590
|
+
ctxBase.toolSchemaTokens
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('excludes deferred-undiscovered instance tools from toolSchemaTokens', async () => {
|
|
595
|
+
const activeTool = createMockTool('active_tool');
|
|
596
|
+
const deferredTool = createMockTool('deferred_tool');
|
|
597
|
+
const programmaticTool = createMockTool('programmatic_tool');
|
|
598
|
+
const toolRegistry: t.LCToolRegistry = new Map([
|
|
599
|
+
['active_tool', { name: 'active_tool' }],
|
|
600
|
+
['deferred_tool', { name: 'deferred_tool', defer_loading: true }],
|
|
601
|
+
[
|
|
602
|
+
'programmatic_tool',
|
|
603
|
+
{
|
|
604
|
+
name: 'programmatic_tool',
|
|
605
|
+
allowed_callers: ['code_execution'],
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
]);
|
|
609
|
+
|
|
610
|
+
const ctxBase = createBasicContext({
|
|
611
|
+
agentConfig: { tools: [activeTool], toolRegistry },
|
|
612
|
+
tokenCounter: mockTokenCounter,
|
|
613
|
+
});
|
|
614
|
+
const ctxWithExcluded = createBasicContext({
|
|
615
|
+
agentConfig: {
|
|
616
|
+
tools: [activeTool, deferredTool, programmaticTool],
|
|
617
|
+
toolRegistry,
|
|
618
|
+
},
|
|
619
|
+
tokenCounter: mockTokenCounter,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
await ctxBase.tokenCalculationPromise;
|
|
623
|
+
await ctxWithExcluded.tokenCalculationPromise;
|
|
624
|
+
|
|
625
|
+
expect(ctxWithExcluded.toolSchemaTokens).toBe(ctxBase.toolSchemaTokens);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('includes deferred instance tools once discovered via discoveredTools input', async () => {
|
|
629
|
+
const tools = [createMockTool('deferred_tool')];
|
|
630
|
+
const toolRegistry: t.LCToolRegistry = new Map([
|
|
631
|
+
['deferred_tool', { name: 'deferred_tool', defer_loading: true }],
|
|
632
|
+
]);
|
|
633
|
+
|
|
634
|
+
const ctxUndiscovered = createBasicContext({
|
|
635
|
+
agentConfig: { tools, toolRegistry },
|
|
636
|
+
tokenCounter: mockTokenCounter,
|
|
637
|
+
});
|
|
638
|
+
const ctxDiscovered = createBasicContext({
|
|
639
|
+
agentConfig: {
|
|
640
|
+
tools,
|
|
641
|
+
toolRegistry,
|
|
642
|
+
discoveredTools: ['deferred_tool'],
|
|
643
|
+
},
|
|
644
|
+
tokenCounter: mockTokenCounter,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
await ctxUndiscovered.tokenCalculationPromise;
|
|
648
|
+
await ctxDiscovered.tokenCalculationPromise;
|
|
649
|
+
|
|
650
|
+
expect(ctxUndiscovered.toolSchemaTokens).toBe(0);
|
|
651
|
+
expect(ctxDiscovered.toolSchemaTokens).toBeGreaterThan(0);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('does not filter instance tools in event-driven mode (matches getEventDrivenToolsForBinding)', async () => {
|
|
655
|
+
// In event-driven mode, getEventDrivenToolsForBinding appends
|
|
656
|
+
// `this.tools` UNFILTERED. Accounting must do the same — otherwise we
|
|
657
|
+
// under-count and risk exceeding the model's context budget.
|
|
658
|
+
const activeDef: t.LCTool = {
|
|
659
|
+
name: 'active_def',
|
|
660
|
+
description: 'Always loaded',
|
|
661
|
+
parameters: { type: 'object', properties: {} },
|
|
662
|
+
};
|
|
663
|
+
const nativeTool = createMockTool('native_tool');
|
|
664
|
+
// Registry marks the native tool as deferred-undiscovered. In the
|
|
665
|
+
// non-event-driven path this would exclude it; in event-driven mode
|
|
666
|
+
// it is still bound and must still be counted.
|
|
667
|
+
const toolRegistry: t.LCToolRegistry = new Map([
|
|
668
|
+
['native_tool', { name: 'native_tool', defer_loading: true }],
|
|
669
|
+
]);
|
|
670
|
+
|
|
671
|
+
const ctxWithoutNative = createBasicContext({
|
|
672
|
+
agentConfig: {
|
|
673
|
+
toolDefinitions: [activeDef],
|
|
674
|
+
toolRegistry,
|
|
675
|
+
},
|
|
676
|
+
tokenCounter: mockTokenCounter,
|
|
677
|
+
});
|
|
678
|
+
const ctxWithNative = createBasicContext({
|
|
679
|
+
agentConfig: {
|
|
680
|
+
toolDefinitions: [activeDef],
|
|
681
|
+
tools: [nativeTool],
|
|
682
|
+
toolRegistry,
|
|
683
|
+
},
|
|
684
|
+
tokenCounter: mockTokenCounter,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await ctxWithoutNative.tokenCalculationPromise;
|
|
688
|
+
await ctxWithNative.tokenCalculationPromise;
|
|
689
|
+
|
|
690
|
+
expect(ctxWithNative.toolSchemaTokens).toBeGreaterThan(
|
|
691
|
+
ctxWithoutNative.toolSchemaTokens
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
|
|
407
695
|
it('includes deferred toolDefinitions once discovered via discoveredTools input', async () => {
|
|
408
696
|
const toolDefinitions: t.LCTool[] = [
|
|
409
697
|
{
|
|
@@ -448,6 +736,36 @@ describe('AgentContext', () => {
|
|
|
448
736
|
expect(ctx.getTokenBudgetBreakdown().toolCount).toBe(1);
|
|
449
737
|
});
|
|
450
738
|
|
|
739
|
+
it('getTokenBudgetBreakdown toolCount excludes deferred-undiscovered instance tools', () => {
|
|
740
|
+
// Mirrors the toolDefinitions test for the instance-tools path so
|
|
741
|
+
// toolCount stays aligned with toolSchemaTokens (and with what
|
|
742
|
+
// getToolsForBinding actually emits) for non-event-driven runs.
|
|
743
|
+
const tools = [
|
|
744
|
+
createMockTool('active_tool'),
|
|
745
|
+
createMockTool('deferred_tool'),
|
|
746
|
+
createMockTool('programmatic_tool'),
|
|
747
|
+
];
|
|
748
|
+
const toolRegistry: t.LCToolRegistry = new Map([
|
|
749
|
+
['active_tool', { name: 'active_tool' }],
|
|
750
|
+
['deferred_tool', { name: 'deferred_tool', defer_loading: true }],
|
|
751
|
+
[
|
|
752
|
+
'programmatic_tool',
|
|
753
|
+
{
|
|
754
|
+
name: 'programmatic_tool',
|
|
755
|
+
allowed_callers: ['code_execution'],
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
]);
|
|
759
|
+
|
|
760
|
+
const ctx = createBasicContext({
|
|
761
|
+
agentConfig: { tools, toolRegistry },
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
expect(ctx.getTokenBudgetBreakdown().toolCount).toBe(1);
|
|
765
|
+
ctx.markToolsAsDiscovered(['deferred_tool']);
|
|
766
|
+
expect(ctx.getTokenBudgetBreakdown().toolCount).toBe(2);
|
|
767
|
+
});
|
|
768
|
+
|
|
451
769
|
it('getTokenBudgetBreakdown toolCount reflects newly discovered deferred tools', () => {
|
|
452
770
|
const toolDefinitions: t.LCTool[] = [
|
|
453
771
|
{
|
|
@@ -464,6 +782,19 @@ describe('AgentContext', () => {
|
|
|
464
782
|
expect(ctx.getTokenBudgetBreakdown().toolCount).toBe(1);
|
|
465
783
|
});
|
|
466
784
|
|
|
785
|
+
it('getTokenBudgetBreakdown toolCount includes graphTools', () => {
|
|
786
|
+
// graphTools (handoff/subagent) are bound to the model alongside
|
|
787
|
+
// instance tools. Now that toolCount derives from getToolsForBinding(),
|
|
788
|
+
// graphTools are reflected in the diagnostic just like they're
|
|
789
|
+
// counted in toolSchemaTokens. Locks in that alignment.
|
|
790
|
+
const ctx = createBasicContext({
|
|
791
|
+
agentConfig: { tools: [createMockTool('direct_tool')] },
|
|
792
|
+
});
|
|
793
|
+
ctx.graphTools = [createMockTool('handoff_tool')];
|
|
794
|
+
|
|
795
|
+
expect(ctx.getTokenBudgetBreakdown().toolCount).toBe(2);
|
|
796
|
+
});
|
|
797
|
+
|
|
467
798
|
it('toolSchemaTokens snapshot does not auto-update after markToolsAsDiscovered', async () => {
|
|
468
799
|
const toolDefinitions: t.LCTool[] = [
|
|
469
800
|
{
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { expect } from '@jest/globals';
|
|
2
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { UsageMetadata } from '@langchain/core/messages';
|
|
4
|
+
import type * as t from '@/types';
|
|
5
|
+
import { GraphEvents, Providers } from '@/common';
|
|
6
|
+
import { AgentContext } from '../AgentContext';
|
|
7
|
+
import { ModelEndHandler } from '@/events';
|
|
8
|
+
import { Run } from '@/run';
|
|
9
|
+
|
|
10
|
+
type LivePromptCacheProvider = Providers.ANTHROPIC | Providers.BEDROCK;
|
|
11
|
+
|
|
12
|
+
type PromptCacheExpectedSystemBlock =
|
|
13
|
+
| { type: 'text'; text: string; cache_control?: { type: 'ephemeral' } }
|
|
14
|
+
| { cachePoint: { type: 'default' } };
|
|
15
|
+
|
|
16
|
+
type LivePromptCacheClientOptions =
|
|
17
|
+
| t.ClientOptions
|
|
18
|
+
| t.BedrockAnthropicClientOptions;
|
|
19
|
+
|
|
20
|
+
export function buildStableInstructions({
|
|
21
|
+
nonce,
|
|
22
|
+
providerLabel,
|
|
23
|
+
}: {
|
|
24
|
+
nonce: string;
|
|
25
|
+
providerLabel: string;
|
|
26
|
+
}): string {
|
|
27
|
+
const records = Array.from(
|
|
28
|
+
{ length: 360 },
|
|
29
|
+
(_, index) =>
|
|
30
|
+
`Stable ${providerLabel} cache record ${index}: nonce ${nonce}; keep this reference in the cacheable prefix and do not use it as the dynamic marker.`
|
|
31
|
+
);
|
|
32
|
+
return [
|
|
33
|
+
`You are a ${providerLabel} prompt-cache verification assistant.`,
|
|
34
|
+
'When asked for the dynamic marker, answer with only the marker value from the Dynamic Marker line.',
|
|
35
|
+
...records,
|
|
36
|
+
].join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function buildDynamicInstructions({
|
|
40
|
+
marker,
|
|
41
|
+
tailDescription,
|
|
42
|
+
}: {
|
|
43
|
+
marker: string;
|
|
44
|
+
tailDescription: string;
|
|
45
|
+
}): string {
|
|
46
|
+
return [`Dynamic Marker: ${marker}`, tailDescription].join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function waitForCachePropagation(): Promise<void> {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, 2000));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function assertSystemPayloadShape({
|
|
54
|
+
agentId,
|
|
55
|
+
provider,
|
|
56
|
+
clientOptions,
|
|
57
|
+
stableInstructions,
|
|
58
|
+
dynamicInstructions,
|
|
59
|
+
expectedContent,
|
|
60
|
+
}: {
|
|
61
|
+
agentId: string;
|
|
62
|
+
provider: LivePromptCacheProvider;
|
|
63
|
+
clientOptions: LivePromptCacheClientOptions;
|
|
64
|
+
stableInstructions: string;
|
|
65
|
+
dynamicInstructions: string;
|
|
66
|
+
expectedContent: PromptCacheExpectedSystemBlock[];
|
|
67
|
+
}): Promise<void> {
|
|
68
|
+
const ctx = AgentContext.fromConfig({
|
|
69
|
+
agentId,
|
|
70
|
+
provider,
|
|
71
|
+
clientOptions,
|
|
72
|
+
instructions: stableInstructions,
|
|
73
|
+
additional_instructions: dynamicInstructions,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const messages = await ctx.systemRunnable!.invoke([
|
|
77
|
+
new HumanMessage('What is the dynamic marker?'),
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
expect(messages[0].content).toEqual(expectedContent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function latestUsage({
|
|
84
|
+
collectedUsage,
|
|
85
|
+
label,
|
|
86
|
+
providerLabel,
|
|
87
|
+
}: {
|
|
88
|
+
collectedUsage: UsageMetadata[];
|
|
89
|
+
label: string;
|
|
90
|
+
providerLabel: string;
|
|
91
|
+
}): UsageMetadata {
|
|
92
|
+
if (collectedUsage.length === 0) {
|
|
93
|
+
throw new Error(`Missing ${providerLabel} usage metadata for ${label}`);
|
|
94
|
+
}
|
|
95
|
+
return collectedUsage[collectedUsage.length - 1];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function collectText(parts: t.MessageContentComplex[] | undefined): string {
|
|
99
|
+
return (parts ?? []).reduce((text, part) => {
|
|
100
|
+
if (part.type === 'text') {
|
|
101
|
+
return text + part.text;
|
|
102
|
+
}
|
|
103
|
+
return text;
|
|
104
|
+
}, '');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function runLiveTurn({
|
|
108
|
+
provider,
|
|
109
|
+
providerLabel,
|
|
110
|
+
clientOptions,
|
|
111
|
+
runId,
|
|
112
|
+
threadId,
|
|
113
|
+
stableInstructions,
|
|
114
|
+
dynamicInstructions,
|
|
115
|
+
}: {
|
|
116
|
+
provider: LivePromptCacheProvider;
|
|
117
|
+
providerLabel: string;
|
|
118
|
+
clientOptions: LivePromptCacheClientOptions;
|
|
119
|
+
runId: string;
|
|
120
|
+
threadId: string;
|
|
121
|
+
stableInstructions: string;
|
|
122
|
+
dynamicInstructions: string;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
text: string;
|
|
125
|
+
usage: UsageMetadata;
|
|
126
|
+
}> {
|
|
127
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
128
|
+
const run = await Run.create<t.IState>({
|
|
129
|
+
runId,
|
|
130
|
+
graphConfig: {
|
|
131
|
+
type: 'standard',
|
|
132
|
+
llmConfig: {
|
|
133
|
+
provider,
|
|
134
|
+
...clientOptions,
|
|
135
|
+
} as t.LLMConfig,
|
|
136
|
+
instructions: stableInstructions,
|
|
137
|
+
additional_instructions: dynamicInstructions,
|
|
138
|
+
},
|
|
139
|
+
returnContent: true,
|
|
140
|
+
skipCleanup: true,
|
|
141
|
+
customHandlers: {
|
|
142
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const config = {
|
|
147
|
+
configurable: { thread_id: threadId },
|
|
148
|
+
streamMode: 'values',
|
|
149
|
+
version: 'v2' as const,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const contentParts = await run.processStream(
|
|
153
|
+
{
|
|
154
|
+
messages: [
|
|
155
|
+
new HumanMessage('What is the dynamic marker? Reply with only it.'),
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
config
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
text: collectText(contentParts),
|
|
163
|
+
usage: latestUsage({ collectedUsage, label: runId, providerLabel }),
|
|
164
|
+
};
|
|
165
|
+
}
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -399,12 +399,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
399
399
|
): (string | number | undefined)[] {
|
|
400
400
|
if (!metadata) return [];
|
|
401
401
|
|
|
402
|
+
const configurable = this.config?.configurable;
|
|
403
|
+
const runId =
|
|
404
|
+
(metadata.run_id as string | undefined) ??
|
|
405
|
+
(configurable?.run_id as string | undefined) ??
|
|
406
|
+
this.runId;
|
|
407
|
+
const threadId =
|
|
408
|
+
(metadata.thread_id as string | undefined) ??
|
|
409
|
+
(configurable?.thread_id as string | undefined) ??
|
|
410
|
+
runId;
|
|
411
|
+
const checkpointNs =
|
|
412
|
+
(metadata.checkpoint_ns as string | undefined) ??
|
|
413
|
+
(metadata.langgraph_checkpoint_ns as string | undefined) ??
|
|
414
|
+
'';
|
|
402
415
|
const keyList = [
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
runId,
|
|
417
|
+
threadId,
|
|
405
418
|
metadata.langgraph_node as string,
|
|
406
419
|
metadata.langgraph_step as number,
|
|
407
|
-
|
|
420
|
+
checkpointNs,
|
|
408
421
|
];
|
|
409
422
|
|
|
410
423
|
const agentContext = this.getAgentContext(metadata);
|
|
@@ -1461,7 +1474,14 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1461
1474
|
}),
|
|
1462
1475
|
});
|
|
1463
1476
|
const workflow = new StateGraph(StateAnnotation)
|
|
1464
|
-
.addNode(
|
|
1477
|
+
.addNode(
|
|
1478
|
+
this.defaultAgentId,
|
|
1479
|
+
agentNode as Runnable<
|
|
1480
|
+
t.AgentSubgraphState,
|
|
1481
|
+
Partial<t.AgentSubgraphState>
|
|
1482
|
+
>,
|
|
1483
|
+
{ ends: [END] }
|
|
1484
|
+
)
|
|
1465
1485
|
.addEdge(START, this.defaultAgentId)
|
|
1466
1486
|
// LangGraph compile() types are overly strict for opt-in options
|
|
1467
1487
|
.compile(this.compileOptions as unknown as never);
|