Skip to content

feat: add Exa AI-powered search tool#90

Open
tgonzalezc5 wants to merge 1 commit into1jehuang:masterfrom
tgonzalezc5:feat/exa-search
Open

feat: add Exa AI-powered search tool#90
tgonzalezc5 wants to merge 1 commit into1jehuang:masterfrom
tgonzalezc5:feat/exa-search

Conversation

@tgonzalezc5
Copy link
Copy Markdown

@tgonzalezc5 tgonzalezc5 commented Apr 30, 2026

Summary

  • Adds a new exa_search tool backed by the Exa Search API, sitting alongside the existing DuckDuckGo websearch tool.
  • Exposes Exa-specific tuning as tool parameters: search type, category, include_domains / exclude_domains, published-date range, user_location, and per-result contents (text / highlights / summary).
  • Reads EXA_API_KEY lazily at execute time so the tool registers unconditionally and returns a clear, actionable error when the key is unset.
  • Sends x-exa-integration: jcode on every request so API usage can be attributed to this integration.

Why a separate tool

websearch scrapes DuckDuckGo HTML and has no API key, no provider abstraction, and no per-result content control. Bolting Exa-specific knobs (search type, category, content modes) onto it would either dilute its parameter surface or create a hidden coupling between it and an unrelated provider. A dedicated exa_search tool keeps the existing tool unchanged and gives the agent a clear way to opt in when an Exa key is configured.

Usage

// Tool input shape (JSON):
{
  "query": "rust async runtime benchmarks 2026",
  "num_results": 10,
  "type": "neural",
  "category": "research paper",
  "include_domains": ["arxiv.org", "research.google"],
  "start_published_date": "2025-01-01",
  "contents": {
    "highlights": true,
    "summary": { "query": "key benchmark numbers" },
    "text": { "max_characters": 2000 }
  }
}

The tool POSTs to https://api.exa.ai/search, parses the response into typed Rust structs, and renders a numbered result list. Each rendered result picks the best available snippet via a summary -> highlights -> truncated text cascade, so the output stays useful regardless of which contents flags the caller sets.

Files changed

  • src/tool/exa_search.rs (new) — tool implementation, request/response types, formatter
  • src/tool/exa_search_tests.rs (new) — unit tests (response parsing, snippet fallback ladder, contents-builder defaults, missing-key error)
  • src/tool/mod.rs — register the new tool in base_tools

No new crate dependencies; reuses reqwest, serde, serde_json, async-trait, and crate::provider::shared_http_client() already in the workspace.

Test plan

  • cargo check --bin jcode passes (no new warnings introduced)
  • cargo test --lib --bin jcode exa_search — 14 / 14 pass
  • cargo test --lib --bin jcode tool::tests — 14 / 14 pass (registry sort and schema validators still green with the new tool registered)
  • Live smoke test against api.exa.ai with a real EXA_API_KEY (not run in this PR; covered by parsing tests against a captured response shape)

Notes / tradeoffs

  • The tool is registered unconditionally; the env-var check happens at execute time. This keeps tool discovery deterministic (matches the rest of the registry) and lets the agent surface a clear error to the user instead of silently hiding the tool.
  • The contents schema is permissive: text and summary accept either true or an object with extra knobs (max_characters, query). The serializer collapses empty/no-op shapes so we never send {} to the API.
  • I picked sensible, conservative defaults (highlights on, text capped at 1000 chars, num_results clamped 1..=25) so a bare { "query": "..." } call still returns useful, bounded output.
  • Did not modify websearch. Happy to wire Exa in as a websearch backend instead if you'd prefer a single tool surface — let me know.

View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Adds a dedicated `exa_search` tool that calls the Exa Search API and
returns ranked results with optional highlights, page text, and LLM
summaries. Sits alongside the existing DuckDuckGo `websearch` tool
rather than replacing it, so neither path changes for users without an
Exa key.

Reads `EXA_API_KEY` from the environment at execute time and returns a
clear error when it is missing, rather than failing at registration.
Sets the `x-exa-integration: jcode` header on every request so usage
can be attributed to this integration.

Exposes the search-tuning surface as JSON-schema parameters: search
type, category, include/exclude domains, published-date range, user
location, and per-result content controls (text/highlights/summary).
Snippet rendering cascades summary -> highlights -> truncated text so
output is useful regardless of which contents flags the caller passes.

Tests cover response parsing (full and minimal), the snippet fallback
ladder, contents-builder defaults, and the missing-key error path.
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