@loopstack/ai-module 0.16.3 → 0.18.0-rc.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 (52) hide show
  1. package/README.md +33 -31
  2. package/dist/ai.module.js +8 -14
  3. package/dist/ai.module.js.map +1 -1
  4. package/dist/documents/ai-message-document.js +6 -10
  5. package/dist/documents/ai-message-document.js.map +1 -1
  6. package/dist/providers/anthropic.provider.d.ts +2 -1
  7. package/dist/providers/anthropic.provider.js.map +1 -1
  8. package/dist/providers/openai.provider.d.ts +2 -1
  9. package/dist/providers/openai.provider.js.map +1 -1
  10. package/dist/schemas/ai-generate-tool-base.schema.d.ts +9 -46
  11. package/dist/services/ai-messages-helper.service.d.ts +1 -1
  12. package/dist/services/ai-messages-helper.service.js.map +1 -1
  13. package/dist/services/ai-provider-model-helper.service.d.ts +2 -1
  14. package/dist/services/ai-provider-model-helper.service.js.map +1 -1
  15. package/dist/services/ai-provider-registry.service.d.ts +2 -1
  16. package/dist/services/ai-provider-registry.service.js +1 -1
  17. package/dist/services/ai-provider-registry.service.js.map +1 -1
  18. package/dist/services/ai-tools-helper.service.js.map +1 -1
  19. package/dist/tools/ai-generate-document.tool.d.ts +3 -3
  20. package/dist/tools/ai-generate-document.tool.js +1 -1
  21. package/dist/tools/ai-generate-document.tool.js.map +1 -1
  22. package/dist/tools/ai-generate-object.tool.d.ts +11 -63
  23. package/dist/tools/ai-generate-object.tool.js +3 -4
  24. package/dist/tools/ai-generate-object.tool.js.map +1 -1
  25. package/dist/tools/ai-generate-text.tool.d.ts +11 -51
  26. package/dist/tools/ai-generate-text.tool.js +10 -13
  27. package/dist/tools/ai-generate-text.tool.js.map +1 -1
  28. package/dist/tools/delegate-tool-call.tool.d.ts +4 -44
  29. package/dist/tools/delegate-tool-call.tool.js +1 -1
  30. package/dist/tools/delegate-tool-call.tool.js.map +1 -1
  31. package/package.json +35 -66
  32. package/src/ai.module.ts +38 -0
  33. package/src/documents/ai-message-document.ts +22 -0
  34. package/src/documents/ai-message-document.yaml +5 -0
  35. package/src/documents/index.ts +1 -0
  36. package/src/index.ts +3 -0
  37. package/src/providers/anthropic.provider.ts +18 -0
  38. package/src/providers/openai.provider.ts +18 -0
  39. package/src/schemas/ai-generate-tool-base.schema.ts +22 -0
  40. package/src/services/ai-messages-helper.service.ts +25 -0
  41. package/src/services/ai-provider-model-helper.service.ts +61 -0
  42. package/src/services/ai-provider-registry.service.ts +73 -0
  43. package/src/services/ai-tools-helper.service.ts +28 -0
  44. package/src/services/index.ts +4 -0
  45. package/src/tools/ai-generate-document.tool.ts +41 -0
  46. package/src/tools/ai-generate-object.tool.ts +91 -0
  47. package/src/tools/ai-generate-text.tool.ts +110 -0
  48. package/src/tools/delegate-tool-call.tool.ts +72 -0
  49. package/src/tools/index.ts +4 -0
  50. package/.prettierrc +0 -4
  51. package/LICENSE +0 -19
  52. package/tsconfig.build.json +0 -4
@@ -0,0 +1,73 @@
1
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
+ import { DiscoveryService, Reflector } from '@nestjs/core';
3
+ import { LanguageModel } from 'ai';
4
+ import {
5
+ AI_PROVIDER_DECORATOR,
6
+ AiProviderDecoratorOptions,
7
+ AiProviderInterface,
8
+ AiProviderOptions,
9
+ } from '@loopstack/common';
10
+
11
+ @Injectable()
12
+ export class AiProviderRegistryService implements OnModuleInit {
13
+ private readonly logger = new Logger(AiProviderRegistryService.name);
14
+ private readonly providers = new Map<string, AiProviderInterface>();
15
+
16
+ constructor(
17
+ private readonly discoveryService: DiscoveryService,
18
+ private readonly reflector: Reflector,
19
+ ) {}
20
+
21
+ onModuleInit() {
22
+ this.loadProviders();
23
+ }
24
+
25
+ private loadProviders() {
26
+ const providers = this.discoveryService.getProviders();
27
+
28
+ providers
29
+ .filter((wrapper) => wrapper.isDependencyTreeStatic())
30
+ .filter((wrapper) => wrapper.instance)
31
+ .forEach((wrapper) => {
32
+ const instance = wrapper.instance as AiProviderInterface;
33
+ const metadata = this.reflector.get<AiProviderDecoratorOptions>(
34
+ AI_PROVIDER_DECORATOR,
35
+ (instance as object).constructor,
36
+ );
37
+
38
+ if (metadata) {
39
+ this.registerProvider(metadata.name, instance);
40
+ this.logger.log(`Registered AI provider: ${metadata.name}`);
41
+ }
42
+ });
43
+ }
44
+
45
+ private registerProvider(name: string, provider: AiProviderInterface) {
46
+ if (this.providers.has(name)) {
47
+ this.logger.warn(`Provider ${name} already exists, overriding...`);
48
+ }
49
+ this.providers.set(name.toLowerCase(), provider);
50
+ }
51
+
52
+ createModel(providerName: string, options: AiProviderOptions): LanguageModel {
53
+ const provider = this.providers.get(providerName.toLowerCase());
54
+
55
+ if (!provider) {
56
+ throw new Error(
57
+ `AI provider '${providerName}' not found. Available providers: ${this.getAvailableProviders().join(', ')}`,
58
+ );
59
+ }
60
+
61
+ try {
62
+ const client: unknown = provider.createClient(options);
63
+ return provider.getModel(client, options.model) as LanguageModel;
64
+ } catch (error) {
65
+ this.logger.error(`Failed to create model for provider ${providerName}:`, error);
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ getAvailableProviders(): string[] {
71
+ return Array.from(this.providers.keys());
72
+ }
73
+ }
@@ -0,0 +1,28 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { WorkflowBase } from '@loopstack/core';
3
+
4
+ @Injectable()
5
+ export class AiToolsHelperService {
6
+ getTools(tools: string[], parent: WorkflowBase): Record<string, any> | undefined {
7
+ // using any instead of ToolSet bc ai sdk types have some nesting issue
8
+ const toolDefinitions: Record<string, any> = {};
9
+
10
+ for (const toolName of tools) {
11
+ const tool = parent.getTool(toolName);
12
+ if (!tool) {
13
+ throw new Error(`Tool with name ${toolName} not available in Workflow context.`);
14
+ }
15
+
16
+ const inputSchema = tool.argsSchema;
17
+
18
+ if (inputSchema) {
19
+ toolDefinitions[toolName] = {
20
+ description: tool.config.description,
21
+ inputSchema,
22
+ } as unknown; // using unknown bc ai sdk types have some nesting issue
23
+ }
24
+ }
25
+
26
+ return Object.keys(toolDefinitions).length ? toolDefinitions : undefined;
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ai-messages-helper.service';
2
+ export * from './ai-provider-model-helper.service';
3
+ export * from './ai-messages-helper.service';
4
+ export * from './ai-tools-helper.service';
@@ -0,0 +1,41 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { BlockConfig, Tool, ToolResult, WithArguments } from '@loopstack/common';
3
+ import { ToolBase, WorkflowBase } from '@loopstack/core';
4
+ import { CreateDocument } from '@loopstack/core-ui-module';
5
+ import { WorkflowExecution } from '@loopstack/core/dist/workflow-processor/interfaces/workflow-execution.interface';
6
+ import { AiGenerateObject, AiGenerateObjectArgsType, AiGenerateObjectSchema } from './ai-generate-object.tool';
7
+
8
+ @Injectable()
9
+ @BlockConfig({
10
+ config: {
11
+ description: 'Generates a structured object using a LLM and creates it as document',
12
+ },
13
+ })
14
+ @WithArguments(AiGenerateObjectSchema)
15
+ export class AiGenerateDocument extends ToolBase<AiGenerateObjectArgsType> {
16
+ @Tool() private aiGenerateObject!: AiGenerateObject;
17
+ @Tool() private createDocument!: CreateDocument;
18
+
19
+ private getRequiredTool(name: string): ToolBase<any> {
20
+ const tool = this.getTool(name);
21
+ if (tool === undefined) {
22
+ throw new Error(`Tool "${name}" is not available`);
23
+ }
24
+ return tool;
25
+ }
26
+
27
+ async execute(args: AiGenerateObjectArgsType, ctx: WorkflowExecution, parent: WorkflowBase): Promise<ToolResult> {
28
+ const result = await this.getRequiredTool('aiGenerateObject').execute(args, ctx, parent);
29
+ return this.getRequiredTool('createDocument').execute(
30
+ {
31
+ id: args.response.id,
32
+ document: args.response.document,
33
+ update: {
34
+ content: result.data as unknown,
35
+ },
36
+ },
37
+ ctx,
38
+ parent,
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,91 @@
1
+ import { ModelMessage } from '@ai-sdk/provider-utils';
2
+ import { GenerateObjectResult, LanguageModel, generateObject } from 'ai';
3
+ import { z } from 'zod';
4
+ import { BlockConfig, ToolResult, WithArguments } from '@loopstack/common';
5
+ import { ToolBase, WorkflowBase } from '@loopstack/core';
6
+ import { Block } from '@loopstack/core/dist/workflow-processor/abstract/block.abstract';
7
+ import { WorkflowExecution } from '@loopstack/core/dist/workflow-processor/interfaces/workflow-execution.interface';
8
+ import { AiGenerateToolBaseSchema } from '../schemas/ai-generate-tool-base.schema';
9
+ import { AiMessagesHelperService } from '../services';
10
+ import { AiProviderModelHelperService } from '../services';
11
+
12
+ export const AiGenerateObjectSchema = AiGenerateToolBaseSchema.extend({
13
+ response: z.object({
14
+ id: z.string().optional(),
15
+ document: z.string(),
16
+ }),
17
+ }).strict();
18
+
19
+ export type AiGenerateObjectArgsType = z.infer<typeof AiGenerateObjectSchema>;
20
+
21
+ @BlockConfig({
22
+ config: {
23
+ description: 'Generates a structured object using a LLM',
24
+ },
25
+ })
26
+ @WithArguments(AiGenerateObjectSchema)
27
+ export class AiGenerateObject extends ToolBase<AiGenerateObjectArgsType> {
28
+ constructor(
29
+ private readonly aiMessagesHelperService: AiMessagesHelperService,
30
+ private readonly aiProviderModelHelperService: AiProviderModelHelperService,
31
+ ) {
32
+ super();
33
+ }
34
+
35
+ async execute(args: AiGenerateObjectArgsType, ctx: WorkflowExecution, parent: WorkflowBase): Promise<ToolResult> {
36
+ const model = this.aiProviderModelHelperService.getProviderModel(args.llm);
37
+
38
+ const options: {
39
+ prompt?: string;
40
+ messages?: ModelMessage[];
41
+ schema?: z.ZodSchema;
42
+ } = {};
43
+
44
+ if (args.prompt) {
45
+ options.prompt = args.prompt;
46
+ } else {
47
+ options.messages = this.aiMessagesHelperService.getMessages(ctx.state.getMetadata('documents'), {
48
+ messages: args.messages as ModelMessage[],
49
+ messagesSearchTag: args.messagesSearchTag,
50
+ });
51
+ }
52
+
53
+ const document: Block | undefined = parent.getDocument(args.response.document);
54
+ if (!document) {
55
+ throw new Error(`Document with name "${args.response.document}" not found in tool execution context.`);
56
+ }
57
+ const responseSchema = document.argsSchema;
58
+ if (!responseSchema) {
59
+ throw new Error(`AI object generation source document must have a schema.`);
60
+ }
61
+
62
+ options.schema = responseSchema;
63
+
64
+ const response = await this.handleGenerateObject(model, options);
65
+
66
+ return {
67
+ data: response.object,
68
+ };
69
+ }
70
+
71
+ private async handleGenerateObject(
72
+ model: LanguageModel,
73
+ options: {
74
+ prompt?: string;
75
+ messages?: ModelMessage[];
76
+ schema?: z.ZodSchema;
77
+ },
78
+ ): Promise<GenerateObjectResult<unknown>> {
79
+ const startTime = performance.now();
80
+ try {
81
+ return generateObject({
82
+ model,
83
+ ...options,
84
+ } as Parameters<typeof generateObject>[0]);
85
+ } catch (error) {
86
+ const errorResponseTime = performance.now() - startTime;
87
+ console.error(`Request failed after ${errorResponseTime}ms:`, error);
88
+ throw error;
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,110 @@
1
+ import { ModelMessage } from '@ai-sdk/provider-utils';
2
+ import { Injectable } from '@nestjs/common';
3
+ import { LanguageModel, UIMessage, createUIMessageStream, streamText } from 'ai';
4
+ import { z } from 'zod';
5
+ import { BlockConfig, ToolResult, WithArguments } from '@loopstack/common';
6
+ import { ToolBase, WorkflowBase } from '@loopstack/core';
7
+ import { WorkflowExecution } from '@loopstack/core/dist/workflow-processor/interfaces/workflow-execution.interface';
8
+ import { AiGenerateToolBaseSchema } from '../schemas/ai-generate-tool-base.schema';
9
+ import { AiMessagesHelperService } from '../services';
10
+ import { AiProviderModelHelperService } from '../services';
11
+ import { AiToolsHelperService } from '../services';
12
+
13
+ export const AiGenerateTextSchema = AiGenerateToolBaseSchema.extend({
14
+ tools: z.array(z.string()).optional(),
15
+ }).strict();
16
+
17
+ type AiGenerateTextArgsType = z.infer<typeof AiGenerateTextSchema>;
18
+
19
+ @Injectable()
20
+ @BlockConfig({
21
+ config: {
22
+ description: 'Generates text using a LLM',
23
+ },
24
+ })
25
+ @WithArguments(AiGenerateTextSchema)
26
+ export class AiGenerateText extends ToolBase<AiGenerateTextArgsType> {
27
+ constructor(
28
+ private readonly aiMessagesHelperService: AiMessagesHelperService,
29
+ private readonly aiToolsHelperService: AiToolsHelperService,
30
+ private readonly aiProviderModelHelperService: AiProviderModelHelperService,
31
+ ) {
32
+ super();
33
+ }
34
+
35
+ async execute(args: AiGenerateTextArgsType, ctx: WorkflowExecution, parent: WorkflowBase): Promise<ToolResult> {
36
+ const model = this.aiProviderModelHelperService.getProviderModel(args.llm);
37
+
38
+ const options: {
39
+ prompt?: string;
40
+ messages?: ModelMessage[];
41
+ tools?: Record<string, unknown>;
42
+ } = {};
43
+
44
+ options.tools = args.tools ? this.aiToolsHelperService.getTools(args.tools, parent) : undefined;
45
+
46
+ if (args.prompt) {
47
+ options.prompt = args.prompt;
48
+ } else {
49
+ options.messages = this.aiMessagesHelperService.getMessages(ctx.state.getMetadata('documents'), {
50
+ messages: args.messages as ModelMessage[],
51
+ messagesSearchTag: args.messagesSearchTag,
52
+ });
53
+ }
54
+
55
+ const uiMessage = await this.handleGenerateText(model, options);
56
+
57
+ return {
58
+ data: uiMessage,
59
+ };
60
+ }
61
+
62
+ private async handleGenerateText(
63
+ model: LanguageModel,
64
+ options: {
65
+ prompt?: string;
66
+ messages?: ModelMessage[];
67
+ tools?: Record<string, unknown>;
68
+ },
69
+ ): Promise<UIMessage> {
70
+ const startTime = performance.now();
71
+ try {
72
+ const result = streamText({
73
+ model,
74
+ ...options,
75
+ } as Parameters<typeof streamText>[0]);
76
+
77
+ return new Promise((resolve, reject) => {
78
+ const stream = createUIMessageStream({
79
+ execute({ writer }) {
80
+ writer.merge(
81
+ result.toUIMessageStream({
82
+ sendReasoning: true,
83
+ }),
84
+ );
85
+ },
86
+ onFinish: (data) => {
87
+ resolve(data.responseMessage);
88
+ },
89
+ });
90
+
91
+ // Consume the stream to trigger execution
92
+ void (async () => {
93
+ try {
94
+ const reader = stream.getReader();
95
+ while (true) {
96
+ const { done } = await reader.read();
97
+ if (done) break;
98
+ }
99
+ } catch (error) {
100
+ reject(error instanceof Error ? error : new Error(String(error)));
101
+ }
102
+ })();
103
+ });
104
+ } catch (error) {
105
+ const errorResponseTime = performance.now() - startTime;
106
+ console.error(`Request failed after ${errorResponseTime}ms:`, error);
107
+ throw error;
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,72 @@
1
+ import { ToolUIPart, UIMessage } from 'ai';
2
+ import { z } from 'zod';
3
+ import { BlockConfig, ToolResult, WithArguments } from '@loopstack/common';
4
+ import { ToolBase, WorkflowBase } from '@loopstack/core';
5
+ import { WorkflowExecution } from '@loopstack/core/dist/workflow-processor/interfaces/workflow-execution.interface';
6
+
7
+ const DelegateToolCallsToolSchema = z.object({
8
+ message: z.object({
9
+ id: z.string(),
10
+ parts: z.array(
11
+ z.object({
12
+ type: z.string(),
13
+ input: z.any().optional(),
14
+ toolCallId: z.string().optional(),
15
+ }),
16
+ ),
17
+ }),
18
+ });
19
+
20
+ type DelegateToolCallsToolArgs = z.infer<typeof DelegateToolCallsToolSchema>;
21
+
22
+ @BlockConfig({
23
+ config: {
24
+ description: 'Delegate a tool call.',
25
+ },
26
+ })
27
+ @WithArguments(DelegateToolCallsToolSchema)
28
+ export class DelegateToolCall extends ToolBase<DelegateToolCallsToolArgs> {
29
+ async execute(args: DelegateToolCallsToolArgs, ctx: WorkflowExecution, parent: WorkflowBase): Promise<ToolResult> {
30
+ const parts = args.message.parts;
31
+ const resultParts: ToolUIPart[] = [];
32
+
33
+ for (const part of parts) {
34
+ if (!part.type.startsWith('tool-')) {
35
+ continue;
36
+ }
37
+
38
+ if (!part.toolCallId) {
39
+ throw new Error(`No toolCallId provided`);
40
+ }
41
+
42
+ const toolName = part.type.replace(/^tool-/, '');
43
+
44
+ const tool = parent.getTool(toolName);
45
+ if (!tool) {
46
+ throw new Error(`Tool ${toolName} not found.`);
47
+ }
48
+ const result: ToolResult = await tool.execute(part.input as Record<string, unknown>, ctx, parent);
49
+
50
+ resultParts.push({
51
+ type: part.type as ToolUIPart['type'],
52
+ toolCallId: part.toolCallId,
53
+ output: {
54
+ type: result.type || 'text',
55
+ value: result.data as unknown,
56
+ },
57
+ input: part.input as Record<string, unknown>,
58
+ state: 'output-available',
59
+ } satisfies ToolUIPart);
60
+ }
61
+
62
+ const resultMessage: UIMessage = {
63
+ id: args.message.id,
64
+ role: 'assistant',
65
+ parts: resultParts,
66
+ };
67
+
68
+ return {
69
+ data: resultMessage,
70
+ };
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ai-generate-document.tool';
2
+ export * from './ai-generate-text.tool';
3
+ export * from './ai-generate-object.tool';
4
+ export * from './delegate-tool-call.tool';
package/.prettierrc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "singleQuote": true,
3
- "trailingComma": "all"
4
- }
package/LICENSE DELETED
@@ -1,19 +0,0 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- Copyright 2025 Loopstack GmbH
8
-
9
- Licensed under the Apache License, Version 2.0 (the "License");
10
- you may not use this skeleton application except in compliance with the License.
11
- You may obtain a copy of the License at
12
-
13
- http://www.apache.org/licenses/LICENSE-2.0
14
-
15
- Unless required by applicable law or agreed to in writing, software
16
- distributed under the License is distributed on an "AS IS" BASIS,
17
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
- See the License for the specific language governing permissions and
19
- limitations under the License.
@@ -1,4 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
- }