Skip to main content

systemprompt_models/brain/
candidate.rs

1//! A fact an extractor proposes, before validation, dedupe and routing.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use systemprompt_identifiers::{FactKindId, TopicId};
9
10use super::scope::FactScope;
11use super::source::FactSource;
12use super::subject::SubjectKey;
13
14/// An unvalidated fact. `scope` is `None` when routing decides it.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
16pub struct FactCandidate {
17    pub kind: FactKindId,
18    pub subject: SubjectKey,
19    // JSON: candidate fields arrive from extractor output and are checked against the kind.
20    pub fields: serde_json::Map<String, serde_json::Value>,
21    pub source: FactSource,
22    pub topics: Vec<TopicId>,
23    pub scope: Option<FactScope>,
24    pub extractor: String,
25    pub confidence: Option<f32>,
26}
27
28impl FactCandidate {
29    #[must_use]
30    pub fn builder(
31        kind: FactKindId,
32        subject: SubjectKey,
33        source: FactSource,
34    ) -> FactCandidateBuilder {
35        FactCandidateBuilder {
36            candidate: Self {
37                kind,
38                subject,
39                fields: serde_json::Map::new(),
40                source,
41                topics: Vec::new(),
42                scope: None,
43                extractor: String::new(),
44                confidence: None,
45            },
46        }
47    }
48}
49
50/// Builds a [`FactCandidate`] from its kind, subject and source.
51#[derive(Debug, Clone)]
52pub struct FactCandidateBuilder {
53    candidate: FactCandidate,
54}
55
56impl FactCandidateBuilder {
57    #[must_use]
58    // JSON: candidate field values are free-form until validated against the kind.
59    pub fn field(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
60        self.candidate.fields.insert(name.into(), value);
61        self
62    }
63
64    #[must_use]
65    // JSON: candidate fields arrive from extractor output and are checked against
66    // the kind.
67    pub fn fields(mut self, fields: serde_json::Map<String, serde_json::Value>) -> Self {
68        self.candidate.fields = fields;
69        self
70    }
71
72    #[must_use]
73    pub fn topic(mut self, topic: TopicId) -> Self {
74        self.candidate.topics.push(topic);
75        self
76    }
77
78    #[must_use]
79    pub fn scope(mut self, scope: FactScope) -> Self {
80        self.candidate.scope = Some(scope);
81        self
82    }
83
84    #[must_use]
85    pub fn extractor(mut self, extractor: impl Into<String>) -> Self {
86        self.candidate.extractor = extractor.into();
87        self
88    }
89
90    #[must_use]
91    pub const fn confidence(mut self, confidence: f32) -> Self {
92        self.candidate.confidence = Some(confidence);
93        self
94    }
95
96    #[must_use]
97    pub fn build(self) -> FactCandidate {
98        self.candidate
99    }
100}