Skip to content
Skillv1.0.0

exa-migration-deep-dive

Migrate from other search APIs (Google, Bing, Tavily, Serper) to Exa neural search. Use when switching to Exa from another search provider, migrating search pipelines, or evaluating Exa as a replaceme

by jeremylongshore(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jeremylongshore/tons-of-skills-marketplace (plugins/saas-packs/exa-pack/skills/exa-migration-deep-dive/SKILL.md). Install upstream with npx skills add jeremylongshore/tons-of-skills-marketplace --skill exa-migration-deep-dive. Copyright stays with the author (MIT).

Exa Migration Deep Dive

Prerequisites

  • An inventory of existing retrieval/data flows, policy classification, sanitized evaluation set, acceptance threshold, and rollback owner.
  • Separate staging credentials and review for any automation or customer-query migration.

Output

  • A staged migration record with compatibility/evaluation evidence, owner, observation window, and rollback configuration.

Examples

Run prior and target retrieval flows against sanitized evaluation queries in staging, compare aggregate citation/quality/latency/error data, then promote an approved canary. Roll back on policy or quality regression; do not bulk-replay customer queries or turn migration output into unreviewed decisions.

Current State

!npm list exa-js 2>/dev/null | grep exa-js || echo 'exa-js not installed' !npm list 2>/dev/null | grep -E '(google|bing|tavily|serper|serpapi)' || echo 'No competing search SDK found'

Overview

Migrate from traditional search APIs (Google Custom Search, Bing Web Search, Tavily, Serper) to Exa's neural search API. Key differences: Exa uses semantic/neural search instead of keyword matching, returns content (text/highlights/summary) in a single API call, and supports similarity search from a seed URL.

API Comparison

Feature Google/Bing Tavily Exa
Search model Keyword AI-enhanced Neural embeddings
Content in results Snippets only Full text Text + highlights + summary
Similarity search No No findSimilar() by URL
AI answer No Yes answer() + streamAnswer()
Categories No No company, news, research paper, tweet, people
Date filtering Limited Yes startPublishedDate / endPublishedDate
Domain filtering Yes Yes includeDomains / excludeDomains (up to 1200)

Instructions

Step 1: Install Exa SDK

set -euo pipefail
npm install exa-js
# Remove old SDK if replacing
# npm uninstall google-search-api tavily serpapi

Step 2: Create Adapter Layer

// src/search/adapter.ts
import Exa from "exa-js";

// Define a provider-agnostic search interface
interface SearchResult {
  title: string;
  url: string;
  snippet: string;
  score?: number;
  publishedDate?: string;
}

interface SearchResponse {
  results: SearchResult[];
  query: string;
}

// Exa implementation
class ExaSearchAdapter {
  private exa: Exa;

  constructor(apiKey: string) {
    this.exa = new Exa(apiKey);
  }

  async search(query: string, numResults = 10): Promise<SearchResponse> {
    const response = await this.exa.searchAndContents(query, {
      type: "auto",
      numResults,
      text: { maxCharacters: 500 },
      highlights: { maxCharacters: 300, query },
    });

    return {
      query,
      results: response.results.map(r => ({
        title: r.title || "Untitled",
        url: r.url,
        snippet: r.highlights?.join(" ") || r.text?.substring(0, 300) || "",
        score: r.score,
        publishedDate: r.publishedDate || undefined,
      })),
    };
  }

  // Exa-only: similarity search (no equivalent in Google/Bing)
  async findSimilar(url: string, numResults = 5): Promise<SearchResponse> {
    const response = await this.exa.findSimilarAndContents(url, {
      numResults,
      text: { maxCharacters: 500 },
      excludeSourceDomain: true,
    });

    return {
      query: url,
      results: response.results.map(r => ({
        title: r.title || "Untitled",
        url: r.url,
        snippet: r.text?.substring(0, 300) || "",
        score: r.score,
      })),
    };
  }
}

Step 3: Feature Flag Traffic Shift

// src/search/router.ts
function getSearchProvider(): "legacy" | "exa" {
  const exaPercentage = Number(process.env.EXA_TRAFFIC_PERCENTAGE || "0");
  return Math.random() * 100 < exaPercentage ? "exa" : "legacy";
}

async function search(query: string, numResults = 10): Promise<SearchResponse> {
  const provider = getSearchProvider();

  if (provider === "exa") {
    return exaAdapter.search(query, numResults);
  }
  return legacyAdapter.search(query, numResults);
}

// Gradually increase: 0% → 10% → 50% → 100%
// EXA_TRAFFIC_PERCENTAGE=10

Step 4: Query Translation

// Exa neural search works best with natural language, not keyword syntax
function translateQuery(legacyQuery: string): string {
  return legacyQuery
    // Remove boolean operators (Exa doesn't use them)
    .replace(/\b(AND|OR|NOT)\b/gi, " ")
    // Remove quotes (Exa uses semantic matching, not exact)
    .replace(/"/g, "")
    // Remove site: operator (use includeDomains instead)
    .replace(/site:\S+/gi, "")
    // Clean up extra whitespace
    .replace(/\s+/g, " ")
    .trim();
}

// Extract domain filters from legacy query
function extractDomainFilter(query: string): string[] {
  const domains: string[] = [];
  const siteMatches = query.matchAll(/site:(\S+)/gi);
  for (const match of siteMatches) {
    domains.push(match[1]);
  }
  return domains;
}

Step 5: Validation and Comparison

async function compareResults(query: string) {
  const [legacyResults, exaResults] = await Promise.all([
    legacyAdapter.search(query, 5),
    exaAdapter.search(query, 5),
  ]);

  // Compare URL overlap
  const legacyUrls = new Set(legacyResults.results.map(r => new URL(r.url).hostname));
  const exaUrls = new Set(exaResults.results.map(r => new URL(r.url).hostname));
  const overlap = [...legacyUrls].filter(u => exaUrls.has(u));

  console.log(`Legacy results: ${legacyResults.results.length}`);
  console.log(`Exa results: ${exaResults.results.length}`);
  console.log(`Domain overlap: ${overlap.length}/${legacyUrls.size}`);

  return { legacyResults, exaResults, overlapRate: overlap.length / legacyUrls.size };
}

Error Handling

Issue Cause Solution
Lower result count Exa filters more aggressively Increase numResults
Different ranking Neural vs keyword ranking Expected — evaluate by relevance
Boolean queries fail Exa doesn't support AND/OR Translate to natural language
Missing site: filter Different API parameter Use includeDomains parameter

Resources

Next Steps

For advanced troubleshooting, see exa-advanced-troubleshooting.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jeremylongshore-tons-of-skills-marketplace-exa-migration-ed9853/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jeremylongshore-tons-of-skills-marketplace-exa-migration-ed9853.ocm.jsonjson
{
  "ocm": "1",
  "id": "jeremylongshore-tons-of-skills-marketplace-exa-migration-ed9853",
  "kind": "skill",
  "name": "exa-migration-deep-dive",
  "description": "Migrate from other search APIs (Google, Bing, Tavily, Serper) to Exa neural search. Use when switching to Exa from another search provider, migrating search pipelines, or evaluating Exa as a replacement for traditional search APIs. Trigger with phrases like \"migrate to exa\", \"switch to exa\", \"replace google search with exa\", \"exa vs tavily\", \"exa migration\", \"move to exa\".",
  "publisher": "jeremylongshore",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "saas",
      "exa",
      "migration",
      "skills-sh"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Migrate from other search APIs (Google, Bing, Tavily, Serper) to Exa neural search. Use when switching to Exa from another search provider, migrating search pipelines, or evaluating Exa as a replacement for traditional search APIs. Trigger with phrases like \"migrate to exa\", \"switch to exa\", \"replace google search with exa\", \"exa vs tavily\", \"exa migration\", \"move to exa\"."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/jeremylongshore/tons-of-skills-marketplace",
      "path": "plugins/saas-packs/exa-pack/skills/exa-migration-deep-dive/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/jeremylongshore/tons-of-skills-marketplace/blob/HEAD/plugins/saas-packs/exa-pack/skills/exa-migration-deep-dive/SKILL.md",
      "key": "jeremylongshore/tons-of-skills-marketplace/plugins/saas-packs/exa-pack/skills/exa-migration-deep-dive/SKILL.md"
    },
    "compatibility": "Designed for Claude Code",
    "allowed_tools": [
      "Read,",
      "Write,",
      "Edit,",
      "Bash(npm:*),",
      "Bash(node:*)"
    ],
    "license": "MIT"
  },
  "instructions": "# Exa Migration Deep Dive\n\n## Prerequisites\n\n- An inventory of existing retrieval/data flows, policy classification, sanitized evaluation set, acceptance threshold, and rollback owner.\n- Separate staging credentials and review for any automation or customer-query migration.\n\n## Output\n\n- A staged migration record with compatibility/evaluation evidence, owner, observation window, and rollback configuration.\n\n## Examples\n\nRun prior and target retrieval flows against sanitized evaluation queries in staging, compare aggregate citation/quality/latency/error data, then promote an approved canary. Ro",
  "cost": {
    "context_tokens": 1627
  }
}

Fetch it by URL: GET /api/v1/registry/jeremylongshore-tons-of-skills-marketplace-exa-migration-ed9853/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.