Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds python code executor #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
79 changes: 79 additions & 0 deletions app/src/Agents/Calculator/CalculatorAgent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace App\Agents\Calculator;

use LLM\Agents\Agent\AgentAggregate;
use LLM\Agents\Agent\Agent;
use LLM\Agents\OpenAI\Client\OpenAIModel;
use LLM\Agents\Solution\Model;
use LLM\Agents\Solution\ToolLink;
use LLM\Agents\Solution\MetadataType;
use LLM\Agents\Solution\SolutionMetadata;

// This class represents the Calculator Agent,
// which uses the Tool with uses python language to perform mathematical calculations.
final class CalculatorAgent extends AgentAggregate
{
public const NAME = 'python_calculator_agent';

public static function create(): self
{
$agent = new Agent(
key: self::NAME,
name: 'Python Calculator Agent',
description: 'This agent generates Python code for mathematical calculations. It can handle addition, subtraction, multiplication, division, averaging, and square root operations.',
instruction: <<<'INSTRUCTION'
You are a Python Calculator Agent.
Your primary goal is to assist users in performing arithmetic calculations by generating appropriate Python code.
When a user provides a mathematical expression or asks for a calculation, you should use the python_calculator tool to generate the code.
Always provide the generated Python code along with an explanation of how it solves the user\'s request.
If there are any potential errors or invalid inputs, explain them clearly to the user.
INSTRUCTION
,
);

$aggregate = new self($agent);

$aggregate->addMetadata(
new SolutionMetadata(
type: MetadataType::Memory,
key: 'operation_mapping',
content: "Map user requests to the correct operation: 'add' for addition, 'subtract' for subtraction, 'multiply' for multiplication, 'divide' for division, 'average' for averaging, and 'sqrt' for square root.",
),
new SolutionMetadata(
type: MetadataType::Memory,
key: 'number_formatting',
content: 'Ensure that numbers are correctly formatted as a Python list when passing them to the tool.',
),
new SolutionMetadata(
type: MetadataType::Memory,
key: 'error_handling',
content: 'Be aware of potential errors like division by zero or invalid operations, and explain these to the user if the generated code might raise such errors.',
),
new SolutionMetadata(
type: MetadataType::Prompt,
key: 'example_addition',
content: 'Add 5, 10, and 15',
),
new SolutionMetadata(
type: MetadataType::Prompt,
key: 'example_division',
content: 'Divide 100 by 5 and then by 2',
),
new SolutionMetadata(
type: MetadataType::Prompt,
key: 'example_square_root',
content: 'Calculate the square root of 16',
),
);

$model = new Model(model: OpenAIModel::Gpt4oMini->value);
$aggregate->addAssociation($model);

$aggregate->addAssociation(new ToolLink(name: PythonCalculatorTool::NAME));

return $aggregate;
}
}
21 changes: 21 additions & 0 deletions app/src/Agents/Calculator/PythonCalculatorInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace App\Agents\Calculator;

use Spiral\JsonSchemaGenerator\Attribute\Field;

final class PythonCalculatorInput
{
public function __construct(
#[Field(title: 'Operation', description: 'The mathematical operation to perform (add, subtract, multiply, divide, average, sqrt)')]
public string $operation,

/**
* @var array<int>
*/
#[Field(title: 'Numbers', description: 'An array of numbers to perform the operation on')]
public array $numbers,
) {}
}
55 changes: 55 additions & 0 deletions app/src/Agents/Calculator/PythonCalculatorTool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace App\Agents\Calculator;

use LLM\Agents\Tool\ExecutorAwareInterface;
use LLM\Agents\Tool\ExecutorInterface;
use LLM\Agents\Tool\Tool;
use LLM\Agents\Tool\ToolLanguage;

// This class is a tool that uses a code written in Python to perform mathematical calculations.
final class PythonCalculatorTool extends Tool implements ExecutorAwareInterface
{
public const NAME = 'python_calculator';

private const CODE = <<<'PYTHON'

PYTHON;

private ?ExecutorInterface $executor = null;

public function __construct()
{
parent::__construct(
name: self::NAME,
inputSchema: PythonCalculatorInput::class,
description: 'This tool generates Python code for mathematical calculations.',
);
}

public function getLanguage(): ToolLanguage
{
return ToolLanguage::Python;
}

public function execute(object $input): string
{
if (!$this->executor instanceof ExecutorInterface) {
throw new \RuntimeException('Executor is not set for the tool');
}

return $this->executor->execute(
\sprintf(self::CODE, \json_encode($input)),
$input,
);
}

public function setExecutor(ExecutorInterface $executor): static
{
$this->executor = $executor;

return $this;
}
}
9 changes: 9 additions & 0 deletions app/src/Application/Bootloader/AgentsBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Application\Bootloader;

use App\Application\AgentsLocator;
use App\Application\PythonExecutor;
use App\Application\ToolsLocator;
use App\Domain\Chat\ChatHistoryRepositoryInterface;
use App\Domain\Chat\ChatServiceInterface;
Expand All @@ -21,6 +22,8 @@
use LLM\Agents\OpenAI\Client\ContextFactory;
use LLM\Agents\OpenAI\Client\OptionsFactory;
use LLM\Agents\Tool\SchemaMapperInterface;
use LLM\Agents\Tool\ToolExecutor;
use LLM\Agents\Tool\ToolLanguage;
use LLM\Agents\Tool\ToolRegistry;
use LLM\Agents\Tool\ToolRegistryInterface;
use LLM\Agents\Tool\ToolRepositoryInterface;
Expand All @@ -37,6 +40,8 @@ public function defineSingletons(): array
ToolRegistryInterface::class => ToolRegistry::class,
ToolRepositoryInterface::class => ToolRegistry::class,

ToolExecutor::class => ToolExecutor::class,

AgentRegistry::class => AgentRegistry::class,
AgentRegistryInterface::class => AgentRegistry::class,
AgentRepositoryInterface::class => AgentRegistry::class,
Expand All @@ -62,7 +67,11 @@ public function init(
TokenizerListenerRegistryInterface $listenerRegistry,
ToolsLocator $toolsLocator,
AgentsLocator $agentsLocator,
ToolExecutor $toolExecutor,
PythonExecutor $pythonExecutor,
): void {
$toolExecutor->register(ToolLanguage::Python, $pythonExecutor);

$listenerRegistry->addListener($agentsLocator);
$listenerRegistry->addListener($toolsLocator);
}
Expand Down
38 changes: 38 additions & 0 deletions app/src/Application/PythonExecutor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace App\Application;

use LLM\Agents\Tool\ExecutorInterface;
use Spiral\Boot\DirectoriesInterface;
use Spiral\Files\FilesInterface;

final readonly class PythonExecutor implements ExecutorInterface
{
public function __construct(
private FilesInterface $files,
private DirectoriesInterface $dirs,
private string $pythonPath = 'python3',
) {}

public function execute(string $code, object $input): string|\Stringable
{
$input = \json_encode($input);

$this->files->ensureDirectory($dir = $this->dirs->get('runtime') . 'python');

$dir = \realpath($dir);
$file = $dir . '/' . md5(\microtime()) . '.py';

$this->files->write($file, $code);

try {
$output = \shell_exec(\sprintf('%s %s', $this->pythonPath, $file));
} finally {
$this->files->delete($file);
}

return \trim($output);
}
}