diff --git a/.github/build-packages.php b/.github/build-packages.php index aea55f365..0f4e4e2e2 100644 --- a/.github/build-packages.php +++ b/.github/build-packages.php @@ -9,7 +9,7 @@ use Symfony\Component\Finder\Finder; $finder = (new Finder()) - ->in([__DIR__.'/../src/*/']) + ->in([__DIR__.'/../src/*/', __DIR__.'/../examples/', __DIR__.'/../demo/']) ->depth(0) ->name('composer.json') ; diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml index 42ff9a0c4..21b29a662 100644 --- a/.github/workflows/code-quality.yaml +++ b/.github/workflows/code-quality.yaml @@ -60,9 +60,18 @@ jobs: - name: Build root packages run: php .github/build-packages.php + - name: Link examples + working-directory: examples + run: ../link + + - name: Install examples dependencies + uses: ramsey/composer-install@v3 + with: + working-directory: examples + - name: Run PHPStan on examples - run: | - cd examples/ && $COMPOSER_UP && ../link && $PHPSTAN + working-directory: examples + run: $PHPSTAN - name: Run PHPStan on packages run: | diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 84bb8f3fa..c7748df97 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -65,17 +65,17 @@ jobs: - name: Install root dependencies uses: ramsey/composer-install@v3 - - name: Install examples dependencies + - name: Build root packages + run: php .github/build-packages.php + + - name: Install root dependencies uses: ramsey/composer-install@v3 with: working-directory: examples - - name: Link examples - working-directory: examples - run: ../link - - name: Run commands examples - run: php examples/commands/stores.php + working-directory: examples + run: php commands/stores.php demo: runs-on: ubuntu-latest diff --git a/examples/composer.json b/examples/composer.json index 70c946e26..e268ca9aa 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -19,6 +19,7 @@ "symfony/ai-agent": "@dev", "symfony/ai-platform": "@dev", "symfony/ai-store": "@dev", + "symfony/ai-brave-tool": "@dev", "symfony/cache": "^7.3|^8.0", "symfony/console": "^7.3|^8.0", "symfony/css-selector": "^7.3|^8.0", diff --git a/examples/toolbox/brave.php b/examples/toolbox/brave.php index f0729733e..55da3c3ce 100644 --- a/examples/toolbox/brave.php +++ b/examples/toolbox/brave.php @@ -10,8 +10,8 @@ */ use Symfony\AI\Agent\Agent; +use Symfony\AI\Agent\Bridge\Brave\Brave; use Symfony\AI\Agent\Toolbox\AgentProcessor; -use Symfony\AI\Agent\Toolbox\Tool\Brave; use Symfony\AI\Agent\Toolbox\Tool\Crawler; use Symfony\AI\Agent\Toolbox\Toolbox; use Symfony\AI\Platform\Bridge\OpenAi\Gpt; diff --git a/link b/link index 64f795382..93b2b42f6 100755 --- a/link +++ b/link @@ -38,7 +38,10 @@ if (!is_dir("$pathToProject/vendor/symfony")) { $sfPackages = array(); $filesystem = new Filesystem(); -$directories = glob(__DIR__.'/src/*', GLOB_ONLYDIR | GLOB_NOSORT); +$braces = array('*', '*/src/Bridge/*'); +$directories = array_merge(...array_values(array_map(function ($part) { + return glob(__DIR__.'/src/'.$part, GLOB_ONLYDIR | GLOB_NOSORT); +}, $braces))); foreach ($directories as $dir) { if ($filesystem->exists($composer = "$dir/composer.json")) { diff --git a/src/agent/src/Bridge/Brave/.gitattributes b/src/agent/src/Bridge/Brave/.gitattributes new file mode 100644 index 000000000..14c3c3594 --- /dev/null +++ b/src/agent/src/Bridge/Brave/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.git* export-ignore diff --git a/src/agent/src/Bridge/Brave/.github/PULL_REQUEST_TEMPLATE.md b/src/agent/src/Bridge/Brave/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..55e9c1e26 --- /dev/null +++ b/src/agent/src/Bridge/Brave/.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! \ No newline at end of file diff --git a/src/agent/src/Bridge/Brave/.github/close-pull-request.yml b/src/agent/src/Bridge/Brave/.github/close-pull-request.yml new file mode 100644 index 000000000..bb5a02835 --- /dev/null +++ b/src/agent/src/Bridge/Brave/.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/agent/src/Bridge/Brave/.gitignore b/src/agent/src/Bridge/Brave/.gitignore new file mode 100644 index 000000000..76367ee5b --- /dev/null +++ b/src/agent/src/Bridge/Brave/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +.phpunit.result.cache diff --git a/src/agent/src/Toolbox/Tool/Brave.php b/src/agent/src/Bridge/Brave/Brave.php similarity index 98% rename from src/agent/src/Toolbox/Tool/Brave.php rename to src/agent/src/Bridge/Brave/Brave.php index f0c01d1e0..b43cace62 100644 --- a/src/agent/src/Toolbox/Tool/Brave.php +++ b/src/agent/src/Bridge/Brave/Brave.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\AI\Agent\Toolbox\Tool; +namespace Symfony\AI\Agent\Bridge\Brave; use Symfony\AI\Agent\Toolbox\Attribute\AsTool; use Symfony\AI\Platform\Contract\JsonSchema\Attribute\With; diff --git a/src/agent/src/Bridge/Brave/CHANGELOG.md b/src/agent/src/Bridge/Brave/CHANGELOG.md new file mode 100644 index 000000000..ffc9be05a --- /dev/null +++ b/src/agent/src/Bridge/Brave/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +0.1 +--- + + * Add the bridge \ No newline at end of file diff --git a/src/agent/src/Bridge/Brave/LICENSE b/src/agent/src/Bridge/Brave/LICENSE new file mode 100644 index 000000000..bc38d714e --- /dev/null +++ b/src/agent/src/Bridge/Brave/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/agent/tests/Toolbox/Tool/BraveTest.php b/src/agent/src/Bridge/Brave/Tests/BraveTest.php similarity index 83% rename from src/agent/tests/Toolbox/Tool/BraveTest.php rename to src/agent/src/Bridge/Brave/Tests/BraveTest.php index 90326509b..5927e1ef9 100644 --- a/src/agent/tests/Toolbox/Tool/BraveTest.php +++ b/src/agent/src/Bridge/Brave/Tests/BraveTest.php @@ -9,19 +9,18 @@ * file that was distributed with this source code. */ -namespace Symfony\AI\Agent\Tests\Toolbox\Tool; +namespace Symfony\AI\Agent\Bridge\Brave\Tests; use PHPUnit\Framework\TestCase; -use Symfony\AI\Agent\Toolbox\Tool\Brave; +use Symfony\AI\Agent\Bridge\Brave\Brave; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\JsonMockResponse; -use Symfony\Component\HttpClient\Response\MockResponse; final class BraveTest extends TestCase { public function testReturnsSearchResults() { - $result = JsonMockResponse::fromFile(__DIR__.'/../../fixtures/Tool/brave.json'); + $result = JsonMockResponse::fromFile(__DIR__.'/fixtures/search-results.json'); $httpClient = new MockHttpClient($result); $brave = new Brave($httpClient, 'test-api-key'); @@ -38,7 +37,7 @@ public function testReturnsSearchResults() public function testPassesCorrectParametersToApi() { - $result = JsonMockResponse::fromFile(__DIR__.'/../../fixtures/Tool/brave.json'); + $result = JsonMockResponse::fromFile(__DIR__.'/fixtures/search-results.json'); $httpClient = new MockHttpClient($result); $brave = new Brave($httpClient, 'test-api-key', ['extra' => 'option']); @@ -52,13 +51,13 @@ public function testPassesCorrectParametersToApi() $requestOptions = $result->getRequestOptions(); $this->assertArrayHasKey('headers', $requestOptions); + $this->assertIsArray($requestOptions['headers']); $this->assertContains('X-Subscription-Token: test-api-key', $requestOptions['headers']); } public function testHandlesEmptyResults() { - $result = new MockResponse(json_encode(['web' => ['results' => []]])); - $httpClient = new MockHttpClient($result); + $httpClient = new MockHttpClient(new JsonMockResponse(['web' => ['results' => []]])); $brave = new Brave($httpClient, 'test-api-key'); $results = $brave('this should return nothing'); diff --git a/src/agent/tests/fixtures/Tool/brave.json b/src/agent/src/Bridge/Brave/Tests/fixtures/search-results.json similarity index 100% rename from src/agent/tests/fixtures/Tool/brave.json rename to src/agent/src/Bridge/Brave/Tests/fixtures/search-results.json diff --git a/src/agent/src/Bridge/Brave/composer.json b/src/agent/src/Bridge/Brave/composer.json new file mode 100644 index 000000000..6a7e70481 --- /dev/null +++ b/src/agent/src/Bridge/Brave/composer.json @@ -0,0 +1,53 @@ +{ + "name": "symfony/ai-brave-tool", + "description": "Brave Search AI tool bridge for Symfony applications.", + "license": "MIT", + "type": "library", + "keywords": [ + "ai", + "bridge", + "brave", + "agent", + "tool" + ], + "authors": [ + { + "name": "Christopher Hertel", + "email": "mail@christopher-hertel.de" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + } + ], + "require": { + "php": ">=8.2", + "symfony/ai-agent": "@dev", + "symfony/http-client": "^7.3|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.13" + }, + "autoload": { + "psr-4": { + "Symfony\\AI\\Agent\\Bridge\\Brave\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Symfony\\AI\\Agent\\Bridge\\Brave\\Tests\\": "Tests/" + } + }, + "config": { + "sort-packages": true + }, + "extra": { + "thanks": { + "name": "symfony/ai", + "url": "https://github.com/symfony/ai" + } + }, + "minimum-stability": "dev" +} diff --git a/src/agent/src/Bridge/Brave/phpstan.neon.dist b/src/agent/src/Bridge/Brave/phpstan.neon.dist new file mode 100644 index 000000000..9bf345ac1 --- /dev/null +++ b/src/agent/src/Bridge/Brave/phpstan.neon.dist @@ -0,0 +1,16 @@ +includes: + - ../../../../../.phpstan/extension.neon + +parameters: + level: max + paths: + - . + excludePaths: + - vendor/ + - Tests/fixtures/ + ignoreErrors: + - + message: "#^Method .*::test.*\\(\\) has no return type specified\\.$#" + - + identifier: missingType.iterableValue + path: Tests/* diff --git a/src/agent/src/Bridge/Brave/phpunit.xml.dist b/src/agent/src/Bridge/Brave/phpunit.xml.dist new file mode 100644 index 000000000..53057875f --- /dev/null +++ b/src/agent/src/Bridge/Brave/phpunit.xml.dist @@ -0,0 +1,32 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + +