1use crate::domain::{Edge, Properties, PropertyValue, string_to_node_id};
2use crate::services::kb::domain::{KnowledgeBase, LinkType, LuhmannId, Note, NoteId, NoteLink, AgentId};
3use crate::storage::{GraphStorage, StorageError, EdgeDirection};
4use async_trait::async_trait;
5use thiserror::Error;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8pub mod domain;
9
10#[derive(Error, Debug)]
11pub enum KbError {
12 #[error("Note not found: {0}")]
13 NoteNotFound(NoteId),
14
15 #[error("Knowledge base not found for agent: {0}")]
16 KnowledgeBaseNotFound(AgentId),
17
18 #[error("Agent not found: {0}")]
19 AgentNotFound(AgentId),
20
21 #[error("Invalid link type: {0}")]
22 InvalidLinkType(String),
23
24 #[error("Storage error: {0}")]
25 Storage(#[from] StorageError),
26
27 #[error("Note already linked")]
28 AlreadyLinked,
29
30 #[error("Cannot link note to itself")]
31 SelfLink,
32}
33
34pub type Result<T> = std::result::Result<T, KbError>;
35
36#[async_trait]
37pub trait KnowledgeBaseService: Send + Sync {
38 async fn create_knowledge_base(&self, agent_id: AgentId, name: impl Into<String> + Send) -> Result<KnowledgeBase>;
40 async fn get_knowledge_base(&self, agent_id: AgentId) -> Result<KnowledgeBase>;
41
42 async fn create_note(
44 &self,
45 agent_id: AgentId,
46 title: impl Into<String> + Send,
47 content: impl Into<String> + Send,
48 ) -> Result<Note>;
49
50 async fn get_note(&self, note_id: NoteId) -> Result<Note>;
51 async fn update_note(&self, note: &Note) -> Result<Note>;
52 async fn delete_note(&self, note_id: NoteId) -> Result<()>;
53 async fn list_agent_notes(&self, agent_id: AgentId) -> Result<Vec<Note>>;
54 async fn search_notes(&self, agent_id: AgentId, query: &str) -> Result<Vec<Note>>;
55
56 async fn add_tag(&self, note_id: NoteId, tag: impl Into<String> + Send) -> Result<Note>;
58 async fn remove_tag(&self, note_id: NoteId, tag: &str) -> Result<Note>;
59 async fn list_notes_by_tag(&self, agent_id: AgentId, tag: &str) -> Result<Vec<Note>>;
60 async fn get_all_tags(&self, agent_id: AgentId) -> Result<Vec<String>>;
61
62 async fn link_notes(
64 &self,
65 from_note_id: NoteId,
66 to_note_id: NoteId,
67 link_type: LinkType,
68 context: Option<String>,
69 ) -> Result<()>;
70
71 async fn unlink_notes(&self, from_note_id: NoteId, to_note_id: NoteId, link_type: LinkType) -> Result<()>;
72 async fn get_links_from(&self, note_id: NoteId, link_type: Option<LinkType>) -> Result<Vec<NoteLink>>;
73 async fn get_links_to(&self, note_id: NoteId, link_type: Option<LinkType>) -> Result<Vec<NoteLink>>;
74 async fn get_backlinks(&self, note_id: NoteId) -> Result<Vec<Note>>;
75
76 async fn get_related_notes(&self, note_id: NoteId, depth: usize) -> Result<Vec<Note>>;
78 async fn find_path(&self, start_note_id: NoteId, end_note_id: NoteId, max_depth: usize) -> Result<Option<Vec<NoteId>>>;
79 async fn get_note_graph(&self, note_id: NoteId, depth: usize) -> Result<NoteGraph>;
80
81 async fn create_note_with_luhmann_id(
83 &self,
84 agent_id: AgentId,
85 luhmann_id: LuhmannId,
86 title: impl Into<String> + Send,
87 content: impl Into<String> + Send,
88 ) -> Result<Note>;
89
90 async fn create_note_branch(
91 &self,
92 agent_id: AgentId,
93 parent_note_id: NoteId,
94 title: impl Into<String> + Send,
95 content: impl Into<String> + Send,
96 ) -> Result<Note>;
97
98 async fn get_note_by_luhmann_id(&self, agent_id: AgentId, luhmann_id: &LuhmannId) -> Result<Option<Note>>;
99 async fn get_next_available_id(&self, agent_id: AgentId, parent_id: Option<&LuhmannId>) -> Result<LuhmannId>;
100 async fn list_notes_by_luhmann_prefix(&self, agent_id: AgentId, prefix: &LuhmannId) -> Result<Vec<Note>>;
101}
102
103#[derive(Debug, Clone)]
105pub struct NoteGraph {
106 pub center_note_id: NoteId,
107 pub notes: Vec<Note>,
108 pub links: Vec<NoteLink>,
109}
110
111pub struct KnowledgeBaseServiceImpl<S: GraphStorage> {
112 storage: S,
113}
114
115impl<S: GraphStorage> KnowledgeBaseServiceImpl<S> {
116 pub fn new(storage: S) -> Self {
117 Self { storage }
118 }
119
120 async fn note_exists(&self, note_id: NoteId) -> Result<bool> {
121 match self.storage.get_node(note_id).await {
122 Ok(node) => Ok(Note::from_node(&node).is_some()),
123 Err(StorageError::NodeNotFound(_)) => Ok(false),
124 Err(e) => Err(KbError::Storage(e)),
125 }
126 }
127}
128
129#[async_trait]
130impl<S: GraphStorage> KnowledgeBaseService for KnowledgeBaseServiceImpl<S> {
131 async fn create_knowledge_base(&self, agent_id: AgentId, name: impl Into<String> + Send) -> Result<KnowledgeBase> {
132 let agent_id_for_err = agent_id.clone();
134 let node_id = string_to_node_id(&agent_id);
136 let node = self.storage.get_node(node_id).await
137 .map_err(|e| match e {
138 StorageError::NodeNotFound(_) => KbError::AgentNotFound(agent_id_for_err),
139 _ => KbError::Storage(e),
140 })?;
141
142 let mut kb = KnowledgeBase::new(agent_id.clone(), name);
144 kb.agent_id = agent_id.clone(); let mut updated_node = node.clone();
148 updated_node.properties.insert("kb_name".to_string(), PropertyValue::String(kb.name.clone()));
149 updated_node.properties.insert("kb_enabled".to_string(), PropertyValue::Boolean(true));
150 updated_node.properties.insert("next_main_id".to_string(), PropertyValue::Integer(1));
151
152 self.storage.update_node(&updated_node).await?;
153 Ok(kb)
154 }
155
156 async fn get_knowledge_base(&self, agent_id: AgentId) -> Result<KnowledgeBase> {
157 let agent_id_err = agent_id.clone();
158 let node_id = string_to_node_id(&agent_id);
159 let node = self.storage.get_node(node_id).await
160 .map_err(|e| match e {
161 StorageError::NodeNotFound(_) => KbError::KnowledgeBaseNotFound(agent_id_err),
162 _ => KbError::Storage(e),
163 })?;
164
165 let kb_enabled = node.get_property("kb_enabled")
167 .and_then(|v| match v {
168 PropertyValue::Boolean(b) => Some(*b),
169 _ => None,
170 })
171 .unwrap_or(false);
172
173 if !kb_enabled {
174 return Err(KbError::KnowledgeBaseNotFound(agent_id.clone()));
175 }
176
177 let name = node.get_property("kb_name")
178 .and_then(|v| v.as_str())
179 .unwrap_or("Untitled KB")
180 .to_string();
181
182 let next_main_id = node.get_property("next_main_id")
183 .and_then(|v| match v {
184 PropertyValue::Integer(n) => Some(*n as u32),
185 _ => Some(1),
186 })
187 .unwrap_or(1);
188
189 let agent_id = node.get_property("agent_id")
191 .and_then(|v| v.as_str())
192 .map(String::from)
193 .unwrap_or_else(|| node.id.to_string());
194
195 Ok(KnowledgeBase {
196 agent_id,
197 name,
198 description: None,
199 created_at: node.created_at,
200 next_main_id,
201 })
202 }
203
204 async fn create_note(
205 &self,
206 agent_id: AgentId,
207 title: impl Into<String> + Send,
208 content: impl Into<String> + Send,
209 ) -> Result<Note> {
210 let agent_id_for_err = agent_id.clone();
212
213 let _ = self.storage.get_node(string_to_node_id(&agent_id)).await
215 .map_err(|e| match e {
216 StorageError::NodeNotFound(_) => KbError::AgentNotFound(agent_id_for_err),
217 _ => KbError::Storage(e),
218 })?;
219
220 let luhmann_id = self.get_next_available_id(agent_id.clone(), None).await?;
222
223 let note = Note::new(agent_id.clone(), title, content)
224 .with_luhmann_id(luhmann_id);
225 let node = note.to_node();
226 self.storage.create_node(&node).await?;
227
228 let edge = Edge::new(
230 "owns_note",
231 string_to_node_id(&agent_id),
232 note.id,
233 Properties::new(),
234 );
235 self.storage.create_edge(&edge).await?;
236
237 Ok(note)
238 }
239
240 async fn get_note(&self, note_id: NoteId) -> Result<Note> {
241 let node = self.storage.get_node(note_id).await
242 .map_err(|e| match e {
243 StorageError::NodeNotFound(_) => KbError::NoteNotFound(note_id),
244 _ => KbError::Storage(e),
245 })?;
246
247 Note::from_node(&node)
248 .ok_or_else(|| KbError::NoteNotFound(note_id))
249 }
250
251 async fn update_note(&self, note: &Note) -> Result<Note> {
252 self.get_note(note.id).await?;
254
255 let node = note.to_node();
256 self.storage.update_node(&node).await?;
257 Ok(note.clone())
258 }
259
260 async fn delete_note(&self, note_id: NoteId) -> Result<()> {
261 self.get_note(note_id).await?;
263
264 self.storage.delete_node(note_id).await?;
266 Ok(())
267 }
268
269 async fn list_agent_notes(&self, agent_id: AgentId) -> Result<Vec<Note>> {
270 let agent_id_err = agent_id.clone();
272
273 let agent_node_id = string_to_node_id(&agent_id);
275 let _ = self.storage.get_node(agent_node_id).await
276 .map_err(|e| match e {
277 StorageError::NodeNotFound(_) => KbError::AgentNotFound(agent_id_err),
278 _ => KbError::Storage(e),
279 })?;
280
281 let notes = self.storage
283 .get_neighbors(agent_node_id, Some("owns_note"), EdgeDirection::Outgoing)
284 .await?;
285
286 let notes: Vec<Note> = notes.iter()
287 .filter_map(Note::from_node)
288 .collect();
289
290 Ok(notes)
291 }
292
293 async fn search_notes(&self, agent_id: AgentId, query: &str) -> Result<Vec<Note>> {
294 let all_notes = self.list_agent_notes(agent_id).await?;
295 let query_lower = query.to_lowercase();
296
297 let filtered: Vec<Note> = all_notes.into_iter()
298 .filter(|note| {
299 note.title.to_lowercase().contains(&query_lower) ||
300 note.content.to_lowercase().contains(&query_lower) ||
301 note.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower))
302 })
303 .collect();
304
305 Ok(filtered)
306 }
307
308 async fn add_tag(&self, note_id: NoteId, tag: impl Into<String> + Send) -> Result<Note> {
309 let mut note = self.get_note(note_id).await?;
310 note.add_tag(tag);
311 self.update_note(¬e).await?;
312 Ok(note)
313 }
314
315 async fn remove_tag(&self, note_id: NoteId, tag: &str) -> Result<Note> {
316 let mut note = self.get_note(note_id).await?;
317 note.remove_tag(tag);
318 self.update_note(¬e).await?;
319 Ok(note)
320 }
321
322 async fn list_notes_by_tag(&self, agent_id: AgentId, tag: &str) -> Result<Vec<Note>> {
323 let all_notes = self.list_agent_notes(agent_id).await?;
324 let filtered: Vec<Note> = all_notes.into_iter()
325 .filter(|note| note.tags.contains(&tag.to_string()))
326 .collect();
327 Ok(filtered)
328 }
329
330 async fn get_all_tags(&self, agent_id: AgentId) -> Result<Vec<String>> {
331 let notes = self.list_agent_notes(agent_id).await?;
332 let mut tags = HashSet::new();
333 for note in notes {
334 for tag in note.tags {
335 tags.insert(tag);
336 }
337 }
338 let mut tags: Vec<String> = tags.into_iter().collect();
339 tags.sort();
340 Ok(tags)
341 }
342
343 async fn link_notes(
344 &self,
345 from_note_id: NoteId,
346 to_note_id: NoteId,
347 link_type: LinkType,
348 context: Option<String>,
349 ) -> Result<()> {
350 if from_note_id == to_note_id {
351 return Err(KbError::SelfLink);
352 }
353
354 self.get_note(from_note_id).await?;
356 self.get_note(to_note_id).await?;
357
358 let mut props = Properties::new();
360 if let Some(ctx) = context {
361 props.insert("context".to_string(), crate::domain::PropertyValue::String(ctx));
362 }
363
364 let edge = Edge::new(
365 link_type.as_str(),
366 from_note_id,
367 to_note_id,
368 props,
369 );
370
371 self.storage.create_edge(&edge).await?;
372 Ok(())
373 }
374
375 async fn unlink_notes(&self, from_note_id: NoteId, to_note_id: NoteId, link_type: LinkType) -> Result<()> {
376 let edges = self.storage.get_edges_from(from_note_id, Some(link_type.as_str())).await?;
378
379 for edge in edges {
381 if edge.to_node_id == to_note_id {
382 self.storage.delete_edge(edge.id).await?;
383 return Ok(());
384 }
385 }
386
387 Err(KbError::NoteNotFound(to_note_id))
388 }
389
390 async fn get_links_from(&self, note_id: NoteId, link_type: Option<LinkType>) -> Result<Vec<NoteLink>> {
391 let edges = if let Some(lt) = link_type {
392 self.storage.get_edges_from(note_id, Some(lt.as_str())).await?
393 } else {
394 self.storage.get_edges_from(note_id, None).await?
395 };
396
397 let links: Vec<NoteLink> = edges.iter()
398 .filter_map(|edge| {
399 LinkType::from_str(&edge.edge_type).map(|lt| {
400 let context = edge.properties.get("context")
401 .and_then(|v| v.as_str())
402 .map(String::from);
403
404 NoteLink::new(edge.from_node_id, edge.to_node_id, lt, context)
405 })
406 })
407 .collect();
408
409 Ok(links)
410 }
411
412 async fn get_links_to(&self, note_id: NoteId, link_type: Option<LinkType>) -> Result<Vec<NoteLink>> {
413 let edges = if let Some(lt) = link_type {
414 self.storage.get_edges_to(note_id, Some(lt.as_str())).await?
415 } else {
416 self.storage.get_edges_to(note_id, None).await?
417 };
418
419 let links: Vec<NoteLink> = edges.iter()
420 .filter_map(|edge| {
421 LinkType::from_str(&edge.edge_type).map(|lt| {
422 let context = edge.properties.get("context")
423 .and_then(|v| v.as_str())
424 .map(String::from);
425
426 NoteLink::new(edge.from_node_id, edge.to_node_id, lt, context)
427 })
428 })
429 .collect();
430
431 Ok(links)
432 }
433
434 async fn get_backlinks(&self, note_id: NoteId) -> Result<Vec<Note>> {
435 let edges = self.storage.get_edges_to(note_id, None).await?;
437
438 let mut notes = Vec::new();
439 for edge in edges {
440 if let Ok(note) = self.get_note(edge.from_node_id).await {
441 notes.push(note);
442 }
443 }
444
445 Ok(notes)
446 }
447
448 async fn get_related_notes(&self, note_id: NoteId, depth: usize) -> Result<Vec<Note>> {
449 if depth == 0 {
450 return Ok(vec![]);
451 }
452
453 let mut visited = HashSet::new();
454 let mut result = Vec::new();
455 let mut queue = VecDeque::new();
456
457 queue.push_back((note_id, 0usize));
458 visited.insert(note_id);
459
460 while let Some((current_id, current_depth)) = queue.pop_front() {
461 if current_depth >= depth {
462 continue;
463 }
464
465 let neighbors = self.storage
467 .get_neighbors(current_id, None, EdgeDirection::Both)
468 .await?;
469
470 for neighbor in neighbors {
471 if visited.insert(neighbor.id) {
472 if let Some(note) = Note::from_node(&neighbor) {
473 result.push(note);
474 queue.push_back((neighbor.id, current_depth + 1));
475 }
476 }
477 }
478 }
479
480 Ok(result)
481 }
482
483 async fn find_path(&self, start_note_id: NoteId, end_note_id: NoteId, max_depth: usize) -> Result<Option<Vec<NoteId>>> {
484 if start_note_id == end_note_id {
485 return Ok(Some(vec![start_note_id]));
486 }
487
488 let mut visited = HashSet::new();
489 let mut queue = VecDeque::new();
490 let mut parent_map: HashMap<NoteId, NoteId> = HashMap::new();
491
492 queue.push_back((start_note_id, 0usize));
493 visited.insert(start_note_id);
494
495 while let Some((current_id, depth)) = queue.pop_front() {
496 if depth >= max_depth {
497 continue;
498 }
499
500 let neighbors = self.storage
502 .get_neighbors(current_id, None, EdgeDirection::Outgoing)
503 .await?;
504
505 for neighbor in neighbors {
506 if visited.insert(neighbor.id) {
507 parent_map.insert(neighbor.id, current_id);
508
509 if neighbor.id == end_note_id {
510 let mut path = vec![end_note_id];
512 let mut current = end_note_id;
513
514 while let Some(&parent) = parent_map.get(¤t) {
515 path.push(parent);
516 current = parent;
517 }
518
519 path.reverse();
520 return Ok(Some(path));
521 }
522
523 queue.push_back((neighbor.id, depth + 1));
524 }
525 }
526 }
527
528 Ok(None)
529 }
530
531 async fn get_note_graph(&self, note_id: NoteId, depth: usize) -> Result<NoteGraph> {
532 let center_note = self.get_note(note_id).await?;
533 let related_notes = self.get_related_notes(note_id, depth).await?;
534
535 let mut notes = vec![center_note.clone()];
536 notes.extend(related_notes);
537
538 let mut links = Vec::new();
540 let note_ids: HashSet<NoteId> = notes.iter().map(|n| n.id).collect();
541
542 for note in ¬es {
543 let outgoing = self.get_links_from(note.id, None).await?;
544 for link in outgoing {
545 if note_ids.contains(&link.to_note_id) {
546 links.push(link);
547 }
548 }
549 }
550
551 Ok(NoteGraph {
552 center_note_id: note_id,
553 notes,
554 links,
555 })
556 }
557
558 async fn create_note_with_luhmann_id(
559 &self,
560 agent_id: AgentId,
561 luhmann_id: LuhmannId,
562 title: impl Into<String> + Send,
563 content: impl Into<String> + Send,
564 ) -> Result<Note> {
565 let agent_id_for_err = agent_id.clone();
567
568 let _ = self.storage.get_node(string_to_node_id(&agent_id)).await
570 .map_err(|e| match e {
571 StorageError::NodeNotFound(_) => KbError::AgentNotFound(agent_id_for_err),
572 _ => KbError::Storage(e),
573 })?;
574
575 let note = Note::new(agent_id.clone(), title, content)
576 .with_luhmann_id(luhmann_id);
577 let node = note.to_node();
578 self.storage.create_node(&node).await?;
579
580 let edge = Edge::new(
582 "owns_note",
583 string_to_node_id(&agent_id),
584 note.id,
585 Properties::new(),
586 );
587 self.storage.create_edge(&edge).await?;
588
589 Ok(note)
590 }
591
592 async fn create_note_branch(
593 &self,
594 agent_id: AgentId,
595 parent_note_id: NoteId,
596 title: impl Into<String> + Send,
597 content: impl Into<String> + Send,
598 ) -> Result<Note> {
599 let parent_note = self.get_note(parent_note_id).await?;
601
602 let parent_luhmann_id = parent_note.luhmann_id
603 .ok_or_else(|| KbError::NoteNotFound(parent_note_id))?;
604
605 let child_id = self.get_next_available_id(agent_id.clone(), Some(&parent_luhmann_id)).await?;
607
608 let note = self.create_note_with_luhmann_id(
610 agent_id,
611 child_id.clone(),
612 title,
613 content,
614 ).await?;
615
616 self.link_notes(note.id, parent_note_id, LinkType::References, Some(format!("Branch of {}", parent_luhmann_id))).await?;
618
619 Ok(note)
620 }
621
622 async fn get_note_by_luhmann_id(&self, agent_id: AgentId, luhmann_id: &LuhmannId) -> Result<Option<Note>> {
623 let notes = self.list_agent_notes(agent_id).await?;
625
626 Ok(notes.into_iter()
627 .find(|note| note.luhmann_id.as_ref() == Some(luhmann_id)))
628 }
629
630 async fn get_next_available_id(&self, agent_id: AgentId, parent_id: Option<&LuhmannId>) -> Result<LuhmannId> {
631 let all_notes = self.list_agent_notes(agent_id).await?;
632
633 let existing_ids: Vec<LuhmannId> = all_notes
635 .iter()
636 .filter_map(|note| note.luhmann_id.clone())
637 .filter(|id| {
638 if let Some(parent) = parent_id {
639 id.parent().as_ref() == Some(parent)
641 } else {
642 id.parent().is_none()
644 }
645 })
646 .collect();
647
648 if let Some(parent) = parent_id {
649 if existing_ids.is_empty() {
651 Ok(parent.first_child())
653 } else {
654 let mut sorted = existing_ids.clone();
656 sorted.sort();
657 if let Some(last) = sorted.last() {
658 Ok(last.next_sibling()
659 .unwrap_or_else(|| last.first_child()))
660 } else {
661 Ok(parent.first_child())
662 }
663 }
664 } else {
665 if existing_ids.is_empty() {
667 Ok(LuhmannId { parts: vec![crate::services::kb::domain::LuhmannPart::Number(1)] })
668 } else {
669 let mut sorted = existing_ids;
670 sorted.sort();
671 if let Some(last) = sorted.last() {
672 Ok(last.next_sibling()
673 .unwrap_or_else(|| LuhmannId { parts: vec![crate::services::kb::domain::LuhmannPart::Number(1)] }))
674 } else {
675 Ok(LuhmannId { parts: vec![crate::services::kb::domain::LuhmannPart::Number(1)] })
676 }
677 }
678 }
679 }
680
681 async fn list_notes_by_luhmann_prefix(&self, agent_id: AgentId, prefix: &LuhmannId) -> Result<Vec<Note>> {
682 let all_notes = self.list_agent_notes(agent_id).await?;
683
684 let filtered: Vec<Note> = all_notes
686 .into_iter()
687 .filter(|note| {
688 if let Some(ref lid) = note.luhmann_id {
689 lid.is_descendant_of(prefix) || lid == prefix
690 } else {
691 false
692 }
693 })
694 .collect();
695
696 Ok(filtered)
697 }
698}