Skip to content

Commit 4aeca02

Browse files
committed
feature #500 [Agent][Toolbox] Add Mapbox.com geocoding tool for address-to-coordinates conversion (OskarStark)
This PR was squashed before being merged into the main branch. Discussion ---------- [Agent][Toolbox] Add Mapbox.com geocoding tool for address-to-coordinates conversion | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | no | Issues | -- | License | MIT <img width="2228" height="670" alt="CleanShot 2025-09-09 at 09 51 05@2x" src="https://github.com/user-attachments/assets/87bea596-4c5c-4dd5-9c7d-97566b48a857" /> Commits ------- 80b83fd [Agent][Toolbox] Add Mapbox.com geocoding tool for address-to-coordinates conversion
2 parents 86a852a + 80b83fd commit 4aeca02

File tree

13 files changed

+507
-1
lines changed

13 files changed

+507
-1
lines changed

examples/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ BRAVE_API_KEY=
6060
FIRECRAWL_HOST=https://api.firecrawl.dev
6161
FIRECRAWL_API_KEY=
6262

63+
# For using Mapbox (tool)
64+
MAPBOX_ACCESS_TOKEN=
65+
6366
# For using MongoDB Atlas (store)
6467
MONGODB_URI=mongodb://symfony:symfony@127.0.0.1:27017
6568

examples/bootstrap.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
require_once __DIR__.'/vendor/autoload.php';
2424
(new Dotenv())->loadEnv(__DIR__.'/.env');
2525

26-
function env(string $var)
26+
function env(string $var): string
2727
{
2828
if (!isset($_SERVER[$var]) || '' === $_SERVER[$var]) {
2929
printf('Please set the "%s" environment variable to run this example.', $var);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
14+
use Symfony\AI\Agent\Toolbox\Tool\Mapbox;
15+
use Symfony\AI\Agent\Toolbox\Toolbox;
16+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
17+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
18+
use Symfony\AI\Platform\Message\Message;
19+
use Symfony\AI\Platform\Message\MessageBag;
20+
21+
require_once dirname(__DIR__).'/bootstrap.php';
22+
23+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
24+
$model = new Gpt(Gpt::GPT_4O_MINI);
25+
26+
$mapbox = new Mapbox(http_client(), env('MAPBOX_ACCESS_TOKEN'));
27+
$toolbox = new Toolbox([$mapbox], logger: logger());
28+
$processor = new AgentProcessor($toolbox);
29+
$agent = new Agent($platform, $model, [$processor], [$processor], logger());
30+
31+
$messages = new MessageBag(Message::ofUser('What are the coordinates of Brandenburg Gate in Berlin?'));
32+
$result = $agent->call($messages);
33+
34+
echo $result->getContent().\PHP_EOL;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
use Symfony\AI\Agent\Agent;
13+
use Symfony\AI\Agent\Toolbox\AgentProcessor;
14+
use Symfony\AI\Agent\Toolbox\Tool\Mapbox;
15+
use Symfony\AI\Agent\Toolbox\Toolbox;
16+
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
17+
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
18+
use Symfony\AI\Platform\Message\Message;
19+
use Symfony\AI\Platform\Message\MessageBag;
20+
21+
require_once dirname(__DIR__).'/bootstrap.php';
22+
23+
$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());
24+
$model = new Gpt(Gpt::GPT_4O_MINI);
25+
26+
$mapbox = new Mapbox(http_client(), env('MAPBOX_ACCESS_TOKEN'));
27+
$toolbox = new Toolbox([$mapbox], logger: logger());
28+
$processor = new AgentProcessor($toolbox);
29+
$agent = new Agent($platform, $model, [$processor], [$processor], logger());
30+
31+
$messages = new MessageBag(Message::ofUser('What address is at coordinates longitude -73.985131, latitude 40.758895?'));
32+
$result = $agent->call($messages);
33+
34+
echo $result->getContent().\PHP_EOL;

src/agent/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ CHANGELOG
2626
- `Clock` for current date/time
2727
- `Brave` for web search integration
2828
- `Crawler` for web page crawling
29+
- `Mapbox` for geocoding addresses to coordinates and reverse geocoding
2930
- `OpenMeteo` for weather information
3031
- `SerpApi` for search engine results
3132
- `Tavily` for AI-powered search

src/agent/doc/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ messages will be added to your MessageBag::
352352
* `Brave Tool`_
353353
* `Clock Tool`_
354354
* `Crawler Tool`_
355+
* `Mapbox Geocode Tool`_
356+
* `Mapbox Reverse Geocode Tool`_
355357
* `SerpAPI Tool`_
356358
* `Tavily Tool`_
357359
* `Weather Tool with Event Listener`_
@@ -716,6 +718,8 @@ making your tests more reliable and easier to maintain.
716718
.. _`Brave Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
717719
.. _`Clock Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/clock.php
718720
.. _`Crawler Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/brave.php
721+
.. _`Mapbox Geocode Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/mapbox-geocode.php
722+
.. _`Mapbox Reverse Geocode Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/mapbox-reverse-geocode.php
719723
.. _`SerpAPI Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/serpapi.php
720724
.. _`Tavily Tool`: https://github.com/symfony/ai/blob/main/examples/toolbox/tavily.php
721725
.. _`Weather Tool with Event Listener`: https://github.com/symfony/ai/blob/main/examples/toolbox/weather-event.php
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Agent\Toolbox\Tool;
13+
14+
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
15+
use Symfony\AI\Platform\Contract\JsonSchema\Attribute\With;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
18+
/**
19+
* @author Oskar Stark <oskarstark@googlemail.com>
20+
*/
21+
#[AsTool(name: 'geocode', description: 'Convert addresses to coordinates using Mapbox Geocoding API', method: 'geocode')]
22+
#[AsTool(name: 'reverse_geocode', description: 'Convert coordinates to addresses using Mapbox Reverse Geocoding API', method: 'reverseGeocode')]
23+
final readonly class Mapbox
24+
{
25+
public function __construct(
26+
private HttpClientInterface $httpClient,
27+
#[\SensitiveParameter] private string $accessToken,
28+
) {
29+
}
30+
31+
/**
32+
* @param string $address The address to geocode (e.g., "1600 Pennsylvania Ave, Washington DC")
33+
* @param int $limit Maximum number of results to return (1-10)
34+
*
35+
* @return array{
36+
* results: array<array{
37+
* address: string,
38+
* coordinates: array{longitude: float, latitude: float},
39+
* relevance: float,
40+
* place_type: string[]
41+
* }>,
42+
* count: int
43+
* }
44+
*/
45+
public function geocode(
46+
string $address,
47+
#[With(minimum: 1, maximum: 10)]
48+
int $limit = 1,
49+
): array {
50+
$response = $this->httpClient->request('GET', 'https://api.mapbox.com/geocoding/v5/mapbox.places/'.urlencode($address).'.json', [
51+
'query' => [
52+
'access_token' => $this->accessToken,
53+
'limit' => $limit,
54+
],
55+
]);
56+
57+
$data = $response->toArray();
58+
59+
if (!isset($data['features']) || [] === $data['features']) {
60+
return [
61+
'results' => [],
62+
'count' => 0,
63+
];
64+
}
65+
66+
$results = [];
67+
foreach ($data['features'] as $feature) {
68+
$center = $feature['center'] ?? [0.0, 0.0];
69+
$results[] = [
70+
'address' => $feature['place_name'] ?? '',
71+
'coordinates' => [
72+
'longitude' => $center[0] ?? 0.0,
73+
'latitude' => $center[1] ?? 0.0,
74+
],
75+
'relevance' => $feature['relevance'] ?? 0.0,
76+
'place_type' => $feature['place_type'] ?? [],
77+
];
78+
}
79+
80+
return [
81+
'results' => $results,
82+
'count' => \count($results),
83+
];
84+
}
85+
86+
/**
87+
* @param float $longitude The longitude coordinate
88+
* @param float $latitude The latitude coordinate
89+
* @param int $limit Maximum number of results to return (1-5)
90+
*
91+
* @return array{
92+
* results: array<array{
93+
* address: string,
94+
* coordinates: array{longitude: float, latitude: float},
95+
* place_type: string[],
96+
* context: array<array{id: string, text: string}>
97+
* }>,
98+
* count: int
99+
* }
100+
*/
101+
public function reverseGeocode(
102+
float $longitude,
103+
float $latitude,
104+
#[With(minimum: 1, maximum: 5)]
105+
int $limit = 1,
106+
): array {
107+
$response = $this->httpClient->request('GET', 'https://api.mapbox.com/search/geocode/v6/reverse', [
108+
'query' => [
109+
'longitude' => $longitude,
110+
'latitude' => $latitude,
111+
'access_token' => $this->accessToken,
112+
'limit' => $limit,
113+
],
114+
]);
115+
116+
$data = $response->toArray();
117+
118+
if (!isset($data['features']) || [] === $data['features']) {
119+
return [
120+
'results' => [],
121+
'count' => 0,
122+
];
123+
}
124+
125+
$results = [];
126+
foreach ($data['features'] as $feature) {
127+
$properties = $feature['properties'] ?? [];
128+
$coordinates = $properties['coordinates'] ?? [];
129+
130+
$context = [];
131+
if (isset($properties['context'])) {
132+
foreach ($properties['context'] as $key => $contextItem) {
133+
if (\is_array($contextItem) && isset($contextItem['name'])) {
134+
$context[] = [
135+
'id' => $contextItem['id'] ?? $contextItem['mapbox_id'] ?? '',
136+
'text' => $contextItem['name'],
137+
'type' => $key,
138+
];
139+
}
140+
}
141+
}
142+
143+
$results[] = [
144+
'address' => $properties['place_formatted'] ?? $properties['name'] ?? '',
145+
'coordinates' => [
146+
'longitude' => $coordinates['longitude'] ?? 0.0,
147+
'latitude' => $coordinates['latitude'] ?? 0.0,
148+
],
149+
'place_type' => [$properties['feature_type'] ?? 'unknown'],
150+
'context' => $context,
151+
];
152+
}
153+
154+
return [
155+
'results' => $results,
156+
'count' => \count($results),
157+
];
158+
}
159+
}

0 commit comments

Comments
 (0)