@lobehub/chat 0.134.1 → 0.135.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 +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/package.json +3 -3
- package/src/app/api/plugin/store/route.ts +1 -1
- package/src/config/modelProviders/anthropic.ts +1 -2
- package/src/config/modelProviders/bedrock.ts +21 -5
- package/src/database/models/message.ts +88 -77
- package/src/database/models/plugin.ts +9 -4
- package/src/database/models/session.ts +93 -83
- package/src/database/models/sessionGroup.ts +38 -30
- package/src/database/models/topic.ts +104 -97
- package/src/database/models/user.ts +12 -3
- package/src/libs/agent-runtime/anthropic/index.ts +12 -55
- package/src/libs/agent-runtime/bedrock/index.test.ts +217 -0
- package/src/libs/agent-runtime/bedrock/index.ts +37 -16
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +59 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +47 -0
- package/src/libs/swr/index.ts +18 -0
- package/src/store/chat/slices/message/action.test.ts +1 -1
- package/src/store/chat/slices/message/action.ts +8 -9
- package/src/store/session/slices/session/action.ts +2 -1
|
@@ -2,15 +2,23 @@ import {
|
|
|
2
2
|
BedrockRuntimeClient,
|
|
3
3
|
InvokeModelWithResponseStreamCommand,
|
|
4
4
|
} from '@aws-sdk/client-bedrock-runtime';
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
AWSBedrockLlama2Stream,
|
|
7
|
+
AWSBedrockStream,
|
|
8
|
+
StreamingTextResponse
|
|
9
|
+
} from 'ai';
|
|
10
|
+
import { experimental_buildLlama2Prompt } from 'ai/prompts';
|
|
7
11
|
|
|
8
12
|
import { LobeRuntimeAI } from '../BaseAI';
|
|
9
13
|
import { AgentRuntimeErrorType } from '../error';
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
ChatCompetitionOptions,
|
|
16
|
+
ChatStreamPayload,
|
|
17
|
+
ModelProvider,
|
|
18
|
+
} from '../types';
|
|
11
19
|
import { AgentRuntimeError } from '../utils/createError';
|
|
12
20
|
import { debugStream } from '../utils/debugStream';
|
|
13
|
-
import {
|
|
21
|
+
import { buildAnthropicMessages } from '../utils/anthropicHelpers';
|
|
14
22
|
|
|
15
23
|
export interface LobeBedrockAIParams {
|
|
16
24
|
accessKeyId?: string;
|
|
@@ -38,23 +46,32 @@ export class LobeBedrockAI implements LobeRuntimeAI {
|
|
|
38
46
|
});
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
async chat(payload: ChatStreamPayload) {
|
|
49
|
+
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
|
42
50
|
if (payload.model.startsWith('meta')) return this.invokeLlamaModel(payload);
|
|
43
51
|
|
|
44
|
-
return this.invokeClaudeModel(payload);
|
|
52
|
+
return this.invokeClaudeModel(payload, options);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
private invokeClaudeModel = async (
|
|
48
56
|
payload: ChatStreamPayload,
|
|
57
|
+
options?: ChatCompetitionOptions
|
|
49
58
|
): Promise<StreamingTextResponse> => {
|
|
59
|
+
const { max_tokens, messages, model, temperature, top_p } = payload;
|
|
60
|
+
const system_message = messages.find((m) => m.role === 'system');
|
|
61
|
+
const user_messages = messages.filter((m) => m.role !== 'system');
|
|
62
|
+
|
|
50
63
|
const command = new InvokeModelWithResponseStreamCommand({
|
|
51
64
|
accept: 'application/json',
|
|
52
65
|
body: JSON.stringify({
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
anthropic_version: "bedrock-2023-05-31",
|
|
67
|
+
max_tokens: max_tokens || 4096,
|
|
68
|
+
messages: buildAnthropicMessages(user_messages),
|
|
69
|
+
system: system_message?.content as string,
|
|
70
|
+
temperature: temperature,
|
|
71
|
+
top_p: top_p,
|
|
55
72
|
}),
|
|
56
73
|
contentType: 'application/json',
|
|
57
|
-
modelId:
|
|
74
|
+
modelId: model,
|
|
58
75
|
});
|
|
59
76
|
|
|
60
77
|
try {
|
|
@@ -62,11 +79,11 @@ export class LobeBedrockAI implements LobeRuntimeAI {
|
|
|
62
79
|
const bedrockResponse = await this.client.send(command);
|
|
63
80
|
|
|
64
81
|
// Convert the response into a friendly text-stream
|
|
65
|
-
const stream =
|
|
82
|
+
const stream = AWSBedrockStream(bedrockResponse, options?.callback, (chunk) => chunk.delta?.text);
|
|
66
83
|
|
|
67
84
|
const [debug, output] = stream.tee();
|
|
68
85
|
|
|
69
|
-
if (
|
|
86
|
+
if (process.env.DEBUG_BEDROCK_CHAT_COMPLETION === '1') {
|
|
70
87
|
debugStream(debug).catch(console.error);
|
|
71
88
|
}
|
|
72
89
|
|
|
@@ -88,15 +105,18 @@ export class LobeBedrockAI implements LobeRuntimeAI {
|
|
|
88
105
|
}
|
|
89
106
|
};
|
|
90
107
|
|
|
91
|
-
private invokeLlamaModel = async (
|
|
108
|
+
private invokeLlamaModel = async (
|
|
109
|
+
payload: ChatStreamPayload
|
|
110
|
+
): Promise<StreamingTextResponse> => {
|
|
111
|
+
const { max_tokens, messages, model } = payload;
|
|
92
112
|
const command = new InvokeModelWithResponseStreamCommand({
|
|
93
113
|
accept: 'application/json',
|
|
94
114
|
body: JSON.stringify({
|
|
95
|
-
max_gen_len:
|
|
96
|
-
prompt: experimental_buildLlama2Prompt(
|
|
115
|
+
max_gen_len: max_tokens || 400,
|
|
116
|
+
prompt: experimental_buildLlama2Prompt(messages as any),
|
|
97
117
|
}),
|
|
98
118
|
contentType: 'application/json',
|
|
99
|
-
modelId:
|
|
119
|
+
modelId: model,
|
|
100
120
|
});
|
|
101
121
|
|
|
102
122
|
try {
|
|
@@ -108,7 +128,7 @@ export class LobeBedrockAI implements LobeRuntimeAI {
|
|
|
108
128
|
|
|
109
129
|
const [debug, output] = stream.tee();
|
|
110
130
|
|
|
111
|
-
if (
|
|
131
|
+
if (process.env.DEBUG_BEDROCK_CHAT_COMPLETION === '1') {
|
|
112
132
|
debugStream(debug).catch(console.error);
|
|
113
133
|
}
|
|
114
134
|
// Respond with the stream
|
|
@@ -129,6 +149,7 @@ export class LobeBedrockAI implements LobeRuntimeAI {
|
|
|
129
149
|
});
|
|
130
150
|
}
|
|
131
151
|
};
|
|
152
|
+
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
export default LobeBedrockAI;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildAnthropicMessage,
|
|
5
|
+
buildAnthropicBlock,
|
|
6
|
+
} from './anthropicHelpers';
|
|
7
|
+
|
|
8
|
+
import { parseDataUri } from './uriParser';
|
|
9
|
+
import {
|
|
10
|
+
OpenAIChatMessage,
|
|
11
|
+
UserMessageContentPart,
|
|
12
|
+
} from '../types/chat';
|
|
13
|
+
|
|
14
|
+
describe('anthropicHelpers', () => {
|
|
15
|
+
|
|
16
|
+
// Mock the parseDataUri function since it's an implementation detail
|
|
17
|
+
vi.mock('./uriParser', () => ({
|
|
18
|
+
parseDataUri: vi.fn().mockReturnValue({
|
|
19
|
+
mimeType: 'image/jpeg',
|
|
20
|
+
base64: 'base64EncodedString',
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('buildAnthropicBlock', () => {
|
|
25
|
+
it('should return the content as is for text type', () => {
|
|
26
|
+
const content: UserMessageContentPart =
|
|
27
|
+
{ type: 'text', text: 'Hello!' };
|
|
28
|
+
const result = buildAnthropicBlock(content);
|
|
29
|
+
expect(result).toEqual(content);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should transform an image URL into an Anthropic.ImageBlockParam', () => {
|
|
33
|
+
const content: UserMessageContentPart =
|
|
34
|
+
{ type: 'image_url', image_url: { url: 'data:image/jpeg;base64,base64EncodedString' } };
|
|
35
|
+
const result = buildAnthropicBlock(content);
|
|
36
|
+
expect(parseDataUri).toHaveBeenCalledWith(content.image_url.url);
|
|
37
|
+
expect(result).toEqual({
|
|
38
|
+
source: {
|
|
39
|
+
data: 'base64EncodedString',
|
|
40
|
+
media_type: 'image/jpeg',
|
|
41
|
+
type: 'base64',
|
|
42
|
+
},
|
|
43
|
+
type: 'image',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('buildAnthropicMessage', () => {
|
|
49
|
+
it('should correctly convert system message to assistant message', () => {
|
|
50
|
+
const message: OpenAIChatMessage =
|
|
51
|
+
{ content: [{ type: 'text', text: 'Hello!' }], role: 'system' };
|
|
52
|
+
const result = buildAnthropicMessage(message);
|
|
53
|
+
expect(result).toEqual(
|
|
54
|
+
{ content: [{ type: 'text', text: 'Hello!' }], role: 'assistant' }
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
OpenAIChatMessage,
|
|
5
|
+
UserMessageContentPart,
|
|
6
|
+
} from '../types';
|
|
7
|
+
|
|
8
|
+
import { parseDataUri } from './uriParser';
|
|
9
|
+
|
|
10
|
+
export const buildAnthropicBlock = (
|
|
11
|
+
content: UserMessageContentPart,
|
|
12
|
+
): Anthropic.ContentBlock | Anthropic.ImageBlockParam => {
|
|
13
|
+
switch (content.type) {
|
|
14
|
+
case 'text': {
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
case 'image_url': {
|
|
19
|
+
const { mimeType, base64 } = parseDataUri(content.image_url.url);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
source: {
|
|
23
|
+
data: base64 as string,
|
|
24
|
+
media_type: mimeType as Anthropic.ImageBlockParam.Source['media_type'],
|
|
25
|
+
type: 'base64',
|
|
26
|
+
},
|
|
27
|
+
type: 'image',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const buildAnthropicMessage = (
|
|
34
|
+
message: OpenAIChatMessage,
|
|
35
|
+
): Anthropic.Messages.MessageParam => {
|
|
36
|
+
const content = message.content as string | UserMessageContentPart[];
|
|
37
|
+
return {
|
|
38
|
+
content:
|
|
39
|
+
typeof content === 'string' ? content : content.map((c) => buildAnthropicBlock(c)),
|
|
40
|
+
role: message.role === 'function' || message.role === 'system' ? 'assistant' : message.role,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const buildAnthropicMessages = (
|
|
45
|
+
messages: OpenAIChatMessage[],
|
|
46
|
+
): Anthropic.Messages.MessageParam[] =>
|
|
47
|
+
messages.map((message) => buildAnthropicMessage(message));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import useSWR, { SWRHook } from 'swr';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 这一类请求方法是比较「死」的请求模式,只会在第一次请求时触发。不会自动刷新,刷新需要搭配 refreshXXX 这样的方法实现,
|
|
5
|
+
* 适用于 messages、topics、sessions 等由用户在客户端交互产生的数据。
|
|
6
|
+
*/
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
export const useClientDataSWR: SWRHook = (key, fetch, config) =>
|
|
9
|
+
useSWR(key, fetch, {
|
|
10
|
+
// default is 2000ms ,it makes the user's quick switch don't work correctly.
|
|
11
|
+
// Cause issue like this: https://github.com/lobehub/lobe-chat/issues/532
|
|
12
|
+
// we need to set it to 0.
|
|
13
|
+
dedupingInterval: 0,
|
|
14
|
+
refreshWhenOffline: false,
|
|
15
|
+
revalidateOnFocus: false,
|
|
16
|
+
revalidateOnReconnect: false,
|
|
17
|
+
...config,
|
|
18
|
+
});
|
|
@@ -559,7 +559,7 @@ describe('chatMessage actions', () => {
|
|
|
559
559
|
});
|
|
560
560
|
|
|
561
561
|
// 确保 mutate 调用了正确的参数
|
|
562
|
-
expect(mutate).toHaveBeenCalledWith([activeId, activeTopicId]);
|
|
562
|
+
expect(mutate).toHaveBeenCalledWith(['SWR_USE_FETCH_MESSAGES', activeId, activeTopicId]);
|
|
563
563
|
});
|
|
564
564
|
it('should handle errors during refreshing messages', async () => {
|
|
565
565
|
useChatStore.setState({ refreshMessages: realRefreshMessages });
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// Disable the auto sort key eslint rule to make the code more logic and readable
|
|
3
3
|
import { copyToClipboard } from '@lobehub/ui';
|
|
4
4
|
import { template } from 'lodash-es';
|
|
5
|
-
import
|
|
5
|
+
import { SWRResponse, mutate } from 'swr';
|
|
6
6
|
import { StateCreator } from 'zustand/vanilla';
|
|
7
7
|
|
|
8
8
|
import { LOADING_FLAT, isFunctionMessageAtStart, testFunctionMessageAtEnd } from '@/const/message';
|
|
9
9
|
import { TraceEventType, TraceNameMap } from '@/const/trace';
|
|
10
10
|
import { CreateMessageParams } from '@/database/models/message';
|
|
11
|
+
import { useClientDataSWR } from '@/libs/swr';
|
|
11
12
|
import { chatService } from '@/services/chat';
|
|
12
13
|
import { messageService } from '@/services/message';
|
|
13
14
|
import { topicService } from '@/services/topic';
|
|
@@ -25,6 +26,8 @@ import { MessageDispatch, messagesReducer } from './reducer';
|
|
|
25
26
|
|
|
26
27
|
const n = setNamespace('message');
|
|
27
28
|
|
|
29
|
+
const SWR_USE_FETCH_MESSAGES = 'SWR_USE_FETCH_MESSAGES';
|
|
30
|
+
|
|
28
31
|
interface SendMessageParams {
|
|
29
32
|
message: string;
|
|
30
33
|
files?: { id: string; url: string }[];
|
|
@@ -274,9 +277,9 @@ export const chatMessage: StateCreator<
|
|
|
274
277
|
await get().internalUpdateMessageContent(id, content);
|
|
275
278
|
},
|
|
276
279
|
useFetchMessages: (sessionId, activeTopicId) =>
|
|
277
|
-
|
|
278
|
-
[sessionId, activeTopicId],
|
|
279
|
-
async ([sessionId, topicId]: [string, string | undefined]) =>
|
|
280
|
+
useClientDataSWR<ChatMessage[]>(
|
|
281
|
+
[SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId],
|
|
282
|
+
async ([, sessionId, topicId]: [string, string, string | undefined]) =>
|
|
280
283
|
messageService.getMessages(sessionId, topicId),
|
|
281
284
|
{
|
|
282
285
|
onSuccess: (messages, key) => {
|
|
@@ -289,14 +292,10 @@ export const chatMessage: StateCreator<
|
|
|
289
292
|
}),
|
|
290
293
|
);
|
|
291
294
|
},
|
|
292
|
-
// default is 2000ms ,it makes the user's quick switch don't work correctly.
|
|
293
|
-
// Cause issue like this: https://github.com/lobehub/lobe-chat/issues/532
|
|
294
|
-
// we need to set it to 0.
|
|
295
|
-
dedupingInterval: 0,
|
|
296
295
|
},
|
|
297
296
|
),
|
|
298
297
|
refreshMessages: async () => {
|
|
299
|
-
await mutate([get().activeId, get().activeTopicId]);
|
|
298
|
+
await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId]);
|
|
300
299
|
},
|
|
301
300
|
|
|
302
301
|
// the internal process method of the AI message
|
|
@@ -6,6 +6,7 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
6
6
|
|
|
7
7
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
8
8
|
import { SESSION_CHAT_URL } from '@/const/url';
|
|
9
|
+
import { useClientDataSWR } from '@/libs/swr';
|
|
9
10
|
import { sessionService } from '@/services/session';
|
|
10
11
|
import { useGlobalStore } from '@/store/global';
|
|
11
12
|
import { settingsSelectors } from '@/store/global/selectors';
|
|
@@ -154,7 +155,7 @@ export const createSessionSlice: StateCreator<
|
|
|
154
155
|
},
|
|
155
156
|
|
|
156
157
|
useFetchSessions: () =>
|
|
157
|
-
|
|
158
|
+
useClientDataSWR<ChatSessionList>(FETCH_SESSIONS_KEY, sessionService.getSessionsWithGroup, {
|
|
158
159
|
onSuccess: (data) => {
|
|
159
160
|
// 由于 https://github.com/lobehub/lobe-chat/pull/541 的关系
|
|
160
161
|
// 只有触发了 refreshSessions 才会更新 sessions,进而触发页面 rerender
|