Skip to main content

examples/branching/actions/decide_truth.ex

defmodule Jido.Runic.Examples.Branching.Actions.DecideTruth do
  @moduledoc """
  Ask the LLM for a structured boolean decision on an input statement.
  """

  @output_schema [
    question: [type: :string, required: true],
    is_true: [type: :boolean, required: true],
    confidence: [type: :float, required: true],
    reasoning: [type: :string, required: true]
  ]

  use Jido.Action,
    name: "branching_decide_truth",
    description: "Returns a structured true/false decision for a statement",
    schema: [
      question: [type: :string, required: true]
    ],
    output_schema: @output_schema

  alias Jido.Runic.Examples.Studio.Actions.Helpers

  @impl true
  def run(%{question: question}, _context) do
    prompt = """
    You are a binary decision system.
    Decide if the statement below should be treated as true or false.

    Statement: "#{question}"

    Return only a JSON object with these keys:
    - question (string)
    - is_true (boolean)
    - confidence (float from 0.0 to 1.0)
    - reasoning (one short sentence)
    """

    case Helpers.generate_object(prompt, @output_schema, temperature: 0.9, max_tokens: 200) do
      {:ok, object} ->
        {:ok,
         %{
           question: object["question"] || question,
           is_true: normalize_bool(object["is_true"]),
           confidence: normalize_confidence(object["confidence"]),
           reasoning: normalize_reasoning(object["reasoning"])
         }}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp normalize_bool(value) when is_boolean(value), do: value
  defp normalize_bool(value) when is_binary(value), do: String.downcase(value) in ["true", "yes", "1"]
  defp normalize_bool(value) when is_number(value), do: value > 0
  defp normalize_bool(_), do: false

  defp normalize_confidence(value) when is_integer(value),
    do: value |> Kernel./(1.0) |> clamp_confidence()

  defp normalize_confidence(value) when is_float(value), do: clamp_confidence(value)

  defp normalize_confidence(value) when is_binary(value) do
    case Float.parse(value) do
      {parsed, _} -> clamp_confidence(parsed)
      :error -> 0.5
    end
  end

  defp normalize_confidence(_), do: 0.5

  defp clamp_confidence(value), do: min(1.0, max(0.0, value))

  defp normalize_reasoning(value) when is_binary(value) and value != "", do: value
  defp normalize_reasoning(_), do: "No reasoning provided."
end