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