Skip to content

Commit b602f56

Browse files
committed
Add optional translation to system prompts
So that system prompts can be written in any locale
1 parent ff08204 commit b602f56

File tree

7 files changed

+63
-10
lines changed

7 files changed

+63
-10
lines changed

src/agent/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ CHANGELOG
5454
* Add model capability detection before processing
5555
* Add comprehensive type safety with full PHP type hints
5656
* Add clear exception hierarchy for different error scenarios
57+
* Add translation support for system prompts

src/agent/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"symfony/property-access": "^6.4 || ^7.1",
3131
"symfony/property-info": "^6.4 || ^7.1",
3232
"symfony/serializer": "^6.4 || ^7.1",
33+
"symfony/translation": "^6.4 || ^7.1",
3334
"symfony/type-info": "^7.2.3"
3435
},
3536
"require-dev": {

src/agent/src/InputProcessor/SystemPromptInputProcessor.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
1919
use Symfony\AI\Platform\Message\Message;
2020
use Symfony\AI\Platform\Tool\Tool;
21+
use Symfony\Contracts\Translation\TranslatorInterface;
2122

2223
/**
2324
* @author Christopher Hertel <mail@christopher-hertel.de>
@@ -31,6 +32,7 @@
3132
public function __construct(
3233
private \Stringable|string $systemPrompt,
3334
private ?ToolboxInterface $toolbox = null,
35+
private ?TranslatorInterface $translator = null,
3436
private LoggerInterface $logger = new NullLogger(),
3537
) {
3638
}
@@ -45,7 +47,9 @@ public function processInput(Input $input): void
4547
return;
4648
}
4749

48-
$message = (string) $this->systemPrompt;
50+
$message = $this->translator
51+
? $this->translator->trans((string) $this->systemPrompt)
52+
: (string) $this->systemPrompt;
4953

5054
if ($this->toolbox instanceof ToolboxInterface
5155
&& [] !== $this->toolbox->getTools()

src/agent/tests/InputProcessor/SystemPromptInputProcessorTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\AI\Platform\Result\ToolCall;
3030
use Symfony\AI\Platform\Tool\ExecutionReference;
3131
use Symfony\AI\Platform\Tool\Tool;
32+
use Symfony\Contracts\Translation\TranslatorInterface;
3233

3334
#[CoversClass(SystemPromptInputProcessor::class)]
3435
#[UsesClass(Gpt::class)]
@@ -186,4 +187,31 @@ public function execute(ToolCall $toolCall): mixed
186187
A tool without parameters
187188
PROMPT, $messages[0]->content);
188189
}
190+
191+
public function testWithTranslatedSystemPrompt()
192+
{
193+
$processor = new SystemPromptInputProcessor(
194+
systemPrompt: 'This is a',
195+
translator: new class implements TranslatorInterface {
196+
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
197+
{
198+
return \sprintf('%s cool translated system prompt', $id);
199+
}
200+
201+
public function getLocale(): string
202+
{
203+
return 'en';
204+
}
205+
}
206+
);
207+
208+
$input = new Input(new Gpt(), new MessageBag(Message::ofUser('This is a user message')), []);
209+
$processor->processInput($input);
210+
211+
$messages = $input->messages->getMessages();
212+
$this->assertCount(2, $messages);
213+
$this->assertInstanceOf(SystemMessage::class, $messages[0]);
214+
$this->assertInstanceOf(UserMessage::class, $messages[1]);
215+
$this->assertSame('This is a cool translated system prompt', $messages[0]->content);
216+
}
189217
}

src/ai-bundle/config/options.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,25 @@
125125
->end()
126126
->end()
127127
->booleanNode('structured_output')->defaultTrue()->end()
128-
->scalarNode('system_prompt')
129-
->validate()
130-
->ifTrue(fn ($v) => null !== $v && '' === trim($v))
131-
->thenInvalid('The default system prompt must not be an empty string')
128+
->arrayNode('system_prompt')
129+
->beforeNormalization()
130+
->ifString()
131+
->then(fn ($v) => ['system_prompt' => $v])
132+
->end()
133+
->children()
134+
->scalarNode('system_prompt')
135+
->validate()
136+
->ifTrue(fn ($v) => null !== $v && '' === trim($v))
137+
->thenInvalid('The default system prompt must not be an empty string')
138+
->end()
139+
->defaultNull()
140+
->info('The default system prompt of the agent')
141+
->end()
142+
->scalarNode('translator')
143+
->defaultNull()
144+
->info('Service name of translator for the system prompt')
145+
->end()
132146
->end()
133-
->defaultNull()
134-
->info('The default system prompt of the agent')
135147
->end()
136148
->booleanNode('include_tools')
137149
->info('Include tool definitions at the end of the system prompt')

src/ai-bundle/src/AiBundle.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,12 @@ private function processAgentConfig(string $name, array $config, ContainerBuilde
585585
}
586586

587587
// SYSTEM PROMPT
588-
if (\is_string($config['system_prompt'])) {
588+
if (\array_key_exists('system_prompt', $config)) {
589589
$systemPromptInputProcessorDefinition = (new Definition(SystemPromptInputProcessor::class))
590590
->setArguments([
591-
$config['system_prompt'],
591+
$config['system_prompt']['system_prompt'],
592592
$config['include_tools'] ? new Reference('ai.toolbox.'.$name) : null,
593+
$config['system_prompt']['translator'],
593594
new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
594595
])
595596
->addTag('ai.agent.input_processor', ['agent' => $agentId, 'priority' => -30]);

src/ai-bundle/tests/DependencyInjection/AiBundleTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\DependencyInjection\ContainerInterface;
2828
use Symfony\Component\DependencyInjection\Definition;
2929
use Symfony\Component\DependencyInjection\Reference;
30+
use Symfony\Contracts\Translation\TranslatorInterface;
3031

3132
#[CoversClass(AiBundle::class)]
3233
#[UsesClass(ContainerBuilder::class)]
@@ -447,7 +448,10 @@ public function testMultipleAgentsWithProcessors()
447448
'tools' => [
448449
['service' => 'tool_two', 'description' => 'Tool for second agent'],
449450
],
450-
'system_prompt' => 'Second agent prompt',
451+
'system_prompt' => [
452+
'system_prompt' => 'Second agent prompt',
453+
'translator' => TranslatorInterface::class,
454+
],
451455
],
452456
],
453457
],
@@ -471,11 +475,13 @@ public function testMultipleAgentsWithProcessors()
471475
$firstSystemPrompt = $container->getDefinition('ai.agent.first_agent.system_prompt_processor');
472476
$firstSystemTags = $firstSystemPrompt->getTag('ai.agent.input_processor');
473477
$this->assertSame($firstAgentId, $firstSystemTags[0]['agent']);
478+
$this->assertCount(2, array_filter($firstSystemPrompt->getArguments()));
474479

475480
// Second agent system prompt processor
476481
$secondSystemPrompt = $container->getDefinition('ai.agent.second_agent.system_prompt_processor');
477482
$secondSystemTags = $secondSystemPrompt->getTag('ai.agent.input_processor');
478483
$this->assertSame($secondAgentId, $secondSystemTags[0]['agent']);
484+
$this->assertCount(3, array_filter($secondSystemPrompt->getArguments()));
479485
}
480486

481487
#[TestDox('Processors work correctly when using the default toolbox')]

0 commit comments

Comments
 (0)