openinference

Decision Spans

Decision spans capture calls to a decision model: a model that scores or selects among candidate options supplied in the request rather than generating free-form text. Examples include choosing a route, judging whether a condition holds, or scoring an item against a rubric. Decision models are not language models, so decision spans use a dedicated decision.* namespace for model identification instead of llm.*.

Background

A decision model takes unstructured or structured state plus a set of typed questions and returns a typed, probabilistic answer for each question. The set of possible answers is fixed in advance by the caller, so the model cannot produce malformed output, and every answer carries a calibrated probability or confidence score that software can act on directly. Decision models trade free-form generation for speed, cost, and predictability, which makes them a fit for “smart if-statements”: routing, classification, guardrails, scoring, extraction, and other branching decisions inside an application.

Representative decision model families:

OpenInference models these calls as DECISION spans rather than LLM spans because the semantics differ in ways that matter to observability tooling: there are no input or output messages, output tokens are often absent or free, model names and pricing live in a separate catalogue from language models, and the interesting output is a distribution over caller-supplied options rather than generated text. The design discussion is in Arize-ai/openinference#3808 and Arize-ai/openinference#3894.

Required Attributes

All decision spans MUST include:

Common Attributes

Decision spans typically include:

Model Identification

The decision.* identification attributes mirror their llm.* counterparts and follow the same rules:

Decision attribute LLM counterpart
decision.system llm.system
decision.provider llm.provider
decision.model_name llm.model_name
decision.request.model_name llm.request.model_name
decision.response.model_name llm.response.model_name

System versus Provider

decision.system and decision.provider answer two different questions, exactly as llm.system and llm.provider do for LLM spans:

Call decision.system decision.provider
TypeSafe SDK calling api.typesafe.ai system_one with Jev typesafe typesafe
Jev reached through a gateway that resells TypeSafe’s API unchanged typesafe the gateway’s provider value
OpenAI Decisions API openai openai
Self-hosted vLLM serving the Jev-compatible POST /v1/systemone shape typesafe the deployment’s provider value (for example vllm)
vLLM’s native POST /v1/decisions API custom value (no well-known value yet) the deployment’s provider value
vLLM Semantic Router Decision 1.0 models through the router’s own API custom value (no well-known value yet) the deployment’s provider value

When a well-known value exists for the ecosystem (typesafe, openai), it MUST be used. vLLM’s native decision API has no well-known value yet, so a custom value is used for it; one may be added once that API stabilizes.

Token Counts

Decision spans record token usage with decision.token_count.input and decision.token_count.output when the API reports it. Both are integers.

Decision spans SHOULD NOT reuse llm.token_count.*. Keeping decision usage in its own namespace lets consumers price decision models from their own catalogue (for example models.dev for Jev, where output tokens are free) instead of applying LLM pricing to them. The Transition Note below applies to instrumentations that still emit llm.token_count.* for decision models.

Attributes Not Used in Decision Spans

Decision spans SHOULD NOT set llm.system, llm.provider, llm.model_name, llm.request.model_name, llm.response.model_name, or the llm.token_count.* attributes. Those attributes identify language models; using them on decision spans conflates decision model usage with LLM usage in downstream analytics such as model-level cost and token reporting.

Transition Note

This section is not yet normative. Instrumentations written before the DECISION span kind and the decision.* attributes existed recorded decision model calls with llm.* attributes (llm.provider, the llm.*model_name attributes, llm.token_count.*, and in JavaScript llm.system). The OpenInference TypeSafe instrumentors for Python and JavaScript have migrated to decision.*, but spans from their earlier releases still carry the llm.* attributes. Consumers SHOULD accept both representations, and the SHOULD NOT above is guidance for new instrumentations rather than a conformance requirement for existing ones.

Context Attributes

Decision spans inherit the same context attributes as every other OpenInference span (session.id, user.id, metadata, tag.tags) when they are set via the instrumentation context API. See Configuration for details.

Example

A TypeSafe System One call that asks Jev one Noul (yes/no) question about a piece of state. The call goes directly to TypeSafe, so decision.system and decision.provider are both typesafe. The caller requested the jev-latest alias and the provider answered with jev-1.13.0, so both model attributes are set and decision.model_name carries the resolved version. The response’s usage block supplies the token counts.

openinference.span.kind = "DECISION"
decision.system = "typesafe"
decision.provider = "typesafe"
decision.request.model_name = "jev-latest"
decision.response.model_name = "jev-1.13.0"
decision.model_name = "jev-1.13.0"
decision.token_count.input = 412
decision.token_count.output = 2
input.mime_type = "application/json"
input.value = "{\"model\": \"jev-latest\", \"state\": \"The assistant replied: 'Per the 2024 audit (p. 12), revenue grew 8%.'\", \"questions\": {\"cites_source\": {\"type\": \"noul\", \"question\": \"Does the response cite a source?\"}}}"
output.mime_type = "application/json"
output.value = "{\"model\": \"jev-1.13.0\", \"answers\": {\"cites_source\": {\"answer\": true, \"probability\": 0.93}}, \"usage\": {\"input_tokens\": 412, \"output_tokens\": 2}}"

References