Skip to content

Commit

Permalink
Adds agent executor with interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
butschster committed Sep 4, 2024
1 parent 1221fb2 commit d7ae416
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/Agent/AgentExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
use LLM\Agents\Tool\ToolInterface;
use LLM\Agents\Tool\ToolRepositoryInterface;

/**
* @deprecated
*/
final readonly class AgentExecutor
{
public function __construct(
Expand Down
10 changes: 10 additions & 0 deletions src/AgentExecutor/Exception/ExecutorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Exception;

class ExecutorException extends \DomainException
{

}
10 changes: 10 additions & 0 deletions src/AgentExecutor/Exception/InvalidPromptException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Exception;

final class InvalidPromptException extends ExecutorException
{

}
23 changes: 23 additions & 0 deletions src/AgentExecutor/ExecutorInterceptorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor;

use LLM\Agents\Agent\Execution;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\PromptContextInterface;

interface ExecutorInterceptorInterface
{
public function execute(
string $agent,
string|\Stringable|Prompt $prompt,
ContextInterface $context,
OptionsInterface $options,
PromptContextInterface $promptContext,
ExecutorInterface $next,
): Execution;
}
25 changes: 25 additions & 0 deletions src/AgentExecutor/ExecutorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor;

use LLM\Agents\Agent\Execution;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\Prompt\Context;
use LLM\Agents\LLM\PromptContextInterface;

interface ExecutorInterface
{
public function execute(
string $agent,
string|\Stringable|Prompt $prompt,
?ContextInterface $context = null,
?OptionsInterface $options = null,
PromptContextInterface $promptContext = new Context(),
): Execution;

public function withInterceptor(ExecutorInterceptorInterface ...$interceptor): self;
}
78 changes: 78 additions & 0 deletions src/AgentExecutor/ExecutorPipeline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor;

use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\Exception\InvalidPromptException;
use LLM\Agents\LLM\ContextFactoryInterface;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\LLMInterface;
use LLM\Agents\LLM\OptionsFactoryInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\Prompt\Context;
use LLM\Agents\LLM\Prompt\PromptInterface;
use LLM\Agents\LLM\PromptContextInterface;

final class ExecutorPipeline implements ExecutorInterface
{
/** @var ExecutorInterceptorInterface[] */
private array $interceptors = [];
private int $offset = 0;

public function __construct(
private readonly LLMInterface $llm,
private readonly OptionsFactoryInterface $optionsFactory,
private readonly ContextFactoryInterface $contextFactory,
) {}

public function execute(
string $agent,
\Stringable|string|Prompt $prompt,
?ContextInterface $context = null,
?OptionsInterface $options = null,
PromptContextInterface $promptContext = new Context(),
): Execution {
$context ??= $this->contextFactory->create();
$options ??= $this->optionsFactory->create();

if (!isset($this->interceptors[$this->offset])) {
if (!$prompt instanceof PromptInterface) {
throw new InvalidPromptException(\sprintf('Prompt must be an instance of %s', PromptInterface::class));
}

$result = $this->llm->generate($context, $prompt, $options);

return new Execution(
result: $result,
prompt: $prompt,
);
}

return $this->interceptors[$this->offset]->execute(
agent: $agent,
prompt: $prompt,
context: $context,
options: $options,
promptContext: $promptContext,
next: $this->next(),
);
}

public function withInterceptor(ExecutorInterceptorInterface ...$interceptor): ExecutorInterface
{
$pipeline = clone $this;
$pipeline->interceptors = \array_merge($this->interceptors, $interceptor);

return $pipeline;
}

private function next(): self
{
$pipeline = clone $this;
$pipeline->offset++;
return $pipeline;
}
}
45 changes: 45 additions & 0 deletions src/AgentExecutor/Interceptor/GeneratePromptInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\AgentPromptGeneratorInterface;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\PromptContextInterface;

final readonly class GeneratePromptInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
private AgentRepositoryInterface $agents,
private AgentPromptGeneratorInterface $promptGenerator,
) {}

public function execute(
string $agent,
\Stringable|string|Prompt $prompt,
ContextInterface $context,
OptionsInterface $options,
PromptContextInterface $promptContext,
ExecutorInterface $next,
): Execution {
if (!$prompt instanceof Prompt) {
$agent = $this->agents->get($agent);
$prompt = $this->promptGenerator->generate($agent, $prompt, $promptContext);
}

return $next->execute(
agent: $agent,
prompt: $prompt,
context: $context,
options: $options,
promptContext: $promptContext,
);
}
}
41 changes: 41 additions & 0 deletions src/AgentExecutor/Interceptor/InjectModelInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\AgentPromptGeneratorInterface;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\PromptContextInterface;

final readonly class InjectModelInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
private AgentRepositoryInterface $agents,
) {}

public function execute(
string $agent,
\Stringable|string|Prompt $prompt,
ContextInterface $context,
OptionsInterface $options,
PromptContextInterface $promptContext,
ExecutorInterface $next,
): Execution {
$agent = $this->agents->get($agent);

return $next->execute(
agent: $agent->getKey(),
prompt: $prompt,
context: $context,
options: $options->with('model', $agent->getModel()->name),
promptContext: $promptContext,
);
}
}
44 changes: 44 additions & 0 deletions src/AgentExecutor/Interceptor/InjectOptionsInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\PromptContextInterface;

final readonly class InjectOptionsInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
private AgentRepositoryInterface $agents,
) {}

public function execute(
string $agent,
\Stringable|string|Prompt $prompt,
ContextInterface $context,
OptionsInterface $options,
PromptContextInterface $promptContext,
ExecutorInterface $next,
): Execution {
$agent = $this->agents->get($agent);

foreach ($agent->getConfiguration() as $configuration) {
$options = $options->with($configuration->key, $configuration->content);
}

return $next->execute(
agent: $agent->getKey(),
prompt: $prompt,
context: $context,
options: $options,
promptContext: $promptContext,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;
use LLM\Agents\LLM\PromptContextInterface;
use LLM\Agents\LLM\Response\ChatResponse;
use LLM\Agents\LLM\Response\ToolCalledResponse;

final class InjectResponseIntoPromptInterceptor implements ExecutorInterceptorInterface
{
public function execute(
string $agent,
\Stringable|string|Prompt $prompt,
ContextInterface $context,
OptionsInterface $options,
PromptContextInterface $promptContext,
ExecutorInterface $next,
): Execution {
$execution = $next->execute(
agent: $agent,
prompt: $prompt,
context: $context,
options: $options,
promptContext: $promptContext,
);

if (
$execution->result instanceof ChatResponse
|| $execution->result instanceof ToolCalledResponse
) {
$prompt = $execution->prompt->withAddedMessage($execution->result->toMessage());
}

return new Execution(
result: $execution->result,
prompt: $prompt,
);
}
}
Loading

0 comments on commit d7ae416

Please sign in to comment.