@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
@@ -130,6 +130,7 @@ export const buildAnthropicMessage = async (
|
|
130
130
|
|
131
131
|
export const buildAnthropicMessages = async (
|
132
132
|
oaiMessages: OpenAIChatMessage[],
|
133
|
+
options: { enabledContextCaching?: boolean } = {},
|
133
134
|
): Promise<Anthropic.Messages.MessageParam[]> => {
|
134
135
|
const messages: Anthropic.Messages.MessageParam[] = [];
|
135
136
|
let pendingToolResults: Anthropic.ToolResultBlockParam[] = [];
|
@@ -180,13 +181,46 @@ export const buildAnthropicMessages = async (
|
|
180
181
|
}
|
181
182
|
}
|
182
183
|
|
184
|
+
const lastMessage = messages.at(-1);
|
185
|
+
if (options.enabledContextCaching && !!lastMessage) {
|
186
|
+
if (typeof lastMessage.content === 'string') {
|
187
|
+
lastMessage.content = [
|
188
|
+
{
|
189
|
+
cache_control: { type: 'ephemeral' },
|
190
|
+
text: lastMessage.content as string,
|
191
|
+
type: 'text',
|
192
|
+
},
|
193
|
+
];
|
194
|
+
} else {
|
195
|
+
const lastContent = lastMessage.content.at(-1);
|
196
|
+
|
197
|
+
if (
|
198
|
+
lastContent &&
|
199
|
+
lastContent.type !== 'thinking' &&
|
200
|
+
lastContent.type !== 'redacted_thinking'
|
201
|
+
) {
|
202
|
+
lastContent.cache_control = { type: 'ephemeral' };
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
183
206
|
return messages;
|
184
207
|
};
|
185
|
-
|
186
|
-
|
187
|
-
|
208
|
+
|
209
|
+
export const buildAnthropicTools = (
|
210
|
+
tools?: OpenAI.ChatCompletionTool[],
|
211
|
+
options: { enabledContextCaching?: boolean } = {},
|
212
|
+
) => {
|
213
|
+
if (!tools) return;
|
214
|
+
|
215
|
+
return tools.map(
|
216
|
+
(tool, index): Anthropic.Tool => ({
|
217
|
+
cache_control:
|
218
|
+
options.enabledContextCaching && index === tools.length - 1
|
219
|
+
? { type: 'ephemeral' }
|
220
|
+
: undefined,
|
188
221
|
description: tool.function.description,
|
189
222
|
input_schema: tool.function.parameters as Anthropic.Tool.InputSchema,
|
190
223
|
name: tool.function.name,
|
191
224
|
}),
|
192
225
|
);
|
226
|
+
};
|
@@ -164,11 +164,11 @@
|
|
164
164
|
"metadata": { "loc": { "lines": { "from": 547, "to": 570 } } }
|
165
165
|
},
|
166
166
|
{
|
167
|
-
"pageContent": "const abortController = internal_toggleChatLoading(\n true,\n assistantId,\n n('generateMessage(start)', { assistantId, messages }) as string,\n );\n\n const agentConfig = getAgentConfig();\n const chatConfig = agentConfig.chatConfig;\n\n const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.
|
167
|
+
"pageContent": "const abortController = internal_toggleChatLoading(\n true,\n assistantId,\n n('generateMessage(start)', { assistantId, messages }) as string,\n );\n\n const agentConfig = getAgentConfig();\n const chatConfig = agentConfig.chatConfig;\n\n const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.getSlicedMessages(messages, chatConfig);",
|
168
168
|
"metadata": { "loc": { "lines": { "from": 566, "to": 582 } } }
|
169
169
|
},
|
170
170
|
{
|
171
|
-
"pageContent": "const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.
|
171
|
+
"pageContent": "const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\\S\\s]+?)}}/g });\n\n // ================================== //\n // messages uniformly preprocess //\n // ================================== //\n\n // 1. slice messages with config\n let preprocessMsgs = chatHelpers.getSlicedMessages(messages, chatConfig);\n\n // 2. replace inputMessage template\n preprocessMsgs = !chatConfig.inputTemplate\n ? preprocessMsgs\n : preprocessMsgs.map((m) => {\n if (m.role === 'user') {\n try {\n return { ...m, content: compiler({ text: m.content }) };\n } catch (error) {\n console.error(error);\n\n return m;\n }\n }\n\n return m;\n });",
|
172
172
|
"metadata": { "loc": { "lines": { "from": 575, "to": 599 } } }
|
173
173
|
},
|
174
174
|
{
|
@@ -579,7 +579,7 @@ export const chatMessage: StateCreator<
|
|
579
579
|
// ================================== //
|
580
580
|
|
581
581
|
// 1. slice messages with config
|
582
|
-
let preprocessMsgs = chatHelpers.
|
582
|
+
let preprocessMsgs = chatHelpers.getSlicedMessages(messages, chatConfig);
|
583
583
|
|
584
584
|
// 2. replace inputMessage template
|
585
585
|
preprocessMsgs = !chatConfig.inputTemplate
|
@@ -33,7 +33,12 @@ export default {
|
|
33
33
|
duplicateTitle: '{{title}} 副本',
|
34
34
|
emptyAgent: '暂无助手',
|
35
35
|
extendParams: {
|
36
|
+
disableContextCaching: {
|
37
|
+
desc: '单条对话生成成本最高可降低 90%,响应速度提升 4 倍(<1>了解更多</1>)。开启后将自动禁用历史消息数限制',
|
38
|
+
title: '开启上下文缓存',
|
39
|
+
},
|
36
40
|
enableReasoning: {
|
41
|
+
desc: '基于 Claude Thinking 机制限制(<1>了解更多</1>),开启后将自动禁用历史消息数限制',
|
37
42
|
title: '开启深度思考',
|
38
43
|
},
|
39
44
|
reasoningBudgetToken: {
|
@@ -41,6 +46,9 @@ export default {
|
|
41
46
|
},
|
42
47
|
title: '模型扩展功能',
|
43
48
|
},
|
49
|
+
history: {
|
50
|
+
title: '助手将只记住最后{{count}}条消息',
|
51
|
+
},
|
44
52
|
historyRange: '历史范围',
|
45
53
|
historySummary: '历史消息总结',
|
46
54
|
inbox: {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { AgentState, initialAgentChatState } from './slices/chat/initialState';
|
2
2
|
|
3
|
-
export type
|
3
|
+
export type AgentStoreState = AgentState;
|
4
4
|
|
5
|
-
export const initialState:
|
5
|
+
export const initialState: AgentStoreState = {
|
6
6
|
...initialAgentChatState,
|
7
7
|
};
|
@@ -1 +1 @@
|
|
1
|
-
export { agentSelectors } from './slices/chat/selectors';
|
1
|
+
export { agentChatConfigSelectors,agentSelectors } from './slices/chat/selectors';
|
@@ -6,8 +6,8 @@ import { AgentStore } from '@/store/agent';
|
|
6
6
|
import { AgentState } from '@/store/agent/slices/chat/initialState';
|
7
7
|
import { merge } from '@/utils/merge';
|
8
8
|
|
9
|
-
import { initialState } from '
|
10
|
-
import { agentSelectors } from './
|
9
|
+
import { initialState } from '../../../initialState';
|
10
|
+
import { agentSelectors } from './agent';
|
11
11
|
|
12
12
|
vi.mock('i18next', () => ({
|
13
13
|
t: vi.fn((key) => key), // Simplified mock return value
|
@@ -7,64 +7,62 @@ import {
|
|
7
7
|
DEFAULT_PROVIDER,
|
8
8
|
DEFAUTT_AGENT_TTS_CONFIG,
|
9
9
|
} from '@/const/settings';
|
10
|
-
import {
|
11
|
-
import {
|
10
|
+
import { AgentStoreState } from '@/store/agent/initialState';
|
11
|
+
import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
|
12
12
|
import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase';
|
13
13
|
import { merge } from '@/utils/merge';
|
14
14
|
|
15
|
-
const isInboxSession = (s:
|
15
|
+
const isInboxSession = (s: AgentStoreState) => s.activeId === INBOX_SESSION_ID;
|
16
16
|
|
17
17
|
// ========== Config ============== //
|
18
18
|
|
19
|
-
const inboxAgentConfig = (s:
|
19
|
+
const inboxAgentConfig = (s: AgentStoreState) =>
|
20
20
|
merge(DEFAULT_AGENT_CONFIG, s.agentMap[INBOX_SESSION_ID]);
|
21
|
-
const inboxAgentModel = (s:
|
21
|
+
const inboxAgentModel = (s: AgentStoreState) => inboxAgentConfig(s).model;
|
22
22
|
|
23
23
|
const getAgentConfigById =
|
24
24
|
(id: string) =>
|
25
|
-
(s:
|
25
|
+
(s: AgentStoreState): LobeAgentConfig =>
|
26
26
|
merge(s.defaultAgentConfig, s.agentMap[id]);
|
27
27
|
|
28
|
-
const currentAgentConfig = (s:
|
28
|
+
export const currentAgentConfig = (s: AgentStoreState): LobeAgentConfig =>
|
29
|
+
getAgentConfigById(s.activeId)(s);
|
29
30
|
|
30
|
-
const
|
31
|
-
currentAgentConfig(s).chatConfig || {};
|
32
|
-
|
33
|
-
const currentAgentSystemRole = (s: AgentStore) => {
|
31
|
+
const currentAgentSystemRole = (s: AgentStoreState) => {
|
34
32
|
return currentAgentConfig(s).systemRole;
|
35
33
|
};
|
36
34
|
|
37
|
-
const currentAgentModel = (s:
|
35
|
+
const currentAgentModel = (s: AgentStoreState): string => {
|
38
36
|
const config = currentAgentConfig(s);
|
39
37
|
|
40
38
|
return config?.model || DEFAULT_MODEL;
|
41
39
|
};
|
42
40
|
|
43
|
-
const currentAgentModelProvider = (s:
|
41
|
+
const currentAgentModelProvider = (s: AgentStoreState) => {
|
44
42
|
const config = currentAgentConfig(s);
|
45
43
|
|
46
44
|
return config?.provider || DEFAULT_PROVIDER;
|
47
45
|
};
|
48
46
|
|
49
|
-
const currentAgentPlugins = (s:
|
47
|
+
const currentAgentPlugins = (s: AgentStoreState) => {
|
50
48
|
const config = currentAgentConfig(s);
|
51
49
|
|
52
50
|
return config?.plugins || [];
|
53
51
|
};
|
54
52
|
|
55
|
-
const currentAgentKnowledgeBases = (s:
|
53
|
+
const currentAgentKnowledgeBases = (s: AgentStoreState) => {
|
56
54
|
const config = currentAgentConfig(s);
|
57
55
|
|
58
56
|
return config?.knowledgeBases || [];
|
59
57
|
};
|
60
58
|
|
61
|
-
const currentAgentFiles = (s:
|
59
|
+
const currentAgentFiles = (s: AgentStoreState) => {
|
62
60
|
const config = currentAgentConfig(s);
|
63
61
|
|
64
62
|
return config?.files || [];
|
65
63
|
};
|
66
64
|
|
67
|
-
const currentAgentTTS = (s:
|
65
|
+
const currentAgentTTS = (s: AgentStoreState): LobeAgentTTSConfig => {
|
68
66
|
const config = currentAgentConfig(s);
|
69
67
|
|
70
68
|
return config?.tts || DEFAUTT_AGENT_TTS_CONFIG;
|
@@ -72,7 +70,7 @@ const currentAgentTTS = (s: AgentStore): LobeAgentTTSConfig => {
|
|
72
70
|
|
73
71
|
const currentAgentTTSVoice =
|
74
72
|
(lang: string) =>
|
75
|
-
(s:
|
73
|
+
(s: AgentStoreState): string => {
|
76
74
|
const { voice, ttsService } = currentAgentTTS(s);
|
77
75
|
const voiceList = new VoiceList(lang);
|
78
76
|
let currentVoice;
|
@@ -93,7 +91,7 @@ const currentAgentTTSVoice =
|
|
93
91
|
return currentVoice || 'alloy';
|
94
92
|
};
|
95
93
|
|
96
|
-
const currentEnabledKnowledge = (s:
|
94
|
+
const currentEnabledKnowledge = (s: AgentStoreState) => {
|
97
95
|
const knowledgeBases = currentAgentKnowledgeBases(s);
|
98
96
|
const files = currentAgentFiles(s);
|
99
97
|
|
@@ -107,29 +105,27 @@ const currentEnabledKnowledge = (s: AgentStore) => {
|
|
107
105
|
] as KnowledgeItem[];
|
108
106
|
};
|
109
107
|
|
110
|
-
const
|
111
|
-
|
112
|
-
const hasSystemRole = (s: AgentStore) => {
|
108
|
+
const hasSystemRole = (s: AgentStoreState) => {
|
113
109
|
const config = currentAgentConfig(s);
|
114
110
|
|
115
111
|
return !!config.systemRole;
|
116
112
|
};
|
117
113
|
|
118
|
-
const hasKnowledgeBases = (s:
|
114
|
+
const hasKnowledgeBases = (s: AgentStoreState) => {
|
119
115
|
const knowledgeBases = currentAgentKnowledgeBases(s);
|
120
116
|
|
121
117
|
return knowledgeBases.length > 0;
|
122
118
|
};
|
123
119
|
|
124
|
-
const hasFiles = (s:
|
120
|
+
const hasFiles = (s: AgentStoreState) => {
|
125
121
|
const files = currentAgentFiles(s);
|
126
122
|
|
127
123
|
return files.length > 0;
|
128
124
|
};
|
129
125
|
|
130
|
-
const hasKnowledge = (s:
|
131
|
-
const hasEnabledKnowledge = (s:
|
132
|
-
const currentKnowledgeIds = (s:
|
126
|
+
const hasKnowledge = (s: AgentStoreState) => hasKnowledgeBases(s) || hasFiles(s);
|
127
|
+
const hasEnabledKnowledge = (s: AgentStoreState) => currentEnabledKnowledge(s).length > 0;
|
128
|
+
const currentKnowledgeIds = (s: AgentStoreState) => {
|
133
129
|
return {
|
134
130
|
fileIds: currentAgentFiles(s)
|
135
131
|
.filter((item) => item.enabled)
|
@@ -140,13 +136,9 @@ const currentKnowledgeIds = (s: AgentStore) => {
|
|
140
136
|
};
|
141
137
|
};
|
142
138
|
|
143
|
-
const isAgentConfigLoading = (s:
|
144
|
-
|
145
|
-
const isAgentEnableSearch = (s: AgentStore) => agentSearchMode(s) !== 'off';
|
139
|
+
const isAgentConfigLoading = (s: AgentStoreState) => !s.agentConfigInitMap[s.activeId];
|
146
140
|
|
147
141
|
export const agentSelectors = {
|
148
|
-
agentSearchMode,
|
149
|
-
currentAgentChatConfig,
|
150
142
|
currentAgentConfig,
|
151
143
|
currentAgentFiles,
|
152
144
|
currentAgentKnowledgeBases,
|
@@ -165,6 +157,5 @@ export const agentSelectors = {
|
|
165
157
|
inboxAgentConfig,
|
166
158
|
inboxAgentModel,
|
167
159
|
isAgentConfigLoading,
|
168
|
-
isAgentEnableSearch,
|
169
160
|
isInboxSession,
|
170
161
|
};
|
@@ -0,0 +1,184 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
4
|
+
import { AgentStoreState, initialState } from '@/store/agent/initialState';
|
5
|
+
import { LobeAgentChatConfig, LobeAgentConfig } from '@/types/agent';
|
6
|
+
import { merge } from '@/utils/merge';
|
7
|
+
|
8
|
+
import { agentChatConfigSelectors, currentAgentChatConfig } from './chatConfig';
|
9
|
+
|
10
|
+
// Helper function to create mock state
|
11
|
+
const createMockState = (
|
12
|
+
chatConfig?: Partial<LobeAgentChatConfig>,
|
13
|
+
config?: Partial<LobeAgentConfig>,
|
14
|
+
): AgentStoreState =>
|
15
|
+
merge(initialState, {
|
16
|
+
agentMap: {
|
17
|
+
agent1: merge(DEFAULT_AGENT_CONFIG, { ...config, chatConfig }),
|
18
|
+
},
|
19
|
+
activeId: 'agent1',
|
20
|
+
}) as AgentStoreState;
|
21
|
+
|
22
|
+
describe('agentChatConfigSelectors', () => {
|
23
|
+
describe('currentAgentChatConfig', () => {
|
24
|
+
it('should return default object when chatConfig is not defined', () => {
|
25
|
+
const state = createMockState({});
|
26
|
+
expect(currentAgentChatConfig(state)).toEqual(DEFAULT_AGENT_CHAT_CONFIG);
|
27
|
+
});
|
28
|
+
|
29
|
+
it('should return the chatConfig when defined', () => {
|
30
|
+
const chatConfig = { historyCount: 5 };
|
31
|
+
const state = createMockState(chatConfig);
|
32
|
+
expect(currentAgentChatConfig(state).historyCount).toEqual(5);
|
33
|
+
});
|
34
|
+
});
|
35
|
+
|
36
|
+
describe('enableHistoryCount', () => {
|
37
|
+
it('should return the enableHistoryCount value when defined', () => {
|
38
|
+
const state = createMockState({ enableHistoryCount: false });
|
39
|
+
expect(agentChatConfigSelectors.enableHistoryCount(state)).toBe(false);
|
40
|
+
});
|
41
|
+
|
42
|
+
it('should return false value when enable context caching with claude models', () => {
|
43
|
+
const state = createMockState(
|
44
|
+
{ enableHistoryCount: true, disableContextCaching: false },
|
45
|
+
{ model: 'claude-3-7-sonnet-20250219' },
|
46
|
+
);
|
47
|
+
|
48
|
+
expect(agentChatConfigSelectors.enableHistoryCount(merge(state))).toBe(false);
|
49
|
+
});
|
50
|
+
|
51
|
+
it('should return true value when enable context caching with other models', () => {
|
52
|
+
const state = createMockState(
|
53
|
+
{ enableHistoryCount: true, disableContextCaching: false },
|
54
|
+
{ model: 'gpt-4o-min' },
|
55
|
+
);
|
56
|
+
|
57
|
+
expect(agentChatConfigSelectors.enableHistoryCount(merge(state))).toBe(true);
|
58
|
+
});
|
59
|
+
|
60
|
+
it('should return false when enable search with claude 3.7 models', () => {
|
61
|
+
const state = createMockState(
|
62
|
+
{ enableHistoryCount: true, disableContextCaching: true, searchMode: 'auto' },
|
63
|
+
{ model: 'claude-3-7-sonnet-20250219' },
|
64
|
+
);
|
65
|
+
|
66
|
+
expect(agentChatConfigSelectors.enableHistoryCount(merge(state))).toBe(false);
|
67
|
+
});
|
68
|
+
|
69
|
+
it('should return true when disable search with claude 3.7 models', () => {
|
70
|
+
const state = createMockState(
|
71
|
+
{ enableHistoryCount: true, disableContextCaching: true, searchMode: 'off' },
|
72
|
+
{ model: 'claude-3-7-sonnet-20250219' },
|
73
|
+
);
|
74
|
+
|
75
|
+
expect(agentChatConfigSelectors.enableHistoryCount(merge(state))).toBe(true);
|
76
|
+
});
|
77
|
+
|
78
|
+
it('should return true when enable search with claude 3.5 models', () => {
|
79
|
+
const state = createMockState(
|
80
|
+
{ enableHistoryCount: true, disableContextCaching: true, searchMode: 'auto' },
|
81
|
+
{ model: 'claude-3-5-sonnet-latest' },
|
82
|
+
);
|
83
|
+
|
84
|
+
expect(agentChatConfigSelectors.enableHistoryCount(merge(state))).toBe(true);
|
85
|
+
});
|
86
|
+
});
|
87
|
+
|
88
|
+
describe('historyCount', () => {
|
89
|
+
it('should return undefined when historyCount is not defined', () => {
|
90
|
+
const state = createMockState();
|
91
|
+
expect(agentChatConfigSelectors.historyCount(state)).toBe(8);
|
92
|
+
});
|
93
|
+
|
94
|
+
it('should return the historyCount value when defined', () => {
|
95
|
+
const state = createMockState({ historyCount: 20 });
|
96
|
+
expect(agentChatConfigSelectors.historyCount(state)).toBe(20);
|
97
|
+
});
|
98
|
+
});
|
99
|
+
|
100
|
+
describe('agentSearchMode', () => {
|
101
|
+
it('should return "off" when searchMode is not defined', () => {
|
102
|
+
const state = createMockState();
|
103
|
+
expect(agentChatConfigSelectors.agentSearchMode(state)).toBe('off');
|
104
|
+
});
|
105
|
+
|
106
|
+
it('should return the searchMode value when defined', () => {
|
107
|
+
const state = createMockState({ searchMode: 'auto' });
|
108
|
+
expect(agentChatConfigSelectors.agentSearchMode(state)).toBe('auto');
|
109
|
+
});
|
110
|
+
});
|
111
|
+
|
112
|
+
describe('useModelBuiltinSearch', () => {
|
113
|
+
it('should return undefined when useModelBuiltinSearch is not defined', () => {
|
114
|
+
const state = createMockState();
|
115
|
+
expect(agentChatConfigSelectors.useModelBuiltinSearch(state)).toBeUndefined();
|
116
|
+
});
|
117
|
+
|
118
|
+
it('should return the useModelBuiltinSearch value when defined', () => {
|
119
|
+
const state = createMockState({ useModelBuiltinSearch: true });
|
120
|
+
expect(agentChatConfigSelectors.useModelBuiltinSearch(state)).toBe(true);
|
121
|
+
});
|
122
|
+
});
|
123
|
+
|
124
|
+
describe('isAgentEnableSearch', () => {
|
125
|
+
it('should return false when searchMode is "off"', () => {
|
126
|
+
const state = createMockState({ searchMode: 'off' });
|
127
|
+
expect(agentChatConfigSelectors.isAgentEnableSearch(state)).toBe(false);
|
128
|
+
});
|
129
|
+
|
130
|
+
it('should return true when searchMode is not "off"', () => {
|
131
|
+
const state = createMockState({ searchMode: 'auto' });
|
132
|
+
expect(agentChatConfigSelectors.isAgentEnableSearch(state)).toBe(true);
|
133
|
+
});
|
134
|
+
|
135
|
+
it('should return false when searchMode is undefined (defaults to "off")', () => {
|
136
|
+
const state = createMockState();
|
137
|
+
expect(agentChatConfigSelectors.isAgentEnableSearch(state)).toBe(false);
|
138
|
+
});
|
139
|
+
});
|
140
|
+
|
141
|
+
describe('displayMode', () => {
|
142
|
+
it('should return "chat" when displayMode is not defined', () => {
|
143
|
+
const state = createMockState();
|
144
|
+
expect(agentChatConfigSelectors.displayMode(state)).toBe('chat');
|
145
|
+
});
|
146
|
+
|
147
|
+
it('should return the displayMode value when defined', () => {
|
148
|
+
const state = createMockState({ displayMode: 'docs' });
|
149
|
+
expect(agentChatConfigSelectors.displayMode(state)).toBe('docs');
|
150
|
+
});
|
151
|
+
});
|
152
|
+
|
153
|
+
describe('enableHistoryDivider', () => {
|
154
|
+
it('should return false when enableHistoryCount is false', () => {
|
155
|
+
const state = createMockState({ enableHistoryCount: false, historyCount: 5 });
|
156
|
+
const selector = agentChatConfigSelectors.enableHistoryDivider(10, 5);
|
157
|
+
expect(selector(state)).toBe(false);
|
158
|
+
});
|
159
|
+
|
160
|
+
it('should return false when historyLength is less than or equal to historyCount', () => {
|
161
|
+
const state = createMockState({ enableHistoryCount: true, historyCount: 10 });
|
162
|
+
const selector = agentChatConfigSelectors.enableHistoryDivider(10, 5);
|
163
|
+
expect(selector(state)).toBe(false);
|
164
|
+
});
|
165
|
+
|
166
|
+
it('should return false when currentIndex is not at the historyCount position', () => {
|
167
|
+
const state = createMockState({ enableHistoryCount: true, historyCount: 5 });
|
168
|
+
const selector = agentChatConfigSelectors.enableHistoryDivider(10, 3);
|
169
|
+
expect(selector(state)).toBe(false);
|
170
|
+
});
|
171
|
+
|
172
|
+
it('should return true when all conditions are met', () => {
|
173
|
+
const state = createMockState({ enableHistoryCount: true, historyCount: 5 });
|
174
|
+
const selector = agentChatConfigSelectors.enableHistoryDivider(10, 5);
|
175
|
+
expect(selector(state)).toBe(true);
|
176
|
+
});
|
177
|
+
|
178
|
+
it('should handle undefined or null historyCount', () => {
|
179
|
+
const state = createMockState({ enableHistoryCount: true, historyCount: null as any });
|
180
|
+
const selector = agentChatConfigSelectors.enableHistoryDivider(10, 5);
|
181
|
+
expect(selector(state)).toBe(false);
|
182
|
+
});
|
183
|
+
});
|
184
|
+
});
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { contextCachingModels, thinkingWithToolClaudeModels } from '@/const/models';
|
2
|
+
import { AgentStoreState } from '@/store/agent/initialState';
|
3
|
+
import { LobeAgentChatConfig } from '@/types/agent';
|
4
|
+
|
5
|
+
import { currentAgentConfig } from './agent';
|
6
|
+
|
7
|
+
export const currentAgentChatConfig = (s: AgentStoreState): LobeAgentChatConfig =>
|
8
|
+
currentAgentConfig(s).chatConfig || {};
|
9
|
+
|
10
|
+
const agentSearchMode = (s: AgentStoreState) => currentAgentChatConfig(s).searchMode || 'off';
|
11
|
+
const isAgentEnableSearch = (s: AgentStoreState) => agentSearchMode(s) !== 'off';
|
12
|
+
|
13
|
+
const useModelBuiltinSearch = (s: AgentStoreState) =>
|
14
|
+
currentAgentChatConfig(s).useModelBuiltinSearch;
|
15
|
+
|
16
|
+
const enableHistoryCount = (s: AgentStoreState) => {
|
17
|
+
const config = currentAgentConfig(s);
|
18
|
+
const chatConfig = currentAgentChatConfig(s);
|
19
|
+
|
20
|
+
// 如果开启了上下文缓存,且当前模型类型匹配,则不开启历史记录
|
21
|
+
const enableContextCaching = !chatConfig.disableContextCaching;
|
22
|
+
|
23
|
+
if (enableContextCaching && contextCachingModels.has(config.model)) return false;
|
24
|
+
|
25
|
+
// 当开启搜索时,针对 claude 3.7 sonnet 模型不开启历史记录
|
26
|
+
const enableSearch = isAgentEnableSearch(s);
|
27
|
+
|
28
|
+
if (enableSearch && thinkingWithToolClaudeModels.has(config.model)) return false;
|
29
|
+
|
30
|
+
return chatConfig.enableHistoryCount;
|
31
|
+
};
|
32
|
+
|
33
|
+
const historyCount = (s: AgentStoreState) => {
|
34
|
+
const chatConfig = currentAgentChatConfig(s);
|
35
|
+
|
36
|
+
return chatConfig.historyCount;
|
37
|
+
};
|
38
|
+
|
39
|
+
const displayMode = (s: AgentStoreState) => {
|
40
|
+
const chatConfig = currentAgentChatConfig(s);
|
41
|
+
|
42
|
+
return chatConfig.displayMode || 'chat';
|
43
|
+
};
|
44
|
+
|
45
|
+
const enableHistoryDivider =
|
46
|
+
(historyLength: number, currentIndex: number) => (s: AgentStoreState) => {
|
47
|
+
const config = currentAgentChatConfig(s);
|
48
|
+
|
49
|
+
return (
|
50
|
+
enableHistoryCount(s) &&
|
51
|
+
historyLength > (config.historyCount ?? 0) &&
|
52
|
+
config.historyCount === historyLength - currentIndex
|
53
|
+
);
|
54
|
+
};
|
55
|
+
|
56
|
+
export const agentChatConfigSelectors = {
|
57
|
+
agentSearchMode,
|
58
|
+
currentChatConfig: currentAgentChatConfig,
|
59
|
+
displayMode,
|
60
|
+
enableHistoryCount,
|
61
|
+
enableHistoryDivider,
|
62
|
+
historyCount,
|
63
|
+
isAgentEnableSearch,
|
64
|
+
useModelBuiltinSearch,
|
65
|
+
};
|
package/src/store/agent/store.ts
CHANGED
@@ -3,12 +3,12 @@ import { createWithEqualityFn } from 'zustand/traditional';
|
|
3
3
|
import { StateCreator } from 'zustand/vanilla';
|
4
4
|
|
5
5
|
import { createDevtools } from '../middleware/createDevtools';
|
6
|
-
import {
|
6
|
+
import { AgentStoreState, initialState } from './initialState';
|
7
7
|
import { AgentChatAction, createChatSlice } from './slices/chat/action';
|
8
8
|
|
9
9
|
// =============== aggregate createStoreFn ============ //
|
10
10
|
|
11
|
-
export interface AgentStore extends AgentChatAction,
|
11
|
+
export interface AgentStore extends AgentChatAction, AgentStoreState {}
|
12
12
|
|
13
13
|
const createStore: StateCreator<AgentStore, [['zustand/devtools', never]]> = (...parameters) => ({
|
14
14
|
...initialState,
|
@@ -66,7 +66,7 @@ describe('chatHelpers', () => {
|
|
66
66
|
});
|
67
67
|
});
|
68
68
|
|
69
|
-
describe('
|
69
|
+
describe('getSlicedMessages', () => {
|
70
70
|
const messages = [
|
71
71
|
{ id: '1', content: 'First' },
|
72
72
|
{ id: '2', content: 'Second' },
|
@@ -75,13 +75,13 @@ describe('chatHelpers', () => {
|
|
75
75
|
|
76
76
|
it('returns all messages if history is disabled', () => {
|
77
77
|
const config = { enableHistoryCount: false, historyCount: undefined } as LobeAgentChatConfig;
|
78
|
-
const slicedMessages = chatHelpers.
|
78
|
+
const slicedMessages = chatHelpers.getSlicedMessages(messages, config);
|
79
79
|
expect(slicedMessages).toEqual(messages);
|
80
80
|
});
|
81
81
|
|
82
82
|
it('returns last N messages based on historyCount', () => {
|
83
83
|
const config = { enableHistoryCount: true, historyCount: 2 } as LobeAgentChatConfig;
|
84
|
-
const slicedMessages = chatHelpers.
|
84
|
+
const slicedMessages = chatHelpers.getSlicedMessages(messages, config);
|
85
85
|
expect(slicedMessages).toEqual([
|
86
86
|
{ id: '2', content: 'Second' },
|
87
87
|
{ id: '3', content: 'Third' },
|
@@ -90,25 +90,25 @@ describe('chatHelpers', () => {
|
|
90
90
|
|
91
91
|
it('returns empty array when historyCount is negative', () => {
|
92
92
|
const config = { enableHistoryCount: true, historyCount: -1 } as LobeAgentChatConfig;
|
93
|
-
const slicedMessages = chatHelpers.
|
93
|
+
const slicedMessages = chatHelpers.getSlicedMessages(messages, config);
|
94
94
|
expect(slicedMessages).toEqual([]);
|
95
95
|
});
|
96
96
|
|
97
97
|
it('returns all messages if historyCount exceeds the array length', () => {
|
98
98
|
const config = { enableHistoryCount: true, historyCount: 5 } as LobeAgentChatConfig;
|
99
|
-
const slicedMessages = chatHelpers.
|
99
|
+
const slicedMessages = chatHelpers.getSlicedMessages(messages, config);
|
100
100
|
expect(slicedMessages).toEqual(messages);
|
101
101
|
});
|
102
102
|
|
103
103
|
it('returns an empty array for an empty message array', () => {
|
104
104
|
const config = { enableHistoryCount: true, historyCount: 2 } as LobeAgentChatConfig;
|
105
|
-
const slicedMessages = chatHelpers.
|
105
|
+
const slicedMessages = chatHelpers.getSlicedMessages([], config);
|
106
106
|
expect(slicedMessages).toEqual([]);
|
107
107
|
});
|
108
108
|
|
109
109
|
it('returns an empty array when historyCount is zero', () => {
|
110
110
|
const config = { enableHistoryCount: true, historyCount: 0 } as LobeAgentChatConfig;
|
111
|
-
const slicedMessages = chatHelpers.
|
111
|
+
const slicedMessages = chatHelpers.getSlicedMessages(messages, config);
|
112
112
|
expect(slicedMessages).toEqual([]);
|
113
113
|
});
|
114
114
|
});
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import { LobeAgentChatConfig } from '@/types/agent';
|
2
1
|
import { ChatMessage } from '@/types/message';
|
3
2
|
import { OpenAIChatMessage } from '@/types/openai/chat';
|
4
3
|
import { encodeAsync } from '@/utils/tokenizer';
|
@@ -9,16 +8,21 @@ export const getMessagesTokenCount = async (messages: OpenAIChatMessage[]) =>
|
|
9
8
|
export const getMessageById = (messages: ChatMessage[], id: string) =>
|
10
9
|
messages.find((m) => m.id === id);
|
11
10
|
|
12
|
-
const
|
11
|
+
const getSlicedMessages = (
|
13
12
|
messages: ChatMessage[],
|
14
|
-
|
15
|
-
|
13
|
+
options: {
|
14
|
+
enableHistoryCount?: boolean;
|
15
|
+
historyCount?: number;
|
16
|
+
includeNewUserMessage?: boolean;
|
17
|
+
},
|
16
18
|
): ChatMessage[] => {
|
17
19
|
// if historyCount is not enabled, return all messages
|
18
|
-
if (!
|
20
|
+
if (!options.enableHistoryCount || options.historyCount === undefined) return messages;
|
19
21
|
|
20
22
|
// if user send message, history will include this message so the total length should +1
|
21
|
-
const messagesCount = !!includeNewUserMessage
|
23
|
+
const messagesCount = !!options.includeNewUserMessage
|
24
|
+
? options.historyCount + 1
|
25
|
+
: options.historyCount;
|
22
26
|
|
23
27
|
// if historyCount is negative or set to 0, return empty array
|
24
28
|
if (messagesCount <= 0) return [];
|
@@ -30,5 +34,5 @@ const getSlicedMessagesWithConfig = (
|
|
30
34
|
export const chatHelpers = {
|
31
35
|
getMessageById,
|
32
36
|
getMessagesTokenCount,
|
33
|
-
|
37
|
+
getSlicedMessages,
|
34
38
|
};
|