@nocobase/ai 2.0.0

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 (58) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +30 -0
  3. package/lib/ai-manager.d.ts +16 -0
  4. package/lib/ai-manager.js +49 -0
  5. package/lib/document-manager/document.d.ts +8 -0
  6. package/lib/document-manager/document.js +9 -0
  7. package/lib/document-manager/id-mapper.d.ts +17 -0
  8. package/lib/document-manager/id-mapper.js +79 -0
  9. package/lib/document-manager/index.d.ts +19 -0
  10. package/lib/document-manager/index.js +63 -0
  11. package/lib/document-manager/search-index.d.ts +19 -0
  12. package/lib/document-manager/search-index.js +69 -0
  13. package/lib/index.d.ts +12 -0
  14. package/lib/index.js +36 -0
  15. package/lib/loader/index.d.ts +11 -0
  16. package/lib/loader/index.js +34 -0
  17. package/lib/loader/scanner.d.ts +27 -0
  18. package/lib/loader/scanner.js +91 -0
  19. package/lib/loader/tools.d.ts +36 -0
  20. package/lib/loader/tools.js +151 -0
  21. package/lib/loader/types.d.ts +18 -0
  22. package/lib/loader/types.js +49 -0
  23. package/lib/loader/utils.d.ts +9 -0
  24. package/lib/loader/utils.js +51 -0
  25. package/lib/tools-manager/index.d.ts +23 -0
  26. package/lib/tools-manager/index.js +127 -0
  27. package/lib/tools-manager/types.d.ts +42 -0
  28. package/lib/tools-manager/types.js +24 -0
  29. package/package.json +21 -0
  30. package/src/__tests__/resource/ai/skills/document/tools/read.ts +23 -0
  31. package/src/__tests__/resource/ai/skills/document/tools/search/description.md +1 -0
  32. package/src/__tests__/resource/ai/skills/document/tools/search/index.ts +23 -0
  33. package/src/__tests__/resource/ai/tools/desc/description.md +1 -0
  34. package/src/__tests__/resource/ai/tools/desc/index.ts +23 -0
  35. package/src/__tests__/resource/ai/tools/empty.ts +9 -0
  36. package/src/__tests__/resource/ai/tools/group/group1.ts +23 -0
  37. package/src/__tests__/resource/ai/tools/group/group2.ts +23 -0
  38. package/src/__tests__/resource/ai/tools/group/group3/description.md +1 -0
  39. package/src/__tests__/resource/ai/tools/group/group3/index.ts +23 -0
  40. package/src/__tests__/resource/ai/tools/hallow/index.ts +23 -0
  41. package/src/__tests__/resource/ai/tools/ignored.ts +13 -0
  42. package/src/__tests__/resource/ai/tools/jsfiles/formFiller.js +61 -0
  43. package/src/__tests__/resource/ai/tools/jsfiles/formFiller2/index.js +61 -0
  44. package/src/__tests__/resource/ai/tools/print.ts +23 -0
  45. package/src/__tests__/tools.test.ts +133 -0
  46. package/src/ai-manager.ts +21 -0
  47. package/src/document-manager/document.ts +9 -0
  48. package/src/document-manager/id-mapper.ts +56 -0
  49. package/src/document-manager/index.ts +38 -0
  50. package/src/document-manager/search-index.ts +48 -0
  51. package/src/index.ts +13 -0
  52. package/src/loader/index.ts +12 -0
  53. package/src/loader/scanner.ts +54 -0
  54. package/src/loader/tools.ts +135 -0
  55. package/src/loader/types.ts +25 -0
  56. package/src/loader/utils.ts +14 -0
  57. package/src/tools-manager/index.ts +97 -0
  58. package/src/tools-manager/types.ts +50 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { importModule } from '@nocobase/utils';
11
+ import { ToolsOptions } from '../tools-manager';
12
+ import { DirectoryScanner, DirectoryScannerOptions, FileDescriptor } from './scanner';
13
+ import { readFile } from 'fs/promises';
14
+ import _ from 'lodash';
15
+ import { existsSync } from 'fs';
16
+ import { AIManager } from '../ai-manager';
17
+ import { LoadAndRegister } from './types';
18
+ import { Logger } from '@nocobase/logger';
19
+ import { isNonEmptyObject } from './utils';
20
+
21
+ export type ToolsLoaderOptions = { scan: DirectoryScannerOptions; log?: Logger };
22
+ export class ToolsLoader extends LoadAndRegister<ToolsLoaderOptions> {
23
+ protected readonly scanner: DirectoryScanner;
24
+
25
+ protected files: FileDescriptor[] = [];
26
+ protected toolsDescriptors: ToolsDescriptor[] = [];
27
+ protected log: Logger;
28
+
29
+ constructor(
30
+ protected readonly ai: AIManager,
31
+ protected readonly options: ToolsLoaderOptions,
32
+ ) {
33
+ super(ai, options);
34
+ this.log = options.log;
35
+ this.scanner = new DirectoryScanner(this.options.scan);
36
+ }
37
+
38
+ protected async scan(): Promise<void> {
39
+ this.files = await this.scanner.scan();
40
+ }
41
+
42
+ protected async import(): Promise<void> {
43
+ if (!this.files.length) {
44
+ return;
45
+ }
46
+ const grouped = new Map<string, FileDescriptor[]>();
47
+ for (const fd of this.files) {
48
+ const key =
49
+ fd.basename === 'index.ts' || fd.basename === 'index.js' || fd.basename === 'description.md'
50
+ ? fd.directory
51
+ : fd.name;
52
+ if (!grouped.has(key)) {
53
+ grouped.set(key, []);
54
+ }
55
+ grouped.get(key).push(fd);
56
+ }
57
+
58
+ this.toolsDescriptors = (
59
+ await Promise.all(
60
+ Array.from(grouped.entries()).map(async ([name, fds]) => {
61
+ const tsFile = fds.find((fd) => fd.extname === '.ts' || fd.extname === '.js');
62
+ const descFile = fds.find((fd) => fd.basename === 'description.md');
63
+ const entry: ToolsDescriptor = { name, tsFile, descFile };
64
+ if (!tsFile || !existsSync(tsFile.path)) {
65
+ this.log?.error(`tools [${name}] ignored: can not find .ts file`);
66
+ return null;
67
+ }
68
+ try {
69
+ const module = await importModule(tsFile.path);
70
+ if (isNonEmptyObject(module)) {
71
+ entry.toolsOptions = typeof module === 'function' ? module() : module;
72
+ } else {
73
+ entry.toolsOptions = undefined;
74
+ }
75
+ } catch (e) {
76
+ this.log?.error(`tools [${name}] load fail: error occur when import ${tsFile.path}`, e);
77
+ return null;
78
+ }
79
+ if (descFile && existsSync(descFile.path)) {
80
+ try {
81
+ entry.description = await readFile(descFile.path, 'utf-8');
82
+ } catch (e) {
83
+ this.log?.error(
84
+ `tools [${name}] load fail: error occur when reading description.md at ${descFile.path}`,
85
+ e,
86
+ );
87
+ return null;
88
+ }
89
+ }
90
+ return entry;
91
+ }),
92
+ )
93
+ ).filter((t) => t != null);
94
+ }
95
+
96
+ protected async register(): Promise<void> {
97
+ if (!this.toolsDescriptors.length) {
98
+ return;
99
+ }
100
+ const { toolsManager } = this.ai;
101
+ for (const descriptor of this.toolsDescriptors) {
102
+ if (!descriptor.toolsOptions) {
103
+ this.log?.warn(
104
+ `tools [${descriptor.name}] register ignored: ToolsOptions not export as default at ${descriptor.tsFile.path}`,
105
+ );
106
+ continue;
107
+ }
108
+ const { name, toolsOptions, description } = descriptor;
109
+ if (await toolsManager.getTools(name)) {
110
+ this.log?.warn(`tools [${descriptor.name}] register ignored: duplicate register for tools`);
111
+ continue;
112
+ }
113
+ if (toolsOptions.definition) {
114
+ toolsOptions.definition.name = name;
115
+ if (!_.isEmpty(description)) {
116
+ toolsOptions.definition.description = description;
117
+ }
118
+ }
119
+ try {
120
+ toolsManager.registerTools(toolsOptions);
121
+ } catch (e) {
122
+ this.log?.error(`tools [${descriptor.name}] register ignored: error occur when invoke registerTools`, e);
123
+ continue;
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ export type ToolsDescriptor = {
130
+ name: string;
131
+ tsFile?: FileDescriptor;
132
+ descFile?: FileDescriptor;
133
+ toolsOptions?: ToolsOptions;
134
+ description?: string;
135
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { AIManager } from '../ai-manager';
11
+
12
+ export abstract class LoadAndRegister<TOptions> {
13
+ constructor(
14
+ protected readonly ai: AIManager,
15
+ protected readonly options: TOptions,
16
+ ) {}
17
+ protected abstract scan(): Promise<void>;
18
+ protected abstract import(): Promise<void>;
19
+ protected abstract register(): Promise<void>;
20
+ async load(): Promise<void> {
21
+ await this.scan();
22
+ await this.import();
23
+ await this.register();
24
+ }
25
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import _ from 'lodash';
11
+
12
+ export function isNonEmptyObject(val: any) {
13
+ return _.isPlainObject(val) && !_.isEmpty(val);
14
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { Registry } from '@nocobase/utils';
11
+ import { DynamicToolsProvider, ToolsEntry, ToolsFilter, ToolsManager, ToolsOptions } from './types';
12
+ import _ from 'lodash';
13
+
14
+ export class DefaultToolsManager implements ToolsManager {
15
+ constructor(
16
+ private readonly tools = new Registry<ToolsEntry>(),
17
+ private readonly dynamicTools: DynamicToolsProvider[] = [],
18
+ ) {}
19
+
20
+ async getTools(toolName: string): Promise<ToolsEntry> {
21
+ const target = this.tools.get(toolName);
22
+ if (target) {
23
+ return target;
24
+ }
25
+ const dynamicTools = await this.syncDynamicTools();
26
+ return dynamicTools.find((x) => x.definition.name === toolName);
27
+ }
28
+
29
+ async listTools(filter?: ToolsFilter): Promise<ToolsEntry[]> {
30
+ const toolsList = await this.getToolsList();
31
+ return toolsList.filter((x) => {
32
+ if (!filter) {
33
+ return true;
34
+ }
35
+
36
+ let result = true;
37
+ if (filter.scope) {
38
+ result &&= filter.scope === x.scope;
39
+ }
40
+
41
+ if (filter.defaultPermission) {
42
+ result &&= filter.defaultPermission === x.defaultPermission;
43
+ }
44
+
45
+ if (filter.silence != null && filter.silence !== undefined) {
46
+ result &&= filter.silence === x.silence;
47
+ }
48
+
49
+ return result;
50
+ });
51
+ }
52
+
53
+ registerTools(options: ToolsOptions | ToolsOptions[]): void {
54
+ const list = _.isArray(options) ? options : [options];
55
+ for (const item of list) {
56
+ const toolsEntry = { ...item } as ToolsEntry;
57
+ if (!toolsEntry.execution) {
58
+ toolsEntry.execution = 'backend';
59
+ }
60
+ if (!toolsEntry.defaultPermission) {
61
+ toolsEntry.defaultPermission = 'ASK';
62
+ }
63
+ toolsEntry.silence = toolsEntry.silence === true;
64
+ if (!toolsEntry.introduction) {
65
+ toolsEntry.introduction = {
66
+ title: toolsEntry.definition.name,
67
+ };
68
+ }
69
+ this.tools.register(item.definition.name, toolsEntry);
70
+ }
71
+ }
72
+
73
+ registerDynamicTools(provider: DynamicToolsProvider): void {
74
+ this.dynamicTools.push(provider);
75
+ }
76
+
77
+ private async getToolsList(): Promise<ToolsEntry[]> {
78
+ const dynamicTools = await this.syncDynamicTools();
79
+ return [...this.tools.getValues(), ...dynamicTools];
80
+ }
81
+
82
+ private async syncDynamicTools(): Promise<ToolsEntry[]> {
83
+ if (this.dynamicTools.length === 0) {
84
+ return [];
85
+ }
86
+ const registry = new Registry<ToolsEntry>();
87
+ const ephemeral = new DefaultToolsManager(registry);
88
+ await Promise.all(this.dynamicTools.map((register) => register(ephemeral)));
89
+ return [...registry.getValues()];
90
+ }
91
+ }
92
+
93
+ export function defineTools(options: ToolsOptions) {
94
+ return options;
95
+ }
96
+
97
+ export * from './types';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import type { Context } from '@nocobase/actions';
11
+
12
+ export interface ToolsManager extends ToolsRegistration {
13
+ getTools(toolName: string): Promise<ToolsEntry>;
14
+ listTools(filter?: ToolsFilter): Promise<ToolsEntry[]>;
15
+ }
16
+
17
+ export interface ToolsRegistration {
18
+ registerTools(options: ToolsOptions | ToolsOptions[]): void;
19
+ registerDynamicTools(provider: (register: ToolsRegistration) => Promise<void>): void;
20
+ }
21
+
22
+ export type ToolsOptions = {
23
+ scope: Scope;
24
+ execution?: 'frontend' | 'backend';
25
+ defaultPermission?: Permission;
26
+ silence?: boolean;
27
+ introduction?: {
28
+ title: string;
29
+ about?: string;
30
+ };
31
+ definition: {
32
+ name: string;
33
+ description: string;
34
+ schema?: any;
35
+ };
36
+ invoke: (ctx: Context, args: any, id: string) => Promise<any>;
37
+ };
38
+
39
+ export type ToolsEntry = ToolsOptions;
40
+
41
+ export type Scope = 'SPECIFIED' | 'GENERAL' | 'CUSTOM';
42
+ export type Permission = 'ASK' | 'ALLOW';
43
+
44
+ export type DynamicToolsProvider = (register: ToolsRegistration) => Promise<void>;
45
+
46
+ export type ToolsFilter = {
47
+ scope?: Scope;
48
+ defaultPermission?: Permission;
49
+ silence?: boolean;
50
+ };