Skip to content

Commit 0cf8262

Browse files
committed
Add various tools for enhanced functionality
1 parent b2f0973 commit 0cf8262

File tree

138 files changed

+78068
-1
lines changed

Some content is hidden

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

138 files changed

+78068
-1
lines changed

src/agent/src/Toolbox/Tool/Airtable.php

Lines changed: 502 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.

src/agent/src/Toolbox/Tool/Ansible.php

Lines changed: 428 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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 Mathieu Ledru <[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+
125+
return $this->parseArxivXml($xml);
126+
}
127+
128+
/**
129+
* Search ArXiv by query string.
130+
*
131+
* @return array<int, array{
132+
* id: string,
133+
* title: string,
134+
* authors: array<int, string>,
135+
* summary: string,
136+
* published: string,
137+
* updated: string,
138+
* categories: array<int, string>,
139+
* link: string,
140+
* }>
141+
*/
142+
private function searchByQuery(string $query, int $maxResults, int $start): array
143+
{
144+
$response = $this->httpClient->request('GET', 'http://export.arxiv.org/api/query', [
145+
'query' => array_merge($this->options, [
146+
'search_query' => $query,
147+
'start' => $start,
148+
'max_results' => $maxResults,
149+
'sortBy' => 'relevance',
150+
'sortOrder' => 'descending',
151+
]),
152+
]);
153+
154+
$xml = $response->getContent();
155+
156+
return $this->parseArxivXml($xml);
157+
}
158+
159+
/**
160+
* Parse ArXiv XML response.
161+
*
162+
* @return array<int, array{
163+
* id: string,
164+
* title: string,
165+
* authors: array<int, string>,
166+
* summary: string,
167+
* published: string,
168+
* updated: string,
169+
* categories: array<int, string>,
170+
* link: string,
171+
* }>
172+
*/
173+
private function parseArxivXml(string $xml): array
174+
{
175+
try {
176+
$dom = new \DOMDocument();
177+
$dom->loadXML($xml);
178+
179+
$entries = $dom->getElementsByTagName('entry');
180+
$results = [];
181+
182+
foreach ($entries as $entry) {
183+
$id = $this->getElementText($entry, 'id');
184+
$title = $this->getElementText($entry, 'title');
185+
$summary = $this->getElementText($entry, 'summary');
186+
$published = $this->getElementText($entry, 'published');
187+
$updated = $this->getElementText($entry, 'updated');
188+
189+
// Extract authors
190+
$authors = [];
191+
$authorNodes = $entry->getElementsByTagName('author');
192+
foreach ($authorNodes as $authorNode) {
193+
$name = $this->getElementText($authorNode, 'name');
194+
if ($name) {
195+
$authors[] = $name;
196+
}
197+
}
198+
199+
// Extract categories
200+
$categories = [];
201+
$categoryNodes = $entry->getElementsByTagName('category');
202+
foreach ($categoryNodes as $categoryNode) {
203+
$term = $categoryNode->getAttribute('term');
204+
if ($term) {
205+
$categories[] = $term;
206+
}
207+
}
208+
209+
// Extract link
210+
$link = '';
211+
$linkNodes = $entry->getElementsByTagName('link');
212+
foreach ($linkNodes as $linkNode) {
213+
if ('pdf' === $linkNode->getAttribute('title')) {
214+
$link = $linkNode->getAttribute('href');
215+
break;
216+
}
217+
}
218+
219+
$results[] = [
220+
'id' => $id,
221+
'title' => $title,
222+
'authors' => $authors,
223+
'summary' => trim($summary),
224+
'published' => $published,
225+
'updated' => $updated,
226+
'categories' => $categories,
227+
'link' => $link,
228+
];
229+
}
230+
231+
return $results;
232+
} catch (\Exception $e) {
233+
return [
234+
[
235+
'id' => 'parse_error',
236+
'title' => 'Parse Error',
237+
'authors' => [],
238+
'summary' => 'Unable to parse ArXiv response: '.$e->getMessage(),
239+
'published' => '',
240+
'updated' => '',
241+
'categories' => [],
242+
'link' => '',
243+
],
244+
];
245+
}
246+
}
247+
248+
private function getElementText(\DOMElement $parent, string $tagName): string
249+
{
250+
$elements = $parent->getElementsByTagName($tagName);
251+
if ($elements->length > 0) {
252+
return $elements->item(0)->textContent ?? '';
253+
}
254+
255+
return '';
256+
}
257+
}

0 commit comments

Comments
 (0)