One typed question inside an ALLM.ClassificationRequest — Layer A
serializable data.
iex> q = ALLM.ClassificationQuestion.choice("Which team should handle this?", ["billing", "technical"])
iex> q.type
:choice
iex> q.criteria
%{"billing" => nil, "technical" => nil}Question types
There are three, and a request may mix them freely:
:choice— pick exactly one option from a named set. The answer reports the chosen option plus a probability per option.:score— place the input on an ordered scale of levels, low to high. The answer reports a (fractional) position on that scale.:yes_no— the probability that the answer is "yes". Some providers use their own name for this type (TypeSafe calls it "noul"); ALLM always spells it:yes_no, and an adapter translates on the wire.
Instructions and criteria
:instructions is the question itself. It is a string, or a JSON object or
array when the question is better expressed as structured data.
:criteria depends on the type:
| Type | :criteria |
|---|---|
:choice | a map of option name to an optional description (nil, a string, or structured data) |
:score | a list of level descriptions, where index = level |
:yes_no | nil, or a map whose keys are among "true" and "false", describing what each answer means |
Use the builders choice/2, score/2 and yes_no/2; they produce these
shapes and stringify atom option names so the question survives a JSON
round trip unchanged.
Construction
new/1 is a bare struct!/2 pass-through: unknown keys raise KeyError
and nothing else is checked. The builders raise on a wrongly-typed
argument (FunctionClauseError, or ArgumentError for an unknown
yes_no/2 option or colliding choice/2 option names), but counts, emptiness and provider limits are left to
ALLM.Validate.classification_request/1, so a hand-built struct and a
builder-made one are judged by the same rules.
Summary
Types
A question type. :yes_no is ALLM's neutral name for a yes/no probability.
String, JSON object, or JSON array: structured instructions or criteria.
Functions
Choice over options.
Build a %ClassificationQuestion{} from keyword opts.
Score on ordered levels, low to high. Level N is Enum.at(levels, N).
Yes/no. Optional true: / false: describe what each answer means.
Types
@type question_type() :: :choice | :score | :yes_no
A question type. :yes_no is ALLM's neutral name for a yes/no probability.
String, JSON object, or JSON array: structured instructions or criteria.
@type t() :: %ALLM.ClassificationQuestion{ criteria: %{required(String.t()) => structured() | nil} | [structured()] | map() | nil, instructions: structured() | nil, type: question_type() | nil }
Functions
@spec choice( structured(), [String.t() | atom()] | %{required(String.t() | atom()) => structured() | nil} ) :: t()
Choice over options.
options is a list of names (each description is nil) or a map of
name to description. Atom names are stringified. An options that is
neither a list nor a map raises FunctionClauseError. A keyword list is
still a list, so choice(q, billing: "Payments") treats each
{name, description} pair as a name and raises Protocol.UndefinedError —
pass a map for named descriptions. Two distinct names that become the same
string (:billing and "billing") raise ArgumentError rather than
silently merging into one option; repeating the identical name in a list
is harmless.
Examples
iex> q = ALLM.ClassificationQuestion.choice("Which team?", [:billing, "sales"])
iex> q.criteria
%{"billing" => nil, "sales" => nil}
iex> q = ALLM.ClassificationQuestion.choice("Which team?", %{billing: "Payments and invoices"})
iex> q.criteria
%{"billing" => "Payments and invoices"}
Build a %ClassificationQuestion{} from keyword opts.
Unknown keys raise KeyError via struct!/2. No validation.
Examples
iex> q = ALLM.ClassificationQuestion.new(type: :yes_no, instructions: "Is this spam?")
iex> {q.type, q.criteria}
{:yes_no, nil}
@spec score(structured(), [structured()]) :: t()
Score on ordered levels, low to high. Level N is Enum.at(levels, N).
A non-list levels raises FunctionClauseError.
Examples
iex> q = ALLM.ClassificationQuestion.score("How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"])
iex> {q.type, Enum.at(q.criteria, 1)}
{:score, "Frustrated"}
@spec yes_no( structured(), keyword() ) :: t()
Yes/no. Optional true: / false: describe what each answer means.
With neither option :criteria is nil; otherwise it carries only the
keys given. Any other option raises ArgumentError.
Examples
iex> ALLM.ClassificationQuestion.yes_no("Is a refund requested?").criteria
nil
iex> ALLM.ClassificationQuestion.yes_no("Is a refund requested?", true: "The customer asks for money back").criteria
%{"true" => "The customer asks for money back"}