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