@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix anthropic max tokens."
6
+ ]
7
+ },
8
+ "date": "2025-03-10",
9
+ "version": "1.70.1"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
@@ -65,7 +65,7 @@
65
65
  "warp": "تغيير السطر"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "جارٍ تحليل وفهم نواياك..."
68
+ "title": "جارٍ فهم وتحليل نواياك..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "جميع المحتويات",
@@ -65,7 +65,7 @@
65
65
  "warp": "Нов ред"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Анализирам и разбирам вашето намерение..."
68
+ "title": "Разбирам и анализирам вашето намерение..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Всички съдържания",
@@ -65,7 +65,7 @@
65
65
  "warp": "Zeilenumbruch"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analysiere und verstehe Ihre Absicht..."
68
+ "title": "Verstehe und analysiere gerade Ihre Absicht..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Alle Inhalte",
@@ -65,7 +65,7 @@
65
65
  "warp": "New Line"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analyzing and understanding your intent..."
68
+ "title": "Understanding and analyzing your intent..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "All Content",
@@ -65,7 +65,7 @@
65
65
  "warp": "Salto de línea"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analizando y comprendiendo su intención..."
68
+ "title": "Entendiendo y analizando su intención..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Todo el contenido",
@@ -65,7 +65,7 @@
65
65
  "warp": "خط جدید"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "در حال تحلیل و درک نیت شما..."
68
+ "title": "در حال درک و تحلیل نیت شما..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "همه محتوا",
@@ -65,7 +65,7 @@
65
65
  "warp": "Saut de ligne"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analyse et comprend votre intention..."
68
+ "title": "En train de comprendre et d'analyser votre intention..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Tout le contenu",
@@ -65,7 +65,7 @@
65
65
  "warp": "A capo"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analizzando e comprendendo le tue intenzioni..."
68
+ "title": "Stiamo comprendendo e analizzando la tua intenzione..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Tutti i contenuti",
@@ -65,7 +65,7 @@
65
65
  "warp": "改行"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "あなたの意図を分析し理解しています..."
68
+ "title": "あなたの意図を理解し、分析しています..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "すべてのコンテンツ",
@@ -65,7 +65,7 @@
65
65
  "warp": "줄바꿈"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "귀하의 의도를 분석하고 이해하는 중입니다..."
68
+ "title": "귀하의 의도를 이해하고 분석하는 중입니다..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "모든 내용",
@@ -65,7 +65,7 @@
65
65
  "warp": "Nieuwe regel"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Bezig met het analyseren en begrijpen van uw intentie..."
68
+ "title": "Bezig met het begrijpen en analyseren van uw intentie..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Alle inhoud",
@@ -65,7 +65,7 @@
65
65
  "warp": "Złamanie wiersza"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analizuję i rozumiem Twoje intencje..."
68
+ "title": "Rozumiemy i analizujemy Twoje intencje..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Wszystkie treści",
@@ -65,7 +65,7 @@
65
65
  "warp": "Quebrar linha"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Analisando e compreendendo sua intenção..."
68
+ "title": "Entendendo e analisando sua intenção..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Todo conteúdo",
@@ -65,7 +65,7 @@
65
65
  "warp": "Перенос строки"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Анализ и понимание вашего намерения..."
68
+ "title": "Мы понимаем и анализируем ваше намерение..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Все содержимое",
@@ -65,7 +65,7 @@
65
65
  "warp": "Satır atla"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Niyetinizi analiz ediyor ve anlıyor..."
68
+ "title": "Niyetinizi anlama ve analiz etme aşamasındayız..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Tüm İçerik",
@@ -65,7 +65,7 @@
65
65
  "warp": "Xuống dòng"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "Đang phân tích và hiểu ý định của bạn..."
68
+ "title": "Đang hiểu và phân tích ý định của bạn..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "Tất cả nội dung",
@@ -65,7 +65,7 @@
65
65
  "warp": "换行"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "正在分析并理解意图您的意图..."
68
+ "title": "正在理解并分析您的意图..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "所有内容",
@@ -65,7 +65,7 @@
65
65
  "warp": "換行"
66
66
  },
67
67
  "intentUnderstanding": {
68
- "title": "正在分析並理解您的意圖..."
68
+ "title": "正在理解並分析您的意圖..."
69
69
  },
70
70
  "knowledgeBase": {
71
71
  "all": "所有內容",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.70.0",
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
- const maxTokens =
105
- // claude 3.7 thinking has max output of 64000 tokens
106
- max_tokens ?? (thinking?.budget_tokens ? thinking?.budget_tokens + 64_000 : 8192);
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.
@@ -66,7 +66,7 @@ export default {
66
66
  warp: '换行',
67
67
  },
68
68
  intentUnderstanding: {
69
- title: '正在分析并理解意图您的意图...',
69
+ title: '正在理解并分析您的意图...',
70
70
  },
71
71
  knowledgeBase: {
72
72
  all: '所有内容',