@librechat/agents 3.1.39 → 3.1.41
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 +20 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +4 -4
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +68 -17
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +6 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +20 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +4 -4
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +68 -17
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +6 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +2 -0
- package/package.json +2 -2
- package/src/agents/AgentContext.ts +22 -1
- package/src/agents/__tests__/AgentContext.test.ts +25 -4
- package/src/graphs/MultiAgentGraph.ts +6 -4
- package/src/messages/format.ts +75 -18
- package/src/messages/formatAgentMessages.test.ts +993 -4
- package/src/run.ts +7 -1
- package/src/specs/custom-event-await.test.ts +215 -0
package/src/run.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CallbackHandler } from '@langfuse/langchain';
|
|
|
4
4
|
import { PromptTemplate } from '@langchain/core/prompts';
|
|
5
5
|
import { RunnableLambda } from '@langchain/core/runnables';
|
|
6
6
|
import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
|
|
7
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
7
8
|
import type {
|
|
8
9
|
MessageContentComplex,
|
|
9
10
|
BaseMessage,
|
|
@@ -240,9 +241,14 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
240
241
|
? this.getCallbacks(streamOptions.callbacks)
|
|
241
242
|
: [];
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
const customHandler = BaseCallbackHandler.fromMethods({
|
|
244
245
|
[Callback.CUSTOM_EVENT]: customEventCallback,
|
|
245
246
|
});
|
|
247
|
+
customHandler.awaitHandlers = true;
|
|
248
|
+
|
|
249
|
+
config.callbacks = baseCallbacks
|
|
250
|
+
.concat(streamCallbacks)
|
|
251
|
+
.concat(customHandler);
|
|
246
252
|
|
|
247
253
|
if (
|
|
248
254
|
isPresent(process.env.LANGFUSE_SECRET_KEY) &&
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
2
|
+
import type * as t from '@/types';
|
|
3
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
4
|
+
import { ContentTypes, GraphEvents, Providers } from '@/common';
|
|
5
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
6
|
+
import { Run } from '@/run';
|
|
7
|
+
|
|
8
|
+
describe('Custom event handler awaitHandlers behavior', () => {
|
|
9
|
+
jest.setTimeout(15000);
|
|
10
|
+
|
|
11
|
+
const llmConfig: t.LLMConfig = {
|
|
12
|
+
provider: Providers.OPENAI,
|
|
13
|
+
streaming: true,
|
|
14
|
+
streamUsage: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const config = {
|
|
18
|
+
configurable: {
|
|
19
|
+
thread_id: 'test-thread',
|
|
20
|
+
},
|
|
21
|
+
streamMode: 'values' as const,
|
|
22
|
+
version: 'v2' as const,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
it('should fully aggregate all content before processStream returns', async () => {
|
|
26
|
+
const longResponse =
|
|
27
|
+
'The quick brown fox jumps over the lazy dog and then runs across the field to find shelter from the rain';
|
|
28
|
+
|
|
29
|
+
let aggregateCallCount = 0;
|
|
30
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
31
|
+
|
|
32
|
+
const wrappedAggregate: t.ContentAggregator = (params) => {
|
|
33
|
+
aggregateCallCount++;
|
|
34
|
+
aggregateContent(params);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let messageDeltaCount = 0;
|
|
38
|
+
|
|
39
|
+
const customHandlers: Record<string | GraphEvents, t.EventHandler> = {
|
|
40
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
41
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
42
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
43
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
44
|
+
handle: (
|
|
45
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
46
|
+
data: t.StreamEventData
|
|
47
|
+
) => {
|
|
48
|
+
wrappedAggregate({
|
|
49
|
+
event,
|
|
50
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
55
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData) => {
|
|
56
|
+
wrappedAggregate({ event, data: data as t.RunStep });
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
60
|
+
handle: (
|
|
61
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
62
|
+
data: t.StreamEventData
|
|
63
|
+
) => {
|
|
64
|
+
wrappedAggregate({ event, data: data as t.RunStepDeltaEvent });
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
68
|
+
handle: async (
|
|
69
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
70
|
+
data: t.StreamEventData
|
|
71
|
+
) => {
|
|
72
|
+
messageDeltaCount++;
|
|
73
|
+
wrappedAggregate({ event, data: data as t.MessageDeltaEvent });
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
77
|
+
handle: (
|
|
78
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
79
|
+
data: t.StreamEventData
|
|
80
|
+
) => {
|
|
81
|
+
wrappedAggregate({ event, data: data as t.ReasoningDeltaEvent });
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const run = await Run.create<t.IState>({
|
|
87
|
+
runId: 'test-await-handlers',
|
|
88
|
+
graphConfig: {
|
|
89
|
+
type: 'standard',
|
|
90
|
+
llmConfig,
|
|
91
|
+
},
|
|
92
|
+
returnContent: true,
|
|
93
|
+
customHandlers,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
run.Graph!.overrideTestModel([longResponse]);
|
|
97
|
+
|
|
98
|
+
const inputs = { messages: [new HumanMessage('hello')] };
|
|
99
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
100
|
+
|
|
101
|
+
expect(finalContentParts).toBeDefined();
|
|
102
|
+
expect(finalContentParts!.length).toBeGreaterThan(0);
|
|
103
|
+
|
|
104
|
+
expect(messageDeltaCount).toBeGreaterThan(0);
|
|
105
|
+
expect(aggregateCallCount).toBeGreaterThan(0);
|
|
106
|
+
|
|
107
|
+
const typedParts = contentParts as t.MessageContentComplex[];
|
|
108
|
+
const textParts = typedParts.filter(
|
|
109
|
+
(p: t.MessageContentComplex | undefined) =>
|
|
110
|
+
p !== undefined && p.type === ContentTypes.TEXT
|
|
111
|
+
);
|
|
112
|
+
expect(textParts.length).toBeGreaterThan(0);
|
|
113
|
+
|
|
114
|
+
const aggregatedText = textParts
|
|
115
|
+
.map(
|
|
116
|
+
(p) =>
|
|
117
|
+
(p as { type: string; [ContentTypes.TEXT]: string })[
|
|
118
|
+
ContentTypes.TEXT
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
.join('');
|
|
122
|
+
expect(aggregatedText).toBe(longResponse);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should aggregate content from async handlers before processStream returns', async () => {
|
|
126
|
+
const response =
|
|
127
|
+
'This is a test of async handler aggregation with multiple tokens';
|
|
128
|
+
|
|
129
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
130
|
+
|
|
131
|
+
let asyncHandlerCompletions = 0;
|
|
132
|
+
|
|
133
|
+
const customHandlers: Record<string | GraphEvents, t.EventHandler> = {
|
|
134
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
135
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
136
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
137
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
138
|
+
handle: (
|
|
139
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
140
|
+
data: t.StreamEventData
|
|
141
|
+
) => {
|
|
142
|
+
aggregateContent({
|
|
143
|
+
event,
|
|
144
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
149
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData) => {
|
|
150
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
154
|
+
handle: (
|
|
155
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
156
|
+
data: t.StreamEventData
|
|
157
|
+
) => {
|
|
158
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
162
|
+
handle: async (
|
|
163
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
164
|
+
data: t.StreamEventData
|
|
165
|
+
) => {
|
|
166
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 5));
|
|
167
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
168
|
+
asyncHandlerCompletions++;
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
[GraphEvents.ON_REASONING_DELTA]: {
|
|
172
|
+
handle: (
|
|
173
|
+
event: GraphEvents.ON_REASONING_DELTA,
|
|
174
|
+
data: t.StreamEventData
|
|
175
|
+
) => {
|
|
176
|
+
aggregateContent({ event, data: data as t.ReasoningDeltaEvent });
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const run = await Run.create<t.IState>({
|
|
182
|
+
runId: 'test-async-handlers',
|
|
183
|
+
graphConfig: {
|
|
184
|
+
type: 'standard',
|
|
185
|
+
llmConfig,
|
|
186
|
+
},
|
|
187
|
+
returnContent: true,
|
|
188
|
+
customHandlers,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
run.Graph!.overrideTestModel([response]);
|
|
192
|
+
|
|
193
|
+
const inputs = { messages: [new HumanMessage('hello')] };
|
|
194
|
+
await run.processStream(inputs, config);
|
|
195
|
+
|
|
196
|
+
expect(asyncHandlerCompletions).toBeGreaterThan(0);
|
|
197
|
+
|
|
198
|
+
const typedParts = contentParts as t.MessageContentComplex[];
|
|
199
|
+
const textParts = typedParts.filter(
|
|
200
|
+
(p: t.MessageContentComplex | undefined) =>
|
|
201
|
+
p !== undefined && p.type === ContentTypes.TEXT
|
|
202
|
+
);
|
|
203
|
+
expect(textParts.length).toBeGreaterThan(0);
|
|
204
|
+
|
|
205
|
+
const aggregatedText = textParts
|
|
206
|
+
.map(
|
|
207
|
+
(p) =>
|
|
208
|
+
(p as { type: string; [ContentTypes.TEXT]: string })[
|
|
209
|
+
ContentTypes.TEXT
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
.join('');
|
|
213
|
+
expect(aggregatedText).toBe(response);
|
|
214
|
+
});
|
|
215
|
+
});
|