Skip to content

feat: add Exa AI-powered search tool#1

Open
tgonzalezc5 wants to merge 1 commit into
TheAiSingularity:mainfrom
tgonzalezc5:add-exa-search
Open

feat: add Exa AI-powered search tool#1
tgonzalezc5 wants to merge 1 commit into
TheAiSingularity:mainfrom
tgonzalezc5:add-exa-search

Conversation

@tgonzalezc5

Copy link
Copy Markdown

What

Adds Exa as an optional web-search provider behind a SEARCH_PROVIDER env switch. The default stays searxng, so the local-first / no-cloud experience is unchanged.

Why

The pipeline currently has one web-search backend (SearXNG). For users who don't run SearXNG locally, or who want a single hosted endpoint with semantic ranking + content extraction in one call, Exa is a natural drop-in. The provider returns the same {url, title, snippet} shape that _search_one already consumes, so retrieve / fetch_url / compress / synthesize / verify are untouched.

How

  • SEARCH_PROVIDER env var (default searxng) routes _web_search to either _searxng (existing) or _exa (new).
  • _exa calls exa.search_and_contents requesting summary + highlights together, then the snippet extractor cascades summary → joined highlights → text head so a missing field in any one mode doesn't drop the result.
  • _get_exa_client lazy-imports exa-py and stamps an x-exa-integration tracking header so usage is attributable. Lazy import means installs without exa-py keep importing the module cleanly; the import only happens when the provider is actually used.
  • _searxng stays public for backward compat (and so the existing recipe tests that monkeypatch it don't change).
  • Filter env vars (EXA_CATEGORY, EXA_INCLUDE_DOMAINS, EXA_EXCLUDE_DOMAINS, EXA_START_PUBLISHED_DATE, EXA_END_PUBLISHED_DATE, EXA_SEARCH_TYPE) are forwarded to the SDK only when set, so an unset value never overrides the SDK default.
  • exa-py>=2.0.0 lives under a new [exa] optional extra in pyproject.toml; default pip install is still light.

Usage

pip install agentic-research-engine[exa]
export SEARCH_PROVIDER=exa
export EXA_API_KEY=your-key
agentic-research ask "what is contextual retrieval?" --domain papers

Optional tuning:

export EXA_SEARCH_TYPE=neural          # auto (default) | neural | fast | deep | ...
export EXA_CATEGORY="research paper"   # company | news | research paper | ...
export EXA_INCLUDE_DOMAINS=arxiv.org,nature.com
export EXA_START_PUBLISHED_DATE=2025-01-01

Tests

New file: engine/tests/test_search_providers.py — 11 mocked tests, no network, no API key:

  • snippet fallback chain (summary present / highlights only / text only / nothing)
  • kwargs forwarded to the SDK (search type, category, domain filters, date filters)
  • lazy client raises a clear error when EXA_API_KEY is unset
  • tracking header (x-exa-integration) set on the client
  • dispatcher routing for both providers
  • _search_one end-to-end through the new dispatcher

Repo-wide test count: 234 → 245 (+11) green. engine/tests: 104 → 115. (Pre-existing recipes/by-use-case/trading-copilot collection error from missing ta / yfinance deps is unrelated and reproduces on main.)

Files changed

  • engine/core/pipeline.py_exa, _web_search, _get_exa_client, env gates
  • engine/core/__init__.py — re-export _exa, _web_search
  • engine/tests/test_search_providers.py — new (11 tests)
  • pyproject.toml[exa] extra with exa-py>=2.0.0
  • engine/requirements.txt — comment line documenting the optional dep
  • README.mdSEARCH_PROVIDER + EXA_API_KEY rows in the env table
  • docs/architecture.md — new web search row in the env-vars groupings

Checklist

  • Tests pass locally (pytest core/rag recipes engine/tests --ignore=recipes/by-use-case/trading-copilot -q → 245 passed)
  • New env vars documented (README env table + architecture.md groupings)
  • Live smoke works — not run; would require EXA_API_KEY + cloud egress
  • No secrets in diff

Adds an `exa` provider behind a SEARCH_PROVIDER env switch (default
remains `searxng`, so the local-first / no-cloud experience is
unchanged). When SEARCH_PROVIDER=exa and EXA_API_KEY is set, the search
node calls Exa's search_and_contents instead of SearXNG; everything
downstream (retrieve, fetch_url, compress, synthesize, verify) is
untouched because the provider returns the same {url, title, snippet}
shape.

  engine/core/pipeline.py
    SEARCH_PROVIDER, EXA_API_KEY, EXA_SEARCH_TYPE, EXA_CATEGORY,
    EXA_INCLUDE_DOMAINS, EXA_EXCLUDE_DOMAINS, EXA_START_PUBLISHED_DATE,
    EXA_END_PUBLISHED_DATE, EXA_HIGHLIGHTS_CHARS — new env gates.
    _exa(query, n) requests summary + highlights so the snippet
    extractor can cascade summary → highlights → text head.
    _get_exa_client() lazy-imports exa-py and stamps an
    x-exa-integration tracking header.
    _web_search(query, n) is the new dispatcher; _search_one calls it
    instead of _searxng directly. _searxng stays public for tests +
    backward compat.

  engine/core/__init__.py
    Re-export _exa and _web_search alongside _searxng.

  engine/tests/test_search_providers.py
    11 mocked tests: snippet fallback chain, kwargs forwarding, lazy
    client + tracking header, dispatcher routing, _search_one wiring.
    No network, no API key.

  pyproject.toml + engine/requirements.txt
    exa-py>=2.0.0 added under the new `exa` extra; the requirements
    file documents it as opt-in (commented). Default install stays
    light — exa-py is only imported when the provider is used.

  README.md + docs/architecture.md
    SEARCH_PROVIDER + EXA_API_KEY rows added to the env-vars table;
    architecture reference grouped under a new "web search" row.

Tests: 234 → 245 (+11) repo-wide green; engine/tests 104 → 115.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant