Skip to content

Commit

Permalink
Merge pull request #3 from HelgeSverre/feature/support-latest-stuff
Browse files Browse the repository at this point in the history
Use string instead of Enum as Model parameter, improve readme
  • Loading branch information
HelgeSverre authored Jan 6, 2024
2 parents 5309063 + eb85519 commit 095ccb5
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 79 deletions.
53 changes: 42 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,27 +165,58 @@ ReceiptScanner::scan(
)
```

## Specifying the model

To use a different model, you can specify the model name to use with the `model` named argument when calling
the `scan()` method.

```php
use HelgeSverre\ReceiptScanner\Facades\ReceiptScanner;

// With the ModelNames class
ReceiptScanner::scan($content, model: Model::GPT4_1106_PREVIEW)

// With a string
ReceiptScanner::scan($content, model: 'gpt-4-1106-preview')
```

## All parameters and what they do

**`$text` (TextContent|string)**

The input text from the receipt or invoice that needs to be parsed. It accepts either a `TextContent` object or a
string.

**`$model` (Model)**
**`$model` (string)

This parameter specifies the OpenAI model used for the extraction process.

It accepts a `Model` enum value. The default model is `Model::TURBO_INSTRUCT`. Different models have different
speed/accuracy characteristics.

Available Models:

- `Model::TURBO_INSTRUCT` – Uses the `gpt-3.5-turbo-instruct` model, Fastest and 7/10 accuracy.
- `Model::TURBO_16K` – Uses the `gpt-3.5-turbo-16k` model, Fast, 8/10 accuracy, accepts longer input.
- `Model::TURBO` – Uses the `gpt-3.5-turbo` model, Same, but accepts shorter input.
- `Model::GPT4` – Uses the `gpt-4` model, Slow, 9.5/10 accuracy.
- `Model::GPT4_32K` – Uses the `gpt-4-32k` model, Same, but accepts longer input.
`HelgeSverre\ReceiptScanner\ModelNames` is a class containing constants for each model, provided for convenience.
However, you can also directly
use a string to specify the model if you prefer.

Different models have different speed/accuracy characteristics.

If you require high accuracy, use a GPT-4 model, if you need speed, use a GPT-3 model, if you need even more speed, use
the `gpt-3.5-turbo-instruct` model.

The default model is `ModelNames::TURBO_INSTRUCT`.

| `ModelNames` Constant | Value |
|---------------------------------|--------------------------|
| `ModelNames::TURBO` | `gpt-3.5-turbo` |
| `ModelNames::TURBO_INSTRUCT` | `gpt-3.5-turbo-instruct` |
| `ModelNames::TURBO_1106` | `gpt-3.5-turbo-1106` |
| `ModelNames::TURBO_16K` | `gpt-3.5-turbo-16k` |
| `ModelNames::TURBO_0613` | `gpt-3.5-turbo-0613` |
| `ModelNames::TURBO_16K_0613` | `gpt-3.5-turbo-16k-0613` |
| `ModelNames::TURBO_0301` | `gpt-3.5-turbo-0301` |
| `ModelNames::GPT4` | `gpt-4` |
| `ModelNames::GPT4_32K` | `gpt-4-32k` |
| `ModelNames::GPT4_32K_0613` | `gpt-4-32k-0613` |
| `ModelNames::GPT4_1106_PREVIEW` | `gpt-4-1106-preview` |
| `ModelNames::GPT4_0314` | `gpt-4-0314` |
| `ModelNames::GPT4_32K_0314` | `gpt-4-32k-0314` |

**`$maxTokens` (int)**

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"illuminate/contracts": "^10.0",
"jstewmc/rtf": "^0.5.2",
"league/flysystem-aws-s3-v3": "^3.16",
"openai-php/laravel": "^0.7.0",
"openai-php/laravel": "^v0.8.1",
"prinsfrank/standards": "^2.1",
"smalot/pdfparser": "*",
"spatie/laravel-package-tools": "^1.14.0",
Expand Down
10 changes: 10 additions & 0 deletions pint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"preset": "laravel",
"rules": {
"class_attributes_separation": {
"elements": {
"const": "only_if_meta"
}
}
}
}
25 changes: 0 additions & 25 deletions src/Enums/Model.php

This file was deleted.

37 changes: 37 additions & 0 deletions src/ModelNames.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace HelgeSverre\ReceiptScanner;

class ModelNames
{
const DEFAULT = 'gpt-3.5-turbo-instruct';

// Turbo Models
const TURBO = 'gpt-3.5-turbo';
const TURBO_INSTRUCT = 'gpt-3.5-turbo-instruct';
const TURBO_1106 = 'gpt-3.5-turbo-1106';
const TURBO_16K = 'gpt-3.5-turbo-16k';

// Legacy Turbo Models
const TURBO_0613 = 'gpt-3.5-turbo-0613';
const TURBO_16K_0613 = 'gpt-3.5-turbo-16k-0613';
const TURBO_0301 = 'gpt-3.5-turbo-0301';

// GPT-4 Models
const GPT4 = 'gpt-4';
const GPT4_32K = 'gpt-4-32k';
const GPT4_32K_0613 = 'gpt-4-32k-0613';
const GPT4_1106_PREVIEW = 'gpt-4-1106-preview';

// Legacy GPT-4 Models
const GPT4_0314 = 'gpt-4-0314';
const GPT4_32K_0314 = 'gpt-4-32k-0314';

public static function isCompletionModel(string $modelName): bool
{
return match ($modelName) {
self::TURBO_INSTRUCT => true,
default => false,
};
}
}
40 changes: 25 additions & 15 deletions src/ReceiptScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
namespace HelgeSverre\ReceiptScanner;

use HelgeSverre\ReceiptScanner\Data\Receipt;
use HelgeSverre\ReceiptScanner\Enums\Model;
use HelgeSverre\ReceiptScanner\Exceptions\InvalidJsonReturnedError;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use OpenAI\Laravel\Facades\OpenAI;
use OpenAI\Responses\Chat\CreateResponse as ChatResponse;
use OpenAI\Responses\Completions\CreateResponse as CompletionResponse;
Expand All @@ -13,27 +14,29 @@ class ReceiptScanner
{
public function raw(
array $data = [],
Model $model = Model::TURBO_INSTRUCT,
string $model = ModelNames::DEFAULT,
int $maxTokens = 2000,
float $temperature = 0.1,
string $template = 'receipt',
): array {
$response = $this->sendRequest(
prompt: Prompt::load($template, $data),
params: [
'model' => $model->value,
'model' => $model,
'max_tokens' => $maxTokens,
'temperature' => $temperature,
],
model: $model
);

return $this->parseResponse($response);
}

/**
* @throws InvalidJsonReturnedError
*/
public function scan(
TextContent|string $text,
Model $model = Model::TURBO_INSTRUCT,
string $model = ModelNames::DEFAULT,
int $maxTokens = 2000,
float $temperature = 0.1,
string $template = 'receipt',
Expand All @@ -42,36 +45,43 @@ public function scan(
$response = $this->sendRequest(
prompt: Prompt::load($template, ['context' => $text]),
params: [
'model' => $model->value,
'model' => $model,
'max_tokens' => $maxTokens,
'temperature' => $temperature,
'response_format' => ['type' => 'json_object'],
],
model: $model

isCompletion: ModelNames::isCompletionModel($model)
);

$data = $this->parseResponse($response);

return $asArray ? $data : Receipt::fromJson($data);
}

protected function sendRequest(string $prompt, array $params, Model $model): ChatResponse|CompletionResponse
protected function sendRequest(string $prompt, array $params, bool $isCompletion = false): ChatResponse|CompletionResponse
{
return $model->isCompletion()
? OpenAI::completions()->create(array_merge($params, ['prompt' => $prompt]))
return $isCompletion
? OpenAI::completions()->create(array_merge(Arr::except($params, ['response_format']), ['prompt' => $prompt]))
: OpenAI::chat()->create(array_merge($params, ['messages' => [['role' => 'user', 'content' => $prompt]]]));
}

/**
* @throws InvalidJsonReturnedError
*/
protected function parseResponse(ChatResponse|CompletionResponse $response): array
{
$json = $this->extractResponseText($response);
$text = $this->extractResponseText($response);

$decoded = json_decode($json, true);
if ($data = json_decode($text, true)) {
return $data;
}

if ($decoded === null) {
throw new InvalidJsonReturnedError("Invalid JSON returned:\n$json");
if ($maybeData = json_decode(Str::between($text, '```json', '```'), true)) {
return $maybeData;
}

return $decoded;
throw new InvalidJsonReturnedError("Invalid JSON returned:\n$text");
}

protected function extractResponseText(ChatResponse|CompletionResponse $response): string
Expand Down
8 changes: 0 additions & 8 deletions src/Services/Textract/Data/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,13 @@
final class Block
{
const TABLE = 'TABLE';

const CELL = 'CELL';

const PAGE = 'PAGE';

const WORD = 'WORD';

const LINE = 'LINE';

const KEY_VALUE_SET = 'KEY_VALUE_SET';

const SELECTION_ELEMENT = 'SELECTION_ELEMENT';

const KEY = 'KEY';

const VALUE = 'VALUE';

public static function isTable($block): bool
Expand Down
3 changes: 0 additions & 3 deletions src/Services/Textract/TextractService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@
class TextractService
{
protected const STATUS_IN_PROGRESS = 'IN_PROGRESS';

protected const STATUS_PARTIAL_SUCCESS = 'PARTIAL_SUCCESS';

protected const STATUS_FAILED = 'FAILED';

protected const STATUS_SUCCEEDED = 'SUCCEEDED';

public function __construct(protected TextractClient $textractClient)
Expand Down
37 changes: 30 additions & 7 deletions tests/SanityCheck.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

use HelgeSverre\ReceiptScanner\Data\Receipt;
use HelgeSverre\ReceiptScanner\Enums\Model;
use HelgeSverre\ReceiptScanner\Facades\ReceiptScanner;
use HelgeSverre\ReceiptScanner\ModelNames;
use HelgeSverre\ReceiptScanner\Prompt;
use HelgeSverre\ReceiptScanner\TextContent;
use HelgeSverre\ReceiptScanner\TextLoader\Textract;
Expand All @@ -17,7 +17,7 @@
it('validates parsing of receipt data into dto', function () {
OpenAI::fake([
ChatResponse::fake([
'model' => 'gpt-3.5-turbo',
'model' => ModelNames::TURBO,
'choices' => [
[
'index' => 0,
Expand All @@ -33,7 +33,7 @@
]);

$text = file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.txt');
$result = ReceiptScanner::scan($text, model: Model::TURBO);
$result = ReceiptScanner::scan($text, model: ModelNames::TURBO);

expect($result)->toBeInstanceOf(Receipt::class)
->and($result->totalAmount)->toBe(568.00)
Expand All @@ -55,10 +55,10 @@
}
});

it('confirms real world usability with Turbo Instruct 16K model', function () {
it('confirms real world usability with GPT4_1106_PREVIEW model', function () {

$text = file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.txt');
$result = ReceiptScanner::scan($text, model: Model::TURBO_16K);
$result = ReceiptScanner::scan($text, model: ModelNames::GPT4_1106_PREVIEW);

expect($result)->toBeInstanceOf(Receipt::class)
->and($result->totalAmount)->toBe(568.00)
Expand All @@ -69,6 +69,7 @@
->and($result->merchant->name)->toBe('Minde Pizzeria')
->and($result->merchant->vatId)->toBe('921670362MVA')
->and($result->merchant->address)->toBe('Conrad Mohrs veg 5, 5068 Bergen, NOR');

$expectedResult = json_decode(file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.json'), true);

foreach ($result->lineItems as $index => $lineItem) {
Expand All @@ -79,10 +80,32 @@
}
});

it('confirms real world usability with Turbo Instruct model', function () {

$text = file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.txt');
$result = ReceiptScanner::scan($text, model: ModelNames::TURBO_INSTRUCT);

dump($result->toArray());

expect($result)->toBeInstanceOf(Receipt::class)
->and($result->totalAmount)->toBe(568.00)
->and($result->orderRef)->toBe('61e4fb2646c424c5cbc9bc88')
->and($result->date->format('Y-m-d'))->toBe('2023-07-21')
->and($result->taxAmount)->toBe(74.08)
->and($result->currency->value)->toBe('NOK')
->and($result->merchant->name)->toContain('Minde Pizzeria')
->and($result->merchant->vatId)->toContain('921670362')
->and($result->merchant->address)->toContain('Conrad Mohrs veg 5, 5068 Bergen');

foreach ($result->lineItems as $lineItem) {
expect($lineItem->toArray())->toHaveKeys(['text', 'qty', 'price', 'sku']);
}
});

it('validates returning parsed receipt as array', function () {
OpenAI::fake([
CompletionResponse::fake([
'model' => 'gpt-3.5-turbo',
'model' => ModelNames::TURBO,
'choices' => [
[
'text' => file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.json'),
Expand All @@ -92,7 +115,7 @@
]);

$text = file_get_contents(__DIR__.'/samples/wolt-pizza-norwegian.txt');
$result = ReceiptScanner::scan($text, model: Model::TURBO_INSTRUCT, asArray: true);
$result = ReceiptScanner::scan($text, model: ModelNames::TURBO_INSTRUCT, asArray: true);

expect($result)->toBeArray()
->and($result['totalAmount'])->toBe(568.00)
Expand Down
Loading

0 comments on commit 095ccb5

Please sign in to comment.