defmodule ArcanaWeb.EvaluationLive do @moduledoc """ LiveView for retrieval evaluation in Arcana. """ use Phoenix.LiveView import ArcanaWeb.DashboardComponents alias Arcana.Evaluation @impl true def mount(_params, session, socket) do repo = get_repo_from_session(session) {:ok, socket |> assign(repo: repo) |> assign( eval_view: :test_cases, eval_running: false, eval_generating: false, eval_message: nil, stats: nil, eval_test_cases: [], eval_runs: [], eval_test_case_count: 0, collections: [] )} end @impl true def handle_params(_params, _uri, socket) do {:noreply, load_data(socket)} end defp load_data(socket) do repo = socket.assigns.repo socket |> assign(stats: load_stats(repo)) |> load_evaluation_data() end defp load_evaluation_data(socket) do repo = socket.assigns.repo test_cases = Evaluation.list_test_cases(repo: repo) runs = Evaluation.list_runs(repo: repo, limit: 10) test_case_count = Evaluation.count_test_cases(repo: repo) collections = load_collections(repo) assign(socket, eval_test_cases: test_cases, eval_runs: runs, eval_test_case_count: test_case_count, collections: collections ) end @impl true def handle_event("eval_switch_view", %{"view" => view}, socket) do {:noreply, assign(socket, eval_view: String.to_existing_atom(view))} end def handle_event("eval_run", params, socket) do repo = socket.assigns.repo mode = parse_mode(params["mode"]) evaluate_answers = params["evaluate_answers"] == "true" # Check if LLM is configured when evaluate_answers is requested llm = Application.get_env(:arcana, :llm) if evaluate_answers and is_nil(llm) do {:noreply, assign(socket, eval_message: {:error, "No LLM configured. Set :arcana, :llm in your config."} )} else socket = assign(socket, eval_running: true, eval_message: nil) opts = build_run_opts(repo, mode, evaluate_answers, llm) parent = self() Task.Supervisor.start_child(Arcana.TaskSupervisor, fn -> result = Evaluation.run(opts) send(parent, {:eval_run_complete, result}) end) {:noreply, socket} end end def handle_event("eval_generate", params, socket) do repo = socket.assigns.repo sample_size = parse_int(params["sample_size"], 10) collection = blank_to_nil(params["collection"]) case Application.get_env(:arcana, :llm) do nil -> {:noreply, assign(socket, eval_message: {:error, "No LLM configured. Set :arcana, :llm in your config."} )} llm -> socket = assign(socket, eval_generating: true, eval_message: nil) parent = self() opts = build_generate_opts(repo, llm, sample_size, collection) Task.Supervisor.start_child(Arcana.TaskSupervisor, fn -> result = Evaluation.generate_test_cases(opts) send(parent, {:eval_generate_complete, result}) end) {:noreply, socket} end end def handle_event("eval_delete_test_case", %{"id" => id}, socket) do repo = socket.assigns.repo case Evaluation.delete_test_case(id, repo: repo) do {:ok, _} -> {:noreply, load_evaluation_data(socket)} {:error, _} -> {:noreply, socket} end end def handle_event("eval_delete_run", %{"id" => id}, socket) do repo = socket.assigns.repo case Evaluation.delete_run(id, repo: repo) do {:ok, _} -> {:noreply, load_evaluation_data(socket)} {:error, _} -> {:noreply, socket} end end @impl true def handle_info({:eval_run_complete, result}, socket) do socket = case result do {:ok, _run} -> socket |> assign(eval_running: false, eval_message: {:success, "Evaluation completed!"}) |> load_evaluation_data() |> assign(eval_view: :history) {:error, :no_test_cases} -> assign(socket, eval_running: false, eval_message: {:error, "No test cases. Generate some first."} ) end {:noreply, socket} end def handle_info({:eval_generate_complete, result}, socket) do socket = case result do {:ok, test_cases} -> socket |> assign( eval_generating: false, eval_message: {:success, "Generated #{length(test_cases)} test case(s)!"} ) |> load_evaluation_data() {:error, reason} -> assign(socket, eval_generating: false, eval_message: {:error, "Generation failed: #{inspect(reason)}"} ) end {:noreply, socket} end defp build_run_opts(repo, mode, false, _llm), do: [repo: repo, mode: mode] defp build_run_opts(repo, mode, true, llm) do [repo: repo, mode: mode, evaluate_answers: true, llm: llm] end defp build_generate_opts(repo, llm, sample_size, nil) do [repo: repo, llm: llm, sample_size: sample_size] end defp build_generate_opts(repo, llm, sample_size, collection) do [repo: repo, llm: llm, sample_size: sample_size, collection: collection] end @impl true def render(assigns) do ~H""" <.dashboard_layout stats={@stats} current_tab={:evaluation}>
Test retrieval quality with test cases and measure recall and precision metrics.
No test cases yet. Click "Generate Test Cases" above or use the API.
<% else %> <%= for tc <- @test_cases do %>Run evaluation against your <%= @test_case_count %> test case(s) to measure retrieval quality.
<%= if @test_case_count == 0 do %>
No test cases available. Generate some first using mix arcana.eval.generate.
No evaluation runs yet. Run an evaluation to see results here.
<% else %> <%= for run <- @runs do %>