#![cfg(feature = "persistence")]
use tempfile::TempDir;
use velesdb_memory::{
ExtractError, ExtractedFact, ExtractedRelation, Extraction, Extractor, HashEmbedder,
MemoryService, OutlineExtractor, DEFAULT_DIMENSION,
};
type Triple = (&'static str, &'static str, &'static str);
type EdgeSet = Vec<(String, u64)>;
struct ScriptedExtractor {
triples: &'static [Triple],
}
impl Extractor for ScriptedExtractor {
fn extract(&self, text: &str) -> Result<Vec<ExtractedFact>, ExtractError> {
Ok(self.extract_graph(text)?.facts)
}
fn extract_graph(&self, text: &str) -> Result<Extraction, ExtractError> {
Ok(Extraction {
facts: vec![ExtractedFact {
text: text.to_string(),
entities: vec![],
}],
relations: self
.triples
.iter()
.map(|(subject, predicate, object)| ExtractedRelation {
subject: (*subject).to_string(),
predicate: (*predicate).to_string(),
object: (*object).to_string(),
})
.collect(),
attributes: vec![],
})
}
}
fn service() -> (TempDir, MemoryService<HashEmbedder>) {
let dir = tempfile::tempdir().expect("tempdir");
let svc = MemoryService::open(dir.path(), HashEmbedder::new(DEFAULT_DIMENSION))
.expect("open service");
(dir, svc)
}
fn outline_remember(passage: &str) -> (TempDir, MemoryService<HashEmbedder>) {
let (dir, svc) = service();
svc.remember_extracted(passage, &OutlineExtractor, None)
.expect("outline remember");
(dir, svc)
}
fn scripted_remember(
passage: &str,
triples: &'static [Triple],
) -> (TempDir, MemoryService<HashEmbedder>) {
let (dir, svc) = service();
svc.remember_extracted(passage, &ScriptedExtractor { triples }, None)
.expect("scripted remember");
(dir, svc)
}
fn hub_id(svc: &MemoryService<HashEmbedder>, name: &str) -> u64 {
svc.entity_profile(name)
.expect("profile lookup")
.unwrap_or_else(|| panic!("{name} has a hub"))
.id
}
fn edges_seen(svc: &MemoryService<HashEmbedder>, name: &str) -> (EdgeSet, EdgeSet) {
let profile = svc
.entity_profile(name)
.expect("profile lookup")
.unwrap_or_else(|| panic!("{name} has a hub"));
let mut outgoing: EdgeSet = profile
.relations
.into_iter()
.map(|r| (r.predicate, r.target_id))
.collect();
let mut incoming: EdgeSet = profile
.relations_in
.into_iter()
.map(|r| (r.predicate, r.target_id))
.collect();
outgoing.sort();
incoming.sort();
(outgoing, incoming)
}
fn assert_entity_sees(
svc: &MemoryService<HashEmbedder>,
name: &str,
expected_out: &[(&str, &str)],
expected_in: &[(&str, &str)],
) {
let resolve = |pairs: &[(&str, &str)]| {
let mut resolved: EdgeSet = pairs
.iter()
.map(|(predicate, far)| ((*predicate).to_string(), hub_id(svc, far)))
.collect();
resolved.sort();
resolved
};
let (seen_out, seen_in) = edges_seen(svc, name);
assert_eq!(seen_out, resolve(expected_out), "outgoing edges of {name}");
assert_eq!(seen_in, resolve(expected_in), "incoming edges of {name}");
}
#[test]
fn an_outlined_asymmetric_edge_is_stored_the_way_the_line_states_it() {
let (_dir, svc) = outline_remember("edge: Alice Martin | travaille chez | Wiscale");
assert_entity_sees(&svc, "alice martin", &[("travaille chez", "wiscale")], &[]);
assert_entity_sees(&svc, "wiscale", &[], &[("travaille chez", "alice martin")]);
}
#[test]
fn an_outlined_accented_predicate_survives_verbatim_and_oriented() {
let (_dir, svc) = outline_remember("edge: Alice Martin | a fondé | Wiscale");
assert_entity_sees(&svc, "alice martin", &[("a fondé", "wiscale")], &[]);
assert_entity_sees(&svc, "wiscale", &[], &[("a fondé", "alice martin")]);
}
#[test]
fn outlined_converse_predicates_each_keep_their_own_direction() {
let (_dir, svc) = outline_remember(
"edge: Wiscale | emploie | Alice Martin\n\
edge: Alice Martin | travaille pour | Wiscale",
);
assert_entity_sees(
&svc,
"alice martin",
&[("travaille pour", "wiscale")],
&[("emploie", "wiscale")],
);
assert_entity_sees(
&svc,
"wiscale",
&[("emploie", "alice martin")],
&[("travaille pour", "alice martin")],
);
}
#[test]
fn an_outlined_symmetric_predicate_is_stored_only_in_the_stated_direction() {
let (_dir, svc) = outline_remember("edge: Alice Martin | est amie de | Bob Durand");
assert_entity_sees(&svc, "alice martin", &[("est amie de", "bob durand")], &[]);
assert_entity_sees(&svc, "bob durand", &[], &[("est amie de", "alice martin")]);
}
#[test]
fn a_generative_triple_is_stored_exactly_as_the_backend_returned_it() {
let (_dir, svc) = scripted_remember(
"Alice Martin travaille chez Wiscale depuis 2019.",
&[("alice martin", "travaille chez", "wiscale")],
);
assert_entity_sees(&svc, "alice martin", &[("travaille chez", "wiscale")], &[]);
assert_entity_sees(&svc, "wiscale", &[], &[("travaille chez", "alice martin")]);
}
#[test]
fn a_generative_english_triple_is_stored_exactly_as_returned() {
let (_dir, svc) = scripted_remember(
"Alice Martin founded Wiscale in 2019.",
&[("alice martin", "founded", "wiscale")],
);
assert_entity_sees(&svc, "alice martin", &[("founded", "wiscale")], &[]);
assert_entity_sees(&svc, "wiscale", &[], &[("founded", "alice martin")]);
}
#[test]
fn a_generative_symmetric_triple_is_not_mirrored() {
let (_dir, svc) = scripted_remember(
"Alice Martin est amie de Camille Roy.",
&[("alice martin", "amie de", "camille roy")],
);
assert_entity_sees(&svc, "alice martin", &[("amie de", "camille roy")], &[]);
assert_entity_sees(&svc, "camille roy", &[], &[("amie de", "alice martin")]);
}
#[test]
fn the_kinship_pass_leaves_a_non_kinship_edge_on_the_same_pair_untouched() {
let (_dir, svc) = scripted_remember(
"Marie Dupont a un frère, Paul Martin. Paul Martin travaille avec Marie Dupont.",
&[
("marie dupont", "frere de", "paul martin"),
("paul martin", "travaille avec", "marie dupont"),
],
);
assert_entity_sees(
&svc,
"paul martin",
&[
("frere de", "marie dupont"),
("travaille avec", "marie dupont"),
],
&[],
);
assert_entity_sees(
&svc,
"marie dupont",
&[],
&[
("frere de", "paul martin"),
("travaille avec", "paul martin"),
],
);
}
#[test]
fn converse_triples_across_languages_are_not_conflated() {
let (_dir, svc) = scripted_remember(
"Wiscale emploie Alice Martin. Alice Martin works for Wiscale.",
&[
("wiscale", "emploie", "alice martin"),
("alice martin", "works for", "wiscale"),
],
);
assert_entity_sees(
&svc,
"alice martin",
&[("works for", "wiscale")],
&[("emploie", "wiscale")],
);
assert_entity_sees(
&svc,
"wiscale",
&[("emploie", "alice martin")],
&[("works for", "alice martin")],
);
}