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 request = crate::serve::Request::IngestArchive(crate::serve::IngestArchiveArgs {
27 content: archive_content.to_string(),
28 session_id: session_id.to_string(),
29 log_number,
30 });
31 let report: crate::graph::types::IngestionReport =
32 serde_json::from_value(crate::serve_client::execute(memory_dir, &request).await?)?;
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
53pub async fn sync_pipeline_into_graph(
60 memory_dir: &std::path::Path,
61 docs: crate::graph::types::PipelineDocuments,
62) -> Result<crate::graph::types::PipelineSyncReport, crate::error::RecallError> {
63 let request = crate::serve::Request::SyncPipeline(crate::serve::SyncPipelineArgs { docs });
64 Ok(serde_json::from_value(
65 crate::serve_client::execute(memory_dir, &request).await?,
66 )?)
67}
68
69#[cfg(feature = "pulse-null")]
74pub async fn ingest_into_graph_with_llm(
75 memory_dir: &std::path::Path,
76 archive_content: &str,
77 session_id: &str,
78 log_number: Option<u32>,
79 provider: Option<&dyn pulse_system_types::llm::LmProvider>,
80) -> Result<crate::graph::types::IngestionReport, crate::error::RecallError> {
81 let graph_dir = memory_dir.join("graph");
82 if !graph_dir.exists() {
83 return Err(crate::error::RecallError::NotInitialized(
84 "graph/ not initialized \u{2014} run `graph init` first".into(),
85 ));
86 }
87
88 let report = crate::serve_client::exclusive(memory_dir, |gm| async move {
91 let bridge = provider.map(GraphLlmBridge::new);
92 let llm_ref: Option<&dyn crate::graph::llm::LlmProvider> = bridge
93 .as_ref()
94 .map(|b| b as &dyn crate::graph::llm::LlmProvider);
95
96 Ok(gm
97 .ingest_archive(archive_content, session_id, log_number, llm_ref)
98 .await?)
99 })
100 .await?;
101
102 eprintln!(
103 "recall-echo: graph ingested \u{2014} {} episodes, {} entities created, {} merged, {} skipped, {} relationships",
104 report.episodes_created,
105 report.entities_created,
106 report.entities_merged,
107 report.entities_skipped,
108 report.relationships_created,
109 );
110
111 if !report.errors.is_empty() {
112 eprintln!(
113 "recall-echo: graph ingestion had {} warnings",
114 report.errors.len()
115 );
116 }
117
118 Ok(report)
119}
120
121#[cfg(feature = "pulse-null")]
124pub struct GraphLlmBridge<'a> {
125 provider: &'a dyn pulse_system_types::llm::LmProvider,
126}
127
128#[cfg(feature = "pulse-null")]
129impl<'a> GraphLlmBridge<'a> {
130 pub fn new(provider: &'a dyn pulse_system_types::llm::LmProvider) -> Self {
131 Self { provider }
132 }
133}
134
135#[cfg(feature = "pulse-null")]
136#[async_trait::async_trait]
137impl crate::graph::llm::LlmProvider for GraphLlmBridge<'_> {
138 async fn complete(
139 &self,
140 system_prompt: &str,
141 user_message: &str,
142 max_tokens: u32,
143 ) -> Result<String, crate::graph::error::GraphError> {
144 use pulse_system_types::llm::{Message, MessageContent, Role};
145
146 let messages = vec![Message {
147 role: Role::User,
148 content: MessageContent::Text(user_message.to_string()),
149 source: None,
150 }];
151
152 let response = self
153 .provider
154 .invoke(system_prompt, &messages, max_tokens, None)
155 .await
156 .map_err(|e| crate::graph::error::GraphError::Llm(e.to_string()))?;
157
158 Ok(response.text())
159 }
160}