Custom Tools for TypeScript AI Agents

Create custom tools for TypeScript AI Agents — from plain functions to named registration and full FunctionTool control

Attach plain TypeScript functions as agent tools, register them by name like Python, or wrap them in a FunctionTool for full control.

Diagram

User

Agent

Custom Tool

Result

Quick Start

  1. Simple Usage
    typescript
    import { Agent } from 'praisonai';
    
    async function getWeather(location: string) {
      return `${Math.floor(Math.random() * 30)}°C in ${location}`;
    }
    
    const agent = new Agent({
      instructions: 'You provide weather for requested locations.',
      name: 'WeatherAgent',
      tools: [getWeather],
    });
    
    await agent.start('What is the weather in Paris?');
  2. Named Registration
    typescript
    import { Agent, register_tool, get_tool } from 'praisonai';
    
    async function getWeather(location: string) {
      return `${Math.floor(Math.random() * 30)}°C in ${location}`;
    }
    
    register_tool(getWeather, { name: 'weather' });
    
    const agent = new Agent({
      instructions: 'You provide weather for requested locations.',
      tools: [get_tool('weather')!],
    });
    
    await agent.start('What is the weather in Paris?');

Which One Do I Pick?

Three ways to build a tool, from least to most control.

Diagram

Just run my function

Share by name across agents

Approval, retries, validation

How much control?

Pass the function
tools: myFn

register_tool + get_tool

FunctionTool / functionToTool


Level 1 — Pass a Plain Function

The easiest way — hand the Agent your function directly. The model returns named arguments; the agent maps them onto your function’s parameters, so order doesn’t matter.

typescript
import { Agent } from 'praisonai';

// Named args from the model ({ city: "Paris" }) map to the `city` parameter.
const getWeather = (city: string) => `Weather in ${city}: 22°C, sunny`;

const agent = new Agent({
  instructions: 'Answer weather questions using getWeather.',
  tools: [getWeather],
});

await agent.start('What is the weather in Paris?');
// Calls getWeather("Paris") → "Weather in Paris: 22°C, sunny"
  • Named arguments map to your declared parameter names — order-independent.
  • Return values are JSON-serialized before going back to the model. Objects and null are safe — no manual JSON.stringify.

Level 2 — Register by Name

Register a tool once on the global registry, then look it up anywhere — the same register_tool / get_tool names Python uses.

typescript
import {
  Agent,
  register_tool,
  get_tool,
  has_tool,
  list_tools,
} from 'praisonai';

const getWeather = (city: string) => `Weather in ${city}: 22°C, sunny`;

register_tool(getWeather, { name: 'weather' });

has_tool('weather');   // true
list_tools();          // ['weather']

const agent = new Agent({
  instructions: 'Report the weather using the weather tool.',
  tools: [get_tool('weather')!],
});

await agent.start('Weather in Paris?');

register_tool accepts anything Python accepts — a plain function, a FunctionTool, or a BaseTool-like object with run() / execute().

FunctioncamelCaseWhat it does
register_tool(tool, options?)registerToolAdd to the global registry (skips duplicates)
get_tool(name)getToolLook up a registered tool by name
has_tool(name)hasToolPresence check
remove_tool(name)removeToolDelete; returns boolean
list_tools()listToolsArray of registered names
get_tool_definitions()getToolDefinitionsOpenAI-shape definitions for every tool
get_registry()getRegistryThe global ToolRegistry singleton

Level 3 — Full Control with FunctionTool

Wrap a function in a FunctionTool when you want to validate it, call it directly, or attach approval and retry behaviour.

typescript
import { Agent, FunctionTool } from 'praisonai';

const weather = new FunctionTool({
  name: 'weather',
  description: 'Get the current weather for a location',
  parameters: {
    type: 'object',
    properties: {
      location: { type: 'string', description: 'City name' },
    },
    required: ['location'],
  },
  execute: async ({ location }) => `22°C, sunny in ${location}`,
});

weather.validate();                         // throws if malformed, else true
await weather.run({ location: 'Paris' });   // "22°C, sunny in Paris"

const agent = new Agent({
  instructions: 'Report the weather.',
  tools: [weather],
});

await agent.start('Weather in Paris?');

Prefer tool({...}) if you don’t need a class instance — it returns the same FunctionTool:

typescript
import { tool } from 'praisonai';

const weather = tool({
  name: 'weather',
  description: 'Get the current weather for a location',
  execute: async ({ location }: { location: string }) => `22°C in ${location}`,
});

.run() and .validate()

MethodSignatureBehaviour
run(params, context?)`Promise

| Python-parity entry point; returns the full result | | validate() | boolean | Throws ToolValidationError listing every problem, else returns true | | execute(params, context?) | Promise

| Model-facing result (after toModelOutput`) |

Trust Levels

Mark where a tool came from when you register it. TOOL_TRUST_LEVELS is ['trusted', 'external'].

typescript
import { register_tool, TOOL_TRUST_LEVELS } from 'praisonai';

// In-repo code you wrote.
register_tool(getWeather, { name: 'weather', trustLevel: 'trusted' });

// User-supplied code from outside your codebase.
register_tool(userTool, { name: 'user-plugin', trustLevel: 'external' });

An invalid trustLevel throws immediately. Read it back with get_registry().getTrustLevel(name).

Dynamic Schema Overrides

Reshape a parameter’s schema at registration time — useful for injecting enums from runtime state.

typescript
import { register_tool } from 'praisonai';

register_tool(getWeather, {
  name: 'weather',
  dynamicSchemaOverrides: (schema) => ({
    ...schema,
    properties: {
      ...schema.properties,
      location: { type: 'string', enum: ['Paris', 'Tokyo', 'Berlin'] },
    },
  }),
});

Multi Agents

Share tools across multiple Agents by passing the same functions:

typescript
import { Agent, AgentTeam } from 'praisonai';

async function getWeather(location: string) {
  return `${Math.floor(Math.random() * 30)}°C`;
}

async function getTime(location: string) {
  const now = new Date();
  return `${now.getHours()}:${now.getMinutes()}`;
}

const agent = new Agent({
  instructions: 'You provide the current weather and time for locations.',
  name: 'DirectFunctionAgent',
  tools: [getWeather, getTime],
});

await agent.start("What's the weather and time in Paris and Tokyo?");

Best Practices

Give every tool a clear description

Without a description, the tool advertises as Tool: <name> — the model has less to go on. A one-line description makes tool selection reliable.

Validate before you register

Call .validate() on a FunctionTool before registration — it throws ToolValidationError listing every problem, so you catch a missing name or bad schema early.

Mark external tools 'external'

Set trustLevel: 'external' for user-supplied code and 'trusted' for in-repo tools, so you can audit where a registered tool came from.

Reuse the global registry for the common case

Use register_tool / get_tool for “I already have my tool”. Reach for the factory registry only when you build instances from user config.


Related