Files
laya-php-sdk/README.md
T

156 lines
6.3 KiB
Markdown
Raw Normal View History

# Laya PHP SDK
PHP client for [Laya](https://github.com/), 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
```
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": "<string>"}`. An array is sent as-is. `model` is only included if you pass it.
3. **Read the answers.** The response's `answers.<name>` 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<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
```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
```
## Known issues
See [CODE_REVIEW.md](CODE_REVIEW.md) for open review findings. Note that its findings about the autoload namespace and inverted `require` sections have been fixed.
## License
MIT