Skip to main content

lc_rag/graph_rag/
graph_store.rs

1// src/retrieval/graph_rag/graph_store.rs
2//! In-memory graph store using adjacency lists.
3//!
4//! Stores entities, relations, and communities without any external graph crate.
5
6use std::collections::HashMap;
7
8/// A named entity extracted from a document.
9#[derive(Debug, Clone)]
10pub struct Entity {
11    pub id: String,
12    pub name: String,
13    pub entity_type: String,
14    pub description: String,
15}
16
17/// A directed relation between two entities.
18#[derive(Debug, Clone)]
19pub struct Relation {
20    pub source: String,
21    pub target: String,
22    pub relation_type: String,
23    pub description: String,
24    pub doc_id: Option<String>,
25}
26
27/// A community of entities detected by community detection.
28#[derive(Debug, Clone)]
29pub struct Community {
30    pub id: usize,
31    pub entities: Vec<String>,
32    pub level: usize,
33}
34
35/// In-memory graph store backed by adjacency lists.
36#[derive(Clone)]
37pub struct GraphStore {
38    entities: HashMap<String, Entity>,
39    relations: Vec<Relation>,
40    adjacency: HashMap<String, Vec<usize>>,
41    communities: Vec<Community>,
42    community_summaries: Vec<String>,
43}
44
45impl GraphStore {
46    /// Creates an empty graph store.
47    pub fn new() -> Self {
48        Self {
49            entities: HashMap::new(),
50            relations: Vec::new(),
51            adjacency: HashMap::new(),
52            communities: Vec::new(),
53            community_summaries: Vec::new(),
54        }
55    }
56
57    /// Adds an entity, replacing any existing entity with the same id.
58    pub fn add_entity(&mut self, entity: Entity) {
59        self.entities.insert(entity.id.clone(), entity);
60    }
61
62    /// Adds a directed relation and updates the adjacency list.
63    pub fn add_relation(&mut self, relation: Relation) {
64        let idx = self.relations.len();
65        self.adjacency
66            .entry(relation.source.clone())
67            .or_default()
68            .push(idx);
69        self.adjacency
70            .entry(relation.target.clone())
71            .or_default()
72            .push(idx);
73        self.relations.push(relation);
74    }
75
76    /// Returns a reference to an entity by id.
77    pub fn get_entity(&self, id: &str) -> Option<&Entity> {
78        self.entities.get(id)
79    }
80
81    /// Returns all entity ids.
82    pub fn entity_ids(&self) -> Vec<String> {
83        self.entities.keys().cloned().collect()
84    }
85
86    /// Returns the number of entities.
87    pub fn entity_count(&self) -> usize {
88        self.entities.len()
89    }
90
91    /// Returns the number of relations.
92    pub fn relation_count(&self) -> usize {
93        self.relations.len()
94    }
95
96    /// Returns the neighbor entity ids of a given entity (both directions).
97    pub fn neighbors(&self, entity_id: &str) -> Vec<String> {
98        let mut result = Vec::new();
99        if let Some(indices) = self.adjacency.get(entity_id) {
100            for &idx in indices {
101                let rel = &self.relations[idx];
102                if rel.source == entity_id {
103                    result.push(rel.target.clone());
104                } else {
105                    result.push(rel.source.clone());
106                }
107            }
108        }
109        result.sort();
110        result.dedup();
111        result
112    }
113
114    /// Returns the subgraph around a seed entity: the seed, its direct
115    /// neighbors, and all relations among them.
116    pub fn subgraph(&self, seed: &str, depth: usize) -> (Vec<Entity>, Vec<Relation>) {
117        let mut visited = std::collections::HashSet::new();
118        let mut frontier = vec![seed.to_string()];
119        visited.insert(seed.to_string());
120
121        for _ in 0..depth {
122            let mut next_frontier = Vec::new();
123            for eid in &frontier {
124                for nb in self.neighbors(eid) {
125                    if visited.insert(nb.clone()) {
126                        next_frontier.push(nb);
127                    }
128                }
129            }
130            frontier = next_frontier;
131        }
132
133        let entities: Vec<Entity> = visited
134            .iter()
135            .filter_map(|id| self.entities.get(id))
136            .cloned()
137            .collect();
138
139        let relations: Vec<Relation> = self
140            .relations
141            .iter()
142            .filter(|r| visited.contains(&r.source) && visited.contains(&r.target))
143            .cloned()
144            .collect();
145
146        (entities, relations)
147    }
148
149    /// Returns all relations involving a given entity.
150    pub fn relations_for(&self, entity_id: &str) -> Vec<&Relation> {
151        self.adjacency
152            .get(entity_id)
153            .map(|indices| indices.iter().map(|&i| &self.relations[i]).collect())
154            .unwrap_or_default()
155    }
156
157    /// Returns a reference to all entities.
158    pub fn all_entities(&self) -> &HashMap<String, Entity> {
159        &self.entities
160    }
161
162    /// Returns a reference to all relations.
163    pub fn all_relations(&self) -> &[Relation] {
164        &self.relations
165    }
166
167    // -- Community management --------------------------------------------------
168
169    /// Replaces the stored communities.
170    pub fn set_communities(&mut self, communities: Vec<Community>) {
171        self.communities = communities;
172    }
173
174    /// Returns a reference to the stored communities.
175    pub fn communities(&self) -> &[Community] {
176        &self.communities
177    }
178
179    /// Replaces the stored community summaries.
180    pub fn set_community_summaries(&mut self, summaries: Vec<String>) {
181        self.community_summaries = summaries;
182    }
183
184    /// Returns a reference to the stored community summaries.
185    pub fn community_summaries(&self) -> &[String] {
186        &self.community_summaries
187    }
188
189    /// Returns a mutable reference to the stored community summaries.
190    pub fn community_summaries_mut(&mut self) -> &mut Vec<String> {
191        &mut self.community_summaries
192    }
193}
194
195impl Default for GraphStore {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    fn make_entity(id: &str, name: &str) -> Entity {
206        Entity {
207            id: id.to_string(),
208            name: name.to_string(),
209            entity_type: "concept".to_string(),
210            description: format!("Entity {}", name),
211        }
212    }
213
214    fn make_relation(source: &str, target: &str, rel_type: &str) -> Relation {
215        Relation {
216            source: source.to_string(),
217            target: target.to_string(),
218            relation_type: rel_type.to_string(),
219            description: format!("{} {}", rel_type, target),
220            doc_id: None,
221        }
222    }
223
224    #[test]
225    fn test_add_and_get_entity() {
226        let mut store = GraphStore::new();
227        store.add_entity(make_entity("e1", "Rust"));
228        assert_eq!(store.entity_count(), 1);
229        assert!(store.get_entity("e1").is_some());
230        assert!(store.get_entity("e2").is_none());
231    }
232
233    #[test]
234    fn test_add_entity_replaces_existing() {
235        let mut store = GraphStore::new();
236        store.add_entity(make_entity("e1", "Rust"));
237        store.add_entity(Entity {
238            id: "e1".to_string(),
239            name: "Rust Lang".to_string(),
240            entity_type: "language".to_string(),
241            description: "Updated".to_string(),
242        });
243        assert_eq!(store.entity_count(), 1);
244        assert_eq!(store.get_entity("e1").unwrap().name, "Rust Lang");
245    }
246
247    #[test]
248    fn test_add_relation_and_count() {
249        let mut store = GraphStore::new();
250        store.add_entity(make_entity("e1", "Alice"));
251        store.add_entity(make_entity("e2", "Bob"));
252        store.add_relation(make_relation("e1", "e2", "mentors"));
253        assert_eq!(store.relation_count(), 1);
254    }
255
256    #[test]
257    fn test_neighbors_bidirectional() {
258        let mut store = GraphStore::new();
259        store.add_entity(make_entity("e1", "Alice"));
260        store.add_entity(make_entity("e2", "Bob"));
261        store.add_relation(make_relation("e1", "e2", "mentors"));
262
263        let alice_neighbors = store.neighbors("e1");
264        assert_eq!(alice_neighbors, vec!["e2"]);
265
266        let bob_neighbors = store.neighbors("e2");
267        assert_eq!(bob_neighbors, vec!["e1"]);
268    }
269
270    #[test]
271    fn test_neighbors_dedup() {
272        let mut store = GraphStore::new();
273        store.add_entity(make_entity("e1", "Alice"));
274        store.add_entity(make_entity("e2", "Bob"));
275        store.add_relation(make_relation("e1", "e2", "mentor"));
276        store.add_relation(make_relation("e1", "e2", "collaborator"));
277
278        let neighbors = store.neighbors("e1");
279        assert_eq!(neighbors, vec!["e2"]); // deduped
280    }
281
282    #[test]
283    fn test_subgraph_depth_1() {
284        let mut store = GraphStore::new();
285        store.add_entity(make_entity("e1", "Alice"));
286        store.add_entity(make_entity("e2", "Bob"));
287        store.add_entity(make_entity("e3", "Charlie"));
288        store.add_relation(make_relation("e1", "e2", "mentor"));
289        store.add_relation(make_relation("e2", "e3", "colleague"));
290
291        let (entities, relations) = store.subgraph("e1", 1);
292        // Depth 1 from Alice: Alice + Bob
293        assert_eq!(entities.len(), 2);
294        assert_eq!(relations.len(), 1); // only Alice->Bob
295    }
296
297    #[test]
298    fn test_community_management() {
299        let mut store = GraphStore::new();
300        store.set_communities(vec![Community {
301            id: 0,
302            entities: vec!["e1".to_string(), "e2".to_string()],
303            level: 0,
304        }]);
305        assert_eq!(store.communities().len(), 1);
306
307        store.set_community_summaries(vec!["Community of Alice and Bob".to_string()]);
308        assert_eq!(store.community_summaries().len(), 1);
309        assert_eq!(store.community_summaries()[0], "Community of Alice and Bob");
310    }
311
312    #[test]
313    fn test_entity_ids() {
314        let mut store = GraphStore::new();
315        store.add_entity(make_entity("e1", "A"));
316        store.add_entity(make_entity("e2", "B"));
317        let mut ids = store.entity_ids();
318        ids.sort();
319        assert_eq!(ids, vec!["e1", "e2"]);
320    }
321
322    #[test]
323    fn test_relations_for_entity() {
324        let mut store = GraphStore::new();
325        store.add_entity(make_entity("e1", "Alice"));
326        store.add_entity(make_entity("e2", "Bob"));
327        store.add_entity(make_entity("e3", "Charlie"));
328        store.add_relation(make_relation("e1", "e2", "mentor"));
329        store.add_relation(make_relation("e1", "e3", "advisor"));
330
331        let rels = store.relations_for("e1");
332        assert_eq!(rels.len(), 2);
333    }
334
335    #[test]
336    fn test_default_is_empty() {
337        let store = GraphStore::default();
338        assert_eq!(store.entity_count(), 0);
339        assert_eq!(store.relation_count(), 0);
340        assert!(store.communities().is_empty());
341        assert!(store.community_summaries().is_empty());
342    }
343}