DocsCustom Tools Deep Dive

CUSTOM_TOOLS

ADVANCED

Extend agents with specialized capabilities using the Tool API.

OVERVIEW#

Tools bridge the gap between language models and the real world. They allow agents to execute code, fetch data, and interact with external APIs. We use zod for schema validation and strict TypeScript typing for developer experience.

TYPE_SAFETY

Full TypeScript integration with automatic type inference from Zod schemas.

VALIDATION

Runtime validation ensures LLMs always provide valid inputs for your functions.

ASYNC_SYNC

Support for both synchronous and asynchronous execution patterns.

DYNAMIC

Tools can access agent state and context during execution.

1

CREATE_A_BASIC_TOOL

basic-tool.ts
import { Tool } from '@AKIOS/sdk'
import { z } from 'zod'

export class WeatherTool extends Tool {
  constructor() {
    super({
      name: 'get_weather',
      description: 'Get current weather for a location',
      schema: z.object({
        location: z.string().min(1, 'Location is required')
      })
    })
  }

  async execute({ location }: { location: string }) {
    // Tool logic here
    const response = await fetch(`https://api.weather.com/${location}`)
    return response.json()
  }
}
2

HANDLE_COMPLEX_DATA_TYPES

complex-tool.ts
import { Tool } from '@AKIOS/sdk'
import { z } from 'zod'

export class DatabaseQueryTool extends Tool {
  constructor() {
    super({
      name: 'query_database',
      description: 'Execute a SQL query with proper sanitization',
      schema: z.object({
        table: z.string().min(1),
        columns: z.array(z.string()).min(1),
        where: z.record(z.string()).optional(),
        limit: z.number().max(1000).optional()
      })
    })
  }

  async execute({ table, columns, where, limit }: {
    table: string,
    columns: string[],
    where?: Record<string, any>,
    limit?: number
  }) {
    // Build parameterized query
    const query = this.buildQuery(table, columns, where, limit)
    const params = this.extractParams(where)

    return await this.db.execute(query, params)
  }

  private buildQuery(table: string, columns: string[], where?: Record<string, any>, limit?: number) {
    const select = `SELECT ${columns.join(', ')} FROM ${table}`
    const whereClause = where ? `WHERE ${Object.keys(where).map(k => `${k} = ?`).join(' AND ')}` : ''
    const limitClause = limit ? `LIMIT ${limit}` : ''
    return `${select} ${whereClause} ${limitClause}`.trim()
  }

  private extractParams(where?: Record<string, any>) {
    return where ? Object.values(where) : []
  }
}
3

ADD_CONFIGURATION_OPTIONS

configurable-tool.ts
import { Tool } from '@AKIOS/sdk'
import { z } from 'zod'

interface ApiToolConfig {
  baseUrl: string
  apiKey: string
  timeout: number
  retries: number
}

export class ConfigurableApiTool extends Tool {
  constructor(private config: ApiToolConfig) {
    super({
      name: 'api_call',
      description: 'Make authenticated API calls with retry logic',
      schema: z.object({
        endpoint: z.string().regex(/^\//, 'Endpoint must start with /'),
        method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
        body: z.record(z.any()).optional(),
        headers: z.record(z.string()).optional()
      })
    })
  }

  async execute({ endpoint, method, body, headers }: {
    endpoint: string,
    method: string,
    body?: any,
    headers?: Record<string, string>
  }) {
    const url = `${this.config.baseUrl}${endpoint}`

    for (let attempt = 1; attempt <= this.config.retries; attempt++) {
      try {
        const response = await fetch(url, {
          method,
          headers: {
            'Authorization': `Bearer ${this.config.apiKey}`,
            'Content-Type': 'application/json',
            ...headers
          },
          body: body ? JSON.stringify(body) : undefined,
          signal: AbortSignal.timeout(this.config.timeout)
        })

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`)
        }

        return await response.json()
      } catch (error) {
        if (attempt === this.config.retries) {
          throw error
        }
        // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1)))
      }
    }
  }
}
4

IMPLEMENT_STREAMING_TOOLS

streaming-tool.ts
import { Tool } from '@AKIOS/sdk'
import { z } from 'zod'

export class StreamingSearchTool extends Tool {
  constructor() {
    super({
      name: 'search_documents',
      description: 'Search through documents with real-time streaming results',
      schema: z.object({
        query: z.string().min(1),
        maxResults: z.number().max(100).optional()
      })
    })
  }

  async *execute({ query, maxResults = 10 }: { query: string, maxResults?: number }) {
    // Simulate streaming search results
    const results = await this.performSearch(query)

    for (const result of results.slice(0, maxResults)) {
      yield {
        title: result.title,
        snippet: result.content.substring(0, 200) + '...',
        score: result.score
      }

      // Allow agent to process results incrementally
      await new Promise(resolve => setTimeout(resolve, 100))
    }
  }

  private async performSearch(query: string) {
    // Implementation would search vector database
    return [
      { title: 'Document 1', content: 'Content...', score: 0.95 },
      { title: 'Document 2', content: 'Content...', score: 0.89 }
    ]
  }
}

// Usage in agent
const agent = new Agent({
  name: 'ResearchAssistant',
  tools: [new StreamingSearchTool()],
  onToolResult: (result) => {
    // Process streaming results as they arrive
    console.log('Got result:', result)
  }
})
5

BUILD_TOOL_CHAINS

tool-chain.ts
import { Tool, Agent } from '@AKIOS/sdk'
import { z } from 'zod'

export class ToolChain extends Tool {
  constructor(private tools: Tool[]) {
    super({
      name: 'execute_chain',
      description: 'Execute a sequence of tools with data flow between them',
      schema: z.object({
        steps: z.array(z.object({
          tool: z.string(),
          params: z.record(z.any())
        }))
      })
    })
  }

  async execute({ steps }: { steps: Array<{ tool: string, params: any }> }) {
    let result = null

    for (const step of steps) {
      const tool = this.tools.find(t => t.name === step.tool)
      if (!tool) {
        throw new Error(`Tool ${step.tool} not found`)
      }

      // Pass previous result as context if available
      const params = {
        ...step.params,
        ...(result && { previousResult: result })
      }

      result = await tool.execute(params)
    }

    return result
  }
}

// Example chain: Search -> Summarize -> Email
const searchTool = new StreamingSearchTool()
const summarizeTool = new SummarizeTool()
const emailTool = new EmailTool()

const chainTool = new ToolChain([searchTool, summarizeTool, emailTool])

// Agent can now execute complex workflows
await chainTool.execute({
  steps: [
    { tool: 'search_documents', params: { query: 'AI trends 2024' } },
    { tool: 'summarize_text', params: { maxLength: 500 } },
    { tool: 'send_email', params: { to: 'boss@company.com', subject: 'AI Trends Report' } }
  ]
})

TESTING_YOUR_TOOLS#

Tools are critical components that need thorough testing. Test both success and failure scenarios.

tool-testing.ts
import { jest } from '@jest/globals'

describe('WeatherTool', () => {
  let tool: WeatherTool

  beforeEach(() => {
    tool = new WeatherTool()
  })

  test('validates input correctly', async () => {
    await expect(tool.execute({ location: '' })).rejects.toThrow('Location is required')
  })

  test('handles API errors gracefully', async () => {
    // Mock fetch to simulate API failure
    global.fetch = jest.fn().mockRejectedValue(new Error('Network error'))

    await expect(tool.execute({ location: 'New York' })).rejects.toThrow('Network error')
  })

  test('returns expected data structure', async () => {
    const mockResponse = {
      temperature: 72,
      conditions: 'Sunny',
      location: 'New York, NY'
    }

    global.fetch = jest.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockResponse)
    })

    const result = await tool.execute({ location: 'New York' })
    expect(result).toEqual(mockResponse)
  })

  test('respects schema constraints', async () => {
    // Test with invalid data types
    await expect(tool.execute({ location: 123 })).rejects.toThrow()
  })
})

SECURITY_CONSIDERATIONS#

SECURITY_FIRST

Tools can be dangerous if not properly secured. Always validate inputs, use parameterized queries, and limit resource access.

🚨 DANGEROUS_PATTERNS

  • String concatenation in SQL queries
  • Direct file system access without validation
  • Executing user-provided code
  • Unlimited resource allocation

✅ SECURE_PATTERNS

  • Parameterized queries with input validation
  • Sandboxed execution environments
  • Resource limits and timeouts
  • Audit logging for all operations

PERFORMANCE_OPTIMIZATION#

Tools should be fast and efficient. Profile your tools and optimize bottlenecks.

performance-tool.ts
import { Tool } from '@AKIOS/sdk'
import { z } from 'zod'

export class CachedApiTool extends Tool {
  private cache = new Map<string, { data: any, expires: number }>()

  constructor(private ttlMs: number = 300000) { // 5 minutes
    super({
      name: 'cached_api_call',
      description: 'API calls with intelligent caching',
      schema: z.object({
        url: z.string().url(),
        method: z.enum(['GET', 'POST']).default('GET'),
        body: z.record(z.any()).optional()
      })
    })
  }

  async execute({ url, method, body }: { url: string, method?: string, body?: any }) {
    const cacheKey = `${method || 'GET'}:${url}:${JSON.stringify(body || {})}`

    // Check cache for GET requests
    if (method !== 'POST') {
      const cached = this.cache.get(cacheKey)
      if (cached && cached.expires > Date.now()) {
        return cached.data
      }
    }

    // Make the actual request
    const startTime = Date.now()
    const response = await fetch(url, {
      method: method || 'GET',
      headers: { 'Content-Type': 'application/json' },
      body: body ? JSON.stringify(body) : undefined
    })

    const data = await response.json()
    const duration = Date.now() - startTime

    // Log slow requests
    if (duration > 1000) {
      console.warn(`Slow API call: ${duration}ms for ${url}`)
    }

    // Cache successful GET responses
    if (response.ok && method !== 'POST') {
      this.cache.set(cacheKey, {
        data,
        expires: Date.now() + this.ttlMs
      })
    }

    return data
  }
}

BEST_PRACTICES

Design for Failure

Handle network timeouts, API rate limits, and invalid responses gracefully.

Validate Everything

Use Zod schemas for both inputs and outputs. Never trust external data.

Log Operations

Implement comprehensive logging for debugging and monitoring in production.

Test Thoroughly

Write unit tests covering success cases, error cases, and edge cases.