@smythos/sre 1.5.70 → 1.5.72

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.
Files changed (27) hide show
  1. package/dist/index.js +14 -15
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/Components/MemoryDeleteKeyVal.class.d.ts +8 -0
  4. package/dist/types/Components/MemoryReadKeyVal.class.d.ts +7 -0
  5. package/dist/types/subsystems/IO/Storage.service/connectors/AzureBlobStorage.class.d.ts +211 -0
  6. package/dist/types/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.d.ts +3 -7
  7. package/dist/types/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.d.ts +1 -0
  8. package/dist/types/subsystems/LLMManager/models.d.ts +0 -1
  9. package/dist/types/types/LLM.types.d.ts +1 -1
  10. package/package.json +5 -1
  11. package/src/Components/ECMASandbox.class.ts +4 -3
  12. package/src/Components/MemoryDeleteKeyVal.class.ts +3 -3
  13. package/src/Components/MemoryReadKeyVal.class.ts +5 -4
  14. package/src/Components/MemoryWriteObject.class.ts +1 -1
  15. package/src/helpers/AWSLambdaCode.helper.ts +30 -22
  16. package/src/helpers/Conversation.helper.ts +3 -2
  17. package/src/helpers/ECMASandbox.helper.ts +17 -7
  18. package/src/helpers/Sysconfig.helper.ts +18 -0
  19. package/src/subsystems/AgentManager/AgentData.service/connectors/LocalAgentDataConnector.class.ts +3 -0
  20. package/src/subsystems/IO/VectorDB.service/connectors/RAMVecrtorDB.class.ts +93 -108
  21. package/src/subsystems/IO/VectorDB.service/embed/GoogleEmbedding.ts +1 -1
  22. package/src/subsystems/IO/VectorDB.service/embed/OpenAIEmbedding.ts +1 -1
  23. package/src/subsystems/LLMManager/LLM.inference.ts +3 -0
  24. package/src/subsystems/LLMManager/ModelsProvider.service/connectors/JSONModelsProvider.class.ts +16 -0
  25. package/src/subsystems/LLMManager/models.ts +2 -2
  26. package/src/subsystems/Security/Account.service/connectors/JSONFileAccount.class.ts +6 -0
  27. package/src/types/LLM.types.ts +1 -1
@@ -13,6 +13,24 @@ export function findSmythPath(_path: string = '', callback?: (smythDir: string,
13
13
 
14
14
  const searchDirectories = [];
15
15
 
16
+ if (process.env.SMYTH_PATH) {
17
+ if (!fs.existsSync(process.env.SMYTH_PATH)) {
18
+ console.error('CRITICAL : SMYTH_PATH environment variable is not a valid directory');
19
+ process.exit(1);
20
+ }
21
+
22
+ const envDir = path.resolve(process.env.SMYTH_PATH, _path);
23
+ //check if the directory exists
24
+ if (!fs.existsSync(envDir)) {
25
+ callback?.(envDir, false, null);
26
+ console.error(`CRITICAL : missing directory (${envDir}) under SMYTH_PATH `);
27
+ //process.exit(1);
28
+ } else {
29
+ callback?.(envDir, true, null);
30
+ }
31
+ return envDir;
32
+ }
33
+
16
34
  // 1. Try to find in local directory (the directory from which the program was run)
17
35
  const localDir = path.resolve(process.cwd(), '.smyth', _path);
18
36
  searchDirectories.push(localDir);
@@ -2,6 +2,9 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { AgentDataConnector } from '../AgentDataConnector';
4
4
  import { uid } from '@sre/utils/general.utils';
5
+ import { Logger } from '@sre/helpers/Log.helper';
6
+
7
+ const console = Logger('LocalAgentDataConnector');
5
8
 
6
9
  export type LocalAgentDataSettings = { devDir: string; prodDir: string };
7
10
 
@@ -5,7 +5,7 @@ import { IAccessCandidate, IACL, TAccessLevel } from '@sre/types/ACL.types';
5
5
  import { AccessRequest } from '@sre/Security/AccessControl/AccessRequest.class';
6
6
  import { AccessCandidate } from '@sre/Security/AccessControl/AccessCandidate.class';
7
7
  import { SecureConnector } from '@sre/Security/SecureConnector.class';
8
- import { VectorDBConnector } from '../VectorDBConnector';
8
+ import { VectorDBConnector, DeleteTarget } from '../VectorDBConnector';
9
9
  import {
10
10
  DatasourceDto,
11
11
  IStorageVectorDataSource,
@@ -21,6 +21,8 @@ import { OpenAIEmbeds } from '@sre/IO/VectorDB.service/embed/OpenAIEmbedding';
21
21
  import crypto from 'crypto';
22
22
  import { BaseEmbedding, TEmbeddings } from '../embed/BaseEmbedding';
23
23
  import { EmbeddingsFactory } from '../embed';
24
+ import { chunkText } from '@sre/utils/string.utils';
25
+ import { jsonrepair } from 'jsonrepair';
24
26
 
25
27
  const console = Logger('RAM VectorDB');
26
28
 
@@ -66,6 +68,7 @@ export class RAMVectorDB extends VectorDBConnector {
66
68
  if (!_settings.embeddings) {
67
69
  _settings.embeddings = { provider: 'OpenAI', model: 'text-embedding-3-large', params: { dimensions: 1024 } };
68
70
  }
71
+ if (!_settings.embeddings.params) _settings.embeddings.params = { dimensions: 1024 };
69
72
  if (!_settings.embeddings.params?.dimensions) _settings.embeddings.params.dimensions = 1024;
70
73
 
71
74
  this.embedder = EmbeddingsFactory.create(_settings.embeddings.provider, _settings.embeddings);
@@ -182,12 +185,26 @@ export class RAMVectorDB extends VectorDBConnector {
182
185
 
183
186
  for (const vector of namespaceData) {
184
187
  const similarity = this.cosineSimilarity(queryVector as number[], vector.values);
188
+
189
+ let userMetadata = undefined;
190
+ if (options.includeMetadata) {
191
+ if (vector.metadata?.[this.USER_METADATA_KEY]) {
192
+ try {
193
+ userMetadata = JSON.parse(vector.metadata[this.USER_METADATA_KEY]);
194
+ } catch {
195
+ userMetadata = vector.metadata[this.USER_METADATA_KEY];
196
+ }
197
+ } else {
198
+ userMetadata = {}; // Return empty object when no metadata exists, like Milvus
199
+ }
200
+ }
201
+
185
202
  results.push({
186
203
  id: vector.id,
187
204
  score: similarity,
188
205
  values: vector.values,
189
- metadata: options.includeMetadata ? vector.metadata : undefined,
190
- text: vector.metadata?.text,
206
+ text: vector.metadata?.text as string | undefined,
207
+ metadata: options.includeMetadata ? userMetadata : undefined,
191
208
  });
192
209
  }
193
210
 
@@ -207,28 +224,28 @@ export class RAMVectorDB extends VectorDBConnector {
207
224
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
208
225
  const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
209
226
 
210
- const sources = Array.isArray(sourceWrapper) ? sourceWrapper : [sourceWrapper];
211
- const insertedIds: string[] = [];
227
+ sourceWrapper = Array.isArray(sourceWrapper) ? sourceWrapper : [sourceWrapper];
212
228
 
213
- if (!this.vectors[preparedNs]) {
214
- this.vectors[preparedNs] = [];
229
+ // make sure that all sources are of the same type (source.source)
230
+ if (sourceWrapper.some((s) => this.embedder.detectSourceType(s.source) !== this.embedder.detectSourceType(sourceWrapper[0].source))) {
231
+ throw new Error('All sources must be of the same type');
215
232
  }
216
233
 
217
- for (const source of sources) {
218
- let vector: number[] = [];
234
+ const sourceType = this.embedder.detectSourceType(sourceWrapper[0].source);
235
+ if (sourceType === 'unknown' || sourceType === 'url') throw new Error('Invalid source type');
219
236
 
220
- if (typeof source.source === 'string') {
221
- // Text embedding
237
+ const transformedSource = await this.embedder.transformSource(sourceWrapper, sourceType, acRequest.candidate as AccessCandidate);
222
238
 
223
- vector = await this.embedder.embedText(source.source, acRequest.candidate as AccessCandidate);
224
- } else {
225
- // Direct vector
226
- vector = source.source;
227
- }
239
+ if (!this.vectors[preparedNs]) {
240
+ this.vectors[preparedNs] = [];
241
+ }
242
+
243
+ const insertedIds: string[] = [];
228
244
 
245
+ for (const source of transformedSource) {
229
246
  const vectorData: VectorData = {
230
247
  id: source.id,
231
- values: vector,
248
+ values: source.source as number[],
232
249
  datasource: source.metadata?.datasourceId || 'unknown',
233
250
  metadata: source.metadata,
234
251
  };
@@ -248,107 +265,96 @@ export class RAMVectorDB extends VectorDBConnector {
248
265
  }
249
266
 
250
267
  @SecureConnector.AccessControl
251
- protected async delete(acRequest: AccessRequest, namespace: string, id: string | string[]): Promise<void> {
268
+ protected async delete(acRequest: AccessRequest, namespace: string, deleteTarget: DeleteTarget): Promise<void> {
252
269
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
253
270
  const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
254
271
 
255
- const ids = Array.isArray(id) ? id : [id];
272
+ const isDeleteByFilter = typeof deleteTarget === 'object' && !Array.isArray(deleteTarget);
256
273
 
257
- if (this.vectors[preparedNs]) {
258
- this.vectors[preparedNs] = this.vectors[preparedNs].filter((vector) => !ids.includes(vector.id));
274
+ if (isDeleteByFilter) {
275
+ // Handle delete by filter (e.g., by datasourceId)
276
+ if ('datasourceId' in deleteTarget && deleteTarget.datasourceId) {
277
+ if (this.vectors[preparedNs]) {
278
+ this.vectors[preparedNs] = this.vectors[preparedNs].filter((vector) => vector.datasource !== deleteTarget.datasourceId);
279
+ }
280
+ } else {
281
+ throw new Error('Unsupported delete filter');
282
+ }
283
+ } else {
284
+ // Handle delete by ID(s)
285
+ const ids = Array.isArray(deleteTarget) ? deleteTarget : [deleteTarget];
286
+ if (this.vectors[preparedNs]) {
287
+ this.vectors[preparedNs] = this.vectors[preparedNs].filter((vector) => !ids.includes(vector.id));
288
+ }
259
289
  }
260
290
  }
261
291
 
262
292
  @SecureConnector.AccessControl
263
293
  protected async createDatasource(acRequest: AccessRequest, namespace: string, datasource: DatasourceDto): Promise<IStorageVectorDataSource> {
264
294
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
265
- const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
266
- const datasourceId = datasource.id || crypto.randomUUID();
267
-
268
- // Ensure namespace exists
269
- if (!this.namespaces[preparedNs]) {
270
- await this.createNamespace(acRequest, namespace);
271
- }
272
-
273
- // Process text and create vectors
274
- const vectorIds: string[] = [];
275
-
276
- // Split text into chunks if needed
277
- const chunkSize = datasource.chunkSize || 1000;
278
- const chunkOverlap = datasource.chunkOverlap || 200;
279
- const chunks = this.splitTextIntoChunks(datasource.text, chunkSize, chunkOverlap);
280
-
281
- // Initialize namespace vectors if not exists (should already exist if namespace was created properly)
282
- if (!this.vectors[preparedNs]) {
283
- this.vectors[preparedNs] = [];
284
- }
285
-
286
- for (let i = 0; i < chunks.length; i++) {
287
- const chunkId = `${datasourceId}_chunk_${i}`;
288
- const vector = await this.embedder.embedText(chunks[i], acRequest.candidate as AccessCandidate);
289
-
290
- const vectorData: VectorData = {
291
- id: chunkId,
292
- values: vector,
293
- datasource: datasourceId,
295
+ const acl = new ACL().addAccess(acRequest.candidate.role, acRequest.candidate.id, TAccessLevel.Owner);
296
+ const dsId = datasource.id || crypto.randomUUID();
297
+
298
+ const formattedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
299
+ const chunkedText = chunkText(datasource.text, {
300
+ chunkSize: datasource.chunkSize,
301
+ chunkOverlap: datasource.chunkOverlap,
302
+ });
303
+ const label = datasource.label || 'Untitled';
304
+ const ids = Array.from({ length: chunkedText.length }, (_, i) => `${dsId}_${crypto.randomUUID()}`);
305
+ const source: IVectorDataSourceDto[] = chunkedText.map((doc, i) => {
306
+ return {
307
+ id: ids[i],
308
+ source: doc,
294
309
  metadata: {
295
- ...datasource.metadata,
296
- text: chunks[i],
297
- chunkIndex: i,
298
- totalChunks: chunks.length,
310
+ acl: acl.serializedACL,
311
+ namespaceId: formattedNs,
312
+ datasourceId: dsId,
313
+ datasourceLabel: label,
314
+ [this.USER_METADATA_KEY]: datasource.metadata ? jsonrepair(JSON.stringify(datasource.metadata)) : undefined,
299
315
  },
300
316
  };
317
+ });
301
318
 
302
- this.vectors[preparedNs].push(vectorData);
303
- vectorIds.push(chunkId);
304
- }
319
+ const _vIds = await this.insert(acRequest, namespace, source);
305
320
 
306
- const storageDataSource: IStorageVectorDataSource = {
307
- namespaceId: preparedNs,
321
+ const dsData: IStorageVectorDataSource = {
322
+ namespaceId: formattedNs,
308
323
  candidateId: acRequest.candidate.id,
309
324
  candidateRole: acRequest.candidate.role,
310
- name: datasource.label || `Datasource ${datasourceId}`,
311
- metadata: JSON.stringify(datasource.metadata || {}),
325
+ name: datasource.label || 'Untitled',
326
+ metadata: datasource.metadata ? jsonrepair(JSON.stringify(datasource.metadata)) : undefined,
312
327
  text: datasource.text,
313
- vectorIds,
314
- id: datasourceId,
328
+ vectorIds: _vIds,
329
+ id: dsId,
315
330
  };
316
331
 
317
332
  // Store datasource metadata in memory
318
- if (!this.datasources[preparedNs]) {
319
- this.datasources[preparedNs] = {};
320
- }
321
- if (!this.datasources[preparedNs][datasourceId]) {
322
- this.datasources[preparedNs][datasourceId] = storageDataSource;
323
- } else {
324
- this.datasources[preparedNs][datasourceId].vectorIds.push(...vectorIds);
333
+ if (!this.datasources[formattedNs]) {
334
+ this.datasources[formattedNs] = {};
325
335
  }
336
+ this.datasources[formattedNs][dsId] = dsData;
326
337
 
327
- return storageDataSource;
338
+ return dsData;
328
339
  }
329
340
 
330
341
  @SecureConnector.AccessControl
331
342
  protected async deleteDatasource(acRequest: AccessRequest, namespace: string, datasourceId: string): Promise<void> {
332
343
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
333
- const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
344
+ const formattedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
334
345
 
335
- // Ensure namespace exists
336
- if (!this.namespaces[preparedNs]) {
337
- throw new Error('Namespace does not exist');
346
+ // Get datasource info to get vector IDs
347
+ const ds = this.datasources[formattedNs]?.[datasourceId];
348
+ if (!ds) {
349
+ throw new Error(`Data source not found with id: ${datasourceId}`);
338
350
  }
339
351
 
340
- // Get datasource info
341
- const datasource = this.datasources[preparedNs]?.[datasourceId];
342
- if (datasource) {
343
- // Delete all vectors belonging to this datasource
344
- if (this.vectors[preparedNs]) {
345
- this.vectors[preparedNs] = this.vectors[preparedNs].filter((vector) => vector.datasource !== datasourceId);
346
- }
347
- }
352
+ // Delete all vectors belonging to this datasource using the delete method
353
+ await this.delete(acRequest, namespace, ds.vectorIds || []);
348
354
 
349
355
  // Delete datasource metadata
350
- if (this.datasources[preparedNs]) {
351
- delete this.datasources[preparedNs][datasourceId];
356
+ if (this.datasources[formattedNs]) {
357
+ delete this.datasources[formattedNs][datasourceId];
352
358
  }
353
359
  }
354
360
 
@@ -362,15 +368,12 @@ export class RAMVectorDB extends VectorDBConnector {
362
368
  }
363
369
 
364
370
  @SecureConnector.AccessControl
365
- protected async getDatasource(acRequest: AccessRequest, namespace: string, datasourceId: string): Promise<IStorageVectorDataSource> {
371
+ protected async getDatasource(acRequest: AccessRequest, namespace: string, datasourceId: string): Promise<IStorageVectorDataSource | undefined> {
366
372
  //const teamId = await this.accountConnector.getCandidateTeam(acRequest.candidate);
367
373
  const preparedNs = this.constructNsName(acRequest.candidate as AccessCandidate, namespace);
368
374
 
369
375
  const datasource = this.datasources[preparedNs]?.[datasourceId];
370
- if (!datasource) {
371
- throw new Error(`Datasource ${datasourceId} not found`);
372
- }
373
- return datasource;
376
+ return datasource; // Return undefined if not found, like MilvusVectorDB
374
377
  }
375
378
 
376
379
  /**
@@ -400,22 +403,4 @@ export class RAMVectorDB extends VectorDBConnector {
400
403
 
401
404
  return dotProduct / (normA * normB);
402
405
  }
403
-
404
- /**
405
- * Split text into chunks with overlap
406
- */
407
- private splitTextIntoChunks(text: string, chunkSize: number, overlap: number): string[] {
408
- const chunks: string[] = [];
409
- let start = 0;
410
-
411
- while (start < text.length) {
412
- const end = Math.min(start + chunkSize, text.length);
413
- chunks.push(text.slice(start, end));
414
-
415
- if (end === text.length) break;
416
- start = end - overlap;
417
- }
418
-
419
- return chunks;
420
- }
421
406
  }
@@ -50,7 +50,7 @@ export class GoogleEmbeds extends BaseEmbedding {
50
50
  const modelInfo: TLLMModel = {
51
51
  provider: 'GoogleAI',
52
52
  modelId: this.model,
53
- credentials: this.settings?.credentials as unknown as TLLMCredentials,
53
+ credentials: (this.settings?.credentials as unknown as TLLMCredentials) || [TLLMCredentials.Internal, TLLMCredentials.Vault],
54
54
  };
55
55
  const credentials = await getLLMCredentials(candidate, modelInfo);
56
56
  apiKey = (credentials as BasicCredentials)?.apiKey;
@@ -82,7 +82,7 @@ export class OpenAIEmbeds extends BaseEmbedding {
82
82
  const modelInfo: TLLMModel = {
83
83
  provider: 'OpenAI',
84
84
  modelId: this.model,
85
- credentials: this.settings?.credentials as unknown as TLLMCredentials,
85
+ credentials: (this.settings?.credentials as unknown as TLLMCredentials) || [TLLMCredentials.Internal, TLLMCredentials.Vault],
86
86
  };
87
87
  const credentials = await getLLMCredentials(candidate, modelInfo);
88
88
 
@@ -127,6 +127,7 @@ export class LLMInference {
127
127
  return this.llmConnector.user(AccessCandidate.agent(params.agentId)).imageEditRequest(params);
128
128
  }
129
129
 
130
+ //@deprecated
130
131
  public async streamRequest(params: any, agent: string | IAgent) {
131
132
  const agentId = isAgent(agent) ? (agent as IAgent).id : agent;
132
133
  try {
@@ -149,6 +150,7 @@ export class LLMInference {
149
150
  }
150
151
  }
151
152
 
153
+ //@deprecated
152
154
  public async multimodalStreamRequest(params: any, fileSources, agent: string | IAgent) {
153
155
  const agentId = isAgent(agent) ? (agent as IAgent).id : agent;
154
156
 
@@ -180,6 +182,7 @@ export class LLMInference {
180
182
  }
181
183
  }
182
184
 
185
+ //@deprecated
183
186
  public async multimodalStreamRequestLegacy(prompt, files: string[], config: any = {}, agent: string | IAgent) {
184
187
  const agentId = isAgent(agent) ? (agent as IAgent).id : agent;
185
188
 
@@ -14,6 +14,7 @@ import chokidar from 'chokidar';
14
14
  import fs from 'fs/promises';
15
15
  import fsSync from 'fs';
16
16
  import path from 'path';
17
+ import { findSmythPath } from '@sre/helpers/Sysconfig.helper';
17
18
 
18
19
  const console = Logger('SmythModelsProvider');
19
20
 
@@ -52,6 +53,10 @@ export class JSONModelsProvider extends ModelsProviderConnector {
52
53
  else this.models = this._settings.models as TLLMModelsList;
53
54
  this.started = true;
54
55
  } else {
56
+ const modelsFolder = this.findModelsFolder();
57
+ if (modelsFolder) {
58
+ this.initDirWatcher(modelsFolder);
59
+ }
55
60
  this.started = true;
56
61
  }
57
62
  }
@@ -59,6 +64,17 @@ export class JSONModelsProvider extends ModelsProviderConnector {
59
64
  super.start();
60
65
  }
61
66
 
67
+ private findModelsFolder() {
68
+ const _modelsFolder = findSmythPath('models');
69
+
70
+ if (fsSync.existsSync(_modelsFolder)) {
71
+ console.warn('Using default models folder : ', _modelsFolder);
72
+ return _modelsFolder;
73
+ }
74
+
75
+ return null;
76
+ }
77
+
62
78
  @SecureConnector.AccessControl
63
79
  public async addModels(acRequest: AccessRequest, models: TLLMModelsList): Promise<void> {
64
80
  await this.ready();
@@ -68,11 +68,11 @@ export const models = {
68
68
  // keep the gpt-4o-mini as default model for now
69
69
  'gpt-4o-mini': {
70
70
  llm: 'OpenAI',
71
- alias: 'gpt-4o-mini-2024-07-18',
71
+
72
72
  components: ['PromptGenerator', 'LLMAssistant', 'Classifier', 'VisionLLM', 'AgentPlugin', 'Chatbot', 'GPTPlugin', 'GenAILLM'],
73
73
 
74
74
  label: 'GPT 4o Mini',
75
- modelId: 'gpt-4o-mini-2024-07-18',
75
+ modelId: 'gpt-4o-mini',
76
76
  provider: 'OpenAI',
77
77
  features: ['text', 'tools', 'image', 'search'],
78
78
  tags: ['Personal'],
@@ -74,8 +74,14 @@ export class JSONFileAccount extends AccountConnector {
74
74
  }
75
75
 
76
76
  public async isTeamMember(team: string, candidate: IAccessCandidate): Promise<boolean> {
77
+ if (team === DEFAULT_TEAM_ID) return true; //everyone is a member of the default team
78
+
77
79
  if (!this.data[team]) return false;
78
80
 
81
+ if (candidate.role === TAccessRole.Team && team === candidate.id) {
82
+ return true;
83
+ }
84
+
79
85
  if (candidate.role === TAccessRole.User) {
80
86
  return !!this.data[team].users?.[candidate.id];
81
87
  } else if (candidate.role === TAccessRole.Agent) {
@@ -221,7 +221,7 @@ export type TLLMModel = {
221
221
  tokens: number;
222
222
  completionTokens: number;
223
223
  };
224
- credentials?: TLLMCredentials;
224
+ credentials?: TLLMCredentials | TLLMCredentials[];
225
225
 
226
226
  //models can come with predefined params
227
227
  //this can also be used to pass a preconfigured model object