@librechat/agents 3.2.68 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +117 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +4 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +243 -49
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +103 -28
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +4 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +11 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +60 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/main.cjs +21 -7
- package/dist/cjs/messages/format.cjs +136 -4
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +16 -5
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +2 -0
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +101 -0
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -0
- package/dist/cjs/run.cjs +177 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +205 -49
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +55 -7
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +2 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +69 -8
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/cjs/utils/errors.cjs +317 -52
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +118 -4
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +4 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +242 -48
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +103 -28
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +4 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +11 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +59 -4
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/main.mjs +10 -10
- package/dist/esm/messages/format.mjs +136 -5
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +14 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +2 -1
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +100 -0
- package/dist/esm/prompts/activityLabel.mjs.map +1 -0
- package/dist/esm/run.mjs +178 -4
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +205 -49
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +55 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +62 -9
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/esm/utils/errors.mjs +317 -53
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +62 -3
- package/dist/types/common/enum.d.ts +5 -1
- package/dist/types/graphs/Graph.d.ts +16 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +4 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
- package/dist/types/llm/invoke.d.ts +35 -6
- package/dist/types/messages/format.d.ts +22 -0
- package/dist/types/messages/prune.d.ts +10 -2
- package/dist/types/messages/recency.d.ts +1 -0
- package/dist/types/prompts/activityLabel.d.ts +31 -0
- package/dist/types/run.d.ts +16 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -1
- package/dist/types/types/activityLabel.d.ts +53 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/llm.d.ts +7 -4
- package/dist/types/types/stream.d.ts +7 -4
- package/dist/types/types/summarize.d.ts +22 -0
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
- package/dist/types/utils/errors.d.ts +65 -16
- package/dist/types/utils/redactSecrets.d.ts +3 -0
- package/package.json +7 -8
- package/src/agents/AgentContext.ts +188 -7
- package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
- package/src/common/enum.ts +4 -0
- package/src/graphs/Graph.ts +409 -58
- package/src/graphs/MultiAgentGraph.ts +184 -46
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
- package/src/langfuseToolOutputTracing.ts +4 -1
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
- package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
- package/src/llm/bedrock/index.ts +25 -12
- package/src/llm/contextOverflowRecovery.ts +292 -0
- package/src/llm/invoke.ts +119 -4
- package/src/messages/foldToollessToolBlocks.test.ts +438 -0
- package/src/messages/format.ts +233 -5
- package/src/messages/prune.ts +24 -11
- package/src/messages/recency.ts +3 -1
- package/src/prompts/activityLabel.ts +177 -0
- package/src/run.ts +322 -3
- package/src/scripts/context-overflow-probe.ts +997 -0
- package/src/specs/activity-label-prompt.test.ts +128 -0
- package/src/specs/activity-label-trace-seed.test.ts +47 -0
- package/src/specs/agent-handoffs.test.ts +903 -1
- package/src/specs/bedrock-toolless.live.test.ts +123 -0
- package/src/specs/context-overflow-recovery.live.test.ts +213 -0
- package/src/splitStream.test.ts +882 -0
- package/src/stream.ts +315 -51
- package/src/summarization/__tests__/aggregator.test.ts +83 -0
- package/src/summarization/__tests__/node.test.ts +139 -0
- package/src/summarization/node.ts +99 -14
- package/src/tools/BashExecutor.ts +4 -2
- package/src/tools/BashProgrammaticToolCalling.ts +4 -7
- package/src/tools/CodeExecutor.ts +119 -8
- package/src/tools/ProgrammaticToolCalling.ts +29 -27
- package/src/tools/ToolNode.ts +50 -8
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
- package/src/types/activityLabel.ts +55 -0
- package/src/types/index.ts +1 -0
- package/src/types/llm.ts +8 -1
- package/src/types/stream.ts +7 -4
- package/src/types/summarize.ts +22 -0
- package/src/utils/__tests__/errors.test.ts +270 -0
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
- package/src/utils/__tests__/redactSecrets.test.ts +56 -0
- package/src/utils/errors.ts +484 -66
- package/src/utils/redactSecrets.ts +61 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/specs/bedrock-toolless.live.test.ts
|
|
2
|
+
/**
|
|
3
|
+
* Live Bedrock verification for the tool-less-destination toolConfig fix.
|
|
4
|
+
*
|
|
5
|
+
* A tool-less agent in a multi-agent graph inherits the prior agent's
|
|
6
|
+
* toolUse/toolResult history. Because it binds no tools, Bedrock's Converse API
|
|
7
|
+
* rejects the request ("The toolConfig field must be defined when using toolUse
|
|
8
|
+
* and toolResult content blocks"). `foldToolBlocksForToollessAgent` folds that
|
|
9
|
+
* history into text so the request becomes valid.
|
|
10
|
+
*
|
|
11
|
+
* Run with:
|
|
12
|
+
* RUN_BEDROCK_LIVE_TESTS=1 BEDROCK_AWS_ACCESS_KEY_ID=... BEDROCK_AWS_SECRET_ACCESS_KEY=... \
|
|
13
|
+
* BEDROCK_AWS_DEFAULT_REGION=us-west-2 npm test -- bedrock-toolless.live.test.ts --runInBand
|
|
14
|
+
*/
|
|
15
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
16
|
+
dotenvConfig();
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
AIMessage,
|
|
20
|
+
HumanMessage,
|
|
21
|
+
ToolMessage,
|
|
22
|
+
} from '@langchain/core/messages';
|
|
23
|
+
import { beforeAll, describe, expect, it } from '@jest/globals';
|
|
24
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
25
|
+
import type * as t from '@/types';
|
|
26
|
+
import { Providers } from '@/common';
|
|
27
|
+
import { initializeModel } from '@/llm/init';
|
|
28
|
+
import { foldToolBlocksForToollessAgent } from '@/messages';
|
|
29
|
+
|
|
30
|
+
const accessKeyId =
|
|
31
|
+
process.env.BEDROCK_AWS_ACCESS_KEY_ID ?? process.env.AWS_ACCESS_KEY_ID;
|
|
32
|
+
const secretAccessKey =
|
|
33
|
+
process.env.BEDROCK_AWS_SECRET_ACCESS_KEY ??
|
|
34
|
+
process.env.AWS_SECRET_ACCESS_KEY;
|
|
35
|
+
|
|
36
|
+
const shouldRunLive =
|
|
37
|
+
process.env.RUN_BEDROCK_LIVE_TESTS === '1' &&
|
|
38
|
+
accessKeyId != null &&
|
|
39
|
+
accessKeyId !== '' &&
|
|
40
|
+
secretAccessKey != null &&
|
|
41
|
+
secretAccessKey !== '';
|
|
42
|
+
|
|
43
|
+
const describeIfLive = shouldRunLive ? describe : describe.skip;
|
|
44
|
+
|
|
45
|
+
const MODEL =
|
|
46
|
+
process.env.LIVE_BEDROCK_MODEL ??
|
|
47
|
+
'us.anthropic.claude-sonnet-4-5-20250929-v1:0';
|
|
48
|
+
const REGION =
|
|
49
|
+
process.env.BEDROCK_AWS_DEFAULT_REGION ??
|
|
50
|
+
process.env.AWS_REGION ??
|
|
51
|
+
'us-west-2';
|
|
52
|
+
|
|
53
|
+
/** History a tool-less destination inherits: a completed tool call, then a
|
|
54
|
+
* follow-up user turn that itself invokes no tool. */
|
|
55
|
+
function toollessHistory(): BaseMessage[] {
|
|
56
|
+
return [
|
|
57
|
+
new HumanMessage('Search my files for the roadmap.'),
|
|
58
|
+
new AIMessage({
|
|
59
|
+
content: '',
|
|
60
|
+
tool_calls: [
|
|
61
|
+
{
|
|
62
|
+
id: 'tt_live_1',
|
|
63
|
+
name: 'file_search',
|
|
64
|
+
args: { query: 'roadmap' },
|
|
65
|
+
type: 'tool_call',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
}),
|
|
69
|
+
new ToolMessage({
|
|
70
|
+
content: 'Found: roadmap.md — Q3 goals and milestones.',
|
|
71
|
+
tool_call_id: 'tt_live_1',
|
|
72
|
+
name: 'file_search',
|
|
73
|
+
}),
|
|
74
|
+
new AIMessage('I found roadmap.md with your Q3 goals.'),
|
|
75
|
+
new HumanMessage('thanks!'),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describeIfLive('Bedrock tool-less destination (live)', () => {
|
|
80
|
+
let clientOptions: t.BedrockConverseClientOptions;
|
|
81
|
+
|
|
82
|
+
beforeAll(() => {
|
|
83
|
+
// Force SigV4 with the explicit keys; the Bedrock API-key (bearer) auth
|
|
84
|
+
// scheme otherwise takes precedence in the AWS SDK.
|
|
85
|
+
delete process.env.AWS_BEARER_TOKEN_BEDROCK;
|
|
86
|
+
// `shouldRunLive` already guarantees both keys are set; `?? ''` only keeps
|
|
87
|
+
// the credential fields typed as strings.
|
|
88
|
+
clientOptions = {
|
|
89
|
+
region: REGION,
|
|
90
|
+
model: MODEL,
|
|
91
|
+
credentials: {
|
|
92
|
+
accessKeyId: accessKeyId ?? '',
|
|
93
|
+
secretAccessKey: secretAccessKey ?? '',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('rejects inherited tool history when the agent binds no tools', async () => {
|
|
99
|
+
const model = initializeModel({
|
|
100
|
+
provider: Providers.BEDROCK,
|
|
101
|
+
clientOptions,
|
|
102
|
+
tools: undefined,
|
|
103
|
+
});
|
|
104
|
+
await expect(model.invoke(toollessHistory())).rejects.toThrow(
|
|
105
|
+
/toolConfig field must be defined/i
|
|
106
|
+
);
|
|
107
|
+
}, 30000);
|
|
108
|
+
|
|
109
|
+
it('succeeds once inherited tool blocks are folded to text', async () => {
|
|
110
|
+
const model = initializeModel({
|
|
111
|
+
provider: Providers.BEDROCK,
|
|
112
|
+
clientOptions,
|
|
113
|
+
tools: undefined,
|
|
114
|
+
});
|
|
115
|
+
const folded = foldToolBlocksForToollessAgent(toollessHistory());
|
|
116
|
+
const res = await model.invoke(folded);
|
|
117
|
+
const text =
|
|
118
|
+
typeof res.content === 'string'
|
|
119
|
+
? res.content
|
|
120
|
+
: JSON.stringify(res.content);
|
|
121
|
+
expect(text.length).toBeGreaterThan(0);
|
|
122
|
+
}, 30000);
|
|
123
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live end-to-end verification that a real provider rejection for an
|
|
3
|
+
* over-limit prompt is absorbed by forced compaction instead of surfacing.
|
|
4
|
+
*
|
|
5
|
+
* Each case deliberately configures `maxContextTokens` far above the model's
|
|
6
|
+
* real window, so the proactive pruner does not prevent the overflow and the
|
|
7
|
+
* reactive recovery path is what gets exercised.
|
|
8
|
+
*
|
|
9
|
+
* Run with:
|
|
10
|
+
* RUN_CONTEXT_OVERFLOW_LIVE_TESTS=1 OPENROUTER_API_KEY=... npm test -- context-overflow-recovery.live.test.ts --runInBand
|
|
11
|
+
*
|
|
12
|
+
* Cases self-skip when their provider credentials are absent. Models are the
|
|
13
|
+
* smallest-window, cheapest option per provider — the prompt has to exceed
|
|
14
|
+
* the window for the test to mean anything, so a large window is a large bill.
|
|
15
|
+
*
|
|
16
|
+
* The Bedrock case needs `NODE_OPTIONS='--experimental-vm-modules'`; the AWS
|
|
17
|
+
* SDK uses a dynamic import that jest otherwise refuses.
|
|
18
|
+
*/
|
|
19
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
20
|
+
dotenvConfig();
|
|
21
|
+
|
|
22
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
23
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
24
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
25
|
+
import type * as t from '@/types';
|
|
26
|
+
import { createTokenCounter } from '@/utils/tokens';
|
|
27
|
+
import { hasAnyEnv, hasEnv } from '@/specs/spec.utils';
|
|
28
|
+
import { GraphEvents, Providers } from '@/common';
|
|
29
|
+
import { Run } from '@/run';
|
|
30
|
+
|
|
31
|
+
jest.setTimeout(300_000);
|
|
32
|
+
|
|
33
|
+
const liveEnabled = process.env.RUN_CONTEXT_OVERFLOW_LIVE_TESTS === '1';
|
|
34
|
+
|
|
35
|
+
interface LiveCase {
|
|
36
|
+
label: string;
|
|
37
|
+
provider: Providers;
|
|
38
|
+
model: string;
|
|
39
|
+
/** The model's real input window. */
|
|
40
|
+
contextWindow: number;
|
|
41
|
+
envKeys: readonly string[];
|
|
42
|
+
clientOptions: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const LIVE_CASES: readonly LiveCase[] = [
|
|
46
|
+
{
|
|
47
|
+
label: 'openrouter / qwen-2.5-7b (32k window)',
|
|
48
|
+
provider: Providers.OPENROUTER,
|
|
49
|
+
model: 'qwen/qwen-2.5-7b-instruct',
|
|
50
|
+
contextWindow: 32_768,
|
|
51
|
+
envKeys: ['OPENROUTER_API_KEY'],
|
|
52
|
+
clientOptions: {
|
|
53
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
54
|
+
configuration: {
|
|
55
|
+
baseURL:
|
|
56
|
+
process.env.OPENROUTER_BASE_URL ?? 'https://openrouter.ai/api/v1',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
/**
|
|
62
|
+
* Google's text models all carry a ~1M window, which would make this the
|
|
63
|
+
* most expensive case in the file by an order of magnitude. This model
|
|
64
|
+
* accepts ordinary text and caps its input at 64k, so it exercises the
|
|
65
|
+
* same Gemini API rejection for a fraction of the tokens.
|
|
66
|
+
*/
|
|
67
|
+
label: 'google / gemini-3.1-flash-image (64k window)',
|
|
68
|
+
provider: Providers.GOOGLE,
|
|
69
|
+
model: 'gemini-3.1-flash-image',
|
|
70
|
+
contextWindow: 65_536,
|
|
71
|
+
envKeys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
|
|
72
|
+
clientOptions: {
|
|
73
|
+
apiKey: process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'anthropic / haiku (200k window)',
|
|
78
|
+
provider: Providers.ANTHROPIC,
|
|
79
|
+
model: 'claude-haiku-4-5-20251001',
|
|
80
|
+
contextWindow: 200_000,
|
|
81
|
+
envKeys: ['ANTHROPIC_API_KEY'],
|
|
82
|
+
clientOptions: { apiKey: process.env.ANTHROPIC_API_KEY },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'bedrock / haiku (200k window)',
|
|
86
|
+
provider: Providers.BEDROCK,
|
|
87
|
+
model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
|
|
88
|
+
contextWindow: 200_000,
|
|
89
|
+
envKeys: ['BEDROCK_AWS_ACCESS_KEY_ID', 'AWS_ACCESS_KEY_ID'],
|
|
90
|
+
clientOptions: {
|
|
91
|
+
region:
|
|
92
|
+
process.env.BEDROCK_AWS_REGION ?? process.env.AWS_REGION ?? 'us-east-1',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Single-token words, so an N-word message is at least N tokens under every
|
|
99
|
+
* provider's tokenizer — the prompt must clear the window with certainty,
|
|
100
|
+
* since a request that squeaks under it is billed in full and proves nothing.
|
|
101
|
+
*/
|
|
102
|
+
const FILLER =
|
|
103
|
+
'the quick brown fox jumps over lazy dog and then runs past'.split(' ');
|
|
104
|
+
|
|
105
|
+
function buildOversizedHistory(totalWords: number): BaseMessage[] {
|
|
106
|
+
const perMessage = Math.ceil(totalWords / 4);
|
|
107
|
+
const messages: BaseMessage[] = [];
|
|
108
|
+
for (let turn = 0; turn < 4; turn++) {
|
|
109
|
+
const words: string[] = new Array(perMessage);
|
|
110
|
+
for (let i = 0; i < perMessage; i++) {
|
|
111
|
+
words[i] = FILLER[(i + turn) % FILLER.length];
|
|
112
|
+
}
|
|
113
|
+
messages.push(new HumanMessage(`Notes part ${turn}: ${words.join(' ')}`));
|
|
114
|
+
}
|
|
115
|
+
messages.push(
|
|
116
|
+
new HumanMessage('In one short sentence, what were those notes about?')
|
|
117
|
+
);
|
|
118
|
+
return messages;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const describeIfLive = liveEnabled ? describe : describe.skip;
|
|
122
|
+
|
|
123
|
+
describeIfLive('context overflow recovery (live)', () => {
|
|
124
|
+
for (const testCase of LIVE_CASES) {
|
|
125
|
+
const runnable = hasAnyEnv(testCase.envKeys);
|
|
126
|
+
const maybeIt = runnable ? it : it.skip;
|
|
127
|
+
|
|
128
|
+
maybeIt(
|
|
129
|
+
`recovers without surfacing an error — ${testCase.label}`,
|
|
130
|
+
async () => {
|
|
131
|
+
const summarizeEvents: t.SummarizeCompleteEvent[] = [];
|
|
132
|
+
const tokenCounter = await createTokenCounter();
|
|
133
|
+
|
|
134
|
+
const run = await Run.create<t.IState>({
|
|
135
|
+
runId: `overflow-live-${testCase.provider}-${Date.now()}`,
|
|
136
|
+
graphConfig: {
|
|
137
|
+
type: 'standard',
|
|
138
|
+
llmConfig: {
|
|
139
|
+
provider: testCase.provider,
|
|
140
|
+
...testCase.clientOptions,
|
|
141
|
+
model: testCase.model,
|
|
142
|
+
} as t.LLMConfig,
|
|
143
|
+
/**
|
|
144
|
+
* Deliberately wrong, by a wide margin: the pruner will happily
|
|
145
|
+
* build a prompt the provider cannot accept, which is exactly the
|
|
146
|
+
* situation the recovery path exists for.
|
|
147
|
+
*/
|
|
148
|
+
maxContextTokens: testCase.contextWindow * 2,
|
|
149
|
+
summarizationEnabled: true,
|
|
150
|
+
summarizationConfig: {
|
|
151
|
+
provider: testCase.provider,
|
|
152
|
+
model: testCase.model,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
returnContent: true,
|
|
156
|
+
/**
|
|
157
|
+
* The assertions below read post-run `AgentContext` state, and the
|
|
158
|
+
* default cleanup path calls `clearHeavyState()` → `reset()`, which
|
|
159
|
+
* deliberately undoes the overflow correction for the next turn.
|
|
160
|
+
*/
|
|
161
|
+
skipCleanup: true,
|
|
162
|
+
tokenCounter,
|
|
163
|
+
customHandlers: {
|
|
164
|
+
[GraphEvents.ON_SUMMARIZE_COMPLETE]: {
|
|
165
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
166
|
+
summarizeEvents.push(
|
|
167
|
+
data as unknown as t.SummarizeCompleteEvent
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const messages = buildOversizedHistory(
|
|
175
|
+
Math.ceil(testCase.contextWindow * 1.3)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const content = await run.processStream({ messages }, {
|
|
179
|
+
configurable: { thread_id: `overflow-live-${testCase.provider}` },
|
|
180
|
+
streamMode: 'values',
|
|
181
|
+
version: 'v2',
|
|
182
|
+
} as never);
|
|
183
|
+
|
|
184
|
+
expect(content).toBeDefined();
|
|
185
|
+
expect(Array.isArray(content)).toBe(true);
|
|
186
|
+
expect((content ?? []).length).toBeGreaterThan(0);
|
|
187
|
+
|
|
188
|
+
const agentContext = run.Graph?.agentContexts.get('default');
|
|
189
|
+
expect(agentContext?.overflowRecoveryAttempts).toBeGreaterThan(0);
|
|
190
|
+
/** The corrected budget must be below the fiction we started with. */
|
|
191
|
+
expect(agentContext?.maxContextTokens).toBeLessThan(
|
|
192
|
+
testCase.contextWindow * 2
|
|
193
|
+
);
|
|
194
|
+
/**
|
|
195
|
+
* No summarization assertion: the first recovery compacts by
|
|
196
|
+
* compressing tool output, so a run that fits after compression alone
|
|
197
|
+
* never spends a summarization call. Any summary event that does
|
|
198
|
+
* arrive must at least carry no error.
|
|
199
|
+
*/
|
|
200
|
+
for (const event of summarizeEvents) {
|
|
201
|
+
expect(event.error).toBeUndefined();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
it('has at least one runnable provider configured', () => {
|
|
208
|
+
const anyRunnable = LIVE_CASES.some((testCase) =>
|
|
209
|
+
testCase.envKeys.some(hasEnv)
|
|
210
|
+
);
|
|
211
|
+
expect(anyRunnable).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
});
|