Skip to content

Commit 94683f5

Browse files
committed
add concurrency to openai requests
1 parent bc20c78 commit 94683f5

File tree

4 files changed

+185
-53
lines changed

4 files changed

+185
-53
lines changed

config/translator.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
'service' => null,
8585
'services' => [
8686
'openai' => [
87+
'concurrency' => true,
88+
'chunk' => 10,
8789
'model' => 'gpt-4.1-mini',
8890
'prompt' => '
8991
# Role:
@@ -122,6 +124,8 @@
122124
'service' => null,
123125
'services' => [
124126
'openai' => [
127+
'concurrency' => true,
128+
'chunk' => 10,
125129
'model' => 'gpt-4.1-mini',
126130
'prompt' => '
127131
# Role:
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Elegantly\Translator\Services;
6+
7+
use Closure;
8+
9+
abstract class AbstractOpenAiService
10+
{
11+
public static function getTimeout(): int
12+
{
13+
return (int) (config('translator.services.openai.request_timeout') ?? 120);
14+
}
15+
16+
/**
17+
* @template TValue
18+
*
19+
* @param (Closure():TValue) $callback
20+
* @return TValue
21+
*/
22+
protected function withTemporaryTimeout(int $limit, Closure $callback): mixed
23+
{
24+
$initial = (int) ini_get('max_execution_time');
25+
26+
set_time_limit($limit);
27+
28+
try {
29+
return $callback();
30+
} catch (\Throwable $th) {
31+
throw $th;
32+
} finally {
33+
set_time_limit($initial);
34+
}
35+
}
36+
}

src/Services/Proofread/OpenAiService.php

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44

55
namespace Elegantly\Translator\Services\Proofread;
66

7-
use Illuminate\Support\Collection;
7+
use Elegantly\Translator\Services\AbstractOpenAiService;
8+
use Illuminate\Support\Facades\Concurrency;
89
use InvalidArgumentException;
910
use OpenAI;
1011

11-
class OpenAiService implements ProofreadServiceInterface
12+
class OpenAiService extends AbstractOpenAiService implements ProofreadServiceInterface
1213
{
1314
public function __construct(
1415
public \OpenAI\Client $client,
1516
public string $model,
1617
public string $prompt,
18+
public bool $concurrency,
19+
public int $chunk,
1720
) {
1821
//
1922
}
@@ -24,6 +27,8 @@ public static function make(): self
2427
client: static::makeClient(),
2528
model: config('translator.proofread.services.openai.model'),
2629
prompt: config('translator.proofread.services.openai.prompt'),
30+
concurrency: config('translator.proofread.services.openai.concurrency') ?? true,
31+
chunk: config('translator.proofread.services.openai.chunk') ?? 10,
2732
);
2833
}
2934

@@ -51,11 +56,68 @@ public static function makeClient(): \OpenAI\Client
5156
->make();
5257
}
5358

59+
public static function getTimeout(): int
60+
{
61+
return (int) (config('translator.proofread.services.openai.request_timeout') ?? parent::getTimeout());
62+
}
63+
64+
/**
65+
* @param array<array-key, null|scalar> $texts
66+
* @return array<array-key, null|scalar>
67+
*/
68+
public function proofreadAllWithConcurrency(array $texts): array
69+
{
70+
$prompt = $this->prompt;
71+
72+
$tasks = collect($texts)
73+
->chunk($this->chunk)
74+
->map(function ($chunk) use ($prompt) {
75+
76+
return function () use ($prompt, $chunk) {
77+
$response = static::makeClient()->chat()->create([
78+
'model' => $this->model,
79+
'response_format' => ['type' => 'json_object'],
80+
'messages' => [
81+
[
82+
'role' => 'system',
83+
'content' => $prompt,
84+
],
85+
[
86+
'role' => 'user',
87+
'content' => json_encode($chunk),
88+
],
89+
],
90+
]);
91+
92+
$content = $response->choices[0]->message->content;
93+
$content = str_replace('\\\/', "\/", $content);
94+
$translations = json_decode($content, true);
95+
96+
return $translations;
97+
};
98+
})
99+
->all();
100+
101+
$results = $this->withTemporaryTimeout(
102+
static::getTimeout() * count($tasks),
103+
fn () => Concurrency::run($tasks),
104+
);
105+
106+
return collect($results)->collapse()->toArray();
107+
}
108+
54109
public function proofreadAll(array $texts): array
55110
{
56-
return collect($texts)
57-
->chunk(20)
58-
->map(function (Collection $chunk) {
111+
if ($this->concurrency) {
112+
return $this->proofreadAllWithConcurrency($texts);
113+
}
114+
115+
$chunks = collect($texts)->chunk($this->chunk);
116+
117+
return $this->withTemporaryTimeout(
118+
static::getTimeout() * count($chunks),
119+
fn () => $chunks->map(function ($chunk) {
120+
59121
$response = $this->client->chat()->create([
60122
'model' => $this->model,
61123
'response_format' => ['type' => 'json_object'],
@@ -76,8 +138,7 @@ public function proofreadAll(array $texts): array
76138
$translations = json_decode($content, true);
77139

78140
return $translations;
79-
})
80-
->collapse()
81-
->toArray();
141+
})->collapse()->toArray()
142+
);
82143
}
83144
}

src/Services/Translate/OpenAiService.php

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44

55
namespace Elegantly\Translator\Services\Translate;
66

7-
use Closure;
7+
use Elegantly\Translator\Services\AbstractOpenAiService;
8+
use Illuminate\Support\Facades\Concurrency;
89
use InvalidArgumentException;
910
use OpenAI;
1011

11-
class OpenAiService implements TranslateServiceInterface
12+
class OpenAiService extends AbstractOpenAiService implements TranslateServiceInterface
1213
{
1314
public function __construct(
1415
public \OpenAI\Client $client,
1516
public string $model,
1617
public string $prompt,
18+
public bool $concurrency,
19+
public int $chunk,
1720
) {
1821
//
1922
}
@@ -24,6 +27,8 @@ public static function make(): self
2427
client: static::makeClient(),
2528
model: config('translator.translate.services.openai.model'),
2629
prompt: config('translator.translate.services.openai.prompt'),
30+
concurrency: config('translator.translate.services.openai.concurrency') ?? true,
31+
chunk: config('translator.translate.services.openai.chunk') ?? 10,
2732
);
2833
}
2934

@@ -53,62 +58,88 @@ public static function makeClient(): \OpenAI\Client
5358

5459
public static function getTimeout(): int
5560
{
56-
return (int) (config('translator.translate.services.openai.request_timeout') ?? config('translator.services.openai.request_timeout') ?? 120);
61+
return (int) (config('translator.translate.services.openai.request_timeout') ?? parent::getTimeout());
5762
}
5863

5964
/**
60-
* @template TValue
61-
*
62-
* @param (Closure():TValue) $callback
63-
* @return TValue
65+
* @param array<array-key, null|scalar> $texts
66+
* @return array<array-key, null|scalar>
6467
*/
65-
protected function withTemporaryTimeout(int $limit, Closure $callback): mixed
68+
public function translateAllWithConcurrency(array $texts, string $targetLocale): array
6669
{
67-
$initial = (int) ini_get('max_execution_time');
70+
$model = $this->model;
71+
$prompt = $this->prompt;
72+
73+
$tasks = collect($texts)
74+
->chunk($this->chunk)
75+
->map(function ($chunk) use ($model, $prompt, $targetLocale) {
76+
77+
return function () use ($model, $prompt, $targetLocale, $chunk) {
78+
$response = static::makeClient()->chat()->create([
79+
'model' => $model,
80+
'response_format' => ['type' => 'json_object'],
81+
'messages' => [
82+
[
83+
'role' => 'system',
84+
'content' => str_replace('{targetLocale}', $targetLocale, $prompt),
85+
],
86+
[
87+
'role' => 'user',
88+
'content' => $chunk->toJson(),
89+
],
90+
],
91+
]);
6892

69-
set_time_limit($limit);
93+
$content = $response->choices[0]->message->content;
94+
$content = str_replace('\\\/', "\/", $content);
95+
$translations = json_decode($content, true);
7096

71-
try {
72-
return $callback();
73-
} catch (\Throwable $th) {
74-
throw $th;
75-
} finally {
76-
set_time_limit($initial);
77-
}
97+
return $translations;
98+
};
99+
})
100+
->all();
101+
102+
$results = $this->withTemporaryTimeout(
103+
static::getTimeout() * count($tasks),
104+
fn () => Concurrency::run($tasks),
105+
);
106+
107+
return collect($results)->collapse()->toArray();
78108
}
79109

80110
public function translateAll(array $texts, string $targetLocale): array
81111
{
82-
return $this->withTemporaryTimeout(
83-
static::getTimeout(),
84-
function () use ($texts, $targetLocale) {
85-
return collect($texts)
86-
->chunk(50)
87-
->map(function ($chunk) use ($targetLocale) {
88-
$response = $this->client->chat()->create([
89-
'model' => $this->model,
90-
'response_format' => ['type' => 'json_object'],
91-
'messages' => [
92-
[
93-
'role' => 'system',
94-
'content' => str_replace('{targetLocale}', $targetLocale, $this->prompt),
95-
],
96-
[
97-
'role' => 'user',
98-
'content' => $chunk->toJson(),
99-
],
100-
],
101-
]);
112+
if ($this->concurrency) {
113+
return $this->translateAllWithConcurrency($texts, $targetLocale);
114+
}
102115

103-
$content = $response->choices[0]->message->content;
104-
$content = str_replace('\\\/', "\/", $content);
105-
$translations = json_decode($content, true);
116+
$chunks = collect($texts)->chunk($this->chunk);
106117

107-
return $translations;
108-
})
109-
->collapse()
110-
->toArray();
111-
}
118+
return $this->withTemporaryTimeout(
119+
static::getTimeout() * count($chunks),
120+
fn () => $chunks->map(function ($chunk) use ($targetLocale) {
121+
122+
$response = $this->client->chat()->create([
123+
'model' => $this->model,
124+
'response_format' => ['type' => 'json_object'],
125+
'messages' => [
126+
[
127+
'role' => 'system',
128+
'content' => str_replace('{targetLocale}', $targetLocale, $this->prompt),
129+
],
130+
[
131+
'role' => 'user',
132+
'content' => $chunk->toJson(),
133+
],
134+
],
135+
]);
136+
137+
$content = $response->choices[0]->message->content;
138+
$content = str_replace('\\\/', "\/", $content);
139+
$translations = json_decode($content, true);
140+
141+
return $translations;
142+
})->collapse()->toArray()
112143
);
113144

114145
}

0 commit comments

Comments
 (0)