Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9eb3ccd
[Store][Pinecone] Add `ManagedStoreInterface` support
OskarStark Dec 19, 2025
42400a7
feature #1215 [Store][Pinecone] Add `ManagedStoreInterface` support (…
OskarStark Dec 21, 2025
584581e
Add ClaudeModelClientTest for Bedrock Anthropic bridge
OskarStark Dec 21, 2025
4c51025
minor #1231 [Platform][Bedrock] Add `ClaudeModelClientTest` for Anthr…
OskarStark Dec 21, 2025
7b03968
feat(platform): allow configure bedrock platform with support of mult…
uerka Dec 18, 2025
1107cf1
fix(nova): avoid populate model name to message payload
uerka Dec 18, 2025
6f955a9
chore(bedrock): throw exception if client is missing
uerka Dec 18, 2025
2bd33fb
chore(bedrock): ensure bedrock platform can be initiated via bundle c…
uerka Dec 18, 2025
754aae8
chore(bedrock): cover model clients with tests
uerka Dec 18, 2025
da78df0
chore(bedrock): allow pass null as bedrock client
uerka Dec 18, 2025
dad6939
chore(bedrock): rollback unnecessary change
uerka Dec 18, 2025
bb3258b
fix(demo): fixing typo in package name
uerka Dec 18, 2025
6788d75
chore(cs): strict comparison to null for passed bedrock runtime client
uerka Dec 20, 2025
9031970
chore(cs): assertEquals -> assertSame
uerka Dec 20, 2025
44bc2e2
chore(cs): missing quote
uerka Dec 20, 2025
14bdc91
fix(bundle): check for bedrock platform package only in case at least…
uerka Dec 20, 2025
4a597bd
chore(cs): rollback assertEquals -> assertSame where was not intended
uerka Dec 20, 2025
232f3f9
chore(bedrock): rename ai.platform.bedrock_{name} to ai.platform.bedr…
uerka Dec 21, 2025
aa46462
chore(docs): change to ai.platform.bedrock.{name} for agent definition
uerka Dec 21, 2025
6c8dec9
chore(cs): fixing cs
uerka Dec 21, 2025
f219864
chore(cs): fixing cs
uerka Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/bundles/ai-bundle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ Advanced Example with Multiple Agents
deployment: '%env(AZURE_OPENAI_GPT)%'
api_key: '%env(AZURE_OPENAI_KEY)%'
api_version: '%env(AZURE_GPT_VERSION)%'
bedrock:
# multiple instances possible - for example region depending
default: ~
eu:
bedrock_runtime_client: 'async_aws.client.bedrock_runtime_eu'
eleven_labs:
host: '%env(ELEVEN_LABS_HOST)%'
api_key: '%env(ELEVEN_LABS_API_KEY)%'
Expand Down Expand Up @@ -100,6 +105,10 @@ Advanced Example with Multiple Agents
platform: 'ai.platform.eleven_labs'
model: 'text-to-speech'
tools: false
nova:
platform: 'ai.platform.bedrock.default'
model: 'nova-pro'
tools: false
store:
chromadb:
# multiple collections possible per type
Expand Down
4 changes: 2 additions & 2 deletions examples/.env
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ MAPBOX_ACCESS_TOKEN=
MONGODB_URI=mongodb://symfony:symfony@127.0.0.1:27017

# For using Pinecone (store)
PINECONE_API_KEY=
PINECONE_HOST=
PINECONE_API_KEY=pclocal
PINECONE_HOST=http://127.0.0.1:5080

# For using Postgres (store)
POSTGRES_URI=pdo-pgsql://postgres:postgres@127.0.1:5432/my_database
Expand Down
6 changes: 6 additions & 0 deletions examples/commands/stores.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tools\DsnParser;
use MongoDB\Client as MongoDbClient;
use Probots\Pinecone\Client as PineconeClient;
use Symfony\AI\Store\Bridge\Cache\Store as CacheStore;
use Symfony\AI\Store\Bridge\ClickHouse\Store as ClickHouseStore;
use Symfony\AI\Store\Bridge\Elasticsearch\Store as ElasticsearchStore;
Expand All @@ -24,6 +25,7 @@
use Symfony\AI\Store\Bridge\MongoDb\Store as MongoDbStore;
use Symfony\AI\Store\Bridge\Neo4j\Store as Neo4jStore;
use Symfony\AI\Store\Bridge\OpenSearch\Store as OpenSearchStore;
use Symfony\AI\Store\Bridge\Pinecone\Store as PineconeStore;
use Symfony\AI\Store\Bridge\Postgres\Store as PostgresStore;
use Symfony\AI\Store\Bridge\Qdrant\Store as QdrantStore;
use Symfony\AI\Store\Bridge\Redis\Store as RedisStore;
Expand Down Expand Up @@ -99,6 +101,10 @@
// env('OPENSEARCH_ENDPOINT'),
// 'symfony',
// ),
// 'pinecone' => static fn (): PineconeStore => new PineconeStore(
// new PineconeClient(env('PINECONE_API_KEY'), env('PINECONE_HOST')),
// 'symfony',
// ),
'postgres' => static fn (): PostgresStore => PostgresStore::fromDbal(
DriverManager::getConnection((new DsnParser())->parse(env('POSTGRES_URI'))),
'my_table',
Expand Down
9 changes: 9 additions & 0 deletions examples/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ services:
ports:
- '9201:9200'

pinecone:
image: ghcr.io/pinecone-io/pinecone-local:latest
platform: linux/amd64
environment:
PORT: 5080
PINECONE_HOST: localhost
ports:
- '5080-5090:5080-5090'

opensearch:
image: opensearchproject/opensearch
environment:
Expand Down
2 changes: 1 addition & 1 deletion examples/rag/pinecone.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
require_once dirname(__DIR__).'/bootstrap.php';

// initialize the store
$store = new Store(Pinecone::client(env('PINECONE_API_KEY'), env('PINECONE_HOST')));
$store = new Store(Pinecone::client(env('PINECONE_API_KEY'), env('PINECONE_HOST')), 'symfony');

// create embeddings and documents
$documents = [];
Expand Down
13 changes: 13 additions & 0 deletions src/ai-bundle/config/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@
->end()
->end()
->end()
->arrayNode('bedrock')
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->stringNode('bedrock_runtime_client')
->defaultNull()
->info('Service ID of the Bedrock runtime client to use')
->end()
->stringNode('model_catalog')->defaultNull()->end()
->end()
->end()
->end()
->arrayNode('cache')
->useAttributeAsKey('name')
->arrayPrototype()
Expand Down Expand Up @@ -800,6 +812,7 @@
->cannotBeEmpty()
->defaultValue(PineconeClient::class)
->end()
->stringNode('index_name')->isRequired()->end()
->stringNode('namespace')->end()
->arrayNode('filter')
->scalarPrototype()
Expand Down
2 changes: 2 additions & 0 deletions src/ai-bundle/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Symfony\AI\Platform\Bridge\Anthropic\Contract\AnthropicContract;
use Symfony\AI\Platform\Bridge\Anthropic\ModelCatalog as AnthropicModelCatalog;
use Symfony\AI\Platform\Bridge\Azure\OpenAi\ModelCatalog as AzureOpenAiModelCatalog;
use Symfony\AI\Platform\Bridge\Bedrock\ModelCatalog as BedrockModelCatalog;
use Symfony\AI\Platform\Bridge\Cartesia\ModelCatalog as CartesiaModelCatalog;
use Symfony\AI\Platform\Bridge\Cerebras\ModelCatalog as CerebrasModelCatalog;
use Symfony\AI\Platform\Bridge\Decart\ModelCatalog as DecartModelCatalog;
Expand Down Expand Up @@ -96,6 +97,7 @@
->set('ai.platform.model_catalog.albert', AlbertModelCatalog::class)
->set('ai.platform.model_catalog.anthropic', AnthropicModelCatalog::class)
->set('ai.platform.model_catalog.azure.openai', AzureOpenAiModelCatalog::class)
->set('ai.platform.model_catalog.bedrock', BedrockModelCatalog::class)
->set('ai.platform.model_catalog.cartesia', CartesiaModelCatalog::class)
->set('ai.platform.model_catalog.cerebras', CerebrasModelCatalog::class)
->set('ai.platform.model_catalog.decart', DecartModelCatalog::class)
Expand Down
30 changes: 29 additions & 1 deletion src/ai-bundle/src/AiBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use Symfony\AI\Platform\Bridge\Albert\PlatformFactory as AlbertPlatformFactory;
use Symfony\AI\Platform\Bridge\Anthropic\PlatformFactory as AnthropicPlatformFactory;
use Symfony\AI\Platform\Bridge\Azure\OpenAi\PlatformFactory as AzureOpenAiPlatformFactory;
use Symfony\AI\Platform\Bridge\Bedrock\PlatformFactory as BedrockFactory;
use Symfony\AI\Platform\Bridge\Cartesia\PlatformFactory as CartesiaPlatformFactory;
use Symfony\AI\Platform\Bridge\Cerebras\PlatformFactory as CerebrasPlatformFactory;
use Symfony\AI\Platform\Bridge\Decart\PlatformFactory as DecartPlatformFactory;
Expand Down Expand Up @@ -408,6 +409,31 @@ private function processPlatformConfig(string $type, array $platform, ContainerB
return;
}

if ('bedrock' === $type) {
foreach ($platform as $name => $config) {
if (!ContainerBuilder::willBeAvailable('symfony/ai-bedrock-platform', BedrockFactory::class, ['symfony/ai-bundle'])) {
throw new RuntimeException('Bedrock platform configuration requires "symfony/ai-bedrock-platform" package. Try running "composer require symfony/ai-bedrock-platform".');
}

$platformId = 'ai.platform.bedrock.'.$name;
$definition = (new Definition(Platform::class))
->setFactory(BedrockFactory::class.'::create')
->setLazy(true)
->addTag('proxy', ['interface' => PlatformInterface::class])
->setArguments([
$config['bedrock_runtime_client'] ? new Reference($config['bedrock_runtime_client'], ContainerInterface::NULL_ON_INVALID_REFERENCE) : null,
$config['model_catalog'] ? new Reference($config['model_catalog']) : new Reference('ai.platform.model_catalog.bedrock'),
null,
new Reference('event_dispatcher'),
])
->addTag('ai.platform', ['name' => 'bedrock.'.$name]);

$container->setDefinition($platformId, $definition);
}

return;
}

if ('cache' === $type) {
foreach ($platform as $name => $cachedPlatformConfig) {
$definition = (new Definition(CachedPlatform::class))
Expand Down Expand Up @@ -1519,19 +1545,21 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde
foreach ($stores as $name => $store) {
$arguments = [
new Reference($store['client']),
$store['index_name'],
$store['namespace'] ?? $name,
$store['filter'],
];

if (\array_key_exists('top_k', $store)) {
$arguments[3] = $store['top_k'];
$arguments[4] = $store['top_k'];
}

$definition = new Definition(PineconeStore::class);
$definition
->setLazy(true)
->setArguments($arguments)
->addTag('proxy', ['interface' => StoreInterface::class])
->addTag('proxy', ['interface' => ManagedStoreInterface::class])
->addTag('ai.store');

$container->setDefinition('ai.store.'.$type.'.'.$name, $definition);
Expand Down
107 changes: 23 additions & 84 deletions src/ai-bundle/tests/DependencyInjection/AiBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\AI\AiBundle\Tests\DependencyInjection;

use AsyncAws\BedrockRuntime\BedrockRuntimeClient;
use Codewithkyrian\ChromaDB\Client;
use MongoDB\Client as MongoDbClient;
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
Expand Down Expand Up @@ -2370,48 +2371,13 @@ public function testOpenSearchStoreWithCustomHttpClientCanBeConfigured()
}

public function testPineconeStoreCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
'store' => [
'pinecone' => [
'my_pinecone_store' => [],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store'));

$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
$this->assertSame(PineconeStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());
$this->assertCount(3, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
$this->assertSame('my_pinecone_store', $definition->getArgument(1));
$this->assertSame([], $definition->getArgument(2));

$this->assertTrue($definition->hasTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
$this->assertTrue($definition->hasTag('ai.store'));

$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore'));
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
}

public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
'store' => [
'pinecone' => [
'my_pinecone_store' => [
'namespace' => 'my_namespace',
'index_name' => 'my_index',
],
],
],
Expand All @@ -2424,14 +2390,15 @@ public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
$this->assertSame(PineconeStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());
$this->assertCount(3, $definition->getArguments());
$this->assertCount(4, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
$this->assertSame('my_namespace', $definition->getArgument(1));
$this->assertSame([], $definition->getArgument(2));
$this->assertSame('my_index', $definition->getArgument(1));
$this->assertSame('my_pinecone_store', $definition->getArgument(2));
$this->assertSame([], $definition->getArgument(3));

$this->assertTrue($definition->hasTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class], ['interface' => ManagedStoreInterface::class]], $definition->getTag('proxy'));
$this->assertTrue($definition->hasTag('ai.store'));

$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
Expand All @@ -2441,14 +2408,14 @@ public function testPineconeStoreWithCustomNamespaceCanBeConfigured()
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
}

public function testPineconeStoreWithCustomClientCanBeConfigured()
public function testPineconeStoreWithCustomIndexNameCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
'store' => [
'pinecone' => [
'my_pinecone_store' => [
'client' => 'foo',
'index_name' => 'custom_index',
'namespace' => 'my_namespace',
],
],
Expand All @@ -2461,54 +2428,16 @@ public function testPineconeStoreWithCustomClientCanBeConfigured()
$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
$this->assertSame(PineconeStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());
$this->assertCount(3, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame('foo', (string) $definition->getArgument(0));
$this->assertSame('my_namespace', $definition->getArgument(1));
$this->assertSame([], $definition->getArgument(2));

$this->assertTrue($definition->hasTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
$this->assertTrue($definition->hasTag('ai.store'));

$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $myPineconeStore'));
$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $pinecone_my_pinecone_store'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface $pineconeMyPineconeStore'));
$this->assertTrue($container->hasAlias('Symfony\AI\Store\StoreInterface'));
}

public function testPineconeStoreWithTopKCanBeConfigured()
{
$container = $this->buildContainer([
'ai' => [
'store' => [
'pinecone' => [
'my_pinecone_store' => [
'namespace' => 'my_namespace',
'top_k' => 100,
],
],
],
],
]);

$this->assertTrue($container->hasDefinition('ai.store.pinecone.my_pinecone_store'));

$definition = $container->getDefinition('ai.store.pinecone.my_pinecone_store');
$this->assertSame(PineconeStore::class, $definition->getClass());

$this->assertTrue($definition->isLazy());
$this->assertCount(4, $definition->getArguments());
$this->assertInstanceOf(Reference::class, $definition->getArgument(0));
$this->assertSame(PineconeClient::class, (string) $definition->getArgument(0));
$this->assertSame('my_namespace', $definition->getArgument(1));
$this->assertSame([], $definition->getArgument(2));
$this->assertSame(100, $definition->getArgument(3));
$this->assertSame('custom_index', $definition->getArgument(1));
$this->assertSame('my_namespace', $definition->getArgument(2));
$this->assertSame([], $definition->getArgument(3));

$this->assertTrue($definition->hasTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class]], $definition->getTag('proxy'));
$this->assertSame([['interface' => StoreInterface::class], ['interface' => ManagedStoreInterface::class]], $definition->getTag('proxy'));
$this->assertTrue($definition->hasTag('ai.store'));

$this->assertTrue($container->hasAlias('.Symfony\AI\Store\StoreInterface $my_pinecone_store'));
Expand Down Expand Up @@ -7013,6 +6942,7 @@ private function buildContainer(array $configuration): ContainerBuilder
$container->setParameter('kernel.environment', 'dev');
$container->setParameter('kernel.build_dir', 'public');
$container->setDefinition(ClockInterface::class, new Definition(MonotonicClock::class));
$container->setDefinition('async_aws.client.bedrock_us', new Definition(BedrockRuntimeClient::class));

$extension = (new AiBundle())->getContainerExtension();
$extension->load($configuration, $container);
Expand Down Expand Up @@ -7049,6 +6979,12 @@ private function getFullConfig(): array
'api_version' => '2024-02-15-preview',
],
],
'bedrock' => [
'default' => [],
'us' => [
'bedrock_runtime_client' => 'async_aws.client.bedrock_us',
],
],
'cache' => [
'azure' => [
'platform' => 'ai.platform.azure.my_azure_instance',
Expand Down Expand Up @@ -7317,15 +7253,18 @@ private function getFullConfig(): array
],
'pinecone' => [
'my_pinecone_store' => [
'index_name' => 'my_index',
'namespace' => 'my_namespace',
'filter' => ['category' => 'books'],
'top_k' => 10,
],
'my_pinecone_store_with_filter' => [
'index_name' => 'my_index',
'namespace' => 'my_namespace',
'filter' => ['category' => 'books'],
],
'my_pinecone_store_with_top_k' => [
'index_name' => 'my_index',
'namespace' => 'my_namespace',
'filter' => ['category' => 'books'],
'top_k' => 10,
Expand Down
2 changes: 2 additions & 0 deletions src/platform/src/Bridge/Bedrock/Nova/NovaModelClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public function supports(Model $model): bool

public function request(Model $model, array|string $payload, array $options = []): RawBedrockResult
{
unset($payload['model']);

$modelOptions = [];
if (isset($options['tools'])) {
$modelOptions['toolConfig']['tools'] = $options['tools'];
Expand Down
Loading