1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3
4pub type SpanId = String;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10pub enum SpanKind {
11 Llm,
13 Chain,
15 Tool,
17 Retriever,
19 Agent,
21 Custom(String),
23}
24
25impl std::fmt::Display for SpanKind {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 SpanKind::Llm => write!(f, "llm"),
29 SpanKind::Chain => write!(f, "chain"),
30 SpanKind::Tool => write!(f, "tool"),
31 SpanKind::Retriever => write!(f, "retriever"),
32 SpanKind::Agent => write!(f, "agent"),
33 SpanKind::Custom(name) => write!(f, "custom:{}", name),
34 }
35 }
36}
37
38impl From<crate::RunType> for SpanKind {
39 fn from(run_type: crate::RunType) -> Self {
40 match run_type {
41 crate::RunType::Llm => SpanKind::Llm,
42 crate::RunType::Chain => SpanKind::Chain,
43 crate::RunType::Tool => SpanKind::Tool,
44 crate::RunType::Retriever => SpanKind::Retriever,
45 crate::RunType::Embedding => SpanKind::Custom("embedding".to_string()),
47 crate::RunType::Prompt => SpanKind::Custom("prompt".to_string()),
48 crate::RunType::Parser => SpanKind::Custom("parser".to_string()),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
55pub struct SpanTokenUsage {
56 pub prompt_tokens: usize,
57 pub completion_tokens: usize,
58 pub total_tokens: usize,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
63#[serde(rename_all = "snake_case")]
64pub enum SpanStatus {
65 Ok,
67 Error(String),
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct TraceSpan {
74 pub id: SpanId,
76 pub parent_id: Option<SpanId>,
78 pub name: String,
80 pub kind: SpanKind,
82 pub start_time: Option<String>,
84 pub end_time: Option<String>,
86 pub tokens: Option<SpanTokenUsage>,
88 pub cost: Option<f64>,
90 pub latency_ms: Option<u64>,
92 pub metadata: serde_json::Value,
94 pub status: SpanStatus,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
100 pub gen_ai_system: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub gen_ai_request_model: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub gen_ai_response_model: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub gen_ai_finish_reason: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub gen_ai_request_max_tokens: Option<u64>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub gen_ai_request_temperature: Option<f64>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub gen_ai_operation_name: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub gen_ai_tool_name: Option<String>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct TraceNode {
127 pub span: TraceSpan,
128 pub children: Vec<TraceNode>,
129}
130
131pub(crate) fn build_tree(root: &TraceSpan, all_spans: &[TraceSpan]) -> TraceNode {
132 let children: Vec<TraceNode> = all_spans
133 .iter()
134 .filter(|s| s.parent_id.as_deref() == Some(root.id.as_str()))
135 .map(|child| build_tree(child, all_spans))
136 .collect();
137
138 TraceNode {
139 span: root.clone(),
140 children,
141 }
142}
143
144pub(crate) fn make_span(
146 id: String,
147 parent_id: Option<SpanId>,
148 name: &str,
149 kind: SpanKind,
150) -> TraceSpan {
151 TraceSpan {
152 id,
153 parent_id,
154 name: name.to_string(),
155 kind,
156 start_time: Some(Utc::now().to_rfc3339()),
157 end_time: None,
158 tokens: None,
159 cost: None,
160 latency_ms: None,
161 metadata: serde_json::Value::Object(serde_json::Map::new()),
162 status: SpanStatus::Ok,
163 gen_ai_system: None,
164 gen_ai_request_model: None,
165 gen_ai_response_model: None,
166 gen_ai_finish_reason: None,
167 gen_ai_request_max_tokens: None,
168 gen_ai_request_temperature: None,
169 gen_ai_operation_name: None,
170 gen_ai_tool_name: None,
171 }
172}