@lobehub/lobehub 2.0.5 → 2.0.7
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v2.json +14 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +2 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +2 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +2 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +2 -0
- package/docs/self-hosting/platform/docker.mdx +6 -0
- package/docs/self-hosting/platform/docker.zh-CN.mdx +6 -0
- package/docs/self-hosting/platform/vercel.mdx +0 -49
- package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -47
- package/docs/self-hosting/start.mdx +0 -20
- package/docs/self-hosting/start.zh-CN.mdx +0 -18
- package/package.json +1 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +52 -0
- package/packages/database/src/repositories/aiInfra/index.ts +103 -0
- package/packages/model-runtime/src/core/streams/protocol.ts +3 -1
- package/src/app/(backend)/api/workflows/memory-user-memory/pipelines/persona/update-writing/route.ts +19 -19
- package/src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert.tsx +3 -2
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +3 -3
- package/src/app/[variants]/onboarding/components/KlavisServerList/hooks/useKlavisOAuth.ts +12 -5
- package/src/components/DebugNode.tsx +21 -0
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/ToolItem.tsx +16 -13
- package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +51 -40
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +7 -1
- package/src/features/ChatInput/ActionBar/components/ActionPopover.tsx +14 -11
- package/src/layout/GlobalProvider/useUserStateRedirect.ts +6 -2
- package/src/libs/observability/traceparent.test.ts +46 -7
- package/src/libs/observability/traceparent.ts +12 -10
- package/src/server/routers/lambda/klavis.ts +38 -10
- package/src/server/services/memory/userMemory/__tests__/extract.runtime.test.ts +181 -26
- package/src/server/services/memory/userMemory/extract.ts +120 -96
- package/src/server/services/memory/userMemory/persona/__tests__/service.test.ts +46 -0
- package/src/server/services/memory/userMemory/persona/service.ts +47 -6
- package/src/store/tool/slices/klavisStore/action.ts +20 -0
- package/docs/self-hosting/server-database.mdx +0 -157
- package/docs/self-hosting/server-database.zh-CN.mdx +0 -146
|
@@ -57,54 +57,123 @@ const createExecutor = (privateOverrides?: Partial<MemoryExtractionPrivateConfig
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
describe('MemoryExtractionExecutor.resolveRuntimeKeyVaults', () => {
|
|
60
|
-
it('prefers configured providers/models for gatekeeper, embedding, and layer extractors', () => {
|
|
60
|
+
it('prefers configured providers/models for gatekeeper, embedding, and layer extractors', async () => {
|
|
61
61
|
const executor = createExecutor({
|
|
62
|
-
embeddingPreferredProviders: ['provider-
|
|
63
|
-
agentGateKeeperPreferredModels: ['
|
|
64
|
-
agentGateKeeperPreferredProviders: ['provider-
|
|
65
|
-
agentLayerExtractorPreferredProviders: ['provider-
|
|
62
|
+
embeddingPreferredProviders: ['provider-c', 'provider-a'],
|
|
63
|
+
agentGateKeeperPreferredModels: ['model-chat-1', 'vendor-prefix/model-chat-1'],
|
|
64
|
+
agentGateKeeperPreferredProviders: ['provider-c', 'provider-a'],
|
|
65
|
+
agentLayerExtractorPreferredProviders: ['provider-c', 'provider-a'],
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
const runtimeState = createRuntimeState(
|
|
69
69
|
[
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
{
|
|
70
|
+
{
|
|
71
|
+
abilities: {},
|
|
72
|
+
enabled: true,
|
|
73
|
+
id: 'model-chat-1',
|
|
74
|
+
type: 'chat',
|
|
75
|
+
providerId: 'provider-a',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
abilities: {},
|
|
79
|
+
enabled: true,
|
|
80
|
+
id: 'model-embedding-1',
|
|
81
|
+
type: 'embedding',
|
|
82
|
+
providerId: 'provider-e',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
abilities: {},
|
|
86
|
+
enabled: true,
|
|
87
|
+
id: 'vendor-prefix/model-chat-1',
|
|
88
|
+
type: 'chat',
|
|
89
|
+
providerId: 'provider-b',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
abilities: {},
|
|
93
|
+
enabled: true,
|
|
94
|
+
id: 'vendor-prefix/model-embedding-1',
|
|
95
|
+
type: 'embedding',
|
|
96
|
+
providerId: 'provider-b',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
abilities: {},
|
|
100
|
+
enabled: false,
|
|
101
|
+
id: 'model-chat-1',
|
|
102
|
+
type: 'chat',
|
|
103
|
+
providerId: 'provider-c',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
abilities: {},
|
|
107
|
+
enabled: false,
|
|
108
|
+
id: 'model-embedding-1',
|
|
109
|
+
type: 'embedding',
|
|
110
|
+
providerId: 'provider-c',
|
|
111
|
+
},
|
|
78
112
|
],
|
|
79
113
|
{
|
|
80
114
|
'provider-a': { apiKey: 'a-key' },
|
|
81
115
|
'provider-b': { apiKey: 'b-key' },
|
|
116
|
+
'provider-c': { apiKey: 'c-key' },
|
|
82
117
|
'provider-e': { apiKey: 'e-key' },
|
|
83
|
-
'provider-l': { apiKey: 'l-key' },
|
|
84
118
|
},
|
|
85
119
|
);
|
|
86
120
|
|
|
87
|
-
const keyVaults = (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
121
|
+
const keyVaults = await (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
88
122
|
|
|
89
123
|
expect(keyVaults).toMatchObject({
|
|
90
|
-
'provider-a': { apiKey: 'a-key' },
|
|
91
|
-
'provider-e': { apiKey: 'e-key' },
|
|
92
|
-
'provider-l': { apiKey: 'l-key' }, // layer extractor models resolved
|
|
124
|
+
'provider-a': { apiKey: 'a-key' },
|
|
125
|
+
'provider-e': { apiKey: 'e-key' },
|
|
93
126
|
});
|
|
94
127
|
});
|
|
95
128
|
|
|
96
|
-
it('warns and falls back to server provider when no enabled provider satisfies embedding model', () => {
|
|
129
|
+
it('warns and falls back to server provider when no enabled provider satisfies embedding model', async () => {
|
|
97
130
|
const executor = createExecutor();
|
|
98
131
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
99
132
|
|
|
100
133
|
const runtimeState = createRuntimeState(
|
|
101
134
|
[
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
{
|
|
136
|
+
abilities: {},
|
|
137
|
+
enabled: true,
|
|
138
|
+
id: 'model-chat-1',
|
|
139
|
+
type: 'chat',
|
|
140
|
+
providerId: 'provider-a',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
abilities: {},
|
|
144
|
+
enabled: true,
|
|
145
|
+
id: 'model-embedding-1',
|
|
146
|
+
type: 'embedding',
|
|
147
|
+
providerId: 'provider-e',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
abilities: {},
|
|
151
|
+
enabled: true,
|
|
152
|
+
id: 'vendor-prefix/model-chat-1',
|
|
153
|
+
type: 'chat',
|
|
154
|
+
providerId: 'provider-b',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
abilities: {},
|
|
158
|
+
enabled: true,
|
|
159
|
+
id: 'vendor-prefix/model-embedding-1',
|
|
160
|
+
type: 'embedding',
|
|
161
|
+
providerId: 'provider-b',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
abilities: {},
|
|
165
|
+
enabled: false,
|
|
166
|
+
id: 'model-chat-1',
|
|
167
|
+
type: 'chat',
|
|
168
|
+
providerId: 'provider-c',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
abilities: {},
|
|
172
|
+
enabled: false,
|
|
173
|
+
id: 'model-embedding-1',
|
|
174
|
+
type: 'embedding',
|
|
175
|
+
providerId: 'provider-c',
|
|
176
|
+
},
|
|
108
177
|
],
|
|
109
178
|
{
|
|
110
179
|
'provider-b': { apiKey: 'b-key' },
|
|
@@ -112,7 +181,7 @@ describe('MemoryExtractionExecutor.resolveRuntimeKeyVaults', () => {
|
|
|
112
181
|
},
|
|
113
182
|
);
|
|
114
183
|
|
|
115
|
-
const keyVaults = (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
184
|
+
const keyVaults = await (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
116
185
|
|
|
117
186
|
expect(keyVaults).toMatchObject({
|
|
118
187
|
'provider-b': { apiKey: 'b-key' },
|
|
@@ -123,4 +192,90 @@ describe('MemoryExtractionExecutor.resolveRuntimeKeyVaults', () => {
|
|
|
123
192
|
|
|
124
193
|
warnSpy.mockRestore();
|
|
125
194
|
});
|
|
195
|
+
|
|
196
|
+
it('ignores disabled providers when resolving key vaults', async () => {
|
|
197
|
+
const executor = createExecutor({
|
|
198
|
+
embeddingPreferredProviders: ['provider-disabled', 'provider-a'],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const runtimeState = createRuntimeState(
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
abilities: {},
|
|
205
|
+
enabled: false,
|
|
206
|
+
id: 'embed-1',
|
|
207
|
+
type: 'embedding',
|
|
208
|
+
providerId: 'provider-disabled',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
abilities: {},
|
|
212
|
+
enabled: true,
|
|
213
|
+
id: 'embed-1',
|
|
214
|
+
type: 'embedding',
|
|
215
|
+
providerId: 'provider-a',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
{
|
|
219
|
+
'provider-disabled': { apiKey: 'disabled-key' },
|
|
220
|
+
'provider-a': { apiKey: 'a-key' },
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const keyVaults = await (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
225
|
+
|
|
226
|
+
expect(keyVaults).toMatchObject({
|
|
227
|
+
'provider-a': { apiKey: 'a-key' },
|
|
228
|
+
});
|
|
229
|
+
expect(keyVaults).not.toHaveProperty('provider-disabled');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('respects preferred provider order when multiple providers have the model', async () => {
|
|
233
|
+
const executor = createExecutor({
|
|
234
|
+
agentGateKeeper: {
|
|
235
|
+
model: 'gate-2',
|
|
236
|
+
provider: 'provider-a', // fallback provider differs from preferred order
|
|
237
|
+
apiKey: 'sys-a-key',
|
|
238
|
+
baseURL: 'https://api-a.example.com',
|
|
239
|
+
language: 'English',
|
|
240
|
+
},
|
|
241
|
+
agentGateKeeperPreferredProviders: ['provider-b', 'provider-a'],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const runtimeState = createRuntimeState(
|
|
245
|
+
[
|
|
246
|
+
{ abilities: {}, enabled: true, id: 'gate-2', type: 'chat', providerId: 'provider-a' },
|
|
247
|
+
{ abilities: {}, enabled: true, id: 'gate-2', type: 'chat', providerId: 'provider-b' },
|
|
248
|
+
],
|
|
249
|
+
{
|
|
250
|
+
'provider-a': { apiKey: 'a-key' },
|
|
251
|
+
'provider-b': { apiKey: 'b-key' },
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const keyVaults = await (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
256
|
+
|
|
257
|
+
expect(keyVaults).toMatchObject({
|
|
258
|
+
'provider-b': { apiKey: 'b-key' }, // picks first preferred provider
|
|
259
|
+
});
|
|
260
|
+
expect(keyVaults).not.toHaveProperty('provider-a');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('falls back to configured provider when no enabled models match', async () => {
|
|
264
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
265
|
+
const executor = createExecutor({
|
|
266
|
+
agentGateKeeper: { model: 'gate-2', provider: 'provider-fallback', apiKey: 'sys-fb-key' },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const runtimeState = createRuntimeState([], {
|
|
270
|
+
'provider-fallback': { apiKey: 'fb-key' },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const keyVaults = await (executor as any).resolveRuntimeKeyVaults(runtimeState);
|
|
274
|
+
|
|
275
|
+
expect(keyVaults).toMatchObject({
|
|
276
|
+
'provider-fallback': { apiKey: 'fb-key' },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
warnSpy.mockRestore();
|
|
280
|
+
});
|
|
126
281
|
});
|
|
@@ -296,20 +296,54 @@ const maskSecret = (value?: string) => {
|
|
|
296
296
|
return `${value.slice(0, 6)}***${value.slice(-4)}`;
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
type ProviderCredential = { apiKey?: string; baseURL?: string };
|
|
300
|
+
|
|
301
|
+
type RuntimeResolveOptions = {
|
|
302
|
+
fallback?: ProviderCredential;
|
|
303
|
+
preferred?: {
|
|
304
|
+
providerIds?: string[];
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const resolveRuntimeAgentConfig = (
|
|
309
|
+
agent: MemoryAgentConfig,
|
|
310
|
+
keyVaults?: ProviderKeyVaultMap,
|
|
311
|
+
options?: RuntimeResolveOptions,
|
|
312
|
+
) => {
|
|
313
|
+
const normalizedPreferredProviders = (options?.preferred?.providerIds || [])
|
|
314
|
+
.map(normalizeProvider)
|
|
315
|
+
.filter(Boolean);
|
|
316
|
+
|
|
317
|
+
const providerOrder = Array.from(
|
|
318
|
+
new Set([
|
|
319
|
+
...normalizedPreferredProviders,
|
|
320
|
+
normalizeProvider(agent.provider || 'openai'),
|
|
321
|
+
...Object.keys(keyVaults || {}),
|
|
322
|
+
]),
|
|
303
323
|
);
|
|
304
324
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
for (const provider of providerOrder) {
|
|
326
|
+
const { apiKey: userApiKey, baseURL: userBaseURL } = extractCredentialsFromVault(
|
|
327
|
+
keyVaults?.[provider],
|
|
328
|
+
);
|
|
329
|
+
if (!userApiKey) continue;
|
|
330
|
+
|
|
331
|
+
// Only use the user baseURL if we are also using their API key; otherwise fall back entirely
|
|
332
|
+
// to system config to avoid mixing credentials.
|
|
333
|
+
return {
|
|
334
|
+
apiKey: userApiKey,
|
|
335
|
+
baseURL: userBaseURL || agent.baseURL || options?.fallback?.baseURL,
|
|
336
|
+
provider,
|
|
337
|
+
source: 'user-keyvault' as const,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
311
340
|
|
|
312
|
-
return {
|
|
341
|
+
return {
|
|
342
|
+
apiKey: agent.apiKey || options?.fallback?.apiKey,
|
|
343
|
+
baseURL: agent.baseURL || options?.fallback?.baseURL,
|
|
344
|
+
provider: agent.provider || 'openai',
|
|
345
|
+
source: 'system-config' as const,
|
|
346
|
+
};
|
|
313
347
|
};
|
|
314
348
|
|
|
315
349
|
const logRuntime = debug('lobe-server:memory:user-memory:runtime');
|
|
@@ -329,8 +363,12 @@ const debugRuntimeInit = (
|
|
|
329
363
|
});
|
|
330
364
|
};
|
|
331
365
|
|
|
332
|
-
const initRuntimeForAgent = async (
|
|
333
|
-
|
|
366
|
+
const initRuntimeForAgent = async (
|
|
367
|
+
agent: MemoryAgentConfig,
|
|
368
|
+
keyVaults?: ProviderKeyVaultMap,
|
|
369
|
+
options?: RuntimeResolveOptions,
|
|
370
|
+
) => {
|
|
371
|
+
const resolved = resolveRuntimeAgentConfig(agent, keyVaults, options);
|
|
334
372
|
debugRuntimeInit(agent, resolved);
|
|
335
373
|
|
|
336
374
|
if (!resolved.apiKey) {
|
|
@@ -1142,7 +1180,7 @@ export class MemoryExtractionExecutor {
|
|
|
1142
1180
|
userModel.getUserState(KeyVaultsGateKeeper.getUserKeyVaults),
|
|
1143
1181
|
this.getAiProviderRuntimeState(job.userId),
|
|
1144
1182
|
]);
|
|
1145
|
-
const keyVaults = this.resolveRuntimeKeyVaults(aiProviderRuntimeState);
|
|
1183
|
+
const keyVaults = await this.resolveRuntimeKeyVaults(aiProviderRuntimeState);
|
|
1146
1184
|
const language = userState.settings?.general?.responseLanguage;
|
|
1147
1185
|
|
|
1148
1186
|
const runtimes = await this.getRuntime(job.userId, keyVaults);
|
|
@@ -1827,7 +1865,9 @@ export class MemoryExtractionExecutor {
|
|
|
1827
1865
|
return aiInfraRepos.getAiProviderRuntimeState(KeyVaultsGateKeeper.getUserKeyVaults);
|
|
1828
1866
|
}
|
|
1829
1867
|
|
|
1830
|
-
private resolveRuntimeKeyVaults(
|
|
1868
|
+
private async resolveRuntimeKeyVaults(
|
|
1869
|
+
runtimeState: AiProviderRuntimeState,
|
|
1870
|
+
): Promise<ProviderKeyVaultMap> {
|
|
1831
1871
|
const normalizedRuntimeConfig = Object.fromEntries(
|
|
1832
1872
|
Object.entries(runtimeState.runtimeConfig || {}).map(([providerId, config]) => [
|
|
1833
1873
|
normalizeProvider(providerId),
|
|
@@ -1835,98 +1875,46 @@ export class MemoryExtractionExecutor {
|
|
|
1835
1875
|
]),
|
|
1836
1876
|
);
|
|
1837
1877
|
|
|
1838
|
-
const providerModels = runtimeState.enabledAiModels.reduce<Record<string, Set<string>>>(
|
|
1839
|
-
(acc, model) => {
|
|
1840
|
-
const providerId = normalizeProvider(model.providerId);
|
|
1841
|
-
acc[providerId] = acc[providerId] || new Set<string>();
|
|
1842
|
-
acc[providerId].add(model.id);
|
|
1843
|
-
return acc;
|
|
1844
|
-
},
|
|
1845
|
-
{},
|
|
1846
|
-
);
|
|
1847
|
-
|
|
1848
|
-
const resolveProviderForModel = (
|
|
1849
|
-
modelId: string,
|
|
1850
|
-
fallbackProvider?: string,
|
|
1851
|
-
preferredProviders?: string[],
|
|
1852
|
-
preferredModels?: string[],
|
|
1853
|
-
label?: string,
|
|
1854
|
-
) => {
|
|
1855
|
-
const providerOrder = Array.from(
|
|
1856
|
-
new Set(
|
|
1857
|
-
[
|
|
1858
|
-
...(preferredProviders?.map(normalizeProvider) || []),
|
|
1859
|
-
fallbackProvider ? normalizeProvider(fallbackProvider) : undefined,
|
|
1860
|
-
...Object.keys(providerModels),
|
|
1861
|
-
].filter(Boolean) as string[],
|
|
1862
|
-
),
|
|
1863
|
-
);
|
|
1864
|
-
|
|
1865
|
-
const candidateModels = preferredModels && preferredModels.length > 0 ? preferredModels : [];
|
|
1866
|
-
|
|
1867
|
-
for (const providerId of providerOrder) {
|
|
1868
|
-
const models = providerModels[providerId];
|
|
1869
|
-
if (!models) continue;
|
|
1870
|
-
if (models.has(modelId)) return providerId;
|
|
1871
|
-
|
|
1872
|
-
const preferredMatch = candidateModels.find((preferredModel) => models.has(preferredModel));
|
|
1873
|
-
if (preferredMatch) return providerId;
|
|
1874
|
-
}
|
|
1875
|
-
if (fallbackProvider) {
|
|
1876
|
-
console.warn(
|
|
1877
|
-
`[memory-extraction] no enabled provider found for ${label || 'model'} "${modelId}"`,
|
|
1878
|
-
`(preferred ${preferredProviders}), falling back to server-configured provider "${fallbackProvider}".`,
|
|
1879
|
-
);
|
|
1880
|
-
|
|
1881
|
-
return normalizeProvider(fallbackProvider);
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
throw new Error(
|
|
1885
|
-
`Unable to resolve provider for ${label || 'model'} "${modelId}". ` +
|
|
1886
|
-
`Check preferred providers/models configuration.`,
|
|
1887
|
-
);
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
1878
|
const keyVaults: ProviderKeyVaultMap = {};
|
|
1891
1879
|
|
|
1892
|
-
const gatekeeperProvider =
|
|
1893
|
-
this.
|
|
1894
|
-
|
|
1895
|
-
this.
|
|
1896
|
-
this.gatekeeperPreferredModels,
|
|
1897
|
-
|
|
1898
|
-
);
|
|
1880
|
+
const gatekeeperProvider = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1881
|
+
fallbackProvider: this.privateConfig.agentGateKeeper.provider,
|
|
1882
|
+
label: 'gatekeeper',
|
|
1883
|
+
modelId: this.modelConfig.gateModel,
|
|
1884
|
+
preferredModels: this.gatekeeperPreferredModels,
|
|
1885
|
+
preferredProviders: this.gatekeeperPreferredProviders,
|
|
1886
|
+
});
|
|
1899
1887
|
const gatekeeperRuntime = normalizedRuntimeConfig[gatekeeperProvider];
|
|
1900
1888
|
if (gatekeeperRuntime?.keyVaults) {
|
|
1901
1889
|
keyVaults[gatekeeperProvider] = gatekeeperRuntime.keyVaults;
|
|
1902
1890
|
}
|
|
1903
1891
|
|
|
1904
|
-
const embeddingProvider =
|
|
1905
|
-
this.
|
|
1906
|
-
|
|
1907
|
-
this.
|
|
1908
|
-
this.embeddingPreferredModels,
|
|
1909
|
-
|
|
1910
|
-
);
|
|
1892
|
+
const embeddingProvider = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1893
|
+
fallbackProvider: this.privateConfig.embedding.provider,
|
|
1894
|
+
label: 'embedding',
|
|
1895
|
+
modelId: this.modelConfig.embeddingsModel,
|
|
1896
|
+
preferredModels: this.embeddingPreferredModels,
|
|
1897
|
+
preferredProviders: this.embeddingPreferredProviders,
|
|
1898
|
+
});
|
|
1911
1899
|
const embeddingRuntime = normalizedRuntimeConfig[embeddingProvider];
|
|
1912
1900
|
if (embeddingRuntime?.keyVaults) {
|
|
1913
1901
|
keyVaults[embeddingProvider] = embeddingRuntime.keyVaults;
|
|
1914
1902
|
}
|
|
1915
1903
|
|
|
1916
|
-
Object.values(this.modelConfig.layerModels)
|
|
1917
|
-
if (!model)
|
|
1918
|
-
const providerId =
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
this.layerPreferredModels,
|
|
1923
|
-
|
|
1924
|
-
);
|
|
1904
|
+
for (const model of Object.values(this.modelConfig.layerModels)) {
|
|
1905
|
+
if (!model) continue;
|
|
1906
|
+
const providerId = await AiInfraRepos.tryMatchingProviderFrom(runtimeState, {
|
|
1907
|
+
fallbackProvider: this.privateConfig.agentLayerExtractor.provider,
|
|
1908
|
+
label: 'layer extractor',
|
|
1909
|
+
modelId: model,
|
|
1910
|
+
preferredModels: this.layerPreferredModels,
|
|
1911
|
+
preferredProviders: this.layerPreferredProviders,
|
|
1912
|
+
});
|
|
1925
1913
|
const runtime = normalizedRuntimeConfig[providerId];
|
|
1926
1914
|
if (runtime?.keyVaults) {
|
|
1927
1915
|
keyVaults[providerId] = runtime.keyVaults;
|
|
1928
1916
|
}
|
|
1929
|
-
}
|
|
1917
|
+
}
|
|
1930
1918
|
|
|
1931
1919
|
return keyVaults;
|
|
1932
1920
|
}
|
|
@@ -1944,10 +1932,46 @@ export class MemoryExtractionExecutor {
|
|
|
1944
1932
|
const cached = this.runtimeCache.get(userId);
|
|
1945
1933
|
if (cached) return cached;
|
|
1946
1934
|
|
|
1935
|
+
const embeddingOptions: RuntimeResolveOptions = {
|
|
1936
|
+
fallback: {
|
|
1937
|
+
apiKey: this.privateConfig.embedding.apiKey,
|
|
1938
|
+
baseURL: this.privateConfig.embedding.baseURL,
|
|
1939
|
+
},
|
|
1940
|
+
preferred: { providerIds: this.embeddingPreferredProviders },
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
const gatekeeperOptions: RuntimeResolveOptions = {
|
|
1944
|
+
fallback: {
|
|
1945
|
+
apiKey: this.privateConfig.agentGateKeeper.apiKey,
|
|
1946
|
+
baseURL: this.privateConfig.agentGateKeeper.baseURL,
|
|
1947
|
+
},
|
|
1948
|
+
preferred: { providerIds: this.gatekeeperPreferredProviders },
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
const layerExtractorOptions: RuntimeResolveOptions = {
|
|
1952
|
+
fallback: {
|
|
1953
|
+
apiKey: this.privateConfig.agentLayerExtractor.apiKey,
|
|
1954
|
+
baseURL: this.privateConfig.agentLayerExtractor.baseURL,
|
|
1955
|
+
},
|
|
1956
|
+
preferred: { providerIds: this.layerPreferredProviders },
|
|
1957
|
+
};
|
|
1958
|
+
|
|
1947
1959
|
const runtimes: RuntimeBundle = {
|
|
1948
|
-
embeddings: await initRuntimeForAgent(
|
|
1949
|
-
|
|
1950
|
-
|
|
1960
|
+
embeddings: await initRuntimeForAgent(
|
|
1961
|
+
{ ...this.privateConfig.embedding },
|
|
1962
|
+
keyVaults,
|
|
1963
|
+
embeddingOptions,
|
|
1964
|
+
),
|
|
1965
|
+
gatekeeper: await initRuntimeForAgent(
|
|
1966
|
+
{ ...this.privateConfig.agentGateKeeper },
|
|
1967
|
+
keyVaults,
|
|
1968
|
+
gatekeeperOptions,
|
|
1969
|
+
),
|
|
1970
|
+
layerExtractor: await initRuntimeForAgent(
|
|
1971
|
+
{ ...this.privateConfig.agentLayerExtractor },
|
|
1972
|
+
keyVaults,
|
|
1973
|
+
layerExtractorOptions,
|
|
1974
|
+
),
|
|
1951
1975
|
};
|
|
1952
1976
|
|
|
1953
1977
|
this.runtimeCache.set(userId, runtimes);
|
|
@@ -1986,7 +2010,7 @@ export class MemoryExtractionExecutor {
|
|
|
1986
2010
|
userModel.getUserState(KeyVaultsGateKeeper.getUserKeyVaults),
|
|
1987
2011
|
this.getAiProviderRuntimeState(params.userId),
|
|
1988
2012
|
]);
|
|
1989
|
-
const keyVaults = this.resolveRuntimeKeyVaults(aiProviderRuntimeState);
|
|
2013
|
+
const keyVaults = await this.resolveRuntimeKeyVaults(aiProviderRuntimeState);
|
|
1990
2014
|
const language = params.language || userState.settings?.general?.responseLanguage;
|
|
1991
2015
|
|
|
1992
2016
|
const runtimes = await this.getRuntime(params.userId, keyVaults);
|
|
@@ -2266,7 +2290,7 @@ export class MemoryExtractionWorkflowService {
|
|
|
2266
2290
|
|
|
2267
2291
|
const url = getWorkflowUrl(WORKFLOW_PATHS.personaUpdate, baseUrl);
|
|
2268
2292
|
return this.getClient().trigger({
|
|
2269
|
-
body: { userId },
|
|
2293
|
+
body: { userIds: [userId] },
|
|
2270
2294
|
flowControl: {
|
|
2271
2295
|
key: `memory-user-memory.pipelines.persona.update-write.${userId}`,
|
|
2272
2296
|
parallelism: 1,
|
|
@@ -8,6 +8,32 @@ import { UserPersonaModel } from '@/database/models/userMemory/persona';
|
|
|
8
8
|
|
|
9
9
|
import { UserPersonaService } from '../service';
|
|
10
10
|
|
|
11
|
+
// Use var to avoid TDZ with vi.mock hoisting
|
|
12
|
+
var aiInfraMocks:
|
|
13
|
+
| undefined
|
|
14
|
+
| {
|
|
15
|
+
getAiProviderRuntimeState: ReturnType<typeof vi.fn>;
|
|
16
|
+
tryMatchingModelFrom: ReturnType<typeof vi.fn>;
|
|
17
|
+
tryMatchingProviderFrom: ReturnType<typeof vi.fn>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
vi.mock('@/database/repositories/aiInfra', () => {
|
|
21
|
+
aiInfraMocks = {
|
|
22
|
+
getAiProviderRuntimeState: vi.fn(),
|
|
23
|
+
tryMatchingModelFrom: vi.fn(),
|
|
24
|
+
tryMatchingProviderFrom: vi.fn(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const AiInfraRepos = vi.fn().mockImplementation(() => ({
|
|
28
|
+
getAiProviderRuntimeState: aiInfraMocks!.getAiProviderRuntimeState,
|
|
29
|
+
})) as unknown as typeof import('@/database/repositories/aiInfra').AiInfraRepos;
|
|
30
|
+
|
|
31
|
+
(AiInfraRepos as any).tryMatchingModelFrom = aiInfraMocks!.tryMatchingModelFrom;
|
|
32
|
+
(AiInfraRepos as any).tryMatchingProviderFrom = aiInfraMocks!.tryMatchingProviderFrom;
|
|
33
|
+
|
|
34
|
+
return { AiInfraRepos };
|
|
35
|
+
});
|
|
36
|
+
|
|
11
37
|
vi.mock('@/server/globalConfig/parseMemoryExtractionConfig', () => ({
|
|
12
38
|
parseMemoryExtractionConfig: () => ({
|
|
13
39
|
agentLayerExtractor: {
|
|
@@ -28,6 +54,10 @@ vi.mock('@/server/globalConfig/parseMemoryExtractionConfig', () => ({
|
|
|
28
54
|
}),
|
|
29
55
|
}));
|
|
30
56
|
|
|
57
|
+
vi.mock('@/server/modules/KeyVaultsEncrypt', () => ({
|
|
58
|
+
KeyVaultsGateKeeper: { getUserKeyVaults: vi.fn() },
|
|
59
|
+
}));
|
|
60
|
+
|
|
31
61
|
const structuredResult = {
|
|
32
62
|
diff: '- updated',
|
|
33
63
|
memoryIds: ['mem-1'],
|
|
@@ -56,6 +86,22 @@ const userId = 'user-persona-service';
|
|
|
56
86
|
|
|
57
87
|
beforeEach(async () => {
|
|
58
88
|
toolCall.mockClear();
|
|
89
|
+
aiInfraMocks!.getAiProviderRuntimeState.mockReset();
|
|
90
|
+
aiInfraMocks!.tryMatchingModelFrom.mockReset();
|
|
91
|
+
aiInfraMocks!.tryMatchingProviderFrom.mockReset();
|
|
92
|
+
aiInfraMocks!.tryMatchingModelFrom.mockResolvedValue('openai');
|
|
93
|
+
aiInfraMocks!.tryMatchingProviderFrom.mockResolvedValue('openai');
|
|
94
|
+
aiInfraMocks!.getAiProviderRuntimeState.mockResolvedValue({
|
|
95
|
+
enabledAiModels: [
|
|
96
|
+
{ abilities: {}, enabled: true, id: 'gpt-mock', providerId: 'openai', type: 'chat' },
|
|
97
|
+
],
|
|
98
|
+
enabledAiProviders: [],
|
|
99
|
+
enabledChatAiProviders: [],
|
|
100
|
+
enabledImageAiProviders: [],
|
|
101
|
+
runtimeConfig: {
|
|
102
|
+
openai: { keyVaults: { apiKey: 'vault-key', baseURL: 'https://vault.example.com' } },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
59
105
|
db = await getTestDB();
|
|
60
106
|
|
|
61
107
|
await db.delete(users);
|