> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://respan.ai/docs/integrations/exa/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/_mcp/server. # Exa (tracing) > Trace Exa search, contents, grounded answers, streaming, tool helpers, and Agent runs with Respan. [Exa](https://exa.ai/) provides search, contents, grounded-answer, tool, and Agent APIs. Respan's native Exa instrumentations turn those SDK calls into connected OpenTelemetry spans with provider-neutral names and canonical Respan metadata. #### Set up Respan Create an account at [platform.respan.ai](https://platform.respan.ai) and grab an [API key](https://platform.respan.ai/platform/api/api-keys). Run `npx @respan/cli setup` to set up with your coding agent. #### Package source and example projects * [Python instrumentation source](https://github.com/respanai/respan/tree/main/python-sdks/instrumentations/respan-instrumentation-exa) * [JavaScript instrumentation source](https://github.com/respanai/respan/tree/main/javascript-sdks/instrumentations/respan-instrumentation-exa) * [Python examples](https://github.com/respanai/respan-example-projects/tree/main/python/tracing/exa) * [JavaScript examples](https://github.com/respanai/respan-example-projects/tree/main/typescript/tracing/exa) > **Warning** > > The first release of the Python `respan-instrumentation-exa` package is pending and is not currently available from PyPI. The Python installation steps below use a Respan source checkout. The JavaScript `@respan/instrumentation-exa` package is available from npm. > **Note** > > This is an SDK tracing integration, not a Respan Gateway provider route. Respan Gateway does not currently route Exa search, contents, answer, Agent, or Research APIs. `EXA_API_KEY` is used directly by the official Exa SDK; `RESPAN_BASE_URL` configures trace export only. The Python package requires Python `>=3.11,<3.14`, supports `exa-py>=2.20.0,<3.0.0`, and is tested with `exa-py==2.20.0`. The JavaScript package requires Node.js 18 or newer, supports `exa-js>=2.19.0 <3.0.0`, and is tested with the npm-stable `exa-js@2.19.0`. ## Setup #### Python #### Install from source ```bash git clone https://github.com/respanai/respan.git python -m pip install respan-ai "exa-py==2.20.0" python -m pip install \ -e ./respan/python-sdks/respan-tracing \ -e ./respan/python-sdks/instrumentations/respan-instrumentation-exa ``` #### Set environment variables ```bash export RESPAN_API_KEY="YOUR_RESPAN_API_KEY" export EXA_API_KEY="YOUR_EXA_API_KEY" # Optional: override the Respan API base URL. export RESPAN_BASE_URL="https://api.respan.ai/api" ``` #### Initialize Respan and call Exa Initialize the explicit instrumentor before making Exa SDK calls, and shut Respan down when the application exits so pending spans are flushed. ```python import os from exa_py import Exa from respan import Respan from respan_instrumentation_exa import ExaInstrumentor respan = Respan( api_key=os.environ["RESPAN_API_KEY"], base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"), app_name="exa-tracing", instrumentations=[ExaInstrumentor()], ) exa = Exa(api_key=os.environ["EXA_API_KEY"]) try: result = exa.search( "recent advances in retrieval", type="auto", num_results=3, contents={"highlights": True}, ) print(result.results) finally: respan.shutdown() ``` #### View your trace Open the [Traces page](https://platform.respan.ai/platform/traces) and inspect the `tool.search` span, its input and output, status, timing, and Exa metadata. #### JavaScript #### Install packages ```bash npm install @respan/respan @respan/instrumentation-exa@0.1.0 exa-js@2.19.0 ``` #### Set environment variables ```bash export RESPAN_API_KEY="YOUR_RESPAN_API_KEY" export EXA_API_KEY="YOUR_EXA_API_KEY" # Optional: override the Respan API base URL. export RESPAN_BASE_URL="https://api.respan.ai/api" ``` #### Initialize Respan and call Exa `await respan.initialize()` before creating traced work. Shut the client down in `finally` so pending spans are flushed. ```javascript import { Exa } from "exa-js"; import { Respan } from "@respan/respan"; import { ExaInstrumentor } from "@respan/instrumentation-exa"; const respanApiKey = process.env.RESPAN_API_KEY; const exaApiKey = process.env.EXA_API_KEY; if (!respanApiKey || !exaApiKey) { throw new Error("RESPAN_API_KEY and EXA_API_KEY are required"); } const respan = new Respan({ apiKey: respanApiKey, baseURL: process.env.RESPAN_BASE_URL, appName: "exa-tracing", instrumentations: [new ExaInstrumentor()], }); await respan.initialize(); const exa = new Exa(exaApiKey); try { const result = await exa.search("recent advances in retrieval", { type: "auto", numResults: 3, contents: { highlights: true }, }); console.log(result.results); } finally { await respan.shutdown(); } ``` #### View your trace Open the [Traces page](https://platform.respan.ai/platform/traces) and inspect the `tool.search` span, its input and output, status, timing, and Exa metadata. ## Captured SDK surfaces The integration is explicit-only: pass `ExaInstrumentor` in `instrumentations` as shown above. Exa can execute OpenAI or Anthropic tool adapters internally, so it is not enabled as direct-LLM auto-instrumentation. | Surface | Python | JavaScript | Respan shape | | ---------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Search | `search()`, `search_and_contents()`, `find_similar()`, `find_similar_and_contents()` | `search()`, `searchAndContents()`, `findSimilar()`, `findSimilarAndContents()` | Provider-neutral tool spans such as `tool.search` and `tool.find_similar` | | Contents | `get_contents()` | `getContents()` | `tool.get_contents` with URLs/options as input and retrieved content as output | | Grounded answers | `answer()`, `stream_answer()` | `answer()`, `streamAnswer()` | Chat spans; the model is recorded only when the SDK request or response supplies one | | Search streaming | `stream_search()` | `streamSearch()` | One span whose output aggregates the consumed chunks and citations | | Agent runs | Sync and async `agent.runs` create, stream, get, list, cancel, stop, delete, poll, wait, and event-list operations | `agent.runs` and beta Agent run create, stream, get, list, cancel, stop, delete, poll, wait, and event-list operations | `agent.run` plus lifecycle task spans | | Tool helpers | `tools.web_search()`, Python 2.20 `tools.get_contents()`, and the OpenAI Chat/Responses and Anthropic helper adapters | `tools.webSearch()` plus the OpenAI Chat/Responses and Anthropic helper adapters | The helper's underlying core Exa call is traced once, without a duplicate wrapper span | | Legacy Research | Sync and async `research.create()`, `get()`, `list()`, and `poll_until_finished()` | `research.create()`, `get()`, `list()`, and `pollUntilFinished()` | `agent.research` plus lifecycle task spans, marked as legacy metadata | Native SDK operation names, language, stream state, result counts, request IDs, resolved search type, cost, citations, and legacy Research markers are stored in the canonical `respan.metadata` JSON attribute. Successful and failed calls also retain their real status and error information. API keys and authorization-like fields are always redacted. ## Streaming lifecycle Streaming spans end when the iterator is exhausted, iteration raises an error, or the iterator is explicitly closed. JavaScript early exit from a `for await` loop calls the wrapped iterator's `return()` and closes the span. Python exposes the SDK's close behavior through the wrapped iterator, including the synchronous `close()` method on `exa-py` 2.20 async stream response objects. If an application abandons a stream without exhausting or closing it, the instrumentation cannot record an exact completion timestamp. Always exhaust the stream or close it explicitly. ## Content and privacy controls Request and response content is captured by default. Disable it per instrumentor: **`Python`** ```python Python ExaInstrumentor(capture_content=False) ``` **`JavaScript`** ```javascript JavaScript new ExaInstrumentor({ captureContent: false }) ``` Or disable content capture for the process: ```bash export TRACELOOP_TRACE_CONTENT=false ``` With content capture disabled, query, result, prompt, completion, citation, and stream payloads are omitted. Operation, stream state, status, timing, and other non-content metadata remain available. ## Limitations * Websets, Search Monitor CRUD, and beta Agent Monitor operations are not patched; they are long-lived control-plane APIs rather than in-process AI operations. * `exa-js@2.19.0` supports core `getContents()`, but does not ship a `tools.getContents()` helper. The Python 2.20 SDK does ship `tools.get_contents()`. * Legacy Research remains instrumented for compatibility, but Exa recommends deep search (`type="deep-reasoning"`) for new research flows. * Helper calls are deduplicated against the core SDK methods they invoke; activating overlapping provider instrumentations may still add their own lower-level model spans. > Trace Exa search, contents, grounded answers, streaming, tool helpers, and Agent runs with Respan.