wesichain-langsmith 0.3.0

LangSmith observability for Wesichain
Documentation
use regex::Regex;
use serde_json::Value;

const REDACTED: &str = "[REDACTED]";

pub fn ensure_object(value: Value) -> Value {
    match value {
        Value::Object(_) => value,
        other => Value::Object(serde_json::Map::from_iter([("value".to_string(), other)])),
    }
}

pub fn sanitize_value(value: Value, regex: Option<&Regex>) -> Value {
    match value {
        Value::String(text) => match regex {
            Some(pattern) => Value::String(pattern.replace_all(&text, REDACTED).to_string()),
            None => Value::String(text),
        },
        Value::Array(items) => Value::Array(
            items
                .into_iter()
                .map(|item| sanitize_value(item, regex))
                .collect(),
        ),
        Value::Object(map) => Value::Object(
            map.into_iter()
                .map(|(key, value)| (key, sanitize_value(value, regex)))
                .collect(),
        ),
        other => other,
    }
}

pub fn truncate_value(value: Value, max_bytes: usize) -> Value {
    match value {
        Value::String(text) => Value::String(truncate_string(&text, max_bytes)),
        Value::Array(items) => Value::Array(
            items
                .into_iter()
                .map(|item| truncate_value(item, max_bytes))
                .collect(),
        ),
        Value::Object(map) => Value::Object(
            map.into_iter()
                .map(|(key, value)| (key, truncate_value(value, max_bytes)))
                .collect(),
        ),
        other => other,
    }
}

fn truncate_string(text: &str, max_bytes: usize) -> String {
    if text.len() <= max_bytes {
        return text.to_string();
    }
    let mut end = 0;
    for (idx, _) in text.char_indices() {
        if idx <= max_bytes {
            end = idx;
        } else {
            break;
        }
    }
    text[..end].to_string()
}