diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml new file mode 100644 index 000000000..ec25b5d93 --- /dev/null +++ b/.github/workflows/validation.yaml @@ -0,0 +1,81 @@ +name: Validation + +on: + push: + paths: + - 'src/store/src/Bridge/**/composer.json' + - 'src/ai-bundle/config/options.php' + - '.github/workflows/validation.yaml' + pull_request: + paths: + - 'src/store/src/Bridge/**/composer.json' + - 'src/ai-bundle/config/options.php' + - '.github/workflows/validation.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + validate_stores: + name: Validate Store Bridge Naming + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Validate store bridge naming conventions + run: | + #!/bin/bash + set -e + + ERRORS=0 + + # Find all store bridges with composer.json + for composer_file in src/store/src/Bridge/*/composer.json; do + if [[ ! -f "$composer_file" ]]; then + continue + fi + + # Get the bridge directory name (e.g., ChromaDb) + bridge_dir=$(dirname "$composer_file") + bridge_name=$(basename "$bridge_dir") + + # Get the package name from composer.json + package_name=$(jq -r '.name' "$composer_file") + + # Expected package name format: symfony/ai-{lowercase-with-dashes}-store + # Convert PascalCase to kebab-case (e.g., ChromaDb -> chroma-db) + expected_kebab=$(echo "$bridge_name" | sed 's/\([a-z]\)\([A-Z]\)/\1-\2/g' | tr '[:upper:]' '[:lower:]') + expected_package="symfony/ai-${expected_kebab}-store" + + if [[ "$package_name" != "$expected_package" ]]; then + echo "::error file=$composer_file::Package name '$package_name' does not match expected '$expected_package' for bridge '$bridge_name'" + ERRORS=$((ERRORS + 1)) + else + echo "✓ $bridge_name: package name '$package_name' is correct" + fi + + # Check options.php for the config key (should be lowercase without dashes/underscores) + expected_config_key=$(echo "$bridge_name" | tr '[:upper:]' '[:lower:]') + options_file="src/ai-bundle/config/options.php" + + if [[ -f "$options_file" ]]; then + # Look for ->arrayNode('configkey') under the store section + if ! grep -q -- "->arrayNode('$expected_config_key')" "$options_file"; then + echo "::error file=$options_file::Missing or incorrect config key for bridge '$bridge_name'. Expected '->arrayNode('$expected_config_key')' in store configuration" + ERRORS=$((ERRORS + 1)) + else + echo "✓ $bridge_name: config key '$expected_config_key' found in options.php" + fi + fi + done + + if [[ $ERRORS -gt 0 ]]; then + echo "" + echo "::error::Found $ERRORS naming convention violation(s)" + exit 1 + fi + + echo "" + echo "All store bridge naming conventions are valid!" diff --git a/demo/composer.json b/demo/composer.json index 04fe859a6..5ad6c3b0a 100644 --- a/demo/composer.json +++ b/demo/composer.json @@ -8,7 +8,7 @@ "ext-ctype": "*", "ext-gd": "*", "ext-iconv": "*", - "codewithkyrian/chromadb-php": "^0.4.0", + "symfony/ai-chroma-db-store": "@dev", "league/commonmark": "^2.7.1", "mrmysql/youtube-transcript": "^0.0.5", "php-http/discovery": "^1.20", diff --git a/deptrac.yaml b/deptrac.yaml index a250fea73..8f05dd754 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -64,7 +64,11 @@ deptrac: - name: StoreComponent collectors: - type: classLike - value: Symfony\\AI\\Store.* + value: ^Symfony\\AI\\Store\\(?!Bridge\\).* + - name: ChromaDbStore + collectors: + - type: classLike + value: Symfony\\AI\\Store\\Bridge\\ChromaDb\\.* ruleset: AgentComponent: - PlatformComponent @@ -101,6 +105,9 @@ deptrac: PlatformComponent: ~ StoreComponent: - PlatformComponent + ChromaDbStore: + - StoreComponent + - PlatformComponent # Baseline of known violations to be skipped for now skip_violations: Symfony\AI\Platform\Bridge\Anthropic\TokenOutputProcessor: [Symfony\AI\Agent\OutputProcessorInterface, Symfony\AI\Agent\Output] diff --git a/examples/composer.json b/examples/composer.json index 8812251e7..14e27d476 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -26,7 +26,7 @@ "ext-pdo": "*", "ext-redis": "*", "async-aws/bedrock-runtime": "^1.1", - "codewithkyrian/chromadb-php": "^0.4.0", + "symfony/ai-chroma-db-store": "@dev", "codewithkyrian/transformers": "^0.6.2", "doctrine/dbal": "^3.3|^4.0", "google/auth": "^1.47", diff --git a/src/ai-bundle/composer.json b/src/ai-bundle/composer.json index a984746b1..960da4498 100644 --- a/src/ai-bundle/composer.json +++ b/src/ai-bundle/composer.json @@ -31,7 +31,7 @@ "symfony/string": "^7.3|^8.0" }, "require-dev": { - "codewithkyrian/chromadb-php": "^0.2.1|^0.3|^0.4", + "symfony/ai-chroma-db-store": "@dev", "google/auth": "^1.47", "mongodb/mongodb": "^1.21|^2.0", "phpstan/phpstan": "^2.1", diff --git a/src/ai-bundle/src/AiBundle.php b/src/ai-bundle/src/AiBundle.php index 7fada7854..de61545c0 100644 --- a/src/ai-bundle/src/AiBundle.php +++ b/src/ai-bundle/src/AiBundle.php @@ -1033,6 +1033,10 @@ private function processStoreConfig(string $type, array $stores, ContainerBuilde } if ('chromadb' === $type) { + if (!ContainerBuilder::willBeAvailable('symfony/ai-chroma-db-store', ChromaDbStore::class, ['symfony/ai-bundle'])) { + throw new RuntimeException('ChromaDB store configuration requires "symfony/ai-chroma-db-store" package. Try running "composer require symfony/ai-chroma-db-store".'); + } + foreach ($stores as $name => $store) { $definition = new Definition(ChromaDbStore::class); $definition diff --git a/src/store/src/Bridge/ChromaDb/.gitattributes b/src/store/src/Bridge/ChromaDb/.gitattributes new file mode 100644 index 000000000..14c3c3594 --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.git* export-ignore diff --git a/src/store/src/Bridge/ChromaDb/.github/PULL_REQUEST_TEMPLATE.md b/src/store/src/Bridge/ChromaDb/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..fcb87228a --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/ai + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/src/store/src/Bridge/ChromaDb/.github/close-pull-request.yml b/src/store/src/Bridge/ChromaDb/.github/close-pull-request.yml new file mode 100644 index 000000000..bb5a02835 --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/.github/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/ai + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! diff --git a/src/store/src/Bridge/ChromaDb/.gitignore b/src/store/src/Bridge/ChromaDb/.gitignore new file mode 100644 index 000000000..76367ee5b --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +.phpunit.result.cache diff --git a/src/store/src/Bridge/ChromaDb/CHANGELOG.md b/src/store/src/Bridge/ChromaDb/CHANGELOG.md new file mode 100644 index 000000000..0915f3546 --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +0.1 +--- + + * Add the bridge diff --git a/src/store/src/Bridge/ChromaDb/LICENSE b/src/store/src/Bridge/ChromaDb/LICENSE new file mode 100644 index 000000000..bc38d714e --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/store/src/Bridge/ChromaDb/README.md b/src/store/src/Bridge/ChromaDb/README.md new file mode 100644 index 000000000..71083c9d4 --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/README.md @@ -0,0 +1,12 @@ +ChromaDB Store +============== + +Provides [ChromaDB](https://www.trychroma.com/) vector store integration for Symfony AI Store. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/ai/issues) and + [send Pull Requests](https://github.com/symfony/ai/pulls) + in the [main Symfony AI repository](https://github.com/symfony/ai) diff --git a/src/store/src/Bridge/ChromaDb/Store.php b/src/store/src/Bridge/ChromaDb/Store.php index 45ff3f933..fe6605114 100644 --- a/src/store/src/Bridge/ChromaDb/Store.php +++ b/src/store/src/Bridge/ChromaDb/Store.php @@ -15,7 +15,6 @@ use Symfony\AI\Platform\Vector\Vector; use Symfony\AI\Store\Document\Metadata; use Symfony\AI\Store\Document\VectorDocument; -use Symfony\AI\Store\Exception\RuntimeException; use Symfony\AI\Store\StoreInterface; use Symfony\Component\Uid\Uuid; @@ -28,9 +27,6 @@ public function __construct( private readonly Client $client, private readonly string $collectionName, ) { - if (!class_exists(Client::class)) { - throw new RuntimeException('For using the ChromaDB as retrieval vector store, the codewithkyrian/chromadb-php package is required. Try running "composer require codewithkyrian/chromadb-php".'); - } } public function add(VectorDocument ...$documents): void diff --git a/src/store/tests/Bridge/ChromaDb/StoreTest.php b/src/store/src/Bridge/ChromaDb/Tests/StoreTest.php similarity index 99% rename from src/store/tests/Bridge/ChromaDb/StoreTest.php rename to src/store/src/Bridge/ChromaDb/Tests/StoreTest.php index 5cb8d9944..d8cd6238d 100644 --- a/src/store/tests/Bridge/ChromaDb/StoreTest.php +++ b/src/store/src/Bridge/ChromaDb/Tests/StoreTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\AI\Store\Tests\Bridge\ChromaDb; +namespace Symfony\AI\Store\Bridge\ChromaDb\Tests; use Codewithkyrian\ChromaDB\Client; use Codewithkyrian\ChromaDB\Generated\Responses\QueryItemsResponse; diff --git a/src/store/src/Bridge/ChromaDb/composer.json b/src/store/src/Bridge/ChromaDb/composer.json new file mode 100644 index 000000000..b2e2c234e --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/composer.json @@ -0,0 +1,60 @@ +{ + "name": "symfony/ai-chroma-db-store", + "description": "ChromaDB vector store bridge for Symfony AI", + "license": "MIT", + "type": "symfony-ai-store", + "keywords": [ + "ai", + "bridge", + "chromadb", + "store", + "vector" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "codewithkyrian/chromadb-php": "^0.2.1|^0.3|^0.4", + "symfony/ai-platform": "@dev", + "symfony/ai-store": "@dev", + "symfony/uid": "^7.3|^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.13" + }, + "minimum-stability": "dev", + "autoload": { + "psr-4": { + "Symfony\\AI\\Store\\Bridge\\ChromaDb\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Symfony\\AI\\Store\\Bridge\\ChromaDb\\Tests\\": "Tests/" + } + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-main": "0.x-dev" + }, + "thanks": { + "name": "symfony/ai", + "url": "https://github.com/symfony/ai" + } + } +} diff --git a/src/store/src/Bridge/ChromaDb/phpunit.xml.dist b/src/store/src/Bridge/ChromaDb/phpunit.xml.dist new file mode 100644 index 000000000..2183ef0a6 --- /dev/null +++ b/src/store/src/Bridge/ChromaDb/phpunit.xml.dist @@ -0,0 +1,32 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + +