@lobehub/chat 1.66.1 → 1.66.2

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.66.2](https://github.com/lobehub/lobe-chat/compare/v1.66.1...v1.66.2)
6
+
7
+ <sup>Released on **2025-02-27**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Update Claude sonnet 3.7 model ID.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Update Claude sonnet 3.7 model ID, closes [#6567](https://github.com/lobehub/lobe-chat/issues/6567) ([d1039d6](https://github.com/lobehub/lobe-chat/commit/d1039d6))
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.66.1](https://github.com/lobehub/lobe-chat/compare/v1.66.0...v1.66.1)
6
31
 
7
32
  <sup>Released on **2025-02-27**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Update Claude sonnet 3.7 model ID."
6
+ ]
7
+ },
8
+ "date": "2025-02-27",
9
+ "version": "1.66.2"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.66.1",
3
+ "version": "1.66.2",
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",
@@ -33,7 +33,7 @@ const bedrockChatModels: AIChatModelCard[] = [
33
33
  'Claude 3.7 sonnet 是 Anthropic 最快的下一代模型。与 Claude 3 Haiku 相比,Claude 3.7 Sonnet 在各项技能上都有所提升,并在许多智力基准测试中超越了上一代最大的模型 Claude 3 Opus。',
34
34
  displayName: 'Claude 3.7 Sonnet',
35
35
  enabled: true,
36
- id: 'anthropic.claude-3-7-sonnet-20250219-v1:0',
36
+ id: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
37
37
  maxOutput: 8192,
38
38
  pricing: {
39
39
  input: 3,
@@ -33,7 +33,7 @@ const Bedrock: ModelProviderCard = {
33
33
  displayName: 'Claude 3.7 Sonnet',
34
34
  enabled: true,
35
35
  functionCall: true,
36
- id: 'anthropic.claude-3-7-sonnet-20250219-v1:0',
36
+ id: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
37
37
  maxOutput: 8192,
38
38
  pricing: {
39
39
  cachedInput: 0.1,
@@ -50,7 +50,7 @@ const Bedrock: ModelProviderCard = {
50
50
  displayName: 'Claude 3.7 Sonnet Extended thinking',
51
51
  enabled: true,
52
52
  functionCall: true,
53
- id: 'anthropic.claude-3-7-sonnet-20250219-v1:0',
53
+ id: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
54
54
  maxOutput: 64_000,
55
55
  pricing: {
56
56
  cachedInput: 0.1,
@@ -19,7 +19,7 @@ export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
19
19
  enableAutoCreateTopic: true,
20
20
  enableCompressHistory: true,
21
21
  enableHistoryCount: true,
22
- enableReasoning: true,
22
+ enableReasoning: false,
23
23
  historyCount: 8,
24
24
  reasoningBudgetToken: 1024,
25
25
  searchMode: 'off',
@@ -228,22 +228,7 @@ describe('anthropicHelpers', () => {
228
228
  ]);
229
229
  });
230
230
 
231
- it('messages should start with user', async () => {
232
- const messages: OpenAIChatMessage[] = [
233
- { content: 'Hi', role: 'assistant' },
234
- { content: 'Hello', role: 'user' },
235
- ];
236
-
237
- const contents = await buildAnthropicMessages(messages);
238
-
239
- expect(contents).toHaveLength(2);
240
- expect(contents).toEqual([
241
- { content: 'Hi', role: 'user' },
242
- { content: 'Hello', role: 'user' },
243
- ]);
244
- });
245
-
246
- it('messages should end with user', async () => {
231
+ it('messages should dont need end with user', async () => {
247
232
  const messages: OpenAIChatMessage[] = [
248
233
  { content: 'Hello', role: 'user' },
249
234
  { content: 'Hello', role: 'user' },
@@ -260,128 +245,266 @@ describe('anthropicHelpers', () => {
260
245
  ]);
261
246
  });
262
247
 
263
- it('messages should pair', async () => {
264
- const messages: OpenAIChatMessage[] = [
265
- { content: 'a', role: 'assistant' },
266
- { content: 'b', role: 'assistant' },
267
- { content: 'c', role: 'assistant' },
268
- { content: 'd', role: 'assistant' },
269
- { content: '你好', role: 'user' },
270
- ];
248
+ describe('Tool messages', () => {
249
+ it('should handle empty tools', async () => {
250
+ const messages: OpenAIChatMessage[] = [
251
+ {
252
+ content: '## Tools\n\nYou can use these tools',
253
+ role: 'user',
254
+ },
255
+ {
256
+ content: '',
257
+ role: 'assistant',
258
+ tool_calls: [],
259
+ },
260
+ ];
271
261
 
272
- const contents = await buildAnthropicMessages(messages);
262
+ const contents = await buildAnthropicMessages(messages);
273
263
 
274
- expect(contents).toHaveLength(5);
275
- expect(contents).toEqual([
276
- { content: 'a', role: 'user' },
277
- { content: 'b', role: 'assistant' },
278
- { content: 'c', role: 'assistant' },
279
- { content: 'd', role: 'assistant' },
280
- { content: '你好', role: 'user' },
281
- ]);
282
- });
264
+ expect(contents).toEqual([
265
+ {
266
+ content: '## Tools\n\nYou can use these tools',
267
+ role: 'user',
268
+ },
269
+ {
270
+ content: '',
271
+ role: 'assistant',
272
+ },
273
+ ]);
274
+ });
275
+ it('should correctly convert OpenAI tool message to Anthropic format', async () => {
276
+ const messages: OpenAIChatMessage[] = [
277
+ {
278
+ content: '告诉我杭州和北京的天气,先回答我好的',
279
+ role: 'user',
280
+ },
281
+ {
282
+ content:
283
+ '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
284
+ role: 'assistant',
285
+ tool_calls: [
286
+ {
287
+ function: {
288
+ arguments: '{"city": "\\u676d\\u5dde"}',
289
+ name: 'realtime-weather____fetchCurrentWeather',
290
+ },
291
+ id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
292
+ type: 'function',
293
+ },
294
+ {
295
+ function: {
296
+ arguments: '{"city": "\\u5317\\u4eac"}',
297
+ name: 'realtime-weather____fetchCurrentWeather',
298
+ },
299
+ id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
300
+ type: 'function',
301
+ },
302
+ ],
303
+ },
304
+ {
305
+ content:
306
+ '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
307
+ name: 'realtime-weather____fetchCurrentWeather',
308
+ role: 'tool',
309
+ tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
310
+ },
311
+ {
312
+ content:
313
+ '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
314
+ name: 'realtime-weather____fetchCurrentWeather',
315
+ role: 'tool',
316
+ tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
317
+ },
318
+ {
319
+ content: '继续',
320
+ role: 'user',
321
+ },
322
+ ];
283
323
 
284
- it('should correctly convert OpenAI tool message to Anthropic format', async () => {
285
- const messages: OpenAIChatMessage[] = [
286
- {
287
- content: '告诉我杭州和北京的天气,先回答我好的',
288
- role: 'user',
289
- },
290
- {
291
- content:
292
- '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
293
- role: 'assistant',
294
- tool_calls: [
295
- {
296
- function: {
297
- arguments: '{"city": "\\u676d\\u5dde"}',
324
+ const contents = await buildAnthropicMessages(messages);
325
+
326
+ expect(contents).toEqual([
327
+ { content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
328
+ {
329
+ content: [
330
+ {
331
+ text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
332
+ type: 'text',
333
+ },
334
+ {
335
+ id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
336
+ input: { city: '杭州' },
298
337
  name: 'realtime-weather____fetchCurrentWeather',
338
+ type: 'tool_use',
299
339
  },
300
- id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
301
- type: 'function',
302
- },
303
- {
304
- function: {
305
- arguments: '{"city": "\\u5317\\u4eac"}',
340
+ {
341
+ id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
342
+ input: { city: '北京' },
306
343
  name: 'realtime-weather____fetchCurrentWeather',
344
+ type: 'tool_use',
307
345
  },
308
- id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
309
- type: 'function',
310
- },
311
- ],
312
- },
313
- {
314
- content:
315
- '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
316
- name: 'realtime-weather____fetchCurrentWeather',
317
- role: 'tool',
318
- tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
319
- },
320
- {
321
- content:
322
- '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
323
- name: 'realtime-weather____fetchCurrentWeather',
324
- role: 'tool',
325
- tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
326
- },
327
- {
328
- content: '继续',
329
- role: 'user',
330
- },
331
- ];
346
+ ],
347
+ role: 'assistant',
348
+ },
349
+ {
350
+ content: [
351
+ {
352
+ content: [
353
+ {
354
+ text: '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
355
+ type: 'text',
356
+ },
357
+ ],
358
+ tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
359
+ type: 'tool_result',
360
+ },
361
+ {
362
+ content: [
363
+ {
364
+ text: '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
365
+ type: 'text',
366
+ },
367
+ ],
368
+ tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
369
+ type: 'tool_result',
370
+ },
371
+ ],
372
+ role: 'user',
373
+ },
374
+ { content: '继续', role: 'user' },
375
+ ]);
376
+ });
377
+ it('should handle user messages with tool correctly', async () => {
378
+ const messages: OpenAIChatMessage[] = [
379
+ {
380
+ content: '搜索下 482的所有质因数?\n\n',
381
+ role: 'user',
382
+ },
383
+ {
384
+ content: '',
385
+ role: 'assistant',
386
+ tool_calls: [
387
+ {
388
+ function: {
389
+ arguments: '{"query": "482的质因数分解"}',
390
+ name: 'searchWithSearXNG',
391
+ },
392
+ id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
393
+ type: 'function',
394
+ },
395
+ ],
396
+ },
397
+ {
398
+ content:
399
+ '[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
400
+ name: 'searchWithSearXNG',
401
+ role: 'tool',
402
+ tool_call_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
403
+ },
404
+ ];
332
405
 
333
- const contents = await buildAnthropicMessages(messages);
406
+ const contents = await buildAnthropicMessages(messages);
334
407
 
335
- expect(contents).toEqual([
336
- { content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
337
- {
338
- content: [
339
- {
340
- text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
341
- type: 'text',
342
- },
343
- {
344
- id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
345
- input: { city: '杭州' },
346
- name: 'realtime-weather____fetchCurrentWeather',
347
- type: 'tool_use',
348
- },
349
- {
350
- id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
351
- input: { city: '北京' },
352
- name: 'realtime-weather____fetchCurrentWeather',
353
- type: 'tool_use',
354
- },
355
- ],
356
- role: 'assistant',
357
- },
358
- {
359
- content: [
360
- {
361
- content: [
362
- {
363
- text: '[{"city":"杭州市","adcode":"330100","province":"浙江","reporttime":"2024-06-24 17:02:14","casts":[{"date":"2024-06-24","week":"1","dayweather":"小雨","nightweather":"中雨","daytemp":"26","nighttemp":"20","daywind":"西","nightwind":"西","daypower":"1-3","nightpower":"1-3","daytemp_float":"26.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"大雨","nightweather":"中雨","daytemp":"23","nighttemp":"19","daywind":"东","nightwind":"东","daypower":"1-3","nightpower":"1-3","daytemp_float":"23.0","nighttemp_float":"19.0"},{"date":"2024-06-26","week":"3","dayweather":"中雨","nightweather":"中雨","daytemp":"24","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"21.0"},{"date":"2024-06-27","week":"4","dayweather":"中雨-大雨","nightweather":"中雨","daytemp":"24","nighttemp":"22","daywind":"南","nightwind":"南","daypower":"1-3","nightpower":"1-3","daytemp_float":"24.0","nighttemp_float":"22.0"}]}]',
364
- type: 'text',
408
+ expect(contents).toEqual([
409
+ { content: '搜索下 482的所有质因数?\n\n', role: 'user' },
410
+ {
411
+ content: [
412
+ {
413
+ id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
414
+ input: { query: '482的质因数分解' },
415
+ name: 'searchWithSearXNG',
416
+ type: 'tool_use',
417
+ },
418
+ ],
419
+ role: 'assistant',
420
+ },
421
+ {
422
+ content: [
423
+ {
424
+ content: [
425
+ {
426
+ text: '[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
427
+ type: 'text',
428
+ },
429
+ ],
430
+ tool_use_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
431
+ type: 'tool_result',
432
+ },
433
+ ],
434
+ role: 'user',
435
+ },
436
+ ]);
437
+ });
438
+
439
+ it('should work well starting with tool message', async () => {
440
+ const messages: OpenAIChatMessage[] = [
441
+ {
442
+ content:
443
+ '[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
444
+ name: 'searchWithSearXNG',
445
+ role: 'tool',
446
+ tool_call_id: 'toolu_01AgNoyb9FKuY8TGePPjEfrE',
447
+ },
448
+ {
449
+ content: '',
450
+ role: 'assistant',
451
+ tool_calls: [
452
+ {
453
+ function: {
454
+ arguments: '{"query": "杭州有啥好吃的"}',
455
+ name: 'searchWithSearXNG',
365
456
  },
366
- ],
367
- tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
368
- type: 'tool_result',
369
- },
370
- {
371
- content: [
372
- {
373
- text: '[{"city":"北京市","adcode":"110000","province":"北京","reporttime":"2024-06-24 17:03:11","casts":[{"date":"2024-06-24","week":"1","dayweather":"晴","nightweather":"晴","daytemp":"33","nighttemp":"20","daywind":"北","nightwind":"北","daypower":"1-3","nightpower":"1-3","daytemp_float":"33.0","nighttemp_float":"20.0"},{"date":"2024-06-25","week":"2","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"21","daywind":"东南","nightwind":"东南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"21.0"},{"date":"2024-06-26","week":"3","dayweather":"晴","nightweather":"晴","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"},{"date":"2024-06-27","week":"4","dayweather":"多云","nightweather":"多云","daytemp":"35","nighttemp":"23","daywind":"西南","nightwind":"西南","daypower":"1-3","nightpower":"1-3","daytemp_float":"35.0","nighttemp_float":"23.0"}]}]',
374
- type: 'text',
457
+ id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
458
+ type: 'function',
459
+ },
460
+ ],
461
+ },
462
+ {
463
+ content: '[{"content":"没啥好吃的","title":"该数性质482","url":"e.com/482"}]',
464
+ name: 'searchWithSearXNG',
465
+ role: 'tool',
466
+ tool_call_id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
467
+ },
468
+ ];
469
+
470
+ const contents = await buildAnthropicMessages(messages);
471
+
472
+ expect(contents).toEqual([
473
+ {
474
+ content:
475
+ '[{"content":"因式分解, 2 * 241 ; 因数, 1, 2, 241, 482 ; 因数个数, 4 ; 因数和, 726 ; 前一个整数, 481.","title":"该数性质482","url":"https://zh.numberempire.com/482"}]',
476
+ role: 'user',
477
+ },
478
+ {
479
+ content: [
480
+ {
481
+ id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
482
+ input: {
483
+ query: '杭州有啥好吃的',
375
484
  },
376
- ],
377
- tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
378
- type: 'tool_result',
379
- },
380
- ],
381
- role: 'user',
382
- },
383
- { content: '继续', role: 'user' },
384
- ]);
485
+ name: 'searchWithSearXNG',
486
+ type: 'tool_use',
487
+ },
488
+ ],
489
+ role: 'assistant',
490
+ },
491
+ {
492
+ content: [
493
+ {
494
+ content: [
495
+ {
496
+ text: '[{"content":"没啥好吃的","title":"该数性质482","url":"e.com/482"}]',
497
+ type: 'text',
498
+ },
499
+ ],
500
+ tool_use_id: 'toolu_02AgNoyb9FKuY8TGePPjEfrE',
501
+ type: 'tool_result',
502
+ },
503
+ ],
504
+ role: 'user',
505
+ },
506
+ ]);
507
+ });
385
508
  });
386
509
 
387
510
  it('should correctly handle thinking content part', async () => {
@@ -8,14 +8,19 @@ import { parseDataUri } from './uriParser';
8
8
 
9
9
  export const buildAnthropicBlock = async (
10
10
  content: UserMessageContentPart,
11
- ): Promise<Anthropic.ContentBlock | Anthropic.ImageBlockParam> => {
11
+ ): Promise<Anthropic.ContentBlock | Anthropic.ImageBlockParam | undefined> => {
12
12
  switch (content.type) {
13
- case 'thinking':
14
- case 'text': {
13
+ case 'thinking': {
15
14
  // just pass-through the content
16
15
  return content as any;
17
16
  }
18
17
 
18
+ case 'text': {
19
+ if (!!content.text) return content as any;
20
+
21
+ return undefined;
22
+ }
23
+
19
24
  case 'image_url': {
20
25
  const { mimeType, base64, type } = parseDataUri(content.image_url.url);
21
26
 
@@ -46,6 +51,16 @@ export const buildAnthropicBlock = async (
46
51
  }
47
52
  };
48
53
 
54
+ const buildArrayContent = async (content: UserMessageContentPart[]) => {
55
+ let messageContent = (await Promise.all(
56
+ (content as UserMessageContentPart[]).map(async (c) => await buildAnthropicBlock(c)),
57
+ )) as Anthropic.Messages.ContentBlockParam[];
58
+
59
+ messageContent = messageContent.filter(Boolean);
60
+
61
+ return messageContent;
62
+ };
63
+
49
64
  export const buildAnthropicMessage = async (
50
65
  message: OpenAIChatMessage,
51
66
  ): Promise<Anthropic.Messages.MessageParam> => {
@@ -58,10 +73,7 @@ export const buildAnthropicMessage = async (
58
73
 
59
74
  case 'user': {
60
75
  return {
61
- content:
62
- typeof content === 'string'
63
- ? content
64
- : await Promise.all(content.map(async (c) => await buildAnthropicBlock(c))),
76
+ content: typeof content === 'string' ? content : await buildArrayContent(content),
65
77
  role: 'user',
66
78
  };
67
79
  }
@@ -83,11 +95,13 @@ export const buildAnthropicMessage = async (
83
95
  case 'assistant': {
84
96
  // if there is tool_calls , we need to covert the tool_calls to tool_use content block
85
97
  // refs: https://docs.anthropic.com/claude/docs/tool-use#tool-use-and-tool-result-content-blocks
86
- if (message.tool_calls) {
87
- const messageContent =
98
+ if (message.tool_calls && message.tool_calls.length > 0) {
99
+ const rawContent =
88
100
  typeof content === 'string'
89
- ? [{ text: message.content, type: 'text' }]
90
- : await Promise.all(content.map(async (c) => await buildAnthropicBlock(c)));
101
+ ? ([{ text: message.content, type: 'text' }] as UserMessageContentPart[])
102
+ : content;
103
+
104
+ const messageContent = await buildArrayContent(rawContent);
91
105
 
92
106
  return {
93
107
  content: [
@@ -120,39 +134,54 @@ export const buildAnthropicMessages = async (
120
134
  const messages: Anthropic.Messages.MessageParam[] = [];
121
135
  let pendingToolResults: Anthropic.ToolResultBlockParam[] = [];
122
136
 
137
+ // 首先收集所有 assistant 消息中的 tool_call_id 以便后续查找
138
+ const validToolCallIds = new Set<string>();
139
+ for (const message of oaiMessages) {
140
+ if (message.role === 'assistant' && message.tool_calls?.length) {
141
+ message.tool_calls.forEach((call) => {
142
+ if (call.id) {
143
+ validToolCallIds.add(call.id);
144
+ }
145
+ });
146
+ }
147
+ }
148
+
123
149
  for (const message of oaiMessages) {
124
150
  const index = oaiMessages.indexOf(message);
125
151
 
126
152
  // refs: https://docs.anthropic.com/claude/docs/tool-use#tool-use-and-tool-result-content-blocks
127
153
  if (message.role === 'tool') {
128
- pendingToolResults.push({
129
- content: [{ text: message.content as string, type: 'text' }],
130
- tool_use_id: message.tool_call_id!,
131
- type: 'tool_result',
132
- });
154
+ // 检查这个工具消息是否有对应的 assistant 工具调用
155
+ if (message.tool_call_id && validToolCallIds.has(message.tool_call_id)) {
156
+ pendingToolResults.push({
157
+ content: [{ text: message.content as string, type: 'text' }],
158
+ tool_use_id: message.tool_call_id,
159
+ type: 'tool_result',
160
+ });
133
161
 
134
- // If this is the last message or the next message is not a 'tool' message,
135
- // we add the accumulated tool results as a single 'user' message
136
- if (index === oaiMessages.length - 1 || oaiMessages[index + 1].role !== 'tool') {
162
+ // 如果这是最后一个消息或者下一个消息不是 'tool',则添加累积的工具结果作为一个 'user' 消息
163
+ if (index === oaiMessages.length - 1 || oaiMessages[index + 1].role !== 'tool') {
164
+ messages.push({
165
+ content: pendingToolResults,
166
+ role: 'user',
167
+ });
168
+ pendingToolResults = [];
169
+ }
170
+ } else {
171
+ // 如果工具消息没有对应的 assistant 工具调用,则作为普通文本处理
137
172
  messages.push({
138
- content: pendingToolResults,
173
+ content: message.content as string,
139
174
  role: 'user',
140
175
  });
141
- pendingToolResults = [];
142
176
  }
143
177
  } else {
144
178
  const anthropicMessage = await buildAnthropicMessage(message);
145
-
146
- messages.push({
147
- ...anthropicMessage,
148
- role: index === 0 && anthropicMessage.role === 'assistant' ? 'user' : anthropicMessage.role,
149
- });
179
+ messages.push({ ...anthropicMessage, role: anthropicMessage.role });
150
180
  }
151
181
  }
152
182
 
153
183
  return messages;
154
184
  };
155
-
156
185
  export const buildAnthropicTools = (tools?: OpenAI.ChatCompletionTool[]) =>
157
186
  tools?.map(
158
187
  (tool): Anthropic.Tool => ({
@@ -910,6 +910,37 @@ describe('ChatService', () => {
910
910
  });
911
911
  });
912
912
 
913
+ it('should handle empty tool calls messages correctly', () => {
914
+ const messages = [
915
+ {
916
+ content: '## Tools\n\nYou can use these tools',
917
+ role: 'system',
918
+ },
919
+ {
920
+ content: '',
921
+ role: 'assistant',
922
+ tool_calls: [],
923
+ },
924
+ ] as ChatMessage[];
925
+
926
+ const result = chatService['processMessages']({
927
+ messages,
928
+ model: 'gpt-4',
929
+ provider: 'openai',
930
+ });
931
+
932
+ expect(result).toEqual([
933
+ {
934
+ content: '## Tools\n\nYou can use these tools',
935
+ role: 'system',
936
+ },
937
+ {
938
+ content: '',
939
+ role: 'assistant',
940
+ },
941
+ ]);
942
+ });
943
+
913
944
  it('should handle assistant messages with reasoning correctly', () => {
914
945
  const messages = [
915
946
  {
@@ -8,7 +8,7 @@ exports[`agentSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CONFIG
8
8
  "enableAutoCreateTopic": true,
9
9
  "enableCompressHistory": true,
10
10
  "enableHistoryCount": true,
11
- "enableReasoning": true,
11
+ "enableReasoning": false,
12
12
  "historyCount": 8,
13
13
  "reasoningBudgetToken": 1024,
14
14
  "searchMode": "off",
@@ -75,7 +75,7 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
75
75
  "enableAutoCreateTopic": true,
76
76
  "enableCompressHistory": true,
77
77
  "enableHistoryCount": true,
78
- "enableReasoning": true,
78
+ "enableReasoning": false,
79
79
  "historyCount": 8,
80
80
  "reasoningBudgetToken": 1024,
81
81
  "searchMode": "off",
@@ -30,6 +30,7 @@ const useStyles = createStyles(({ css, token }) => ({
30
30
  -webkit-box-orient: vertical;
31
31
  -webkit-line-clamp: 2;
32
32
 
33
+ color: ${token.colorTextSecondary};
33
34
  text-overflow: ellipsis;
34
35
  `,
35
36
  url: css`