Skip to main content

systemprompt_models/artifacts/chart/
mod.rs

1//! Chart artifact.
2//!
3//! A [`ChartArtifact`] carries labelled axes and one or more [`ChartDataset`]s
4//! for a given [`ChartType`]. Axis configuration and presentation hints are
5//! serialized into the emitted JSON schema; the artifact implements
6//! [`Artifact`].
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use crate::artifacts::metadata::ExecutionMetadata;
12use crate::artifacts::traits::Artifact;
13use crate::artifacts::types::{ArtifactType, AxisType, ChartType};
14use crate::execution::context::RequestContext;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17use serde_json::{Value as JsonValue, json};
18use systemprompt_identifiers::SkillId;
19
20fn default_artifact_type() -> String {
21    "chart".to_owned()
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25pub struct ChartDataset {
26    pub label: String,
27    pub data: Vec<f64>,
28}
29
30impl ChartDataset {
31    pub fn new(label: impl Into<String>, data: Vec<f64>) -> Self {
32        Self {
33            label: label.into(),
34            data,
35        }
36    }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40pub struct ChartArtifact {
41    #[serde(rename = "x-artifact-type")]
42    #[serde(default = "default_artifact_type")]
43    pub artifact_type: String,
44    pub labels: Vec<String>,
45    pub datasets: Vec<ChartDataset>,
46    #[serde(skip)]
47    #[schemars(skip)]
48    chart_type: ChartType,
49    #[serde(skip)]
50    #[schemars(skip)]
51    title: String,
52    #[serde(skip)]
53    #[schemars(skip)]
54    x_axis_label: String,
55    #[serde(skip)]
56    #[schemars(skip)]
57    y_axis_label: String,
58    #[serde(skip)]
59    #[schemars(skip)]
60    x_axis_type: AxisType,
61    #[serde(skip)]
62    #[schemars(skip)]
63    y_axis_type: AxisType,
64    #[serde(skip)]
65    #[schemars(skip)]
66    metadata: ExecutionMetadata,
67}
68
69impl ChartArtifact {
70    pub const ARTIFACT_TYPE_STR: &'static str = "chart";
71
72    pub fn new(title: impl Into<String>, chart_type: ChartType) -> Self {
73        Self {
74            artifact_type: "chart".to_owned(),
75            labels: Vec::new(),
76            datasets: Vec::new(),
77            chart_type,
78            title: title.into(),
79            x_axis_label: "X".to_owned(),
80            y_axis_label: "Y".to_owned(),
81            x_axis_type: AxisType::Category,
82            y_axis_type: AxisType::Linear,
83            metadata: ExecutionMetadata::default(),
84        }
85    }
86
87    pub fn with_request(mut self, ctx: &RequestContext) -> Self {
88        self.metadata = ExecutionMetadata::with_request(ctx);
89        self
90    }
91
92    pub fn with_x_axis_labels(mut self, labels: Vec<String>) -> Self {
93        self.labels = labels;
94        self
95    }
96
97    pub fn with_labels(self, labels: Vec<String>) -> Self {
98        self.with_x_axis_labels(labels)
99    }
100
101    pub fn with_datasets(mut self, datasets: Vec<ChartDataset>) -> Self {
102        self.datasets = datasets;
103        self
104    }
105
106    pub fn add_dataset(mut self, dataset: ChartDataset) -> Self {
107        self.datasets.push(dataset);
108        self
109    }
110
111    pub const fn with_x_axis_type(mut self, axis_type: AxisType) -> Self {
112        self.x_axis_type = axis_type;
113        self
114    }
115
116    pub const fn with_y_axis_type(mut self, axis_type: AxisType) -> Self {
117        self.y_axis_type = axis_type;
118        self
119    }
120
121    pub fn with_axes(mut self, x_label: impl Into<String>, y_label: impl Into<String>) -> Self {
122        self.x_axis_label = x_label.into();
123        self.y_axis_label = y_label.into();
124        self
125    }
126
127    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
128        self.metadata.execution_id = Some(id.into());
129        self
130    }
131
132    pub fn with_skill(
133        mut self,
134        skill_id: impl Into<SkillId>,
135        skill_name: impl Into<String>,
136    ) -> Self {
137        self.metadata.skill_id = Some(skill_id.into());
138        self.metadata.skill_name = Some(skill_name.into());
139        self
140    }
141}
142
143impl Artifact for ChartArtifact {
144    fn artifact_type(&self) -> ArtifactType {
145        ArtifactType::Chart
146    }
147
148    fn to_schema(&self) -> JsonValue {
149        json!({
150            "type": "object",
151            "properties": {
152                "labels": {
153                    "type": "array",
154                    "items": {"type": "string"}
155                },
156                "datasets": {
157                    "type": "array",
158                    "items": {
159                        "type": "object",
160                        "properties": {
161                            "label": {"type": "string"},
162                            "data": {"type": "array", "items": {"type": "number"}}
163                        }
164                    }
165                },
166                "_execution_id": {"type": "string"}
167            },
168            "required": ["labels", "datasets"],
169            "x-artifact-type": "chart",
170            "x-chart-hints": {
171                "chart_type": self.chart_type,
172                "title": self.title,
173                "x_axis": {
174                    "label": self.x_axis_label,
175                    "type": self.x_axis_type
176                },
177                "y_axis": {
178                    "label": self.y_axis_label,
179                    "type": self.y_axis_type
180                }
181            }
182        })
183    }
184}