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
30 changes: 22 additions & 8 deletions examples/toolbox/brave.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,37 @@
use Symfony\AI\Agent\Agent;
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\Tool\Clock;
use Symfony\AI\Agent\Toolbox\Tool\Scraper;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\Clock\Clock as SymfonyClock;

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

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());

$brave = new Brave(http_client(), env('BRAVE_API_KEY'));
$crawler = new Crawler(http_client());
$toolbox = new Toolbox([$brave, $crawler], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor]);
$clock = new Clock(new SymfonyClock());
$crawler = new Scraper(http_client());
$toolbox = new Toolbox([$brave, $clock, $crawler], logger: logger());
$processor = new AgentProcessor($toolbox, includeSources: true);
$agent = new Agent($platform, 'gpt-4o', [$processor], [$processor]);

$messages = new MessageBag(Message::ofUser('What was the latest game result of Dallas Cowboys?'));
$result = $agent->call($messages);
$prompt = <<<PROMPT
Summarize the latest game of the Dallas Cowboys. When and where was it? Who was the opponent, what was the result,
and how was the game and the weather in the city. Use tools for the research and only answer based on information
given in the context - don't make up information.
PROMPT;

echo $result->getContent().\PHP_EOL;
$result = $agent->call(new MessageBag(Message::ofUser($prompt)));

echo $result->getContent().\PHP_EOL.\PHP_EOL;

echo 'Used sources:'.\PHP_EOL;
foreach ($result->getMetadata()->get('sources', []) as $source) {
echo sprintf(' - %s (%s)', $source->getName(), $source->getReference()).\PHP_EOL;
}
echo \PHP_EOL;
28 changes: 22 additions & 6 deletions examples/toolbox/serpapi.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,38 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Tool\Clock;
use Symfony\AI\Agent\Toolbox\Tool\Scraper;
use Symfony\AI\Agent\Toolbox\Tool\SerpApi;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\Clock\Clock as SymfonyClock;

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

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());

$clock = new Clock(new SymfonyClock());
$crawler = new Scraper(http_client());
$serpApi = new SerpApi(http_client(), env('SERP_API_KEY'));
$toolbox = new Toolbox([$serpApi], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor]);
$toolbox = new Toolbox([$clock, $crawler, $serpApi], logger: logger());
$processor = new AgentProcessor($toolbox, includeSources: true);
$agent = new Agent($platform, 'gpt-4o', [$processor], [$processor]);

$messages = new MessageBag(Message::ofUser('Who is the current chancellor of Germany?'));
$result = $agent->call($messages);
$prompt = <<<PROMPT
Summarize the latest game of the Dallas Cowboys. When and where was it? Who was the opponent, what was the result,
and how was the game and the weather in the city. Use tools for the research and only answer based on information
given in the context - don't make up information.
PROMPT;

echo $result->getContent().\PHP_EOL;
$result = $agent->call(new MessageBag(Message::ofUser($prompt)));

echo $result->getContent().\PHP_EOL.\PHP_EOL;

echo 'Used sources:'.\PHP_EOL;
foreach ($result->getMetadata()->get('sources', []) as $source) {
echo sprintf(' - %s (%s)', $source->getName(), $source->getReference()).\PHP_EOL;
}
echo \PHP_EOL;
26 changes: 20 additions & 6 deletions examples/toolbox/tavily.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,36 @@

use Symfony\AI\Agent\Agent;
use Symfony\AI\Agent\Toolbox\AgentProcessor;
use Symfony\AI\Agent\Toolbox\Tool\Clock;
use Symfony\AI\Agent\Toolbox\Tool\Tavily;
use Symfony\AI\Agent\Toolbox\Toolbox;
use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\Component\Clock\Clock as SymfonyClock;

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

$platform = PlatformFactory::create(env('OPENAI_API_KEY'), http_client());

$clock = new Clock(new SymfonyClock());
$tavily = new Tavily(http_client(), env('TAVILY_API_KEY'));
$toolbox = new Toolbox([$tavily], logger: logger());
$processor = new AgentProcessor($toolbox);
$agent = new Agent($platform, 'gpt-4o-mini', [$processor], [$processor]);
$toolbox = new Toolbox([$clock, $tavily], logger: logger());
$processor = new AgentProcessor($toolbox, includeSources: true);
$agent = new Agent($platform, 'gpt-4o', [$processor], [$processor]);

$messages = new MessageBag(Message::ofUser('What was the latest game result of Dallas Cowboys?'));
$result = $agent->call($messages);
$prompt = <<<PROMPT
Summarize the latest game of the Dallas Cowboys. When and where was it? Who was the opponent, what was the result,
and how was the game and the weather in the city. Use tools for the research and only answer based on information
given in the context - don't make up information.
PROMPT;

echo $result->getContent().\PHP_EOL;
$result = $agent->call(new MessageBag(Message::ofUser($prompt)));

echo $result->getContent().\PHP_EOL.\PHP_EOL;

echo 'Used sources:'.\PHP_EOL;
foreach ($result->getMetadata()->get('sources', []) as $source) {
echo sprintf(' - %s (%s)', $source->getName(), $source->getReference()).\PHP_EOL;
}
echo \PHP_EOL;
2 changes: 1 addition & 1 deletion src/agent/src/Toolbox/Source/SourceMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\AI\Agent\Toolbox\Source;

class SourceMap
final class SourceMap
{
/**
* @var Source[]
Expand Down
20 changes: 16 additions & 4 deletions src/agent/src/Toolbox/Tool/Brave.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@
namespace Symfony\AI\Agent\Toolbox\Tool;

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

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
#[AsTool('brave_search', 'Tool that searches the web using Brave Search')]
final readonly class Brave
final class Brave implements HasSourcesInterface
{
use HasSourcesTrait;

/**
* @param array<string, mixed> $options See https://api-dashboard.search.brave.com/app/documentation/web-search/query#WebSearchAPIQueryParameters
*/
public function __construct(
private HttpClientInterface $httpClient,
#[\SensitiveParameter] private string $apiKey,
private array $options = [],
private readonly HttpClientInterface $httpClient,
#[\SensitiveParameter] private readonly string $apiKey,
private readonly array $options = [],
) {
}

Expand Down Expand Up @@ -61,6 +66,13 @@ public function __invoke(
]);

$data = $result->toArray();
$results = $data['web']['results'] ?? [];

foreach ($results as $result) {
$this->addSource(
new Source($result['title'] ?? '', $result['url'] ?? '', $result['description'] ?? '')
);
}

return array_map(static function (array $result) {
return ['title' => $result['title'], 'description' => $result['description'], 'url' => $result['url']];
Expand Down
15 changes: 12 additions & 3 deletions src/agent/src/Toolbox/Tool/Clock.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@
namespace Symfony\AI\Agent\Toolbox\Tool;

use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesInterface;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesTrait;
use Symfony\AI\Agent\Toolbox\Source\Source;
use Symfony\Component\Clock\Clock as SymfonyClock;
use Symfony\Component\Clock\ClockInterface;

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
#[AsTool('clock', description: 'Provides the current date and time.')]
final readonly class Clock
final class Clock implements HasSourcesInterface
{
use HasSourcesTrait;

public function __construct(
private ClockInterface $clock = new SymfonyClock(),
private ?string $timezone = null,
private readonly ClockInterface $clock = new SymfonyClock(),
private readonly ?string $timezone = null,
) {
}

Expand All @@ -35,6 +40,10 @@ public function __invoke(): string
$now = $now->setTimezone(new \DateTimeZone($this->timezone));
}

$this->addSource(
new Source('Current Time', 'Clock', $now->format('Y-m-d H:i:s'))
);

return \sprintf(
'Current date is %s (YYYY-MM-DD) and the time is %s (HH:MM:SS).',
$now->format('Y-m-d'),
Expand Down
42 changes: 0 additions & 42 deletions src/agent/src/Toolbox/Tool/Crawler.php

This file was deleted.

58 changes: 58 additions & 0 deletions src/agent/src/Toolbox/Tool/Scraper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?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\Exception\RuntimeException;
use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesInterface;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesTrait;
use Symfony\AI\Agent\Toolbox\Source\Source;
use Symfony\Component\DomCrawler\Crawler as DomCrawler;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
#[AsTool('scraper', 'Loads the visible text and title of a website by URL.')]
final class Scraper implements HasSourcesInterface
{
use HasSourcesTrait;

public function __construct(
private readonly HttpClientInterface $httpClient,
) {
if (!class_exists(DomCrawler::class)) {
throw new RuntimeException('For using the Scraper tool, the symfony/dom-crawler package is required. Try running "composer require symfony/dom-crawler".');
}
}

/**
* @param string $url the URL of the page to load data from
*
* @return array{title: string, content: string}
*/
public function __invoke(string $url): array
{
$result = $this->httpClient->request('GET', $url);
$crawler = new DomCrawler($result->getContent());

$title = $crawler->filter('title')->text();
$content = $crawler->filter('body')->text();

$this->addSource(new Source($title, $url, $content));

return [
'title' => $title,
'content' => $content,
];
}
}
36 changes: 24 additions & 12 deletions src/agent/src/Toolbox/Tool/SerpApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,31 @@
namespace Symfony\AI\Agent\Toolbox\Tool;

use Symfony\AI\Agent\Toolbox\Attribute\AsTool;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesInterface;
use Symfony\AI\Agent\Toolbox\Source\HasSourcesTrait;
use Symfony\AI\Agent\Toolbox\Source\Source;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
#[AsTool(name: 'serpapi', description: 'search for information on the internet')]
final readonly class SerpApi
final class SerpApi implements HasSourcesInterface
{
use HasSourcesTrait;

public function __construct(
private HttpClientInterface $httpClient,
private string $apiKey,
private readonly HttpClientInterface $httpClient,
private readonly string $apiKey,
) {
}

/**
* @param string $query The search query to use
*
* @return array{title: string, link: string, content: string}[]
*/
public function __invoke(string $query): string
public function __invoke(string $query): array
{
$result = $this->httpClient->request('GET', 'https://serpapi.com/search', [
'query' => [
Expand All @@ -38,14 +45,19 @@ public function __invoke(string $query): string
],
]);

return \sprintf('Results for "%s" are "%s".', $query, $this->extractBestResponse($result->toArray()));
}
$data = $result->toArray();

/**
* @param array<string, mixed> $results
*/
private function extractBestResponse(array $results): string
{
return implode('. ', array_map(fn ($story) => $story['title'], $results['organic_results']));
$results = [];
foreach ($data['organic_results'] as $result) {
$results[] = [
'title' => $result['title'],
'link' => $result['link'],
'content' => $result['snippet'],
];

$this->addSource(new Source($result['title'], $result['link'], $result['snippet']));
}

return $results;
}
}
Loading