1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ObservationEvent {
8 pub trace_id: String,
10 pub span_id: String,
12 #[serde(default, skip_serializing_if = "Option::is_none")]
14 pub parent_span_id: Option<String>,
15 pub turn_id: String,
17 pub agent_id: String,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub actor_id: Option<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub session_id: Option<String>,
23 pub event_type: EventType,
25 pub purpose: ObservationPurpose,
27 pub status: EventStatus,
29 pub timestamp: DateTime<Utc>,
31 pub duration_ms: u64,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub tokens: Option<ObservationTokenUsage>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub cost: Option<CostEstimate>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub error: Option<ObservationError>,
39 #[serde(default)]
41 pub dimensions: HashMap<String, String>,
42 #[serde(default)]
44 pub tags: HashMap<String, String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub payload: Option<serde_json::Value>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum EventType {
53 LlmCall {
54 provider: String,
55 model: String,
56 alias: Option<String>,
57 streaming: bool,
58 },
59 ToolCall {
60 tool_id: String,
61 },
62 SkillExecution {
63 skill_id: String,
64 },
65 SkillStep {
66 skill_id: String,
67 step_index: usize,
68 step_type: String,
69 },
70 StateTransition {
71 from: Option<String>,
72 to: String,
73 },
74 Orchestration {
75 pattern: String,
76 },
77 HitlApproval {
78 trigger: String,
79 },
80 MemoryOperation {
81 operation: String,
82 },
83 PersonaEvent {
84 event: String,
85 },
86 FactsEvent {
87 event: String,
88 },
89 RelationshipEvent {
90 event: String,
91 },
92 Error,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
97#[serde(rename_all = "snake_case")]
98pub enum ObservationPurpose {
99 MainResponse,
100 Router,
101 SkillRouting,
102 SkillPrompt,
103 ProcessDetect,
104 ProcessExtract,
105 ProcessValidate,
106 ProcessTransform,
107 DisambiguationDetection,
108 DisambiguationClarification,
109 StateTransitionEvaluation,
110 StateAction,
111 ContextExtraction,
112 Summarization,
113 ReflectionDecision,
114 ReflectionEvaluation,
115 PlanGeneration,
116 PlanStep,
117 FactsExtraction,
118 RelationshipUpdate,
119 HitlLocalization,
120 OrchestrationRouting,
121 OrchestrationAggregation,
122 OrchestrationConversation,
123 EvaluationJudge,
124 Other(String),
125}
126
127impl ObservationPurpose {
128 pub fn as_label(&self) -> String {
130 match self {
131 Self::MainResponse => "main_response".to_string(),
132 Self::Router => "router".to_string(),
133 Self::SkillRouting => "skill_routing".to_string(),
134 Self::SkillPrompt => "skill_prompt".to_string(),
135 Self::ProcessDetect => "process_detect".to_string(),
136 Self::ProcessExtract => "process_extract".to_string(),
137 Self::ProcessValidate => "process_validate".to_string(),
138 Self::ProcessTransform => "process_transform".to_string(),
139 Self::DisambiguationDetection => "disambiguation_detection".to_string(),
140 Self::DisambiguationClarification => "disambiguation_clarification".to_string(),
141 Self::StateTransitionEvaluation => "state_transition_evaluation".to_string(),
142 Self::StateAction => "state_action".to_string(),
143 Self::ContextExtraction => "context_extraction".to_string(),
144 Self::Summarization => "summarization".to_string(),
145 Self::ReflectionDecision => "reflection_decision".to_string(),
146 Self::ReflectionEvaluation => "reflection_evaluation".to_string(),
147 Self::PlanGeneration => "plan_generation".to_string(),
148 Self::PlanStep => "plan_step".to_string(),
149 Self::FactsExtraction => "facts_extraction".to_string(),
150 Self::RelationshipUpdate => "relationship_update".to_string(),
151 Self::HitlLocalization => "hitl_localization".to_string(),
152 Self::OrchestrationRouting => "orchestration_routing".to_string(),
153 Self::OrchestrationAggregation => "orchestration_aggregation".to_string(),
154 Self::OrchestrationConversation => "orchestration_conversation".to_string(),
155 Self::EvaluationJudge => "evaluation_judge".to_string(),
156 Self::Other(value) => value.clone(),
157 }
158 }
159}
160
161impl Default for ObservationPurpose {
162 fn default() -> Self {
163 Self::Other("other".to_string())
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
169#[serde(rename_all = "snake_case")]
170pub enum EventStatus {
171 Success,
172 Error,
173 Cancelled,
174 Skipped,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct ObservationTokenUsage {
180 pub input_tokens: u64,
182 pub output_tokens: u64,
184 pub total_tokens: u64,
186 pub source: TokenUsageSource,
188}
189
190impl ObservationTokenUsage {
191 pub fn new(input_tokens: u64, output_tokens: u64, source: TokenUsageSource) -> Self {
193 Self {
194 input_tokens,
195 output_tokens,
196 total_tokens: input_tokens + output_tokens,
197 source,
198 }
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
204#[serde(rename_all = "snake_case")]
205pub enum TokenUsageSource {
206 Provider,
207 StreamFinalChunk,
208 Estimated,
209 Missing,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct CostEstimate {
215 pub input_usd: f64,
216 pub output_usd: f64,
217 pub total_usd: f64,
218 pub source: CostSource,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
223#[serde(rename_all = "snake_case")]
224pub enum CostSource {
225 Configured,
226 BuiltIn,
227 Unknown,
228 Estimated,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ObservationError {
234 pub kind: String,
236 pub message: String,
238}
239
240impl ObservationError {
241 pub fn new(kind: impl Into<String>, message: impl Into<String>) -> Self {
243 Self {
244 kind: kind.into(),
245 message: message.into(),
246 }
247 }
248}