@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix claude 3.7 sonnet thinking with tool use."
6
+ ]
7
+ },
8
+ "date": "2025-02-26",
9
+ "version": "1.65.1"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.65.0",
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 {
@@ -7,3 +7,6 @@ export const MESSAGE_THREAD_DIVIDER_ID = '__THREAD_DIVIDER__';
7
7
  export const MESSAGE_WELCOME_GUIDE_ID = 'welcome';
8
8
 
9
9
  export const THREAD_DRAFT_ID = '__THREAD_DRAFT_ID__';
10
+
11
+
12
+ export const MESSAGE_FLAGGED_THINKING='FLAGGED_THINKING'
@@ -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 (content: UserMessageContentPart): Promise<Part> => {
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 = UserMessageContentPartText | UserMessageContentPartImage;
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
- !!message.content && {
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),