Skip to content

Commit 358dc13

Browse files
committed
Updated docs
1 parent 8c761d3 commit 358dc13

File tree

3 files changed

+197
-29
lines changed

3 files changed

+197
-29
lines changed

docs/hub/advanced/language_programs.md

+29-29
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@ $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');
3838
class EmailAnalysis extends SignatureData {
3939
#[InputField('content of email')]
4040
public string $text;
41-
4241
#[OutputField('identify most relevant email topic: sales, support, other, spam')]
4342
public string $topic;
44-
4543
#[OutputField('one word sentiment: positive, neutral, negative')]
4644
public string $sentiment;
4745

@@ -62,19 +60,14 @@ class CategoryCount {
6260
class EmailStats extends SignatureData {
6361
#[InputField('directory containing emails')]
6462
public string $directory;
65-
6663
#[OutputField('number of emails')]
6764
public int $emails;
68-
6965
#[OutputField('number of spam emails')]
7066
public int $spam;
71-
7267
#[OutputField('average sentiment ratio')]
7368
public float $sentimentRatio;
74-
7569
#[OutputField('spam ratio')]
7670
public float $spamRatio;
77-
7871
#[OutputField('category counts')]
7972
public CategoryCount $categories;
8073

@@ -83,56 +76,56 @@ class EmailStats extends SignatureData {
8376
}
8477
}
8578

79+
// MODULE DECLARATIONS ////////////////////////////////////////////////////////////////////
80+
8681
class ReadEmails extends Module {
8782
public function __construct(
8883
private array $directoryContents = []
8984
) {}
9085

9186
public function signature() : string|Signature {
92-
return 'directory -> emails';
87+
return 'directory -> emails : string[]';
9388
}
9489

95-
public function forward(string $directory) : array {
90+
protected function forward(string $directory) : array {
9691
return $this->directoryContents[$directory];
9792
}
9893
}
9994

10095
class ParseEmail extends Module {
10196
public function signature() : string|Signature {
102-
return 'email -> sender, body';
97+
return 'email -> sender, subject, body';
10398
}
10499

105100
protected function forward(string $email) : array {
106101
$parts = explode(',', $email);
107102
return [
108103
'sender' => trim(explode(':', $parts[0])[1]),
109-
'body' => trim(explode(':', $parts[1])[1]),
104+
'subject' => trim(explode(':', $parts[1])[1]),
105+
'body' => trim(explode(':', $parts[2])[1]),
110106
];
111107
}
112108
}
113109

114110
class GetStats extends Module {
115-
private ReadEmails $readEmails;
116-
private ParseEmail $parseEmail;
117-
private Predict $analyseEmail;
118-
119-
public function __construct(Instructor $instructor, array $directoryContents = []) {
120-
$this->readEmails = new ReadEmails($directoryContents);
121-
$this->parseEmail = new ParseEmail();
122-
$this->analyseEmail = new Predict(signature: EmailAnalysis::class, instructor: $instructor);
123-
}
111+
public function __construct(
112+
private ReadEmails $readEmails,
113+
private ParseEmail $parseEmail,
114+
private Predict $analyseEmail,
115+
) {}
124116

125117
public function signature() : string|Signature {
126118
return EmailStats::class;
127119
}
128120

129-
public function forward(string $directory) : EmailStats {
121+
protected function forward(string $directory) : EmailStats {
130122
$emails = $this->readEmails->withArgs(directory: $directory)->get('emails');
131123
$aggregateSentiment = 0;
132124
$categories = new CategoryCount;
133125
foreach ($emails as $email) {
134126
$parsedEmail = $this->parseEmail->withArgs(email: $email);
135-
$emailAnalysis = $this->analyseEmail->with(EmailAnalysis::for($parsedEmail->get('body')));
127+
$emailData = EmailAnalysis::for(text: $parsedEmail->get('body'));
128+
$emailAnalysis = $this->analyseEmail->with($emailData);
136129
$topic = $emailAnalysis->get('topic');
137130
$sentiment = $emailAnalysis->get('sentiment');
138131
$topic = (in_array($topic, ['sales', 'support', 'spam'])) ? $topic : 'other';
@@ -160,19 +153,26 @@ class GetStats extends Module {
160153
}
161154

162155
$directoryContents['inbox'] = [
163-
'sender: [email protected], body: I am happy about the discount you offered and accept contract renewal',
164-
'sender: xxx, body: Get Viagra and Ozempic for free',
165-
'sender: [email protected], body: My internet connection keeps failing',
166-
'sender: [email protected], body: How long do I have to wait for the pricing of custom support service?!?',
167-
'sender: [email protected], body: 2 weeks of waiting and still no improvement of my connection',
156+
'sender: [email protected], subject: Offer, body: I am happy about the discount you offered and accept contract renewal',
157+
'sender: xxx, subject: Free!!!, body: Get Ozempic for free',
158+
'sender: [email protected], subject: Problem, body: My internet connection keeps failing',
159+
'sender: [email protected], subject: Still no pricing, body: How long do I have to wait for the pricing of custom support service?!?',
160+
'sender: [email protected], subject: Slow connection, body: 2 weeks of waiting and still no improvement of my connection',
168161
];
169162

163+
// PREPARE DEPENDENCIES
164+
170165
$instructor = (new Instructor);
171-
$getStats = new GetStats($instructor, $directoryContents);
166+
$readEmails = new ReadEmails($directoryContents);
167+
$parseEmail = new ParseEmail();
168+
$analyseEmail = new Predict(signature: EmailAnalysis::class, instructor: $instructor);
169+
$getStats = new GetStats($readEmails, $parseEmail, $analyseEmail);
170+
171+
// EXECUTE LANGUAGE PROGRAM
172+
172173
$emailStats = $getStats->with(EmailStats::for('inbox'));
173174

174175
echo "Results:\n";
175176
dump($emailStats->get());
176177
?>
177178
```
178-
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Language programs
2+
3+
Instructor provides an addon allowing to implement complex processing flows
4+
using LLM in a modular way. This addon to Instructor has been inspired by DSPy
5+
library for Python (https://github.com/stanfordnlp/dspy).
6+
7+
This example demonstrates multistep processing with LLMs:
8+
- parse text to extract email data from text (sender, subject and content) -> result is an object containing parsed email data
9+
- fix spelling mistakes in the subject and content fields -> result is an object containing fixed email subject and content
10+
- translate subject into specified language -> result is an object containing translated data
11+
12+
All the steps are packaged into a single, reusable module, which is easy to call via:
13+
14+
```
15+
(new ProcessEmail)->withArgs(
16+
text: $text,
17+
language: $language,
18+
);
19+
```
20+
21+
`ProcessEmail` inherits from a `Module`, which is a base class for Instructor modules. It returns a predefined object containing, in this case, the data from all steps of processing.
22+
23+
The outputs and flow can be arbitrarily shaped to the needs of specific use case (within the bounds of how Module & Predict components work).
24+
25+
```php
26+
<?php
27+
28+
use Cognesy\Instructor\Extras\Module\Addons\Predict\Predict;
29+
use Cognesy\Instructor\Extras\Module\Core\Module;
30+
use Cognesy\Instructor\Extras\Module\Signature\Attributes\InputField;
31+
use Cognesy\Instructor\Extras\Module\Signature\Attributes\OutputField;
32+
use Cognesy\Instructor\Extras\Module\CallData\SignatureData;
33+
use Cognesy\Instructor\Instructor;
34+
use Cognesy\Instructor\Schema\Attributes\Description;
35+
36+
$loader = require 'vendor/autoload.php';
37+
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');
38+
39+
// DATA MODEL DECLARATIONS ////////////////////////////////////////////////////////////////
40+
41+
//#[Description('extract email details from text')]
42+
class ParsedEmail extends SignatureData {
43+
#[InputField('text containing email')]
44+
public string $text;
45+
46+
#[OutputField('email address of sender')]
47+
public string $senderEmail;
48+
#[OutputField('subject of the email')]
49+
public string $subject;
50+
#[OutputField('body of the email')]
51+
public string $body;
52+
}
53+
54+
class FixedEmail extends SignatureData {
55+
#[InputField('subject of the email')]
56+
public string $subject;
57+
#[InputField('body of the email')]
58+
public string $body;
59+
60+
#[OutputField('subject of the email with fixed spelling mistakes')]
61+
public string $fixedSubject;
62+
#[OutputField('body of the email with fixed spelling mistakes')]
63+
public string $fixedBody;
64+
}
65+
66+
class EmailTranslation extends SignatureData {
67+
#[InputField('subject of email')]
68+
public string $subject;
69+
#[InputField('body of email')]
70+
public string $body;
71+
#[InputField('language to translate to')]
72+
public string $language;
73+
74+
#[OutputField('translated subject of email')]
75+
public string $translatedSubject;
76+
#[OutputField('translated body of email')]
77+
public string $translatedBody;
78+
}
79+
80+
class Email {
81+
public function __construct(
82+
public string $senderEmail,
83+
public string $subject,
84+
public string $body
85+
) {}
86+
}
87+
88+
class EmailProcessingResults {
89+
public function __construct(
90+
public Email $original,
91+
public Email $fixed,
92+
public Email $translated
93+
) {}
94+
}
95+
96+
// MODULE DECLARATIONS ////////////////////////////////////////////////////////////////////
97+
98+
class ProcessEmail extends Module {
99+
private Predict $parse;
100+
private Predict $fix;
101+
private Predict $translate;
102+
103+
public function __construct() {
104+
$instructor = new Instructor();
105+
106+
$this->parse = new Predict(signature: ParsedEmail::class, instructor: $instructor);
107+
$this->fix = new Predict(signature: FixedEmail::class, instructor: $instructor);
108+
$this->translate = new Predict(signature: EmailTranslation::class, instructor: $instructor);
109+
}
110+
111+
public function signature(): string {
112+
return 'text: string, language: string -> result: EmailProcessingResults';
113+
}
114+
115+
public function forward(string $text, string $language): EmailProcessingResults {
116+
$parsedEmail = $this->parse->with(
117+
ParsedEmail::fromArgs(
118+
text: $text
119+
)
120+
)->result();
121+
122+
$fixedEmail = $this->fix->with(
123+
FixedEmail::fromArgs(
124+
subject: $parsedEmail->subject,
125+
body: $parsedEmail->body
126+
)
127+
)->result();
128+
129+
$translatedEmail = $this->translate->with(
130+
EmailTranslation::fromArgs(
131+
subject: $fixedEmail->fixedSubject,
132+
body: $fixedEmail->fixedBody,
133+
language: $language
134+
)
135+
)->result();
136+
137+
return new EmailProcessingResults(
138+
new Email(
139+
$parsedEmail->senderEmail,
140+
$parsedEmail->subject,
141+
$parsedEmail->body
142+
),
143+
new Email(
144+
$parsedEmail->senderEmail,
145+
$fixedEmail->fixedSubject,
146+
$fixedEmail->fixedBody
147+
),
148+
new Email(
149+
$parsedEmail->senderEmail,
150+
$translatedEmail->translatedSubject,
151+
$translatedEmail->translatedBody
152+
)
153+
);
154+
}
155+
}
156+
157+
// EXECUTE LANGUAGE PROGRAM ///////////////////////////////////////////////////////////////
158+
159+
$text = 'sender: [email protected], subject: Ofer, body: Im hapy abut the discount you offered and accept contrac renewal';
160+
$language = 'French';
161+
162+
$result = (new ProcessEmail)->withArgs(text: $text, language: $language)->result();
163+
164+
echo "Results:\n";
165+
dump($result);
166+
?>
167+
```

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ nav:
211211
- Custom validation using Symfony Validator: 'hub/advanced/custom_validator.md'
212212
- Extracting arguments of function or method: 'hub/advanced/function_arguments.md'
213213
- Language programs: 'hub/advanced/language_programs.md'
214+
- Language programs: 'hub/advanced/language_programs2.md'
214215
- Streaming partial updates during inference: 'hub/advanced/partial_updates.md'
215216
- Providing example inputs and outputs: 'hub/advanced/providing_examples.md'
216217
- Extracting scalar values: 'hub/advanced/scalars.md'

0 commit comments

Comments
 (0)