Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,33 @@ async fn main() -> Result<(), memmesh::Error> {
Errors are [`memmesh::Error`] (`Http`, `Decode`, `Api { status, body }`).

Apache-2.0 · [memmesh.ai](https://memmesh.ai) · [docs](https://docs.memmesh.ai)

## Knowledge graph

Observing doesn't only produce embeddable rows — extraction also resolves
entities and writes typed edges between them. That graph reaches facts no single
memory states outright.

```rust
use memmesh::graph::{ListEntities, Traverse};

// How much of what you remember made it into the graph?
let st = mm.graph().stats().await?;
println!("{} entities, {} edges", st.entity_count, st.edge_count);

// Multi-hop: who does Sarah ultimately report to?
let ents = mm.graph().list_entities(ListEntities { search: Some("Sarah".into()), ..Default::default() }).await?;
let chain = mm.graph().traverse(&ents[0].id, Traverse {
hops: Some(2),
predicates: Some(vec!["member_of".into(), "led_by".into()]),
..Default::default()
}).await?;
```

Edges come back hydrated — `subject` and `object` are full entities, not ids.

Use `stats()`, not `list_entities(..).len()`, for any "how big is it" question:
the list routes page, so their length is the page size, not the total.

Read-only. Entities and edges are written by extraction during `observe()`.

39 changes: 39 additions & 0 deletions examples/graph_live.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Live check of the graph surface. Read-only — no observe() call, so it
//! never writes into the project's corpus.
//!
//! cargo run --example graph_live -- <api-key>
use memmesh::{graph::ListEntities, graph::Traverse, MemMesh};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let key = std::env::args().nth(1).expect("usage: graph_live <api-key>");
let mm = MemMesh::new(key, "YIPKT3NV8RR3UxFfq0PI5");

let st = mm.graph().stats().await?;
println!("stats: entities={} edges={} withEdges={}", st.entity_count, st.edge_count, st.memories_with_edges);
println!(" extraction: {:?}", st.extraction);

let ents = mm.graph().list_entities(ListEntities { limit: Some(3), ..Default::default() }).await?;
println!("entities({}): {:?}", ents.len(), ents.iter().map(|e| &e.canonical_name).collect::<Vec<_>>());

let edges = mm.graph().list_edges(None, Some(3)).await?;
println!("edges({}):", edges.len());
for e in &edges {
let obj = e
.object
.as_ref()
.map(|o| o.canonical_name.clone())
.or_else(|| e.object_literal.clone())
.unwrap_or_default();
println!(" hop={} {} -[{}]-> {} (w={})", e.hop, e.subject.canonical_name, e.predicate, obj, e.weight);
}

if let Some(first) = ents.first() {
let hood = mm.graph().get_entity(&first.id, None).await?;
println!("get_entity: {} -> {} edges", hood.entity.map(|e| e.canonical_name).unwrap_or_default(), hood.edges.len());
let walk = mm.graph().traverse(&first.id, Traverse { hops: Some(2), ..Default::default() }).await?;
println!("traverse(2 hops): {} edges", walk.len());
}
println!("OK — all five graph methods work live");
Ok(())
}
Loading