switchyard_protocol/decision.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Provider-neutral decision questions and answers, separate from LLM messages.
5//!
6//! Enums use snake-case `type` tags and a `data` payload in serialized form.
7//! Fields are public; providers and callers are responsible for valid values.
8
9use std::collections::BTreeMap;
10
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14use crate::{ModelId, Usage};
15
16/// Answer probability on a `[0, 1]` scale.
17#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct Probability(pub f64);
20
21/// Position in the request's rubric, including fractional positions.
22#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
23#[serde(transparent)]
24pub struct ScoreValue(pub f64);
25
26/// Provider confidence; its scale and meaning are provider-specific.
27#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct ProviderConfidence(pub f64);
30
31/// Shared input evaluated against independent, named questions.
32#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33pub struct DecisionRequest {
34 /// Optional until a target is selected.
35 pub model: Option<ModelId>,
36 /// Conversation, application state, or other material to evaluate.
37 /// Sent as `state` (System One) or `input` (OpenAI Decisions).
38 pub input: Value,
39 /// Independent questions keyed by their IDs.
40 pub questions: BTreeMap<String, DecisionQuestion>,
41}
42
43/// Instructions and the expected answer shape for one question.
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45pub struct DecisionQuestion {
46 /// Structured or textual instructions shared with the provider.
47 pub instructions: Value,
48 /// Expected answer shape.
49 pub kind: DecisionKind,
50}
51
52/// The answer shape and any options or ordered rubric levels.
53#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
54#[serde(tag = "type", content = "data", rename_all = "snake_case")]
55pub enum DecisionKind {
56 /// A Boolean judgment or probability of true.
57 Boolean {
58 /// Meaning of a true answer, when needed.
59 true_description: Option<Value>,
60 /// Meaning of a false answer, when needed.
61 false_description: Option<Value>,
62 },
63 /// Select one of the declared options.
64 Choice {
65 /// Nonempty options with unique IDs, preserving caller order.
66 options: Vec<ChoiceOption>,
67 },
68 /// A position on an ordered rubric, not an arbitrary numeric measurement.
69 Score {
70 /// At least two levels, ordered low to high and indexed from zero.
71 levels: Vec<Value>,
72 },
73}
74
75/// An identified choice with an optional structured description.
76#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
77pub struct ChoiceOption {
78 /// Stable identifier used by choice answers and distributions.
79 /// System One uses it as the `criteria` key; OpenAI Decisions uses `value`.
80 pub id: String,
81 /// Meaning of this option, when its ID alone is insufficient.
82 /// System One uses it as the `criteria` value; OpenAI Decisions uses `description`.
83 pub description: Option<Value>,
84}
85
86/// Answers keyed by the matching request's question IDs.
87#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
88pub struct DecisionResponse {
89 /// Provider-reported response identifier.
90 pub id: Option<String>,
91 /// Provider-reported model identifier.
92 pub model: Option<ModelId>,
93 /// Typed answers corresponding to the request's questions.
94 pub answers: BTreeMap<String, DecisionAnswer>,
95 /// Available token counts; absent counts remain unknown.
96 #[serde(default)]
97 pub usage: Usage,
98}
99
100/// An answer and separate, optional provider confidence.
101#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
102pub struct DecisionAnswer {
103 /// The estimate for the matching question.
104 pub value: DecisionValue,
105 /// Provider-specific confidence, distinct from answer probabilities.
106 pub provider_confidence: Option<ProviderConfidence>,
107}
108
109/// A typed estimate; missing distributions remain unknown.
110#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
111#[serde(tag = "type", content = "data", rename_all = "snake_case")]
112pub enum DecisionValue {
113 /// A Boolean judgment or probability, without an implicit threshold.
114 Boolean(BooleanEstimate),
115 /// One selected option with an optional complete distribution.
116 Choice {
117 /// Must name an option in the matching question.
118 selected: String,
119 /// Maps every declared option ID to its probability when available.
120 probabilities: Option<BTreeMap<String, Probability>>,
121 },
122 /// A fractional position in the matching request's rubric.
123 Score {
124 /// Must lie in `0..=N-1` for the request's N levels.
125 value: ScoreValue,
126 /// Follows the request's level order. Retain that request to interpret it.
127 probabilities: Option<Vec<Probability>>,
128 },
129}
130
131/// Preserves Boolean-only answers without inventing probability or certainty.
132#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
133#[serde(tag = "type", content = "data", rename_all = "snake_case")]
134pub enum BooleanEstimate {
135 /// A Boolean judgment with no probability supplied.
136 Value(bool),
137 /// Probability of true; algorithms choose their own thresholds.
138 ProbabilityTrue(Probability),
139}