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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.