@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 +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/config/aiModels/bedrock.ts +1 -1
- package/src/config/modelProviders/bedrock.ts +2 -2
- package/src/const/settings/agent.ts +1 -1
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +250 -127
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +56 -27
- package/src/services/__tests__/chat.test.ts +31 -0
- package/src/store/agent/slices/chat/__snapshots__/selectors.test.ts.snap +1 -1
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +1 -1
- package/src/tools/web-browsing/Render/SearchResult/SearchResultItem.tsx +1 -0
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
|
+
[](#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
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.66.
|
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:
|
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
|
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
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
-
|
262
|
+
const contents = await buildAnthropicMessages(messages);
|
273
263
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
content: '告诉我杭州和北京的天气,先回答我好的',
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
301
|
-
|
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
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
406
|
+
const contents = await buildAnthropicMessages(messages);
|
334
407
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
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
|
-
:
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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:
|
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":
|
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":
|
78
|
+
"enableReasoning": false,
|
79
79
|
"historyCount": 8,
|
80
80
|
"reasoningBudgetToken": 1024,
|
81
81
|
"searchMode": "off",
|