Learn/Cookbook/Patterns

Product Search with Vector

Semantic product search with metadata filtering

Build a product search that understands natural language queries and filters by category, price, or other attributes.

The Pattern

Vector search finds semantically similar products. Combine with metadata filtering for precise results.

import { createAgent, type AgentContext } from '@agentuity/runtime';
import { z } from 'zod';
 
interface ProductMetadata {
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}
 
const agent = createAgent('Product Search', {
  description: 'Semantic search for products',
  schema: {
    input: z.object({
      query: z.string().describe('Natural language search query'),
      category: z.string().optional().describe('Filter by category'),
      maxPrice: z.number().optional().describe('Maximum price filter'),
      limit: z.number().min(1).max(50).default(10),
    }),
    output: z.object({
      products: z.array(z.object({
        id: z.string(),
        name: z.string(),
        description: z.string(),
        price: z.number(),
        category: z.string(),
        relevance: z.number(),
      })),
      total: z.number(),
    }),
  },
  handler: async (ctx: AgentContext, input) => {
    ctx.logger.info('Searching products', {
      query: input.query,
      category: input.category,
      maxPrice: input.maxPrice,
    });
 
    // Search with semantic similarity
    const results = await ctx.vector.search<ProductMetadata>('products', {
      query: input.query,
      limit: input.limit * 2, // Fetch extra for filtering
      similarity: 0.6,
    });
 
    // Apply metadata filters
    let filtered = results;
 
    if (input.category) {
      filtered = filtered.filter(r =>
        r.metadata?.category?.toLowerCase() === input.category?.toLowerCase()
      );
    }
 
    if (input.maxPrice) {
      filtered = filtered.filter(r =>
        (r.metadata?.price ?? Infinity) <= input.maxPrice!
      );
    }
 
    // Only show in-stock items
    filtered = filtered.filter(r => r.metadata?.inStock !== false);
 
    // Limit to requested count
    const products = filtered.slice(0, input.limit).map(r => ({
      id: r.key,
      name: r.metadata?.name || 'Unknown',
      description: r.document || '',
      price: r.metadata?.price || 0,
      category: r.metadata?.category || 'Uncategorized',
      relevance: r.similarity,
    }));
 
    ctx.logger.info('Search complete', {
      found: results.length,
      afterFilters: products.length,
    });
 
    return {
      products,
      total: products.length,
    };
  },
});
 
export default agent;

Indexing Products

Add products to the vector database:

// src/agent/product-indexer/agent.ts
import { createAgent, type AgentContext } from '@agentuity/runtime';
import { z } from 'zod';
 
const ProductSchema = z.object({
  id: z.string(),
  name: z.string(),
  description: z.string(),
  price: z.number(),
  category: z.string(),
  inStock: z.boolean().default(true),
});
 
const agent = createAgent('ProductIndexer', {
  schema: {
    input: z.object({
      products: z.array(ProductSchema),
    }),
    output: z.object({
      indexed: z.number(),
    }),
  },
  handler: async (ctx: AgentContext, input) => {
    for (const product of input.products) {
      // Use description as the searchable document
      await ctx.vector.upsert('products', {
        key: product.id,
        document: `${product.name}. ${product.description}`,
        metadata: {
          name: product.name,
          price: product.price,
          category: product.category,
          inStock: product.inStock,
        },
      });
    }
 
    return { indexed: input.products.length };
  },
});
 
export default agent;

Route with Query Parameters

import { createRouter } from '@agentuity/runtime';
import productSearch from '@agent/product-search';
 
const router = createRouter();
 
router.get('/search', async (c) => {
  const query = c.req.query('q') || '';
  const category = c.req.query('category');
  const maxPrice = c.req.query('maxPrice');
  const limit = parseInt(c.req.query('limit') || '10');
 
  const result = await productSearch.run({
    query,
    category,
    maxPrice: maxPrice ? parseFloat(maxPrice) : undefined,
    limit,
  });
 
  return c.json(result);
});
 
export default router;

Example Usage

# Natural language search
curl "http://localhost:3500/products/search?q=comfortable%20office%20chair"
 
# With filters
curl "http://localhost:3500/products/search?q=laptop&category=electronics&maxPrice=1000"

AI-Powered Recommendations

Enhance search results with AI-generated recommendations. Use generateObject to analyze matches and suggest the best option.

import { createAgent, type AgentContext } from '@agentuity/runtime';
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { s } from '@agentuity/schema';
import { z } from 'zod';
 
interface ProductMetadata {
  sku: string;
  name: string;
  price: number;
  rating: number;
  description: string;
  feedback: string;
}
 
const agent = createAgent('Product Advisor', {
  description: 'Semantic search with AI recommendations',
  schema: {
    input: s.object({
      query: s.string(),
    }),
    output: s.object({
      matches: s.array(
        s.object({
          sku: s.string(),
          name: s.string(),
          price: s.number(),
          rating: s.number(),
          similarity: s.number(),
        })
      ),
      recommendation: s.string(),
      recommendedSKU: s.string(),
    }),
  },
  handler: async (ctx: AgentContext, input) => {
    // Semantic search for matching products
    const results = await ctx.vector.search<ProductMetadata>('products', {
      query: input.query,
      limit: 3,
      similarity: 0.3,
    });
 
    if (results.length === 0) {
      return {
        matches: [],
        recommendation: 'No matching products found. Try a different search.',
        recommendedSKU: '',
      };
    }
 
    // Format matches for response
    const matches = results.map((r) => ({
      sku: r.metadata?.sku ?? '',
      name: r.metadata?.name ?? '',
      price: r.metadata?.price ?? 0,
      rating: r.metadata?.rating ?? 0,
      similarity: r.similarity,
    }));
 
    // Build context for AI recommendation
    const context = results
      .map((r) => {
        const p = r.metadata;
        return `${p?.name}: SKU ${p?.sku}, $${p?.price}, ${p?.rating} stars. "${p?.feedback}"`;
      })
      .join('\n');
 
    // Generate personalized recommendation
    const { object } = await generateObject({
      model: openai('gpt-5-mini'),
      system: 'You are a product consultant. Provide a brief 2-3 sentence recommendation based on the search results. Reference customer feedback when relevant.',
      prompt: `Customer searched for: "${input.query}"\n\nMatching products:\n${context}`,
      schema: z.object({
        summary: z.string().describe('Brief recommendation explaining the best choice'),
        recommendedSKU: z.string().describe('SKU of the recommended product'),
      }),
    });
 
    return {
      matches,
      recommendation: object.summary,
      recommendedSKU: object.recommendedSKU,
    };
  },
});
 
export default agent;

When to Use AI Recommendations

Add AI recommendations when customers benefit from personalized guidance: comparing similar products, explaining trade-offs, or highlighting relevant features based on their search intent.

Example Response

{
  "matches": [
    { "sku": "CHAIR-ERG-001", "name": "ErgoMax Pro", "price": 549, "rating": 4.8, "similarity": 0.89 },
    { "sku": "CHAIR-BUD-002", "name": "ComfortBasic", "price": 129, "rating": 4.2, "similarity": 0.76 }
  ],
  "recommendation": "For a comfortable office chair, I recommend the ErgoMax Pro. Customers report significant back pain relief, and the premium lumbar support justifies the higher price for long work sessions.",
  "recommendedSKU": "CHAIR-ERG-001"
}

Key Points

  • Semantic search finds products by meaning, not just keywords
  • Metadata filtering narrows results by category, price, stock
  • AI recommendations add personalized guidance based on search context
  • Customer feedback in metadata helps AI make relevant suggestions
  • Document field should include searchable text (name + description)

See Also

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!