6.3 KiB
Laya PHP SDK
PHP client for Laya, the open-source System 1 decision engine. You give Laya some state (free text or a structured document) and a set of questions. It answers each question with a choice, a score or a yes/no probability, plus a confidence value.
The SDK is a thin layer over Laya's HTTP API. It builds the JSON request, sends it over any PSR-18 HTTP client, and wraps the response in small value objects.
Requirements
- PHP 8.1+ (uses
readonlyproperties andnever-stylethrowexpressions) - A running Laya server (the default URL is
http://127.0.0.1:8000) - An HTTP client: Guzzle is installed with the package. If your app already uses another PSR-18 client (Symfony HttpClient, etc.), the SDK discovers and prefers it through
php-http/discovery.
Installation
composer require liquidsky/laya_php_sdk
Quick start
use Laya\Laya;
use Laya\Question\ChoiceQuestion;
use Laya\Question\QuestionSet;
use Laya\Question\ScoreQuestion;
use Laya\Question\YesNoQuestion;
$client = Laya::client('http://127.0.0.1:8000');
$questions = QuestionSet::make()
->add(
(new ChoiceQuestion('category', 'What is this ticket about?'))
->option('billing', 'Payments, invoices, refunds')
->option('bug', 'Something is broken')
->option('other', 'Anything else')
)
->add(new ScoreQuestion('urgency', 'How urgent is this?', ['not urgent', 'soon', 'critical']))
->add(new YesNoQuestion('angry', 'Is the customer upset?'));
$decisions = $client->decide('My card was charged twice and nobody replies!!', $questions);
$decisions->get('category')->choice(); // "billing"
$decisions->get('urgency')->score(); // e.g. 2.0
$decisions->get('angry')->probability(); // e.g. 0.93 (P(yes))
$decisions->get('angry')->confidence(); // e.g. 0.88
$decisions->routedModel; // model Laya routed to, if reported
How it works
state + QuestionSet ──► LayaClient::decide() ──► POST /predict ──► Laya server
│
DecisionSet / Decision ◄── DecisionSet::fromResponse() ◄── JSON ◄──────┘
-
Build questions. Each question is an object implementing
Question(name()andtoArray()). AQuestionSetcollects them, keyed by name. -
Call
decide().LayaClient::decide()sends this body toPOST /predict:{ "state": { "body": "My card was charged twice..." }, "questions": { "category": { "type": "choice", "instructions": "...", "criteria": { "billing": "..." } }, "urgency": { "type": "score", "instructions": "...", "criteria": ["not urgent", "soon", "critical"] }, "angry": { "type": "noul", "instructions": "..." } }, "model": "optional-model-name" }A string
$stateis wrapped as{"body": "<string>"}. An array is sent as-is.modelis only included if you pass it. -
Read the answers. The response's
answers.<name>entries becomeDecisionobjects inside aDecisionSet.routing.modelis exposed as$decisions->routedModel.
Question types
| Class | Wire type |
Constructor / builder | Read with |
|---|---|---|---|
ChoiceQuestion |
choice |
new ChoiceQuestion($name, $instructions) then ->option($label, $description) |
choice() |
ScoreQuestion |
score |
new ScoreQuestion($name, $instructions, list<string> $scale) |
score() |
YesNoQuestion |
noul |
new YesNoQuestion($name, $instructions) |
probability() |
noul is the name Laya's API uses for a yes/no question. The value is the probability of "yes".
To add your own type, implement Laya\Question\Question.
Reading results
$decisions->get($name) returns a Decision, or throws OutOfBoundsException if the server returned no answer for that name.
| Member | Returns |
|---|---|
$decision->name, $decision->type |
Question name and the type reported by the server |
choice() |
Chosen label (choice questions), else null |
score() |
Score as float (score questions), else null |
probability() |
P(yes) as float (noul questions), else null |
confidence() |
Confidence as float, or null |
distribution() |
Full probability distribution from the response |
Other endpoints
$client->models(); // GET /models, returns the decoded JSON as an array
Errors
- HTTP status 400 or above throws
Laya\Exception\UnexpectedResponseException. Its message includes the server'sdetailfield when present, andstatusCode()returns the HTTP status. - Invalid JSON in a response or request throws
JsonException. - Network failures surface as whatever your PSR-18 client throws (
Psr\Http\Client\ClientExceptionInterface). Laya::client()throws if no PSR-18 client or PSR-17 factory can be discovered.
Using your own HTTP client
Laya::client() auto-discovers dependencies. To control them (timeouts, auth headers, middleware), construct the client yourself:
use Laya\LayaClient;
$client = new LayaClient(
$psr18Client, // Psr\Http\Client\ClientInterface
$psr17RequestFactory, // Psr\Http\Message\RequestFactoryInterface
$psr17StreamFactory, // Psr\Http\Message\StreamFactoryInterface
'https://laya.example.com',
);
Configure timeouts and authentication on the PSR-18 client you pass in. The SDK adds only Content-Type and Accept headers.
Project layout
src/
├── Laya.php Factory: Laya::client() with auto-discovery
├── LayaClient.php decide(), models(); builds and sends requests
├── Exception/
│ └── UnexpectedResponseException.php
├── Question/
│ ├── Question.php Interface
│ ├── QuestionSet.php Named collection, serialises to the "questions" object
│ ├── ChoiceQuestion.php
│ ├── ScoreQuestion.php
│ └── YesNoQuestion.php
└── Result/
├── DecisionSet.php Parsed response, lookup by question name
└── Decision.php One answer
Known issues
See CODE_REVIEW.md for open review findings. Note that its findings about the autoload namespace and inverted require sections have been fixed.
License
MIT