pub trait Task {
type Output;
type Value;
type ParseError: Error;
// Required methods
fn prompt(&self) -> &str;
fn schema(&self) -> &Self::Value;
fn grammar(&self) -> Grammar;
fn parse(&self, raw: &str) -> Result<Self::Output, Self::ParseError>;
}Expand description
A structured-output task description.
Implementations supply the prompt, the constrained-decoding
Grammar, and a parser that turns the model’s raw text into
a typed Output.
No thread-safety bounds at the trait level. Task itself
is unbounded so non-Send implementors (e.g., a Task carrying
an Rc for setup-time state) compile without ceremony.
Engines that spawn parse work across threads (e.g., a tokio-
driven inference server) add Send + Sync + 'static bounds at
their generic call sites, on T, T::Output, and
T::ParseError. The Rust pattern: bound where you need, not
where you might.
Implementations should cache their grammar (build it once in
new) rather than rebuilding it per call.
§Required methods
Implementors provide all four method signatures explicitly —
there are no default implementations. The trait used to expose
only schema(&self) -> &serde_json::Value and a fixed
ParseError enum, but both have been generalized:
schema(&self) -> &Self::Value— borrow the typed schema (engines that bindValue = serde_json::Valueget typed access without going through theGrammarenum).grammar(&self) -> Grammar— wrap the schema in the engine-agnostic enum (engines that handle multiple variants pattern-match on this).parse(&self, raw: &str) -> Result<Self::Output, Self::ParseError>— typed parse step.prompt(&self) -> &str— the user-message prompt.
Tasks that parse JSON output typically set
type ParseError = llmtask::JsonParseError; (the convenience
type behind the json feature). Custom Tasks pick any error
type that’s core::error::Error.
Required Associated Types§
Sourcetype Value
type Value
The schema/grammar value the Task carries. Typically:
serde_json::Valuefor JSON Schema taskssmol_str::SmolStrfor Lark / Regex string-grammar tasks- any other type the Task wants to expose as its schema representation
Engines that handle ONE specific schema type can bind it
directly: fn run<T: Task<Value = serde_json::Value>>(...)
— task.schema() then returns the typed value without an
enum match. Engines that handle multiple schema types use
task.grammar() and pattern-match on the Grammar enum.
Sourcetype ParseError: Error
type ParseError: Error
The error type returned by Task::parse. JSON-parsing
Tasks typically use crate::JsonParseError (behind the
json feature).
Required Methods§
Sourcefn schema(&self) -> &Self::Value
fn schema(&self) -> &Self::Value
Borrow the schema/grammar value. Zero-cost typed access for
engines that bind Task::Value to a concrete type. Engines
that need a unified Grammar enum should call
Task::grammar instead.
Pair schema() with grammar() when implementing: cache the
schema once on the Task struct, return a borrow from
schema(), build the Grammar wrapper in grammar().
Sourcefn grammar(&self) -> Grammar
fn grammar(&self) -> Grammar
Constrained-decoding grammar for this task, wrapped in the
engine-agnostic Grammar enum.
Always required (no default impl). Implementations are
typically a one-liner that wraps self.schema() in the
appropriate variant — for example:
// JSON task:
fn grammar(&self) -> Grammar {
Grammar::JsonSchema(self.schema().clone())
}
// Lark task:
fn grammar(&self) -> Grammar {
Grammar::Lark(self.schema().clone())
}A default impl was considered but rejected: the bound it
would have required (Self::Value: Clone + Into<Grammar>)
also gets checked at every call site, so Tasks whose
Value doesn’t satisfy the bound (e.g., SmolStr, which
is ambiguous between Lark and Regex) couldn’t have their
grammar() called even when overridden. Two-line override
per Task is the simpler shape.