Add runtime tasks to regenerate embeddings

This commit is contained in:
Claudio Ortolina
2025-10-12 13:47:28 +02:00
parent ee199272b1
commit e17a445574
2 changed files with 24 additions and 96 deletions
@@ -1,96 +0,0 @@
defmodule Mix.Tasks.MusicLibrary.GenerateEmbeddings do
@moduledoc """
Generates embeddings for records.
## Usage
# Generate embeddings for all records without embeddings
mix music_library.generate_embeddings
# Regenerate embeddings for all records (force)
mix music_library.generate_embeddings --force
# Generate embeddings for specific record IDs
mix music_library.generate_embeddings --ids=id1,id2,id3
## Options
* `--force` - Regenerate embeddings for all records, even if they already exist
* `--ids` - Comma-separated list of record IDs to process
"""
use Mix.Task
import Ecto.Query
alias MusicLibrary.Repo
alias MusicLibrary.Records.{Record, RecordEmbedding}
alias MusicLibrary.Worker.GenerateRecordEmbedding
@shortdoc "Generates embeddings for records"
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
{opts, _} =
OptionParser.parse!(args,
strict: [force: :boolean, ids: :string],
aliases: [f: :force, i: :ids]
)
force? = Keyword.get(opts, :force, false)
ids = parse_ids(Keyword.get(opts, :ids))
records = get_records(force?, ids)
total = length(records)
Mix.shell().info("Found #{total} records to process...")
records
|> Enum.with_index(1)
|> Enum.each(fn {record, index} ->
Mix.shell().info("[#{index}/#{total}] Enqueueing job for: #{record.title}")
%{record_id: record.id}
|> GenerateRecordEmbedding.new()
|> Oban.insert!()
end)
Mix.shell().info("\nSuccessfully enqueued #{total} embedding generation jobs.")
Mix.shell().info("Monitor progress in Oban dashboard or logs.")
end
defp parse_ids(nil), do: []
defp parse_ids(ids_string) do
ids_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
end
defp get_records(force?, []) when force? do
Record
|> order_by(asc: :title)
|> Repo.all()
end
defp get_records(false, []) do
existing_record_ids =
RecordEmbedding
|> select([re], re.record_id)
|> Repo.all()
|> MapSet.new()
Record
|> order_by(asc: :title)
|> Repo.all()
|> Enum.reject(fn record -> MapSet.member?(existing_record_ids, record.id) end)
end
defp get_records(_force?, ids) when is_list(ids) and length(ids) > 0 do
Record
|> where([r], r.id in ^ids)
|> Repo.all()
end
end
+24
View File
@@ -9,6 +9,7 @@ defmodule MusicLibrary.Records.Similarity do
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibrary.Records.{Record, RecordEmbedding} alias MusicLibrary.Records.{Record, RecordEmbedding}
alias MusicLibrary.Repo alias MusicLibrary.Repo
alias MusicLibrary.Worker.GenerateRecordEmbedding
@doc """ @doc """
Generates a text representation of a record for embedding generation. Generates a text representation of a record for embedding generation.
@@ -110,6 +111,29 @@ defmodule MusicLibrary.Records.Similarity do
) )
end end
def generate_embedding_async(record) do
meta = %{title: record.title, artists: Enum.map(record.artists, & &1.name)}
params = %{record_id: record.id}
params
|> GenerateRecordEmbedding.new(meta: meta)
|> Oban.insert!()
end
def generate_all_embeddings_async do
Record
|> Repo.all()
|> Enum.map(fn record ->
meta = %{title: record.title, artists: Enum.map(record.artists, & &1.name)}
params = %{record_id: record.id}
params
|> GenerateRecordEmbedding.new(meta: meta)
end)
|> Oban.insert_all()
|> Enum.count()
end
# Private functions # Private functions
defp extract_year(nil), do: "Unknown" defp extract_year(nil), do: "Unknown"