Skip to content

Commit

Permalink
Docs updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ddebowczyk committed Jun 2, 2024
1 parent 956c6d0 commit 882832a
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 18 deletions.
85 changes: 79 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,52 @@ var_dump($person);
// age: 28
// }
```
> **NOTE:** Instructor only supports classes / objects as response models. In case you want to extract simple types or enums, you need to wrap them in Scalar adapter - see section below: Extracting Scalar Values. If you want to define the shape of data during runtime, you can use structures (see [Structures](docs/structures.md) section).
> **NOTE:** Instructor supports classes / objects as response models. In case you want to extract simple types or enums, you need to wrap them in Scalar adapter - see section below: Extracting Scalar Values.
>
### Support for dynamic schemas

If you want to define the shape of data during runtime, you can use structures,

Structures allow dynamically define the shape of data to be extracted
by LLM. Classes may not be the best fit for this purpose, as declaring or modifying
them at a runtime is not possible.

With structures, you can define custom data shapes dynamically, for example based
on the user input or context of the processing, to specify the information you need
LLM to infer from the provided text or chat messages.

Example below demonstrates how to define a structure and use it as a response model:

```php
<?php
use Cognesy\Instructor\Extras\Structure\Field;
use Cognesy\Instructor\Extras\Structure\Structure;

enum Role : string {
case Manager = 'manager';
case Line = 'line';
}

$structure = Structure::define('person', [
Field::string('name'),
Field::int('age'),
Field::enum('role', Role::class),
]);

$person = (new Instructor)->respond(
messages: 'Jason is 25 years old and is a manager.',
responseModel: $structure,
);

// you can access structure data via field API...
assert($person->field('name') === 'Jason');
// ...or as structure object properties
assert($person->age === 25);
?>
```

For more information see [Structures](docs/structures.md) section.


### Validation
Expand Down Expand Up @@ -165,7 +210,7 @@ $person = (new Instructor)->respond(
You can call `request()` method to set the parameters of the request and then call `get()` to get the response.

```php
use Cognesy\Instructor;
use Cognesy\Instructor\Instructor;

$instructor = (new Instructor)->request(
messages: "His name is Jason, he is 28 years old.",
Expand All @@ -175,14 +220,44 @@ $person = $instructor->get();
```


### Streaming support

Instructor supports streaming of partial results, allowing you to start
processing the data as soon as it is available.

```php
<?php
use Cognesy\Instructor\Instructor;

$stream = (new Instructor)->request(
messages: "His name is Jason, he is 28 years old.",
responseModel: Person::class,
options: ['stream' => true]
)->stream();

foreach ($stream as $partialPerson) {
// process partial person data
echo $partialPerson->name;
echo $partialPerson->age;
}

// after streaming is done you can get the final, fully processed person object...
$person = $stream->getLastUpdate()
// ...to, for example, save it to the database
$db->save($person);
?>
```



### Partial results

You can define `onPartialUpdate()` callback to receive partial results that can be used to start updating UI before LLM completes the inference.

> NOTE: Partial updates are not validated. The response is only validated after it is fully received.
```php
use Cognesy\Instructor;
use Cognesy\Instructor\Instructor;

function updateUI($person) {
// Here you get partially completed Person object update UI with the partial result
Expand All @@ -202,9 +277,6 @@ $this->db->save($person); // ...for example: save to DB






## Shortcuts

### String as Input
Expand Down Expand Up @@ -272,6 +344,7 @@ var_dump($value);
// enum(ActivityType:Entertainment)
```


### Extracting Sequences of Objects

Sequence is a wrapper class that can be used to represent a list of objects to
Expand Down
37 changes: 37 additions & 0 deletions docs/hub/advanced/function_arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Extracting arguments of function or method

Instructor offers Call class to extract arguments of a function or method from content.
This is useful when you want to build tool usage capability, e.g. for AI chatbots or agents.

```php
<?php
$loader = require 'vendor/autoload.php';
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

use Cognesy\Instructor\Extras\FunctionCall\FunctionCall;
use Cognesy\Instructor\Instructor;

/** Save user data to storage */
function saveUser(string $name, int $age, string $country) {
// Save user to database
echo "Saving user ... saveUser('$name', $age, '$country')\n";
}

$text = "His name is Jason, he is 28 years old and he lives in Germany.";
$args = (new Instructor)->respond(
messages: $text,
responseModel: FunctionCall::fromCallable(saveUser(...)),
);

echo "\nCalling the function with the extracted arguments:\n";
saveUser(...$args);

echo "\nExtracted arguments:\n";
dump($args);

assert(count($args) == 3);
expect($args['name'] == 'Jason');
expect($args['age'] == 28);
expect($args['country'] == 'Germany');
?>
```
147 changes: 147 additions & 0 deletions docs/hub/advanced/language_programs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Language programs

```php
<?php

use Cognesy\Instructor\Extras\Module\Addons\Predict\Predict;
use Cognesy\Instructor\Extras\Module\Core\Module;
use Cognesy\Instructor\Extras\Module\Signature\Attributes\InputField;
use Cognesy\Instructor\Extras\Module\Signature\Attributes\OutputField;
use Cognesy\Instructor\Extras\Module\Signature\Signature;
use Cognesy\Instructor\Extras\Module\CallData\SignatureData;
use Cognesy\Instructor\Instructor;
use Tests\MockLLM;

$loader = require 'vendor/autoload.php';
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

// DATA MODEL DECLARATIONS ////////////////////////////////////////////////////////////////

class EmailAnalysis extends SignatureData {
#[InputField('content of email')]
public string $text;
#[OutputField('identify most relevant email topic: sales, support, other, spam')]
public string $topic;
#[OutputField('one word sentiment: positive, neutral, negative')]
public string $sentiment;

public static function for(string $text) : static {
return self::fromArgs(text: $text);
}
}

class CategoryCount {
public function __construct(
public int $sales = 0,
public int $support = 0,
public int $spam = 0,
public int $other = 0,
) {}
}

class EmailStats extends SignatureData {
#[InputField('directory containing emails')]
public string $directory;
#[OutputField('number of emails')]
public int $emails;
#[OutputField('number of spam emails')]
public int $spam;
#[OutputField('average sentiment ratio')]
public float $sentimentRatio;
#[OutputField('spam ratio')]
public float $spamRatio;
#[OutputField('category counts')]
public CategoryCount $categories;

static public function for(string $directory) : static {
return self::fromArgs(directory: $directory);
}
}

class ReadEmails extends Module {
public function __construct(
private array $directoryContents = []
) {}
public function signature() : string|Signature {
return 'directory -> emails';
}
public function forward(string $directory) : array {
return $this->directoryContents[$directory];
}
}

class ParseEmail extends Module {
public function signature() : string|Signature {
return 'email -> sender, body';
}
protected function forward(string $email) : array {
$parts = explode(',', $email);
return [
'sender' => trim(explode(':', $parts[0])[1]),
'body' => trim(explode(':', $parts[1])[1]),
];
}
}

class GetStats extends Module {
private ReadEmails $readEmails;
private ParseEmail $parseEmail;
private Predict $analyseEmail;

public function __construct(Instructor $instructor, array $directoryContents = []) {
$this->readEmails = new ReadEmails($directoryContents);
$this->parseEmail = new ParseEmail();
$this->analyseEmail = new Predict(signature: EmailAnalysis::class, instructor: $instructor);
}

public function signature() : string|Signature {
return EmailStats::class;
}

public function forward(string $directory) : EmailStats {
$emails = $this->readEmails->withArgs(directory: $directory)->get('emails');
$aggregateSentiment = 0;
$categories = new CategoryCount;
foreach ($emails as $email) {
$parsedEmail = $this->parseEmail->withArgs(email: $email);
$emailAnalysis = $this->analyseEmail->with(EmailAnalysis::for($parsedEmail->get('body')));
$topic = $emailAnalysis->get('topic');
$sentiment = $emailAnalysis->get('sentiment');
$topic = (in_array($topic, ['sales', 'support', 'spam'])) ? $topic : 'other';
$categories->$topic++;
if ($topic === 'spam') {
continue;
}
$aggregateSentiment += match($sentiment) {
'positive' => 1,
'neutral' => 0,
'negative' => -1,
};
}
$spamRatio = $categories->spam / count($emails);
$sentimentRatio = $aggregateSentiment / (count($emails) - $categories->spam);

$result = new EmailStats;
$result->emails = count($emails);
$result->spam = $categories->spam;
$result->sentimentRatio = $sentimentRatio;
$result->spamRatio = $spamRatio;
$result->categories = $categories;
return $result;
}
}

$directoryContents['inbox'] = [
'sender: [email protected], body: I am happy about the discount you offered and accept contract renewal',
'sender: xxx, body: Get Viagra and Ozempic for free',
'sender: [email protected], body: My internet connection keeps failing',
'sender: [email protected], body: How long do I have to wait for the pricing of custom support service?!?',
'sender: [email protected], body: 2 weeks of waiting and still no improvement of my connection',
];

$instructor = (new Instructor);
$getStats = new GetStats($instructor, $directoryContents);
$emailStats = $getStats->with(EmailStats::for('inbox'));

echo "Results:\n";
dump($emailStats->get());
2 changes: 1 addition & 1 deletion docs/hub/api_support/llm_support_mistral.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ $user = $instructor
responseModel: User::class,
model: 'open-mixtral-8x7b',
mode: Mode::Json,
options: ['debug' => true ]
//options: ['debug' => true ]
);

print("Completed response model:\n\n");
Expand Down
2 changes: 1 addition & 1 deletion docs/hub/basics/complex_object_extraction_anthropic.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ $events = $instructor
->request(
messages: $report,
responseModel: Sequence::of(ProjectEvent::class),
model: 'claude-3-opus-20240229',
model: 'claude-3-haiku-20240307',
mode: Mode::Json,
options: [
'max_tokens' => 2048,
Expand Down
10 changes: 5 additions & 5 deletions docs/structures.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Structures

Structures allow dynamically define the shape of data to be extracted
by LLM. Classes may not be the best fit for this purpose, as they declaring
them at runtime is not possible.
by LLM. Classes may not be the best fit for this purpose, as declaring
or modifying them at a runtime is not possible.

With structures, you can define custom data shapes dynamically, at runtime
to specify the information you need LLM to infer from the provided text or
chat messages.
With structures, you can define custom data shapes dynamically, for example based
on the user input or context of the processing, to specify the information you need
LLM to infer from the provided text or chat messages.



Expand Down
35 changes: 33 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ $value = (new Instructor)->respond(
```


## Alternative ways to call Instructor
## Alternative way to get results

You can call `request()` method to create the request object and then call `get()` to get the response.
You can call `request()` method to initiate Instructor with request data
and then call `get()` to get the response.

```php
use Cognesy\Instructor\Instructor;
Expand All @@ -81,6 +82,36 @@ $person = $instructor->get();
```



## Streaming support

Instructor supports streaming of partial results, allowing you to start
processing the data as soon as it is available.

```php
<?php
use Cognesy\Instructor\Instructor;

$stream = (new Instructor)->request(
messages: "His name is Jason, he is 28 years old.",
responseModel: Person::class,
options: ['stream' => true]
)->stream();

foreach ($stream as $partialPerson) {
// process partial person data
echo $partialPerson->name;
echo $partialPerson->age;
}

// after streaming is done you can get the final, fully processed person object...
$person = $stream->getLastUpdate()
// ...to, for example, save it to the database
$db->save($person);
?>
```


## Scalar responses

See [Scalar responses](scalars.md) for more information on how to generate scalar responses with `Scalar` adapter class.
Expand Down
Loading

0 comments on commit 882832a

Please sign in to comment.