Skip to content

Commit

Permalink
Merge pull request #9 from llm-agents-php/feature/agent-executor-pipe…
Browse files Browse the repository at this point in the history
…line

Adds agent executor pipeline
  • Loading branch information
butschster authored Sep 4, 2024
2 parents 1221fb2 + 1e1d89e commit ffa332f
Show file tree
Hide file tree
Showing 13 changed files with 437 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
{

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

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor;

use LLM\Agents\LLM\ContextInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\PromptInterface;
use LLM\Agents\LLM\PromptContextInterface;

final readonly class ExecutionInput
{
public function __construct(
public string $agent,
public \Stringable|string|PromptInterface $prompt,
public ContextInterface $context,
public OptionsInterface $options,
public PromptContextInterface $promptContext,
) {}

public function withAgent(string $agent): self
{
return new self(
$agent,
$this->prompt,
$this->context,
$this->options,
$this->promptContext,
);
}

public function withPromptContext(PromptContextInterface $context): self
{
return new self(
$this->agent,
$this->prompt,
$this->context,
$this->options,
$context,
);
}

public function withContext(ContextInterface $context): self
{
return new self(
$this->agent,
$this->prompt,
$context,
$this->options,
$this->promptContext,
);
}

public function withPrompt(PromptInterface $prompt): self
{
return new self(
$this->agent,
$prompt,
$this->context,
$this->options,
$this->promptContext,
);
}

public function withOptions(OptionsInterface $options): self
{
return new self(
$this->agent,
$this->prompt,
$this->context,
$options,
$this->promptContext,
);
}
}
15 changes: 15 additions & 0 deletions src/AgentExecutor/ExecutorInterceptorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor;

use LLM\Agents\Agent\Execution;

interface ExecutorInterceptorInterface
{
public function execute(
ExecutionInput $input,
InterceptorHandler $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;
}
80 changes: 80 additions & 0 deletions src/AgentExecutor/ExecutorPipeline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?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(
input: new ExecutionInput(
agent: $agent,
prompt: $prompt,
context: $context,
options: $options,
promptContext: $promptContext,
),
next: new InterceptorHandler(executor: $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;
}
}
38 changes: 38 additions & 0 deletions src/AgentExecutor/Interceptor/GeneratePromptInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutionInput;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\InterceptorHandler;
use LLM\Agents\LLM\AgentPromptGeneratorInterface;
use LLM\Agents\LLM\Prompt\Chat\Prompt;

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

public function execute(
ExecutionInput $input,
InterceptorHandler $next,
): Execution {
if (!$input->prompt instanceof Prompt) {
$input = $input->withPrompt(
$this->promptGenerator->generate(
$this->agents->get($input->agent),
$input->prompt,
$input->promptContext,
),
);
}

return $next($input);
}
}
31 changes: 31 additions & 0 deletions src/AgentExecutor/Interceptor/InjectModelInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutionInput;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\InterceptorHandler;

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

public function execute(
ExecutionInput $input,
InterceptorHandler $next,
): Execution {
$agent = $this->agents->get($input->agent);

$input = $input->withOptions(
$input->options->with('model', $agent->getModel()->name),
);

return $next($input);
}
}
33 changes: 33 additions & 0 deletions src/AgentExecutor/Interceptor/InjectOptionsInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\AgentRepositoryInterface;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutionInput;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\InterceptorHandler;

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

public function execute(
ExecutionInput $input,
InterceptorHandler $next,
): Execution {
$agent = $this->agents->get($input->agent);

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

return $next($input);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\AgentExecutor\Interceptor;

use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutionInput;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\InterceptorHandler;
use LLM\Agents\LLM\Response\ChatResponse;
use LLM\Agents\LLM\Response\ToolCalledResponse;

final class InjectResponseIntoPromptInterceptor implements ExecutorInterceptorInterface
{
public function execute(
ExecutionInput $input,
InterceptorHandler $next,
): Execution {
$execution = $next($input);

$prompt = $execution->prompt;

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

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

0 comments on commit ffa332f

Please sign in to comment.