@librechat/agents 2.4.311 → 2.4.313
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/graphs/Graph.cjs +2 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/splitStream.cjs +2 -1
- package/dist/cjs/splitStream.cjs.map +1 -1
- package/dist/cjs/stream.cjs +80 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +2 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/splitStream.mjs +2 -1
- package/dist/esm/splitStream.mjs.map +1 -1
- package/dist/esm/stream.mjs +80 -0
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/package.json +1 -1
- package/src/graphs/Graph.ts +6 -2
- package/src/splitStream.test.ts +132 -71
- package/src/splitStream.ts +2 -1
- package/src/stream.ts +93 -0
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
|
+
});
|
package/src/splitStream.ts
CHANGED
package/src/stream.ts
CHANGED
|
@@ -6,6 +6,57 @@ import type { Graph } from '@/graphs';
|
|
|
6
6
|
import type * as t from '@/types';
|
|
7
7
|
import { StepTypes, ContentTypes, GraphEvents, ToolCallTypes } from '@/common';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Parses content to extract thinking sections enclosed in <think> tags using string operations
|
|
11
|
+
* @param content The content to parse
|
|
12
|
+
* @returns An object with separated text and thinking content
|
|
13
|
+
*/
|
|
14
|
+
function parseThinkingContent(content: string): {
|
|
15
|
+
text: string;
|
|
16
|
+
thinking: string;
|
|
17
|
+
} {
|
|
18
|
+
// If no think tags, return the original content as text
|
|
19
|
+
if (!content.includes('<think>')) {
|
|
20
|
+
return { text: content, thinking: '' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let textResult = '';
|
|
24
|
+
const thinkingResult: string[] = [];
|
|
25
|
+
let position = 0;
|
|
26
|
+
|
|
27
|
+
while (position < content.length) {
|
|
28
|
+
const thinkStart = content.indexOf('<think>', position);
|
|
29
|
+
|
|
30
|
+
if (thinkStart === -1) {
|
|
31
|
+
// No more think tags, add the rest and break
|
|
32
|
+
textResult += content.slice(position);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Add text before the think tag
|
|
37
|
+
textResult += content.slice(position, thinkStart);
|
|
38
|
+
|
|
39
|
+
const thinkEnd = content.indexOf('</think>', thinkStart);
|
|
40
|
+
if (thinkEnd === -1) {
|
|
41
|
+
// Malformed input, no closing tag
|
|
42
|
+
textResult += content.slice(thinkStart);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add the thinking content
|
|
47
|
+
const thinkContent = content.slice(thinkStart + 7, thinkEnd);
|
|
48
|
+
thinkingResult.push(thinkContent);
|
|
49
|
+
|
|
50
|
+
// Move position to after the think tag
|
|
51
|
+
position = thinkEnd + 8; // 8 is the length of '</think>'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
text: textResult.trim(),
|
|
56
|
+
thinking: thinkingResult.join('\n').trim(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
9
60
|
function getNonEmptyValue(possibleValues: string[]): string | undefined {
|
|
10
61
|
for (const value of possibleValues) {
|
|
11
62
|
if (value && value.trim() !== '') {
|
|
@@ -239,6 +290,40 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
239
290
|
},
|
|
240
291
|
],
|
|
241
292
|
});
|
|
293
|
+
} else if (graph.currentTokenType === 'think_and_text') {
|
|
294
|
+
const { text, thinking } = parseThinkingContent(content);
|
|
295
|
+
if (thinking) {
|
|
296
|
+
graph.dispatchReasoningDelta(stepId, {
|
|
297
|
+
content: [
|
|
298
|
+
{
|
|
299
|
+
type: ContentTypes.THINK,
|
|
300
|
+
think: thinking,
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (text) {
|
|
306
|
+
graph.currentTokenType = ContentTypes.TEXT;
|
|
307
|
+
graph.tokenTypeSwitch = 'content';
|
|
308
|
+
const newStepKey = graph.getStepKey(metadata);
|
|
309
|
+
const message_id = getMessageId(newStepKey, graph) ?? '';
|
|
310
|
+
graph.dispatchRunStep(newStepKey, {
|
|
311
|
+
type: StepTypes.MESSAGE_CREATION,
|
|
312
|
+
message_creation: {
|
|
313
|
+
message_id,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const newStepId = graph.getStepIdByKey(stepKey);
|
|
318
|
+
graph.dispatchMessageDelta(newStepId, {
|
|
319
|
+
content: [
|
|
320
|
+
{
|
|
321
|
+
type: ContentTypes.TEXT,
|
|
322
|
+
text: text,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
});
|
|
326
|
+
}
|
|
242
327
|
} else {
|
|
243
328
|
graph.dispatchReasoningDelta(stepId, {
|
|
244
329
|
content: [
|
|
@@ -384,6 +469,14 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
384
469
|
) {
|
|
385
470
|
graph.currentTokenType = ContentTypes.TEXT;
|
|
386
471
|
graph.tokenTypeSwitch = 'content';
|
|
472
|
+
} else if (
|
|
473
|
+
chunk.content != null &&
|
|
474
|
+
typeof chunk.content === 'string' &&
|
|
475
|
+
chunk.content.includes('<think>') &&
|
|
476
|
+
chunk.content.includes('</think>')
|
|
477
|
+
) {
|
|
478
|
+
graph.currentTokenType = 'think_and_text';
|
|
479
|
+
graph.tokenTypeSwitch = 'content';
|
|
387
480
|
} else if (
|
|
388
481
|
chunk.content != null &&
|
|
389
482
|
typeof chunk.content === 'string' &&
|