@lobehub/chat 1.68.11 → 1.69.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/changelog/v1.json +18 -0
- package/locales/ar/chat.json +8 -0
- package/locales/bg-BG/chat.json +8 -0
- package/locales/de-DE/chat.json +8 -0
- package/locales/en-US/chat.json +8 -0
- package/locales/es-ES/chat.json +8 -0
- package/locales/fa-IR/chat.json +8 -0
- package/locales/fr-FR/chat.json +8 -0
- package/locales/it-IT/chat.json +8 -0
- package/locales/ja-JP/chat.json +8 -0
- package/locales/ko-KR/chat.json +8 -0
- package/locales/nl-NL/chat.json +8 -0
- package/locales/pl-PL/chat.json +8 -0
- package/locales/pt-BR/chat.json +8 -0
- package/locales/ru-RU/chat.json +8 -0
- package/locales/tr-TR/chat.json +8 -0
- package/locales/vi-VN/chat.json +8 -0
- package/locales/zh-CN/chat.json +8 -0
- package/locales/zh-TW/chat.json +8 -0
- package/next.config.ts +6 -0
- package/package.json +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +19 -12
- package/packages/web-crawler/src/urlRules.ts +9 -1
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +9 -18
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +2 -5
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/HeaderAction.tsx +3 -2
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +56 -30
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/HistoryLimitTags.tsx +26 -0
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{SearchTags.tsx → Tags/SearchTags.tsx} +7 -4
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{Tags.tsx → Tags/index.tsx} +4 -1
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/index.tsx +1 -1
- package/src/config/aiModels/anthropic.ts +16 -1
- package/src/config/aiModels/google.ts +0 -1
- package/src/config/aiModels/groq.ts +14 -0
- package/src/config/aiModels/novita.ts +36 -0
- package/src/config/aiModels/siliconcloud.ts +18 -2
- package/src/config/modelProviders/anthropic.ts +0 -2
- package/src/const/layoutTokens.test.ts +1 -1
- package/src/const/layoutTokens.ts +1 -1
- package/src/const/models.ts +27 -0
- package/src/features/ChatInput/ActionBar/History.tsx +6 -3
- package/src/features/ChatInput/ActionBar/Model/ContextCachingSwitch.tsx +20 -0
- package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +49 -7
- package/src/features/ChatInput/ActionBar/Model/ReasoningTokenSlider.tsx +6 -14
- package/src/features/ChatInput/ActionBar/Search/ModelBuiltinSearch.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Search/SwitchPanel.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +3 -5
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +5 -1
- package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +3 -6
- package/src/features/Portal/Thread/Chat/ChatItem.tsx +4 -9
- package/src/hooks/useAgentEnableSearch.ts +2 -2
- package/src/libs/agent-runtime/anthropic/index.test.ts +36 -7
- package/src/libs/agent-runtime/anthropic/index.ts +30 -8
- package/src/libs/agent-runtime/azureOpenai/index.ts +4 -9
- package/src/libs/agent-runtime/azureai/index.ts +4 -9
- package/src/libs/agent-runtime/openai/index.ts +21 -38
- package/src/libs/agent-runtime/types/chat.ts +4 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.test.ts +55 -0
- package/src/libs/agent-runtime/utils/anthropicHelpers.ts +37 -3
- package/src/libs/langchain/loaders/code/__tests__/long.json +2 -2
- package/src/libs/langchain/loaders/code/__tests__/long.txt +1 -1
- package/src/locales/default/chat.ts +8 -0
- package/src/store/agent/initialState.ts +2 -2
- package/src/store/agent/selectors.ts +1 -1
- package/src/store/agent/slices/chat/{selectors.test.ts → selectors/agent.test.ts} +2 -2
- package/src/store/agent/slices/chat/{selectors.ts → selectors/agent.ts} +24 -33
- package/src/store/agent/slices/chat/selectors/chatConfig.test.ts +184 -0
- package/src/store/agent/slices/chat/selectors/chatConfig.ts +65 -0
- package/src/store/agent/slices/chat/selectors/index.ts +2 -0
- package/src/store/agent/store.ts +2 -2
- package/src/store/chat/helpers.test.ts +7 -7
- package/src/store/chat/helpers.ts +11 -7
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +3 -3
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -2
- package/src/store/chat/slices/aiChat/actions/helpers.ts +6 -2
- package/src/store/chat/slices/builtinTool/actions/searXNG.ts +28 -20
- package/src/store/chat/slices/message/selectors.ts +7 -3
- package/src/store/chat/slices/thread/selectors/index.ts +7 -3
- package/src/tools/web-browsing/Render/PageContent/Result.tsx +4 -2
- package/src/tools/web-browsing/Render/index.tsx +2 -0
- package/src/types/agent/index.ts +4 -0
- package/src/types/aiModel.ts +1 -1
- package/src/types/aiProvider.ts +60 -31
- /package/packages/web-crawler/src/{__test__ → __tests__}/crawler.test.ts +0 -0
- /package/packages/web-crawler/src/crawImpl/{__test__ → __tests__}/jina.test.ts +0 -0
- /package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/{KnowledgeTag.tsx → Tags/KnowledgeTag.tsx} +0 -0
- /package/src/store/agent/slices/chat/{__snapshots__/selectors.test.ts.snap → selectors/__snapshots__/agent.test.ts.snap} +0 -0
@@ -6,7 +6,6 @@ import useMergeState from 'use-merge-value';
|
|
6
6
|
const Kibi = 1024;
|
7
7
|
|
8
8
|
const exponent = (num: number) => Math.log2(num);
|
9
|
-
const getRealValue = (num: number) => Math.round(Math.pow(2, num));
|
10
9
|
const powerKibi = (num: number) => Math.round(Math.pow(2, num) * Kibi);
|
11
10
|
|
12
11
|
interface MaxTokenSliderProps {
|
@@ -15,7 +14,7 @@ interface MaxTokenSliderProps {
|
|
15
14
|
value?: number;
|
16
15
|
}
|
17
16
|
|
18
|
-
const
|
17
|
+
const ReasoningTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValue }) => {
|
19
18
|
const [token, setTokens] = useMergeState(0, {
|
20
19
|
defaultValue,
|
21
20
|
onChange,
|
@@ -30,7 +29,7 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
|
|
30
29
|
const updateWithPowValue = (value: number) => {
|
31
30
|
setPowValue(value);
|
32
31
|
|
33
|
-
setTokens(powerKibi(value));
|
32
|
+
setTokens(Math.min(powerKibi(value), 64_000));
|
34
33
|
};
|
35
34
|
|
36
35
|
const updateWithRealValue = (value: number) => {
|
@@ -52,7 +51,7 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
|
|
52
51
|
}, []);
|
53
52
|
|
54
53
|
return (
|
55
|
-
<Flexbox align={'center'} gap={12} horizontal>
|
54
|
+
<Flexbox align={'center'} gap={12} horizontal paddingInline={'4px 0'}>
|
56
55
|
<Flexbox flex={1}>
|
57
56
|
<Slider
|
58
57
|
marks={marks}
|
@@ -60,21 +59,14 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
|
|
60
59
|
min={exponent(1)}
|
61
60
|
onChange={updateWithPowValue}
|
62
61
|
step={null}
|
63
|
-
tooltip={{
|
64
|
-
formatter: (x) => {
|
65
|
-
if (typeof x === 'undefined') return;
|
66
|
-
|
67
|
-
let value = getRealValue(x);
|
68
|
-
|
69
|
-
if (value < Kibi) return ((value * Kibi) / 1000).toFixed(0) + 'k';
|
70
|
-
},
|
71
|
-
}}
|
62
|
+
tooltip={{ open: false }}
|
72
63
|
value={powValue}
|
73
64
|
/>
|
74
65
|
</Flexbox>
|
75
66
|
<div>
|
76
67
|
<InputNumber
|
77
68
|
changeOnWheel
|
69
|
+
max={64_000}
|
78
70
|
min={0}
|
79
71
|
onChange={(e) => {
|
80
72
|
if (!e && e !== 0) return;
|
@@ -89,4 +81,4 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
|
|
89
81
|
</Flexbox>
|
90
82
|
);
|
91
83
|
});
|
92
|
-
export default
|
84
|
+
export default ReasoningTokenSlider;
|
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Flexbox } from 'react-layout-kit';
|
8
8
|
|
9
9
|
import { useAgentStore } from '@/store/agent';
|
10
|
-
import { agentSelectors } from '@/store/agent/selectors';
|
10
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
11
11
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
12
12
|
|
13
13
|
import ExaIcon from './ExaIcon';
|
@@ -37,7 +37,7 @@ const ModelBuiltinSearch = memo(() => {
|
|
37
37
|
const [model, provider, checked, updateAgentChatConfig] = useAgentStore((s) => [
|
38
38
|
agentSelectors.currentAgentModel(s),
|
39
39
|
agentSelectors.currentAgentModelProvider(s),
|
40
|
-
|
40
|
+
agentChatConfigSelectors.useModelBuiltinSearch(s),
|
41
41
|
s.updateAgentChatConfig,
|
42
42
|
]);
|
43
43
|
|
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
9
9
|
|
10
10
|
import { useAgentStore } from '@/store/agent';
|
11
|
-
import { agentSelectors } from '@/store/agent/slices/chat';
|
11
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/chat';
|
12
12
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
13
13
|
import { SearchMode } from '@/types/search';
|
14
14
|
|
@@ -84,7 +84,7 @@ const Item = memo<NetworkOption>(({ value, description, icon, label, disable })
|
|
84
84
|
const { t } = useTranslation('chat');
|
85
85
|
const { styles } = useStyles();
|
86
86
|
const [mode, updateAgentChatConfig] = useAgentStore((s) => [
|
87
|
-
|
87
|
+
agentChatConfigSelectors.agentSearchMode(s),
|
88
88
|
s.updateAgentChatConfig,
|
89
89
|
]);
|
90
90
|
|
@@ -10,7 +10,7 @@ import { useModelContextWindowTokens } from '@/hooks/useModelContextWindowTokens
|
|
10
10
|
import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
|
11
11
|
import { useTokenCount } from '@/hooks/useTokenCount';
|
12
12
|
import { useAgentStore } from '@/store/agent';
|
13
|
-
import { agentSelectors } from '@/store/agent/selectors';
|
13
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
14
14
|
import { useChatStore } from '@/store/chat';
|
15
15
|
import { topicSelectors } from '@/store/chat/selectors';
|
16
16
|
import { useToolStore } from '@/store/tool';
|
@@ -31,15 +31,13 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
|
|
31
31
|
]);
|
32
32
|
|
33
33
|
const [systemRole, model, provider] = useAgentStore((s) => {
|
34
|
-
const config = agentSelectors.currentAgentChatConfig(s);
|
35
|
-
|
36
34
|
return [
|
37
35
|
agentSelectors.currentAgentSystemRole(s),
|
38
36
|
agentSelectors.currentAgentModel(s) as string,
|
39
37
|
agentSelectors.currentAgentModelProvider(s) as string,
|
40
38
|
// add these two params to enable the component to re-render
|
41
|
-
|
42
|
-
|
39
|
+
agentChatConfigSelectors.historyCount(s),
|
40
|
+
agentChatConfigSelectors.enableHistoryCount(s),
|
43
41
|
];
|
44
42
|
});
|
45
43
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import isEqual from 'fast-deep-equal';
|
1
2
|
import { Suspense, memo } from 'react';
|
2
3
|
|
3
4
|
import { LOADING_FLAT } from '@/const/message';
|
@@ -16,10 +17,11 @@ interface RenderProps {
|
|
16
17
|
toolCallId: string;
|
17
18
|
toolIndex: number;
|
18
19
|
}
|
20
|
+
|
19
21
|
const Render = memo<RenderProps>(
|
20
22
|
({ toolCallId, toolIndex, messageId, requestArgs, showPluginRender, setShowPluginRender }) => {
|
21
23
|
const loading = useChatStore(chatSelectors.isToolCallStreaming(messageId, toolIndex));
|
22
|
-
const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(toolCallId));
|
24
|
+
const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(toolCallId), isEqual);
|
23
25
|
|
24
26
|
// 如果处于 loading 或者找不到 toolMessage 则展示 Arguments
|
25
27
|
if (loading || !toolMessage) return <Arguments arguments={requestArgs} />;
|
@@ -48,4 +50,6 @@ const Render = memo<RenderProps>(
|
|
48
50
|
},
|
49
51
|
);
|
50
52
|
|
53
|
+
Render.displayName = 'ToolRender';
|
54
|
+
|
51
55
|
export default Render;
|
@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
9
9
|
|
10
10
|
import { useAgentStore } from '@/store/agent';
|
11
|
-
import {
|
11
|
+
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
12
12
|
import { useChatStore } from '@/store/chat';
|
13
13
|
import { chatSelectors } from '@/store/chat/selectors';
|
14
14
|
import { useUserStore } from '@/store/user';
|
@@ -65,15 +65,12 @@ const Item = memo<ChatListItemProps>(
|
|
65
65
|
disableEditing,
|
66
66
|
inPortalThread = false,
|
67
67
|
}) => {
|
68
|
-
const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
|
69
68
|
const { t } = useTranslation('common');
|
70
69
|
const { styles, cx } = useStyles();
|
71
|
-
const [type = 'chat'] = useAgentStore((s) => {
|
72
|
-
const config = agentSelectors.currentAgentChatConfig(s);
|
73
|
-
return [config.displayMode];
|
74
|
-
});
|
75
70
|
|
71
|
+
const type = useAgentStore(agentChatConfigSelectors.displayMode);
|
76
72
|
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
|
73
|
+
const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
|
77
74
|
|
78
75
|
const [
|
79
76
|
isMessageLoading,
|
@@ -3,7 +3,7 @@ import React, { memo, useMemo } from 'react';
|
|
3
3
|
import { ChatItem } from '@/features/Conversation';
|
4
4
|
import ActionsBar from '@/features/Conversation/components/ChatItem/ActionsBar';
|
5
5
|
import { useAgentStore } from '@/store/agent';
|
6
|
-
import {
|
6
|
+
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
7
7
|
import { useChatStore } from '@/store/chat';
|
8
8
|
import { threadSelectors } from '@/store/chat/selectors';
|
9
9
|
|
@@ -35,14 +35,9 @@ const ThreadChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
|
|
35
35
|
[id, isParentMessage],
|
36
36
|
);
|
37
37
|
|
38
|
-
const enableHistoryDivider = useAgentStore(
|
39
|
-
|
40
|
-
|
41
|
-
config.enableHistoryCount &&
|
42
|
-
historyLength > (config.historyCount ?? 0) &&
|
43
|
-
config.historyCount === historyLength - index
|
44
|
-
);
|
45
|
-
});
|
38
|
+
const enableHistoryDivider = useAgentStore(
|
39
|
+
agentChatConfigSelectors.enableHistoryDivider(historyLength, index),
|
40
|
+
);
|
46
41
|
|
47
42
|
return (
|
48
43
|
<ChatItem
|
@@ -1,12 +1,12 @@
|
|
1
1
|
import { useAgentStore } from '@/store/agent';
|
2
|
-
import { agentSelectors } from '@/store/agent/
|
2
|
+
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
3
3
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
4
4
|
|
5
5
|
export const useAgentEnableSearch = () => {
|
6
6
|
const [model, provider, agentSearchMode] = useAgentStore((s) => [
|
7
7
|
agentSelectors.currentAgentModel(s),
|
8
8
|
agentSelectors.currentAgentModelProvider(s),
|
9
|
-
|
9
|
+
agentChatConfigSelectors.agentSearchMode(s),
|
10
10
|
]);
|
11
11
|
|
12
12
|
const isModelSupportToolUse = useAiInfraStore(
|
@@ -81,7 +81,12 @@ describe('LobeAnthropicAI', () => {
|
|
81
81
|
expect(instance['client'].messages.create).toHaveBeenCalledWith(
|
82
82
|
{
|
83
83
|
max_tokens: 4096,
|
84
|
-
messages: [
|
84
|
+
messages: [
|
85
|
+
{
|
86
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
87
|
+
role: 'user',
|
88
|
+
},
|
89
|
+
],
|
85
90
|
model: 'claude-3-haiku-20240307',
|
86
91
|
stream: true,
|
87
92
|
temperature: 0,
|
@@ -117,10 +122,21 @@ describe('LobeAnthropicAI', () => {
|
|
117
122
|
expect(instance['client'].messages.create).toHaveBeenCalledWith(
|
118
123
|
{
|
119
124
|
max_tokens: 4096,
|
120
|
-
messages: [
|
125
|
+
messages: [
|
126
|
+
{
|
127
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
128
|
+
role: 'user',
|
129
|
+
},
|
130
|
+
],
|
121
131
|
model: 'claude-3-haiku-20240307',
|
122
132
|
stream: true,
|
123
|
-
system:
|
133
|
+
system: [
|
134
|
+
{
|
135
|
+
cache_control: { type: 'ephemeral' },
|
136
|
+
type: 'text',
|
137
|
+
text: 'You are an awesome greeter',
|
138
|
+
},
|
139
|
+
],
|
124
140
|
temperature: 0,
|
125
141
|
},
|
126
142
|
{},
|
@@ -152,7 +168,12 @@ describe('LobeAnthropicAI', () => {
|
|
152
168
|
expect(instance['client'].messages.create).toHaveBeenCalledWith(
|
153
169
|
{
|
154
170
|
max_tokens: 2048,
|
155
|
-
messages: [
|
171
|
+
messages: [
|
172
|
+
{
|
173
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
174
|
+
role: 'user',
|
175
|
+
},
|
176
|
+
],
|
156
177
|
model: 'claude-3-haiku-20240307',
|
157
178
|
stream: true,
|
158
179
|
temperature: 0.25,
|
@@ -189,7 +210,12 @@ describe('LobeAnthropicAI', () => {
|
|
189
210
|
expect(instance['client'].messages.create).toHaveBeenCalledWith(
|
190
211
|
{
|
191
212
|
max_tokens: 2048,
|
192
|
-
messages: [
|
213
|
+
messages: [
|
214
|
+
{
|
215
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hello', type: 'text' }],
|
216
|
+
role: 'user',
|
217
|
+
},
|
218
|
+
],
|
193
219
|
model: 'claude-3-haiku-20240307',
|
194
220
|
stream: true,
|
195
221
|
temperature: 0.25,
|
@@ -240,7 +266,7 @@ describe('LobeAnthropicAI', () => {
|
|
240
266
|
});
|
241
267
|
|
242
268
|
describe('chat with tools', () => {
|
243
|
-
it('should call
|
269
|
+
it('should call tools when tools are provided', async () => {
|
244
270
|
// Arrange
|
245
271
|
const tools: ChatCompletionTool[] = [
|
246
272
|
{ function: { name: 'tool1', description: 'desc1' }, type: 'function' },
|
@@ -257,7 +283,10 @@ describe('LobeAnthropicAI', () => {
|
|
257
283
|
|
258
284
|
// Assert
|
259
285
|
expect(instance['client'].messages.create).toHaveBeenCalled();
|
260
|
-
expect(spyOn).toHaveBeenCalledWith(
|
286
|
+
expect(spyOn).toHaveBeenCalledWith(
|
287
|
+
[{ function: { name: 'tool1', description: 'desc1' }, type: 'function' }],
|
288
|
+
{ enabledContextCaching: true },
|
289
|
+
);
|
261
290
|
});
|
262
291
|
});
|
263
292
|
|
@@ -97,10 +97,33 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
97
97
|
}
|
98
98
|
|
99
99
|
private async buildAnthropicPayload(payload: ChatStreamPayload) {
|
100
|
-
const {
|
100
|
+
const {
|
101
|
+
messages,
|
102
|
+
model,
|
103
|
+
max_tokens,
|
104
|
+
temperature,
|
105
|
+
top_p,
|
106
|
+
tools,
|
107
|
+
thinking,
|
108
|
+
enabledContextCaching = true,
|
109
|
+
} = payload;
|
101
110
|
const system_message = messages.find((m) => m.role === 'system');
|
102
111
|
const user_messages = messages.filter((m) => m.role !== 'system');
|
103
112
|
|
113
|
+
const systemPrompts = !!system_message?.content
|
114
|
+
? ([
|
115
|
+
{
|
116
|
+
cache_control: enabledContextCaching ? { type: 'ephemeral' } : undefined,
|
117
|
+
text: system_message?.content as string,
|
118
|
+
type: 'text',
|
119
|
+
},
|
120
|
+
] as Anthropic.TextBlockParam[])
|
121
|
+
: undefined;
|
122
|
+
|
123
|
+
const postMessages = await buildAnthropicMessages(user_messages, { enabledContextCaching });
|
124
|
+
|
125
|
+
const postTools = buildAnthropicTools(tools, { enabledContextCaching });
|
126
|
+
|
104
127
|
if (!!thinking) {
|
105
128
|
const maxTokens =
|
106
129
|
max_tokens ?? (thinking?.budget_tokens ? thinking?.budget_tokens + 4096 : 4096);
|
@@ -109,22 +132,21 @@ export class LobeAnthropicAI implements LobeRuntimeAI {
|
|
109
132
|
// `top_p` must be unset when thinking is enabled.
|
110
133
|
return {
|
111
134
|
max_tokens: maxTokens,
|
112
|
-
messages:
|
135
|
+
messages: postMessages,
|
113
136
|
model,
|
114
|
-
system:
|
115
|
-
|
137
|
+
system: systemPrompts,
|
116
138
|
thinking,
|
117
|
-
tools:
|
139
|
+
tools: postTools,
|
118
140
|
} satisfies Anthropic.MessageCreateParams;
|
119
141
|
}
|
120
142
|
|
121
143
|
return {
|
122
144
|
max_tokens: max_tokens ?? 4096,
|
123
|
-
messages:
|
145
|
+
messages: postMessages,
|
124
146
|
model,
|
125
|
-
system:
|
147
|
+
system: systemPrompts,
|
126
148
|
temperature: payload.temperature !== undefined ? temperature / 2 : undefined,
|
127
|
-
tools:
|
149
|
+
tools: postTools,
|
128
150
|
top_p,
|
129
151
|
} satisfies Anthropic.MessageCreateParams;
|
130
152
|
}
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import OpenAI, { AzureOpenAI } from 'openai';
|
2
2
|
import type { Stream } from 'openai/streaming';
|
3
3
|
|
4
|
+
import { systemToUserModels } from '@/const/models';
|
5
|
+
|
4
6
|
import { LobeRuntimeAI } from '../BaseAI';
|
5
7
|
import { AgentRuntimeErrorType } from '../error';
|
6
8
|
import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
|
@@ -13,7 +15,7 @@ import { OpenAIStream } from '../utils/streams';
|
|
13
15
|
export class LobeAzureOpenAI implements LobeRuntimeAI {
|
14
16
|
client: AzureOpenAI;
|
15
17
|
|
16
|
-
constructor(params: { apiKey?: string; apiVersion?: string
|
18
|
+
constructor(params: { apiKey?: string; apiVersion?: string; baseURL?: string } = {}) {
|
17
19
|
if (!params.apiKey || !params.baseURL)
|
18
20
|
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
|
19
21
|
|
@@ -34,17 +36,10 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
|
34
36
|
// o1 series models on Azure OpenAI does not support streaming currently
|
35
37
|
const enableStreaming = model.includes('o1') ? false : (params.stream ?? true);
|
36
38
|
|
37
|
-
// Convert 'system' role to 'user' or 'developer' based on the model
|
38
|
-
const systemToUserModels = new Set([
|
39
|
-
'o1-preview',
|
40
|
-
'o1-preview-2024-09-12',
|
41
|
-
'o1-mini',
|
42
|
-
'o1-mini-2024-09-12',
|
43
|
-
]);
|
44
|
-
|
45
39
|
const updatedMessages = messages.map((message) => ({
|
46
40
|
...message,
|
47
41
|
role:
|
42
|
+
// Convert 'system' role to 'user' or 'developer' based on the model
|
48
43
|
(model.includes('o1') || model.includes('o3')) && message.role === 'system'
|
49
44
|
? [...systemToUserModels].some((sub) => model.includes(sub))
|
50
45
|
? 'user'
|
@@ -2,6 +2,8 @@ import createClient, { ModelClient } from '@azure-rest/ai-inference';
|
|
2
2
|
import { AzureKeyCredential } from '@azure/core-auth';
|
3
3
|
import OpenAI from 'openai';
|
4
4
|
|
5
|
+
import { systemToUserModels } from '@/const/models';
|
6
|
+
|
5
7
|
import { LobeRuntimeAI } from '../BaseAI';
|
6
8
|
import { AgentRuntimeErrorType } from '../error';
|
7
9
|
import { ChatCompetitionOptions, ChatStreamPayload, ModelProvider } from '../types';
|
@@ -30,24 +32,17 @@ export class LobeAzureAI implements LobeRuntimeAI {
|
|
30
32
|
// o1 series models on Azure OpenAI does not support streaming currently
|
31
33
|
const enableStreaming = model.includes('o1') ? false : (params.stream ?? true);
|
32
34
|
|
33
|
-
// Convert 'system' role to 'user' or 'developer' based on the model
|
34
|
-
const systemToUserModels = new Set([
|
35
|
-
'o1-preview',
|
36
|
-
'o1-preview-2024-09-12',
|
37
|
-
'o1-mini',
|
38
|
-
'o1-mini-2024-09-12',
|
39
|
-
]);
|
40
|
-
|
41
35
|
const updatedMessages = messages.map((message) => ({
|
42
36
|
...message,
|
43
37
|
role:
|
38
|
+
// Convert 'system' role to 'user' or 'developer' based on the model
|
44
39
|
(model.includes('o1') || model.includes('o3')) && message.role === 'system'
|
45
40
|
? [...systemToUserModels].some((sub) => model.includes(sub))
|
46
41
|
? 'user'
|
47
42
|
: 'developer'
|
48
43
|
: message.role,
|
49
44
|
}));
|
50
|
-
|
45
|
+
|
51
46
|
try {
|
52
47
|
const response = this.client.path('/chat/completions').post({
|
53
48
|
body: {
|
@@ -1,25 +1,14 @@
|
|
1
|
+
import { disableStreamModels, systemToUserModels } from '@/const/models';
|
2
|
+
import type { ChatModelCard } from '@/types/llm';
|
3
|
+
|
1
4
|
import { ChatStreamPayload, ModelProvider, OpenAIChatMessage } from '../types';
|
2
5
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
3
6
|
|
4
|
-
import type { ChatModelCard } from '@/types/llm';
|
5
|
-
|
6
7
|
export interface OpenAIModelCard {
|
7
8
|
id: string;
|
8
9
|
}
|
9
10
|
|
10
11
|
export const pruneReasoningPayload = (payload: ChatStreamPayload) => {
|
11
|
-
// TODO: 临时写法,后续要重构成 model card 展示配置
|
12
|
-
const disableStreamModels = new Set([
|
13
|
-
'o1',
|
14
|
-
'o1-2024-12-17'
|
15
|
-
]);
|
16
|
-
const systemToUserModels = new Set([
|
17
|
-
'o1-preview',
|
18
|
-
'o1-preview-2024-09-12',
|
19
|
-
'o1-mini',
|
20
|
-
'o1-mini-2024-09-12',
|
21
|
-
]);
|
22
|
-
|
23
12
|
return {
|
24
13
|
...payload,
|
25
14
|
frequency_penalty: 0,
|
@@ -58,46 +47,40 @@ export const LobeOpenAI = LobeOpenAICompatibleFactory({
|
|
58
47
|
models: async ({ client }) => {
|
59
48
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
60
49
|
|
61
|
-
const functionCallKeywords = [
|
62
|
-
'gpt-4',
|
63
|
-
'gpt-3.5',
|
64
|
-
'o3-mini',
|
65
|
-
];
|
50
|
+
const functionCallKeywords = ['gpt-4', 'gpt-3.5', 'o3-mini'];
|
66
51
|
|
67
|
-
const visionKeywords = [
|
68
|
-
'gpt-4o',
|
69
|
-
'vision',
|
70
|
-
];
|
52
|
+
const visionKeywords = ['gpt-4o', 'vision'];
|
71
53
|
|
72
|
-
const reasoningKeywords = [
|
73
|
-
'o1',
|
74
|
-
'o3',
|
75
|
-
];
|
54
|
+
const reasoningKeywords = ['o1', 'o3'];
|
76
55
|
|
77
|
-
const modelsPage = await client.models.list() as any;
|
56
|
+
const modelsPage = (await client.models.list()) as any;
|
78
57
|
const modelList: OpenAIModelCard[] = modelsPage.data;
|
79
58
|
|
80
59
|
return modelList
|
81
60
|
.map((model) => {
|
82
|
-
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
61
|
+
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
62
|
+
(m) => model.id.toLowerCase() === m.id.toLowerCase(),
|
63
|
+
);
|
83
64
|
|
84
65
|
return {
|
85
66
|
contextWindowTokens: knownModel?.contextWindowTokens ?? undefined,
|
86
67
|
displayName: knownModel?.displayName ?? undefined,
|
87
68
|
enabled: knownModel?.enabled || false,
|
88
69
|
functionCall:
|
89
|
-
functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword)) &&
|
90
|
-
|
91
|
-
||
|
70
|
+
(functionCallKeywords.some((keyword) => model.id.toLowerCase().includes(keyword)) &&
|
71
|
+
!model.id.toLowerCase().includes('audio')) ||
|
72
|
+
knownModel?.abilities?.functionCall ||
|
73
|
+
false,
|
92
74
|
id: model.id,
|
93
75
|
reasoning:
|
94
|
-
reasoningKeywords.some(keyword => model.id.toLowerCase().includes(keyword))
|
95
|
-
|
96
|
-
|
76
|
+
reasoningKeywords.some((keyword) => model.id.toLowerCase().includes(keyword)) ||
|
77
|
+
knownModel?.abilities?.reasoning ||
|
78
|
+
false,
|
97
79
|
vision:
|
98
|
-
visionKeywords.some(keyword => model.id.toLowerCase().includes(keyword)) &&
|
99
|
-
|
100
|
-
||
|
80
|
+
(visionKeywords.some((keyword) => model.id.toLowerCase().includes(keyword)) &&
|
81
|
+
!model.id.toLowerCase().includes('audio')) ||
|
82
|
+
knownModel?.abilities?.vision ||
|
83
|
+
false,
|
101
84
|
};
|
102
85
|
})
|
103
86
|
.filter(Boolean) as ChatModelCard[];
|
@@ -619,6 +619,26 @@ describe('anthropicHelpers', () => {
|
|
619
619
|
{ content: '继续', role: 'user' },
|
620
620
|
]);
|
621
621
|
});
|
622
|
+
|
623
|
+
it('should enable cache control', async () => {
|
624
|
+
const messages: OpenAIChatMessage[] = [
|
625
|
+
{ content: 'Hello', role: 'user' },
|
626
|
+
{ content: 'Hello', role: 'user' },
|
627
|
+
{ content: 'Hi', role: 'assistant' },
|
628
|
+
];
|
629
|
+
|
630
|
+
const contents = await buildAnthropicMessages(messages, { enabledContextCaching: true });
|
631
|
+
|
632
|
+
expect(contents).toHaveLength(3);
|
633
|
+
expect(contents).toEqual([
|
634
|
+
{ content: 'Hello', role: 'user' },
|
635
|
+
{ content: 'Hello', role: 'user' },
|
636
|
+
{
|
637
|
+
content: [{ cache_control: { type: 'ephemeral' }, text: 'Hi', type: 'text' }],
|
638
|
+
role: 'assistant',
|
639
|
+
},
|
640
|
+
]);
|
641
|
+
});
|
622
642
|
});
|
623
643
|
|
624
644
|
describe('buildAnthropicTools', () => {
|
@@ -656,5 +676,40 @@ describe('anthropicHelpers', () => {
|
|
656
676
|
},
|
657
677
|
]);
|
658
678
|
});
|
679
|
+
it('should enable cache control', () => {
|
680
|
+
const tools: OpenAI.ChatCompletionTool[] = [
|
681
|
+
{
|
682
|
+
type: 'function',
|
683
|
+
function: {
|
684
|
+
name: 'search',
|
685
|
+
description: 'Searches the web',
|
686
|
+
parameters: {
|
687
|
+
type: 'object',
|
688
|
+
properties: {
|
689
|
+
query: { type: 'string' },
|
690
|
+
},
|
691
|
+
required: ['query'],
|
692
|
+
},
|
693
|
+
},
|
694
|
+
},
|
695
|
+
];
|
696
|
+
|
697
|
+
const result = buildAnthropicTools(tools, { enabledContextCaching: true });
|
698
|
+
|
699
|
+
expect(result).toEqual([
|
700
|
+
{
|
701
|
+
name: 'search',
|
702
|
+
description: 'Searches the web',
|
703
|
+
input_schema: {
|
704
|
+
type: 'object',
|
705
|
+
properties: {
|
706
|
+
query: { type: 'string' },
|
707
|
+
},
|
708
|
+
required: ['query'],
|
709
|
+
},
|
710
|
+
cache_control: { type: 'ephemeral' },
|
711
|
+
},
|
712
|
+
]);
|
713
|
+
});
|
659
714
|
});
|
660
715
|
});
|