Tool Definition in JavaScript/TypeScript
Outline
In this module, we'll focus on the practical aspects of defining your custom tools using the @optimizely-opal/opal-tools-sdk
. We'll cover the syntax for applying the tool()
decorator, how to structure your tool functions to accept parameters, and how to define parameter schemas with various properties.
By the end of this module, you will be able to:
Define and register a custom Opal tool using the
@tool()
decorator.Structure tool functions to accept and validate various types of parameters.
Use TypeScript interfaces and parameter schemas for type safety and clarity.
Using the tool()
Decorator to Mark a Function as an Opal Tool
As introduced in the previous lesson, the @tool()
decorator is how you tell the SDK that a particular class contains an Opal tool. The execute
method within that class is what Opal will call when the tool is invoked.
Basic Structure:
import { tool } from '@optimizely-opal/opal-tools-sdk';
@tool({
name: 'my_tool_name', // Unique programmatic name for the tool
description: 'A brief description of what this tool does.',
parameters: [] // We'll define these next
})
export class MyToolClass {
// The 'execute' method is the entry point for Opal to run your tool
async execute(params: any): Promise {
// Your tool's logic goes here
console.log('Tool executed with parameters:', params);
return { status: 'success', data: 'Tool completed its task.' };
}
}
-
name
: This is a crucial identifier. It should be unique within your tool service and descriptive, often usingsnake_case
(e.g.,get_user_profile
,send_email
). This is what Opal's AI will use to refer to your tool. -
description
: This provides a human-readable explanation of the tool's purpose. It's used in the Opal UI to help users understand what the tool does. Make it clear and concise. -
parameters
: This is an array where you define the inputs your tool expects. We'll explore this in detail below. -
execute(params: any): Promise<any>
: This is the method that gets called when Opal invokes your tool.-
params
: An object containing the input parameters provided by Opal. The structure of this object will match theparameters
you define in the@tool
decorator. -
Promise<any>
: Yourexecute
method should beasync
and return aPromise
that resolves to a JSON-serializable object. This object will be the response Opal receives.
-
Structuring Your Tool Functions to Accept Parameters
When Opal invokes your tool, it sends the input data as a JSON object in the request body. The SDK automatically parses this and passes it to your execute
method as the params
argument. It's important to note that depending on how Opal's AI assistant constructs the invocation, parameters might be nested under keys like parameters
, arguments
, or directly in the root of the request body. Your tool should be designed to handle this flexibility.
Type Safety with TypeScript: One of the biggest advantages of using TypeScript is the ability to define the shape of your params
object, ensuring type safety and providing excellent autocompletion in your IDE.
// Define an interface for your tool's parameters
interface GreetPersonParams {
name: string;
language?: string; // Optional parameter
}
@tool({
name: 'greet_person',
description: 'Greets a person by name in a specified language.',
parameters: [
// ... parameter definitions (see next section)
]
})
export class GreeterTool {
async execute(params: GreetPersonParams) { // Use the interface here
const { name, language } = params; // Destructure for easier access
let greeting = `Hello, ${name}!`;
if (language === 'es') {
greeting = `¡Hola, ${name}!`;
} else if (language === 'fr') {
greeting = `Bonjour, ${name}!`;
}
return { greeting: greeting, language: language || 'en' };
}
}
Defining Parameter Schemas with name
, type
, description
, and required
Properties
The parameters
array in the @tool()
decorator is where you specify each input your tool expects. Each object in this array describes a single parameter.
-
name
(string): The programmatic name of the parameter. This must match the key in theparams
object that Opal sends. -
type
(ParameterType enum): The data type of the parameter. Use theParameterType
enum from the SDK. This is crucial for validation. -
description
(string): A clear, user-friendly description of what the parameter represents. This is displayed in the Opal UI and helps users understand what input is expected. -
required
(boolean): Set totrue
if the parameter must always be provided by the caller;false
otherwise. The SDK will enforce this validation. -
enum
(array of strings, optional): For parameters that should have a fixed set of values (like a dropdown selection), you can provide anenum
array. Opal's UI can then render this as a selection list.
Example: greet_person
Tool with Detailed Parameter Definitions
Let's refine our GreeterTool
example with full parameter definitions:
import { tool, ParameterType } from '@optimizely-opal/opal-tools-sdk';
// Define an interface for type safety
interface GreetPersonParams {
personName: string; // Renamed to avoid conflict with 'name' property of Parameter object
language?: 'en' | 'es' | 'fr'; // Using literal types for enum-like behavior
includeEmoji?: boolean;
}
@tool({
name: 'greet_person',
description: 'Generates a personalized greeting for a person in a specified language, with an optional emoji.',
parameters: [
{
name: 'personName', // This name matches the property in GreetPersonParams
type: ParameterType.String,
description: 'The name of the person to greet. This is a mandatory field.',
required: true
},
{
name: 'language',
type: ParameterType.String,
description: 'Optional language for the greeting. Choose from English (en), Spanish (es), or French (fr). Defaults to English.',
required: false,
enum: ['en', 'es', 'fr'] // Provides a list of valid options
},
{
name: 'includeEmoji',
type: ParameterType.Boolean,
description: 'Set to true to include a greeting emoji. Defaults to false.',
required: false
}
]
})
export class GreeterTool {
async execute(params: GreetPersonParams) {
const { personName, language = 'en', includeEmoji = false } = params; // Destructure with defaults
let greetingText: string;
switch (language) {
case 'es':
greetingText = `¡Hola, ${personName}!`;
break;
case 'fr':
greetingText = `Bonjour, ${personName}!`;
break;
case 'en':
default:
greetingText = `Hello, ${personName}!`;
break;
}
const emoji = includeEmoji ? ' 👋' : ''; // Add emoji if requested
return {
greeting: greetingText + emoji,
languageUsed: language,
personGreeted: personName
};
}
}
Key Takeaways:
- The
@tool()
decorator is your primary interface for defining tools. - The
execute
method is where your tool's logic resides, accepting parameters and returning a result. - TypeScript interfaces provide strong type checking for your parameters, improving code quality.
- Parameter definitions in the
@tool()
decorator are crucial for Opal to understand and present your tool correctly. Usename
,type
,description
,required
, andenum
effectively.
In the next module, we'll build on these concepts to create more simple, yet practical, Opal tools.