feat(observability): focus PR 596 on Prometheus backend
This commit is contained in:
parent
eba544dbd4
commit
2560399423
12 changed files with 358 additions and 103 deletions
|
|
@ -7,12 +7,31 @@ pub enum ObserverEvent {
|
|||
provider: String,
|
||||
model: String,
|
||||
},
|
||||
/// A request is about to be sent to an LLM provider.
|
||||
///
|
||||
/// This is emitted immediately before a provider call so observers can print
|
||||
/// user-facing progress without leaking prompt contents.
|
||||
LlmRequest {
|
||||
provider: String,
|
||||
model: String,
|
||||
messages_count: usize,
|
||||
},
|
||||
/// Result of a single LLM provider call.
|
||||
LlmResponse {
|
||||
provider: String,
|
||||
model: String,
|
||||
duration: Duration,
|
||||
success: bool,
|
||||
error_message: Option<String>,
|
||||
},
|
||||
AgentEnd {
|
||||
provider: String,
|
||||
model: String,
|
||||
duration: Duration,
|
||||
tokens_used: Option<u64>,
|
||||
cost_usd: Option<f64>,
|
||||
},
|
||||
/// A tool call is about to be executed.
|
||||
ToolCallStart {
|
||||
tool: String,
|
||||
},
|
||||
|
|
@ -21,6 +40,7 @@ pub enum ObserverEvent {
|
|||
duration: Duration,
|
||||
success: bool,
|
||||
},
|
||||
/// The agent produced a final answer for the current user message.
|
||||
TurnComplete,
|
||||
ChannelMessage {
|
||||
channel: String,
|
||||
|
|
@ -31,19 +51,6 @@ pub enum ObserverEvent {
|
|||
component: String,
|
||||
message: String,
|
||||
},
|
||||
// LLM request/response tracking
|
||||
LlmRequest {
|
||||
provider: String,
|
||||
model: String,
|
||||
messages_count: usize,
|
||||
},
|
||||
LlmResponse {
|
||||
provider: String,
|
||||
model: String,
|
||||
duration: Duration,
|
||||
success: bool,
|
||||
error_message: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Numeric metrics
|
||||
|
|
@ -56,7 +63,7 @@ pub enum ObserverMetric {
|
|||
}
|
||||
|
||||
/// Core observability trait — implement for any backend
|
||||
pub trait Observer: Send + Sync {
|
||||
pub trait Observer: Send + Sync + 'static {
|
||||
/// Record a discrete event
|
||||
fn record_event(&self, event: &ObserverEvent);
|
||||
|
||||
|
|
@ -69,6 +76,79 @@ pub trait Observer: Send + Sync {
|
|||
/// Human-readable name of this observer
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Downcast support for backend-specific operations (e.g. Prometheus encoding)
|
||||
/// Downcast to `Any` for backend-specific operations
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use parking_lot::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Default)]
|
||||
struct DummyObserver {
|
||||
events: Mutex<u64>,
|
||||
metrics: Mutex<u64>,
|
||||
}
|
||||
|
||||
impl Observer for DummyObserver {
|
||||
fn record_event(&self, _event: &ObserverEvent) {
|
||||
let mut guard = self.events.lock();
|
||||
*guard += 1;
|
||||
}
|
||||
|
||||
fn record_metric(&self, _metric: &ObserverMetric) {
|
||||
let mut guard = self.metrics.lock();
|
||||
*guard += 1;
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"dummy-observer"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_records_events_and_metrics() {
|
||||
let observer = DummyObserver::default();
|
||||
|
||||
observer.record_event(&ObserverEvent::HeartbeatTick);
|
||||
observer.record_event(&ObserverEvent::Error {
|
||||
component: "test".into(),
|
||||
message: "boom".into(),
|
||||
});
|
||||
observer.record_metric(&ObserverMetric::TokensUsed(42));
|
||||
|
||||
assert_eq!(*observer.events.lock(), 2);
|
||||
assert_eq!(*observer.metrics.lock(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_default_flush_and_as_any_work() {
|
||||
let observer = DummyObserver::default();
|
||||
|
||||
observer.flush();
|
||||
assert_eq!(observer.name(), "dummy-observer");
|
||||
assert!(observer.as_any().downcast_ref::<DummyObserver>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_event_and_metric_are_cloneable() {
|
||||
let event = ObserverEvent::ToolCall {
|
||||
tool: "shell".into(),
|
||||
duration: Duration::from_millis(10),
|
||||
success: true,
|
||||
};
|
||||
let metric = ObserverMetric::RequestLatency(Duration::from_millis(8));
|
||||
|
||||
let cloned_event = event.clone();
|
||||
let cloned_metric = metric.clone();
|
||||
|
||||
assert!(matches!(cloned_event, ObserverEvent::ToolCall { .. }));
|
||||
assert!(matches!(cloned_metric, ObserverMetric::RequestLatency(_)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue