@librechat/agents 3.1.57 → 3.1.61
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 +326 -62
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +13 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +7 -27
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +303 -222
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +60 -0
- package/dist/cjs/llm/init.cjs.map +1 -0
- package/dist/cjs/llm/invoke.cjs +90 -0
- package/dist/cjs/llm/invoke.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +2 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +41 -0
- package/dist/cjs/llm/request.cjs.map +1 -0
- package/dist/cjs/main.cjs +40 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +76 -89
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +156 -0
- package/dist/cjs/messages/contextPruning.cjs.map +1 -0
- package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
- package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
- package/dist/cjs/messages/core.cjs +23 -37
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +156 -11
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1161 -49
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +87 -0
- package/dist/cjs/messages/reducer.cjs.map +1 -0
- package/dist/cjs/run.cjs +81 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +54 -7
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs +75 -0
- package/dist/cjs/summarization/index.cjs.map +1 -0
- package/dist/cjs/summarization/node.cjs +663 -0
- package/dist/cjs/summarization/node.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +16 -8
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +2 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +115 -0
- package/dist/cjs/utils/errors.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +17 -0
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +16 -0
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +10 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +247 -14
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/truncation.cjs +107 -0
- package/dist/cjs/utils/truncation.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +325 -61
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +13 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +8 -28
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +307 -226
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +58 -0
- package/dist/esm/llm/init.mjs.map +1 -0
- package/dist/esm/llm/invoke.mjs +87 -0
- package/dist/esm/llm/invoke.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +2 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +38 -0
- package/dist/esm/llm/request.mjs.map +1 -0
- package/dist/esm/main.mjs +13 -3
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +76 -89
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +154 -0
- package/dist/esm/messages/contextPruning.mjs.map +1 -0
- package/dist/esm/messages/contextPruningSettings.mjs +50 -0
- package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
- package/dist/esm/messages/core.mjs +23 -37
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +156 -11
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1158 -52
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +83 -0
- package/dist/esm/messages/reducer.mjs.map +1 -0
- package/dist/esm/run.mjs +82 -43
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +54 -7
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs +73 -0
- package/dist/esm/summarization/index.mjs.map +1 -0
- package/dist/esm/summarization/node.mjs +659 -0
- package/dist/esm/summarization/node.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +16 -8
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +2 -0
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +111 -0
- package/dist/esm/utils/errors.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +17 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +16 -0
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +10 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +245 -15
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/truncation.mjs +102 -0
- package/dist/esm/utils/truncation.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +124 -6
- package/dist/types/common/enum.d.ts +14 -1
- package/dist/types/graphs/Graph.d.ts +22 -27
- package/dist/types/index.d.ts +5 -0
- package/dist/types/llm/init.d.ts +18 -0
- package/dist/types/llm/invoke.d.ts +48 -0
- package/dist/types/llm/request.d.ts +14 -0
- package/dist/types/messages/contextPruning.d.ts +42 -0
- package/dist/types/messages/contextPruningSettings.d.ts +44 -0
- package/dist/types/messages/core.d.ts +1 -1
- package/dist/types/messages/format.d.ts +17 -1
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +162 -1
- package/dist/types/messages/reducer.d.ts +18 -0
- package/dist/types/run.d.ts +12 -1
- package/dist/types/summarization/index.d.ts +20 -0
- package/dist/types/summarization/node.d.ts +29 -0
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/types/graph.d.ts +44 -6
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +30 -0
- package/dist/types/types/stream.d.ts +31 -4
- package/dist/types/types/summarize.d.ts +47 -0
- package/dist/types/types/tools.d.ts +7 -0
- package/dist/types/utils/errors.d.ts +28 -0
- package/dist/types/utils/events.d.ts +13 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/llm.d.ts +4 -0
- package/dist/types/utils/tokens.d.ts +14 -1
- package/dist/types/utils/truncation.d.ts +49 -0
- package/package.json +3 -3
- package/src/agents/AgentContext.ts +388 -58
- package/src/agents/__tests__/AgentContext.test.ts +265 -5
- package/src/common/enum.ts +13 -0
- package/src/events.ts +9 -39
- package/src/graphs/Graph.ts +468 -331
- package/src/index.ts +7 -0
- package/src/llm/anthropic/llm.spec.ts +3 -3
- package/src/llm/anthropic/utils/message_inputs.ts +6 -4
- package/src/llm/bedrock/llm.spec.ts +1 -1
- package/src/llm/bedrock/utils/message_inputs.ts +6 -2
- package/src/llm/init.ts +63 -0
- package/src/llm/invoke.ts +144 -0
- package/src/llm/request.ts +55 -0
- package/src/messages/__tests__/observationMasking.test.ts +221 -0
- package/src/messages/cache.ts +77 -102
- package/src/messages/contextPruning.ts +191 -0
- package/src/messages/contextPruningSettings.ts +90 -0
- package/src/messages/core.ts +32 -53
- package/src/messages/ensureThinkingBlock.test.ts +39 -39
- package/src/messages/format.ts +227 -15
- package/src/messages/formatAgentMessages.test.ts +511 -1
- package/src/messages/index.ts +3 -0
- package/src/messages/prune.ts +1548 -62
- package/src/messages/reducer.ts +22 -0
- package/src/run.ts +104 -51
- package/src/scripts/bedrock-merge-test.ts +1 -1
- package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
- package/src/scripts/test-thinking-handoff.ts +1 -1
- package/src/scripts/thinking-bedrock.ts +1 -1
- package/src/scripts/thinking.ts +1 -1
- package/src/specs/anthropic.simple.test.ts +1 -1
- package/src/specs/multi-agent-summarization.test.ts +396 -0
- package/src/specs/prune.test.ts +1196 -23
- package/src/specs/summarization-unit.test.ts +868 -0
- package/src/specs/summarization.test.ts +3827 -0
- package/src/specs/summarize-prune.test.ts +376 -0
- package/src/specs/thinking-handoff.test.ts +10 -10
- package/src/specs/thinking-prune.test.ts +7 -4
- package/src/specs/token-accounting-e2e.test.ts +1034 -0
- package/src/specs/token-accounting-pipeline.test.ts +882 -0
- package/src/specs/token-distribution-edge-case.test.ts +25 -26
- package/src/splitStream.test.ts +42 -33
- package/src/stream.ts +64 -11
- package/src/summarization/__tests__/aggregator.test.ts +153 -0
- package/src/summarization/__tests__/node.test.ts +708 -0
- package/src/summarization/__tests__/trigger.test.ts +50 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/node.ts +982 -0
- package/src/tools/ToolNode.ts +25 -3
- package/src/types/graph.ts +62 -7
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +32 -0
- package/src/types/stream.ts +45 -5
- package/src/types/summarize.ts +58 -0
- package/src/types/tools.ts +7 -0
- package/src/utils/errors.ts +117 -0
- package/src/utils/events.ts +31 -0
- package/src/utils/handlers.ts +18 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/llm.ts +12 -0
- package/src/utils/tokens.ts +336 -18
- package/src/utils/truncation.ts +124 -0
- package/src/scripts/image.ts +0 -180
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/**
|
|
4
|
+
* Multi-agent summarization tests.
|
|
5
|
+
*
|
|
6
|
+
* Validates that summarization works correctly when multiple agents
|
|
7
|
+
* share a conversation, each with independent context budgets and
|
|
8
|
+
* summarization state.
|
|
9
|
+
*
|
|
10
|
+
* Uses FakeListChatModel — no API keys required.
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
HumanMessage,
|
|
14
|
+
AIMessage,
|
|
15
|
+
BaseMessage,
|
|
16
|
+
UsageMetadata,
|
|
17
|
+
} from '@langchain/core/messages';
|
|
18
|
+
import { FakeListChatModel } from '@langchain/core/utils/testing';
|
|
19
|
+
import type * as t from '@/types';
|
|
20
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
21
|
+
import { GraphEvents, Providers } from '@/common';
|
|
22
|
+
import { createContentAggregator } from '@/stream';
|
|
23
|
+
import { createTokenCounter } from '@/utils/tokens';
|
|
24
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
25
|
+
import { Run } from '@/run';
|
|
26
|
+
import * as providers from '@/llm/providers';
|
|
27
|
+
|
|
28
|
+
function getSummaryText(summary: t.SummaryContentBlock | undefined): string {
|
|
29
|
+
if (!summary) return '';
|
|
30
|
+
return (summary.content ?? [])
|
|
31
|
+
.map((block) => ('text' in block ? (block as { text: string }).text : ''))
|
|
32
|
+
.join('');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createSpies(): Record<string, jest.Mock> {
|
|
36
|
+
return {
|
|
37
|
+
onMessageDeltaSpy: jest.fn(),
|
|
38
|
+
onRunStepSpy: jest.fn(),
|
|
39
|
+
onSummarizeStartSpy: jest.fn(),
|
|
40
|
+
onSummarizeCompleteSpy: jest.fn(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildHandlers(
|
|
45
|
+
collectedUsage: UsageMetadata[],
|
|
46
|
+
aggregateContent: t.ContentAggregator,
|
|
47
|
+
spies: ReturnType<typeof createSpies>
|
|
48
|
+
): Record<string | GraphEvents, t.EventHandler> {
|
|
49
|
+
return {
|
|
50
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
51
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
|
52
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
53
|
+
handle: (event: GraphEvents, data: t.StreamEventData): void => {
|
|
54
|
+
aggregateContent({ event, data: data as any });
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
58
|
+
handle: (event: GraphEvents, data: t.StreamEventData): void => {
|
|
59
|
+
spies.onRunStepSpy(event, data);
|
|
60
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
64
|
+
handle: (event: GraphEvents, data: t.StreamEventData): void => {
|
|
65
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
69
|
+
handle: (event: GraphEvents, data: t.StreamEventData): void => {
|
|
70
|
+
spies.onMessageDeltaSpy(event, data);
|
|
71
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
[GraphEvents.TOOL_START]: { handle: (): void => {} },
|
|
75
|
+
[GraphEvents.ON_SUMMARIZE_START]: {
|
|
76
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
77
|
+
spies.onSummarizeStartSpy(data);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
[GraphEvents.ON_SUMMARIZE_COMPLETE]: {
|
|
81
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
82
|
+
spies.onSummarizeCompleteSpy(data);
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const SUMMARY_RESPONSE =
|
|
89
|
+
'## Summary\nAgent A discussed math with the user. Key result: 42.';
|
|
90
|
+
|
|
91
|
+
const streamConfig = {
|
|
92
|
+
configurable: { thread_id: 'multi-agent-sum-test' },
|
|
93
|
+
recursionLimit: 50,
|
|
94
|
+
streamMode: 'values' as const,
|
|
95
|
+
version: 'v2' as const,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
describe('Multi-agent summarization', () => {
|
|
99
|
+
let getChatModelClassSpy: jest.SpyInstance;
|
|
100
|
+
const originalGetChatModelClass = providers.getChatModelClass;
|
|
101
|
+
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
getChatModelClassSpy = jest
|
|
104
|
+
.spyOn(providers, 'getChatModelClass')
|
|
105
|
+
.mockImplementation(((provider: Providers) => {
|
|
106
|
+
if (provider === Providers.OPENAI) {
|
|
107
|
+
return class extends FakeListChatModel {
|
|
108
|
+
constructor(_options: any) {
|
|
109
|
+
super({ responses: [SUMMARY_RESPONSE] });
|
|
110
|
+
}
|
|
111
|
+
} as any;
|
|
112
|
+
}
|
|
113
|
+
return originalGetChatModelClass(provider);
|
|
114
|
+
}) as typeof providers.getChatModelClass);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
getChatModelClassSpy.mockRestore();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('sequential A → B: Agent A summarizes independently, Agent B sees full history', async () => {
|
|
122
|
+
const spies = createSpies();
|
|
123
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
124
|
+
const { aggregateContent } = createContentAggregator();
|
|
125
|
+
const tokenCounter = await createTokenCounter();
|
|
126
|
+
|
|
127
|
+
const padding = ' the quick brown fox jumps over the lazy dog'.repeat(30);
|
|
128
|
+
const conversationHistory: BaseMessage[] = [
|
|
129
|
+
new HumanMessage(`What is the meaning of life?${padding}`),
|
|
130
|
+
new AIMessage(`The answer is 42.${padding}`),
|
|
131
|
+
new HumanMessage(`Can you explain more?${padding}`),
|
|
132
|
+
new AIMessage(`It comes from a famous book.${padding}`),
|
|
133
|
+
new HumanMessage(`Which book?${padding}`),
|
|
134
|
+
new AIMessage(`The Hitchhiker's Guide to the Galaxy.${padding}`),
|
|
135
|
+
new HumanMessage('Now pass this to Agent B for confirmation.'),
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const agents: t.AgentInputs[] = [
|
|
139
|
+
{
|
|
140
|
+
agentId: 'agent_a',
|
|
141
|
+
provider: Providers.OPENAI,
|
|
142
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
143
|
+
instructions:
|
|
144
|
+
'You are Agent A. Process the request and pass to Agent B.',
|
|
145
|
+
maxContextTokens: 800,
|
|
146
|
+
summarizationEnabled: true,
|
|
147
|
+
summarizationConfig: {
|
|
148
|
+
provider: Providers.OPENAI,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
agentId: 'agent_b',
|
|
153
|
+
provider: Providers.OPENAI,
|
|
154
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
155
|
+
instructions: 'You are Agent B. Confirm the result from Agent A.',
|
|
156
|
+
maxContextTokens: 4000,
|
|
157
|
+
summarizationEnabled: false,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const edges: t.GraphEdge[] = [
|
|
162
|
+
{ from: 'agent_a', to: 'agent_b', edgeType: 'direct' },
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const indexTokenCountMap: Record<string, number> = {};
|
|
166
|
+
for (let i = 0; i < conversationHistory.length; i++) {
|
|
167
|
+
indexTokenCountMap[i] = tokenCounter(conversationHistory[i]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const run = await Run.create<t.IState>({
|
|
171
|
+
runId: `multi-sum-${Date.now()}`,
|
|
172
|
+
graphConfig: {
|
|
173
|
+
type: 'multi-agent',
|
|
174
|
+
agents,
|
|
175
|
+
edges,
|
|
176
|
+
},
|
|
177
|
+
returnContent: true,
|
|
178
|
+
customHandlers: buildHandlers(collectedUsage, aggregateContent, spies),
|
|
179
|
+
tokenCounter,
|
|
180
|
+
indexTokenCountMap,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
184
|
+
|
|
185
|
+
// Agent A should have triggered summarization due to tight context (200 tokens)
|
|
186
|
+
const startCalls = spies.onSummarizeStartSpy.mock.calls;
|
|
187
|
+
const completeCalls = spies.onSummarizeCompleteSpy.mock.calls;
|
|
188
|
+
|
|
189
|
+
console.log(
|
|
190
|
+
` Summarization events: start=${startCalls.length}, complete=${completeCalls.length}`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(startCalls.length).toBeGreaterThan(0);
|
|
194
|
+
const startPayload = startCalls[0][0] as t.SummarizeStartEvent;
|
|
195
|
+
expect(startPayload.agentId).toBe('agent_a');
|
|
196
|
+
|
|
197
|
+
expect(completeCalls.length).toBeGreaterThan(0);
|
|
198
|
+
const completePayload = completeCalls[0][0] as t.SummarizeCompleteEvent;
|
|
199
|
+
expect(completePayload.agentId).toBe('agent_a');
|
|
200
|
+
expect(completePayload.summary).toBeDefined();
|
|
201
|
+
const summaryText = getSummaryText(completePayload.summary);
|
|
202
|
+
expect(summaryText.length).toBeGreaterThan(0);
|
|
203
|
+
console.log(` Agent A summary: "${summaryText.substring(0, 100)}"`);
|
|
204
|
+
|
|
205
|
+
const finalMessages = run.getRunMessages();
|
|
206
|
+
expect(finalMessages).toBeDefined();
|
|
207
|
+
console.log(` Final messages: ${finalMessages?.length ?? 0}`);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('each agent has independent summarization state', async () => {
|
|
211
|
+
const spies = createSpies();
|
|
212
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
213
|
+
const { aggregateContent } = createContentAggregator();
|
|
214
|
+
const tokenCounter = await createTokenCounter();
|
|
215
|
+
|
|
216
|
+
const padding = ' the quick brown fox jumps over the lazy dog'.repeat(30);
|
|
217
|
+
const conversationHistory: BaseMessage[] = [
|
|
218
|
+
new HumanMessage(`Question one about math${padding}`),
|
|
219
|
+
new AIMessage(`Math answer one${padding}`),
|
|
220
|
+
new HumanMessage(`Question two about science${padding}`),
|
|
221
|
+
new AIMessage(`Science answer two${padding}`),
|
|
222
|
+
new HumanMessage(`Question three about history${padding}`),
|
|
223
|
+
new AIMessage(`History answer three${padding}`),
|
|
224
|
+
new HumanMessage('Summarize everything'),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const indexTokenCountMap: Record<string, number> = {};
|
|
228
|
+
for (let i = 0; i < conversationHistory.length; i++) {
|
|
229
|
+
indexTokenCountMap[i] = tokenCounter(conversationHistory[i]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const agents: t.AgentInputs[] = [
|
|
233
|
+
{
|
|
234
|
+
agentId: 'tight_agent_a',
|
|
235
|
+
provider: Providers.OPENAI,
|
|
236
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
237
|
+
instructions: 'You are Agent A with tight context.',
|
|
238
|
+
maxContextTokens: 800,
|
|
239
|
+
summarizationEnabled: true,
|
|
240
|
+
summarizationConfig: {
|
|
241
|
+
provider: Providers.OPENAI,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
agentId: 'tight_agent_b',
|
|
246
|
+
provider: Providers.OPENAI,
|
|
247
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
248
|
+
instructions: 'You are Agent B with tight context.',
|
|
249
|
+
maxContextTokens: 800,
|
|
250
|
+
summarizationEnabled: true,
|
|
251
|
+
summarizationConfig: {
|
|
252
|
+
provider: Providers.OPENAI,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
const edges: t.GraphEdge[] = [
|
|
258
|
+
{ from: 'tight_agent_a', to: 'tight_agent_b', edgeType: 'direct' },
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
const run = await Run.create<t.IState>({
|
|
262
|
+
runId: `multi-independent-${Date.now()}`,
|
|
263
|
+
graphConfig: {
|
|
264
|
+
type: 'multi-agent',
|
|
265
|
+
agents,
|
|
266
|
+
edges,
|
|
267
|
+
},
|
|
268
|
+
returnContent: true,
|
|
269
|
+
customHandlers: buildHandlers(collectedUsage, aggregateContent, spies),
|
|
270
|
+
tokenCounter,
|
|
271
|
+
indexTokenCountMap,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
let error: Error | undefined;
|
|
275
|
+
try {
|
|
276
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
error = err as Error;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const starts = spies.onSummarizeStartSpy.mock.calls;
|
|
282
|
+
const completes = spies.onSummarizeCompleteSpy.mock.calls;
|
|
283
|
+
console.log(
|
|
284
|
+
` Summarization: start=${starts.length}, complete=${completes.length}`
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (error) {
|
|
288
|
+
console.log(` Error (acceptable): ${error.message.substring(0, 100)}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// In a sequential A→B flow, agent_a summarizes the large initial history.
|
|
292
|
+
// After compaction, agent_b receives the compressed state which typically
|
|
293
|
+
// fits within budget, so agent_b may not trigger. Verify at least agent_a
|
|
294
|
+
// fires and that every event carries the correct agentId.
|
|
295
|
+
expect(starts.length).toBeGreaterThanOrEqual(1);
|
|
296
|
+
for (const call of starts) {
|
|
297
|
+
const payload = call[0] as t.SummarizeStartEvent;
|
|
298
|
+
expect(['tight_agent_a', 'tight_agent_b']).toContain(payload.agentId);
|
|
299
|
+
}
|
|
300
|
+
console.log(
|
|
301
|
+
` Agents summarized: ${starts.map((c: unknown[]) => (c[0] as t.SummarizeStartEvent).agentId).join(', ')}`
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('agent with large context does not summarize while tight agent does', async () => {
|
|
306
|
+
const spies = createSpies();
|
|
307
|
+
const collectedUsage: UsageMetadata[] = [];
|
|
308
|
+
const { aggregateContent } = createContentAggregator();
|
|
309
|
+
const tokenCounter = await createTokenCounter();
|
|
310
|
+
|
|
311
|
+
const padding = ' the quick brown fox jumps over the lazy dog'.repeat(30);
|
|
312
|
+
const conversationHistory: BaseMessage[] = [
|
|
313
|
+
new HumanMessage(`First message${padding}`),
|
|
314
|
+
new AIMessage(`First reply${padding}`),
|
|
315
|
+
new HumanMessage(`Second message${padding}`),
|
|
316
|
+
new AIMessage(`Second reply${padding}`),
|
|
317
|
+
new HumanMessage(`Third message${padding}`),
|
|
318
|
+
new AIMessage(`Third reply${padding}`),
|
|
319
|
+
new HumanMessage('Process this'),
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const indexTokenCountMap: Record<string, number> = {};
|
|
323
|
+
for (let i = 0; i < conversationHistory.length; i++) {
|
|
324
|
+
indexTokenCountMap[i] = tokenCounter(conversationHistory[i]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const agents: t.AgentInputs[] = [
|
|
328
|
+
{
|
|
329
|
+
agentId: 'tight_agent',
|
|
330
|
+
provider: Providers.OPENAI,
|
|
331
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
332
|
+
instructions: 'You are the tight context agent.',
|
|
333
|
+
maxContextTokens: 800,
|
|
334
|
+
summarizationEnabled: true,
|
|
335
|
+
summarizationConfig: {
|
|
336
|
+
provider: Providers.OPENAI,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
agentId: 'large_agent',
|
|
341
|
+
provider: Providers.OPENAI,
|
|
342
|
+
clientOptions: getLLMConfig(Providers.OPENAI),
|
|
343
|
+
instructions: 'You are the large context agent.',
|
|
344
|
+
maxContextTokens: 100_000,
|
|
345
|
+
summarizationEnabled: true,
|
|
346
|
+
summarizationConfig: {
|
|
347
|
+
provider: Providers.OPENAI,
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
const edges: t.GraphEdge[] = [
|
|
353
|
+
{ from: 'tight_agent', to: 'large_agent', edgeType: 'direct' },
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const run = await Run.create<t.IState>({
|
|
357
|
+
runId: `multi-mixed-${Date.now()}`,
|
|
358
|
+
graphConfig: {
|
|
359
|
+
type: 'multi-agent',
|
|
360
|
+
agents,
|
|
361
|
+
edges,
|
|
362
|
+
},
|
|
363
|
+
returnContent: true,
|
|
364
|
+
customHandlers: buildHandlers(collectedUsage, aggregateContent, spies),
|
|
365
|
+
tokenCounter,
|
|
366
|
+
indexTokenCountMap,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
let error: Error | undefined;
|
|
370
|
+
try {
|
|
371
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
error = err as Error;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const starts = spies.onSummarizeStartSpy.mock.calls;
|
|
377
|
+
console.log(` Summarization events: ${starts.length}`);
|
|
378
|
+
|
|
379
|
+
if (error) {
|
|
380
|
+
console.log(` Error: ${error.message.substring(0, 100)}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
expect(starts.length).toBeGreaterThan(0);
|
|
384
|
+
for (const call of starts) {
|
|
385
|
+
const payload = call[0] as t.SummarizeStartEvent;
|
|
386
|
+
expect(payload.agentId).toBe('tight_agent');
|
|
387
|
+
console.log(` Summarization from: ${payload.agentId}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const largeAgentStarts = starts.filter(
|
|
391
|
+
(call: unknown[]) =>
|
|
392
|
+
(call[0] as t.SummarizeStartEvent).agentId === 'large_agent'
|
|
393
|
+
);
|
|
394
|
+
expect(largeAgentStarts.length).toBe(0);
|
|
395
|
+
});
|
|
396
|
+
});
|