use std::sync::Arc;
use crate::graph::GraphStorage;
use crate::traversal::step::map_steps::step_similarity::VectorKey;
use crate::traversal::step::map_steps::temporal::{describe, held_value, replaced};
use crate::value::vector::embed::checked_embed;
use crate::value::{EmbedContext, EmbedError, Embedder};
use crate::*;
#[derive(Clone)]
struct StepEmbed {
key: Option<VectorKey>,
requested: Option<CompactString>,
embedder: Option<Arc<dyn Embedder>>,
}
impl fmt::Display for StepEmbed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("embed(")?;
if let Some(key) = &self.key {
f.write_str(&key.literal())?;
}
let name = match &self.embedder {
Some(embedder) => Some(embedder.info().name.as_str()),
None => self.requested.as_deref(),
};
if let Some(name) = name {
if self.key.is_some() {
f.write_str(", ")?;
}
write!(f, "embedder: {}", crate::value::literal::rhai_string(name))?;
}
f.write_str(")")
}
}
fn embed_step<'g, S: GraphStorage + 'g>(
key: Option<PropKey>,
embedder: Option<&str>,
) -> GraphStep<'g, S> {
GraphStep::Map(
"embed",
Box::new(StepEmbed {
key: key.map(VectorKey::new),
requested: embedder.map(CompactString::new),
embedder: None,
}),
)
}
pub(crate) fn embed_failure<S: GraphStorage>(
context: &TraverserContext<'_, S>,
error: EmbedError,
) -> Box<TraverserError> {
if error == EmbedError::Cancelled
&& let Some(deadline) = &context.deadline
&& let Err(stopped) = deadline.check()
{
return stopped;
}
Box::new(TraverserError::Embed(error))
}
impl StepEmbed {
fn text_of<S: GraphStorage>(
&self,
context: &TraverserContext<'_, S>,
value: &TraverserValue<S>,
) -> TraverserResult<Option<String>> {
let text = match &self.key {
Some(key) => {
match key.peek(context, value, |stored| {
stored
.with_text(str::to_string)
.ok_or_else(|| describe(stored))
})? {
None => return Ok(None),
Some(text) => text,
}
}
None => match held_value(context, value)? {
Ok(held) => held
.with_text(str::to_string)
.ok_or_else(|| describe(held.as_ref())),
Err(kind) => Err(format!("a {kind}")),
},
};
match text {
Ok(text) => {
context.check_string_bytes(text.len())?;
Ok(Some(text))
}
Err(found) => Err(Box::new(TraverserError::Embed(EmbedError::not_text(found)))),
}
}
fn embedder(&self) -> TraverserResult<&Arc<dyn Embedder>> {
self.embedder.as_ref().ok_or_else(|| {
Box::new(TraverserError::QueryExecution {
query: self.to_string(),
reason: "embed() ran without a resolved embedder".to_string(),
})
})
}
}
impl<'g, S: GraphStorage + 'g> GraphStepMap<'g, S> for StepEmbed {
fn process(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Traverser<S>> {
self.process_filter(context, source)?.ok_or_else(|| {
Box::new(TraverserError::QueryExecution {
query: self.to_string(),
reason: "embed() dropped a traverser in a context that cannot filter".to_string(),
})
})
}
fn process_filter(
&self,
context: &mut TraverserContext<'_, S>,
source: Traverser<S>,
) -> TraverserResult<Option<Traverser<S>>> {
Ok(self.process_batch(context, vec![source])?.pop())
}
fn batch_size(&self) -> usize {
self.embedder
.as_ref()
.map_or(1, |embedder| embedder.info().max_batch.max(1))
}
fn process_batch(
&self,
context: &mut TraverserContext<'_, S>,
sources: Vec<Traverser<S>>,
) -> TraverserResult<Vec<Traverser<S>>> {
let embedder = self.embedder()?.clone();
let mut kept = Vec::with_capacity(sources.len());
let mut texts = Vec::with_capacity(sources.len());
for source in sources {
if let Some(text) = self.text_of(context, &source.value)? {
texts.push(text);
kept.push(source);
}
}
if kept.is_empty() {
return Ok(kept);
}
let mut out = Vec::with_capacity(kept.len());
let mut kept = kept.into_iter();
for texts in texts.chunks(embedder.info().max_batch.max(1)) {
let refs: Vec<&str> = texts.iter().map(String::as_str).collect();
let vectors = checked_embed(
&*embedder,
&refs,
&EmbedContext::of_deadline(context.deadline.as_ref()),
)
.map_err(|error| embed_failure(context, error))?;
context.tick_n(refs.len())?;
for (vector, source) in vectors.into_iter().zip(kept.by_ref()) {
out.push(replaced(
source,
TraverserValue::Computed(ElementProperty::Vector(vector), None),
));
}
}
Ok(out)
}
fn may_filter(&self) -> bool {
true
}
fn materializes(&self) -> bool {
true
}
fn embedder_request(&self) -> Option<Option<CompactString>> {
Some(self.requested.clone())
}
fn bind_embedder(&mut self, embedder: Arc<dyn Embedder>) {
self.embedder = Some(embedder);
}
fn embedder_name(&self) -> Option<&str> {
match &self.embedder {
Some(embedder) => Some(embedder.info().name.as_str()),
None => self.requested.as_deref(),
}
}
fn clone_box(&self) -> Box<dyn GraphStepMap<'g, S> + 'g> {
Box::new(self.clone())
}
}
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
pub fn embed(self, key: impl Into<PropKey>, embedder: Option<&str>) -> Self {
self.push_step(embed_step(Some(key.into()), embedder))
}
pub fn embed_value(self, embedder: Option<&str>) -> Self {
self.push_step(embed_step(None, embedder))
}
}
impl __ {
pub fn embed<'g, S: GraphStorage + 'g>(
key: impl Into<PropKey>,
embedder: Option<&str>,
) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().embed(key, embedder)
}
pub fn embed_value<'g, S: GraphStorage + 'g>(
embedder: Option<&str>,
) -> AnonymousTraversal<'g, S> {
AnonymousTraversal::new().embed_value(embedder)
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
pub fn embed(self, key: impl Into<PropKey>, embedder: Option<&str>) -> Self {
self.push_step(embed_step(Some(key.into()), embedder))
}
pub fn embed_value(self, embedder: Option<&str>) -> Self {
self.push_step(embed_step(None, embedder))
}
}