Store Chat Messages & State Without Managing Infrastructure.Check Out DialogueDB
Skip to content

Callable Executor Wrapper

When building LLM agents that need to call tools or functions, createCallableExecutor wraps your executors (or plain functions) into a standardized interface. Use useExecutors to group them into a collection you can query and invoke by name.

Creating a callable executor

Use createCallableExecutor to wrap a handler function or an existing executor:

ts
import { createCallableExecutor, createLlmExecutor, useLlm, createChatPrompt, createParser } from "llm-exe";

// Wrap a plain function
const searchCallable = createCallableExecutor({
  name: "searchInternet",
  description: "Search the internet for information",
  input: JSON.stringify({
    type: "object",
    properties: {
      query: { type: "string", description: "The search query" },
    },
  }),
  handler: async (input: { query: string }) => {
    // your search logic here
    return { results: [`Result for: ${input.query}`] };
  },
});

// Or wrap an existing LlmExecutor
const summarizeExecutor = createLlmExecutor({
  llm: useLlm("openai.chat.v1", { model: "gpt-4o-mini" }),
  prompt: createChatPrompt("Summarize: {{input}}"),
  parser: createParser("string"),
});

const summarizeCallable = createCallableExecutor({
  name: "summarize",
  description: "Summarize the given text",
  input: JSON.stringify({
    type: "object",
    properties: {
      input: { type: "string", description: "Text to summarize" },
    },
  }),
  handler: summarizeExecutor,
});

Grouping with useExecutors

useExecutors groups callables into a collection with lookup and invocation methods:

ts
import { useExecutors } from "llm-exe";

const executors = useExecutors([
  searchCallable,
  summarizeCallable,
]);

// Check if a function exists
executors.hasFunction("searchInternet"); // true

// Get a function by name
const fn = executors.getFunction("searchInternet");

// Call a function by name — input is parsed from a JSON string
const result = await executors.callFunction(
  "searchInternet",
  JSON.stringify({ query: "llm-exe docs" })
);
// result: { result: any, attributes: any }

// Get all functions (useful for building tool lists)
const allFunctions = executors.getFunctions();

Optional features

Visibility handler — conditionally show/hide functions based on context:

ts
const adminCallable = createCallableExecutor({
  name: "deleteUser",
  description: "Delete a user account",
  input: "...",
  handler: async (input) => { /* ... */ },
  visibilityHandler: (input, context, attributes) => {
    return attributes?.role === "admin";
  },
});

// Only returns functions visible for the given context
const visible = executors.getVisibleFunctions(input, { role: "admin" });

Input validation — validate input before execution:

ts
const callable = createCallableExecutor({
  name: "sendEmail",
  description: "Send an email",
  input: "...",
  handler: async (input) => { /* ... */ },
  validateInput: async (input) => {
    if (!input.to) {
      return { result: false, attributes: { error: "Missing 'to' field" } };
    }
    return { result: true, attributes: {} };
  },
});

const validation = await executors.validateFunctionInput(
  "sendEmail",
  JSON.stringify({ subject: "Hi" })
);
// validation: { result: false, attributes: { error: "Missing 'to' field" } }

Using with an LLM agent loop

A typical agent pattern: the LLM decides which function to call, and you execute it:

ts
const executors = useExecutors([
  finalAnswerCallable,
  interactWithUserCallable,
  searchInternetCallable,
  takeNotesCallable,
]);

const functionWithCallableExecutors = async (input: {
  action: string;
  input: string;
  thought: string;
}) => {
  return executors.callFunction(input.action, input.input);
};