@librechat/agents 3.1.39 → 3.1.40
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/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/run.mjs +6 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +2 -0
- package/package.json +1 -1
- 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/run.ts +7 -1
- package/src/specs/custom-event-await.test.ts +215 -0
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getCurrentTaskInput,
|
|
16
16
|
messagesStateReducer,
|
|
17
17
|
} from '@langchain/langgraph';
|
|
18
|
+
import type { LangGraphRunnableConfig } from '@langchain/langgraph';
|
|
18
19
|
import type { BaseMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
19
20
|
import type { ToolRunnableConfig } from '@langchain/core/tools';
|
|
20
21
|
import type * as t from '@/types';
|
|
@@ -745,7 +746,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
745
746
|
|
|
746
747
|
/** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
|
|
747
748
|
const agentWrapper = async (
|
|
748
|
-
state: t.MultiAgentGraphState
|
|
749
|
+
state: t.MultiAgentGraphState,
|
|
750
|
+
config?: LangGraphRunnableConfig
|
|
749
751
|
): Promise<t.MultiAgentGraphState | Command> => {
|
|
750
752
|
let result: t.MultiAgentGraphState;
|
|
751
753
|
|
|
@@ -817,7 +819,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
817
819
|
...state,
|
|
818
820
|
messages: messagesForAgent,
|
|
819
821
|
};
|
|
820
|
-
result = await agentSubgraph.invoke(transformedState);
|
|
822
|
+
result = await agentSubgraph.invoke(transformedState, config);
|
|
821
823
|
result = {
|
|
822
824
|
...result,
|
|
823
825
|
agentMessages: [],
|
|
@@ -863,14 +865,14 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
863
865
|
...state,
|
|
864
866
|
messages: state.agentMessages,
|
|
865
867
|
};
|
|
866
|
-
result = await agentSubgraph.invoke(transformedState);
|
|
868
|
+
result = await agentSubgraph.invoke(transformedState, config);
|
|
867
869
|
result = {
|
|
868
870
|
...result,
|
|
869
871
|
/** Clear agentMessages for next agent */
|
|
870
872
|
agentMessages: [],
|
|
871
873
|
};
|
|
872
874
|
} else {
|
|
873
|
-
result = await agentSubgraph.invoke(state);
|
|
875
|
+
result = await agentSubgraph.invoke(state, config);
|
|
874
876
|
}
|
|
875
877
|
|
|
876
878
|
/** If agent has both handoff and direct edges, use Command for exclusive routing */
|
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
|
+
});
|