Skip to main content

assay_core/otel/
mod.rs

1use crate::model::TestResultRow;
2
3pub mod genai;
4pub mod metrics;
5pub mod redaction;
6pub mod semconv;
7
8#[derive(Debug, Clone, Default)]
9pub struct OTelConfig {
10    pub jsonl_path: Option<std::path::PathBuf>,
11    pub redact_prompts: bool,
12}
13
14pub fn export_jsonl(
15    cfg: &OTelConfig,
16    _suite: &str,
17    results: &[TestResultRow],
18) -> anyhow::Result<()> {
19    let Some(path) = &cfg.jsonl_path else {
20        return Ok(());
21    };
22    let mut f = std::fs::OpenOptions::new()
23        .create(true)
24        .append(true)
25        .open(path)?;
26    for r in results {
27        // GenAI Semantic Conventions (simplified for MVP)
28        // https://opentelemetry.io/docs/specs/semconv/gen-ai/
29        let row = serde_json::json!({
30            "timestamp": chrono::Utc::now().to_rfc3339(),
31            "attributes": {
32                "gen_ai.system": "assay",
33                "gen_ai.request.model": "unknown", // can be enriched if we track it better
34                "gen_ai.response.completion_tokens": 0, // placeholder
35                "assay.test_id": r.test_id,
36                "assay.status": format!("{:?}", r.status),
37                "assay.score": r.score,
38                "assay.cached": r.cached,
39                "assay.duration_ms": r.duration_ms,
40            }
41        });
42
43        // Use details/meta if available to populate standard fields
44        // checking details logic would go here
45
46        use std::io::Write;
47        writeln!(f, "{}", row)?;
48    }
49    Ok(())
50}