> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://respan.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/docs/_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.