scouter_client/drifter/
llm.rs

1use pyo3::prelude::*;
2use scouter_drift::{error::DriftError, LLMEvaluator};
3use scouter_state::app_state;
4use scouter_types::llm::{LLMDriftConfig, LLMDriftMetric, LLMDriftProfile};
5use scouter_types::{LLMMetricRecord, LLMRecord};
6/// Using "ClientLLMDrifter" to avoid confusion with the server-side LLMDrifter
7pub struct ClientLLMDrifter {}
8
9impl Default for ClientLLMDrifter {
10    fn default() -> Self {
11        Self::new()
12    }
13}
14
15impl ClientLLMDrifter {
16    pub fn new() -> Self {
17        Self {}
18    }
19
20    pub fn create_drift_profile(
21        &mut self,
22        config: LLMDriftConfig,
23        metrics: Vec<LLMDriftMetric>,
24        workflow: Option<Bound<'_, PyAny>>,
25    ) -> Result<LLMDriftProfile, DriftError> {
26        let profile = LLMDriftProfile::new(config, metrics, workflow)?;
27        Ok(profile)
28    }
29
30    pub async fn compute_drift_single(
31        record: &LLMRecord,
32        profile: &LLMDriftProfile,
33    ) -> Result<Vec<LLMMetricRecord>, DriftError> {
34        let (metrics, _score, _workflow_duration) =
35            LLMEvaluator::process_drift_record(record, profile).await?;
36        Ok(metrics)
37    }
38
39    pub fn compute_drift(
40        &mut self,
41        data: Vec<LLMRecord>,
42        profile: &LLMDriftProfile,
43    ) -> Result<Vec<LLMMetricRecord>, DriftError> {
44        let results = app_state().handle().block_on(async move {
45            let mut results = Vec::new();
46            for record in data {
47                let metrics = Self::compute_drift_single(&record, profile).await?;
48                results.extend(metrics);
49            }
50            Ok::<_, DriftError>(results)
51        })?; // propagate error from block_on
52
53        Ok(results)
54    }
55}