Current section

Files

Jump to
arcana lib mix tasks arcana.graph.summarize_communities.ex
Raw

lib/mix/tasks/arcana.graph.summarize_communities.ex

defmodule Mix.Tasks.Arcana.Graph.SummarizeCommunities do
@moduledoc """
Generates LLM summaries for knowledge graph communities.
Use this after detecting communities to generate natural language
summaries that provide high-level context for global queries.
$ mix arcana.graph.summarize_communities
## Options
* `--collection` - Only summarize communities for the specified collection
* `--force` - Regenerate all summaries, not just dirty ones
* `--concurrency` - Number of parallel summarization tasks (default: 1)
* `--quiet` - Suppress progress output
## Examples
# Default usage (summarize dirty communities)
mix arcana.graph.summarize_communities
# Summarize a specific collection
mix arcana.graph.summarize_communities --collection my-docs
# Force regenerate all summaries
mix arcana.graph.summarize_communities --force
# Parallel summarization (4 concurrent tasks)
mix arcana.graph.summarize_communities --concurrency 4
# Quiet mode
mix arcana.graph.summarize_communities --quiet
## Requirements
This task requires an LLM to be configured:
config :arcana, :llm, {"openai:gpt-4o-mini", api_key: "..."}
"""
use Mix.Task
@shortdoc "Generates summaries for knowledge graph communities"
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
strict: [
quiet: :boolean,
collection: :string,
force: :boolean,
concurrency: :integer
],
aliases: [f: :force, c: :concurrency]
)
quiet = Keyword.get(opts, :quiet, false)
collection = Keyword.get(opts, :collection)
force = Keyword.get(opts, :force, false)
concurrency = Keyword.get(opts, :concurrency, 1)
# Start the host application (which will start the repo)
Mix.Task.run("app.start")
repo = Application.get_env(:arcana, :repo)
unless repo do
Mix.raise("No repo configured. Set config :arcana, repo: YourApp.Repo")
end
# Check LLM is configured
unless Application.get_env(:arcana, :llm) do
Mix.raise("""
No LLM configured for community summarization.
Add to your config:
config :arcana, :llm, {"openai:gpt-4o-mini", api_key: "..."}
""")
end
# Show current config
info = Arcana.Maintenance.graph_info()
Mix.shell().info("Graph config: #{format_info(info)}")
Mix.shell().info("Summarization: force=#{force}, concurrency=#{concurrency}")
# Build progress callback
progress_fn =
if quiet do
fn _, _ -> :ok end
else
build_progress_fn()
end
scope = if collection, do: "collection '#{collection}'", else: "all collections"
Mix.shell().info("Summarizing communities for #{scope}...\n")
summarize_opts = [
progress: progress_fn,
force: force,
concurrency: concurrency
]
summarize_opts =
if collection,
do: Keyword.put(summarize_opts, :collection, collection),
else: summarize_opts
{:ok, %{communities: communities, summaries: summaries}} =
Arcana.Maintenance.summarize_communities(repo, summarize_opts)
Mix.shell().info(
"\nDone! Processed #{communities} communities, generated #{summaries} summaries."
)
end
defp format_info(%{enabled: enabled, extractor_name: name, community_levels: levels}) do
status = if enabled, do: "enabled", else: "disabled"
"#{status}, extractor: #{name}, community levels: #{levels}"
end
defp format_info(%{enabled: enabled, extractor_type: type, community_levels: levels}) do
status = if enabled, do: "enabled", else: "disabled"
"#{status}, extractor: #{type}, community levels: #{levels}"
end
defp build_progress_fn do
fn
# Called when starting to process a collection
:collection_start, %{collection: name} ->
IO.puts(" Processing '#{name}'...")
# Called after collection completes
:collection_complete, %{index: idx, total: total, collection: name, result: result} ->
IO.puts(
" [#{idx}/#{total}] '#{name}': #{result.summaries}/#{result.communities} summaries generated"
)
# Legacy: simple index/total progress
current, total when is_integer(current) and is_integer(total) ->
:ok
end
end
end