@lobehub/chat 1.70.0 → 1.70.1
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/chat.json +1 -1
- package/locales/bg-BG/chat.json +1 -1
- package/locales/de-DE/chat.json +1 -1
- package/locales/en-US/chat.json +1 -1
- package/locales/es-ES/chat.json +1 -1
- package/locales/fa-IR/chat.json +1 -1
- package/locales/fr-FR/chat.json +1 -1
- package/locales/it-IT/chat.json +1 -1
- package/locales/ja-JP/chat.json +1 -1
- package/locales/ko-KR/chat.json +1 -1
- package/locales/nl-NL/chat.json +1 -1
- package/locales/pl-PL/chat.json +1 -1
- package/locales/pt-BR/chat.json +1 -1
- package/locales/ru-RU/chat.json +1 -1
- package/locales/tr-TR/chat.json +1 -1
- package/locales/vi-VN/chat.json +1 -1
- package/locales/zh-CN/chat.json +1 -1
- package/locales/zh-TW/chat.json +1 -1
- package/package.json +1 -1
- package/src/libs/agent-runtime/anthropic/index.test.ts +315 -1
- package/src/libs/agent-runtime/anthropic/index.ts +7 -4
- package/src/locales/default/chat.ts +1 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.70.1](https://github.com/lobehub/lobe-chat/compare/v1.70.0...v1.70.1)
|
6
|
+
|
7
|
+
<sup>Released on **2025-03-10**</sup>
|
8
|
+
|
9
|
+
#### 🐛 Bug Fixes
|
10
|
+
|
11
|
+
- **misc**: Fix anthropic max tokens.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's fixed
|
19
|
+
|
20
|
+
- **misc**: Fix anthropic max tokens, closes [#6859](https://github.com/lobehub/lobe-chat/issues/6859) ([35fbc6c](https://github.com/lobehub/lobe-chat/commit/35fbc6c))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
## [Version 1.70.0](https://github.com/lobehub/lobe-chat/compare/v1.69.6...v1.70.0)
|
6
31
|
|
7
32
|
<sup>Released on **2025-03-09**</sup>
|
package/changelog/v1.json
CHANGED
package/locales/ar/chat.json
CHANGED
package/locales/bg-BG/chat.json
CHANGED
package/locales/de-DE/chat.json
CHANGED
package/locales/en-US/chat.json
CHANGED
package/locales/es-ES/chat.json
CHANGED
package/locales/fa-IR/chat.json
CHANGED
package/locales/fr-FR/chat.json
CHANGED
package/locales/it-IT/chat.json
CHANGED
package/locales/ja-JP/chat.json
CHANGED
package/locales/ko-KR/chat.json
CHANGED
package/locales/nl-NL/chat.json
CHANGED
package/locales/pl-PL/chat.json
CHANGED
package/locales/pt-BR/chat.json
CHANGED
package/locales/ru-RU/chat.json
CHANGED
package/locales/tr-TR/chat.json
CHANGED
package/locales/vi-VN/chat.json
CHANGED
package/locales/zh-CN/chat.json
CHANGED
package/locales/zh-TW/chat.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.70.
|
3
|
+
"version": "1.70.1",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -1,7 +1,7 @@
|
|
1
1
|
// @vitest-environment node
|
2
2
|
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import { ChatCompletionTool } from '@/libs/agent-runtime';
|
4
|
+
import { ChatCompletionTool, ChatStreamPayload } from '@/libs/agent-runtime';
|
5
5
|
|
6
6
|
import * as anthropicHelpers from '../utils/anthropicHelpers';
|
7
7
|
import * as debugStreamModule from '../utils/debugStream';
|
@@ -511,5 +511,319 @@ describe('LobeAnthropicAI', () => {
|
|
511
511
|
).resolves.toBeInstanceOf(Response);
|
512
512
|
});
|
513
513
|
});
|
514
|
+
|
515
|
+
describe('buildAnthropicPayload', () => {
|
516
|
+
it('should correctly build payload with user messages only', async () => {
|
517
|
+
const payload: ChatStreamPayload = {
|
518
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
519
|
+
model: 'claude-3-haiku-20240307',
|
520
|
+
temperature: 0.5,
|
521
|
+
};
|
522
|
+
|
523
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
524
|
+
|
525
|
+
expect(result).toEqual({
|
526
|
+
max_tokens: 4096,
|
527
|
+
messages: [
|
528
|
+
{
|
529
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
530
|
+
role: 'user',
|
531
|
+
},
|
532
|
+
],
|
533
|
+
model: 'claude-3-haiku-20240307',
|
534
|
+
temperature: 0.25,
|
535
|
+
});
|
536
|
+
});
|
537
|
+
|
538
|
+
it('should correctly build payload with system message', async () => {
|
539
|
+
const payload: ChatStreamPayload = {
|
540
|
+
messages: [
|
541
|
+
{ content: 'You are a helpful assistant', role: 'system' },
|
542
|
+
{ content: 'Hello', role: 'user' },
|
543
|
+
],
|
544
|
+
model: 'claude-3-haiku-20240307',
|
545
|
+
temperature: 0.7,
|
546
|
+
};
|
547
|
+
|
548
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
549
|
+
|
550
|
+
expect(result).toEqual({
|
551
|
+
max_tokens: 4096,
|
552
|
+
messages: [
|
553
|
+
{
|
554
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
555
|
+
role: 'user',
|
556
|
+
},
|
557
|
+
],
|
558
|
+
model: 'claude-3-haiku-20240307',
|
559
|
+
system: [
|
560
|
+
{
|
561
|
+
cache_control: { type: 'ephemeral' },
|
562
|
+
text: 'You are a helpful assistant',
|
563
|
+
type: 'text',
|
564
|
+
},
|
565
|
+
],
|
566
|
+
temperature: 0.35,
|
567
|
+
});
|
568
|
+
});
|
569
|
+
|
570
|
+
it('should correctly build payload with tools', async () => {
|
571
|
+
const tools: ChatCompletionTool[] = [
|
572
|
+
{ function: { name: 'tool1', description: 'desc1' }, type: 'function' },
|
573
|
+
];
|
574
|
+
|
575
|
+
const spyOn = vi.spyOn(anthropicHelpers, 'buildAnthropicTools').mockReturnValueOnce([
|
576
|
+
{
|
577
|
+
name: 'tool1',
|
578
|
+
description: 'desc1',
|
579
|
+
},
|
580
|
+
] as any);
|
581
|
+
|
582
|
+
const payload: ChatStreamPayload = {
|
583
|
+
messages: [{ content: 'Use a tool', role: 'user' }],
|
584
|
+
model: 'claude-3-haiku-20240307',
|
585
|
+
temperature: 0.8,
|
586
|
+
tools,
|
587
|
+
};
|
588
|
+
|
589
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
590
|
+
|
591
|
+
expect(result).toEqual({
|
592
|
+
max_tokens: 4096,
|
593
|
+
messages: [
|
594
|
+
{
|
595
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Use a tool', type: 'text' }],
|
596
|
+
role: 'user',
|
597
|
+
},
|
598
|
+
],
|
599
|
+
model: 'claude-3-haiku-20240307',
|
600
|
+
temperature: 0.4,
|
601
|
+
tools: [{ name: 'tool1', description: 'desc1' }],
|
602
|
+
});
|
603
|
+
|
604
|
+
expect(spyOn).toHaveBeenCalledWith(tools, {
|
605
|
+
enabledContextCaching: true,
|
606
|
+
});
|
607
|
+
});
|
608
|
+
|
609
|
+
it('should correctly build payload with thinking mode enabled', async () => {
|
610
|
+
const payload: ChatStreamPayload = {
|
611
|
+
messages: [{ content: 'Solve this problem', role: 'user' }],
|
612
|
+
model: 'claude-3-haiku-20240307',
|
613
|
+
temperature: 0.9,
|
614
|
+
thinking: { type: 'enabled', budget_tokens: 0 },
|
615
|
+
};
|
616
|
+
|
617
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
618
|
+
|
619
|
+
expect(result).toEqual({
|
620
|
+
max_tokens: 64000,
|
621
|
+
messages: [
|
622
|
+
{
|
623
|
+
content: [
|
624
|
+
{ cache_control: { type: 'ephemeral' }, text: 'Solve this problem', type: 'text' },
|
625
|
+
],
|
626
|
+
role: 'user',
|
627
|
+
},
|
628
|
+
],
|
629
|
+
model: 'claude-3-haiku-20240307',
|
630
|
+
thinking: { type: 'enabled', budget_tokens: 0 },
|
631
|
+
});
|
632
|
+
});
|
633
|
+
|
634
|
+
it('should respect max_tokens in thinking mode when provided', async () => {
|
635
|
+
const payload: ChatStreamPayload = {
|
636
|
+
max_tokens: 1000,
|
637
|
+
messages: [{ content: 'Solve this problem', role: 'user' }],
|
638
|
+
model: 'claude-3-haiku-20240307',
|
639
|
+
temperature: 0.7,
|
640
|
+
thinking: { type: 'enabled', budget_tokens: 0 },
|
641
|
+
};
|
642
|
+
|
643
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
644
|
+
|
645
|
+
expect(result).toEqual({
|
646
|
+
max_tokens: 1000,
|
647
|
+
messages: [
|
648
|
+
{
|
649
|
+
content: [
|
650
|
+
{ cache_control: { type: 'ephemeral' }, text: 'Solve this problem', type: 'text' },
|
651
|
+
],
|
652
|
+
role: 'user',
|
653
|
+
},
|
654
|
+
],
|
655
|
+
model: 'claude-3-haiku-20240307',
|
656
|
+
thinking: { type: 'enabled', budget_tokens: 0 },
|
657
|
+
});
|
658
|
+
});
|
659
|
+
|
660
|
+
it('should use budget_tokens in thinking mode when provided', async () => {
|
661
|
+
const payload: ChatStreamPayload = {
|
662
|
+
max_tokens: 1000,
|
663
|
+
messages: [{ content: 'Solve this problem', role: 'user' }],
|
664
|
+
model: 'claude-3-haiku-20240307',
|
665
|
+
temperature: 0.5,
|
666
|
+
thinking: { type: 'enabled', budget_tokens: 2000 },
|
667
|
+
};
|
668
|
+
|
669
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
670
|
+
|
671
|
+
expect(result).toEqual({
|
672
|
+
max_tokens: 3000, // budget_tokens + max_tokens
|
673
|
+
messages: [
|
674
|
+
{
|
675
|
+
content: [
|
676
|
+
{ cache_control: { type: 'ephemeral' }, text: 'Solve this problem', type: 'text' },
|
677
|
+
],
|
678
|
+
role: 'user',
|
679
|
+
},
|
680
|
+
],
|
681
|
+
model: 'claude-3-haiku-20240307',
|
682
|
+
thinking: { type: 'enabled', budget_tokens: 2000 },
|
683
|
+
});
|
684
|
+
});
|
685
|
+
|
686
|
+
it('should cap max_tokens at 64000 in thinking mode', async () => {
|
687
|
+
const payload: ChatStreamPayload = {
|
688
|
+
max_tokens: 10000,
|
689
|
+
messages: [{ content: 'Solve this problem', role: 'user' }],
|
690
|
+
model: 'claude-3-haiku-20240307',
|
691
|
+
temperature: 0.6,
|
692
|
+
thinking: { type: 'enabled', budget_tokens: 60000 },
|
693
|
+
};
|
694
|
+
|
695
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
696
|
+
|
697
|
+
expect(result).toEqual({
|
698
|
+
max_tokens: 64000, // capped at 64000
|
699
|
+
messages: [
|
700
|
+
{
|
701
|
+
content: [
|
702
|
+
{ cache_control: { type: 'ephemeral' }, text: 'Solve this problem', type: 'text' },
|
703
|
+
],
|
704
|
+
role: 'user',
|
705
|
+
},
|
706
|
+
],
|
707
|
+
model: 'claude-3-haiku-20240307',
|
708
|
+
thinking: { type: 'enabled', budget_tokens: 60000 },
|
709
|
+
});
|
710
|
+
});
|
711
|
+
|
712
|
+
it('should set correct max_tokens based on model for claude-3 models', async () => {
|
713
|
+
const payload: ChatStreamPayload = {
|
714
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
715
|
+
model: 'claude-3-haiku-20240307',
|
716
|
+
temperature: 0.7,
|
717
|
+
};
|
718
|
+
|
719
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
720
|
+
|
721
|
+
expect(result.max_tokens).toBe(4096);
|
722
|
+
});
|
723
|
+
|
724
|
+
it('should set correct max_tokens based on model for non claude-3 models', async () => {
|
725
|
+
const payload: ChatStreamPayload = {
|
726
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
727
|
+
model: 'claude-2.1',
|
728
|
+
temperature: 0.7,
|
729
|
+
};
|
730
|
+
|
731
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
732
|
+
|
733
|
+
expect(result.max_tokens).toBe(8192);
|
734
|
+
});
|
735
|
+
|
736
|
+
it('should respect max_tokens when explicitly provided', async () => {
|
737
|
+
const payload: ChatStreamPayload = {
|
738
|
+
max_tokens: 2000,
|
739
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
740
|
+
model: 'claude-3-haiku-20240307',
|
741
|
+
temperature: 0.7,
|
742
|
+
};
|
743
|
+
|
744
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
745
|
+
|
746
|
+
expect(result.max_tokens).toBe(2000);
|
747
|
+
});
|
748
|
+
|
749
|
+
it('should correctly handle temperature scaling', async () => {
|
750
|
+
const payload: ChatStreamPayload = {
|
751
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
752
|
+
model: 'claude-3-haiku-20240307',
|
753
|
+
temperature: 1.0,
|
754
|
+
};
|
755
|
+
|
756
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
757
|
+
|
758
|
+
expect(result.temperature).toBe(0.5); // Anthropic uses 0-1 scale, so divide by 2
|
759
|
+
});
|
760
|
+
|
761
|
+
it('should not include temperature when not provided in payload', async () => {
|
762
|
+
// We need to create a partial payload without temperature
|
763
|
+
// but since the type requires it, we'll use type assertion
|
764
|
+
const partialPayload = {
|
765
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
766
|
+
model: 'claude-3-haiku-20240307',
|
767
|
+
} as ChatStreamPayload;
|
768
|
+
|
769
|
+
// Delete the temperature property to simulate it not being provided
|
770
|
+
delete (partialPayload as any).temperature;
|
771
|
+
|
772
|
+
const result = await instance['buildAnthropicPayload'](partialPayload);
|
773
|
+
|
774
|
+
expect(result.temperature).toBeUndefined();
|
775
|
+
});
|
776
|
+
|
777
|
+
it('should not include top_p when thinking is enabled', async () => {
|
778
|
+
const payload: ChatStreamPayload = {
|
779
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
780
|
+
model: 'claude-3-haiku-20240307',
|
781
|
+
temperature: 0.7,
|
782
|
+
thinking: { type: 'enabled', budget_tokens: 0 },
|
783
|
+
top_p: 0.9,
|
784
|
+
};
|
785
|
+
|
786
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
787
|
+
|
788
|
+
expect(result.top_p).toBeUndefined();
|
789
|
+
});
|
790
|
+
|
791
|
+
it('should include top_p when thinking is not enabled', async () => {
|
792
|
+
const payload: ChatStreamPayload = {
|
793
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
794
|
+
model: 'claude-3-haiku-20240307',
|
795
|
+
temperature: 0.7,
|
796
|
+
top_p: 0.9,
|
797
|
+
};
|
798
|
+
|
799
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
800
|
+
|
801
|
+
expect(result.top_p).toBe(0.9);
|
802
|
+
});
|
803
|
+
|
804
|
+
it('should handle thinking with type disabled', async () => {
|
805
|
+
const payload: ChatStreamPayload = {
|
806
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
807
|
+
model: 'claude-3-haiku-20240307',
|
808
|
+
temperature: 0.7,
|
809
|
+
thinking: { type: 'disabled', budget_tokens: 0 },
|
810
|
+
};
|
811
|
+
|
812
|
+
const result = await instance['buildAnthropicPayload'](payload);
|
813
|
+
|
814
|
+
// When thinking is disabled, it should be treated as if thinking wasn't provided
|
815
|
+
expect(result).toEqual({
|
816
|
+
max_tokens: 4096,
|
817
|
+
messages: [
|
818
|
+
{
|
819
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
820
|
+
role: 'user',
|
821
|
+
},
|
822
|
+
],
|
823
|
+
model: 'claude-3-haiku-20240307',
|
824
|
+
temperature: 0.35,
|
825
|
+
});
|
826
|
+
});
|
827
|
+
});
|
514
828
|
});
|
515
829
|
});
|
@@ -100,10 +100,13 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
100
100
|
|
101
101
|
const postTools = buildAnthropicTools(tools, { enabledContextCaching });
|
102
102
|
|
103
|
-
if (!!thinking) {
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
if (!!thinking && thinking.type === 'enabled') {
|
104
|
+
// claude 3.7 thinking has max output of 64000 tokens
|
105
|
+
const maxTokens = !!max_tokens
|
106
|
+
? thinking?.budget_tokens && thinking?.budget_tokens > max_tokens
|
107
|
+
? Math.min(thinking?.budget_tokens + max_tokens, 64_000)
|
108
|
+
: max_tokens
|
109
|
+
: 64_000;
|
107
110
|
|
108
111
|
// `temperature` may only be set to 1 when thinking is enabled.
|
109
112
|
// `top_p` must be unset when thinking is enabled.
|