Skip to content

1.4.0

Latest

Choose a tag to compare

@marcreichel marcreichel released this 12 Oct 21:10
7601058

Decisions move further into Laravel: a Decides validation rule checks input against a decision, and the AsDecision cast stores a decision object in a JSON column. Decision classes take float parameters, decideWithResult() keeps the raw probabilities next to the decision, and laya:eval measures a decision class against labelled examples.

// Validation
$request->validate([
    'body'    => ['required', 'string', Decides::that(Moderation::class, fn (Moderation $m) => ! $m->abusive && ! $m->spam)],
    'comment' => [Decides::not('Does the text contain insults, hate speech or threats?')],
]);

// Eloquent
protected function casts(): array
{
    return ['triage' => AsDecision::of(Triage::class)];
}

// The decision and the Result it came from
$decision = $laya->decideWithResult($ticket, Triage::class);
$decision->value;                                         // Triage
$decision->result->choice('department')->probabilities;

New

  • Decides validation rule. Decides::that(Class::class, fn ($decision) => ...), Decides::yes('question') and Decides::not('question'), with ->message() for your own wording. When laya-serve is unreachable or busy, validation fails unless you call ->passWhenUnavailable(). Other errors are thrown. The default validation.laya message ships in 12 languages under the laya namespace, and an app's own validation.laya wins.
  • AsDecision Eloquent cast. AsDecision::of(Triage::class) or AsDecision::class.':'.Triage::class stores a decision object in a JSON column. Enums are stored by backing value and read back through the constructor, so readonly classes and private promoted properties work. Lists (#[Of]), floats, #[Scale] enums and null round-trip, and a null column is a null attribute. Assigning anything other than an instance of the class, or reading JSON with a missing key, a wrong type or an unknown enum value, throws the new InvalidDecisionCastException, which names the model attribute.
  • float parameters in decision classes. A float with #[Levels] becomes a score question and gets the expected level (ScoreAnswer::$score). A float without #[Levels] becomes a yes/no question and gets P(yes). You can now sort and rank with decide() instead of dropping down to predict(). threshold on a float throws InvalidQuestionException. Laya::fake() accepts a fractional expected level and splits it between the two neighbouring levels (1.3 → 70% level 1, 30% level 2).
  • decideWithResult() and decideManyWithResult(). They return a Decision with the hydrated $value and the raw $result, so probabilities, confidences and truncated are available without asking the questions twice. The batch variant is @experimental like decideMany() and keeps the keys of $states.
  • laya:eval and vendor/bin/laya-eval. Score a decision class against labelled rows from CSV or JSON Lines. The report covers accuracy, a confusion matrix (choice, score) or precision and recall (yes/no), calibration per answerConfidence bucket, the lowest minConfidence that reaches --precision with the share sent to a human, and the best-F1 threshold for yes/no. --json prints it as JSON. vendor/bin/laya-eval runs without Laravel and reads --url/--api-key or LAYA_URL/LAYA_API_KEY. The Evaluation class gives you the same report in PHP. See examples/08-evaluation.php.
  • Laya::questions(). It returns the questions decide() would send for a class and throws the same InvalidQuestionException for a broken one, so a one-line test checks a decision class without a server or a fake.
  • Versioned cache keys. With versionCache (LAYA_CACHE_VERSIONED in Laravel, off by default), cache keys include laya-serve's checkpoint revisions, so cached answers from an old checkpoint aren't served after an upgrade. /health is called once per instance, and the revision hash is cached for 60 s. If /health fails, the plain key is used and the prediction still goes through.
  • The full usage report on Result. stateTokens, stateTokensDropped, truncatedQuestions and collapsedOptions (a CollapsedOptions per question id with total, distinct and tokensPerOption). Servers that don't send them leave the fields null or empty. laya:try names the cut-off questions and the dropped tokens in its truncation warning, and its --json output includes the whole report.
  • HTTP client settings in Laravel. LAYA_TIMEOUT and LAYA_RETRIES build a symfony/http-client client that retries on 503 and transport errors with backoff. To use your own client, set laya.http_client to a service id or bind Psr\Http\Client\ClientInterface; it takes precedence over both settings. Setting either one without symfony/http-client installed throws an exception that says what to install.
  • Configurable batch size. Pass batchSize: to new Laya() (Laravel: batch_size, from LAYA_BATCH_SIZE). The default stays 64, and a size below 1 throws InvalidOptionException.
  • Server limits in Laya::fake(). The fake now rejects what laya-serve rejects: too many questions, choice options, score levels or total options, too long a state, and too large a batch, with laya-serve's wording. Pass limits: (MarcReichel\Laya\Testing\Limits) to change them, or Limits::none() to turn them off.

Improved

  • Server errors from decide() and decideMany() name the decision class, e.g. Triage: laya-serve: too many questions (65 > 64) (HTTP 413).

Upgrading

There are no breaking API changes, but two things behave differently:

  • Laya::fake() enforces laya-serve's limits by default. A test that fakes more than 64 questions, an oversized batch or a very long state now throws a ValidationException, just as the real server would answer. Pass limits: Limits::none() to get the old behaviour.
  • ValidationException messages from decide() and decideMany() start with the class name. Update any test that matches the exact message.

compose.yaml now builds Laya v0.4.6 (c73908d) instead of v0.4.0. The 0.4.1–0.4.6 changes are additive and need no SDK changes.