@lobehub/chat 1.65.0 → 1.65.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/app/(backend)/middleware/auth/index.ts +6 -0
- package/src/const/message.ts +3 -0
- package/src/libs/agent-runtime/google/index.test.ts +8 -0
- package/src/libs/agent-runtime/google/index.ts +18 -5
- package/src/libs/agent-runtime/types/chat.ts +9 -1
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +113 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +7 -4
- package/src/libs/agent-runtime/utils/streams/anthropic.test.ts +339 -94
- package/src/libs/agent-runtime/utils/streams/anthropic.ts +54 -34
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +181 -0
- package/src/libs/agent-runtime/utils/streams/openai.ts +40 -30
- package/src/libs/agent-runtime/utils/streams/protocol.ts +4 -0
- package/src/services/__tests__/chat.test.ts +89 -50
- package/src/services/chat.ts +13 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
- package/src/types/message/base.ts +1 -0
- package/src/utils/fetch/__tests__/fetchSSE.test.ts +113 -10
- package/src/utils/fetch/fetchSSE.ts +12 -3
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.65.1](https://github.com/lobehub/lobe-chat/compare/v1.65.0...v1.65.1)
|
6
|
+
|
7
|
+
<sup>Released on **2025-02-26**</sup>
|
8
|
+
|
9
|
+
#### 🐛 Bug Fixes
|
10
|
+
|
11
|
+
- **misc**: Fix claude 3.7 sonnet thinking with tool use.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's fixed
|
19
|
+
|
20
|
+
- **misc**: Fix claude 3.7 sonnet thinking with tool use, closes [#6528](https://github.com/lobehub/lobe-chat/issues/6528) ([a76d2bf](https://github.com/lobehub/lobe-chat/commit/a76d2bf))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
## [Version 1.65.0](https://github.com/lobehub/lobe-chat/compare/v1.64.3...v1.65.0)
|
6
31
|
|
7
32
|
<sup>Released on **2025-02-25**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.65.
|
3
|
+
"version": "1.65.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",
|
@@ -23,6 +23,12 @@ export type RequestHandler = (
|
|
23
23
|
|
24
24
|
export const checkAuth =
|
25
25
|
(handler: RequestHandler) => async (req: Request, options: RequestOptions) => {
|
26
|
+
// we have a special header to debug the api endpoint in development mode
|
27
|
+
const isDebugApi = req.headers.get('lobe-auth-dev-backend-api') === '1';
|
28
|
+
if (process.env.NODE_ENV === 'development' && isDebugApi) {
|
29
|
+
return handler(req, { ...options, jwtPayload: { userId: 'DEV_USER' } });
|
30
|
+
}
|
31
|
+
|
26
32
|
let jwtPayload: JWTPayload;
|
27
33
|
|
28
34
|
try {
|
package/src/const/message.ts
CHANGED
@@ -449,6 +449,14 @@ describe('LobeGoogleAI', () => {
|
|
449
449
|
});
|
450
450
|
expect(result).toEqual({ text: 'Hello' });
|
451
451
|
});
|
452
|
+
it('should handle thinking type messages', async () => {
|
453
|
+
const result = await instance['convertContentToGooglePart']({
|
454
|
+
type: 'thinking',
|
455
|
+
thinking: 'Hello',
|
456
|
+
signature: 'abc',
|
457
|
+
});
|
458
|
+
expect(result).toEqual(undefined);
|
459
|
+
});
|
452
460
|
|
453
461
|
it('should handle base64 type images', async () => {
|
454
462
|
const base64Image =
|
@@ -208,11 +208,18 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
208
208
|
system: system_message?.content,
|
209
209
|
};
|
210
210
|
}
|
211
|
-
private convertContentToGooglePart = async (
|
211
|
+
private convertContentToGooglePart = async (
|
212
|
+
content: UserMessageContentPart,
|
213
|
+
): Promise<Part | undefined> => {
|
212
214
|
switch (content.type) {
|
215
|
+
default: {
|
216
|
+
return undefined;
|
217
|
+
}
|
218
|
+
|
213
219
|
case 'text': {
|
214
220
|
return { text: content.text };
|
215
221
|
}
|
222
|
+
|
216
223
|
case 'image_url': {
|
217
224
|
const { mimeType, base64, type } = parseDataUri(content.image_url.url);
|
218
225
|
|
@@ -261,11 +268,17 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
261
268
|
};
|
262
269
|
}
|
263
270
|
|
271
|
+
const getParts = async () => {
|
272
|
+
if (typeof content === 'string') return [{ text: content }];
|
273
|
+
|
274
|
+
const parts = await Promise.all(
|
275
|
+
content.map(async (c) => await this.convertContentToGooglePart(c)),
|
276
|
+
);
|
277
|
+
return parts.filter(Boolean) as Part[];
|
278
|
+
};
|
279
|
+
|
264
280
|
return {
|
265
|
-
parts:
|
266
|
-
typeof content === 'string'
|
267
|
-
? [{ text: content }]
|
268
|
-
: await Promise.all(content.map(async (c) => await this.convertContentToGooglePart(c))),
|
281
|
+
parts: await getParts(),
|
269
282
|
role: message.role === 'assistant' ? 'model' : 'user',
|
270
283
|
};
|
271
284
|
};
|
@@ -2,6 +2,11 @@ import { MessageToolCall } from '@/types/message';
|
|
2
2
|
|
3
3
|
export type LLMRoleType = 'user' | 'system' | 'assistant' | 'function' | 'tool';
|
4
4
|
|
5
|
+
interface UserMessageContentPartThinking {
|
6
|
+
signature: string;
|
7
|
+
thinking: string;
|
8
|
+
type: 'thinking';
|
9
|
+
}
|
5
10
|
interface UserMessageContentPartText {
|
6
11
|
text: string;
|
7
12
|
type: 'text';
|
@@ -15,7 +20,10 @@ interface UserMessageContentPartImage {
|
|
15
20
|
type: 'image_url';
|
16
21
|
}
|
17
22
|
|
18
|
-
export type UserMessageContentPart =
|
23
|
+
export type UserMessageContentPart =
|
24
|
+
| UserMessageContentPartText
|
25
|
+
| UserMessageContentPartImage
|
26
|
+
| UserMessageContentPartThinking;
|
19
27
|
|
20
28
|
export interface OpenAIChatMessage {
|
21
29
|
/**
|
@@ -383,6 +383,119 @@ describe('anthropicHelpers', () => {
|
|
383
383
|
{ content: '继续', role: 'user' },
|
384
384
|
]);
|
385
385
|
});
|
386
|
+
|
387
|
+
it('should correctly handle thinking content part', async () => {
|
388
|
+
const messages: OpenAIChatMessage[] = [
|
389
|
+
{
|
390
|
+
content: '告诉我杭州和北京的天气,先回答我好的',
|
391
|
+
role: 'user',
|
392
|
+
},
|
393
|
+
{
|
394
|
+
content: [
|
395
|
+
{ thinking: '经过一番思考', type: 'thinking', signature: '123' },
|
396
|
+
{
|
397
|
+
type: 'text',
|
398
|
+
text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
|
399
|
+
},
|
400
|
+
],
|
401
|
+
role: 'assistant',
|
402
|
+
tool_calls: [
|
403
|
+
{
|
404
|
+
function: {
|
405
|
+
arguments: '{"city": "\\u676d\\u5dde"}',
|
406
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
407
|
+
},
|
408
|
+
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
|
409
|
+
type: 'function',
|
410
|
+
},
|
411
|
+
{
|
412
|
+
function: {
|
413
|
+
arguments: '{"city": "\\u5317\\u4eac"}',
|
414
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
415
|
+
},
|
416
|
+
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
|
417
|
+
type: 'function',
|
418
|
+
},
|
419
|
+
],
|
420
|
+
},
|
421
|
+
{
|
422
|
+
content:
|
423
|
+
'[{"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"}]}]',
|
424
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
425
|
+
role: 'tool',
|
426
|
+
tool_call_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
|
427
|
+
},
|
428
|
+
{
|
429
|
+
content:
|
430
|
+
'[{"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"}]}]',
|
431
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
432
|
+
role: 'tool',
|
433
|
+
tool_call_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
|
434
|
+
},
|
435
|
+
{
|
436
|
+
content: '继续',
|
437
|
+
role: 'user',
|
438
|
+
},
|
439
|
+
];
|
440
|
+
|
441
|
+
const contents = await buildAnthropicMessages(messages);
|
442
|
+
|
443
|
+
expect(contents).toEqual([
|
444
|
+
{ content: '告诉我杭州和北京的天气,先回答我好的', role: 'user' },
|
445
|
+
{
|
446
|
+
content: [
|
447
|
+
{
|
448
|
+
signature: '123',
|
449
|
+
thinking: '经过一番思考',
|
450
|
+
type: 'thinking',
|
451
|
+
},
|
452
|
+
{
|
453
|
+
text: '好的,我会为您查询杭州和北京的天气信息。我现在就开始查询这两个城市的当前天气情况。',
|
454
|
+
type: 'text',
|
455
|
+
},
|
456
|
+
{
|
457
|
+
id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
|
458
|
+
input: { city: '杭州' },
|
459
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
460
|
+
type: 'tool_use',
|
461
|
+
},
|
462
|
+
{
|
463
|
+
id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
|
464
|
+
input: { city: '北京' },
|
465
|
+
name: 'realtime-weather____fetchCurrentWeather',
|
466
|
+
type: 'tool_use',
|
467
|
+
},
|
468
|
+
],
|
469
|
+
role: 'assistant',
|
470
|
+
},
|
471
|
+
{
|
472
|
+
content: [
|
473
|
+
{
|
474
|
+
content: [
|
475
|
+
{
|
476
|
+
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"}]}]',
|
477
|
+
type: 'text',
|
478
|
+
},
|
479
|
+
],
|
480
|
+
tool_use_id: 'toolu_018PNQkH8ChbjoJz4QBiFVod',
|
481
|
+
type: 'tool_result',
|
482
|
+
},
|
483
|
+
{
|
484
|
+
content: [
|
485
|
+
{
|
486
|
+
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"}]}]',
|
487
|
+
type: 'text',
|
488
|
+
},
|
489
|
+
],
|
490
|
+
tool_use_id: 'toolu_018VQTQ6fwAEC3eppuEfMxPp',
|
491
|
+
type: 'tool_result',
|
492
|
+
},
|
493
|
+
],
|
494
|
+
role: 'user',
|
495
|
+
},
|
496
|
+
{ content: '继续', role: 'user' },
|
497
|
+
]);
|
498
|
+
});
|
386
499
|
});
|
387
500
|
|
388
501
|
describe('buildAnthropicTools', () => {
|
@@ -10,6 +10,7 @@ export const buildAnthropicBlock = async (
|
|
10
10
|
content: UserMessageContentPart,
|
11
11
|
): Promise<Anthropic.ContentBlock | Anthropic.ImageBlockParam> => {
|
12
12
|
switch (content.type) {
|
13
|
+
case 'thinking':
|
13
14
|
case 'text': {
|
14
15
|
// just pass-through the content
|
15
16
|
return content as any;
|
@@ -83,13 +84,15 @@ export const buildAnthropicMessage = async (
|
|
83
84
|
// if there is tool_calls , we need to covert the tool_calls to tool_use content block
|
84
85
|
// refs: https://docs.anthropic.com/claude/docs/tool-use#tool-use-and-tool-result-content-blocks
|
85
86
|
if (message.tool_calls) {
|
87
|
+
const messageContent =
|
88
|
+
typeof content === 'string'
|
89
|
+
? [{ text: message.content, type: 'text' }]
|
90
|
+
: await Promise.all(content.map(async (c) => await buildAnthropicBlock(c)));
|
91
|
+
|
86
92
|
return {
|
87
93
|
content: [
|
88
94
|
// avoid empty text content block
|
89
|
-
|
90
|
-
text: message.content as string,
|
91
|
-
type: 'text',
|
92
|
-
},
|
95
|
+
...messageContent,
|
93
96
|
...(message.tool_calls.map((tool) => ({
|
94
97
|
id: tool.id,
|
95
98
|
input: JSON.parse(tool.function.arguments),
|