use crate::types::{FinishReason, TokenUsage, ToolDescriptor};
#[derive(Debug, Clone)]
pub struct RunStartInfo<'a> {
pub session_id: &'a str,
pub model: &'a str,
}
#[derive(Debug, Clone)]
pub struct RunEndInfo<'a> {
pub session_id: &'a str,
pub finish_reason: FinishReason,
pub usage: &'a TokenUsage,
}
#[derive(Debug, Clone)]
pub struct RunErrorInfo<'a> {
pub session_id: &'a str,
pub error: &'a str,
}
#[derive(Debug, Clone)]
pub struct ModelRequestInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub model: &'a str,
}
#[derive(Debug, Clone)]
pub struct ModelResponseInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub model: &'a str,
pub usage: &'a TokenUsage,
}
#[derive(Debug, Clone)]
pub struct ModelErrorInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub model: &'a str,
pub error: &'a str,
}
#[derive(Debug, Clone)]
pub struct ToolCallInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub tool: &'a str,
}
#[derive(Debug, Clone)]
pub struct ToolEndInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub tool: &'a str,
pub is_error: bool,
}
#[derive(Debug, Clone)]
pub struct ToolErrorInfo<'a> {
pub session_id: &'a str,
pub step: u32,
pub tool: &'a str,
pub error: &'a str,
}
#[derive(Debug, Clone)]
pub struct ToolDiscoveredInfo<'a> {
pub tools: &'a [ToolDescriptor],
}
#[derive(Debug, Clone)]
pub struct OutputValidationErrorInfo<'a> {
pub session_id: &'a str,
pub error: &'a str,
}
pub trait Instrumenter: Send + Sync {
fn on_run_start(&self, _info: &RunStartInfo<'_>) {}
fn on_run_end(&self, _info: &RunEndInfo<'_>) {}
fn on_run_error(&self, _info: &RunErrorInfo<'_>) {}
fn on_model_request(&self, _info: &ModelRequestInfo<'_>) {}
fn on_model_response(&self, _info: &ModelResponseInfo<'_>) {}
fn on_model_error(&self, _info: &ModelErrorInfo<'_>) {}
fn on_tool_call(&self, _info: &ToolCallInfo<'_>) {}
fn on_tool_end(&self, _info: &ToolEndInfo<'_>) {}
fn on_tool_error(&self, _info: &ToolErrorInfo<'_>) {}
fn on_tool_discovered(&self, _info: &ToolDiscoveredInfo<'_>) {}
fn on_output_validation_error(&self, _info: &OutputValidationErrorInfo<'_>) {}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopInstrumenter;
impl Instrumenter for NoopInstrumenter {}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use super::*;
#[derive(Debug, Default)]
struct RecordingInstrumenter {
calls: Mutex<Vec<String>>,
}
impl Instrumenter for RecordingInstrumenter {
fn on_run_start(&self, _info: &RunStartInfo<'_>) {
self.calls.lock().unwrap_or_else(|p| p.into_inner()).push("run_start".into());
}
fn on_run_end(&self, _info: &RunEndInfo<'_>) {
self.calls.lock().unwrap_or_else(|p| p.into_inner()).push("run_end".into());
}
fn on_model_request(&self, _info: &ModelRequestInfo<'_>) {
self.calls.lock().unwrap_or_else(|p| p.into_inner()).push("model_request".into());
}
fn on_tool_call(&self, _info: &ToolCallInfo<'_>) {
self.calls.lock().unwrap_or_else(|p| p.into_inner()).push("tool_call".into());
}
}
#[test]
fn noop_instrumenter_is_default() {
let inst = NoopInstrumenter;
inst.on_run_start(&RunStartInfo { session_id: "s1", model: "test" });
}
#[test]
fn recording_instrumenter_tracks_calls() {
let inst = RecordingInstrumenter::default();
inst.on_run_start(&RunStartInfo { session_id: "s1", model: "test" });
inst.on_model_request(&ModelRequestInfo { session_id: "s1", step: 1, model: "test" });
inst.on_run_end(&RunEndInfo {
session_id: "s1",
finish_reason: FinishReason::Stop,
usage: &TokenUsage { prompt_tokens: 10, completion_tokens: 20 },
});
let calls = inst.calls.lock().unwrap_or_else(|p| p.into_inner());
assert_eq!(&*calls, &["run_start", "model_request", "run_end"]);
}
#[test]
fn instrumenter_is_object_safe() {
let _inst: Arc<dyn Instrumenter> = Arc::new(NoopInstrumenter);
}
}