Skip to main content

traceAI-exa

OpenTelemetry instrumentation for the exa-py client.

This package wraps Exa client calls; it is not an Exa exporter. Exa does not publish an exporter.

Installation

pip install traceAI-exa

It accepts exa-py>=2.25.0,<3 and is tested with exa-py 2.25.0 on Python 3.10, 3.11, 3.12 and 3.13. The test suite also passes on exa-py 2.26.0.

Live vendor verification (2026-10-17): a real run against api.exa.ai with exa-py 2.25.0 (search, get_contents, answer, both streams, AsyncExa.search and an invalid-key 401 recorded as ERROR) exported the expected spans to Future AGI, and FI_HIDE_INPUTS=true kept the query out of every stored column.

Usage

from exa_py import Exa
from fi_instrumentation import register
from fi_instrumentation.fi_types import ProjectType
from traceai_exa import ExaInstrumentor

tracer_provider = register(project_name="exa-search", project_type=ProjectType.OBSERVE)
ExaInstrumentor().instrument(tracer_provider=tracer_provider)

client = Exa(api_key="your-exa-api-key")
client.search("recent retrieval research", num_results=5)

Instrumented calls

Exa and AsyncExa calls to search, get_contents, answer, and the deprecated search_and_contents alias (traced as exa.search) each produce one span. stream_search and stream_answer produce one span that stays open until the stream is fully read, closed, or garbage-collected; the returned object is still an instance of the vendor's stream class. A stream still open when the interpreter exits is ended as cancelled before register()'s exit hook shuts the exporter down (when instrument() runs after register()).

gen_ai.span.kind is RETRIEVER for search, answer and the two streams, and TOOL for get_contents, which fetches the pages you name rather than searching. Each span is a child of the active span when there is one, and a root span otherwise. The span is current while exa-py sends the request, so a span from an HTTP client instrumentation nests under it.

Span attributes

Attribute Calls Value
gen_ai.retrieval.query, input.value search, answer, streams The query, with the client's Exa API key replaced by [redacted], cut to 1 KB of UTF-8 on a character boundary.
exa.url_count get_contents Number of requested URLs (a single URL, a list of URLs, or a list of results).
exa.urls get_contents with capture_urls=True See get_contents URLs.
exa.result_count successful calls results for search and get_contents, citations for answer, and the citations summed over every chunk of a fully read stream. Absent when the count is unknown (an error or a cancellation), never 0 for unknown.
exa.cancelled cancelled calls and streams true; see Errors and cancellation.

The spans never carry response content: no result titles, text, highlights or summaries, no answer or stream text, no cost, token count or model name. There is no setting that adds them. The Exa API key is never recorded; it is replaced by [redacted] wherever it appears verbatim in a recorded query, URL or error text.

get_contents URLs

get_contents spans record the number of requested URLs, not the URLs, because URLs can carry tokens or personal data. To record the URLs as well, opt in when instrumenting:

ExaInstrumentor().instrument(tracer_provider=tracer_provider, capture_urls=True)

With capture_urls=True, exa.urls holds at most the first 20 requested URLs, each with the Exa API key replaced by [redacted] and cut to 1 KB. Query strings are otherwise kept as given. Hidden inputs (below) turn URL capture off.

Trace config and context

Spans are created through fi_instrumentation's FITracer, as in the other traceAI instrumentors. Pass a TraceConfig to instrument(), or set the matching FI_* environment variables; without config, the environment is read when instrument() runs.

from fi_instrumentation import TraceConfig

ExaInstrumentor().instrument(
    tracer_provider=tracer_provider, config=TraceConfig(hide_inputs=True)
)

With hide_inputs=True (or FI_HIDE_INPUTS=true), input.value is __REDACTED__, gen_ai.retrieval.query and exa.urls are not recorded, and the query or the requested URLs are replaced by __REDACTED__ in error text. The URL and result counts stay. pii_redaction applies to the recorded attributes and to the error text described below, and using_session, using_user, using_metadata and using_tags attributes are added to the Exa spans.

Errors and cancellation

An error raised by exa-py (for example ValueError for an HTTP 4xx/5xx response) sets the span status to ERROR and records an exception event. The exception is re-raised unchanged.

exa-py puts the server's response body in its error message, and a body can repeat the request. On the span, the status description (<ExceptionType>: <message>) and the event's exception.message therefore have the Exa key replaced by [redacted] (and, with hidden inputs, the query or the requested URLs by __REDACTED__; with pii_redaction, PII by tokens such as <EMAIL_ADDRESS>) and the message cut to 1 KB of UTF-8. If the exception's own str() fails, the span records only its type. The event carries exception.type and exception.message only: no exception.stacktrace is exported, because frames hold local file paths, user names and host names. Chained exceptions' messages are not copied. Only verbatim copies are found: an escaped, normalised or truncated echo of the key, query or URLs (for example a JSON-escaped \/ or a percent-encoded URL) is not.

Closing a stream before its last chunk (close() or aclose()), dropping it mid-iteration, or cancelling an AsyncExa call or stream ends the span with status ERROR, description cancelled, and exa.cancelled = true.

A traced AsyncExa stream always has aclose(), which ends the span and closes the HTTP response. exa-py's own async streams have aclose() from 2.26.0 only; 2.25.0's have none. On both versions their close() raises RuntimeError (traced or not; the span is ended first). Code that must also run on exa-py 2.25.0 without this instrumentor can call aclose() only when it exists, for example aclose = getattr(stream, "aclose", None).

A missing API key raises ValueError in Exa() or AsyncExa() itself, before any traced method runs, so no span is recorded for it.

find_similar and find_similar_and_contents are not traced.

Do not use this package together with another instrumentor that wraps the same Exa client methods, or duplicate spans may result.

Metadata

Release files for traceAI-exa 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for traceAI-exa 0.1.0
File Size Uploaded Provenance Actions
traceai_exa-0.1.0.tar.gz 12.7 kB Without provenance Details for traceai_exa-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for traceAI-exa 0.1.0
File Interpreter ABI Platform Actions
traceai_exa-0.1.0-py3-none-any.whl Python 3 none any Details for traceai_exa-0.1.0-py3-none-any.whl

Total release size: 24.7 kB

Release files / traceai_exa-0.1.0.tar.gz

Download URL traceai_exa-0.1.0.tar.gz
Size 12.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5f9049a86d0056fddf4c5b84fb01c61214ba70c1d811d12fc41653d5fb0c7058
BLAKE2b-256 checksum
How to use checksums
c8c7799b9ca33aa5b9c80879667be8c53e6e674783f66cd33ce46ca9e656426a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / traceai_exa-0.1.0-py3-none-any.whl

Download URL traceai_exa-0.1.0-py3-none-any.whl
Size 12.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7fe2b21b1f91ab6d886c74a535fa445f687c4b39a421850c48fc6559b142d382
BLAKE2b-256 checksum
How to use checksums
9618d806fdc8fd59d9fdafbad3bf1ed45f0c5658c597908adcce1f443f0def66
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for traceAI-exa 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page