Skip to main content

objectiveai_sdk/vector/completions/response/unary/
vector_completion.rs

1//! Unary (non-streaming) vector completion response.
2
3use crate::{agent, vector::completions::response};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7/// A complete vector completion response (non-streaming).
8///
9/// Contains the final scores, all votes from the swarm, and the underlying
10/// agent completions that produced those votes.
11#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
12#[schemars(rename = "vector.completions.response.unary.VectorCompletion")]
13pub struct VectorCompletion {
14    /// Unique identifier for this vector completion.
15    pub id: String,
16    /// The underlying agent completions from each agent in the swarm.
17    pub completions: Vec<super::AgentCompletion>,
18    /// Individual votes from each agent, showing their selections.
19    pub votes: Vec<response::Vote>,
20    /// Final weighted scores for each response option. Sums to 1.
21    #[serde(deserialize_with = "crate::serde_util::vec_decimal")]
22    #[schemars(with = "Vec<f64>")]
23    pub scores: Vec<rust_decimal::Decimal>,
24    /// Total weight allocated to each response option. Same length as `scores`.
25    /// For discrete votes, an LLM's full weight goes to its selected response.
26    /// For probabilistic votes, the weight is divided according to the distribution.
27    #[serde(deserialize_with = "crate::serde_util::vec_decimal")]
28    #[schemars(with = "Vec<f64>")]
29    pub weights: Vec<rust_decimal::Decimal>,
30    /// Unix timestamp when the completion was created.
31    pub created: u64,
32    /// ID of the swarm used for this completion.
33    pub swarm: String,
34    /// Object type identifier (`"vector.completion"`).
35    pub object: super::Object,
36    /// Aggregated token and cost usage across all completions.
37    pub usage: agent::completions::response::Usage,
38}
39
40impl VectorCompletion {
41    /// Normalize non-deterministic fields for test snapshot comparison.
42    pub fn normalize_for_tests(&mut self) {
43        self.id = String::new();
44        self.created = 0;
45        self.usage.normalize_for_tests();
46        for completion in &mut self.completions {
47            completion.inner.normalize_for_tests();
48        }
49        self.votes.sort_by_key(|v| v.flat_swarm_index);
50
51        // sort completions by JSON representation since ordering is non-determinstic
52        self.completions
53            .sort_by_cached_key(|c| serde_json::to_string(&c.inner).unwrap());
54
55        // re-apply completion indices since indices are non-deterministic
56        // (first-come-first-served by design) — and REMAP the votes'
57        // `completion_index` through the same renumbering, or the
58        // votes keep pointing at the arrival-ordered indices and leak
59        // the non-determinism into normalized snapshots.
60        let mut index_map = std::collections::HashMap::new();
61        let mut i = 0;
62        for completion in &mut self.completions {
63            index_map.insert(completion.index, i);
64            completion.index = i;
65            i += 1;
66        }
67        for vote in &mut self.votes {
68            if let Some(completion_index) = vote.completion_index {
69                // A dangling reference (no matching completion) is
70                // left as-is rather than silently rewritten.
71                if let Some(new_index) = index_map.get(&completion_index) {
72                    vote.completion_index = Some(*new_index);
73                }
74            }
75        }
76    }
77
78    /// Creates a default completion with uniform scores for the given number of responses.
79    pub fn default_from_request_responses_len(
80        request_responses_len: usize,
81    ) -> Self {
82        let weights = vec![rust_decimal::Decimal::ZERO; request_responses_len];
83        let scores =
84            vec![
85                rust_decimal::Decimal::ONE
86                    / rust_decimal::Decimal::from(request_responses_len);
87                request_responses_len
88            ];
89        Self {
90            id: String::new(),
91            completions: Vec::new(),
92            votes: Vec::new(),
93            scores,
94            weights,
95            created: 0,
96            swarm: String::new(),
97            object: super::Object::default(),
98            usage: agent::completions::response::Usage::default(),
99        }
100    }
101}
102
103impl From<response::streaming::VectorCompletionChunk> for VectorCompletion {
104    fn from(
105        response::streaming::VectorCompletionChunk {
106            id,
107            completions,
108            votes,
109            scores,
110            weights,
111            created,
112            swarm,
113            object,
114            usage,
115        }: response::streaming::VectorCompletionChunk,
116    ) -> Self {
117        Self {
118            id,
119            completions: completions
120                .into_iter()
121                .map(super::AgentCompletion::from)
122                .collect(),
123            votes,
124            scores,
125            weights,
126            created,
127            swarm,
128            object: object.into(),
129            usage: usage.unwrap_or_default(),
130        }
131    }
132}