Skip to main content

lc_callbacks/
run_tree.rs

1// lc-callbacks/src/run_tree.rs
2//! Run tree data structure for tracing
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9use super::RunType;
10
11/// Run tree node for tracing
12///
13/// Each run is a node in a tree, with optional parent and children.
14/// The entire trace forms a tree structure, with the root being the top-level call.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct RunTree {
17    /// Unique run ID (UUID v7 with timestamp)
18    pub id: Uuid,
19
20    /// Run name
21    pub name: String,
22
23    /// Run type
24    pub run_type: RunType,
25
26    /// Input data
27    pub inputs: serde_json::Value,
28
29    /// Output data (set when run ends)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub outputs: Option<serde_json::Value>,
32
33    /// Error message (if run failed)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub error: Option<String>,
36
37    /// Parent run ID
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub parent_run_id: Option<Uuid>,
40
41    /// Trace ID (ID of the root run)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub trace_id: Option<Uuid>,
44
45    /// Start time
46    pub start_time: DateTime<Utc>,
47
48    /// End time
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub end_time: Option<DateTime<Utc>>,
51
52    /// Metadata
53    #[serde(default)]
54    pub metadata: HashMap<String, serde_json::Value>,
55
56    /// Tags
57    #[serde(default)]
58    pub tags: Vec<String>,
59
60    /// Project name (LangSmith project)
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub project_name: Option<String>,
63
64    /// Serialized representation of the component
65    #[serde(default)]
66    pub serialized: serde_json::Value,
67}
68
69impl RunTree {
70    /// Create a new run
71    pub fn new(name: impl Into<String>, run_type: RunType, inputs: serde_json::Value) -> Self {
72        Self {
73            id: Uuid::now_v7(),
74            name: name.into(),
75            run_type,
76            inputs,
77            outputs: None,
78            error: None,
79            parent_run_id: None,
80            trace_id: None,
81            start_time: Utc::now(),
82            end_time: None,
83            metadata: HashMap::new(),
84            tags: Vec::new(),
85            project_name: None,
86            serialized: serde_json::Value::Null,
87        }
88    }
89
90    /// Create a child run from this run
91    pub fn create_child(
92        &self,
93        name: impl Into<String>,
94        run_type: RunType,
95        inputs: serde_json::Value,
96    ) -> Self {
97        let mut child = Self::new(name, run_type, inputs);
98        child.parent_run_id = Some(self.id);
99        child.trace_id = self.trace_id.or(Some(self.id));
100        child.project_name = self.project_name.clone();
101        child
102    }
103
104    /// End the run with outputs
105    pub fn end(&mut self, outputs: serde_json::Value) {
106        self.outputs = Some(outputs);
107        self.end_time = Some(Utc::now());
108    }
109
110    /// End the run with an error
111    pub fn end_with_error(&mut self, error: impl Into<String>) {
112        self.error = Some(error.into());
113        self.end_time = Some(Utc::now());
114    }
115
116    /// Add a tag
117    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
118        self.tags.push(tag.into());
119        self
120    }
121
122    /// Add metadata
123    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
124        self.metadata.insert(key.into(), value);
125        self
126    }
127
128    /// Set project name
129    pub fn with_project(mut self, project: impl Into<String>) -> Self {
130        self.project_name = Some(project.into());
131        self
132    }
133
134    /// Calculate run duration in milliseconds
135    pub fn duration_ms(&self) -> Option<i64> {
136        self.end_time
137            .map(|end| (end - self.start_time).num_milliseconds())
138    }
139}
140
141/// Simplified run structure for API requests
142#[derive(Debug, Serialize)]
143pub struct RunCreate {
144    pub id: String,
145    pub name: String,
146    pub run_type: String,
147    pub inputs: serde_json::Value,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub outputs: Option<serde_json::Value>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub error: Option<String>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub parent_run_id: Option<String>,
154    pub start_time: String,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub end_time: Option<String>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub session_name: Option<String>,
159    #[serde(default, skip_serializing_if = "Vec::is_empty")]
160    pub tags: Vec<String>,
161    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
162    pub metadata: HashMap<String, serde_json::Value>,
163}
164
165impl From<&RunTree> for RunCreate {
166    fn from(run: &RunTree) -> Self {
167        Self {
168            id: run.id.to_string(),
169            name: run.name.clone(),
170            run_type: run.run_type.as_str().to_string(),
171            inputs: run.inputs.clone(),
172            outputs: run.outputs.clone(),
173            error: run.error.clone(),
174            parent_run_id: run.parent_run_id.map(|id| id.to_string()),
175            start_time: run.start_time.to_rfc3339(),
176            end_time: run.end_time.map(|t| t.to_rfc3339()),
177            session_name: run.project_name.clone(),
178            tags: run.tags.clone(),
179            metadata: run.metadata.clone(),
180        }
181    }
182}
183
184/// Run update structure for PATCH requests
185#[derive(Debug, Serialize)]
186pub struct RunUpdate {
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub outputs: Option<serde_json::Value>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub error: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub end_time: Option<String>,
193}
194
195impl From<&RunTree> for RunUpdate {
196    fn from(run: &RunTree) -> Self {
197        Self {
198            outputs: run.outputs.clone(),
199            error: run.error.clone(),
200            end_time: run.end_time.map(|t| t.to_rfc3339()),
201        }
202    }
203}