use crate::chunking;
use crate::cli::{EmbeddingBackendChoice, LlmBackendChoice};
use crate::errors::AppError;
use crate::output;
use crate::paths::AppPaths;
pub(crate) struct EmbedPhaseOutcome {
pub embedding: Option<Vec<f32>>,
pub backend_invoked_passage: Option<&'static str>,
pub chunk_embeddings_cache: Option<Vec<Vec<f32>>>,
pub graph_entity_embeddings: Vec<Vec<f32>>,
pub skip_embed: bool,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_embed_phase(
paths: &AppPaths,
raw_body: &str,
chunks_info: &[crate::chunking::Chunk],
graph: &super::graph_input::GraphInput,
args: &super::args::RememberArgs,
embedding_backend: EmbeddingBackendChoice,
llm_backend: LlmBackendChoice,
) -> Result<EmbedPhaseOutcome, AppError> {
output::emit_progress_i18n("Computing embedding...", "Calculando embedding...");
let mut chunk_embeddings_cache: Option<Vec<Vec<f32>>> = None;
let skip_embed = crate::embedder::should_skip_embedding_on_failure();
let (embedding, backend_invoked_passage): (Option<Vec<f32>>, Option<&str>) = if chunks_info
.len()
== 1
{
match crate::embedder::embed_passage_with_embedding_choice(
&paths.models,
raw_body,
embedding_backend,
llm_backend,
) {
Ok((v, k)) if v.is_empty() => (None, Some(k.as_str())),
Ok((v, k)) => (Some(v), Some(k.as_str())),
Err(
e @ (AppError::Validation(_)
| AppError::BodyTooLarge { .. }
| AppError::TooManyTokens { .. }),
) => return Err(e),
Err(e) if skip_embed => {
tracing::warn!(error = %e, "embedding failed; --skip-embedding-on-failure active, persisting without embedding");
(None, None)
}
Err(e) => return Err(e),
}
} else {
let chunk_texts: Vec<String> = chunks_info
.iter()
.map(|c| chunking::chunk_text(raw_body, c).to_string())
.collect();
output::emit_progress_i18n(
&format!(
"Embedding {} chunks in parallel batches (parallelism {})...",
chunks_info.len(),
args.llm_parallelism
),
&format!(
"Embedding {} chunks em lotes paralelos (paralelismo {})...",
chunks_info.len(),
args.llm_parallelism
),
);
if let Some(rss) = crate::memory_guard::current_process_memory_mb() {
if rss > args.max_rss_mb {
tracing::error!(target: "remember",
rss_mb = rss,
max_rss_mb = args.max_rss_mb,
"RSS exceeded --max-rss-mb threshold; aborting to prevent system instability"
);
return Err(AppError::LowMemory {
available_mb: crate::memory_guard::available_memory_mb(),
required_mb: args.max_rss_mb,
});
}
}
match crate::embedder::embed_passages_parallel_with_embedding_choice(
&paths.models,
&chunk_texts,
args.llm_parallelism as usize,
crate::embedder::chunk_embed_batch_size(),
embedding_backend,
llm_backend,
) {
Ok(chunk_embeddings) => {
output::emit_progress_i18n(
&format!(
"Remember stage: chunk embeddings complete; process RSS {} MB",
crate::memory_guard::current_process_memory_mb().unwrap_or(0)
),
&format!(
"Stage remember: chunk embeddings completed; process RSS {} MB",
crate::memory_guard::current_process_memory_mb().unwrap_or(0)
),
);
let aggregated = chunking::aggregate_embeddings(&chunk_embeddings);
chunk_embeddings_cache = Some(chunk_embeddings);
(Some(aggregated), None)
}
Err(e) if skip_embed => {
tracing::warn!(error = %e, "chunk embedding failed; --skip-embedding-on-failure active, persisting without embedding");
(None, None)
}
Err(e) => return Err(e),
}
};
let entity_texts: Vec<String> = graph
.entities
.iter()
.map(|entity| match &entity.description {
Some(desc) => format!("{} {}", entity.name, desc),
None => entity.name.clone(),
})
.collect();
let (graph_entity_embeddings, embed_cache_stats) =
match crate::embedder::embed_entity_texts_cached(
&paths.models,
&entity_texts,
args.llm_parallelism as usize,
embedding_backend,
llm_backend,
) {
Ok(r) => r,
Err(e) if skip_embed => {
tracing::warn!(error = %e, "entity embedding failed; --skip-embedding-on-failure active");
let empty: Vec<Vec<f32>> = entity_texts.iter().map(|_| vec![]).collect();
(empty, crate::embedder::EmbedCacheStats::default())
}
Err(e) => return Err(e),
};
if embed_cache_stats.hits > 0 {
tracing::debug!(
hits = embed_cache_stats.hits,
misses = embed_cache_stats.misses,
requested = embed_cache_stats.requested,
"G56: entity embed cache hit (remember)"
);
}
Ok(EmbedPhaseOutcome {
embedding,
backend_invoked_passage,
chunk_embeddings_cache,
graph_entity_embeddings,
skip_embed,
})
}