Skip to content

Commit 5b9da48

Browse files
authored
Merge pull request #20 from llm-agents-php/feature/output-formatter
Adds an ability to format LLM response into a given format
2 parents 6e2d40f + cf2973f commit 5b9da48

35 files changed

+557
-114
lines changed

src/Agent/AgentExecutor.php

Lines changed: 0 additions & 98 deletions
This file was deleted.

src/Agent/AgentRegistry.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function register(AgentInterface $agent): void
2727

2828
public function get(string $key): AgentInterface
2929
{
30-
if (! $this->has($key)) {
30+
if (!$this->has($key)) {
3131
throw new AgentNotFoundException(\sprintf('Agent with key \'%s\' is not registered.', $key));
3232
}
3333

src/AgentExecutor/ExecutionInput.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use LLM\Agents\LLM\ContextInterface;
88
use LLM\Agents\LLM\OptionsInterface;
9-
use LLM\Agents\LLM\Prompt\Chat\PromptInterface;
9+
use LLM\Agents\LLM\Prompt\PromptInterface;
1010
use LLM\Agents\LLM\PromptContextInterface;
1111

1212
/**

src/AgentExecutor/Interceptor/GeneratePromptInterceptor.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
1111
use LLM\Agents\AgentExecutor\InterceptorHandler;
1212
use LLM\Agents\LLM\AgentPromptGeneratorInterface;
13-
use LLM\Agents\LLM\Prompt\Chat\PromptInterface;
13+
use LLM\Agents\LLM\Prompt\PromptInterface;
1414

1515
/**
1616
* This interceptor is responsible for generating the prompt for the agent.
17+
* If the input does not have a prompt, it will generate one using the prompt generator.
18+
* After the execution, it will remove temporary messages (which implement LLM\Agents\LLM\Prompt\Chat\TempMessageInterface) from the prompt.
1719
*/
1820
final readonly class GeneratePromptInterceptor implements ExecutorInterceptorInterface
1921
{
@@ -36,6 +38,17 @@ public function execute(
3638
);
3739
}
3840

39-
return $next($input);
41+
$execution = $next($input);
42+
43+
// Remove temporary messages from the prompt.
44+
$prompt = $execution->prompt;
45+
if ($prompt instanceof \LLM\Agents\LLM\Prompt\Chat\PromptInterface) {
46+
$prompt = $prompt->withoutTempMessages();
47+
}
48+
49+
return new Execution(
50+
result: $execution->result,
51+
prompt: $prompt,
52+
);
4053
}
4154
}

src/AgentExecutor/Interceptor/InjectOptionsInterceptor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
1111
use LLM\Agents\AgentExecutor\InterceptorHandler;
1212

13+
/**
14+
* This interceptor is responsible for injecting the agent's configuration options into the execution options.
15+
*/
1316
final readonly class InjectOptionsInterceptor implements ExecutorInterceptorInterface
1417
{
1518
public function __construct(

src/AgentExecutor/Interceptor/InjectResponseIntoPromptInterceptor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use LLM\Agents\LLM\Response\ChatResponse;
1212
use LLM\Agents\LLM\Response\ToolCalledResponse;
1313

14+
/**
15+
* This interceptor is responsible for injecting the LLM response into the prompt history.
16+
*/
1417
final class InjectResponseIntoPromptInterceptor implements ExecutorInterceptorInterface
1518
{
1619
public function execute(

src/AgentExecutor/Interceptor/InjectToolsInterceptor.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
use LLM\Agents\Tool\ToolInterface;
1717
use LLM\Agents\Tool\ToolRepositoryInterface;
1818

19+
/**
20+
* This interceptor is responsible for injecting the tools into the prompt if the agent has linked tools.
21+
*/
1922
final readonly class InjectToolsInterceptor implements ExecutorInterceptorInterface
2023
{
2124
public function __construct(
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Agents\AgentExecutor\Interceptor;
6+
7+
use LLM\Agents\Agent\Execution;
8+
use LLM\Agents\AgentExecutor\ExecutionInput;
9+
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
10+
use LLM\Agents\AgentExecutor\InterceptorHandler;
11+
use LLM\Agents\LLM\Exception\FormatterException;
12+
use LLM\Agents\LLM\Output\EnumFormatter;
13+
use LLM\Agents\LLM\Output\FormatterInterface;
14+
use LLM\Agents\LLM\Output\JsonSchemaFormatter;
15+
use LLM\Agents\LLM\Prompt\PromptInterface;
16+
use LLM\Agents\LLM\Response\ChatResponse;
17+
18+
/**
19+
* This interceptor is responsible for formatting the output of the agent based on the provided output formatter.
20+
*/
21+
final readonly class OutputFormatterInterceptor implements ExecutorInterceptorInterface
22+
{
23+
public function __construct(
24+
private JsonSchemaFormatter $jsonSchemaFormatter,
25+
) {}
26+
27+
public function execute(ExecutionInput $input, InterceptorHandler $next): Execution
28+
{
29+
$outputFormatter = $input->options->get('output_formatter');
30+
31+
if ($outputFormatter === null) {
32+
return $next($input);
33+
}
34+
35+
if (!$input->prompt instanceof PromptInterface) {
36+
throw new FormatterException('Prompt must implement PromptInterface');
37+
}
38+
39+
if (!$outputFormatter instanceof FormatterInterface) {
40+
$outputFormatter = $this->createFormatter($outputFormatter);
41+
}
42+
43+
$input = $input->withPrompt(
44+
$input->prompt->withValues(
45+
['output_format_instruction' => $outputFormatter->getInstruction()],
46+
),
47+
);
48+
49+
return $this->formatResponse($next($input), $outputFormatter);
50+
}
51+
52+
private function formatResponse(Execution $execution, FormatterInterface $outputFormatter): Execution
53+
{
54+
$result = $execution->result;
55+
56+
if (!$result instanceof ChatResponse) {
57+
return $execution;
58+
}
59+
60+
return new Execution(
61+
result: new ChatResponse(
62+
content: $outputFormatter->format($result->content),
63+
),
64+
prompt: $execution->prompt,
65+
);
66+
}
67+
68+
/**
69+
* @param non-empty-string|class-string $schema
70+
*/
71+
private function createFormatter(string $schema): FormatterInterface
72+
{
73+
// If the schema is an existing class, check if it is an enum.
74+
if (\class_exists($schema)) {
75+
$refl = new \ReflectionClass($schema);
76+
if ($refl->isEnum()) {
77+
return new EnumFormatter($refl->getName());
78+
}
79+
}
80+
81+
return $this->jsonSchemaFormatter->withJsonSchema($schema);
82+
}
83+
}

src/AgentExecutor/Interceptor/TokenLimitRetryInterceptor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
use LLM\Agents\AgentExecutor\InterceptorHandler;
1111
use LLM\Agents\LLM\Exception\LimitExceededException;
1212

13+
/**
14+
* This interceptor is responsible for retrying the execution if the token limit is exceeded.
15+
* It will increment the limit by a specified step and retry the execution.
16+
*/
1317
final readonly class TokenLimitRetryInterceptor implements ExecutorInterceptorInterface
1418
{
1519
public function __construct(

src/AgentExecutor/Interceptor/ToolExecutorInterceptor.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
use LLM\Agents\LLM\Response\ToolCalledResponse;
1515
use LLM\Agents\Tool\ToolExecutor;
1616

17+
/**
18+
* This interceptor is responsible for calling the tools if LLM asks for it. After calling the tools, it adds the
19+
* tools responses to the prompt history and return the result of tools execution to the LLM.
20+
*
21+
* If the option 'return_tool_result' is set to true, the interceptor will return the tools result instead of adding
22+
* it to the prompt.
23+
*/
1724
final readonly class ToolExecutorInterceptor implements ExecutorInterceptorInterface
1825
{
1926
public function __construct(

0 commit comments

Comments
 (0)