@lobehub/chat 1.2.14 → 1.3.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,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.3.1](https://github.com/lobehub/lobe-chat/compare/v1.3.0...v1.3.1)
6
+
7
+ <sup>Released on **2024-07-09**</sup>
8
+
9
+ <br/>
10
+
11
+ <details>
12
+ <summary><kbd>Improvements and Fixes</kbd></summary>
13
+
14
+ </details>
15
+
16
+ <div align="right">
17
+
18
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
19
+
20
+ </div>
21
+
22
+ ## [Version 1.3.0](https://github.com/lobehub/lobe-chat/compare/v1.2.14...v1.3.0)
23
+
24
+ <sup>Released on **2024-07-09**</sup>
25
+
26
+ #### ✨ Features
27
+
28
+ - **misc**: Add Taichu model provider.
29
+
30
+ <br/>
31
+
32
+ <details>
33
+ <summary><kbd>Improvements and Fixes</kbd></summary>
34
+
35
+ #### What's improved
36
+
37
+ - **misc**: Add Taichu model provider, closes [#3129](https://github.com/lobehub/lobe-chat/issues/3129) ([a4580e9](https://github.com/lobehub/lobe-chat/commit/a4580e9))
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ### [Version 1.2.14](https://github.com/lobehub/lobe-chat/compare/v1.2.13...v1.2.14)
6
48
 
7
49
  <sup>Released on **2024-07-08**</sup>
@@ -2,14 +2,182 @@
2
2
  title: Groq Tool Calling
3
3
  ---
4
4
 
5
- # Groq Tool Calling
5
+ # Groq 平台模型 Tools Calling 评测(Llama 3/Mistral)
6
6
 
7
- ## LLAMA3-70B
7
+ <Callout type={'info'}>
8
+ 由于 Groq 本身不支持 stream,因此 Tools Calling 的调用是普通请求。
9
+ </Callout>
8
10
 
9
- TODO
11
+ Groq 平台的模型 Tools Calling 能力一览:
12
+
13
+ | 模型 | 支持 Tools Calling | 流式 (Stream) | 并发(Parallel) | 简单指令得分 | 复杂指令 |
14
+ | ------------ | ------------------ | --------------- | ---------------- | ------------ | -------- |
15
+ | LLAMA3 70B | ✅ | ❌ | ✅ | 🌟🌟 | 🌟🌟 |
16
+ | LLAMA3 8B | ✅ | ❌ | ✅ | 🌟🌟 | 🌟 |
17
+ | Mixtral-8x7B | ✅ | ❌ | ✅ | ⛔ | 🌟🌟 |
18
+
19
+
20
+ ## LLAMA3 70B
21
+
22
+ ### 简单调用指令:天气查询
23
+
24
+ 测试指令:指令 ①
25
+
26
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/a509d3f4-20b5-40a4-a2dc-adf11e1789e8" />
27
+
28
+ 从上述视频中可以看到 LLAMA3 70B 支持并发 Tools Calling,可以同时调用多次天气查询。
29
+
30
+ <Image
31
+ alt="LLAMA3 70B 简单指令的 Tools Calling"
32
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/f9cd0d4d-0e44-4bff-98cc-560f11259a60"
33
+ />
34
+
35
+ <details>
36
+ <summary>Tools Calling 原始输出:</summary>
37
+
38
+ ```yml
39
+ [no stream response] 2024-7-8 15:50:40.166
40
+
41
+ {"id":"chatcmpl-ec4b6c0b-1078-4f50-a39c-e58b3b1f9c31","object":"chat.completion","created":1720425030,"model":"llama3-70b-8192","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_v89g","type":"function","function":{"name":"realtime-weather____fetchCurrentWeather","arguments":"{\"city\":\"杭州\"}"}},{"id":"call_jxwk","type":"function","function":{"name":"realtime-weather____fetchCurrentWeather","arguments":"{\"city\":\"北京}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":969,"prompt_time":0.224209489,"completion_tokens":68,"completion_time":0.194285714,"total_tokens":1037,"total_time":0.418495203},"system_fingerprint":"fp_87cbfbbc4d","x_groq":{"id":"req_01j28n57x9e78a6bfbn9sdn139"}}
42
+
43
+ ```
44
+
45
+ </details>
46
+
47
+ ### 复杂调用指令:文生图
48
+
49
+ 测试指令:指令 ②
50
+
51
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/b790f5d8-e797-4325-a974-436505b16fba" />
52
+
53
+ <Image
54
+ alt="LLAMA3 70B 复杂指令的 Tools Calling"
55
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/082170fa-6d09-4602-8e23-eb678024305f"
56
+ />
57
+
58
+ <details>
59
+ <summary>Tools Calling 原始输出:</summary>
60
+
61
+ ```yml
62
+ [no stream response] 2024-7-8 18:0:34.811
63
+
64
+ {"id":"chatcmpl-e3b59ca9-1172-4ae2-96c7-3d6997a1f8a8","object":"chat.completion","created":1720432834,"model":"llama3-70b-8192","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_azm9","type":"function","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\":[\"A small, fluffy, and playful golden retriever puppy with a white patch on its forehead, sitting on a green grass field with a bright blue sky in the background, photo.\",\"A cute, little, brown and white Dalmatian puppy with a red collar, running around in a park with a sunny day, illustration.\",\"A tiny, grey and white Poodle puppy with a pink ribbon, sitting on a white couch with a few toys surrounding it, watercolor painting.\",\"A sweet, small, black and white Chihuahua puppy with a pink bow, lying on a soft, white blanket with a few stuffed animals nearby, oil painting.\"],\"quality\":\"standard\",\"seeds\":[],\"size\":\"1024x1024\",\"style\":\"vivid\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":2305,"prompt_time":3.027052298,"completion_tokens":246,"completion_time":0.702857143,"total_tokens":2551,"total_time":3.729909441},"system_fingerprint":"fp_7ab5f7e105","x_groq":{"id":"req_01j28wk2q0efvs22qatw7rd0ds"}}
65
+
66
+ POST /api/chat/groq 200 in 17462ms
67
+ ```
68
+
69
+ </details>
10
70
 
11
71
  ## LLAMA3-8B
12
72
 
13
- TODO
73
+ ### 简单调用指令:天气查询
74
+
75
+ 测试指令:指令 ①
76
+
77
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/8077f432-b5f6-4e68-a311-2aac1d7ea892" />
78
+
79
+ 从上述视频中可以看到 LLAMA3-8B 对于天气插件可以正常调用,并获得正确的总结结果。但是它并没有完全 follow 我们的描述指令,没有回答「好的」。
80
+
81
+ <Image
82
+ alt="LLAMA3-8B 简单指令的 Tools Calling"
83
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/9d553f4e-0818-4f6c-be9d-70da289dd723"
84
+ />
85
+
86
+ <details>
87
+ <summary>Tools Calling 原始输出:</summary>
88
+
89
+ ```yml
90
+ [no stream response] 2024-7-9 11:33:16.920
91
+
92
+ {"id":"chatcmpl-f3672d59-e91d-4253-af1b-bfc4e0912085","object":"chat.completion","created":1720495996,"model":"llama3-8b-8192","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_rjtk","type":"function","function":{"name":"realtime-weather____fetchCurrentWeather","arguments":"{\"city\":\"杭州市\"}"}},{"id":"call_7pqh","type":"functi,"function":{"name":"realtime-weather____fetchCurrentWeather","arguments":"{\"city\":\"北京市\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":969,"ppt_time":0.145428625,"completion_tokens":128,"completion_time":0.101364747,"total_tokens":1097,"total_time":0.246793372},"system_fingerprint":"fp_33d61fdfc3","x_groq":{"id":"req_01j2artze1exz82nettf2h9066"}}
93
+
94
+ POST /api/chat/groq 200 in 1649ms
95
+ ```
96
+
97
+ </details>
98
+
99
+ ### 复杂调用指令:文生图
100
+
101
+ 测试指令:指令 ②
102
+
103
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/48dcfb0a-ba03-4eae-951e-37f3645eb9c7" />
104
+
105
+ LLAMA3 8B 在 DallE 的输出场景下,只会输出 1 张图片,而不是像 LLAMA3 70B 一样输出 4 张,意味着在复杂 Tools 指令层面,能力和 GPT 3.5 Turbo 接近,不如 GPT 4。
106
+
107
+ <Image
108
+ alt="LLAMA3 8B 复杂指令的 Tools Calling"
109
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/b0180793-5934-4296-a569-60fbc70e8262"
110
+ />
111
+
112
+ <details>
113
+ <summary>Tools Calling 原始输出:</summary>
114
+
115
+ ```yml
116
+ [no stream response] 2024-7-9 11:58:27.40
117
+
118
+ {"id":"chatcmpl-3c38f4d2-3424-416c-9fb0-0969d2683959","object":"chat.completion","created":1720497506,"model":"llama3-8b-8192","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_k6xj","type":"function","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\":[\"Create a watercolor painting of a small white dog with a pink nose, wearing a red collar and sitting on a green grass. The dog's ears should be floppy and its fur should be curly.\"],\"quality\":\"standard\",\"seeds\":[],\"size\":\"1024x1024\",\"style\":\"natural\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":2282,"prompt_time":0.342335558,"completion_tokens":148,"completion_time":0.118023813,"total_tokens":2430,"total_time":0.460359371},"system_fingerprint":"fp_179b0f92c9","x_groq":{"id":"req_01j2at921tec8aymdq48czcw1y"}}
119
+
120
+ POST /api/chat/groq 200 in 2517ms
121
+ ```
122
+
123
+ </details>
14
124
 
15
125
  ## Mixtral-8x7B
126
+
127
+ ### 简单调用指令:天气查询
128
+
129
+ 测试指令:指令 ①
130
+
131
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/70b887fa-0e16-4ed2-892e-db52dcecf71d" />
132
+
133
+ 从上述视频中可以看到 Mixtral-8x7B 对于天气插件的查询输出的参数有问题,导致无法正常调用插件。
134
+
135
+ <Image
136
+ alt="Mixtral-8x7B 简单指令的 Tools Calling"
137
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/1b7bf28e-fa31-4838-ae92-9100a4bbb71c"
138
+ />
139
+
140
+ <details>
141
+ <summary>Tools Calling 原始输出:</summary>
142
+
143
+ ```yml
144
+
145
+ [no stream response] 2024-7-8 22:18:19.682
146
+
147
+ {"id":"chatcmpl-9f89d669-5642-48be-b5cd-7a29756800c0","object":"chat.completion","created":1720448299,"model":"mixtral-8x7b-32768","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_719t","type":"function","function":{"name":"realtime-weather____fetchCurrentWeather","arguments":"{\"city\":\"Hangzhou,Beijing\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1262,"prompt_time":0.116684046,"completion_tokens":102,"completion_time":0.163113006,"total_tokens":1364,"total_time":0.279797052},"system_fingerprint":"fp_c5f20b5bb1","x_groq":{"id":"req_01j29bbc8xen2s3thp9qen5bys"}}
148
+
149
+ POST /api/chat/groq 200 in 4860ms
150
+ ```
151
+
152
+ </details>
153
+
154
+ ### 复杂调用指令:文生图
155
+
156
+ 测试指令:指令 ②
157
+
158
+ <Video src="https://github.com/lobehub/lobe-chat/assets/28616219/86c4ce08-c634-4a45-8782-c74245eee05b" />
159
+
160
+ 非常意外地,Mixtral 8x7B 在文生图的 Tools Calling 的场景下,居然可以正常出图,而且出图的能力基本和 LLAMA3 70B 持平。
161
+
162
+ <Image
163
+ alt="Mixtral-8x7B 复杂指令的 Tools Calling"
164
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/6e7bc3fa-3bc7-4c23-8f88-f38bb889c594"
165
+ />
166
+ <Image
167
+ alt="Mixtral-8x7B 复杂指令的 Tools Calling"
168
+ src="https://github.com/lobehub/lobe-chat/assets/28616219/815b2792-adaf-4315-9014-f20531a3978b"
169
+ />
170
+
171
+ <details>
172
+ <summary>Tools Calling 原始输出:</summary>
173
+
174
+ ```yml
175
+ [no stream response] 2024-7-9 12:6:38.11
176
+
177
+ {"id":"chatcmpl-a90069f3-b119-41b9-b8d7-c7f97373529e","object":"chat.completion","created":1720497998,"model":"mixtral-8x7b-32768","choices":[{"index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_hw8t","type":"function","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\":[\"A colorful and playful illustration of a friendly small dog, looking directly at the viewer with a wagging tail and bright, expressive eyes. The dog's fur is glossy and well-groomed, with a mix of black, white, and brown colors. The background is a lush, green park with a clear blue sky and a few fluffy clouds.\",\"An oil painting of a small, energetic dog in a sunlit park, chasing a frisbee. The dog is a mix of black and white fur, with a distinct brown spot above its left eye. The park features tall trees, providing dappled shade across the grassy landscape. The frisbee is bright orange and stands out against the natural surroundings.\",\"A realistic watercolor painting of a small, fluffy white dog curled up next to a warm fireplace during a cozy winter evening. The dog's eyes are closed in contentment, and a single red bow is tied around its neck. The background includes a plush armchair, a stack of books, and a softly lit room.\",\"A fun and engaging cartoon of a small dog sitting at a café table, enjoying a cup of coffee and a croissant. The dog has a expressive face and a blue scarf around its neck. The café has a vintage, 1920's style and a red awning, with a bustling city background.\"],\"quality\":\"standard\",\"size\":\"1024x1024\",\"style\":\"vivid\"}"}}]},"logprobs":null,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":2920,"prompt_time":0.228639219,"completion_tokens":465,"completion_time":0.755757988,"total_tokens":3385,"total_time":0.984397207},"system_fingerprint":"fp_c5f20b5bb1","x_groq":{"id":"req_01j2atr155f0nv8rmfk448e2at"}}
178
+
179
+ POST /api/chat/groq 200 in 6216ms
180
+
181
+ ```
182
+
183
+ </details>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.2.14",
3
+ "version": "1.3.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,4 +1,5 @@
1
1
  import {
2
+ AiMass,
2
3
  Anthropic,
3
4
  Baichuan,
4
5
  Claude,
@@ -36,6 +37,7 @@ import {
36
37
  PerplexityProviderCard,
37
38
  QwenProviderCard,
38
39
  StepfunProviderCard,
40
+ TaichuProviderCard,
39
41
  TogetherAIProviderCard,
40
42
  ZeroOneProviderCard,
41
43
  ZhiPuProviderCard,
@@ -177,6 +179,11 @@ export const useProviderList = (): ProviderItem[] => {
177
179
  docUrl: urlJoin(BASE_DOC_URL, 'baichuan'),
178
180
  title: <Baichuan.Combine size={ 20 } type={ 'color' } />,
179
181
  },
182
+ {
183
+ ...TaichuProviderCard,
184
+ docUrl: urlJoin(BASE_DOC_URL, 'taichu'),
185
+ title: <AiMass.Combine size={ 28 } type={ 'color' } />,
186
+ },
180
187
  ],
181
188
  [azureProvider, ollamaProvider, ollamaProvider, bedrockProvider],
182
189
  );
@@ -177,6 +177,13 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
177
177
 
178
178
  const apiKey = apiKeyManager.pick(payload?.apiKey || BAICHUAN_API_KEY);
179
179
 
180
+ return { apiKey };
181
+ }
182
+ case ModelProvider.Taichu: {
183
+ const { TAICHU_API_KEY } = getLLMConfig();
184
+
185
+ const apiKey = apiKeyManager.pick(payload?.apiKey || TAICHU_API_KEY);
186
+
180
187
  return { apiKey };
181
188
  }
182
189
  }
@@ -15,7 +15,12 @@ describe('Configuration tests', () => {
15
15
  });
16
16
 
17
17
  it('should contain specific regions in preferredRegion', () => {
18
- expect(preferredRegion).not.contain(['hk1']);
18
+ expect(preferredRegion).not.contain(['hkg1']);
19
+ expect(preferredRegion).not.contain(['dub1']);
20
+ expect(preferredRegion).not.contain(['cdg1']);
21
+ expect(preferredRegion).not.contain(['fra1']);
22
+ expect(preferredRegion).not.contain(['lhr1']);
23
+ expect(preferredRegion).not.contain(['arn1']);
19
24
  });
20
25
  });
21
26
 
@@ -13,8 +13,23 @@ import { POST as UniverseRoute } from '../[provider]/route';
13
13
  // so if you want to use with proxy, you need comment the code below
14
14
  export const runtime = 'edge';
15
15
 
16
- // due to Gemini-1.5-pro is not available in Hong Kong and Ireland, we need to set the preferred region to exclude "Hong Kong" or "Ireland".
17
- // refs: https://github.com/lobehub/lobe-chat/pull/2149
18
- export const preferredRegion = ['cle1', 'iad1', 'pdx1', 'sfo1'];
16
+ // due to Gemini-1.5-pro is not available in Hong Kong, we need to set the preferred region to exclude "Hong Kong (hkg1)".
17
+ // the paid service of the Gemini API is required in the following regions until 8 July 2024. The free service is not available. Therefore, the regions is temporarily disabled.
18
+ // regions include Dublin (dub1, Ireland), Paris (cdg1, France), Frankfurt (fra1, Germany), London (lhr1, UK), and Stockholm (arn1, Sweden).
19
+ // refs: https://ai.google.dev/gemini-api/docs/available-regions
20
+ export const preferredRegion = [
21
+ 'icn1',
22
+ 'sin1',
23
+ 'hnd1',
24
+ 'kix1',
25
+ 'bom1',
26
+ 'cpt1',
27
+ 'pdx1',
28
+ 'cle1',
29
+ 'syd1',
30
+ 'iad1',
31
+ 'sfo1',
32
+ 'gru1',
33
+ ];
19
34
 
20
35
  export const POST = async (req: Request) => UniverseRoute(req, { params: { provider: 'google' } });
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AiMass,
2
3
  Adobe,
3
4
  Ai21,
4
5
  Aws,
@@ -66,6 +67,7 @@ const ModelIcon = memo<ModelProviderIconProps>(({ model: originModel, size = 12
66
67
  if (model.includes('command')) return <Cohere.Avatar size={size} />;
67
68
  if (model.includes('dbrx')) return <Dbrx.Avatar size={size} />;
68
69
  if (model.includes('step')) return <Stepfun.Avatar size={size} />;
70
+ if (model.includes('taichu')) return <AiMass.Avatar size={size} />;
69
71
 
70
72
  // below: To be supported in providers, move up if supported
71
73
  if (model.includes('baichuan'))
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AiMass,
2
3
  Anthropic,
3
4
  Azure,
4
5
  Baichuan,
@@ -119,6 +120,10 @@ const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
119
120
  return <Baichuan size={20} />;
120
121
  }
121
122
 
123
+ case ModelProvider.Taichu: {
124
+ return <AiMass size={20} />;
125
+ }
126
+
122
127
  default: {
123
128
  return null;
124
129
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AdobeFirefly,
3
3
  Ai21,
4
+ AiMass,
4
5
  Aws,
5
6
  Azure,
6
7
  Baichuan,
@@ -61,6 +62,7 @@ const ModelIcon = memo<ModelIconProps>(({ model, size = 12 }) => {
61
62
  if (model.startsWith('openchat')) return <OpenChat size={size} />;
62
63
  if (model.includes('command')) return <Cohere size={size} />;
63
64
  if (model.includes('dbrx')) return <Dbrx size={size} />;
65
+ if (model.includes('taichu')) return <AiMass size={size} />;
64
66
 
65
67
  // below: To be supported in providers, move up if supported
66
68
  if (model.includes('baichuan')) return <Baichuan size={size} />;
package/src/config/llm.ts CHANGED
@@ -85,6 +85,9 @@ export const getLLMConfig = () => {
85
85
 
86
86
  ENABLED_BAICHUAN: z.boolean(),
87
87
  BAICHUAN_API_KEY: z.string().optional(),
88
+
89
+ ENABLED_TAICHU: z.boolean(),
90
+ TAICHU_API_KEY: z.string().optional(),
88
91
  },
89
92
  runtimeEnv: {
90
93
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -161,6 +164,9 @@ export const getLLMConfig = () => {
161
164
 
162
165
  ENABLED_BAICHUAN: !!process.env.BAICHUAN_API_KEY,
163
166
  BAICHUAN_API_KEY: process.env.BAICHUAN_API_KEY,
167
+
168
+ ENABLED_TAICHU: !!process.env.TAICHU_API_KEY,
169
+ TAICHU_API_KEY: process.env.TAICHU_API_KEY,
164
170
  },
165
171
  });
166
172
  };
@@ -16,6 +16,7 @@ import OpenRouterProvider from './openrouter';
16
16
  import PerplexityProvider from './perplexity';
17
17
  import QwenProvider from './qwen';
18
18
  import StepfunProvider from './stepfun';
19
+ import TaichuProvider from './taichu';
19
20
  import TogetherAIProvider from './togetherai';
20
21
  import ZeroOneProvider from './zeroone';
21
22
  import ZhiPuProvider from './zhipu';
@@ -39,6 +40,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
39
40
  ZeroOneProvider.chatModels,
40
41
  StepfunProvider.chatModels,
41
42
  BaichuanProvider.chatModels,
43
+ TaichuProvider.chatModels,
42
44
  ].flat();
43
45
 
44
46
  export const DEFAULT_MODEL_PROVIDER_LIST = [
@@ -61,6 +63,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
61
63
  ZhiPuProvider,
62
64
  StepfunProvider,
63
65
  BaichuanProvider,
66
+ TaichuProvider,
64
67
  ];
65
68
 
66
69
  export const filterEnabledModels = (provider: ModelProviderCard) => {
@@ -83,6 +86,7 @@ export { default as OpenRouterProviderCard } from './openrouter';
83
86
  export { default as PerplexityProviderCard } from './perplexity';
84
87
  export { default as QwenProviderCard } from './qwen';
85
88
  export { default as StepfunProviderCard } from './stepfun';
89
+ export { default as TaichuProviderCard } from './taichu';
86
90
  export { default as TogetherAIProviderCard } from './togetherai';
87
91
  export { default as ZeroOneProviderCard } from './zeroone';
88
92
  export { default as ZhiPuProviderCard } from './zhipu';
@@ -0,0 +1,21 @@
1
+ import { ModelProviderCard } from '@/types/llm';
2
+
3
+ // ref https://ai-maas.wair.ac.cn/#/doc
4
+ const Taichu: ModelProviderCard = {
5
+ chatModels: [
6
+ {
7
+ description: '语言大模型',
8
+ displayName: 'Taichu-2.0',
9
+ enabled: true,
10
+ functionCall: false,
11
+ id: 'taichu_llm',
12
+ tokens: 32_768,
13
+ },
14
+ ],
15
+ checkModel: 'taichu_llm',
16
+ id: 'taichu',
17
+ modelList: { showModelFetcher: true },
18
+ name: 'Taichu',
19
+ };
20
+
21
+ export default Taichu;
@@ -14,6 +14,7 @@ import {
14
14
  PerplexityProviderCard,
15
15
  QwenProviderCard,
16
16
  StepfunProviderCard,
17
+ TaichuProviderCard,
17
18
  TogetherAIProviderCard,
18
19
  ZeroOneProviderCard,
19
20
  ZhiPuProviderCard,
@@ -87,6 +88,10 @@ export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
87
88
  enabled: false,
88
89
  enabledModels: filterEnabledModels(StepfunProviderCard),
89
90
  },
91
+ taichu: {
92
+ enabled: false,
93
+ enabledModels: filterEnabledModels(TaichuProviderCard),
94
+ },
90
95
  togetherai: {
91
96
  enabled: false,
92
97
  enabledModels: filterEnabledModels(TogetherAIProviderCard),
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AiMass,
2
3
  Anthropic,
3
4
  Baichuan,
4
5
  DeepSeek,
@@ -81,6 +82,10 @@ const ProviderAvatar = memo<ProviderAvatarProps>(({ provider }) => {
81
82
  return <Stepfun color={Stepfun.colorPrimary} size={56} />;
82
83
  }
83
84
 
85
+ case ModelProvider.Taichu: {
86
+ return <AiMass.Color size={56} />;
87
+ }
88
+
84
89
  case ModelProvider.TogetherAI: {
85
90
  return <Together color={Together.colorPrimary} size={56} />;
86
91
  }
@@ -19,6 +19,7 @@ import { LobeOpenRouterAI } from './openrouter';
19
19
  import { LobePerplexityAI } from './perplexity';
20
20
  import { LobeQwenAI } from './qwen';
21
21
  import { LobeStepfunAI } from './stepfun';
22
+ import { LobeTaichuAI } from './taichu';
22
23
  import { LobeTogetherAI } from './togetherai';
23
24
  import {
24
25
  ChatCompetitionOptions,
@@ -118,6 +119,7 @@ class AgentRuntime {
118
119
  perplexity: Partial<ClientOptions>;
119
120
  qwen: Partial<ClientOptions>;
120
121
  stepfun: Partial<ClientOptions>;
122
+ taichu: Partial<ClientOptions>;
121
123
  togetherai: Partial<ClientOptions>;
122
124
  zeroone: Partial<ClientOptions>;
123
125
  zhipu: Partial<ClientOptions>;
@@ -226,6 +228,11 @@ class AgentRuntime {
226
228
  runtimeModel = new LobeBaichuanAI(params.baichuan ?? {});
227
229
  break
228
230
  }
231
+
232
+ case ModelProvider.Taichu: {
233
+ runtimeModel = new LobeTaichuAI(params.taichu ?? {});
234
+ break
235
+ }
229
236
  }
230
237
 
231
238
  return new AgentRuntime(runtimeModel);
@@ -0,0 +1,255 @@
1
+ // @vitest-environment node
2
+ import OpenAI from 'openai';
3
+ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import {
6
+ ChatStreamCallbacks,
7
+ LobeOpenAICompatibleRuntime,
8
+ ModelProvider,
9
+ } from '@/libs/agent-runtime';
10
+
11
+ import * as debugStreamModule from '../utils/debugStream';
12
+ import { LobeTaichuAI } from './index';
13
+
14
+ const provider = ModelProvider.Taichu;
15
+ const defaultBaseURL = 'https://ai-maas.wair.ac.cn/maas/v1';
16
+
17
+ const bizErrorType = 'ProviderBizError';
18
+ const invalidErrorType = 'InvalidProviderAPIKey';
19
+
20
+ // Mock the console.error to avoid polluting test output
21
+ vi.spyOn(console, 'error').mockImplementation(() => {});
22
+
23
+ let instance: LobeOpenAICompatibleRuntime;
24
+
25
+ beforeEach(() => {
26
+ instance = new LobeTaichuAI({ apiKey: 'test' });
27
+
28
+ // 使用 vi.spyOn 来模拟 chat.completions.create 方法
29
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
30
+ new ReadableStream() as any,
31
+ );
32
+ });
33
+
34
+ afterEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ describe('LobeTaichuAI', () => {
39
+ describe('init', () => {
40
+ it('should correctly initialize with an API key', async () => {
41
+ const instance = new LobeTaichuAI({ apiKey: 'test_api_key' });
42
+ expect(instance).toBeInstanceOf(LobeTaichuAI);
43
+ expect(instance.baseURL).toEqual(defaultBaseURL);
44
+ });
45
+ });
46
+
47
+ describe('chat', () => {
48
+ describe('Error', () => {
49
+ it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
50
+ // Arrange
51
+ const apiError = new OpenAI.APIError(
52
+ 400,
53
+ {
54
+ status: 400,
55
+ error: {
56
+ message: 'Bad Request',
57
+ },
58
+ },
59
+ 'Error message',
60
+ {},
61
+ );
62
+
63
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
64
+
65
+ // Act
66
+ try {
67
+ await instance.chat({
68
+ messages: [{ content: 'Hello', role: 'user' }],
69
+ model: 'Taichu4',
70
+ temperature: 0,
71
+ });
72
+ } catch (e) {
73
+ expect(e).toEqual({
74
+ endpoint: defaultBaseURL,
75
+ error: {
76
+ error: { message: 'Bad Request' },
77
+ status: 400,
78
+ },
79
+ errorType: bizErrorType,
80
+ provider,
81
+ });
82
+ }
83
+ });
84
+
85
+ it('should throw AgentRuntimeError with NoOpenAIAPIKey if no apiKey is provided', async () => {
86
+ try {
87
+ new LobeTaichuAI({});
88
+ } catch (e) {
89
+ expect(e).toEqual({ errorType: invalidErrorType });
90
+ }
91
+ });
92
+
93
+ it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
94
+ // Arrange
95
+ const errorInfo = {
96
+ stack: 'abc',
97
+ cause: {
98
+ message: 'api is undefined',
99
+ },
100
+ };
101
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
102
+
103
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
104
+
105
+ // Act
106
+ try {
107
+ await instance.chat({
108
+ messages: [{ content: 'Hello', role: 'user' }],
109
+ model: 'Taichu4',
110
+ temperature: 0,
111
+ });
112
+ } catch (e) {
113
+ expect(e).toEqual({
114
+ endpoint: defaultBaseURL,
115
+ error: {
116
+ cause: { message: 'api is undefined' },
117
+ stack: 'abc',
118
+ },
119
+ errorType: bizErrorType,
120
+ provider,
121
+ });
122
+ }
123
+ });
124
+
125
+ it('should return OpenAIBizError with an cause response with desensitize Url', async () => {
126
+ // Arrange
127
+ const errorInfo = {
128
+ stack: 'abc',
129
+ cause: { message: 'api is undefined' },
130
+ };
131
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
132
+
133
+ instance = new LobeTaichuAI({
134
+ apiKey: 'test',
135
+
136
+ baseURL: 'https://api.abc.com/v1',
137
+ });
138
+
139
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
140
+
141
+ // Act
142
+ try {
143
+ await instance.chat({
144
+ messages: [{ content: 'Hello', role: 'user' }],
145
+ model: 'Taichu4',
146
+ temperature: 0,
147
+ });
148
+ } catch (e) {
149
+ expect(e).toEqual({
150
+ endpoint: 'https://api.***.com/v1',
151
+ error: {
152
+ cause: { message: 'api is undefined' },
153
+ stack: 'abc',
154
+ },
155
+ errorType: bizErrorType,
156
+ provider,
157
+ });
158
+ }
159
+ });
160
+
161
+ it('should throw an InvalidTaichuAPIKey error type on 401 status code', async () => {
162
+ // Mock the API call to simulate a 401 error
163
+ const error = new Error('Unauthorized') as any;
164
+ error.status = 401;
165
+ vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);
166
+
167
+ try {
168
+ await instance.chat({
169
+ messages: [{ content: 'Hello', role: 'user' }],
170
+ model: 'Taichu4',
171
+ temperature: 0,
172
+ });
173
+ } catch (e) {
174
+ // Expect the chat method to throw an error with InvalidTaichuAPIKey
175
+ expect(e).toEqual({
176
+ endpoint: defaultBaseURL,
177
+ error: new Error('Unauthorized'),
178
+ errorType: invalidErrorType,
179
+ provider,
180
+ });
181
+ }
182
+ });
183
+
184
+ it('should return AgentRuntimeError for non-OpenAI errors', async () => {
185
+ // Arrange
186
+ const genericError = new Error('Generic Error');
187
+
188
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(genericError);
189
+
190
+ // Act
191
+ try {
192
+ await instance.chat({
193
+ messages: [{ content: 'Hello', role: 'user' }],
194
+ model: 'Taichu4',
195
+ temperature: 0,
196
+ });
197
+ } catch (e) {
198
+ expect(e).toEqual({
199
+ endpoint: defaultBaseURL,
200
+ errorType: 'AgentRuntimeError',
201
+ provider,
202
+ error: {
203
+ name: genericError.name,
204
+ cause: genericError.cause,
205
+ message: genericError.message,
206
+ stack: genericError.stack,
207
+ },
208
+ });
209
+ }
210
+ });
211
+ });
212
+
213
+ describe('DEBUG', () => {
214
+ it('should call debugStream and return StreamingTextResponse when DEBUG_TAICHU_CHAT_COMPLETION is 1', async () => {
215
+ // Arrange
216
+ const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流
217
+ const mockDebugStream = new ReadableStream({
218
+ start(controller) {
219
+ controller.enqueue('Debug stream content');
220
+ controller.close();
221
+ },
222
+ }) as any;
223
+ mockDebugStream.toReadableStream = () => mockDebugStream; // 添加 toReadableStream 方法
224
+
225
+ // 模拟 chat.completions.create 返回值,包括模拟的 tee 方法
226
+ (instance['client'].chat.completions.create as Mock).mockResolvedValue({
227
+ tee: () => [mockProdStream, { toReadableStream: () => mockDebugStream }],
228
+ });
229
+
230
+ // 保存原始环境变量值
231
+ const originalDebugValue = process.env.DEBUG_TAICHU_CHAT_COMPLETION;
232
+
233
+ // 模拟环境变量
234
+ process.env.DEBUG_TAICHU_CHAT_COMPLETION = '1';
235
+ vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());
236
+
237
+ // 执行测试
238
+ // 运行你的测试函数,确保它会在条件满足时调用 debugStream
239
+ // 假设的测试函数调用,你可能需要根据实际情况调整
240
+ await instance.chat({
241
+ messages: [{ content: 'Hello', role: 'user' }],
242
+ model: 'Taichu4',
243
+ stream: true,
244
+ temperature: 0,
245
+ });
246
+
247
+ // 验证 debugStream 被调用
248
+ expect(debugStreamModule.debugStream).toHaveBeenCalled();
249
+
250
+ // 恢复原始环境变量值
251
+ process.env.DEBUG_TAICHU_CHAT_COMPLETION = originalDebugValue;
252
+ });
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,10 @@
1
+ import { ModelProvider } from '../types';
2
+ import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
+
4
+ export const LobeTaichuAI = LobeOpenAICompatibleFactory({
5
+ baseURL: 'https://ai-maas.wair.ac.cn/maas/v1',
6
+ debug: {
7
+ chatCompletion: () => process.env.DEBUG_TAICHU_CHAT_COMPLETION === '1',
8
+ },
9
+ provider: ModelProvider.Taichu,
10
+ });
@@ -38,6 +38,7 @@ export enum ModelProvider {
38
38
  Perplexity = 'perplexity',
39
39
  Qwen = 'qwen',
40
40
  Stepfun = 'stepfun',
41
+ Taichu = 'taichu',
41
42
  TogetherAI = 'togetherai',
42
43
  ZeroOne = 'zeroone',
43
44
  ZhiPu = 'zhipu',
@@ -46,3 +46,8 @@ export const debugStream = async (stream: ReadableStream) => {
46
46
  }
47
47
  }
48
48
  };
49
+
50
+ export const debugResponse = (response: any) => {
51
+ console.log(`\n[no stream response] ${getTime()}\n`);
52
+ console.log(JSON.stringify(response) + '\n');
53
+ };
@@ -8,7 +8,7 @@ import { LobeRuntimeAI } from '../../BaseAI';
8
8
  import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../error';
9
9
  import { ChatCompetitionOptions, ChatCompletionErrorPayload, ChatStreamPayload } from '../../types';
10
10
  import { AgentRuntimeError } from '../createError';
11
- import { debugStream } from '../debugStream';
11
+ import { debugResponse, debugStream } from '../debugStream';
12
12
  import { desensitizeUrl } from '../desensitizeUrl';
13
13
  import { handleOpenAIError } from '../handleOpenAIError';
14
14
  import { StreamingResponse } from '../response';
@@ -106,8 +106,7 @@ export const LobeOpenAICompatibleFactory = ({
106
106
  }
107
107
 
108
108
  if (debug?.chatCompletion?.()) {
109
- console.log('\n[no stream response]\n');
110
- console.log(JSON.stringify(response) + '\n');
109
+ debugResponse(response);
111
110
  }
112
111
 
113
112
  const stream = this.transformResponseToStream(response as unknown as OpenAI.ChatCompletion);
@@ -35,6 +35,7 @@ export const getServerGlobalConfig = () => {
35
35
  ENABLED_QWEN,
36
36
  ENABLED_STEPFUN,
37
37
  ENABLED_BAICHUAN,
38
+ ENABLED_TAICHU,
38
39
 
39
40
  ENABLED_AZURE_OPENAI,
40
41
  AZURE_MODEL_LIST,
@@ -109,6 +110,7 @@ export const getServerGlobalConfig = () => {
109
110
 
110
111
  stepfun: { enabled: ENABLED_STEPFUN },
111
112
 
113
+ taichu: { enabled: ENABLED_TAICHU },
112
114
  togetherai: {
113
115
  enabled: ENABLED_TOGETHERAI,
114
116
  enabledModels: extractEnabledModels(TOGETHERAI_MODEL_LIST),
@@ -34,6 +34,7 @@ export interface UserKeyVaults {
34
34
  perplexity?: OpenAICompatibleKeyVault;
35
35
  qwen?: OpenAICompatibleKeyVault;
36
36
  stepfun?: OpenAICompatibleKeyVault;
37
+ taichu?: OpenAICompatibleKeyVault;
37
38
  togetherai?: OpenAICompatibleKeyVault;
38
39
  zeroone?: OpenAICompatibleKeyVault;
39
40
  zhipu?: OpenAICompatibleKeyVault;