@librechat/agents 2.4.31 → 2.4.34
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/events.cjs +3 -3
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +2 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +5 -2
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/ids.cjs +23 -0
- package/dist/cjs/messages/ids.cjs.map +1 -0
- package/dist/cjs/splitStream.cjs +2 -1
- package/dist/cjs/splitStream.cjs.map +1 -1
- package/dist/cjs/stream.cjs +87 -154
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +14 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +144 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -0
- package/dist/cjs/tools/search/content.cjs +140 -0
- package/dist/cjs/tools/search/content.cjs.map +1 -0
- package/dist/cjs/tools/search/firecrawl.cjs +23 -41
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs +161 -74
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/highlights.cjs +64 -12
- package/dist/cjs/tools/search/highlights.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +35 -50
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +70 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +153 -69
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +247 -58
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +66 -0
- package/dist/cjs/tools/search/utils.cjs.map +1 -0
- package/dist/esm/events.mjs +1 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +2 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/ids.mjs +21 -0
- package/dist/esm/messages/ids.mjs.map +1 -0
- package/dist/esm/splitStream.mjs +2 -1
- package/dist/esm/splitStream.mjs.map +1 -1
- package/dist/esm/stream.mjs +87 -152
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +14 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +141 -0
- package/dist/esm/tools/handlers.mjs.map +1 -0
- package/dist/esm/tools/search/content.mjs +119 -0
- package/dist/esm/tools/search/content.mjs.map +1 -0
- package/dist/esm/tools/search/firecrawl.mjs +24 -41
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs +161 -74
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/highlights.mjs +64 -12
- package/dist/esm/tools/search/highlights.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +35 -50
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +61 -0
- package/dist/esm/tools/search/schema.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +153 -69
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +246 -57
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +61 -0
- package/dist/esm/tools/search/utils.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messages/ids.d.ts +3 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/stream.d.ts +0 -8
- package/dist/types/tools/ToolNode.d.ts +6 -0
- package/dist/types/tools/example.d.ts +23 -3
- package/dist/types/tools/handlers.d.ts +8 -0
- package/dist/types/tools/search/content.d.ts +4 -0
- package/dist/types/tools/search/firecrawl.d.ts +7 -86
- package/dist/types/tools/search/format.d.ts +4 -1
- package/dist/types/tools/search/highlights.d.ts +1 -1
- package/dist/types/tools/search/rerankers.d.ts +8 -5
- package/dist/types/tools/search/schema.d.ts +16 -0
- package/dist/types/tools/search/search.d.ts +2 -2
- package/dist/types/tools/search/test.d.ts +1 -0
- package/dist/types/tools/search/tool.d.ts +25 -4
- package/dist/types/tools/search/types.d.ts +443 -53
- package/dist/types/tools/search/utils.d.ts +10 -0
- package/package.json +9 -7
- package/src/events.ts +49 -15
- package/src/graphs/Graph.ts +6 -2
- package/src/index.ts +1 -0
- package/src/messages/ids.ts +26 -0
- package/src/messages/index.ts +1 -0
- package/src/scripts/search.ts +8 -3
- package/src/splitStream.test.ts +132 -71
- package/src/splitStream.ts +2 -1
- package/src/stream.ts +94 -183
- package/src/tools/ToolNode.ts +37 -14
- package/src/tools/handlers.ts +167 -0
- package/src/tools/search/content.test.ts +173 -0
- package/src/tools/search/content.ts +147 -0
- package/src/tools/search/firecrawl.ts +36 -148
- package/src/tools/search/format.ts +205 -74
- package/src/tools/search/highlights.ts +99 -16
- package/src/tools/search/output.md +2775 -0
- package/src/tools/search/rerankers.ts +50 -62
- package/src/tools/search/schema.ts +63 -0
- package/src/tools/search/search.ts +232 -116
- package/src/tools/search/test.html +884 -0
- package/src/tools/search/test.md +643 -0
- package/src/tools/search/test.ts +159 -0
- package/src/tools/search/tool.ts +363 -87
- package/src/tools/search/types.ts +503 -61
- package/src/tools/search/utils.ts +79 -0
- package/src/utils/llmConfig.ts +1 -1
package/src/events.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
// src/events.ts
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
UsageMetadata,
|
|
5
|
+
BaseMessageFields,
|
|
6
|
+
} from '@langchain/core/messages';
|
|
4
7
|
import type { Graph } from '@/graphs';
|
|
5
8
|
import type * as t from '@/types';
|
|
6
|
-
import { handleToolCalls } from '@/
|
|
9
|
+
import { handleToolCalls } from '@/tools/handlers';
|
|
7
10
|
import { Providers } from '@/common';
|
|
8
11
|
|
|
9
12
|
export class HandlerRegistry {
|
|
@@ -27,7 +30,12 @@ export class ModelEndHandler implements t.EventHandler {
|
|
|
27
30
|
this.collectedUsage = collectedUsage;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
handle(
|
|
33
|
+
handle(
|
|
34
|
+
event: string,
|
|
35
|
+
data: t.ModelEndData,
|
|
36
|
+
metadata?: Record<string, unknown>,
|
|
37
|
+
graph?: Graph
|
|
38
|
+
): void {
|
|
31
39
|
if (!graph || !metadata) {
|
|
32
40
|
console.warn(`Graph or metadata not found in ${event} event`);
|
|
33
41
|
return;
|
|
@@ -43,9 +51,12 @@ export class ModelEndHandler implements t.EventHandler {
|
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
console.log(`====== ${event.toUpperCase()} ======`);
|
|
46
|
-
console.dir(
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
console.dir(
|
|
55
|
+
{
|
|
56
|
+
usage,
|
|
57
|
+
},
|
|
58
|
+
{ depth: null }
|
|
59
|
+
);
|
|
49
60
|
|
|
50
61
|
if (metadata.provider !== Providers.GOOGLE) {
|
|
51
62
|
return;
|
|
@@ -60,7 +71,12 @@ export class ToolEndHandler implements t.EventHandler {
|
|
|
60
71
|
constructor(callback?: t.ToolEndCallback) {
|
|
61
72
|
this.callback = callback;
|
|
62
73
|
}
|
|
63
|
-
handle(
|
|
74
|
+
handle(
|
|
75
|
+
event: string,
|
|
76
|
+
data: t.StreamEventData | undefined,
|
|
77
|
+
metadata?: Record<string, unknown>,
|
|
78
|
+
graph?: Graph
|
|
79
|
+
): void {
|
|
64
80
|
if (!graph || !metadata) {
|
|
65
81
|
console.warn(`Graph or metadata not found in ${event} event`);
|
|
66
82
|
return;
|
|
@@ -74,14 +90,17 @@ export class ToolEndHandler implements t.EventHandler {
|
|
|
74
90
|
|
|
75
91
|
this.callback?.(toolEndData, metadata);
|
|
76
92
|
|
|
77
|
-
graph.handleToolCallCompleted(
|
|
93
|
+
graph.handleToolCallCompleted(
|
|
94
|
+
{ input: toolEndData.input, output: toolEndData.output },
|
|
95
|
+
metadata
|
|
96
|
+
);
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
export class TestLLMStreamHandler implements t.EventHandler {
|
|
82
101
|
handle(event: string, data: t.StreamEventData | undefined): void {
|
|
83
102
|
const chunk = data?.chunk;
|
|
84
|
-
const
|
|
103
|
+
const isMessageChunk = !!(chunk && 'message' in chunk);
|
|
85
104
|
const msg = isMessageChunk ? chunk.message : undefined;
|
|
86
105
|
if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {
|
|
87
106
|
console.log(msg.tool_call_chunks);
|
|
@@ -116,11 +135,17 @@ export class TestChatStreamHandler implements t.EventHandler {
|
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
export class LLMStreamHandler implements t.EventHandler {
|
|
119
|
-
handle(
|
|
138
|
+
handle(
|
|
139
|
+
event: string,
|
|
140
|
+
data: t.StreamEventData | undefined,
|
|
141
|
+
metadata?: Record<string, unknown>
|
|
142
|
+
): void {
|
|
120
143
|
const chunk = data?.chunk;
|
|
121
|
-
const
|
|
144
|
+
const isMessageChunk = !!(chunk && 'message' in chunk);
|
|
122
145
|
const msg = isMessageChunk && chunk.message;
|
|
123
|
-
if (metadata) {
|
|
146
|
+
if (metadata) {
|
|
147
|
+
console.log(metadata);
|
|
148
|
+
}
|
|
124
149
|
if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {
|
|
125
150
|
console.log(msg.tool_call_chunks);
|
|
126
151
|
} else if (msg && msg.content) {
|
|
@@ -133,12 +158,21 @@ export class LLMStreamHandler implements t.EventHandler {
|
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
|
|
136
|
-
export const createMetadataAggregator = (
|
|
161
|
+
export const createMetadataAggregator = (
|
|
162
|
+
_collected?: Record<
|
|
163
|
+
string,
|
|
164
|
+
NonNullable<BaseMessageFields['response_metadata']>
|
|
165
|
+
>[]
|
|
166
|
+
): t.MetadataAggregatorResult => {
|
|
137
167
|
const collected = _collected || [];
|
|
138
168
|
|
|
139
169
|
const handleLLMEnd: t.HandleLLMEnd = (output) => {
|
|
140
170
|
const { generations } = output;
|
|
141
|
-
const lastMessageOutput = (
|
|
171
|
+
const lastMessageOutput = (
|
|
172
|
+
generations[generations.length - 1] as
|
|
173
|
+
| (t.StreamGeneration | undefined)[]
|
|
174
|
+
| undefined
|
|
175
|
+
)?.[0];
|
|
142
176
|
if (!lastMessageOutput) {
|
|
143
177
|
return;
|
|
144
178
|
}
|
|
@@ -149,4 +183,4 @@ export const createMetadataAggregator = (_collected?: Record<string, NonNullable
|
|
|
149
183
|
};
|
|
150
184
|
|
|
151
185
|
return { handleLLMEnd, collected };
|
|
152
|
-
};
|
|
186
|
+
};
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -105,7 +105,8 @@ export abstract class Graph<
|
|
|
105
105
|
lastToken?: string;
|
|
106
106
|
tokenTypeSwitch?: 'reasoning' | 'content';
|
|
107
107
|
reasoningKey: 'reasoning_content' | 'reasoning' = 'reasoning_content';
|
|
108
|
-
currentTokenType: ContentTypes.TEXT | ContentTypes.THINK =
|
|
108
|
+
currentTokenType: ContentTypes.TEXT | ContentTypes.THINK | 'think_and_text' =
|
|
109
|
+
ContentTypes.TEXT;
|
|
109
110
|
messageStepHasToolCalls: Map<string, boolean> = new Map();
|
|
110
111
|
messageIdsByStepKey: Map<string, string> = new Map();
|
|
111
112
|
prelimMessageIdsByStepKey: Map<string, string> = new Map();
|
|
@@ -303,7 +304,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, GraphNode> {
|
|
|
303
304
|
metadata.langgraph_step as number,
|
|
304
305
|
metadata.checkpoint_ns as string,
|
|
305
306
|
];
|
|
306
|
-
if (
|
|
307
|
+
if (
|
|
308
|
+
this.currentTokenType === ContentTypes.THINK ||
|
|
309
|
+
this.currentTokenType === 'think_and_text'
|
|
310
|
+
) {
|
|
307
311
|
keyList.push('reasoning');
|
|
308
312
|
}
|
|
309
313
|
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/stream.ts
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
import type { Graph } from '@/graphs';
|
|
4
|
+
import type * as t from '@/types';
|
|
5
|
+
|
|
6
|
+
export const getMessageId = (
|
|
7
|
+
stepKey: string,
|
|
8
|
+
graph: Graph<t.BaseGraphState>,
|
|
9
|
+
returnExistingId = false
|
|
10
|
+
): string | undefined => {
|
|
11
|
+
const messageId = graph.messageIdsByStepKey.get(stepKey);
|
|
12
|
+
if (messageId != null && messageId) {
|
|
13
|
+
return returnExistingId ? messageId : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const prelimMessageId = graph.prelimMessageIdsByStepKey.get(stepKey);
|
|
17
|
+
if (prelimMessageId != null && prelimMessageId) {
|
|
18
|
+
graph.prelimMessageIdsByStepKey.delete(stepKey);
|
|
19
|
+
graph.messageIdsByStepKey.set(stepKey, prelimMessageId);
|
|
20
|
+
return prelimMessageId;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const message_id = `msg_${nanoid()}`;
|
|
24
|
+
graph.messageIdsByStepKey.set(stepKey, message_id);
|
|
25
|
+
return message_id;
|
|
26
|
+
};
|
package/src/messages/index.ts
CHANGED
package/src/scripts/search.ts
CHANGED
|
@@ -86,7 +86,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
86
86
|
tools: [createSearchTool()],
|
|
87
87
|
instructions:
|
|
88
88
|
'You are a friendly AI assistant. Always address the user by their name.',
|
|
89
|
-
additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
|
|
89
|
+
// additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
|
|
90
90
|
},
|
|
91
91
|
returnContent: true,
|
|
92
92
|
customHandlers,
|
|
@@ -101,7 +101,7 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
101
101
|
version: 'v2' as const,
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
-
console.log('Test 1:
|
|
104
|
+
console.log('Test 1: Search query (search tool test)');
|
|
105
105
|
|
|
106
106
|
// const userMessage = `
|
|
107
107
|
// Make a search for the weather in ${location} today, which is ${currentDate}.
|
|
@@ -109,7 +109,12 @@ async function testStandardStreaming(): Promise<void> {
|
|
|
109
109
|
// Make sure to always refer to me by name, which is ${userName}.
|
|
110
110
|
// After giving me a thorough summary, tell me a joke about the weather forecast we went over.
|
|
111
111
|
// `;
|
|
112
|
-
const userMessage = 'Are massage guns good?';
|
|
112
|
+
// const userMessage = 'Are massage guns good?';
|
|
113
|
+
// const userMessage = 'What is functional programming?';
|
|
114
|
+
const userMessage = "Get me today's trending news.";
|
|
115
|
+
// const userMessage = "search recent italy earthquake volcano activity";
|
|
116
|
+
// const userMessage =
|
|
117
|
+
// "use 'Trump' as the exact search query and tell me what you find.";
|
|
113
118
|
|
|
114
119
|
conversationHistory.push(new HumanMessage(userMessage));
|
|
115
120
|
|
package/src/splitStream.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
import { MessageContentText } from '@langchain/core/messages';
|
|
3
3
|
import type * as t from '@/types';
|
|
4
|
-
import { GraphEvents
|
|
4
|
+
import { GraphEvents, StepTypes, ContentTypes } from '@/common';
|
|
5
5
|
import { createContentAggregator } from './stream';
|
|
6
6
|
import { SplitStreamHandler } from './splitStream';
|
|
7
7
|
import { createMockStream } from './mockStream';
|
|
@@ -87,7 +87,8 @@ End code.`;
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// Make the text longer and ensure it has clear breaking points
|
|
90
|
-
const longText =
|
|
90
|
+
const longText =
|
|
91
|
+
'This is the first sentence. And here is another sentence. And yet another one here. Finally one more.';
|
|
91
92
|
|
|
92
93
|
const stream = createMockStream({
|
|
93
94
|
text: longText,
|
|
@@ -171,7 +172,7 @@ describe('ContentAggregator with SplitStreamHandler', () => {
|
|
|
171
172
|
[GraphEvents.ON_RUN_STEP]: aggregateContent,
|
|
172
173
|
[GraphEvents.ON_MESSAGE_DELTA]: aggregateContent,
|
|
173
174
|
},
|
|
174
|
-
blockThreshold:
|
|
175
|
+
blockThreshold: 5,
|
|
175
176
|
});
|
|
176
177
|
|
|
177
178
|
const text = 'First sentence. Second sentence. Third sentence.';
|
|
@@ -181,8 +182,8 @@ describe('ContentAggregator with SplitStreamHandler', () => {
|
|
|
181
182
|
handler.handle(chunk);
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
expect(contentParts.length).toBeGreaterThan(
|
|
185
|
-
contentParts.forEach(part => {
|
|
185
|
+
expect(contentParts.length).toBeGreaterThan(0);
|
|
186
|
+
contentParts.forEach((part) => {
|
|
186
187
|
expect(part?.type).toBe(ContentTypes.TEXT);
|
|
187
188
|
if (part?.type === ContentTypes.TEXT) {
|
|
188
189
|
expect(typeof part.text).toBe('string');
|
|
@@ -191,8 +192,8 @@ describe('ContentAggregator with SplitStreamHandler', () => {
|
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
const fullText = contentParts
|
|
194
|
-
.filter(part => part?.type === ContentTypes.TEXT)
|
|
195
|
-
.map(part => (part?.type === ContentTypes.TEXT ? part.text : ''))
|
|
195
|
+
.filter((part) => part?.type === ContentTypes.TEXT)
|
|
196
|
+
.map((part) => (part?.type === ContentTypes.TEXT ? part.text : ''))
|
|
196
197
|
.join('');
|
|
197
198
|
expect(fullText).toBe(text);
|
|
198
199
|
});
|
|
@@ -218,8 +219,8 @@ describe('ContentAggregator with SplitStreamHandler', () => {
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
const texts = contentParts
|
|
221
|
-
.filter(part => part?.type === ContentTypes.TEXT)
|
|
222
|
-
.map(part => (part?.type === ContentTypes.TEXT ? part.text : ''));
|
|
222
|
+
.filter((part) => part?.type === ContentTypes.TEXT)
|
|
223
|
+
.map((part) => (part?.type === ContentTypes.TEXT ? part.text : ''));
|
|
223
224
|
|
|
224
225
|
expect(texts[0]).toContain('First');
|
|
225
226
|
expect(texts[texts.length - 1]).toContain('Third');
|
|
@@ -251,9 +252,9 @@ After code.`;
|
|
|
251
252
|
handler.handle(chunk);
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
const codeBlockPart = contentParts.find(
|
|
255
|
-
part
|
|
256
|
-
|
|
255
|
+
const codeBlockPart = contentParts.find(
|
|
256
|
+
(part) =>
|
|
257
|
+
part?.type === ContentTypes.TEXT && part.text.includes('```python')
|
|
257
258
|
);
|
|
258
259
|
|
|
259
260
|
expect(codeBlockPart).toBeDefined();
|
|
@@ -265,7 +266,8 @@ After code.`;
|
|
|
265
266
|
|
|
266
267
|
it('should properly map steps to their content', async () => {
|
|
267
268
|
const runId = nanoid();
|
|
268
|
-
const { contentParts, aggregateContent, stepMap } =
|
|
269
|
+
const { contentParts, aggregateContent, stepMap } =
|
|
270
|
+
createContentAggregator();
|
|
269
271
|
|
|
270
272
|
const handler = new SplitStreamHandler({
|
|
271
273
|
runId,
|
|
@@ -289,7 +291,9 @@ After code.`;
|
|
|
289
291
|
const stepContent = contentParts[currentIndex];
|
|
290
292
|
if (!stepContent && currentIndex > 0) {
|
|
291
293
|
const prevStepContent = contentParts[currentIndex - 1];
|
|
292
|
-
expect(
|
|
294
|
+
expect(
|
|
295
|
+
(prevStepContent as MessageContentText | undefined)?.text
|
|
296
|
+
).toEqual(text);
|
|
293
297
|
} else if (stepContent?.type === ContentTypes.TEXT) {
|
|
294
298
|
expect(stepContent.text.length).toBeGreaterThan(0);
|
|
295
299
|
}
|
|
@@ -297,7 +301,7 @@ After code.`;
|
|
|
297
301
|
|
|
298
302
|
contentParts.forEach((part, index) => {
|
|
299
303
|
const hasMatchingStep = Array.from(stepMap.values()).some(
|
|
300
|
-
step => step?.index === index
|
|
304
|
+
(step) => step?.index === index
|
|
301
305
|
);
|
|
302
306
|
expect(hasMatchingStep).toBe(true);
|
|
303
307
|
});
|
|
@@ -326,10 +330,12 @@ After code.`;
|
|
|
326
330
|
const letters = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
327
331
|
let letterIndex = 0;
|
|
328
332
|
|
|
329
|
-
contentParts.forEach(part => {
|
|
333
|
+
contentParts.forEach((part) => {
|
|
330
334
|
if (part?.type === ContentTypes.TEXT) {
|
|
331
|
-
while (
|
|
332
|
-
|
|
335
|
+
while (
|
|
336
|
+
letterIndex < letters.length &&
|
|
337
|
+
part.text.includes(letters[letterIndex]) === true
|
|
338
|
+
) {
|
|
333
339
|
letterIndex++;
|
|
334
340
|
}
|
|
335
341
|
}
|
|
@@ -351,7 +357,7 @@ describe('SplitStreamHandler with Reasoning Tokens', () => {
|
|
|
351
357
|
const handler = new SplitStreamHandler({
|
|
352
358
|
runId,
|
|
353
359
|
handlers: mockHandlers,
|
|
354
|
-
blockThreshold:
|
|
360
|
+
blockThreshold: 3,
|
|
355
361
|
});
|
|
356
362
|
|
|
357
363
|
const stream = createMockStream({
|
|
@@ -364,21 +370,30 @@ describe('SplitStreamHandler with Reasoning Tokens', () => {
|
|
|
364
370
|
handler.handle(chunk);
|
|
365
371
|
}
|
|
366
372
|
|
|
367
|
-
const runSteps = (mockHandlers[GraphEvents.ON_RUN_STEP] as jest.Mock).mock
|
|
368
|
-
|
|
369
|
-
const
|
|
373
|
+
const runSteps = (mockHandlers[GraphEvents.ON_RUN_STEP] as jest.Mock).mock
|
|
374
|
+
.calls;
|
|
375
|
+
const reasoningDeltas = (
|
|
376
|
+
mockHandlers[GraphEvents.ON_REASONING_DELTA] as jest.Mock
|
|
377
|
+
).mock.calls;
|
|
378
|
+
const messageDeltas = (
|
|
379
|
+
mockHandlers[GraphEvents.ON_MESSAGE_DELTA] as jest.Mock
|
|
380
|
+
).mock.calls;
|
|
370
381
|
|
|
371
382
|
// Both content types should create multiple blocks
|
|
372
|
-
expect(runSteps.length).toBeGreaterThan(
|
|
383
|
+
expect(runSteps.length).toBeGreaterThan(1);
|
|
373
384
|
expect(reasoningDeltas.length).toBeGreaterThan(0);
|
|
374
385
|
expect(messageDeltas.length).toBeGreaterThan(0);
|
|
375
386
|
|
|
376
387
|
// Verify splitting behavior for both types
|
|
377
388
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
378
|
-
const getStepTypes = (calls: any[]): string[] =>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
389
|
+
const getStepTypes = (calls: any[]): string[] =>
|
|
390
|
+
calls
|
|
391
|
+
.map(([{ data }]) =>
|
|
392
|
+
data.stepDetails?.type === StepTypes.MESSAGE_CREATION
|
|
393
|
+
? data.stepDetails.message_creation.message_id
|
|
394
|
+
: null
|
|
395
|
+
)
|
|
396
|
+
.filter(Boolean);
|
|
382
397
|
|
|
383
398
|
const messageSteps = getStepTypes(runSteps);
|
|
384
399
|
expect(new Set(messageSteps).size).toBeGreaterThan(1);
|
|
@@ -386,7 +401,8 @@ describe('SplitStreamHandler with Reasoning Tokens', () => {
|
|
|
386
401
|
|
|
387
402
|
it('should properly map steps to their reasoning content', async () => {
|
|
388
403
|
const runId = nanoid();
|
|
389
|
-
const { contentParts, aggregateContent, stepMap } =
|
|
404
|
+
const { contentParts, aggregateContent, stepMap } =
|
|
405
|
+
createContentAggregator();
|
|
390
406
|
|
|
391
407
|
const handler = new SplitStreamHandler({
|
|
392
408
|
runId,
|
|
@@ -403,7 +419,7 @@ describe('SplitStreamHandler with Reasoning Tokens', () => {
|
|
|
403
419
|
const stream = createMockStream({
|
|
404
420
|
text,
|
|
405
421
|
reasoningText,
|
|
406
|
-
streamRate: 0
|
|
422
|
+
streamRate: 0,
|
|
407
423
|
})();
|
|
408
424
|
|
|
409
425
|
for await (const chunk of stream) {
|
|
@@ -425,21 +441,21 @@ describe('SplitStreamHandler with Reasoning Tokens', () => {
|
|
|
425
441
|
|
|
426
442
|
// Verify at least one reasoning content part exists
|
|
427
443
|
const reasoningParts = contentParts.filter(
|
|
428
|
-
part => part?.type === ContentTypes.THINK
|
|
444
|
+
(part) => part?.type === ContentTypes.THINK
|
|
429
445
|
);
|
|
430
446
|
expect(reasoningParts.length).toBeGreaterThan(0);
|
|
431
447
|
|
|
432
448
|
// Verify the content order (reasoning should come before main content)
|
|
433
449
|
const contentTypes = contentParts
|
|
434
|
-
.filter(part => part !== undefined)
|
|
435
|
-
.map(part => part.type);
|
|
450
|
+
.filter((part) => part !== undefined)
|
|
451
|
+
.map((part) => part.type);
|
|
436
452
|
|
|
437
453
|
expect(contentTypes).toContain(ContentTypes.THINK);
|
|
438
454
|
expect(contentTypes).toContain(ContentTypes.TEXT);
|
|
439
455
|
|
|
440
456
|
// Verify the complete reasoning content is preserved
|
|
441
457
|
const fullReasoningText = reasoningParts
|
|
442
|
-
.map(part => (part?.type === ContentTypes.THINK ? part.think : ''))
|
|
458
|
+
.map((part) => (part?.type === ContentTypes.THINK ? part.think : ''))
|
|
443
459
|
.join('');
|
|
444
460
|
expect(fullReasoningText).toBe(reasoningText);
|
|
445
461
|
});
|
|
@@ -463,7 +479,8 @@ describe('SplitStreamHandler', () => {
|
|
|
463
479
|
},
|
|
464
480
|
});
|
|
465
481
|
|
|
466
|
-
const content =
|
|
482
|
+
const content =
|
|
483
|
+
'Here\'s some regular text. <think>Now I\'m thinking deeply about something important. This should all be reasoning.</think> Back to regular text.';
|
|
467
484
|
|
|
468
485
|
const stream = createMockStream({
|
|
469
486
|
text: content,
|
|
@@ -475,29 +492,49 @@ describe('SplitStreamHandler', () => {
|
|
|
475
492
|
}
|
|
476
493
|
|
|
477
494
|
// Check that content before <think> was handled as regular text
|
|
478
|
-
expect(
|
|
479
|
-
(event
|
|
480
|
-
|
|
495
|
+
expect(
|
|
496
|
+
messageDeltaEvents.some((event) =>
|
|
497
|
+
(
|
|
498
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
499
|
+
)?.text.includes('Here\'s')
|
|
500
|
+
)
|
|
501
|
+
).toBe(true);
|
|
481
502
|
|
|
482
503
|
// Check that <think> tag was handled as reasoning
|
|
483
|
-
expect(
|
|
484
|
-
(event
|
|
485
|
-
|
|
504
|
+
expect(
|
|
505
|
+
reasoningDeltaEvents.some((event) =>
|
|
506
|
+
(
|
|
507
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
508
|
+
)?.think.includes('<think>')
|
|
509
|
+
)
|
|
510
|
+
).toBe(true);
|
|
486
511
|
|
|
487
512
|
// Check that content inside <think> tags was handled as reasoning
|
|
488
|
-
expect(
|
|
489
|
-
(event
|
|
490
|
-
|
|
513
|
+
expect(
|
|
514
|
+
reasoningDeltaEvents.some((event) =>
|
|
515
|
+
(
|
|
516
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
517
|
+
)?.think.includes('thinking')
|
|
518
|
+
)
|
|
519
|
+
).toBe(true);
|
|
491
520
|
|
|
492
521
|
// Check that </think> tag was handled as reasoning
|
|
493
|
-
expect(
|
|
494
|
-
(event
|
|
495
|
-
|
|
522
|
+
expect(
|
|
523
|
+
reasoningDeltaEvents.some((event) =>
|
|
524
|
+
(
|
|
525
|
+
event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined
|
|
526
|
+
)?.think.includes('</think>')
|
|
527
|
+
)
|
|
528
|
+
).toBe(true);
|
|
496
529
|
|
|
497
530
|
// Check that content after </think> was handled as regular text
|
|
498
|
-
expect(
|
|
499
|
-
(event
|
|
500
|
-
|
|
531
|
+
expect(
|
|
532
|
+
messageDeltaEvents.some((event) =>
|
|
533
|
+
(
|
|
534
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
535
|
+
)?.text.includes('Back')
|
|
536
|
+
)
|
|
537
|
+
).toBe(true);
|
|
501
538
|
});
|
|
502
539
|
|
|
503
540
|
it('should ignore think tags inside code blocks', async () => {
|
|
@@ -517,7 +554,8 @@ describe('SplitStreamHandler', () => {
|
|
|
517
554
|
},
|
|
518
555
|
});
|
|
519
556
|
|
|
520
|
-
const content =
|
|
557
|
+
const content =
|
|
558
|
+
'Regular text. ```<think>This should stay as code</think>``` More text.';
|
|
521
559
|
|
|
522
560
|
const stream = createMockStream({
|
|
523
561
|
text: content,
|
|
@@ -529,9 +567,13 @@ describe('SplitStreamHandler', () => {
|
|
|
529
567
|
}
|
|
530
568
|
|
|
531
569
|
// Check that think tags inside code blocks were treated as regular text
|
|
532
|
-
expect(
|
|
533
|
-
(event
|
|
534
|
-
|
|
570
|
+
expect(
|
|
571
|
+
messageDeltaEvents.some((event) =>
|
|
572
|
+
(
|
|
573
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
574
|
+
)?.text.includes('Regular')
|
|
575
|
+
)
|
|
576
|
+
).toBe(true);
|
|
535
577
|
|
|
536
578
|
// Verify no reasoning events were generated
|
|
537
579
|
expect(reasoningDeltaEvents.length).toBe(0);
|
|
@@ -563,7 +605,8 @@ describe('SplitStreamHandler', () => {
|
|
|
563
605
|
},
|
|
564
606
|
});
|
|
565
607
|
|
|
566
|
-
const content =
|
|
608
|
+
const content =
|
|
609
|
+
'Here\'s some regular text. <think>Now I\'m thinking deeply about something important. This is a long thought that should be split into multiple parts. We want to ensure the splitting works correctly.</think> Back to regular text after thinking.';
|
|
567
610
|
|
|
568
611
|
const stream = createMockStream({
|
|
569
612
|
text: content,
|
|
@@ -578,13 +621,21 @@ describe('SplitStreamHandler', () => {
|
|
|
578
621
|
expect(runStepEvents.length).toBeGreaterThan(2);
|
|
579
622
|
|
|
580
623
|
// Check that content before <think> was handled as regular text
|
|
581
|
-
expect(
|
|
582
|
-
(event
|
|
583
|
-
|
|
624
|
+
expect(
|
|
625
|
+
messageDeltaEvents.some((event) =>
|
|
626
|
+
(
|
|
627
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
628
|
+
)?.text.includes('regular')
|
|
629
|
+
)
|
|
630
|
+
).toBe(true);
|
|
584
631
|
|
|
585
632
|
// Verify that reasoning content was split into multiple parts
|
|
586
633
|
const reasoningParts = reasoningDeltaEvents
|
|
587
|
-
.map(
|
|
634
|
+
.map(
|
|
635
|
+
(event) =>
|
|
636
|
+
(event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined)
|
|
637
|
+
?.think
|
|
638
|
+
)
|
|
588
639
|
.filter(Boolean);
|
|
589
640
|
expect(reasoningParts.length).toBeGreaterThan(1);
|
|
590
641
|
|
|
@@ -598,7 +649,7 @@ describe('SplitStreamHandler', () => {
|
|
|
598
649
|
// Check that each reasoning part maintains proper think context
|
|
599
650
|
let seenThinkOpen = false;
|
|
600
651
|
let seenThinkClose = false;
|
|
601
|
-
reasoningParts.forEach(part => {
|
|
652
|
+
reasoningParts.forEach((part) => {
|
|
602
653
|
if (part == null) return;
|
|
603
654
|
if (part.includes('<think>')) {
|
|
604
655
|
seenThinkOpen = true;
|
|
@@ -608,23 +659,33 @@ describe('SplitStreamHandler', () => {
|
|
|
608
659
|
}
|
|
609
660
|
// Middle parts should be handled as reasoning even without explicit think tags
|
|
610
661
|
if (!part.includes('<think>') && !part.includes('</think>')) {
|
|
611
|
-
expect(
|
|
612
|
-
(
|
|
613
|
-
|
|
662
|
+
expect(
|
|
663
|
+
reasoningDeltaEvents.some(
|
|
664
|
+
(event) =>
|
|
665
|
+
(event.delta.content?.[0] as t.ReasoningDeltaUpdate | undefined)
|
|
666
|
+
?.think === part
|
|
667
|
+
)
|
|
668
|
+
).toBe(true);
|
|
614
669
|
}
|
|
615
670
|
});
|
|
616
671
|
expect(seenThinkOpen).toBe(true);
|
|
617
672
|
expect(seenThinkClose).toBe(true);
|
|
618
673
|
|
|
619
674
|
// Check that content after </think> was handled as regular text
|
|
620
|
-
expect(
|
|
621
|
-
(event
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
675
|
+
expect(
|
|
676
|
+
messageDeltaEvents.some((event) =>
|
|
677
|
+
(
|
|
678
|
+
event.delta.content?.[0] as t.MessageDeltaUpdate | undefined
|
|
679
|
+
)?.text.includes('Back')
|
|
680
|
+
)
|
|
681
|
+
).toBe(true);
|
|
682
|
+
|
|
683
|
+
const thinkingBlocks = contentParts.filter(
|
|
684
|
+
(part) => part?.type === ContentTypes.THINK
|
|
626
685
|
);
|
|
627
|
-
expect(thinkingBlocks.length).
|
|
628
|
-
expect(
|
|
686
|
+
expect(thinkingBlocks.length).toBeGreaterThan(0);
|
|
687
|
+
expect(
|
|
688
|
+
(thinkingBlocks[0] as t.ReasoningContentText).think.startsWith('<think>')
|
|
689
|
+
).toBeTruthy();
|
|
629
690
|
});
|
|
630
|
-
});
|
|
691
|
+
});
|