# Laya PHP SDK PHP client for [Laya](https://github.com/NandhaKishorM/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 `readonly` properties and `never`-style `throw` expressions) - 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 ```bash composer require liquidsky/laya_php_sdk ``` ## Quick start ```php 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 ```a state + QuestionSet ──► LayaClient::decide() ──► POST /predict ──► Laya server │ DecisionSet / Decision ◄── DecisionSet::fromResponse() ◄── JSON ◄──────┘ ``` 1. **Build questions.** Each question is an object implementing `Question` (`name()` and `toArray()`). A `QuestionSet` collects them, keyed by name. 2. **Call `decide()`.** `LayaClient::decide()` sends this body to `POST /predict`: ```json { "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 `$state` is wrapped as `{"body": ""}`. An array is sent as-is. `model` is only included if you pass it. 3. **Read the answers.** The response's `answers.` entries become `Decision` objects inside a `DecisionSet`. `routing.model` is 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 $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 ```php $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's `detail` field when present, and `statusCode()` 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: ```php 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 ``` ## License MIT