Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 442e4a5

Browse files
committed
feat: introducing async responses for parallel calling
1 parent 1799ed4 commit 442e4a5

File tree

6 files changed

+145
-1
lines changed

6 files changed

+145
-1
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,33 @@ dump($vectors[0]->getData()); // Array of float values
430430
1. **OpenAI's Emebddings**: [embeddings-openai.php](examples/embeddings-openai.php)
431431
1. **Voyage's Embeddings**: [embeddings-voyage.php](examples/embeddings-voyage.php)
432432

433+
### Parallel Platform Calls
434+
435+
Platform supports multiple model calls in parallel, which can be useful to speed up the processing:
436+
437+
```php
438+
// Initialize Platform & Model
439+
440+
foreach ($inputs as $input) {
441+
$responses[] = $platform->request($model, $input);
442+
}
443+
444+
foreach ($responses as $response) {
445+
echo $response->getContent().PHP_EOL;
446+
}
447+
```
448+
449+
> [!NOTE]
450+
> This requires cURL and the `ext-curl` extension to be installed.
451+
452+
#### Code Examples
453+
454+
1. **Parallel GPT Calls**: [parallel-chat-gpt.php](examples/parallel-chat-gpt.php)
455+
1. **Parallel Embeddings Calls**: [parallel-embeddings.php](examples/parallel-embeddings.php)
456+
457+
> [!NOTE]
458+
> Please be aware that some embeddings models also support batch processing out of the box.
459+
433460
### Input & Output Processing
434461

435462
The behavior of the Chain is extendable with services that implement `InputProcessor` and/or `OutputProcessor`

examples/parallel-chat-gpt.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpLlm\LlmChain\Bridge\OpenAI\GPT;
6+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
7+
use PhpLlm\LlmChain\Model\Message\Message;
8+
use PhpLlm\LlmChain\Model\Message\MessageBag;
9+
use Symfony\Component\Dotenv\Dotenv;
10+
11+
require_once dirname(__DIR__).'/vendor/autoload.php';
12+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
13+
14+
if (empty($_ENV['OPENAI_API_KEY'])) {
15+
echo 'Please set the OPENAI_API_KEY environment variable.'.PHP_EOL;
16+
exit(1);
17+
}
18+
19+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
20+
$llm = new GPT(GPT::GPT_4O_MINI, [
21+
'temperature' => 0.5, // default options for the model
22+
]);
23+
24+
$messages = new MessageBag(
25+
Message::forSystem('You will be given a letter and you answer with only the next letter of the alphabet.'),
26+
);
27+
28+
echo 'Initiating parallel calls to GPT on platform ...'.PHP_EOL;
29+
$responses = [];
30+
foreach (range('A', 'D') as $letter) {
31+
echo ' - Request for the letter '.$letter.' initiated.'.PHP_EOL;
32+
$responses[] = $platform->request($llm, $messages->with(Message::ofUser($letter)));
33+
}
34+
35+
echo 'Waiting for the responses ...'.PHP_EOL;
36+
foreach ($responses as $response) {
37+
echo 'Next Letter: '.$response->getContent().PHP_EOL;
38+
}

examples/parallel-embeddings.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpLlm\LlmChain\Bridge\OpenAI\Embeddings;
6+
use PhpLlm\LlmChain\Bridge\OpenAI\PlatformFactory;
7+
use PhpLlm\LlmChain\Model\Response\VectorResponse;
8+
use Symfony\Component\Dotenv\Dotenv;
9+
10+
require_once dirname(__DIR__).'/vendor/autoload.php';
11+
(new Dotenv())->loadEnv(dirname(__DIR__).'/.env');
12+
13+
if (empty($_ENV['OPENAI_API_KEY'])) {
14+
echo 'Please set the OPENAI_API_KEY environment variable.'.PHP_EOL;
15+
exit(1);
16+
}
17+
18+
$platform = PlatformFactory::create($_ENV['OPENAI_API_KEY']);
19+
$ada = new Embeddings(Embeddings::TEXT_ADA_002);
20+
$small = new Embeddings(Embeddings::TEXT_3_SMALL);
21+
$large = new Embeddings(Embeddings::TEXT_3_LARGE);
22+
23+
echo 'Initiating parallel embeddings calls to platform ...'.PHP_EOL;
24+
$responses = [];
25+
foreach (['ADA' => $ada, 'Small' => $small, 'Large' => $large] as $name => $model) {
26+
echo ' - Request for model '.$name.' initiated.'.PHP_EOL;
27+
$responses[] = $platform->request($model, 'Hello, world!');
28+
}
29+
30+
echo 'Waiting for the responses ...'.PHP_EOL;
31+
foreach ($responses as $response) {
32+
assert($response instanceof VectorResponse);
33+
echo 'Dimensions: '.$response->getContent()[0]->getDimensions().PHP_EOL;
34+
}

src/Chain.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PhpLlm\LlmChain\Exception\MissingModelSupport;
1414
use PhpLlm\LlmChain\Model\LanguageModel;
1515
use PhpLlm\LlmChain\Model\Message\MessageBag;
16+
use PhpLlm\LlmChain\Model\Response\AsyncResponse;
1617
use PhpLlm\LlmChain\Model\Response\ResponseInterface;
1718

1819
final readonly class Chain implements ChainInterface
@@ -55,6 +56,10 @@ public function call(MessageBag $messages, array $options = []): ResponseInterfa
5556

5657
$response = $this->platform->request($this->llm, $messages, $options = $input->getOptions());
5758

59+
if ($response instanceof AsyncResponse) {
60+
$response = $response->unwrap();
61+
}
62+
5863
$output = new Output($this->llm, $response, $messages, $options);
5964
array_map(fn (OutputProcessor $processor) => $processor->processOutput($output), $this->outputProcessor);
6065

src/Model/Response/AsyncResponse.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpLlm\LlmChain\Model\Response;
6+
7+
use PhpLlm\LlmChain\Platform\ResponseConverter;
8+
use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse;
9+
10+
final class AsyncResponse implements ResponseInterface
11+
{
12+
private bool $isConverted = false;
13+
private ResponseInterface $convertedResponse;
14+
15+
/**
16+
* @param array<string, mixed> $options
17+
*/
18+
public function __construct(
19+
private readonly ResponseConverter $responseConverter,
20+
private readonly HttpResponse $response,
21+
private readonly array $options = [],
22+
) {
23+
}
24+
25+
public function getContent(): string|iterable|object|null
26+
{
27+
return $this->unwrap()->getContent();
28+
}
29+
30+
public function unwrap(): ResponseInterface
31+
{
32+
if (!$this->isConverted) {
33+
$this->convertedResponse = $this->responseConverter->convert($this->response, $this->options);
34+
$this->isConverted = true;
35+
}
36+
37+
return $this->convertedResponse;
38+
}
39+
}

src/Platform.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PhpLlm\LlmChain\Exception\InvalidArgumentException;
88
use PhpLlm\LlmChain\Exception\RuntimeException;
99
use PhpLlm\LlmChain\Model\Model;
10+
use PhpLlm\LlmChain\Model\Response\AsyncResponse;
1011
use PhpLlm\LlmChain\Model\Response\ResponseInterface;
1112
use PhpLlm\LlmChain\Platform\ModelClient;
1213
use PhpLlm\LlmChain\Platform\ResponseConverter;
@@ -80,7 +81,7 @@ private function convertResponse(Model $model, object|array|string $input, HttpR
8081
{
8182
foreach ($this->responseConverter as $responseConverter) {
8283
if ($responseConverter->supports($model, $input)) {
83-
return $responseConverter->convert($response, $options);
84+
return new AsyncResponse($responseConverter, $response, $options);
8485
}
8586
}
8687

0 commit comments

Comments
 (0)