> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exulu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Complete guide to configuring ExuluTool for agent tool calling

## Constructor parameters

The ExuluTool constructor accepts a configuration object with the following parameters:

```typescript theme={null}
const tool = new ExuluTool({
  id: string,
  name: string,
  description: string,
  category?: string,
  inputSchema?: z.ZodType,
  type: "context" | "function" | "agent" | "web_search",
  config: ConfigParameter[],
  execute: ExecuteFunction
});
```

## Required parameters

### id

<ParamField path="id" type="string" required>
  Unique identifier for the tool. Must start with a letter (a-z) or underscore (\_). Subsequent characters can be letters, digits (0-9), or underscores. Must be 5-80 characters long.
</ParamField>

```typescript theme={null}
id: "get_user_profile"
```

<Warning>
  The ID is used for database references and should not change after creation. Changing the ID requires updating all references in your database.
</Warning>

<Info>
  The ID must follow PostgreSQL identifier rules since it's used for storing tool references.
</Info>

### name

<ParamField path="name" type="string" required>
  Human-readable name used for tool invocation. Agents use this name when calling the tool.
</ParamField>

```typescript theme={null}
name: "get_user_profile"
```

<Note>
  The name is sanitized during execution to ensure compatibility with the AI SDK. Use snake\_case or camelCase for consistency.
</Note>

### description

<ParamField path="description" type="string" required>
  Clear description of what the tool does. This is shown to agents to help them understand when to use the tool.
</ParamField>

```typescript theme={null}
description: "Retrieves a user's profile information including name, email, and preferences"
```

<Tip>
  Write descriptions from the agent's perspective. Explain what the tool does and when to use it. Good descriptions improve tool selection accuracy.
</Tip>

### type

<ParamField path="type" type="'context' | 'function' | 'agent' | 'web_search'" required>
  The type of tool determines its behavior and execution context
</ParamField>

```typescript theme={null}
type: "function"
```

<Tabs>
  <Tab title="function">
    Custom function tools that execute your business logic, API calls, or data processing.

    ```typescript theme={null}
    type: "function"
    ```

    Use for: Database queries, calculations, API integrations, file processing
  </Tab>

  <Tab title="context">
    Semantic search tools that query ExuluContext instances for RAG applications.

    ```typescript theme={null}
    type: "context"
    ```

    Use for: Knowledge base queries, document search, semantic retrieval

    <Note>
      Context tools are typically auto-generated by ExuluContext when `enableAsTool: true`.
    </Note>
  </Tab>

  <Tab title="agent">
    Agent delegation tools that invoke other agents for specialized tasks.

    ```typescript theme={null}
    type: "agent"
    ```

    Use for: Multi-agent workflows, task routing, specialized expertise
  </Tab>

  <Tab title="web_search">
    Web search tools that fetch real-time information from the internet.

    ```typescript theme={null}
    type: "web_search"
    ```

    Use for: Current events, real-time data, fact-checking
  </Tab>
</Tabs>

### config

<ParamField path="config" type="ConfigParameter[]" required>
  Array of configuration parameters that define runtime settings for the tool
</ParamField>

```typescript theme={null}
config: [
  {
    name: "api_endpoint",
    description: "Base URL for the API",
    type: "string",
    default: "https://api.example.com"
  },
  {
    name: "enable_cache",
    description: "Enable response caching",
    type: "boolean",
    default: true
  },
  {
    name: "timeout_ms",
    description: "Request timeout in milliseconds",
    type: "number",
    default: 5000
  },
  {
    name: "api_key",
    description: "API authentication key",
    type: "variable" // References ExuluVariables
  }
]
```

#### Config parameter properties

<ParamField path="config[].name" type="string" required>
  Parameter name used to reference this configuration value
</ParamField>

<ParamField path="config[].description" type="string" required>
  Description of what this parameter controls
</ParamField>

<ParamField path="config[].type" type="'boolean' | 'string' | 'number' | 'variable'" required>
  Data type of the parameter
</ParamField>

<ParamField path="config[].default" type="string | boolean | number">
  Default value if not specified (not applicable for type `"variable"`)
</ParamField>

### execute

<ParamField path="execute" type="ExecuteFunction" required>
  The function that implements the tool's logic. Can be async function or async generator.
</ParamField>

```typescript theme={null}
execute: async (inputs: any) => {
  // Your logic here
  return {
    result?: string,
    job?: string,
    items?: Item[]
  };
}
```

<Tabs>
  <Tab title="Async function">
    Standard async function for single result:

    ```typescript theme={null}
    execute: async ({ userId, includeHistory }) => {
      const user = await db.users.findOne({ id: userId });

      if (includeHistory) {
        user.history = await db.userHistory.find({ userId });
      }

      return {
        result: JSON.stringify(user)
      };
    }
    ```
  </Tab>

  <Tab title="Async generator">
    Generator function for streaming results:

    ```typescript theme={null}
    execute: async function* ({ documentIds }) {
      for (const docId of documentIds) {
        const doc = await processDocument(docId);

        yield {
          result: JSON.stringify({
            documentId: docId,
            status: "processed",
            content: doc.content
          })
        };
      }
    }
    ```
  </Tab>
</Tabs>

#### Execute function return type

The execute function should return an object with one or more of these properties:

<ResponseField name="result" type="string">
  The main result to return to the agent. Typically JSON stringified data.
</ResponseField>

<ResponseField name="job" type="string">
  Optional job ID if the tool queued a background task for async processing
</ResponseField>

<ResponseField name="items" type="Item[]">
  Optional array of items (typically used by context search tools)
</ResponseField>

```typescript theme={null}
return {
  result: JSON.stringify({
    status: "success",
    data: processedData
  }),
  job: "job-uuid-123" // If queued
};
```

## Optional parameters

### category

<ParamField path="category" type="string">
  Category for organizing tools. Defaults to `"default"` if not specified.
</ParamField>

```typescript theme={null}
category: "database"
```

Common categories:

* `"database"` - Database queries and operations
* `"api"` - External API integrations
* `"file"` - File operations and processing
* `"math"` - Mathematical calculations
* `"search"` - Search and retrieval operations
* `"workflow"` - Multi-step workflows

### inputSchema

<ParamField path="inputSchema" type="z.ZodType">
  Zod schema that defines and validates the tool's input parameters. If not provided, defaults to an empty object schema.
</ParamField>

```typescript theme={null}
import { z } from "zod";

inputSchema: z.object({
  userId: z.string().describe("The user ID to look up"),
  includeHistory: z.boolean()
    .optional()
    .describe("Include user's history in the response"),
  maxResults: z.number()
    .int()
    .positive()
    .max(100)
    .optional()
    .describe("Maximum number of results to return")
})
```

<Note>
  Always use `.describe()` on schema fields. These descriptions are shown to agents and help them understand what each parameter does.
</Note>

#### Zod schema best practices

<AccordionGroup>
  <Accordion title="Use descriptive field names">
    ```typescript theme={null}
    // Good
    z.object({
      userEmail: z.string(),
      includeDeletedRecords: z.boolean()
    })

    // Avoid
    z.object({
      email: z.string(), // Ambiguous - could be any email
      deleted: z.boolean() // Unclear what this controls
    })
    ```
  </Accordion>

  <Accordion title="Add descriptions to all fields">
    ```typescript theme={null}
    z.object({
      query: z.string()
        .min(1)
        .describe("Search query to find relevant documents"),
      limit: z.number()
        .int()
        .positive()
        .max(50)
        .optional()
        .describe("Maximum number of results to return (default: 10)")
    })
    ```
  </Accordion>

  <Accordion title="Use enums for fixed options">
    ```typescript theme={null}
    z.object({
      sortBy: z.enum(["date", "relevance", "popularity"])
        .describe("Sort order for results"),
      format: z.enum(["json", "csv", "xml"])
        .optional()
        .describe("Output format (default: json)")
    })
    ```
  </Accordion>

  <Accordion title="Add validation constraints">
    ```typescript theme={null}
    z.object({
      email: z.string()
        .email()
        .describe("User's email address"),
      age: z.number()
        .int()
        .min(0)
        .max(120)
        .describe("User's age in years"),
      username: z.string()
        .min(3)
        .max(20)
        .regex(/^[a-zA-Z0-9_]+$/)
        .describe("Username (3-20 alphanumeric characters)")
    })
    ```
  </Accordion>

  <Accordion title="Use optional for non-required fields">
    ```typescript theme={null}
    z.object({
      // Required
      userId: z.string()
        .describe("User ID (required)"),

      // Optional with default behavior
      includeArchived: z.boolean()
        .optional()
        .describe("Include archived items (default: false)"),

      // Optional with no default
      filterTag: z.string()
        .optional()
        .describe("Optional tag to filter by")
    })
    ```
  </Accordion>
</AccordionGroup>

## Configuration examples

### Simple function tool

```typescript theme={null}
import { ExuluTool } from "@exulu/backend";
import { z } from "zod";

const addTool = new ExuluTool({
  id: "add_numbers",
  name: "add",
  description: "Adds two numbers together",
  type: "function",
  category: "math",
  inputSchema: z.object({
    a: z.number().describe("First number"),
    b: z.number().describe("Second number")
  }),
  config: [],
  execute: async ({ a, b }) => {
    return {
      result: JSON.stringify({ sum: a + b })
    };
  }
});
```

### Database query tool

```typescript theme={null}
const getUserTool = new ExuluTool({
  id: "get_user_profile",
  name: "get_user_profile",
  description: "Retrieves user profile information from the database",
  type: "function",
  category: "database",
  inputSchema: z.object({
    userId: z.string().describe("User ID to look up"),
    includePreferences: z.boolean()
      .optional()
      .describe("Include user preferences (default: false)")
  }),
  config: [
    {
      name: "database_url",
      description: "Database connection URL",
      type: "variable"
    },
    {
      name: "timeout_seconds",
      description: "Query timeout in seconds",
      type: "number",
      default: 10
    }
  ],
  execute: async ({ userId, includePreferences = false }) => {
    const user = await db.users.findOne({ id: userId });

    if (!user) {
      return {
        result: JSON.stringify({
          error: "User not found",
          userId
        })
      };
    }

    if (includePreferences) {
      user.preferences = await db.preferences.findOne({ userId });
    }

    return {
      result: JSON.stringify(user)
    };
  }
});
```

### API integration tool

```typescript theme={null}
const weatherTool = new ExuluTool({
  id: "weather_lookup",
  name: "get_weather",
  description: "Gets current weather and forecast for a location",
  type: "function",
  category: "api",
  inputSchema: z.object({
    location: z.string().describe("City name or coordinates (lat,long)"),
    units: z.enum(["metric", "imperial"])
      .optional()
      .describe("Temperature units (default: metric)"),
    includeForecast: z.boolean()
      .optional()
      .describe("Include 7-day forecast (default: false)")
  }),
  config: [
    {
      name: "api_key",
      description: "Weather API authentication key",
      type: "variable"
    },
    {
      name: "api_endpoint",
      description: "Weather API base URL",
      type: "string",
      default: "https://api.weatherapi.com/v1"
    },
    {
      name: "cache_duration",
      description: "Cache responses for this many seconds",
      type: "number",
      default: 300
    }
  ],
  execute: async ({ location, units = "metric", includeForecast = false }) => {
    const url = `${config.api_endpoint}/current.json?key=${config.api_key}&q=${location}`;

    const response = await fetch(url);
    const data = await response.json();

    if (includeForecast) {
      const forecastUrl = `${config.api_endpoint}/forecast.json?key=${config.api_key}&q=${location}&days=7`;
      const forecastResponse = await fetch(forecastUrl);
      data.forecast = await forecastResponse.json();
    }

    return {
      result: JSON.stringify(data)
    };
  }
});
```

### Streaming tool with generator

```typescript theme={null}
const batchProcessTool = new ExuluTool({
  id: "batch_process_files",
  name: "batch_process_files",
  description: "Processes multiple files and streams results as they complete",
  type: "function",
  category: "file",
  inputSchema: z.object({
    fileKeys: z.array(z.string())
      .min(1)
      .max(50)
      .describe("S3 keys of files to process (max 50)"),
    operation: z.enum(["extract_text", "generate_thumbnail", "convert_format"])
      .describe("Operation to perform on each file")
  }),
  config: [
    {
      name: "s3_bucket",
      description: "S3 bucket name",
      type: "variable"
    },
    {
      name: "parallel_limit",
      description: "Maximum number of parallel operations",
      type: "number",
      default: 5
    }
  ],
  execute: async function* ({ fileKeys, operation }) {
    for (const fileKey of fileKeys) {
      try {
        const result = await processFile(fileKey, operation);

        yield {
          result: JSON.stringify({
            fileKey,
            status: "success",
            operation,
            output: result
          })
        };
      } catch (error) {
        yield {
          result: JSON.stringify({
            fileKey,
            status: "error",
            operation,
            error: error.message
          })
        };
      }
    }
  }
});
```

### Tool with background job queuing

```typescript theme={null}
const embedGeneratorTool = new ExuluTool({
  id: "generate_embeddings",
  name: "generate_embeddings",
  description: "Generates embeddings for documents (queued for background processing)",
  type: "function",
  category: "processing",
  inputSchema: z.object({
    contextId: z.string().describe("Context ID to generate embeddings for"),
    itemIds: z.array(z.string())
      .optional()
      .describe("Specific item IDs (if not provided, processes all items)")
  }),
  config: [
    {
      name: "queue_name",
      description: "BullMQ queue name",
      type: "string",
      default: "embeddings"
    },
    {
      name: "priority",
      description: "Job priority (1-10, higher is more important)",
      type: "number",
      default: 5
    }
  ],
  execute: async ({ contextId, itemIds }) => {
    const context = app.context(contextId);

    if (!context) {
      return {
        result: JSON.stringify({
          error: "Context not found",
          contextId
        })
      };
    }

    if (itemIds && itemIds.length > 0) {
      // Process specific items
      const jobs = [];
      for (const itemId of itemIds) {
        const { job } = await context.embeddings.generate.one({
          item: { id: itemId },
          trigger: "api",
          config: exuluConfig
        });
        jobs.push(job);
      }

      return {
        result: JSON.stringify({
          status: "queued",
          itemCount: itemIds.length
        }),
        job: jobs.join(",")
      };
    } else {
      // Process all items
      const { jobs, items } = await context.embeddings.generate.all(
        exuluConfig
      );

      return {
        result: JSON.stringify({
          status: "queued",
          itemCount: items
        }),
        job: jobs.join(",")
      };
    }
  }
});
```

## Configuration types reference

<AccordionGroup>
  <Accordion title="string">
    Text configuration values

    ```typescript theme={null}
    {
      name: "api_endpoint",
      description: "API base URL",
      type: "string",
      default: "https://api.example.com"
    }
    ```
  </Accordion>

  <Accordion title="number">
    Numeric configuration values

    ```typescript theme={null}
    {
      name: "timeout_ms",
      description: "Request timeout in milliseconds",
      type: "number",
      default: 5000
    }
    ```
  </Accordion>

  <Accordion title="boolean">
    True/false flags

    ```typescript theme={null}
    {
      name: "enable_cache",
      description: "Enable response caching",
      type: "boolean",
      default: true
    }
    ```
  </Accordion>

  <Accordion title="variable">
    References to ExuluVariables (for secrets and environment values)

    ```typescript theme={null}
    {
      name: "api_key",
      description: "API authentication key",
      type: "variable"
      // No default - must be set in variables table
    }
    ```

    <Note>
      Variables are stored in the database and can be encrypted. Perfect for API keys, credentials, and environment-specific values.
    </Note>
  </Accordion>
</AccordionGroup>

## Error handling patterns

<Tabs>
  <Tab title="Return error in result">
    ```typescript theme={null}
    execute: async (inputs) => {
      try {
        const data = await riskyOperation(inputs);
        return {
          result: JSON.stringify({ success: true, data })
        };
      } catch (error) {
        return {
          result: JSON.stringify({
            success: false,
            error: error.message
          })
        };
      }
    }
    ```
  </Tab>

  <Tab title="Throw error (will be caught by framework)">
    ```typescript theme={null}
    execute: async (inputs) => {
      if (!inputs.userId) {
        throw new Error("userId is required");
      }

      const user = await db.users.findOne({ id: inputs.userId });

      if (!user) {
        throw new Error(`User not found: ${inputs.userId}`);
      }

      return { result: JSON.stringify(user) };
    }
    ```
  </Tab>

  <Tab title="Partial errors in streaming">
    ```typescript theme={null}
    execute: async function* (inputs) {
      for (const item of inputs.items) {
        try {
          const result = await processItem(item);
          yield {
            result: JSON.stringify({
              item,
              status: "success",
              data: result
            })
          };
        } catch (error) {
          yield {
            result: JSON.stringify({
              item,
              status: "error",
              error: error.message
            })
          };
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/core/exulu-tool/api-reference">
    Explore methods and properties
  </Card>

  <Card title="ExuluApp" icon="cube" href="/core/exulu-app/introduction">
    Learn how to register tools with ExuluApp
  </Card>
</CardGroup>
