JSONTask
NAME
LLM::Data::Inference::JSONTask - Task that parses JSON output with schema checks
SYNOPSIS
use LLM::Data::Inference::JSONTask;
# Single backend (legacy)
my $task = LLM::Data::Inference::JSONTask.new(
backend => $backend,
user-prompt => '...',
required-keys => <name age>,
validator => -> %h { %h<age> > 0 },
);
my %parsed = $task.execute;
# Model-chain fallback ā on malformed JSON / missing keys / validator
# failure, the underlying Task advances to the next backend.
my $task2 = LLM::Data::Inference::JSONTask.new(
backends => [$primary, $fallback],
user-prompt => '...',
required-keys => <name age>,
);
DESCRIPTION
Thin composition over LLM::Data::Inference::Task that plugs in a
JSON parser + key-presence + custom-validator pipeline. All fallback
and retry semantics come from the underlying Task ā see its Pod for
the full bucket classification. :parse-retries threads through for
same-backend re-rolls on malformed output; :truncation-policy
threads through too and, left at its default, resolves to 'fail'
here ā a JSON task always has a parser, and a completion cut off by
max_tokens can never parse, so the chain advances at once and
throws X::LLM::Data::Inference::Truncated (an Exhausted
subclass) if every backend truncates; :is-cancelled threads
through for cooperative cancellation (the Task throws
X::LLM::Data::Inference::Cancelled and aborts any in-flight
response as soon as the hook reports True); :on-exhausted threads
through unchanged and fires exactly once, immediately before the
chain throws X::LLM::Data::Inference::Exhausted (or one of its
subclasses ā Truncated above, or TimedOut when the chain ran
out of time) ā see Task's Pod for the full payload shape and the
type precedence.
:retry-feedback threads through as well, and this is the class it
was built for: it makes those :parse-retries re-rolls informed,
appending one extra user turn that quotes the rejection ā a JSON
syntax error, a missing @.required-keys entry, or whatever message
&.validator died with. A semantic rejection ("quote 2 does not
appear verbatim in the passage") is otherwise re-produced verbatim on
every blind re-roll, so the item burns its whole budget failing
identically; with feedback the second attempt is a correction pass.
Each re-roll replaces the previous feedback, and only re-rolls against
the same backend are informed ā see Task's "Retry feedback" Pod
section for the full rules.
Accepts either :$backend (single) or :@backends (chain); both
forms thread through to the inner Task unchanged.
JSON extraction
The extractor tolerates the chatter real models wrap around their
answer: < <think>ā¦</think> > reasoning blocks and markdown code
fences are stripped, then a string-aware balanced-bracket scan
collects every complete top-level JSON structure and picks the
longest one that actually parses (models that plan out loud often
emit a small throwaway object before the real answer). Only when no
complete structure parses does it fall back to the historical
first-bracket ā last-bracket slice ā so truncated completions still
fail with the established error messages.