alye (Python)
Records every LLM call your services make: model, tokens, latency, time to first token, errors and retries. You add two lines at startup; calls through the official OpenAI, Anthropic and Google GenAI clients are recorded automatically.
pip install alye
Set your API key as an environment variable (keep it out of your code):
export ALYE_API_KEY=alye_live_... # from Setup in Alye
import alye
alye.init(
service="support-reply", # one name per microservice
env="production",
)
That is all most services need. Calls are sent to Alye at https://api.alye.ai/ingest; set ALYE_ENDPOINT only if you run your own server. Without an API key the SDK just prints calls to the console. Python 3.10+, no required dependencies.
What gets recorded
One span per model call, using the OpenTelemetry GenAI attribute names:
| Attribute | Meaning |
|---|---|
gen_ai.provider.name |
openai, anthropic, gcp.gemini, gcp.vertex_ai, azure.ai.openai, deepseek, ... |
gen_ai.operation.name |
chat, responses, embeddings, generate_content |
gen_ai.request.model / gen_ai.response.model |
Model asked for / model that answered |
gen_ai.usage.input_tokens / output_tokens |
Tokens, including cached input and reasoning output |
gen_ai.usage.cache_read.input_tokens, cache_creation.input_tokens, reasoning.output_tokens |
Token breakdown when the provider reports it |
gen_ai.response.finish_reasons, gen_ai.response.id |
Why the model stopped |
gen_ai.response.time_to_first_chunk |
Seconds to first streamed chunk |
error.type |
rate_limited, timeout, overloaded, server_error, auth, invalid_request, connection_error, ... |
alye.retries, alye.retry_statuses |
Retries the provider SDK made inside one call (e.g. after a 429) |
alye.usage_unknown |
The call returned but the provider sent no usage; it may still have been billed |
alye.customer_id, session.id, gen_ai.agent.name, alye.data_source |
From alye.context() |
gen_ai.input.messages / gen_ai.output.messages |
Prompt and response, only with capture_content=True |
Failed calls are recorded even if your code catches the exception. Cost is worked out on the server from a versioned price list, so prices are never stale in your deployed code.
Supported automatically:
- openai:
chat.completions.create,responses.create,embeddings.create; sync and async;stream=True;.with_raw_response. Also OpenAI-compatible providers used through the same client. - anthropic:
messages.create(sync, async,stream=True) and themessages.stream()helper. - google-genai:
models.generate_content,generate_content_stream,embed_content; Gemini API and Vertex AI; sync and async.
Tag calls (optional)
with alye.context(customer_id="northwind", session_id=ticket.id, agent="reply-agent"):
answer = run_agent(ticket) # every call inside is tagged
Tags flow through await. For new threads, start them with contextvars.copy_context().run(...). In middleware you can use alye.set_tags(...) instead of a with block. Customer tagging is only needed if your app serves several customers; without it, everything is grouped by service.
Group steps into one trace (optional)
@alye.trace() # or alye.trace("answer_ticket")
def answer_ticket(ticket): ...
with alye.span("retrieve_context", data_source="support-kb"):
docs = search(query)
Model calls made inside become children of the span, so one agent run shows as a single trace with its steps.
Providers that are not patched
with alye.llm_call("voyage", "voyage-3", operation="embeddings") as call:
res = vo.embed(texts, model="voyage-3")
call.set_usage(input_tokens=res.total_tokens, output_tokens=0)
Settings
Every option can be passed to init() or set as an environment variable.
init() argument |
Environment variable | Default |
|---|---|---|
api_key |
ALYE_API_KEY |
none |
service |
ALYE_SERVICE or OTEL_SERVICE_NAME |
unknown_service (with a warning) |
env |
ALYE_ENV |
production |
endpoint |
ALYE_ENDPOINT |
https://api.alye.ai/ingest when an API key is set; without a key, calls are printed to the console |
region |
ALYE_REGION |
none: where calls are processed, for regional pricing (eu, us, or a region like europe-west4); Bedrock and Vertex regions are detected automatically |
capture_content |
ALYE_CAPTURE_CONTENT |
False |
redact_pii |
ALYE_REDACT_PII |
True |
sample_rate |
ALYE_SAMPLE_RATE |
1.0 (errors are always kept) |
exporter |
ALYE_EXPORTER |
otlp (console, none, or an exporter object) |
instrument |
every installed provider (e.g. ["openai"]) |
|
debug |
ALYE_DEBUG |
False |
disabled |
ALYE_DISABLED |
False |
release |
ALYE_RELEASE |
none |
Other options: redact_patterns=[(regex, replacement)], redact=callable, max_content_chars (8000), inject_stream_usage (True: asks OpenAI streams to include usage), flush_interval (2 s), max_batch (256), max_queue (10,000), timeout (5 s), heartbeat_interval (60 s), resource_attributes.
Privacy
- Prompts and responses are not sent unless you set
capture_content=True. - When they are, emails, phone numbers, card numbers (Luhn-checked) and API keys are replaced with
[EMAIL],[PHONE],[CARD],[SECRET]before anything leaves the process. Add your own patterns withredact_patterns. - Content is truncated to
max_content_chars.
How it sends data
- Spans are queued in memory and sent in batches from a background thread, so your request path never waits on the network. If the queue fills up, new spans are dropped and counted rather than slowing you down.
- Format: OTLP/HTTP JSON, gzip, to
{endpoint}/v1/traceswith anx-api-keyheader. Retries 429 and 5xx with backoff. - A heartbeat goes to
{endpoint}/v1/heartbeatevery 60 s with the SDK version, patched libraries and queue stats, so the backend can tell a quiet service from one that stopped reporting. - Works with forking servers (gunicorn, uWSGI): workers restart their own sender after fork.
- In short-lived scripts, jobs and serverless handlers, call
alye.flush()before exiting.atexitalso flushes on normal exit.
Local development
python -m alye.devserver # prints every call it receives
ALYE_ENDPOINT=http://localhost:4318 python your_service.py
Without an API key (and no endpoint) the SDK prints one line per call to stderr, so nothing leaves your laptop.
Tests
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
The tests run the real openai, anthropic and google-genai clients against fake HTTP servers, so no API keys or network are needed.
License
Apache License 2.0. Copyright Onruyl Private Limited. See LICENSE.
Metadata
Release files for alye 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 | |
|---|---|---|---|
| alye-0.1.0.tar.gz | 37.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| alye-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.0 kB
Release files / alye-0.1.0.tar.gz
| Download URL | alye-0.1.0.tar.gz |
|---|---|
| Size | 37.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bf8da7dcdb4501f87f1634a7faf4bf930e04cc67d86ab3dc90e9049194dd6ab2
|
|
BLAKE2b-256 checksum How to use checksums |
f1359ec29a426b9a827efa4335640bd86ccf8dc4a128d58422e0b0448b3e6264
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Release files / alye-0.1.0-py3-none-any.whl
| Download URL | alye-0.1.0-py3-none-any.whl |
|---|---|
| Size | 39.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a822fe0178d16d51eace5fdbfc04635d97deb26e29a0dcd21894fc9cfa9c11a6
|
|
BLAKE2b-256 checksum How to use checksums |
499620d142e0284626b8ccfd178517aa608301415bdd8e0789d72f1085189bd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|