Skip to Content

🛠️ Creating Custom Tools

Custom tools are reusable components that encapsulate specific functionality in your Loopstack workflows. They can accept arguments, use dependency injection, and integrate seamlessly with the workflow execution.

Basic Tool Structure

A custom tool extends the Tool base class and implements the execute() method:

import { BlockConfig, HandlerCallResult } from '@loopstack/shared'; import { z } from 'zod'; import { Tool } from '@loopstack/core'; const propertiesSchema = z.object({ a: z.number(), b: z.number() }); @BlockConfig({ config: { description: 'Add two numbers together', }, properties: propertiesSchema, }) export class MathSumTool extends Tool { async execute(): Promise<HandlerCallResult> { const sum = this.args.a + this.args.b; return { data: sum }; } }

Key Components

1. BlockConfig Decorator

The @BlockConfig decorator defines the tool’s metadata and validation:

  • config: Basic configuration including description
  • properties: Zod schema for validating runtime arguments
  • configSchema: Zod schema for validating workflow configuration (optional)

2. Properties Schema

Define the expected arguments using Zod:

const propertiesSchema = z.object({ a: z.number(), b: z.number() });

3. Config Schema (Optional)

Allow template expressions in workflow configuration:

import { TemplateExpression } from '@loopstack/shared'; const NumberOrTemplateExpression = z.union([ TemplateExpression, // Allow template expressions like ${ args.a } z.number(), // Allow literal values ]); const configSchema = z.object({ a: NumberOrTemplateExpression, b: NumberOrTemplateExpression, });

4. Execute Method

The execute() method contains your tool’s logic. Access arguments via this.args:

async execute(): Promise<HandlerCallResult> { const result = this.args.a + this.args.b; return { data: result }; }

Dependency Injection

Tools support constructor-based dependency injection. Loopstack Tools are NestJs Services with transient scope by default:

import { MathService } from '../services/math.service'; @BlockConfig({ // ... configuration }) export class MathSumTool extends Tool { constructor(private mathService: MathService) { super(); // Required: call parent constructor } async execute(): Promise<HandlerCallResult> { const sum = this.mathService.sum(this.args.a, this.args.b); return { data: sum }; } }

Using Tools in Workflows

1. Import the Tool

Add your tool to the workflow’s imports:

@BlockConfig({ imports: [ MathSumTool, CreateChatMessage, ], // ... }) export class CustomToolExampleWorkflow extends Workflow { // ... }

2. Register as Provider

Add the tool to your module’s providers:

@Module({ providers: [ CustomToolExampleWorkflow, MathSumTool, MathService, // Don't forget service dependencies // ... ], }) export class ExampleModule {}

3. Call in Workflow YAML

Use the tool in your workflow transitions:

transitions: - id: calculate from: start to: end call: - id: calculation tool: MathSumTool args: a: ${ args.a } b: ${ args.b } # Access the result - tool: CreateChatMessage args: role: 'assistant' content: | Result: {{ state.currentTransitionResults.calculation.data }}
Last updated on