Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions examples/.env
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ BRAVE_API_KEY=
FIRECRAWL_HOST=https://api.firecrawl.dev
FIRECRAWL_API_KEY=

# For using Mapbox (tool)
MAPBOX_ACCESS_TOKEN=

# For using MongoDB Atlas (store)
MONGODB_URI=mongodb://symfony:symfony@127.0.0.1:27017

Expand Down
2 changes: 1 addition & 1 deletion examples/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
require_once __DIR__.'/vendor/autoload.php';
(new Dotenv())->loadEnv(__DIR__.'/.env');

function env(string $var)
function env(string $var): string
{
if (!isset($_SERVER[$var])) {
printf('Please set the "%s" environment variable to run this example.', $var);
Expand Down
34 changes: 34 additions & 0 deletions examples/toolbox/mapbox-geocode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Tool\Mapbox;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$model = new Gpt(Gpt::GPT_4O_MINI);

$mapbox = new Mapbox(http_client(), env('MAPBOX_ACCESS_TOKEN'));
$toolbox = new Toolbox([$mapbox], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, $model, [$processor], [$processor], logger());

$messages = new MessageBag(Message::ofUser('What are the coordinates of Brandenburg Gate in Berlin?'));
$result = $agent->call($messages);

echo $result->getContent().\PHP_EOL;
34 changes: 34 additions & 0 deletions examples/toolbox/mapbox-reverse-geocode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Tool\Mapbox;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

require_once dirname(__DIR__).'/bootstrap.php';

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
$model = new Gpt(Gpt::GPT_4O_MINI);

$mapbox = new Mapbox(http_client(), env('MAPBOX_ACCESS_TOKEN'));
$toolbox = new Toolbox([$mapbox], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, $model, [$processor], [$processor], logger());

$messages = new MessageBag(Message::ofUser('What address is at coordinates longitude -73.985131, latitude 40.758895?'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those example coordinates result in an "couldn't retrieve" response for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for me, but I can't find a coordinate which brings up a real address. shall we remove the reverse thing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's actually with your example as well that it brings back the right address in New York, but the array handling of the response data in Symfony\AI\Agent\Toolbox\Tool\Mapbox::reverseGeocode is wrong.

$result = $agent->call($messages);

echo $result->getContent().\PHP_EOL;
1 change: 1 addition & 0 deletions src/agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CHANGELOG
- `Clock` for current date/time
- `Brave` for web search integration
- `Crawler` for web page crawling
- `Mapbox` for geocoding addresses to coordinates and reverse geocoding
- `OpenMeteo` for weather information
- `SerpApi` for search engine results
- `Tavily` for AI-powered search
Expand Down
4 changes: 4 additions & 0 deletions src/agent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ messages will be added to your MessageBag::
* `Brave Tool`_
* `Clock Tool`_
* `Crawler Tool`_
* `Mapbox Geocode Tool`_
* `Mapbox Reverse Geocode Tool`_
* `SerpAPI Tool`_
* `Tavily Tool`_
* `Weather Tool with Event Listener`_
Expand Down Expand Up @@ -623,6 +625,8 @@ useful when certain interactions shouldn't be influenced by the memory context::
.. _`Brave Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
.. _`Clock Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/clock.php
.. _`Crawler Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
.. _`Mapbox Geocode Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/mapbox-geocode.php
.. _`Mapbox Reverse Geocode Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/mapbox-reverse-geocode.php
.. _`SerpAPI Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/serpapi.php
.. _`Tavily Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/tavily.php
.. _`Weather Tool with Event Listener`: https://github.com/symfony/ai/blob/main/examples/toolbox/weather-event.php
Expand Down
159 changes: 159 additions & 0 deletions src/agent/src/Toolbox/Tool/Mapbox.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\AI\Agent\Toolbox\Tool;

use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
use Symfony\AI\Platform\Contract\JsonSchema\Attribute\With;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* @author Oskar Stark <oskarstark@googlemail.com>
*/
#[AsTool(name: 'geocode', description: 'Convert addresses to coordinates using Mapbox Geocoding API', method: 'geocode')]
#[AsTool(name: 'reverse_geocode', description: 'Convert coordinates to addresses using Mapbox Reverse Geocoding API', method: 'reverseGeocode')]
final readonly class Mapbox
{
public function __construct(
private HttpClientInterface $httpClient,
#[\SensitiveParameter] private string $accessToken,
) {
}

/**
* @param string $address The address to geocode (e.g., "1600 Pennsylvania Ave, Washington DC")
* @param int $limit Maximum number of results to return (1-10)
*
* @return array{
* results: array<array{
* address: string,
* coordinates: array{longitude: float, latitude: float},
* relevance: float,
* place_type: string[]
* }>,
* count: int
* }
*/
public function geocode(
string $address,
#[With(minimum: 1, maximum: 10)]
int $limit = 1,
): array {
$response = $this->httpClient->request('GET', 'https://api.mapbox.com/geocoding/v5/mapbox.places/'.urlencode($address).'.json', [
'query' => [
'access_token' => $this->accessToken,
'limit' => $limit,
],
]);

$data = $response->toArray();

if (!isset($data['features']) || [] === $data['features']) {
return [
'results' => [],
'count' => 0,
];
}

$results = [];
foreach ($data['features'] as $feature) {
$center = $feature['center'] ?? [0.0, 0.0];
$results[] = [
'address' => $feature['place_name'] ?? '',
'coordinates' => [
'longitude' => $center[0] ?? 0.0,
'latitude' => $center[1] ?? 0.0,
],
'relevance' => $feature['relevance'] ?? 0.0,
'place_type' => $feature['place_type'] ?? [],
];
}

return [
'results' => $results,
'count' => \count($results),
];
}

/**
* @param float $longitude The longitude coordinate
* @param float $latitude The latitude coordinate
* @param int $limit Maximum number of results to return (1-5)
*
* @return array{
* results: array<array{
* address: string,
* coordinates: array{longitude: float, latitude: float},
* place_type: string[],
* context: array<array{id: string, text: string}>
* }>,
* count: int
* }
*/
public function reverseGeocode(
float $longitude,
float $latitude,
#[With(minimum: 1, maximum: 5)]
int $limit = 1,
): array {
$response = $this->httpClient->request('GET', 'https://api.mapbox.com/search/geocode/v6/reverse', [
'query' => [
'longitude' => $longitude,
'latitude' => $latitude,
'access_token' => $this->accessToken,
'limit' => $limit,
],
]);

$data = $response->toArray();

if (!isset($data['features']) || [] === $data['features']) {
return [
'results' => [],
'count' => 0,
];
}

$results = [];
foreach ($data['features'] as $feature) {
$properties = $feature['properties'] ?? [];
$coordinates = $properties['coordinates'] ?? [];

$context = [];
if (isset($properties['context'])) {
foreach ($properties['context'] as $key => $contextItem) {
if (\is_array($contextItem) && isset($contextItem['name'])) {
$context[] = [
'id' => $contextItem['id'] ?? $contextItem['mapbox_id'] ?? '',
'text' => $contextItem['name'],
'type' => $key,
];
}
}
}

$results[] = [
'address' => $properties['place_formatted'] ?? $properties['name'] ?? '',
'coordinates' => [
'longitude' => $coordinates['longitude'] ?? 0.0,
'latitude' => $coordinates['latitude'] ?? 0.0,
],
'place_type' => [$properties['feature_type'] ?? 'unknown'],
'context' => $context,
];
}

return [
'results' => $results,
'count' => \count($results),
];
}
}
Loading