1use std::collections::BTreeMap;
11use std::fmt;
12use std::sync::Arc;
13
14use ferrin_spec::BoxFuture;
15use ferrin_spec::GenerateResult;
16use ferrin_spec::JsonValue;
17use ferrin_spec::StreamResult;
18use ferrin_spec::ToolCallId;
19use ferrin_spec::ToolName;
20use ferrin_tool::ToolError;
21
22use crate::error::Error;
23
24pub(crate) mod dispatcher;
25mod events;
26mod redact;
27mod redact_provider;
28pub(crate) mod spans;
29
30pub(crate) use dispatcher::TelemetryDispatcher;
31pub use events::AbortEvent;
32pub use events::EmbedEndEvent;
33pub use events::EmbedStartEvent;
34pub use events::EndEvent;
35pub use events::ErrorEvent;
36pub use events::ErrorPhase;
37pub use events::ModelCallEndEvent;
38pub use events::ModelCallStartEvent;
39pub use events::ModelIdentity;
40pub use events::RecordedInputs;
41pub use events::RerankEndEvent;
42pub use events::RerankStartEvent;
43pub use events::StartEvent;
44pub use events::StepEndEvent;
45pub use events::StepStartEvent;
46pub use events::ToolExecutionEndEvent;
47pub use events::ToolExecutionStartEvent;
48pub use events::ToolOutcome;
49pub use spans::WARNINGS_TARGET;
50
51#[derive(Debug, Clone)]
53pub struct ModelCallContext {
54 pub call_id: String,
56 pub step_number: u32,
58 pub model: ModelIdentity,
60 pub function_id: Option<String>,
62}
63
64#[derive(Debug, Clone)]
66pub struct ToolExecutionContext {
67 pub call_id: String,
69 pub tool_call_id: ToolCallId,
71 pub tool_name: ToolName,
73 pub input: Option<JsonValue>,
75 pub record_outputs: bool,
77}
78
79#[derive(Debug)]
81#[non_exhaustive]
82pub enum ModelCallOutcome {
83 Generate(Box<GenerateResult>),
85 Stream(Box<StreamResult>),
87}
88
89macro_rules! callback {
90 ($name:ident, $event:ty, $doc:literal) => {
91 #[doc = $doc]
92 fn $name<'a>(&'a self, _event: &'a $event) -> BoxFuture<'a, ()> {
93 Box::pin(async {})
94 }
95 };
96}
97
98pub trait Telemetry: Send + Sync + 'static {
103 callback!(on_start, StartEvent, "A call started.");
104 callback!(on_step_start, StepStartEvent, "A step started.");
105 callback!(
106 on_language_model_call_start,
107 ModelCallStartEvent,
108 "A model call is about to be made."
109 );
110 callback!(
111 on_language_model_call_end,
112 ModelCallEndEvent,
113 "A model call finished."
114 );
115 callback!(
116 on_tool_execution_start,
117 ToolExecutionStartEvent,
118 "A tool execution started."
119 );
120 callback!(
121 on_tool_execution_end,
122 ToolExecutionEndEvent,
123 "A tool execution finished."
124 );
125 callback!(on_step_end, StepEndEvent, "A step finished.");
126 callback!(
127 on_embed_start,
128 EmbedStartEvent,
129 "An embedding call started."
130 );
131 callback!(on_embed_end, EmbedEndEvent, "An embedding call finished.");
132 callback!(on_rerank_start, RerankStartEvent, "A rerank call started.");
133 callback!(on_rerank_end, RerankEndEvent, "A rerank call finished.");
134 callback!(
135 on_embed_operation_start,
136 crate::embed::EmbedCallStartEvent,
137 "An embedding operation started before its attempts."
138 );
139 callback!(
140 on_embed_operation_end,
141 crate::embed::EmbedCallEndEvent,
142 "An embedding operation completed all its attempts."
143 );
144 callback!(
145 on_rerank_operation_start,
146 crate::rerank::RerankCallStartEvent,
147 "A reranking operation started before its attempts."
148 );
149 callback!(
150 on_rerank_operation_end,
151 crate::rerank::RerankCallEndEvent,
152 "A reranking operation completed all its attempts."
153 );
154 callback!(on_end, EndEvent, "A call finished.");
155 callback!(on_abort, AbortEvent, "A streaming call was aborted.");
156 callback!(on_error, ErrorEvent<'_>, "An error occurred.");
157
158 fn execute_language_model_call<'a>(
161 &'a self,
162 _ctx: &'a ModelCallContext,
163 call: BoxFuture<'a, Result<ModelCallOutcome, Error>>,
164 ) -> BoxFuture<'a, Result<ModelCallOutcome, Error>> {
165 call
166 }
167
168 fn execute_tool<'a>(
170 &'a self,
171 _ctx: &'a ToolExecutionContext,
172 call: BoxFuture<'a, Result<ToolOutcome, ToolError>>,
173 ) -> BoxFuture<'a, Result<ToolOutcome, ToolError>> {
174 call
175 }
176}
177
178#[derive(Clone, Default)]
180pub struct TelemetryOptions {
181 pub enabled: bool,
183 pub record_inputs: bool,
185 pub record_outputs: bool,
187 pub function_id: Option<String>,
189 pub metadata: BTreeMap<String, JsonValue>,
191 pub include_runtime_context: bool,
193 pub include_tools_context: bool,
195 pub integrations: Vec<Arc<dyn Telemetry>>,
197}
198
199impl TelemetryOptions {
200 #[must_use]
202 pub fn enabled() -> Self {
203 Self {
204 enabled: true,
205 ..Self::default()
206 }
207 }
208
209 #[must_use]
211 pub fn with_integration(mut self, integration: Arc<dyn Telemetry>) -> Self {
212 self.integrations.push(integration);
213 self
214 }
215}
216
217impl fmt::Debug for TelemetryOptions {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.debug_struct("TelemetryOptions")
220 .field("enabled", &self.enabled)
221 .field("record_inputs", &self.record_inputs)
222 .field("record_outputs", &self.record_outputs)
223 .field("function_id", &self.function_id)
224 .field("metadata", &self.metadata)
225 .field("include_runtime_context", &self.include_runtime_context)
226 .field("include_tools_context", &self.include_tools_context)
227 .field("integrations", &self.integrations.len())
228 .finish()
229 }
230}