Skip to content

Commit 4de8d4f

Browse files
committed
feat(agent): add various tools for enhanced functionality
1 parent b2f0973 commit 4de8d4f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+17738
-0
lines changed

src/agent/src/Toolbox/Tool/AmadeusTravel.php

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Christopher Hertel <[email protected]>
20+
*/
21+
#[AsTool('arxiv_search', 'Tool that searches the ArXiv API for scientific papers')]
22+
final readonly class ArXiv
23+
{
24+
/**
25+
* @param array<string, mixed> $options Additional search options
26+
*/
27+
public function __construct(
28+
private HttpClientInterface $httpClient,
29+
private array $options = [],
30+
) {
31+
}
32+
33+
/**
34+
* @param string $query search query to look up on ArXiv
35+
* @param int $maxResults The maximum number of results to return
36+
* @param int $start The starting position for results (for pagination)
37+
*
38+
* @return array<int, array{
39+
* id: string,
40+
* title: string,
41+
* authors: array<int, string>,
42+
* summary: string,
43+
* published: string,
44+
* updated: string,
45+
* categories: array<int, string>,
46+
* link: string,
47+
* }>
48+
*/
49+
public function __invoke(
50+
#[With(maximum: 500)]
51+
string $query,
52+
int $maxResults = 10,
53+
#[With(minimum: 0)]
54+
int $start = 0,
55+
): array {
56+
try {
57+
// Check if query is an ArXiv identifier
58+
if ($this->isArxivIdentifier($query)) {
59+
return $this->searchById($query);
60+
}
61+
62+
return $this->searchByQuery($query, $maxResults, $start);
63+
} catch (\Exception $e) {
64+
return [
65+
[
66+
'id' => 'error',
67+
'title' => 'Search Error',
68+
'authors' => [],
69+
'summary' => 'Unable to perform ArXiv search: ' . $e->getMessage(),
70+
'published' => '',
71+
'updated' => '',
72+
'categories' => [],
73+
'link' => '',
74+
],
75+
];
76+
}
77+
}
78+
79+
/**
80+
* Check if a query is an ArXiv identifier
81+
*/
82+
private function isArxivIdentifier(string $query): bool
83+
{
84+
// ArXiv identifier patterns
85+
$patterns = [
86+
'/^\d{4}\.\d{4,5}(v\d+)?$/', // YYMM.NNNN or YYMM.NNNNvN
87+
'/^\d{7}\.\d+$/', // YYMMNNN.N
88+
'/^[a-z-]+\/\d{7}$/', // category/YYMMNNN
89+
];
90+
91+
foreach ($patterns as $pattern) {
92+
if (preg_match($pattern, $query)) {
93+
return true;
94+
}
95+
}
96+
97+
return false;
98+
}
99+
100+
/**
101+
* Search ArXiv by specific paper ID
102+
*
103+
* @return array<int, array{
104+
* id: string,
105+
* title: string,
106+
* authors: array<int, string>,
107+
* summary: string,
108+
* published: string,
109+
* updated: string,
110+
* categories: array<int, string>,
111+
* link: string,
112+
* }>
113+
*/
114+
private function searchById(string $id): array
115+
{
116+
$response = $this->httpClient->request('GET', 'http://export.arxiv.org/api/query', [
117+
'query' => [
118+
'id_list' => $id,
119+
'max_results' => 1,
120+
],
121+
]);
122+
123+
$xml = $response->getContent();
124+
return $this->parseArxivXml($xml);
125+
}
126+
127+
/**
128+
* Search ArXiv by query string
129+
*
130+
* @return array<int, array{
131+
* id: string,
132+
* title: string,
133+
* authors: array<int, string>,
134+
* summary: string,
135+
* published: string,
136+
* updated: string,
137+
* categories: array<int, string>,
138+
* link: string,
139+
* }>
140+
*/
141+
private function searchByQuery(string $query, int $maxResults, int $start): array
142+
{
143+
$response = $this->httpClient->request('GET', 'http://export.arxiv.org/api/query', [
144+
'query' => array_merge($this->options, [
145+
'search_query' => $query,
146+
'start' => $start,
147+
'max_results' => $maxResults,
148+
'sortBy' => 'relevance',
149+
'sortOrder' => 'descending',
150+
]),
151+
]);
152+
153+
$xml = $response->getContent();
154+
return $this->parseArxivXml($xml);
155+
}
156+
157+
/**
158+
* Parse ArXiv XML response
159+
*
160+
* @return array<int, array{
161+
* id: string,
162+
* title: string,
163+
* authors: array<int, string>,
164+
* summary: string,
165+
* published: string,
166+
* updated: string,
167+
* categories: array<int, string>,
168+
* link: string,
169+
* }>
170+
*/
171+
private function parseArxivXml(string $xml): array
172+
{
173+
try {
174+
$dom = new \DOMDocument();
175+
$dom->loadXML($xml);
176+
177+
$entries = $dom->getElementsByTagName('entry');
178+
$results = [];
179+
180+
foreach ($entries as $entry) {
181+
$id = $this->getElementText($entry, 'id');
182+
$title = $this->getElementText($entry, 'title');
183+
$summary = $this->getElementText($entry, 'summary');
184+
$published = $this->getElementText($entry, 'published');
185+
$updated = $this->getElementText($entry, 'updated');
186+
187+
// Extract authors
188+
$authors = [];
189+
$authorNodes = $entry->getElementsByTagName('author');
190+
foreach ($authorNodes as $authorNode) {
191+
$name = $this->getElementText($authorNode, 'name');
192+
if ($name) {
193+
$authors[] = $name;
194+
}
195+
}
196+
197+
// Extract categories
198+
$categories = [];
199+
$categoryNodes = $entry->getElementsByTagName('category');
200+
foreach ($categoryNodes as $categoryNode) {
201+
$term = $categoryNode->getAttribute('term');
202+
if ($term) {
203+
$categories[] = $term;
204+
}
205+
}
206+
207+
// Extract link
208+
$link = '';
209+
$linkNodes = $entry->getElementsByTagName('link');
210+
foreach ($linkNodes as $linkNode) {
211+
if ($linkNode->getAttribute('title') === 'pdf') {
212+
$link = $linkNode->getAttribute('href');
213+
break;
214+
}
215+
}
216+
217+
$results[] = [
218+
'id' => $id,
219+
'title' => $title,
220+
'authors' => $authors,
221+
'summary' => trim($summary),
222+
'published' => $published,
223+
'updated' => $updated,
224+
'categories' => $categories,
225+
'link' => $link,
226+
];
227+
}
228+
229+
return $results;
230+
} catch (\Exception $e) {
231+
return [
232+
[
233+
'id' => 'parse_error',
234+
'title' => 'Parse Error',
235+
'authors' => [],
236+
'summary' => 'Unable to parse ArXiv response: ' . $e->getMessage(),
237+
'published' => '',
238+
'updated' => '',
239+
'categories' => [],
240+
'link' => '',
241+
],
242+
];
243+
}
244+
}
245+
246+
private function getElementText(\DOMElement $parent, string $tagName): string
247+
{
248+
$elements = $parent->getElementsByTagName($tagName);
249+
if ($elements->length > 0) {
250+
return $elements->item(0)->textContent ?? '';
251+
}
252+
253+
return '';
254+
}
255+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Christopher Hertel <[email protected]>
20+
*/
21+
#[AsTool('bing_search', 'Tool that searches the web using Bing Search API')]
22+
#[AsTool('bing_search_results_json', 'Tool that searches Bing and returns structured JSON results', method: 'searchJson')]
23+
final readonly class BingSearch
24+
{
25+
/**
26+
* @param array<string, mixed> $options Additional search options
27+
*/
28+
public function __construct(
29+
private HttpClientInterface $httpClient,
30+
#[\SensitiveParameter] private string $apiKey,
31+
private array $options = [],
32+
) {
33+
}
34+
35+
/**
36+
* @param string $query the search query term
37+
* @param int $count The number of search results returned in response.
38+
* Combine this parameter with offset to paginate search results.
39+
* @param int $offset The number of search results to skip before returning results.
40+
* In order to paginate results use this parameter together with count.
41+
*
42+
* @return array<int, array{
43+
* title: string,
44+
* snippet: string,
45+
* url: string,
46+
* }>
47+
*/
48+
public function __invoke(
49+
#[With(maximum: 500)]
50+
string $query,
51+
int $count = 10,
52+
#[With(minimum: 0, maximum: 9)]
53+
int $offset = 0,
54+
): array {
55+
try {
56+
$response = $this->httpClient->request('GET', 'https://api.bing.microsoft.com/v7.0/search', [
57+
'headers' => [
58+
'Ocp-Apim-Subscription-Key' => $this->apiKey,
59+
],
60+
'query' => array_merge($this->options, [
61+
'q' => $query,
62+
'count' => $count,
63+
'offset' => $offset,
64+
'mkt' => 'en-US',
65+
'safesearch' => 'Moderate',
66+
]),
67+
]);
68+
69+
$data = $response->toArray();
70+
71+
$results = [];
72+
foreach ($data['webPages']['value'] ?? [] as $result) {
73+
$results[] = [
74+
'title' => $result['name'] ?? '',
75+
'snippet' => $result['snippet'] ?? '',
76+
'url' => $result['url'] ?? '',
77+
];
78+
}
79+
80+
return $results;
81+
} catch (\Exception $e) {
82+
return [
83+
[
84+
'title' => 'Search Error',
85+
'snippet' => 'Unable to perform search: ' . $e->getMessage(),
86+
'url' => '',
87+
],
88+
];
89+
}
90+
}
91+
92+
/**
93+
* @param string $query the search query term
94+
* @param int $numResults The number of search results to return
95+
*
96+
* @return array<int, array{
97+
* title: string,
98+
* snippet: string,
99+
* url: string,
100+
* }>
101+
*/
102+
public function searchJson(
103+
#[With(maximum: 500)]
104+
string $query,
105+
int $numResults = 4,
106+
): array {
107+
return $this->__invoke($query, $numResults);
108+
}
109+
}

0 commit comments

Comments
 (0)