weavatrix-memory 0.3.2

Event-sourced bitemporal evidence memory and budgeted context retrieval for coding agents
Documentation
use super::model::{Document, PreparedBenchmark, PreparedCase};
use blazingly_json::Value;
use std::collections::{BTreeMap, BTreeSet};

pub fn prepare(bytes: &[u8]) -> Result<PreparedBenchmark, String> {
    let samples =
        blazingly_json::from_slice::<Vec<Value>>(bytes).map_err(|error| error.to_string())?;
    let mut groups = BTreeMap::new();
    let mut cases = Vec::new();
    for (sample_index, sample) in samples.iter().enumerate() {
        let sample_id = sample
            .get("sample_id")
            .and_then(Value::as_str)
            .map_or_else(|| format!("sample-{sample_index}"), str::to_owned);
        let documents = conversation_documents(sample, &sample_id)?;
        let document_ids = documents
            .iter()
            .map(|document| document.id.as_str())
            .collect::<BTreeSet<_>>();
        let qa = sample
            .get("qa")
            .and_then(Value::as_array)
            .ok_or_else(|| format!("{sample_id} has no qa array"))?;
        for (question_index, question) in qa.iter().enumerate() {
            let query = required_string(question, "question")?;
            let evidence = question
                .get("evidence")
                .and_then(Value::as_array)
                .into_iter()
                .flatten()
                .filter_map(Value::as_str)
                .flat_map(split_evidence)
                .map(|id| document_id(&sample_id, id))
                .filter(|id| document_ids.contains(id.as_str()))
                .collect::<BTreeSet<_>>();
            if evidence.is_empty() {
                continue;
            }
            let category = question
                .get("category")
                .map_or_else(|| "unknown".to_owned(), value_text);
            cases.push(PreparedCase {
                id: format!("{sample_id}::qa::{question_index}"),
                group_id: sample_id.clone(),
                category: format!("locomo_{category}"),
                query,
                relevant_ids: evidence,
            });
        }
        groups.insert(sample_id, documents);
    }
    Ok(PreparedBenchmark {
        name: "locomo".to_owned(),
        groups,
        cases,
    })
}

fn conversation_documents(sample: &Value, sample_id: &str) -> Result<Vec<Document>, String> {
    let conversation = sample
        .get("conversation")
        .and_then(Value::as_object)
        .ok_or_else(|| format!("{sample_id} has no conversation object"))?;
    let mut documents = Vec::new();
    for (key, turns) in conversation {
        if !key.starts_with("session_") || key.ends_with("_date_time") {
            continue;
        }
        let Some(turns) = turns.as_array() else {
            continue;
        };
        for turn in turns {
            let Some(id) = turn.get("dia_id").and_then(Value::as_str) else {
                continue;
            };
            let Some(text) = turn.get("text").and_then(Value::as_str) else {
                continue;
            };
            let speaker = turn
                .get("speaker")
                .and_then(Value::as_str)
                .unwrap_or("speaker");
            documents.push(Document {
                id: document_id(sample_id, id),
                text: format!("{speaker}: {text}"),
            });
        }
    }
    documents.sort_by(|left, right| left.id.cmp(&right.id));
    Ok(documents)
}

fn split_evidence(value: &str) -> impl Iterator<Item = &str> {
    value
        .split(';')
        .map(str::trim)
        .filter(|value| !value.is_empty())
}

fn document_id(sample: &str, dialog: &str) -> String {
    format!("{sample}::dialog::{dialog}")
}

fn required_string(value: &Value, field: &str) -> Result<String, String> {
    value
        .get(field)
        .and_then(Value::as_str)
        .map(str::to_owned)
        .ok_or_else(|| format!("missing string field {field}"))
}

fn value_text(value: &Value) -> String {
    value
        .as_str()
        .map_or_else(|| value.to_string(), str::to_owned)
}

#[cfg(test)]
mod tests {
    #[test]
    fn adapter_splits_compound_dialog_evidence() {
        let input = br#"[
          {
            "sample_id":"conv",
            "qa":[
              {"question":"What happened?","category":2,"evidence":["D1:1; D1:2"]},
              {"question":"Unknown?","category":3,"evidence":[]}
            ],
            "conversation":{
              "speaker_a":"A",
              "speaker_b":"B",
              "session_1_date_time":"today",
              "session_1":[
                {"speaker":"A","dia_id":"D1:1","text":"first"},
                {"speaker":"B","dia_id":"D1:2","text":"second"}
              ]
            }
          }
        ]"#;
        let benchmark = super::prepare(input).unwrap();

        assert_eq!(benchmark.groups["conv"].len(), 2);
        assert_eq!(benchmark.cases.len(), 1);
        assert_eq!(benchmark.cases[0].relevant_ids.len(), 2);
    }
}