Recipe: AI Telemetry
This recipe shows how to wire the ElectriPy AI Telemetry component into an application that uses HTTP resilience and the LLM Gateway.
When to use this: You need correlation IDs, structured telemetry events, and exportable traces for HTTP retries, LLM calls, and policy decisions — without mandating a specific observability backend.
Scenario
You want to:
- Attach correlation IDs to incoming work.
- Emit structured telemetry for HTTP retries, LLM calls, and policy decisions.
- Export telemetry either to JSONL or to OpenTelemetry.
JSONL sink example
from pathlib import Path
from electripy.observability.ai_telemetry import (
JsonlTelemetrySinkAdapter,
create_telemetry_context,
inject_context_headers,
scoped_telemetry_context,
)
from electripy.observability.ai_telemetry.services import (
record_http_retry_attempt,
record_llm_call,
record_policy_decision,
)
telemetry = JsonlTelemetrySinkAdapter(path=Path("telemetry.jsonl"))
ctx = create_telemetry_context(environment="prod")
with scoped_telemetry_context(ctx):
# Inject correlation headers into an outbound HTTP request
headers: dict[str, str] = {}
inject_context_headers(headers)
# Record an HTTP retry attempt
record_http_retry_attempt(
telemetry,
attempt=1,
max_attempts=3,
url="https://api.example.com/resource",
status_code=503,
ctx=ctx,
)
# After an LLM call completes
record_llm_call(
telemetry,
provider="openai",
model="gpt-4.1",
latency_ms=120.0,
input_tokens=1000,
output_tokens=256,
finish_reason="stop",
structured_output_valid=True,
ctx=ctx,
)
# Record a policy decision
record_policy_decision(
telemetry,
decision="allow",
violation_codes=[],
redactions_applied=False,
ctx=ctx,
)
All sensitive fields (for example prompt, response) are hashed by
adapter-level sanitisation, so raw content is never written to disk.
Using AI Telemetry with the LLM Gateway hook
Instead of calling record_llm_call manually after every gateway usage,
you can attach a hook to the LLM Gateway so that every successful call
is automatically exported to your telemetry sink.
from pathlib import Path
from electripy.ai.llm_gateway import (
LlmGatewaySettings,
LlmMessage,
LlmRequest,
build_llm_sync_client,
)
from electripy.observability.ai_telemetry import JsonlTelemetrySinkAdapter
from electripy.observability.ai_telemetry.services import record_llm_call
telemetry = JsonlTelemetrySinkAdapter(path=Path("telemetry.jsonl"))
def llm_telemetry_hook(request: LlmRequest, response: "LlmResponse", latency_ms: float) -> None:
# Approximate tokens if the provider does not report usage.
input_tokens = len(" ".join(m.content for m in request.messages)) // 4
total = response.usage_total_tokens or 0
output_tokens = max(total - input_tokens, 0)
record_llm_call(
telemetry,
provider="openai",
model=response.model or request.model,
latency_ms=latency_ms,
input_tokens=input_tokens,
output_tokens=output_tokens,
finish_reason=response.finish_reason or "unknown",
structured_output_valid=bool(response.raw_json),
)
settings = LlmGatewaySettings(on_llm_call=llm_telemetry_hook)
client = build_llm_sync_client("openai", settings=settings)
response = client.complete(
LlmRequest(
model="gpt-4o-mini",
messages=[LlmMessage.user("Hello with telemetry!")],
)
)
This pattern keeps the gateway decoupled from any specific telemetry backend while still giving you end-to-end latency and token telemetry for every call.
OpenTelemetry example
from electripy.observability.ai_telemetry import OpenTelemetryAdapter
from electripy.observability.ai_telemetry.services import record_llm_call
telemetry = OpenTelemetryAdapter(service_name="example-service")
# Inside a traced operation
record_llm_call(
telemetry,
provider="openai",
model="gpt-4.1",
latency_ms=80.0,
input_tokens=800,
output_tokens=200,
finish_reason="stop",
structured_output_valid=True,
)
The adapter bridges to the active OpenTelemetry span, allowing you to export events and metrics to your existing observability backend.
Cost estimation
To estimate cost per call using a simple rate table:
from electripy.observability.ai_telemetry import TableCostEstimator
estimator = TableCostEstimator(rates={("openai", "gpt-4.1"): (0.01, 0.03)})
cost = estimator.estimate_cost(
provider="openai",
model="gpt-4.1",
input_tokens=1000,
output_tokens=500,
)
print(cost.estimated_cost_usd)
Missing entries return estimated_cost_usd = None so that cost
estimation never breaks critical paths.