Current section

Files

Jump to
torus lib torus search semantic.ex
Raw

lib/torus/search/semantic.ex

defmodule Torus.Search.Semantic do
@moduledoc false
import Torus.Search.Common
@vector_operators_map %{
l2_distance: "<->",
max_inner_product: "<#>",
cosine_distance: "<=>",
l1_distance: "<+>",
hamming_distance: "<~>",
jaccard_distance: "<%>"
}
@distance_types Map.keys(@vector_operators_map)
@order_types ~w[asc desc none]a
def semantic(query, bindings, qualifier, vector_term, opts \\ []) do
# Arguments fetching
distance = get_arg!(opts, :distance, :l2_distance, @distance_types)
order = get_arg!(opts, :order, :asc, @order_types)
operator = Map.fetch!(@vector_operators_map, distance)
pre_filter = Keyword.get(opts, :pre_filter, :none)
order_operator = if order == :desc, do: ">", else: "<"
distance_key = Keyword.get(opts, :distance_key, :none)
# Validations
if not is_atom(distance_key) do
raise "The `distance_key` option must be an atom."
end
# Since we need to generate a valid macro - we need to "trick" it to think that
# we'll pass `asc`/`desc` instead of `:none`
# This is temporary, until we'll generate raw strings
asc_desc = if order == :desc, do: :desc, else: :asc
# Query building
quote do
if not is_struct(unquote(vector_term), Pgvector) do
raise """
`vector_term` should be a Pgvector struct.
The best way to generate it is to use `Torus.to_vector/1,2` or `Torus.to_vectors/1,2` functions.
"""
end
unquote(query)
|> apply_if(
is_float(unquote(pre_filter)),
fn query ->
where(
query,
[unquote_splicing(bindings)],
operator(
operator(unquote(qualifier), unquote(operator), ^unquote(vector_term)),
unquote(order_operator),
unquote(pre_filter)
)
)
end
)
|> apply_if(unquote(order) != :none, fn query ->
order_by(
query,
[unquote_splicing(bindings)],
{unquote(asc_desc),
operator(unquote(qualifier), unquote(operator), ^unquote(vector_term))}
)
end)
|> apply_if(
unquote(distance_key) != :none,
fn query ->
select_merge(
query,
[unquote_splicing(bindings)],
%{
unquote(distance_key) =>
operator(unquote(qualifier), unquote(operator), ^unquote(vector_term))
}
)
end
)
end
end
def to_vectors(terms, opts \\ []) do
embedding_module =
if opts[:embedding_module] do
opts[:embedding_module]
else
Application.fetch_env!(:torus, :embedding_module)
end
terms = List.wrap(terms)
if Code.ensure_loaded?(embedding_module) &&
function_exported?(embedding_module, :generate, 2) do
embedding_module.generate(terms, opts)
else
raise "`embedding_module` must implement the `Torus.Embedding` behaviour"
end
end
def to_vector(term, opts \\ []) do
term |> to_vectors(opts) |> List.first()
end
def embedding_model(opts \\ []) do
embedding_module =
if opts[:embedding_module] do
opts[:embedding_module]
else
Application.fetch_env!(:torus, :embedding_module)
end
if Code.ensure_loaded?(embedding_module) &&
function_exported?(embedding_module, :embedding_model, 1) do
embedding_module.embedding_model(opts)
else
raise "`embedding_module` must implement the `Torus.Embedding` behaviour"
end
end
end