Skip to content

Commit

Permalink
Merge pull request #19 from llm-agents-php/feature/tool-choise
Browse files Browse the repository at this point in the history
Working with tools
  • Loading branch information
butschster authored Sep 9, 2024
2 parents aa4911a + cf507e9 commit ddccce5
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/AgentExecutor/Interceptor/GeneratePromptInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use LLM\Agents\LLM\AgentPromptGeneratorInterface;
use LLM\Agents\LLM\Prompt\Chat\PromptInterface;

/**
* This interceptor is responsible for generating the prompt for the agent.
*/
final readonly class GeneratePromptInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
Expand Down
3 changes: 3 additions & 0 deletions src/AgentExecutor/Interceptor/InjectModelInterceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\InterceptorHandler;

/**
* This interceptor is responsible for injecting the model name into the execution options.
*/
final readonly class InjectModelInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
Expand Down
75 changes: 75 additions & 0 deletions src/AgentExecutor/Interceptor/ToolExecutorInterceptor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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\Prompt\Chat\ToolCallResultMessage;
use LLM\Agents\LLM\Prompt\Chat\ToolsCallResultResponse;
use LLM\Agents\LLM\Response\ToolCall;
use LLM\Agents\LLM\Response\ToolCalledResponse;
use LLM\Agents\Tool\ToolExecutor;

final readonly class ToolExecutorInterceptor implements ExecutorInterceptorInterface
{
public function __construct(
private ToolExecutor $toolExecutor,
) {}

public function execute(ExecutionInput $input, InterceptorHandler $next): Execution
{
$execution = $next($input);

// Check if we should return the tool result instead of adding it to the prompt.
$shouldReturnToolResult = $input->options->get('return_tool_result', false);

while (true) {
$result = $execution->result;
$prompt = $execution->prompt;

// If the result is a ToolCalledResponse, we need to call the tools.
if ($result instanceof ToolCalledResponse) {
// First, call all tools.
$toolsResponse = [];
foreach ($result->tools as $tool) {
$toolsResponse[] = $this->callTool($tool);
}

// In some cases we want to return the tool result instead of adding it to the prompt.
// We don't need an answer from the LLM, all we wanted is to ask LLM to execute desired tools.
if ($shouldReturnToolResult) {
return new Execution(
result: new ToolsCallResultResponse(results: $toolsResponse),
prompt: $prompt,
);
}

// Then add the tools responses to the prompt.
foreach ($toolsResponse as $toolResponse) {
$input = $input->withPrompt($prompt->withAddedMessage($toolResponse));
}

// Continue to the next execution.
$execution = $next($input);

continue;
}

return $execution;
}
}

private function callTool(ToolCall $tool): ToolCallResultMessage
{
$functionResult = $this->toolExecutor->execute($tool->name, $tool->arguments);

return new ToolCallResultMessage(
id: $tool->id,
content: [$functionResult],
);
}
}
29 changes: 29 additions & 0 deletions src/LLM/Prompt/Chat/ToolsCallResultResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\LLM\Prompt\Chat;

use LLM\Agents\LLM\Response\Response;

final class ToolsCallResultResponse extends Response implements \JsonSerializable
{
/**
* @param array<ToolCallResultMessage> $results
*/
public function __construct(
public array $results,
) {
parent::__construct('');
}

public function jsonSerialize(): array
{
return [
'results' => \array_map(
static fn(ToolCallResultMessage $result): array => $result->toArray(),
$this->results,
),
];
}
}
71 changes: 71 additions & 0 deletions src/Tool/ToolChoice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\Tool;

/**
* In some cases, you may want LLM to use a specific tool to answer the user’s question, even if LLM thinks it can
* provide an answer without using a tool.
*
* This class allows you to instruct LLM to have a specific behavior when it comes to using tools.
*/
readonly class ToolChoice
{
private function __construct(
public ToolChoiceType $type,
public ?string $toolName = null,
) {}

/**
* Let LLM decide which function to call or not to call at all
*/
public static function auto(): self
{
return new self(ToolChoiceType::Auto);
}

/**
* Force LLM to always call one or more functions
*/
public static function any(): self
{
return new self(ToolChoiceType::Any);
}

/**
* Force LLM to call a specific function with the given name
*/
public static function specific(string $toolName): self
{
return new self(ToolChoiceType::Specific, $toolName);
}

/**
* Force LLM not to call any function
*/
public static function none(): self
{
return new self(ToolChoiceType::None);
}

public function isAuto(): bool
{
return $this->type === ToolChoiceType::Auto;
}

public function isAny(): bool
{
return $this->type === ToolChoiceType::Any;
}

public function isSpecific(): bool
{
return $this->type === ToolChoiceType::Specific;
}

public function isNone(): bool
{
return $this->type === ToolChoiceType::None;
}
}
20 changes: 20 additions & 0 deletions src/Tool/ToolChoiceType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace LLM\Agents\Tool;

enum ToolChoiceType
{
// Let LLM decide which function to call or not to call at all
case Auto;

// Force LLM to always call one or more functions
case Any;

// Force LLM to call a specific function with the given name
case Specific;

// Force LLM not to call any function
case None;
}

0 comments on commit ddccce5

Please sign in to comment.