Skip to content

keelgate.telemetry

Stability of each symbol (stable or provisional) is listed in the API contract. Narrative and semantics are in the integration contract.

OpenTelemetry tracing, cost tracking and PII redaction for Keelgate.

One trace per loop run; GenAI-convention spans for model calls and tools; Keelgate spans for policy decisions, approvals and memory. Nothing is exported until instrument() is called.

CostTotals dataclass

Calls, tokens and dollars for one tenant/agent selection. Unpriced calls add tokens but no dollars.

CostTracker

Per-tenant and per-agent token and dollar totals, also emitted as OpenTelemetry metrics.

Never invents a price.

total

total(
    *,
    tenant_id: str | None = None,
    agent_id: str | None = None,
) -> CostTotals

Totals, optionally narrowed to one tenant and/or agent.

InstrumentedLLM

An LLMClient that records a span and cost for every complete call.

Cost is attributed to the tenant and agent of the run bound by the loop (see keelgate.telemetry.bind_run); outside a run it is recorded as unattributed.

RedactingSpanProcessor

Bases: SpanProcessor

Wrap delegate so that it only ever receives redacted spans.

Redactor dataclass

Rewrites text and attribute values. drop_keys removes attributes outright.

RunContext dataclass

The tenant, agent and run that spans and costs created inside a block are attributed to.

RunSpan dataclass

The root span of a loop run, with the ids that identify the run's trace.

Telemetry

A tracer, a meter, a cost tracker, and the content-capture switch.

activate

activate(telemetry: Telemetry | None) -> None

Make telemetry the active handle for the process (None restores the default).

active

active() -> Telemetry

The currently active Telemetry handle.

bind_run

bind_run(
    tenant_id: str, agent_id: str, run_id: str
) -> Iterator[RunContext]

Attribute spans, metrics and costs created inside the block to this run.

current_run

current_run() -> RunContext | None

The RunContext bound by the surrounding loop, or None outside a run.

instrument

instrument(
    *,
    service_name: str | None = None,
    endpoint: str | None = None,
    headers: Mapping[str, str] | None = None,
    exporter: SpanExporter | None = None,
    redact: bool | Redactor = False,
    capture_content: bool | None = None,
    batch: bool = True,
    resource_attributes: Mapping[str, Any] | None = None,
    meter_provider: MeterProvider | None = None,
    set_global: bool = False,
) -> Telemetry

Configure tracing and make it the active telemetry for Keelgate.

  • exporter: any OpenTelemetry span exporter. Default: OTLP/HTTP to OTEL_EXPORTER_OTLP_ENDPOINT (or http://localhost:4318, which is Jaeger from make up). Needs keelgate[otlp].
  • redact: True for the default PII patterns, or a :class:Redactor. Spans are redacted before they reach the exporter.
  • capture_content: record prompts and responses on LLM spans. Off by default (they are untrusted and may be sensitive); OTEL_GENAI_CAPTURE_MESSAGE_CONTENT=true turns it on.
  • set_global: also install the provider as OpenTelemetry's global tracer provider. Leave it off to keep Keelgate's spans separate from the application's own.

Returns the :class:Telemetry; call shutdown() on it at exit to flush pending spans.

redact_span

redact_span(
    span: ReadableSpan, redactor: Redactor
) -> ReadableSpan

A copy of a finished span with attribute values, event attributes and the name redacted.

run_span

run_span(
    *,
    tenant_id: str,
    agent_id: str,
    run_id: str,
    loop_type: str,
    trace_id: str | None = None,
    root_span_id: str | None = None,
    resumed: bool = False,
    extra: Mapping[str, Any] | None = None,
) -> Iterator[RunSpan]

The root span for one loop run, bound so everything inside is attributed to the run.

A new run starts a new trace. A resumed run (possibly in another process, days later) is parented on the original root span, so the whole life of the run is one trace. Passing a trace_id to a new run joins that trace instead.

set_attributes

set_attributes(
    span_: Span, attributes: Mapping[str, Any]
) -> None

Set several span attributes at once, dropping None values and truncating long strings.

span

span(
    name: str,
    *,
    kind: SpanKind = SpanKind.INTERNAL,
    attributes: Mapping[str, Any] | None = None,
    context: Context | None = None,
) -> Iterator[Span]

A span that is current inside the block, attributed to the bound run.

An exception marks the span as an error with only its type: exception messages can carry argument or output text, so they are never recorded.

traced

traced(
    name: str,
    *,
    pre: Callable[[Mapping[str, Any]], Mapping[str, Any]]
    | None = None,
    post: Callable[[Any], Mapping[str, Any]] | None = None,
) -> Callable[[Callable[P, R]], Callable[P, R]]

Run a synchronous method inside a span.

pre receives the bound call arguments (self included) and returns the attributes to start the span with; post receives the result and returns attributes to add. Neither is handed anything that should be recorded verbatim: choose attributes deliberately.

use

use(telemetry: Telemetry) -> Iterator[Telemetry]

Temporarily activate telemetry (tests, and short scripts).