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}