Example 4: Appendix and embeddings
- file format 6
- Rust SDK 1.0 preview
Needs the Rust SDK
Write two embedding rows and a tensor into a pack's appendix, read them back, and bind each row to its entity. The rows are stand-in bytes.
04_appendix_and_embeddings.rsrust
// Embed binary data (an appendix) in a pack: fixed-size embedding
// rows and an f32 tensor. Then read them back and bind each
// embedding to its entity.
use plxi_sdk::{
write, AppendixSpec, BindProfile, OpenOptions, Pack, Record,
COMPACT_RECORD_SIZE,
};
fn row(seed: u8) -> Vec<u8> {
// Stand-in bytes. A real row is one Compact embedding record.
(0..COMPACT_RECORD_SIZE)
.map(|i| (i as u8).wrapping_add(seed))
.collect()
}
fn main() -> Result<(), plxi_sdk::Error> {
let lines = [
r#"{"k":"ent","id":"e1","t":"Person"}"#,
r#"{"k":"ent","id":"e2","t":"Person"}"#,
// An `embref` points an entity at row N of appendix section M.
concat!(
r#"{"k":"embref","entity_id":"e1","target":"#,
r#"{"local":{"section_index":0,"record_index":0}}}"#
),
concat!(
r#"{"k":"embref","entity_id":"e2","target":"#,
r#"{"local":{"section_index":0,"record_index":1}}}"#
),
];
let records = lines
.iter()
.map(|l| Record::from_json_line(l))
.collect::<Result<Vec<_>, _>>()?;
// The constructors return None on a caller mistake (wrong row
// length, shape that does not match the data length).
let appendix = AppendixSpec::compact(&[row(1), row(2)])
.expect("every row is COMPACT_RECORD_SIZE bytes")
.with_tensor_f32("probe.f32", &[2, 2], &[0.5, -1.25, 2.0, 0.0])
.expect("2 x 2 = 4 values");
assert!(AppendixSpec::compact(&[vec![0u8; 7]]).is_none());
let bytes = write(&records, Some(&appendix))?;
let pack = Pack::from_bytes(bytes, OpenOptions::default())?;
assert!(pack.has_appendix());
let app = pack.appendix()?.expect("this pack has an appendix");
let sections = app.sections();
assert_eq!(sections[0].section_type, 0x0020); // Compact
assert_eq!(sections[0].record_count, 2);
assert_eq!(sections[0].record_stride as usize, COMPACT_RECORD_SIZE);
let data = app.section_bytes(0)?.expect("section 0 exists");
assert_eq!(data.len(), 2 * COMPACT_RECORD_SIZE);
assert_eq!(app.tensor_names(), ["probe.f32"]);
let tensor = app.tensor_f32("probe.f32")?;
assert_eq!(tensor, Some(vec![0.5, -1.25, 2.0, 0.0]));
assert_eq!(app.tensor_f32("absent")?, None);
// One bound embedding per `embref`, in record order.
let bound = pack.bind_embrefs(BindProfile::Compact)?;
assert_eq!(bound.len(), 2);
assert_eq!(bound[0].entity_id, "e1");
assert_eq!(bound[0].row, row(1));
assert_eq!(bound[1].entity_id, "e2");
assert_eq!(bound[1].row, row(2));
let report = pack.verify()?;
assert!(report.is_ok());
let embrefs = &report.embrefs;
assert_eq!((embrefs.total, embrefs.resolved), (2, 2));
println!("row size = {COMPACT_RECORD_SIZE} bytes");
Ok(())
}
Output
row size = 320 bytesRan with cargo run --example 04_appendix_and_embeddings · exit status 0
The appendix holds the Compact section at index 0 and further sections for the tensor.
Explained in: The appendix