Skip to main content

rustic_ai/
instrumentation.rs

1use std::time::Duration;
2
3use crate::agent::AgentRunState;
4use crate::tools::ToolKind;
5use crate::usage::{RunUsage, UsageLimits};
6
7#[derive(Clone, Debug)]
8pub struct RunStartInfo {
9    pub run_id: String,
10    pub model_name: String,
11    pub message_count: usize,
12    pub tool_count: usize,
13    pub output_schema: bool,
14    pub streaming: bool,
15    pub allow_text_output: bool,
16    pub output_retries: u32,
17    pub usage_limits: UsageLimits,
18}
19
20#[derive(Clone, Debug)]
21pub struct RunEndInfo {
22    pub run_id: String,
23    pub model_name: String,
24    pub state: AgentRunState,
25    pub usage: RunUsage,
26    pub output_len: usize,
27    pub deferred_calls: usize,
28    pub tool_calls: usize,
29    pub duration: Duration,
30}
31
32#[derive(Clone, Debug)]
33pub struct RunErrorInfo {
34    pub run_id: String,
35    pub model_name: String,
36    pub error: String,
37    pub error_kind: Option<String>,
38    pub streaming: bool,
39    pub duration: Duration,
40}
41
42#[derive(Clone, Debug)]
43pub struct ModelRequestInfo {
44    pub run_id: String,
45    pub model_name: String,
46    pub step: u64,
47    pub message_count: usize,
48    pub tool_count: usize,
49    pub output_schema: bool,
50    pub streaming: bool,
51    pub allow_text_output: bool,
52}
53
54#[derive(Clone, Debug)]
55pub struct ModelResponseInfo {
56    pub run_id: String,
57    pub model_name: String,
58    pub step: u64,
59    pub finish_reason: Option<String>,
60    pub usage: RunUsage,
61    pub tool_calls: usize,
62    pub output_len: usize,
63    pub duration: Duration,
64    pub streaming: bool,
65}
66
67#[derive(Clone, Debug)]
68pub struct ModelErrorInfo {
69    pub run_id: String,
70    pub model_name: String,
71    pub step: u64,
72    pub error: String,
73    pub error_kind: Option<String>,
74    pub duration: Duration,
75    pub streaming: bool,
76}
77
78#[derive(Clone, Debug)]
79pub struct ToolCallInfo {
80    pub run_id: String,
81    pub tool_name: String,
82    pub tool_call_id: Option<String>,
83    pub deferred: bool,
84    pub kind: ToolKind,
85    pub sequential: bool,
86}
87
88#[derive(Clone, Debug)]
89pub struct ToolStartInfo {
90    pub run_id: String,
91    pub tool_name: String,
92    pub tool_call_id: Option<String>,
93    pub timeout_secs: Option<f64>,
94    pub sequential: bool,
95}
96
97#[derive(Clone, Debug)]
98pub struct ToolEndInfo {
99    pub run_id: String,
100    pub tool_name: String,
101    pub tool_call_id: Option<String>,
102    pub duration: Duration,
103}
104
105#[derive(Clone, Debug)]
106pub struct ToolErrorInfo {
107    pub run_id: String,
108    pub tool_name: String,
109    pub tool_call_id: Option<String>,
110    pub error: String,
111    pub duration: Duration,
112}
113
114#[derive(Clone, Debug)]
115pub enum UsageLimitKind {
116    Requests,
117    ToolCalls,
118    InputTokens,
119    OutputTokens,
120    TotalTokens,
121}
122
123#[derive(Clone, Debug)]
124pub struct UsageLimitInfo {
125    pub run_id: String,
126    pub model_name: String,
127    pub kind: UsageLimitKind,
128    pub limit: u64,
129    pub usage: RunUsage,
130}
131
132#[derive(Clone, Debug)]
133pub struct OutputValidationErrorInfo {
134    pub run_id: String,
135    pub model_name: String,
136    pub error: String,
137    pub output_len: usize,
138}
139
140pub trait Instrumenter: Send + Sync {
141    fn on_run_start(&self, _info: &RunStartInfo) {}
142    fn on_run_end(&self, _info: &RunEndInfo) {}
143    fn on_run_error(&self, _info: &RunErrorInfo) {}
144    fn on_model_request(&self, _info: &ModelRequestInfo) {}
145    fn on_model_response(&self, _info: &ModelResponseInfo) {}
146    fn on_model_error(&self, _info: &ModelErrorInfo) {}
147    fn on_tool_call(&self, _info: &ToolCallInfo) {}
148    fn on_tool_start(&self, _info: &ToolStartInfo) {}
149    fn on_tool_end(&self, _info: &ToolEndInfo) {}
150    fn on_tool_error(&self, _info: &ToolErrorInfo) {}
151    fn on_usage_limit(&self, _info: &UsageLimitInfo) {}
152    fn on_output_validation_error(&self, _info: &OutputValidationErrorInfo) {}
153}
154
155#[derive(Clone, Default)]
156pub struct NoopInstrumenter;
157
158impl Instrumenter for NoopInstrumenter {}
159
160#[derive(Clone, Default)]
161pub struct TracingInstrumenter;
162
163impl Instrumenter for TracingInstrumenter {
164    fn on_run_start(&self, info: &RunStartInfo) {
165        tracing::info!(
166            run_id = info.run_id.as_str(),
167            model = info.model_name.as_str(),
168            message_count = info.message_count,
169            tool_count = info.tool_count,
170            output_schema = info.output_schema,
171            streaming = info.streaming,
172            allow_text_output = info.allow_text_output,
173            output_retries = info.output_retries,
174            request_limit = info.usage_limits.request_limit.unwrap_or(0),
175            tool_calls_limit = info.usage_limits.tool_calls_limit.unwrap_or(0),
176            input_tokens_limit = info.usage_limits.input_tokens_limit.unwrap_or(0),
177            output_tokens_limit = info.usage_limits.output_tokens_limit.unwrap_or(0),
178            total_tokens_limit = info.usage_limits.total_tokens_limit.unwrap_or(0),
179            "agent run started"
180        );
181    }
182
183    fn on_run_end(&self, info: &RunEndInfo) {
184        tracing::info!(
185            run_id = info.run_id.as_str(),
186            model = info.model_name.as_str(),
187            state = ?info.state,
188            output_len = info.output_len,
189            deferred_calls = info.deferred_calls,
190            tool_calls = info.tool_calls,
191            requests = info.usage.requests,
192            input_tokens = info.usage.input_tokens,
193            output_tokens = info.usage.output_tokens,
194            cache_write_tokens = info.usage.cache_write_tokens,
195            cache_read_tokens = info.usage.cache_read_tokens,
196            input_audio_tokens = info.usage.input_audio_tokens,
197            output_audio_tokens = info.usage.output_audio_tokens,
198            duration_ms = info.duration.as_millis() as u64,
199            "agent run completed"
200        );
201    }
202
203    fn on_run_error(&self, info: &RunErrorInfo) {
204        tracing::error!(
205            run_id = info.run_id.as_str(),
206            model = info.model_name.as_str(),
207            streaming = info.streaming,
208            error = info.error.as_str(),
209            error_kind = info.error_kind.as_deref().unwrap_or(""),
210            duration_ms = info.duration.as_millis() as u64,
211            "agent run failed"
212        );
213    }
214
215    fn on_model_request(&self, info: &ModelRequestInfo) {
216        tracing::info!(
217            run_id = info.run_id.as_str(),
218            model = info.model_name.as_str(),
219            step = info.step,
220            message_count = info.message_count,
221            tool_count = info.tool_count,
222            output_schema = info.output_schema,
223            streaming = info.streaming,
224            allow_text_output = info.allow_text_output,
225            "model request"
226        );
227    }
228
229    fn on_model_response(&self, info: &ModelResponseInfo) {
230        tracing::info!(
231            run_id = info.run_id.as_str(),
232            model = info.model_name.as_str(),
233            step = info.step,
234            finish_reason = info.finish_reason.as_deref().unwrap_or(""),
235            tool_calls = info.tool_calls,
236            requests = info.usage.requests,
237            input_tokens = info.usage.input_tokens,
238            output_tokens = info.usage.output_tokens,
239            output_len = info.output_len,
240            duration_ms = info.duration.as_millis() as u64,
241            streaming = info.streaming,
242            "model response"
243        );
244    }
245
246    fn on_model_error(&self, info: &ModelErrorInfo) {
247        tracing::error!(
248            run_id = info.run_id.as_str(),
249            model = info.model_name.as_str(),
250            step = info.step,
251            error = info.error.as_str(),
252            error_kind = info.error_kind.as_deref().unwrap_or(""),
253            duration_ms = info.duration.as_millis() as u64,
254            streaming = info.streaming,
255            "model request failed"
256        );
257    }
258
259    fn on_tool_call(&self, info: &ToolCallInfo) {
260        tracing::info!(
261            run_id = info.run_id.as_str(),
262            tool = info.tool_name.as_str(),
263            tool_call_id = info.tool_call_id.as_deref().unwrap_or(""),
264            deferred = info.deferred,
265            kind = ?info.kind,
266            sequential = info.sequential,
267            "tool call"
268        );
269    }
270
271    fn on_tool_start(&self, info: &ToolStartInfo) {
272        tracing::debug!(
273            run_id = info.run_id.as_str(),
274            tool = info.tool_name.as_str(),
275            tool_call_id = info.tool_call_id.as_deref().unwrap_or(""),
276            timeout_secs = info.timeout_secs.unwrap_or(0.0),
277            sequential = info.sequential,
278            "tool execution started"
279        );
280    }
281
282    fn on_tool_end(&self, info: &ToolEndInfo) {
283        tracing::info!(
284            run_id = info.run_id.as_str(),
285            tool = info.tool_name.as_str(),
286            tool_call_id = info.tool_call_id.as_deref().unwrap_or(""),
287            duration_ms = info.duration.as_millis() as u64,
288            "tool execution completed"
289        );
290    }
291
292    fn on_tool_error(&self, info: &ToolErrorInfo) {
293        tracing::error!(
294            run_id = info.run_id.as_str(),
295            tool = info.tool_name.as_str(),
296            tool_call_id = info.tool_call_id.as_deref().unwrap_or(""),
297            error = info.error.as_str(),
298            duration_ms = info.duration.as_millis() as u64,
299            "tool execution failed"
300        );
301    }
302
303    fn on_usage_limit(&self, info: &UsageLimitInfo) {
304        tracing::warn!(
305            run_id = info.run_id.as_str(),
306            model = info.model_name.as_str(),
307            kind = ?info.kind,
308            limit = info.limit,
309            requests = info.usage.requests,
310            tool_calls = info.usage.tool_calls,
311            input_tokens = info.usage.input_tokens,
312            output_tokens = info.usage.output_tokens,
313            "usage limit exceeded"
314        );
315    }
316
317    fn on_output_validation_error(&self, info: &OutputValidationErrorInfo) {
318        tracing::warn!(
319            run_id = info.run_id.as_str(),
320            model = info.model_name.as_str(),
321            error = info.error.as_str(),
322            output_len = info.output_len,
323            "output validation failed"
324        );
325    }
326}