Skip to main content

Overview

File: src/tools/CustomDataTool.ts Demonstrates the powerful Custom Data API with semantic vector search. The example uses a movie database, but the patterns work for any searchable content.

What Makes This Special

Vector Search = Semantic Understanding Traditional search:
  • Query: “Inception” → Finds “Inception” ✅
  • Query: “dream movie” → Finds nothing ❌
Vector search:
  • Query: “Inception” → Finds “Inception” ✅
  • Query: “dream movie” → Finds “Inception”! ✅
  • Query: “mind-bending thriller” → Finds similar movies! ✅

Complete Tools

Create Movie Tool

import { LuaTool, Data } from 'lua-cli';
import { z } from 'zod';

export class CreateMovieTool implements LuaTool {
  name = "create_movie";
  description = "Add a new movie to the database";
  
  inputSchema = z.object({
    title: z.string(),
    director: z.string(),
    year: z.number(),
    genre: z.string(),
    description: z.string().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    // ⭐ KEY: Create searchable text with all relevant info
    const searchText = [
      input.title,
      input.director,
      input.genre,
      input.description
    ].filter(Boolean).join(' ');
    
    const movie = await Data.create('movies', input, searchText);
    
    return {
      id: movie.id,
      message: `Added "${input.title}" to database`
    };
  }
}

Search Movies Tool

export class SearchMoviesTool implements LuaTool {
  name = "search_movies";
  description = "Search movies by title, director, genre, or theme";
  
  inputSchema = z.object({
    query: z.string().describe("Search query (can be descriptive)")
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    // ⭐ Vector search with similarity threshold
    const results = await Data.search(
      'movies',
      input.query,
      10,           // Max 10 results
      0.7           // Min similarity score
    );
    
    return {
      movies: results.data.map(entry => ({
        id: entry.id,
        title: entry.data.title,
        year: entry.data.year,
        director: entry.data.director,
        relevance: Math.round(entry.score * 100) + '%'
      })),
      count: results.count
    };
  }
}

Get Movie Tool

export class GetMovieByIdTool implements LuaTool {
  name = "get_movie";
  description = "Get detailed information about a specific movie";
  
  inputSchema = z.object({
    id: z.string()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const movie = await Data.getEntry('movies', input.id);
    return movie.data;
  }
}

Key Concepts

1. Search Text is Critical

The searchText parameter determines what the AI can find:
// ✅ Good - Rich search text
const searchText = `${input.title} ${input.director} ${input.genre} ${input.description} ${input.tags.join(' ')}`;

// ❌ Bad - Only title
const searchText = input.title;

2. Similarity Scores

Understanding score thresholds:
  • 1.0 = Perfect match
  • 0.8-0.9 = Very similar
  • 0.7-0.8 = Somewhat similar
  • 0.6-0.7 = Loosely related
  • <0.6 = May be irrelevant
// Strict (high precision)
await Data.search('movies', query, 10, 0.8);

// Balanced (recommended)
await Data.search('movies', query, 10, 0.7);

// Loose (high recall)
await Data.search('movies', query, 10, 0.6);

3. Natural Language Queries

Users can search naturally:
// All of these work:
"sci-fi movies about space"
"Christopher Nolan films"
"thriller with plot twists"
"movies like Inception"
"romantic comedies from 2020"

Testing

lua test
Try semantic searches:
  • “mind-bending thriller” → Should find Inception
  • “Christopher Nolan movies” → Should find his films
  • “space exploration” → Should find relevant sci-fi
  • “romantic comedy” → Should find rom-coms

Use Cases

Knowledge Base

await Data.create('articles', {
  title: 'How to Reset Password',
  content: 'Step by step guide...',
  category: 'Account'
}, 'password reset account help guide');

// Users can find with:
// - "forgot password"
// - "can't log in"
// - "reset account"

Product Recommendations

await Data.create('products', {
  name: 'Wireless Headphones',
  description: 'Noise cancelling...'
}, 'wireless headphones bluetooth noise cancelling audio');

// Users can find with:
// - "best headphones for travel"
// - "noise canceling earphones"
// - "bluetooth audio"

Customer Notes

await Data.create('customers', {
  name: 'John Doe',
  company: 'Acme Corp',
  notes: 'Interested in enterprise plan'
}, 'John Doe Acme Corp enterprise interested sales');

// Find with:
// - "enterprise customers"
// - "Acme contacts"
// - "sales leads"

Customization Ideas

Add Ratings

inputSchema = z.object({
  ...existing,
  rating: z.number().min(0).max(10)
});

// Sort by rating
results.data.sort((a, b) => b.data.rating - a.data.rating);

Add Filters

// Search with filters
const results = await Data.search('movies', query, 50, 0.7);

// Filter by year
const recentMovies = results.data.filter(m => 
  m.data.year >= 2020
);

Update Movies

export class UpdateMovieTool implements LuaTool {
  name = "update_movie";
  description = "Update movie information";
  
  inputSchema = z.object({
    id: z.string(),
    data: z.object({
      rating: z.number().optional(),
      awards: z.array(z.string()).optional(),
      description: z.string().optional()
    }),
    updateSearchText: z.boolean().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    const movie = await Data.getEntry('movies', input.id);
    
    // Optionally update search text if description changed
    const searchText = input.updateSearchText 
      ? `${movie.title} ${movie.director} ${input.data.description || ''}`
      : undefined;
    
    await Data.update('movies', input.id, input.data, searchText);
    
    return { success: true, message: `Updated movie ${movie.title}` };
  }
}

Using save() Method

export class UpdateMovieRatingTool implements LuaTool {
  name = "update_movie_rating";
  description = "Update movie rating and review";
  
  inputSchema = z.object({
    id: z.string(),
    rating: z.number().min(0).max(10),
    review: z.string().optional()
  });

  async execute(input: z.infer<typeof this.inputSchema>) {
    // Get the entry
    const movie = await Data.getEntry('movies', input.id);
    
    // Modify properties directly
    movie.rating = input.rating;
    if (input.review) {
      movie.review = input.review;
    }
    movie.updatedAt = new Date().toISOString();
    
    // Save all changes at once
    // Optionally update search text if review added
    const searchText = input.review 
      ? `${movie.title} ${movie.director} rating ${input.rating} ${input.review}`
      : undefined;
    
    await movie.save(searchText);
    
    return { 
      success: true,
      message: `Updated "${movie.title}" rating to ${input.rating}` 
    };
  }
}

What You’ll Learn

Vector Search

Semantic similarity search with AI

Custom Collections

Store any data structure

Search Indexing

Optimize for findability

Score Thresholds

Tune precision vs recall

Next Steps

Data API Reference

Complete Data API documentation

Build Your First Skill

Uses Data API with vector search