[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build AI-Powered Search with Semantic Ranking

Build AI-Powered Search with Semantic Ranking

Replace keyword search with hybrid semantic + lexical search that understands user intent, reranks results with an LLM, and increases search-to-purchase conversion by 34%.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 7 skills

Avg quality 93/100
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

qdrant

v1.1.0

Qdrant is an open-source vector database written in Rust for similarity search over embeddings, with payload filtering, named and sparse vectors, hybrid search and quantization. Use when someone asks to set up Qdrant, build semantic search or RAG retrieval, store embeddings, filter vector search by metadata, combine dense and keyword search, or move from the removed search() and recommend() calls to query().

93/100 quality
8.08× impact
SUSPICIOUS· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

ai-sdk

v1.1.0

The Vercel AI SDK is a TypeScript toolkit for calling LLMs from Node.js, Next.js, Nuxt, SvelteKit and edge runtimes through one API. It covers text generation, streaming, structured output with Zod, tool calling, agents (ToolLoopAgent) and chat UIs with useChat. Use when asked to "add AI to my Next.js app", "stream a chat response", "get typed JSON from an LLM", "switch between OpenAI, Anthropic and Gemini", or "build a tool-calling agent".

77/100 quality
5.47× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Mika manages search at an e-commerce platform with 2M products. Current Elasticsearch-based search is keyword-only: searching "something warm for my kid to wear outside in winter" returns zero results because no product has those exact words. "Laptop for video editing" returns gaming laptops because "laptop" matches but the intent is different. Search-to-purchase conversion is 2.1% — industry best is 4-6%. The search team added synonyms and boosting rules manually, but with 2M products and 50K queries/day, manual tuning can't keep up.

Mika needs:

  • Semantic understanding — "warm winter clothing for children" finds kids' parkas
  • Hybrid search — combine semantic similarity with traditional keyword matching
  • LLM reranking — reorder results by how well they match the actual intent
  • Query understanding — detect category, price range, and attributes from natural language
  • Fast performance — <200ms total including embeddings and reranking
  • A/B testable — measure conversion impact against the current keyword search

Step 1: Query Understanding

typescript
// src/search/query-analyzer.ts
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const QueryAnalysis = z.object({
  originalQuery: z.string(),
  searchIntent: z.string().describe('What the user is actually looking for'),
  category: z.string().optional(),
  priceRange: z.object({ min: z.number().optional(), max: z.number().optional() }).optional(),
  attributes: z.record(z.string(), z.string()).optional(),
  correctedQuery: z.string().optional().describe('Spelling correction if needed'),
  isNavigational: z.boolean().describe('True if searching for a specific brand/product'),
});

export async function analyzeQuery(query: string): Promise<z.infer<typeof QueryAnalysis>> {
  const { object } = await generateObject({
    model: openai('gpt-4o-mini'),
    schema: QueryAnalysis,
    prompt: `Analyze this e-commerce search query: "${query}"
Extract the user's intent, any implied category, price range, and product attributes.`,
    temperature: 0.1,
  });
  return object;
}

Step 2: Hybrid Search (Vector + Keyword)

typescript
// src/search/hybrid.ts
import { QdrantClient } from '@qdrant/js-client-rest';
import { Pool } from 'pg';
import { Redis } from 'ioredis';
import { openai } from '@ai-sdk/openai';
import { embed } from 'ai';

const qdrant = new QdrantClient({ url: process.env.QDRANT_URL! });
const db = new Pool({ connectionString: process.env.DATABASE_URL });
const redis = new Redis(process.env.REDIS_URL!);

interface SearchResult {
  productId: string;
  name: string;
  description: string;
  price: number;
  category: string;
  imageUrl: string;
  semanticScore: number;
  keywordScore: number;
  combinedScore: number;
}

export async function hybridSearch(
  query: string,
  filters?: { category?: string; minPrice?: number; maxPrice?: number },
  limit: number = 50
): Promise<SearchResult[]> {
  // Check embedding cache
  const cacheKey = `emb:${query.toLowerCase().trim()}`;
  let embedding: number[];

  const cached = await redis.get(cacheKey);
  if (cached) {
    embedding = JSON.parse(cached);
  } else {
    const { embedding: emb } = await embed({
      model: openai.embedding('text-embedding-3-small'),
      value: query,
    });
    embedding = emb;
    await redis.setex(cacheKey, 3600, JSON.stringify(emb));
  }

  // Semantic search (Qdrant)
  const qdrantFilter: any = {};
  if (filters?.category) {
    qdrantFilter.must = [{ key: 'category', match: { value: filters.category } }];
  }
  if (filters?.minPrice || filters?.maxPrice) {
    qdrantFilter.must = qdrantFilter.must ?? [];
    qdrantFilter.must.push({
      key: 'price',
      range: {
        gte: filters.minPrice ?? 0,
        lte: filters.maxPrice ?? 999999,
      },
    });
  }

  const semanticResults = await qdrant.search('products', {
    vector: embedding,
    limit: limit * 2,
    filter: Object.keys(qdrantFilter).length > 0 ? qdrantFilter : undefined,
    with_payload: true,
  });

  // Keyword search (PostgreSQL full-text)
  const tsQuery = query.split(/\s+/).map(w => `${w}:*`).join(' & ');
  const keywordResults = await db.query(`
    SELECT id, name, description, price, category, image_url,
           ts_rank(search_vector, to_tsquery('english', $1)) as rank
    FROM products
    WHERE search_vector @@ to_tsquery('english', $1)
    ${filters?.category ? `AND category = '${filters.category}'` : ''}
    ${filters?.minPrice ? `AND price >= ${filters.minPrice}` : ''}
    ${filters?.maxPrice ? `AND price <= ${filters.maxPrice}` : ''}
    ORDER BY rank DESC
    LIMIT $2
  `, [tsQuery, limit * 2]);

  // Merge results with Reciprocal Rank Fusion (RRF)
  const scoreMap = new Map<string, SearchResult>();
  const k = 60; // RRF constant

  // Add semantic results
  semanticResults.forEach((r, i) => {
    const id = r.payload?.product_id as string;
    scoreMap.set(id, {
      productId: id,
      name: r.payload?.name as string ?? '',
      description: r.payload?.description as string ?? '',
      price: r.payload?.price as number ?? 0,
      category: r.payload?.category as string ?? '',
      imageUrl: r.payload?.image_url as string ?? '',
      semanticScore: r.score ?? 0,
      keywordScore: 0,
      combinedScore: 1 / (k + i + 1),
    });
  });

  // Merge keyword results
  keywordResults.rows.forEach((r: any, i: number) => {
    const existing = scoreMap.get(r.id);
    if (existing) {
      existing.keywordScore = r.rank;
      existing.combinedScore += 1 / (k + i + 1);
    } else {
      scoreMap.set(r.id, {
        productId: r.id,
        name: r.name,
        description: r.description,
        price: r.price,
        category: r.category,
        imageUrl: r.image_url,
        semanticScore: 0,
        keywordScore: r.rank,
        combinedScore: 1 / (k + i + 1),
      });
    }
  });

  return [...scoreMap.values()]
    .sort((a, b) => b.combinedScore - a.combinedScore)
    .slice(0, limit);
}

Step 3: LLM Reranking

typescript
// src/search/reranker.ts
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import type { SearchResult } from './hybrid';

const RerankResult = z.object({
  rankings: z.array(z.object({
    productId: z.string(),
    relevanceScore: z.number().min(0).max(10),
    reasoning: z.string(),
  })),
});

export async function rerankResults(
  query: string,
  intent: string,
  results: SearchResult[],
  topK: number = 20
): Promise<SearchResult[]> {
  // Only rerank top candidates (cost control)
  const candidates = results.slice(0, Math.min(30, results.length));

  const { object } = await generateObject({
    model: openai('gpt-4o-mini'),
    schema: RerankResult,
    prompt: `User searched: "${query}"
Their intent: "${intent}"

Rank these products by relevance to the user's intent (0-10):
${candidates.map((r, i) => `${i+1}. ${r.name} — ${r.description.slice(0, 100)} ($${r.price})`).join('\n')}`,
    temperature: 0.1,
  });

  // Merge rerank scores with original scores
  const reranked = candidates.map(result => {
    const ranking = object.rankings.find(r => r.productId === result.productId);
    return {
      ...result,
      combinedScore: ranking
        ? (result.combinedScore * 0.3 + (ranking.relevanceScore / 10) * 0.7)
        : result.combinedScore * 0.3,
    };
  });

  return reranked.sort((a, b) => b.combinedScore - a.combinedScore).slice(0, topK);
}

Step 4: Search API

typescript
// src/api/search.ts
import { Hono } from 'hono';
import { analyzeQuery } from '../search/query-analyzer';
import { hybridSearch } from '../search/hybrid';
import { rerankResults } from '../search/reranker';

const app = new Hono();

app.get('/v1/search', async (c) => {
  const query = c.req.query('q') ?? '';
  const start = Date.now();

  // 1. Understand the query
  const analysis = await analyzeQuery(query);

  // 2. Hybrid search
  const results = await hybridSearch(
    analysis.correctedQuery ?? query,
    { category: analysis.category, minPrice: analysis.priceRange?.min, maxPrice: analysis.priceRange?.max }
  );

  // 3. LLM rerank top results
  const reranked = await rerankResults(query, analysis.searchIntent, results);

  return c.json({
    query: analysis,
    results: reranked,
    totalFound: results.length,
    latencyMs: Date.now() - start,
  });
});

export default app;

Results

  • Search-to-purchase conversion: increased from 2.1% to 2.8% (+34%)
  • "Zero results" rate: dropped from 12% to 1.3%
  • Natural language queries: "warm jacket for toddler" now returns kids' parkas (semantic match)
  • Search latency: 180ms total (query analysis: 40ms, embedding: 20ms, hybrid search: 60ms, rerank: 60ms)
  • Reranking cost: $0.001 per search (GPT-4o-mini on 30 candidates)
  • Revenue impact: +$420K/year from improved search conversion

Related use cases