Skip to content

Commit bb6b953

Browse files
committed
feature #409 [Platform][OpenAI] Add region option (OskarStark)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform][OpenAI] Add `region` option | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | no | Issues | Fix #407 | License | MIT Commits ------- 0a0b6bc [Platform][OpenAI] Add `region` option
2 parents 2337657 + 0a0b6bc commit bb6b953

File tree

16 files changed

+246
-62
lines changed

16 files changed

+246
-62
lines changed

src/ai-bundle/config/options.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Codewithkyrian\ChromaDB\Client as ChromaDbClient;
1515
use MongoDB\Client as MongoDbClient;
1616
use Probots\Pinecone\Client as PineconeClient;
17+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
1718
use Symfony\AI\Platform\PlatformInterface;
1819
use Symfony\AI\Store\StoreInterface;
1920

@@ -59,6 +60,14 @@
5960
->arrayNode('openai')
6061
->children()
6162
->scalarNode('api_key')->isRequired()->end()
63+
->scalarNode('region')
64+
->defaultNull()
65+
->validate()
66+
->ifNotInArray([null, PlatformFactory::REGION_EU, PlatformFactory::REGION_US])
67+
->thenInvalid('The region must be either "EU" (https://eu.api.openai.com), "US" (https://us.api.openai.com) or null (https://api.openai.com)')
68+
->end()
69+
->info('The region for OpenAI API (EU, US, or null for default)')
70+
->end()
6271
->end()
6372
->end()
6473
->arrayNode('mistral')

src/ai-bundle/src/AiBundle.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
316316
$platform['api_key'],
317317
new Reference('http_client', ContainerInterface::NULL_ON_INVALID_REFERENCE),
318318
new Reference('ai.platform.contract.openai'),
319+
$platform['region'] ?? null,
319320
])
320321
->addTag('ai.platform');
321322

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ public function testTokenUsageProcessorTags()
459459
'ai' => [
460460
'platform' => [
461461
'openai' => [
462-
'api_key' => 'test_key',
462+
'api_key' => 'sk-test_key',
463463
],
464464
],
465465
'agent' => [
@@ -489,6 +489,71 @@ public function testTokenUsageProcessorTags()
489489
$this->assertTrue($foundTag, 'Token usage processor should have output tag with full agent ID');
490490
}
491491

492+
public function testOpenAiPlatformWithDefaultRegion()
493+
{
494+
$container = $this->buildContainer([
495+
'ai' => [
496+
'platform' => [
497+
'openai' => [
498+
'api_key' => 'sk-test-key',
499+
],
500+
],
501+
],
502+
]);
503+
504+
$this->assertTrue($container->hasDefinition('ai.platform.openai'));
505+
506+
$definition = $container->getDefinition('ai.platform.openai');
507+
$arguments = $definition->getArguments();
508+
509+
$this->assertCount(4, $arguments);
510+
$this->assertSame('sk-test-key', $arguments[0]);
511+
$this->assertNull($arguments[3]); // region should be null by default
512+
}
513+
514+
#[TestWith(['EU'])]
515+
#[TestWith(['US'])]
516+
#[TestWith([null])]
517+
public function testOpenAiPlatformWithRegion(?string $region)
518+
{
519+
$container = $this->buildContainer([
520+
'ai' => [
521+
'platform' => [
522+
'openai' => [
523+
'api_key' => 'sk-test-key',
524+
'region' => $region,
525+
],
526+
],
527+
],
528+
]);
529+
530+
$this->assertTrue($container->hasDefinition('ai.platform.openai'));
531+
532+
$definition = $container->getDefinition('ai.platform.openai');
533+
$arguments = $definition->getArguments();
534+
535+
$this->assertCount(4, $arguments);
536+
$this->assertSame('sk-test-key', $arguments[0]);
537+
$this->assertSame($region, $arguments[3]);
538+
}
539+
540+
public function testOpenAiPlatformWithInvalidRegion()
541+
{
542+
$this->expectException(InvalidConfigurationException::class);
543+
$this->expectExceptionMessage('The region must be either "EU" (https://eu.api.openai.com), "US" (https://us.api.openai.com) or null (https://api.openai.com)');
544+
545+
$this->buildContainer([
546+
'ai' => [
547+
'platform' => [
548+
'openai' => [
549+
'api_key' => 'sk-test-key',
550+
'region' => 'INVALID',
551+
],
552+
],
553+
],
554+
]);
555+
}
556+
492557
private function buildContainer(array $configuration): ContainerBuilder
493558
{
494559
$container = new ContainerBuilder();

src/platform/src/Bridge/LmStudio/Completions/ModelClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313

1414
use Symfony\AI\Platform\Bridge\LmStudio\Completions;
1515
use Symfony\AI\Platform\Model;
16-
use Symfony\AI\Platform\ModelClientInterface as PlatformResponseFactory;
16+
use Symfony\AI\Platform\ModelClientInterface;
1717
use Symfony\AI\Platform\Result\RawHttpResult;
1818
use Symfony\Component\HttpClient\EventSourceHttpClient;
1919
use Symfony\Contracts\HttpClient\HttpClientInterface;
2020

2121
/**
2222
* @author André Lubian <[email protected]>
2323
*/
24-
final readonly class ModelClient implements PlatformResponseFactory
24+
final readonly class ModelClient implements ModelClientInterface
2525
{
2626
private EventSourceHttpClient $httpClient;
2727

src/platform/src/Bridge/LmStudio/Embeddings/ModelClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313

1414
use Symfony\AI\Platform\Bridge\LmStudio\Embeddings;
1515
use Symfony\AI\Platform\Model;
16-
use Symfony\AI\Platform\ModelClientInterface as PlatformResponseFactory;
16+
use Symfony\AI\Platform\ModelClientInterface;
1717
use Symfony\AI\Platform\Result\RawHttpResult;
1818
use Symfony\Contracts\HttpClient\HttpClientInterface;
1919

2020
/**
2121
* @author Christopher Hertel <[email protected]>
2222
* @author André Lubian <[email protected]>
2323
*/
24-
final readonly class ModelClient implements PlatformResponseFactory
24+
final readonly class ModelClient implements ModelClientInterface
2525
{
2626
public function __construct(
2727
private HttpClientInterface $httpClient,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Bridge\OpenAi;
13+
14+
use Symfony\AI\Platform\Exception\InvalidArgumentException;
15+
16+
/**
17+
* @author Oskar Stark <[email protected]>
18+
*/
19+
abstract readonly class AbstractModelClient
20+
{
21+
protected static function getBaseUrl(?string $region): string
22+
{
23+
return match ($region) {
24+
null => 'https://api.openai.com',
25+
PlatformFactory::REGION_EU => 'https://eu.api.openai.com',
26+
PlatformFactory::REGION_US => 'https://us.api.openai.com',
27+
default => throw new InvalidArgumentException(\sprintf('Invalid region "%s". Valid options are: "%s", "%s", or null.', $region, PlatformFactory::REGION_EU, PlatformFactory::REGION_US)),
28+
};
29+
}
30+
31+
protected static function validateApiKey(string $apiKey): void
32+
{
33+
if ('' === $apiKey) {
34+
throw new InvalidArgumentException('The API key must not be empty.');
35+
}
36+
37+
if (!str_starts_with($apiKey, 'sk-')) {
38+
throw new InvalidArgumentException('The API key must start with "sk-".');
39+
}
40+
}
41+
}

src/platform/src/Bridge/OpenAi/DallE/ModelClient.php

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\OpenAi\DallE;
1313

14+
use Symfony\AI\Platform\Bridge\OpenAi\AbstractModelClient;
1415
use Symfony\AI\Platform\Bridge\OpenAi\DallE;
15-
use Symfony\AI\Platform\Exception\InvalidArgumentException;
1616
use Symfony\AI\Platform\Model;
1717
use Symfony\AI\Platform\ModelClientInterface;
1818
use Symfony\AI\Platform\Result\RawHttpResult;
@@ -23,18 +23,14 @@
2323
*
2424
* @author Denis Zunke <[email protected]>
2525
*/
26-
final readonly class ModelClient implements ModelClientInterface
26+
final readonly class ModelClient extends AbstractModelClient implements ModelClientInterface
2727
{
2828
public function __construct(
2929
private HttpClientInterface $httpClient,
3030
#[\SensitiveParameter] private string $apiKey,
31+
private ?string $region = null,
3132
) {
32-
if ('' === $apiKey) {
33-
throw new InvalidArgumentException('The API key must not be empty.');
34-
}
35-
if (!str_starts_with($apiKey, 'sk-')) {
36-
throw new InvalidArgumentException('The API key must start with "sk-".');
37-
}
33+
self::validateApiKey($apiKey);
3834
}
3935

4036
public function supports(Model $model): bool
@@ -44,7 +40,7 @@ public function supports(Model $model): bool
4440

4541
public function request(Model $model, array|string $payload, array $options = []): RawHttpResult
4642
{
47-
return new RawHttpResult($this->httpClient->request('POST', 'https://api.openai.com/v1/images/generations', [
43+
return new RawHttpResult($this->httpClient->request('POST', self::getBaseUrl($this->region).'/v1/images/generations', [
4844
'auth_bearer' => $this->apiKey,
4945
'json' => array_merge($options, [
5046
'model' => $model->getName(),

src/platform/src/Bridge/OpenAi/Embeddings/ModelClient.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,24 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\OpenAi\Embeddings;
1313

14+
use Symfony\AI\Platform\Bridge\OpenAi\AbstractModelClient;
1415
use Symfony\AI\Platform\Bridge\OpenAi\Embeddings;
15-
use Symfony\AI\Platform\Exception\InvalidArgumentException;
1616
use Symfony\AI\Platform\Model;
17-
use Symfony\AI\Platform\ModelClientInterface as PlatformResponseFactory;
17+
use Symfony\AI\Platform\ModelClientInterface;
1818
use Symfony\AI\Platform\Result\RawHttpResult;
1919
use Symfony\Contracts\HttpClient\HttpClientInterface;
2020

2121
/**
2222
* @author Christopher Hertel <[email protected]>
2323
*/
24-
final readonly class ModelClient implements PlatformResponseFactory
24+
final readonly class ModelClient extends AbstractModelClient implements ModelClientInterface
2525
{
2626
public function __construct(
2727
private HttpClientInterface $httpClient,
2828
#[\SensitiveParameter] private string $apiKey,
29+
private ?string $region = null,
2930
) {
30-
if ('' === $apiKey) {
31-
throw new InvalidArgumentException('The API key must not be empty.');
32-
}
33-
if (!str_starts_with($apiKey, 'sk-')) {
34-
throw new InvalidArgumentException('The API key must start with "sk-".');
35-
}
31+
self::validateApiKey($apiKey);
3632
}
3733

3834
public function supports(Model $model): bool
@@ -42,7 +38,7 @@ public function supports(Model $model): bool
4238

4339
public function request(Model $model, array|string $payload, array $options = []): RawHttpResult
4440
{
45-
return new RawHttpResult($this->httpClient->request('POST', 'https://api.openai.com/v1/embeddings', [
41+
return new RawHttpResult($this->httpClient->request('POST', self::getBaseUrl($this->region).'/v1/embeddings', [
4642
'auth_bearer' => $this->apiKey,
4743
'json' => array_merge($options, [
4844
'model' => $model->getName(),

src/platform/src/Bridge/OpenAi/Gpt/ModelClient.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,28 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\OpenAi\Gpt;
1313

14+
use Symfony\AI\Platform\Bridge\OpenAi\AbstractModelClient;
1415
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
15-
use Symfony\AI\Platform\Exception\InvalidArgumentException;
1616
use Symfony\AI\Platform\Model;
17-
use Symfony\AI\Platform\ModelClientInterface as PlatformResponseFactory;
17+
use Symfony\AI\Platform\ModelClientInterface;
1818
use Symfony\AI\Platform\Result\RawHttpResult;
1919
use Symfony\Component\HttpClient\EventSourceHttpClient;
2020
use Symfony\Contracts\HttpClient\HttpClientInterface;
2121

2222
/**
2323
* @author Christopher Hertel <[email protected]>
2424
*/
25-
final readonly class ModelClient implements PlatformResponseFactory
25+
final readonly class ModelClient extends AbstractModelClient implements ModelClientInterface
2626
{
2727
private EventSourceHttpClient $httpClient;
2828

2929
public function __construct(
3030
HttpClientInterface $httpClient,
3131
#[\SensitiveParameter] private string $apiKey,
32+
private ?string $region = null,
3233
) {
3334
$this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
34-
if ('' === $apiKey) {
35-
throw new InvalidArgumentException('The API key must not be empty.');
36-
}
37-
if (!str_starts_with($apiKey, 'sk-')) {
38-
throw new InvalidArgumentException('The API key must start with "sk-".');
39-
}
35+
self::validateApiKey($apiKey);
4036
}
4137

4238
public function supports(Model $model): bool
@@ -46,7 +42,7 @@ public function supports(Model $model): bool
4642

4743
public function request(Model $model, array|string $payload, array $options = []): RawHttpResult
4844
{
49-
return new RawHttpResult($this->httpClient->request('POST', 'https://api.openai.com/v1/chat/completions', [
45+
return new RawHttpResult($this->httpClient->request('POST', self::getBaseUrl($this->region).'/v1/chat/completions', [
5046
'auth_bearer' => $this->apiKey,
5147
'json' => array_merge($options, $payload),
5248
]));

src/platform/src/Bridge/OpenAi/PlatformFactory.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,23 @@
2424
*/
2525
final readonly class PlatformFactory
2626
{
27+
public const REGION_EU = 'EU';
28+
public const REGION_US = 'US';
29+
2730
public static function create(
2831
#[\SensitiveParameter] string $apiKey,
2932
?HttpClientInterface $httpClient = null,
3033
?Contract $contract = null,
34+
?string $region = null,
3135
): Platform {
3236
$httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient);
3337

3438
return new Platform(
3539
[
36-
new Gpt\ModelClient($httpClient, $apiKey),
37-
new Embeddings\ModelClient($httpClient, $apiKey),
38-
new DallE\ModelClient($httpClient, $apiKey),
39-
new WhisperModelClient($httpClient, $apiKey),
40+
new Gpt\ModelClient($httpClient, $apiKey, $region),
41+
new Embeddings\ModelClient($httpClient, $apiKey, $region),
42+
new DallE\ModelClient($httpClient, $apiKey, $region),
43+
new WhisperModelClient($httpClient, $apiKey, $region),
4044
],
4145
[
4246
new Gpt\ResultConverter(),

0 commit comments

Comments
 (0)