Skip to content

solvi.otel

Traces as OpenTelemetry spans: through the OpenTelemetry API or as OTLP/JSON.

Traces as OpenTelemetry spans: one decision → a root span "solvi.decision" with one child span per step of the trace (and one per answer), so solvi decisions show up next to the rest of a service's traces (Jaeger, Tempo, Honeycomb, …).

export(res_or_store, tracer=None, **filters)   # through the OpenTelemetry API (the `otel` extra: opentelemetry-api/sdk)
to_otlp_json(res_or_store, **filters)          # OTLP/JSON (ExportTraceServiceRequest) as a dict, without OpenTelemetry

Attributes of a step span (only those that apply): solvi.step, solvi.kind, solvi.fact, solvi.provenance, solvi.value (a short repr), solvi.confidence, solvi.error, solvi.producer, solvi.tried, solvi.quote.source / .start / .end, solvi.model.type / .id / .fingerprint, solvi.probs (JSON), solvi.safeguard (the kinds that fired on this fact), solvi.inputs (the facts it read), solvi.hash and solvi.prev (the hash chain). An answer span: solvi.question, solvi.answer, solvi.status, solvi.confidence, solvi.why, solvi.guard, solvi.provenance, solvi.source, solvi.safeguard. The root: solvi.questions, solvi.trace.init_hash / .head, solvi.catalog.fingerprint, solvi.stored_id, solvi.ms, and an event per step skipped at run time.

A step that failed or whose output was rejected has status ERROR with the reason; an abstention is not an error (its span says status abstain and the guard). Times: a trace records each step's run time, not its start, so the step spans are laid end to end from the decision's start — their durations are measured, their start times are not (and parallel steps are shown one after another). A stored decision starts at its stored time minus its run time; a fresh response ends when it is exported, unless end_ns= is given. In the OTLP JSON the ids are derived from the trace's hashes and the stored id (the same stored decision exports the same ids); an unstored response adds a random nonce of its own (kept on the response), so two identical unstored decisions never share ids; through the API the SDK assigns them.

spans

spans(res, end_ns=None, stored_time=None)

One decision as neutral span dicts (the root first): {"name", "span_id", "parent", "trace_id", "start", "end", "attributes", "status": None or ("ERROR", message), "events": [(name, time, attributes)]}.

export

export(res_or_store, tracer=None, end_ns=None, **filters)

Export decisions as OpenTelemetry spans: a Response, a TraceStorage (every stored decision, or those matching the query filters) or a list of them. tracer: an opentelemetry Tracer (default: trace.get_tracer("solvi") of the global provider). Each root span is a child of the span current in the caller's context, if any. → the root spans. Needs the otel extra (pip install "solvi[otel]"); without it, use to_otlp_json.

to_otlp_json

to_otlp_json(res_or_store, service_name='solvi', end_ns=None, **filters)

Decisions as OTLP/JSON — the body of an ExportTraceServiceRequest (POST it to a collector's /v1/traces with Content-Type application/json) — as a dict; no OpenTelemetry package needed. The same spans as export(); trace and span ids are derived from the traces' hashes.