recall_echo/
graph_bridge.rs1#[allow(unused_imports)]
7use crate::graph::error::GraphError;
8
9pub async fn ingest_into_graph(
14 memory_dir: &std::path::Path,
15 archive_content: &str,
16 session_id: &str,
17 log_number: Option<u32>,
18) -> Result<crate::graph::types::IngestionReport, String> {
19 let graph_dir = memory_dir.join("graph");
20 if !graph_dir.exists() {
21 return Err("graph/ not initialized \u{2014} run `graph init` first".into());
22 }
23
24 let gm = crate::graph::GraphMemory::open(&graph_dir)
25 .await
26 .map_err(|e| format!("graph open: {e}"))?;
27
28 let report = gm
30 .ingest_archive(archive_content, session_id, log_number, None)
31 .await
32 .map_err(|e| format!("ingestion: {e}"))?;
33
34 eprintln!(
35 "recall-echo: graph ingested \u{2014} {} episodes, {} entities created, {} merged, {} skipped, {} relationships",
36 report.episodes_created,
37 report.entities_created,
38 report.entities_merged,
39 report.entities_skipped,
40 report.relationships_created,
41 );
42
43 if !report.errors.is_empty() {
44 eprintln!(
45 "recall-echo: graph ingestion had {} warnings",
46 report.errors.len()
47 );
48 }
49
50 Ok(report)
51}
52
53#[cfg(feature = "pulse-null")]
58pub async fn ingest_into_graph_with_llm(
59 memory_dir: &std::path::Path,
60 archive_content: &str,
61 session_id: &str,
62 log_number: Option<u32>,
63 provider: Option<&dyn pulse_system_types::llm::LmProvider>,
64) -> Result<crate::graph::types::IngestionReport, String> {
65 let graph_dir = memory_dir.join("graph");
66 if !graph_dir.exists() {
67 return Err("graph/ not initialized \u{2014} run `graph init` first".into());
68 }
69
70 let gm = crate::graph::GraphMemory::open(&graph_dir)
71 .await
72 .map_err(|e| format!("graph open: {e}"))?;
73
74 let bridge = provider.map(GraphLlmBridge::new);
75 let llm_ref: Option<&dyn crate::graph::llm::LlmProvider> = bridge
76 .as_ref()
77 .map(|b| b as &dyn crate::graph::llm::LlmProvider);
78
79 let report = gm
80 .ingest_archive(archive_content, session_id, log_number, llm_ref)
81 .await
82 .map_err(|e| format!("ingestion: {e}"))?;
83
84 eprintln!(
85 "recall-echo: graph ingested \u{2014} {} episodes, {} entities created, {} merged, {} skipped, {} relationships",
86 report.episodes_created,
87 report.entities_created,
88 report.entities_merged,
89 report.entities_skipped,
90 report.relationships_created,
91 );
92
93 if !report.errors.is_empty() {
94 eprintln!(
95 "recall-echo: graph ingestion had {} warnings",
96 report.errors.len()
97 );
98 }
99
100 Ok(report)
101}
102
103#[cfg(feature = "pulse-null")]
106pub struct GraphLlmBridge<'a> {
107 provider: &'a dyn pulse_system_types::llm::LmProvider,
108}
109
110#[cfg(feature = "pulse-null")]
111impl<'a> GraphLlmBridge<'a> {
112 pub fn new(provider: &'a dyn pulse_system_types::llm::LmProvider) -> Self {
113 Self { provider }
114 }
115}
116
117#[cfg(feature = "pulse-null")]
118#[async_trait::async_trait]
119impl crate::graph::llm::LlmProvider for GraphLlmBridge<'_> {
120 async fn complete(
121 &self,
122 system_prompt: &str,
123 user_message: &str,
124 max_tokens: u32,
125 ) -> Result<String, GraphError> {
126 use pulse_system_types::llm::{Message, MessageContent, Role};
127
128 let messages = vec![Message {
129 role: Role::User,
130 content: MessageContent::Text(user_message.to_string()),
131 }];
132
133 let response = self
134 .provider
135 .invoke(system_prompt, &messages, max_tokens, None)
136 .await
137 .map_err(|e| GraphError::Llm(e.to_string()))?;
138
139 Ok(response.text())
140 }
141}